diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 5665469..26dba17 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,33 +16,43 @@ class UserPreferencesService { static const _hapticsKey = "haptics"; static const _themeTypeKey = "themeType"; + static const _primaryColorKey = "primaryColor"; static const _dynamicColorKey = "dynamicColor"; final SharedPreferences _sharedPreferences; UserPreferencesService(this._sharedPreferences); - IsoValue get iso => isoValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_isoKey) ?? 100)); + IsoValue get iso => + isoValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_isoKey) ?? 100)); set iso(IsoValue value) => _sharedPreferences.setInt(_isoKey, value.value); - NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0)); + NdValue get ndFilter => + ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0)); set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value); - EvSourceType get evSourceType => EvSourceType.values[_sharedPreferences.getInt(_evSourceTypeKey) ?? 0]; + EvSourceType get evSourceType => + EvSourceType.values[_sharedPreferences.getInt(_evSourceTypeKey) ?? 0]; set evSourceType(EvSourceType value) => _sharedPreferences.setInt(_evSourceTypeKey, value.index); bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? false; set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value); double get cameraEvCalibration => _sharedPreferences.getDouble(_cameraEvCalibrationKey) ?? 0.0; - set cameraEvCalibration(double value) => _sharedPreferences.setDouble(_cameraEvCalibrationKey, value); + set cameraEvCalibration(double value) => + _sharedPreferences.setDouble(_cameraEvCalibrationKey, value); - double get lightSensorEvCalibration => _sharedPreferences.getDouble(_lightSensorEvCalibrationKey) ?? 0.0; - set lightSensorEvCalibration(double value) => _sharedPreferences.setDouble(_lightSensorEvCalibrationKey, value); + double get lightSensorEvCalibration => + _sharedPreferences.getDouble(_lightSensorEvCalibrationKey) ?? 0.0; + set lightSensorEvCalibration(double value) => + _sharedPreferences.setDouble(_lightSensorEvCalibrationKey, value); 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..8caae68 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -20,6 +20,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 +48,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 +99,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,6 +172,7 @@ class _ThemeDataProvider extends StatelessWidget { return ThemeData( useMaterial3: true, brightness: scheme.brightness, + primaryColor: primaryColor, colorScheme: scheme, dialogBackgroundColor: scheme.surface, dialogTheme: DialogTheme(backgroundColor: scheme.surface), diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart index cb8aff0..c5657ab 100644 --- a/lib/res/dimens.dart +++ b/lib/res/dimens.dart @@ -13,6 +13,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; 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 index d89130d..5bed4c5 100644 --- 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 @@ -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 MeteringSecondaryButton extends StatelessWidget { final IconData icon; 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..1691b72 --- /dev/null +++ b/lib/screens/settings/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart @@ -0,0 +1,104 @@ +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; + + @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), + contentPadding: EdgeInsets.zero, + content: SizedBox( + height: Dimens.grid48, + width: double.maxFinite, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), + 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/screen_settings.dart b/lib/screens/settings/screen_settings.dart index 6bfd1d7..5c25852 100644 --- a/lib/screens/settings/screen_settings.dart +++ b/lib/screens/settings/screen_settings.dart @@ -4,6 +4,7 @@ import 'package:lightmeter/res/dimens.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'; @@ -65,6 +66,7 @@ class SettingsScreen extends StatelessWidget { title: S.of(context).theme, children: const [ ThemeTypeListTile(), + PrimaryColorListTile(), DynamicColorListTile(), ], ), 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