diff --git a/lib/core/app/app_view.dart b/lib/core/app/app_view.dart index 27554541..08549b2a 100644 --- a/lib/core/app/app_view.dart +++ b/lib/core/app/app_view.dart @@ -13,7 +13,7 @@ class AppView extends HookConsumerWidget with PresLogger { @override Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(routerProvider); - final locale = ref.watch(localeProvider).locale; + final locale = ref.watch(localeNotifierProvider).locale; final theme = ref.watch(themeProvider); ref.watch(commonControllersProvider); diff --git a/lib/core/core_providers.dart b/lib/core/core_providers.dart index dfe91c72..41fb3753 100644 --- a/lib/core/core_providers.dart +++ b/lib/core/core_providers.dart @@ -5,11 +5,11 @@ part 'core_providers.g.dart'; @Riverpod(keepAlive: true) TranslationsEn translations(TranslationsRef ref) => - ref.watch(localeProvider).translations(); + ref.watch(localeNotifierProvider).translations(); @Riverpod(keepAlive: true) AppTheme theme(ThemeRef ref) => AppTheme( - ref.watch(themeModeProvider), - ref.watch(trueBlackThemeProvider), - ref.watch(localeProvider).preferredFontFamily, + ref.watch(themeModeNotifierProvider), + ref.watch(trueBlackThemeNotifierProvider), + ref.watch(localeNotifierProvider).preferredFontFamily, ); diff --git a/lib/core/prefs/locale_prefs.dart b/lib/core/prefs/locale_prefs.dart index f61944bb..fa599113 100644 --- a/lib/core/prefs/locale_prefs.dart +++ b/lib/core/prefs/locale_prefs.dart @@ -1,17 +1,28 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/widgets.dart'; +import 'package:hiddify/data/data_providers.dart'; import 'package:hiddify/gen/fonts.gen.dart'; import 'package:hiddify/gen/translations.g.dart'; import 'package:hiddify/utils/pref_notifier.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; export 'package:hiddify/gen/translations.g.dart'; -final localeProvider = AlwaysAlivePrefNotifier.provider( - "locale", - AppLocale.en, - mapFrom: AppLocale.values.byName, - mapTo: (value) => value.name, -); +part 'locale_prefs.g.dart'; + +@Riverpod(keepAlive: true) +class LocaleNotifier extends _$LocaleNotifier { + late final _pref = + Pref(ref.watch(sharedPreferencesProvider), "locale", AppLocale.en); + + @override + AppLocale build() => _pref.getValue(); + + Future update(AppLocale value) { + state = value; + return _pref.update(value); + } +} enum AppLocale { en, diff --git a/lib/core/prefs/theme_prefs.dart b/lib/core/prefs/theme_prefs.dart index 055a4852..8a0eca45 100644 --- a/lib/core/prefs/theme_prefs.dart +++ b/lib/core/prefs/theme_prefs.dart @@ -1,14 +1,39 @@ import 'package:flutter/material.dart'; +import 'package:hiddify/data/data_providers.dart'; import 'package:hiddify/utils/pref_notifier.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final themeModeProvider = AlwaysAlivePrefNotifier.provider( - "theme_mode", - ThemeMode.system, - mapFrom: ThemeMode.values.byName, - mapTo: (value) => value.name, -); +part 'theme_prefs.g.dart'; -final trueBlackThemeProvider = AlwaysAlivePrefNotifier.provider( - "true_black_theme", - false, -); +@Riverpod(keepAlive: true) +class ThemeModeNotifier extends _$ThemeModeNotifier { + late final _pref = Pref( + ref.watch(sharedPreferencesProvider), + "theme_mode", + ThemeMode.system, + mapFrom: ThemeMode.values.byName, + mapTo: (value) => value.name, + ); + + @override + ThemeMode build() => _pref.getValue(); + + Future update(ThemeMode value) { + state = value; + return _pref.update(value); + } +} + +@Riverpod(keepAlive: true) +class TrueBlackThemeNotifier extends _$TrueBlackThemeNotifier { + late final _pref = + Pref(ref.watch(sharedPreferencesProvider), "true_black_theme", false); + + @override + bool build() => _pref.getValue(); + + Future update(bool value) { + state = value; + return _pref.update(value); + } +} diff --git a/lib/features/settings/widgets/general_setting_tiles.dart b/lib/features/settings/widgets/general_setting_tiles.dart index 3c9aaa6d..c8716f61 100644 --- a/lib/features/settings/widgets/general_setting_tiles.dart +++ b/lib/features/settings/widgets/general_setting_tiles.dart @@ -14,7 +14,7 @@ class GeneralSettingTiles extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); - final locale = ref.watch(localeProvider); + final locale = ref.watch(localeNotifierProvider); final theme = ref.watch(themeProvider); @@ -51,7 +51,9 @@ class GeneralSettingTiles extends HookConsumerWidget { }, ); if (selectedLocale != null) { - await ref.read(localeProvider.notifier).update(selectedLocale); + await ref + .read(localeNotifierProvider.notifier) + .update(selectedLocale); } }, ), @@ -66,11 +68,11 @@ class GeneralSettingTiles extends HookConsumerWidget { ), trailing: ThemeModeSwitch( themeMode: theme.mode, - onChanged: ref.read(themeModeProvider.notifier).update, + onChanged: ref.read(themeModeNotifierProvider.notifier).update, ), leading: const Icon(Icons.light_mode), onTap: () async { - await ref.read(themeModeProvider.notifier).update( + await ref.read(themeModeNotifierProvider.notifier).update( Theme.of(context).brightness == Brightness.light ? ThemeMode.dark : ThemeMode.light, @@ -80,7 +82,7 @@ class GeneralSettingTiles extends HookConsumerWidget { SwitchListTile( title: Text(t.settings.general.trueBlack), value: theme.trueBlack, - onChanged: ref.read(trueBlackThemeProvider.notifier).update, + onChanged: ref.read(trueBlackThemeNotifierProvider.notifier).update, ), if (PlatformUtils.isDesktop) ...[ SwitchListTile( diff --git a/lib/utils/pref_notifier.dart b/lib/utils/pref_notifier.dart index cd03f92b..6daccc7a 100644 --- a/lib/utils/pref_notifier.dart +++ b/lib/utils/pref_notifier.dart @@ -3,6 +3,61 @@ import 'package:hiddify/utils/custom_loggers.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; +class Pref with InfraLogger { + const Pref( + this.prefs, + this.key, + this.defaultValue, { + this.mapFrom, + this.mapTo, + }); + + final SharedPreferences prefs; + final String key; + final T defaultValue; + final T Function(String value)? mapFrom; + final String Function(T value)? mapTo; + + /// Updates the value asynchronously. + Future update(T value) async { + loggy.debug("updating preference [$key] to [$value]"); + try { + if (mapTo != null && mapFrom != null) { + await prefs.setString(key, mapTo!(value)); + } else { + switch (value) { + case String _: + await prefs.setString(key, value); + case bool _: + await prefs.setBool(key, value); + case int _: + await prefs.setInt(key, value); + case double _: + await prefs.setDouble(key, value); + case List _: + await prefs.setStringList(key, value); + } + } + } catch (e) { + loggy.warning("error updating preference[$key]: $e"); + } + } + + T getValue() { + try { + loggy.debug("getting persisted preference [$key]"); + if (mapTo != null && mapFrom != null) { + final persisted = prefs.getString(key); + return persisted != null ? mapFrom!(persisted) : defaultValue; + } + return prefs.get(key) as T? ?? defaultValue; + } catch (e) { + loggy.warning("error getting preference[$key]: $e"); + return defaultValue; + } + } +} + class PrefNotifier extends AutoDisposeNotifier with _Prefs, InfraLogger { PrefNotifier(