diff --git a/lib/main.dart b/lib/main.dart index fbb122ea..3b400e8b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -176,6 +176,21 @@ class IslandApp extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = ref.watch(themeProvider); + final settings = ref.watch(appSettingsNotifierProvider); + + // Convert string theme mode to ThemeMode enum + ThemeMode getThemeMode() { + final themeMode = settings.themeMode ?? 'system'; + switch (themeMode) { + case 'light': + return ThemeMode.light; + case 'dark': + return ThemeMode.dark; + case 'system': + default: + return ThemeMode.system; + } + } void handleMessage(RemoteMessage notification) { if (notification.data['meta']?['action_uri'] != null) { @@ -249,7 +264,7 @@ class IslandApp extends HookConsumerWidget { color: Colors.transparent, theme: theme?.light, darkTheme: theme?.dark, - themeMode: ThemeMode.system, + themeMode: getThemeMode(), routerConfig: router, supportedLocales: context.supportedLocales, scrollBehavior: AppScrollBehavior(), diff --git a/lib/pods/config.dart b/lib/pods/config.dart index 7f08db69..09960cde 100644 --- a/lib/pods/config.dart +++ b/lib/pods/config.dart @@ -29,6 +29,7 @@ const kAppWindowOpacity = 'app_window_opacity'; const kAppEnterToSend = 'app_enter_to_send'; const kAppDefaultPoolId = 'app_default_pool_id'; const kAppMessageDisplayStyle = 'app_message_display_style'; +const kAppThemeMode = 'app_theme_mode'; const kFeaturedPostsCollapsedId = 'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post @@ -72,6 +73,7 @@ sealed class AppSettings with _$AppSettings { required double windowOpacity, // The window opacity for desktop platforms required String? defaultPoolId, required String messageDisplayStyle, + required String? themeMode, }) = _AppSettings; } @@ -94,6 +96,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { windowOpacity: prefs.getDouble(kAppWindowOpacity) ?? 1.0, defaultPoolId: prefs.getString(kAppDefaultPoolId), messageDisplayStyle: prefs.getString(kAppMessageDisplayStyle) ?? 'bubble', + themeMode: prefs.getString(kAppThemeMode) ?? 'system', ); } @@ -207,6 +210,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { state = state.copyWith(windowOpacity: value); Future(() => windowManager.setOpacity(value)); } + + void setThemeMode(String value) { + final prefs = ref.read(sharedPreferencesProvider); + prefs.setString(kAppThemeMode, value); + state = state.copyWith(themeMode: value); + } } final updateInfoProvider = diff --git a/lib/pods/config.freezed.dart b/lib/pods/config.freezed.dart index 4872c93e..773c18de 100644 --- a/lib/pods/config.freezed.dart +++ b/lib/pods/config.freezed.dart @@ -17,7 +17,7 @@ mixin _$AppSettings { bool get autoTranslate; bool get dataSavingMode; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type Size? get windowSize;// The window size for desktop platforms double get windowOpacity;// The window opacity for desktop platforms - String? get defaultPoolId; String get messageDisplayStyle; + String? get defaultPoolId; String get messageDisplayStyle; String? get themeMode; /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $AppSettingsCopyWith get copyWith => _$AppSettingsCopyWithImpl Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle); +int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,windowOpacity,defaultPoolId,messageDisplayStyle,themeMode); @override String toString() { - return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)'; + return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, windowOpacity: $windowOpacity, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle, themeMode: $themeMode)'; } @@ -48,7 +48,7 @@ abstract mixin class $AppSettingsCopyWith<$Res> { factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; @useResult $Res call({ - bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle + bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle, String? themeMode }); @@ -63,9 +63,7 @@ class _$AppSettingsCopyWithImpl<$Res> final AppSettings _self; final $Res Function(AppSettings) _then; -/// Create a copy of AppSettings -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = null,}) { return _then(_self.copyWith( autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable @@ -215,7 +213,7 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha class _AppSettings implements AppSettings { - const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.windowOpacity, required this.defaultPoolId, required this.messageDisplayStyle}); + const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.windowOpacity, required this.defaultPoolId, required this.messageDisplayStyle, required this.themeMode}); @override final bool autoTranslate; @@ -234,6 +232,7 @@ class _AppSettings implements AppSettings { // The window opacity for desktop platforms @override final String? defaultPoolId; @override final String messageDisplayStyle; +@override final String? themeMode; /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. @@ -265,7 +264,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; @override @useResult $Res call({ - bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle + bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, double windowOpacity, String? defaultPoolId, String messageDisplayStyle, String? themeMode }); @@ -282,7 +281,7 @@ class __$AppSettingsCopyWithImpl<$Res> /// Create a copy of AppSettings /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? windowOpacity = null,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,Object? themeMode = null,}) { return _then(_AppSettings( autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable @@ -297,6 +296,7 @@ as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ign as Size?,windowOpacity: null == windowOpacity ? _self.windowOpacity : windowOpacity // ignore: cast_nullable_to_non_nullable as double,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable +as String,themeMode: null == themeMode ? _self.themeMode : themeMode // ignore: cast_nullable_to_non_nullable as String, )); } diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 619cca66..a721e2dc 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -92,6 +92,48 @@ class SettingsScreen extends HookConsumerWidget { ), ), + // Theme mode settings + ListTile( + minLeadingWidth: 48, + title: Text('settingsThemeMode').tr(), + contentPadding: const EdgeInsets.only(left: 24, right: 17), + leading: const Icon(Symbols.dark_mode), + trailing: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + items: [ + DropdownMenuItem( + value: 'system', + child: Text('settingsThemeModeSystem').tr().fontSize(14), + ), + DropdownMenuItem( + value: 'light', + child: Text('settingsThemeModeLight').tr().fontSize(14), + ), + DropdownMenuItem( + value: 'dark', + child: Text('settingsThemeModeDark').tr().fontSize(14), + ), + ], + value: settings.themeMode, + onChanged: (String? value) { + if (value != null) { + ref + .read(appSettingsNotifierProvider.notifier) + .setThemeMode(value); + showSnackBar('settingsApplied'.tr()); + } + }, + buttonStyleData: const ButtonStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5), + height: 40, + width: 140, + ), + menuItemStyleData: const MenuItemStyleData(height: 40), + ), + ), + ), + // Custom fonts settings ListTile( isThreeLine: true,