diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f096e2..97cf9fc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,6 @@ }, "dart.lineLength": 100, "[dart]": { - "editor.formatOnSave": true, - "editor.formatOnType": true, "editor.rulers": [ 100, 120, diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 5665469..6d39a13 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'models/ev_source_type.dart'; @@ -15,6 +16,7 @@ class UserPreferencesService { static const _hapticsKey = "haptics"; static const _themeTypeKey = "themeType"; + static const _primaryColorKey = "primaryColor"; static const _dynamicColorKey = "dynamicColor"; final SharedPreferences _sharedPreferences; @@ -42,6 +44,9 @@ class UserPreferencesService { ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0]; set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index); + Color get primaryColor => Color(_sharedPreferences.getInt(_primaryColorKey) ?? 0xff2196f3); + set primaryColor(Color value) => _sharedPreferences.setInt(_primaryColorKey, value.value); + bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false; set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e7c3eb8..7046d31 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -36,10 +36,12 @@ "haptics": "Haptics", "theme": "Theme", "chooseTheme": "Choose theme", - "dynamicColor": "Dynamic color", "themeLight": "Light", "themeDark": "Dark", "themeSystemDefault": "System default", + "dynamicColor": "Dynamic color", + "primaryColor": "Primary color", + "choosePrimaryColor": "Choose primary color", "about": "About", "sourceCode": "Source code", "reportIssue": "Report an issue", diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index e41d71b..c8cf228 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -4,6 +4,7 @@ import 'package:flutter/scheduler.dart'; import 'package:lightmeter/data/models/dynamic_colors_state.dart'; import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/res/dimens.dart'; import 'package:material_color_utilities/material_color_utilities.dart'; import 'package:provider/provider.dart'; @@ -20,6 +21,25 @@ class ThemeProvider extends StatefulWidget { return context.findAncestorStateOfType()!; } + static const primaryColorsList = [ + Color(0xfff44336), + Color(0xffe91e63), + Color(0xff9c27b0), + Color(0xff673ab7), + Color(0xff3f51b5), + Color(0xff2196f3), + Color(0xff03a9f4), + Color(0xff00bcd4), + Color(0xff009688), + Color(0xff4caf50), + Color(0xff8bc34a), + Color(0xffcddc39), + Color(0xffffeb3b), + Color(0xffffc107), + Color(0xffff9800), + Color(0xffff5722), + ]; + @override State createState() => ThemeProviderState(); } @@ -29,7 +49,7 @@ class ThemeProviderState extends State { late final _themeTypeNotifier = ValueNotifier(_prefs.themeType); late final _dynamicColorNotifier = ValueNotifier(_prefs.dynamicColor); - late final _primaryColorNotifier = ValueNotifier(const Color(0xFF2196f3)); + late final _primaryColorNotifier = ValueNotifier(_prefs.primaryColor); @override void dispose() { @@ -80,6 +100,11 @@ class ThemeProviderState extends State { } } + void setPrimaryColor(Color color) { + _primaryColorNotifier.value = color; + _prefs.primaryColor = color; + } + void enableDynamicColor(bool enable) { _dynamicColorNotifier.value = enable; _prefs.dynamicColor = enable; @@ -148,9 +173,33 @@ class _ThemeDataProvider extends StatelessWidget { return ThemeData( useMaterial3: true, brightness: scheme.brightness, + primaryColor: primaryColor, colorScheme: scheme, + appBarTheme: AppBarTheme( + elevation: 4, + color: scheme.surface, + surfaceTintColor: scheme.surfaceTint, + ), + cardTheme: CardTheme( + clipBehavior: Clip.antiAlias, + color: scheme.surface, + elevation: 4, + margin: EdgeInsets.zero, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)), + surfaceTintColor: scheme.surfaceTint, + ), dialogBackgroundColor: scheme.surface, - dialogTheme: DialogTheme(backgroundColor: scheme.surface), + dialogTheme: DialogTheme( + backgroundColor: scheme.surface, + surfaceTintColor: scheme.surfaceTint, + elevation: 6, + ), + listTileTheme: ListTileThemeData( + style: ListTileStyle.list, + iconColor: scheme.onSurface, + textColor: scheme.onSurface, + ), scaffoldBackgroundColor: scheme.surface, ); } diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart index cb8aff0..45756e2 100644 --- a/lib/res/dimens.dart +++ b/lib/res/dimens.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + /// `valueM` represents the base value. /// All other values differs by 8dp. class Dimens { @@ -13,6 +15,7 @@ class Dimens { static const double grid56 = 56; static const double grid168 = 168; + static const double paddingS = 8; static const double paddingM = 16; static const double paddingL = 24; @@ -31,4 +34,8 @@ class Dimens { static const double cameraSliderTrackRadius = cameraSliderTrackHeight / 2; static const double cameraSliderHandleSize = 32; static const double cameraSliderHandleIconSize = cameraSliderHandleSize * 2 / 3; + + // Dialog + // Taken from `Dialog` documentation + static const EdgeInsets dialogMargin = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0); } diff --git a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart index 438f640..083c8d3 100644 --- a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart +++ b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart'; +import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart'; class MeteringMeasureButton extends StatefulWidget { final double size; diff --git a/lib/screens/metering/components/bottom_controls/components/secondary_button/widget_button_secondary.dart b/lib/screens/metering/components/bottom_controls/components/secondary_button/widget_button_secondary.dart deleted file mode 100644 index d89130d..0000000 --- a/lib/screens/metering/components/bottom_controls/components/secondary_button/widget_button_secondary.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart'; - -class MeteringSecondaryButton extends StatelessWidget { - final IconData icon; - final VoidCallback onPressed; - - const MeteringSecondaryButton({ - required this.icon, - required this.onPressed, - super.key, - }); - - @override - Widget build(BuildContext context) { - return Center( - child: FilledCircle( - color: Theme.of(context).colorScheme.surfaceVariant, - size: Dimens.grid48, - child: Center( - child: IconButton( - onPressed: onPressed, - color: Theme.of(context).colorScheme.onSurface, - icon: Icon(icon), - ), - ), - ), - ); - } -} diff --git a/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart new file mode 100644 index 0000000..fe88167 --- /dev/null +++ b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/res/dimens.dart'; + +import 'widget_bottom_controls.dart'; + +class MeteringBottomControlsProvider extends StatelessWidget { + final VoidCallback? onSwitchEvSourceType; + final VoidCallback onMeasure; + final VoidCallback onSettings; + + const MeteringBottomControlsProvider({ + required this.onSwitchEvSourceType, + required this.onMeasure, + required this.onSettings, + super.key, + }); + + @override + Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + return IconButtonTheme( + data: IconButtonThemeData( + style: ButtonStyle( + backgroundColor: MaterialStatePropertyAll(scheme.surface), + elevation: const MaterialStatePropertyAll(4), + iconColor: MaterialStatePropertyAll(scheme.onSurface), + shadowColor: const MaterialStatePropertyAll(Colors.transparent), + surfaceTintColor: MaterialStatePropertyAll(scheme.surfaceTint), + fixedSize: const MaterialStatePropertyAll(Size(Dimens.grid48, Dimens.grid48)), + ), + ), + child: MeteringBottomControls( + onSwitchEvSourceType: onSwitchEvSourceType, + onMeasure: onMeasure, + onSettings: onSettings, + ), + ); + } +} diff --git a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart index 5f89cea..9f3f0a2 100644 --- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart +++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart @@ -4,7 +4,6 @@ import 'package:lightmeter/res/dimens.dart'; import 'package:provider/provider.dart'; import 'components/measure_button/widget_button_measure.dart'; -import 'components/secondary_button/widget_button_secondary.dart'; class MeteringBottomControls extends StatelessWidget { final VoidCallback? onSwitchEvSourceType; @@ -36,11 +35,13 @@ class MeteringBottomControls extends StatelessWidget { children: [ if (onSwitchEvSourceType != null) Expanded( - child: MeteringSecondaryButton( - onPressed: onSwitchEvSourceType!, - icon: context.watch() != EvSourceType.camera - ? Icons.camera_rear - : Icons.wb_incandescent, + child: Center( + child: IconButton( + onPressed: onSwitchEvSourceType, + icon: Icon(context.watch() != EvSourceType.camera + ? Icons.camera_rear + : Icons.wb_incandescent), + ), ), ) else @@ -49,9 +50,11 @@ class MeteringBottomControls extends StatelessWidget { onTap: onMeasure, ), Expanded( - child: MeteringSecondaryButton( - onPressed: onSettings, - icon: Icons.settings, + child: Center( + child: IconButton( + onPressed: onSettings, + icon: const Icon(Icons.settings), + ), ), ), ], diff --git a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart index 820e933..d2eac90 100644 --- a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart +++ b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart @@ -35,6 +35,8 @@ class AnimatedDialogState extends State with SingleTickerProvide late final Animation _borderRadiusAnimation; late final Animation _closedOpacityAnimation; late final Animation _openedOpacityAnimation; + late final Animation _foregroundColorAnimation; + late final Animation _elevationAnimation; bool _isDialogShown = false; @@ -91,8 +93,10 @@ class AnimatedDialogState extends State with SingleTickerProvide begin: _closedSize, end: widget.openedSize ?? Size( - mediaQuery.size.width - mediaQuery.padding.horizontal - Dimens.paddingM * 2, - mediaQuery.size.height - mediaQuery.padding.vertical - Dimens.paddingM * 2, + mediaQuery.size.width - + mediaQuery.padding.horizontal - + Dimens.dialogMargin.horizontal, + mediaQuery.size.height - mediaQuery.padding.vertical - Dimens.dialogMargin.vertical, ), ); _sizeAnimation = _sizeTween.animate(_defaultCurvedAnimation); @@ -112,6 +116,20 @@ class AnimatedDialogState extends State with SingleTickerProvide }); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _foregroundColorAnimation = ColorTween( + begin: Theme.of(context).colorScheme.primaryContainer, + end: Theme.of(context).colorScheme.surface, + ).animate(_defaultCurvedAnimation); + + _elevationAnimation = Tween( + begin: 0, + end: Theme.of(context).dialogTheme.elevation!, + ).animate(_defaultCurvedAnimation); + } + @override void dispose() { _animationController.dispose(); @@ -125,13 +143,7 @@ class AnimatedDialogState extends State with SingleTickerProvide onTap: _openDialog, child: Opacity( opacity: _isDialogShown ? 0 : 1, - child: ClipRRect( - borderRadius: BorderRadius.circular(Dimens.borderRadiusM), - child: ColoredBox( - color: Theme.of(context).colorScheme.primaryContainer, - child: widget.child ?? widget.closedChild, - ), - ), + child: widget.child ?? widget.closedChild, ), ); } @@ -151,6 +163,8 @@ class AnimatedDialogState extends State with SingleTickerProvide sizeAnimation: _sizeAnimation, offsetAnimation: _offsetAnimation, borderRadiusAnimation: _borderRadiusAnimation, + foregroundColorAnimation: _foregroundColorAnimation, + elevationAnimation: _elevationAnimation, onDismiss: close, builder: widget.closedChild != null && widget.openedChild != null ? (_) => _AnimatedSwitcher( @@ -195,6 +209,8 @@ class _AnimatedOverlay extends StatelessWidget { final Animation sizeAnimation; final Animation offsetAnimation; final Animation borderRadiusAnimation; + final Animation foregroundColorAnimation; + final Animation elevationAnimation; final VoidCallback onDismiss; final Widget? child; final Widget Function(BuildContext context)? builder; @@ -205,6 +221,8 @@ class _AnimatedOverlay extends StatelessWidget { required this.sizeAnimation, required this.offsetAnimation, required this.borderRadiusAnimation, + required this.foregroundColorAnimation, + required this.elevationAnimation, required this.onDismiss, this.child, this.builder, @@ -236,7 +254,9 @@ class _AnimatedOverlay extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(borderRadiusAnimation.value), child: Material( - color: Theme.of(context).colorScheme.primaryContainer, + elevation: elevationAnimation.value, + surfaceTintColor: Theme.of(context).colorScheme.surfaceTint, + color: foregroundColorAnimation.value, child: builder?.call(context) ?? child, ), ), diff --git a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/photography_value_picker_dialog/widget_dialog_picker_photography_value.dart b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/photography_value_picker_dialog/widget_dialog_picker_photography_value.dart index 3cb04bc..66cd15b 100644 --- a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/photography_value_picker_dialog/widget_dialog_picker_photography_value.dart +++ b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/photography_value_picker_dialog/widget_dialog_picker_photography_value.dart @@ -57,37 +57,34 @@ class _PhotographyValuePickerDialogState return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - ColoredBox( - color: Theme.of(context).colorScheme.primaryContainer, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB( - Dimens.paddingL, - Dimens.paddingL, - Dimens.paddingL, - Dimens.paddingM, - ), - child: Column( - children: [ - Text( - widget.title, - style: Theme.of(context).textTheme.headlineSmall!, - ), - const SizedBox(height: Dimens.grid16), - Text( - widget.subtitle, - style: Theme.of(context).textTheme.bodyMedium!, - ), - ], - ), + Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB( + Dimens.paddingL, + Dimens.paddingL, + Dimens.paddingL, + Dimens.paddingM, ), - Divider( - color: Theme.of(context).colorScheme.onPrimaryContainer, - height: 0, + child: Column( + children: [ + Text( + widget.title, + style: Theme.of(context).textTheme.headlineSmall!, + ), + const SizedBox(height: Dimens.grid16), + Text( + widget.subtitle, + style: Theme.of(context).textTheme.bodyMedium!, + ), + ], ), - ], - ), + ), + Divider( + color: Theme.of(context).colorScheme.onSurface, + height: 0, + ), + ], ), Expanded( child: ListView.builder( @@ -117,35 +114,32 @@ class _PhotographyValuePickerDialogState ), ), ), - ColoredBox( - color: Theme.of(context).colorScheme.primaryContainer, - child: Column( - children: [ - Divider( - color: Theme.of(context).colorScheme.onPrimaryContainer, - height: 0, + Column( + children: [ + Divider( + color: Theme.of(context).colorScheme.onSurface, + height: 0, + ), + Padding( + padding: const EdgeInsets.all(Dimens.paddingL), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + const Spacer(), + TextButton( + onPressed: widget.onCancel, + child: Text(S.of(context).cancel), + ), + const SizedBox(width: Dimens.grid16), + TextButton( + onPressed: () => widget.onSelect(_selectedValue), + child: Text(S.of(context).select), + ), + ], ), - Padding( - padding: const EdgeInsets.all(Dimens.paddingL), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.max, - children: [ - const Spacer(), - TextButton( - onPressed: widget.onCancel, - child: Text(S.of(context).cancel), - ), - const SizedBox(width: Dimens.grid16), - TextButton( - onPressed: () => widget.onSelect(_selectedValue), - child: Text(S.of(context).select), - ), - ], - ), - ), - ], - ), + ), + ], ), ], ); diff --git a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart index b47817d..9815034 100644 --- a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart +++ b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart @@ -119,7 +119,7 @@ class _NdValueTile extends StatelessWidget { onChanged: onChanged, closedChild: ReadingValueContainer.singleValue( value: ReadingValue( - label: S.of(context).iso, + label: S.of(context).nd, value: value.value.toString(), ), ), diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index 2dba320..465b5bb 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -5,7 +5,7 @@ import 'package:lightmeter/data/models/photography_values/photography_value.dart import 'package:lightmeter/environment.dart'; import 'package:lightmeter/providers/ev_source_type_provider.dart'; -import 'components/bottom_controls/widget_bottom_controls.dart'; +import 'components/bottom_controls/provider_bottom_controls.dart'; import 'components/camera_container/provider_container_camera.dart'; import 'components/light_sensor_container/provider_container_light_sensor.dart'; import 'bloc_metering.dart'; @@ -57,7 +57,7 @@ class _MeteringScreenState extends State { ), ), ), - MeteringBottomControls( + MeteringBottomControlsProvider( onSwitchEvSourceType: context.read().hasLightSensor ? EvSourceTypeProvider.of(context).toggleType : null, diff --git a/lib/screens/settings/components/dynamic_color/widget_list_tile_dynamic_color.dart b/lib/screens/settings/components/dynamic_color/widget_list_tile_dynamic_color.dart index 9f9e82d..db8e7f0 100644 --- a/lib/screens/settings/components/dynamic_color/widget_list_tile_dynamic_color.dart +++ b/lib/screens/settings/components/dynamic_color/widget_list_tile_dynamic_color.dart @@ -9,20 +9,6 @@ class DynamicColorListTile extends StatelessWidget { @override Widget build(BuildContext context) { - if (context.read() == DynamicColorState.unavailable) { - return Opacity( - opacity: 0.5, - child: IgnorePointer( - child: SwitchListTile( - secondary: const Icon(Icons.colorize), - title: Text(S.of(context).dynamicColor), - value: false, - enableFeedback: false, - onChanged: (value) {}, - ), - ), - ); - } return SwitchListTile( secondary: const Icon(Icons.colorize), title: Text(S.of(context).dynamicColor), diff --git a/lib/screens/settings/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart b/lib/screens/settings/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart new file mode 100644 index 0000000..cab9007 --- /dev/null +++ b/lib/screens/settings/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/theme_provider.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart'; + +class PrimaryColorDialogPicker extends StatefulWidget { + const PrimaryColorDialogPicker({super.key}); + + @override + State createState() => _PrimaryColorDialogPickerState(); +} + +class _PrimaryColorDialogPickerState extends State { + late Color _selected = Theme.of(context).primaryColor; + late final ScrollController _scrollController = ScrollController( + initialScrollOffset: + ThemeProvider.primaryColorsList.indexOf(_selected) * (Dimens.grid48 + Dimens.grid8), + ); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + titlePadding: const EdgeInsets.fromLTRB( + Dimens.paddingL, + Dimens.paddingL, + Dimens.paddingL, + Dimens.paddingM, + ), + title: Text(S.of(context).choosePrimaryColor), + content: SizedBox( + height: Dimens.grid48, + width: double.maxFinite, + child: ListView.separated( + controller: _scrollController, + scrollDirection: Axis.horizontal, + padding: EdgeInsets.zero, + separatorBuilder: (_, __) => const SizedBox(width: Dimens.grid8), + itemCount: ThemeProvider.primaryColorsList.length, + itemBuilder: (_, index) { + final color = ThemeProvider.primaryColorsList[index]; + return _SelectableColorItem( + color: color, + selected: color.value == _selected.value, + onTap: () { + setState(() { + _selected = color; + }); + }, + ); + }, + ), + ), + actionsPadding: const EdgeInsets.fromLTRB( + Dimens.paddingL, + Dimens.paddingM, + Dimens.paddingL, + Dimens.paddingL, + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(S.of(context).cancel), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(_selected); + }, + child: Text(S.of(context).save), + ), + ], + ); + } +} + +class _SelectableColorItem extends StatelessWidget { + final Color color; + final bool selected; + final VoidCallback onTap; + + _SelectableColorItem({ + required this.color, + required this.selected, + required this.onTap, + }) : super(key: ValueKey(color)); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: FilledCircle( + size: Dimens.grid48, + color: color, + child: AnimatedSwitcher( + duration: Dimens.durationS, + child: selected + ? Icon( + Icons.check, + color: ThemeData.estimateBrightnessForColor(color) == Brightness.light + ? Colors.black + : Colors.white, + ) + : null, + ), + ), + ); + } +} diff --git a/lib/screens/settings/components/primary_color/widget_list_tile_primary_color.dart b/lib/screens/settings/components/primary_color/widget_list_tile_primary_color.dart new file mode 100644 index 0000000..2ed527b --- /dev/null +++ b/lib/screens/settings/components/primary_color/widget_list_tile_primary_color.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/data/models/dynamic_colors_state.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/theme_provider.dart'; + +import 'components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart'; + +class PrimaryColorListTile extends StatelessWidget { + const PrimaryColorListTile({super.key}); + + @override + Widget build(BuildContext context) { + if (context.watch() == DynamicColorState.enabled) { + return Opacity( + opacity: 0.5, + child: IgnorePointer( + child: ListTile( + leading: const Icon(Icons.palette), + title: Text(S.of(context).primaryColor), + ), + ), + ); + } + return ListTile( + leading: const Icon(Icons.palette), + title: Text(S.of(context).primaryColor), + onTap: () { + showDialog( + context: context, + builder: (_) => const PrimaryColorDialogPicker(), + ).then((value) { + if (value != null) { + ThemeProvider.of(context).setPrimaryColor(value); + } + }); + }, + ); + } +} diff --git a/lib/screens/settings/components/shared/settings_section/widget_settings_section.dart b/lib/screens/settings/components/shared/settings_section/widget_settings_section.dart index 134dbdd..3bce4d8 100644 --- a/lib/screens/settings/components/shared/settings_section/widget_settings_section.dart +++ b/lib/screens/settings/components/shared/settings_section/widget_settings_section.dart @@ -20,10 +20,7 @@ class SettingsSection extends StatelessWidget { Dimens.paddingM, Dimens.paddingM, ), - child: Material( - clipBehavior: Clip.antiAlias, - borderRadius: BorderRadius.circular(Dimens.borderRadiusL), - color: Theme.of(context).colorScheme.primaryContainer, + child: Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM), child: Column( @@ -34,7 +31,10 @@ class SettingsSection extends StatelessWidget { padding: const EdgeInsets.only(left: Dimens.paddingM), child: Text( title, - style: Theme.of(context).textTheme.labelLarge, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(color: Theme.of(context).colorScheme.onSurface), ), ), ...children, diff --git a/lib/screens/settings/screen_settings.dart b/lib/screens/settings/screen_settings.dart index 6bfd1d7..32714a5 100644 --- a/lib/screens/settings/screen_settings.dart +++ b/lib/screens/settings/screen_settings.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/dynamic_colors_state.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; +import 'package:provider/provider.dart'; import 'components/calibration/widget_list_tile_calibration.dart'; import 'components/haptics/provider_list_tile_haptics.dart'; +import 'components/primary_color/widget_list_tile_primary_color.dart'; import 'components/report_issue/widget_list_tile_report_issue.dart'; import 'components/shared/settings_section/widget_settings_section.dart'; import 'components/source_code/widget_list_tile_source_code.dart'; @@ -63,9 +66,11 @@ class SettingsScreen extends StatelessWidget { ), SettingsSection( title: S.of(context).theme, - children: const [ - ThemeTypeListTile(), - DynamicColorListTile(), + children: [ + const ThemeTypeListTile(), + const PrimaryColorListTile(), + if (context.read() != DynamicColorState.unavailable) + const DynamicColorListTile(), ], ), SettingsSection( diff --git a/lib/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart b/lib/screens/shared/filled_circle/widget_circle_filled.dart similarity index 100% rename from lib/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart rename to lib/screens/shared/filled_circle/widget_circle_filled.dart