From ef1846e553ac257e10a394a298dc7a1d69b574d7 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Wed, 6 Sep 2023 12:56:30 +0330 Subject: [PATCH] Refactor preferences --- lib/core/app/app_view.dart | 22 ++- lib/core/core_providers.dart | 17 ++- lib/core/locale/locale.dart | 2 - lib/core/locale/locale_controller.dart | 24 ---- lib/core/locale/locale_pref.dart | 33 ----- lib/core/{theme => prefs}/app_theme.dart | 66 +++++++-- lib/core/prefs/locale_prefs.dart | 44 ++++++ lib/core/prefs/prefs.dart | 3 + lib/core/prefs/theme_prefs.dart | 14 ++ lib/core/theme/constants.dart | 6 - lib/core/theme/theme.dart | 4 - lib/core/theme/theme_controller.dart | 41 ------ lib/core/theme/theme_prefs.dart | 14 -- lib/domain/app/update_failure.dart | 2 +- .../connectivity/connection_failure.dart | 6 +- .../connectivity/connection_status.dart | 2 +- lib/domain/core_service_failure.dart | 6 +- lib/domain/failures.dart | 24 +++- lib/domain/profiles/profile_enums.dart | 2 +- lib/domain/profiles/profiles_failure.dart | 6 +- lib/domain/singbox/config_options.dart | 2 +- lib/features/common/profile_tile.dart | 2 +- .../home/widgets/connection_button.dart | 10 +- .../widgets/general_setting_tiles.dart | 39 ++--- lib/utils/alerts.dart | 7 +- lib/utils/pref_notifier.dart | 134 ++++++++++++++---- 26 files changed, 303 insertions(+), 229 deletions(-) delete mode 100644 lib/core/locale/locale.dart delete mode 100644 lib/core/locale/locale_controller.dart delete mode 100644 lib/core/locale/locale_pref.dart rename lib/core/{theme => prefs}/app_theme.dart (78%) create mode 100644 lib/core/prefs/locale_prefs.dart create mode 100644 lib/core/prefs/theme_prefs.dart delete mode 100644 lib/core/theme/constants.dart delete mode 100644 lib/core/theme/theme.dart delete mode 100644 lib/core/theme/theme_controller.dart delete mode 100644 lib/core/theme/theme_prefs.dart diff --git a/lib/core/app/app_view.dart b/lib/core/app/app_view.dart index 3ae21b51..df3d0063 100644 --- a/lib/core/app/app_view.dart +++ b/lib/core/app/app_view.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/core/router/router.dart'; -import 'package:hiddify/core/theme/theme.dart'; import 'package:hiddify/features/common/common_controllers.dart'; -import 'package:hiddify/gen/fonts.gen.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -15,24 +14,21 @@ class AppView extends HookConsumerWidget with PresLogger { @override Widget build(BuildContext context, WidgetRef ref) { final router = ref.watch(routerProvider); - final locale = ref.watch(localeControllerProvider).locale; - final theme = ref.watch(themeControllerProvider); + final locale = ref.watch(localeProvider).locale; + final theme = ref.watch(themeProvider); ref.watch(commonControllersProvider); - // HACK temporary solution - final fontFamily = locale.languageCode == "fa" ? FontFamily.shabnam : ""; - return MaterialApp.router( routerConfig: router, locale: locale, - supportedLocales: LocalePref.locales, + supportedLocales: AppLocale.locales, localizationsDelegates: GlobalMaterialLocalizations.delegates, debugShowCheckedModeBanner: false, - themeMode: theme.themeMode, - theme: theme.light(fontFamily: fontFamily), - darkTheme: theme.dark(fontFamily: fontFamily), - title: 'Hiddify', + themeMode: theme.mode, + theme: theme.light(), + darkTheme: theme.dark(), + title: 'Hiddify Next', ).animate().fadeIn(); } } diff --git a/lib/core/core_providers.dart b/lib/core/core_providers.dart index 195ca395..aa8ccce1 100644 --- a/lib/core/core_providers.dart +++ b/lib/core/core_providers.dart @@ -1,6 +1,15 @@ -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -final translationsProvider = Provider( - (ref) => ref.watch(localeControllerProvider).translations(), -); +part 'core_providers.g.dart'; + +@Riverpod(keepAlive: true) +TranslationsEn translations(TranslationsRef ref) => + ref.watch(localeProvider).translations(); + +@riverpod +AppTheme theme(ThemeRef ref) => AppTheme( + ref.watch(themeModeProvider), + ref.watch(trueBlackThemeProvider), + ref.watch(localeProvider).preferredFontFamily, + ); diff --git a/lib/core/locale/locale.dart b/lib/core/locale/locale.dart deleted file mode 100644 index f7731cb3..00000000 --- a/lib/core/locale/locale.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'locale_controller.dart'; -export 'locale_pref.dart'; diff --git a/lib/core/locale/locale_controller.dart b/lib/core/locale/locale_controller.dart deleted file mode 100644 index 3263dcc8..00000000 --- a/lib/core/locale/locale_controller.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:hiddify/core/locale/locale_pref.dart'; -import 'package:hiddify/data/data_providers.dart'; -import 'package:hiddify/utils/utils.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -part 'locale_controller.g.dart'; - -@Riverpod(keepAlive: true) -class LocaleController extends _$LocaleController with AppLogger { - @override - LocalePref build() { - return LocalePref.values[_prefs.getInt(_localeKey) ?? 0]; - } - - static const _localeKey = 'locale'; - SharedPreferences get _prefs => ref.read(sharedPreferencesProvider); - - Future change(LocalePref locale) async { - loggy.debug('changing locale to [$locale]'); - await _prefs.setInt(_localeKey, locale.index); - state = locale; - } -} diff --git a/lib/core/locale/locale_pref.dart b/lib/core/locale/locale_pref.dart deleted file mode 100644 index 7fc66436..00000000 --- a/lib/core/locale/locale_pref.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:dartx/dartx.dart'; -import 'package:flutter/widgets.dart'; -import 'package:hiddify/gen/translations.g.dart'; - -export 'package:hiddify/gen/translations.g.dart'; - -enum LocalePref { - en, - fa; - - Locale get locale { - return Locale(name); - } - - static List get locales => - LocalePref.values.map((e) => e.locale).toList(); - - static LocalePref fromString(String e) { - return LocalePref.values.firstOrNullWhere((element) => element.name == e) ?? - LocalePref.en; - } - - static LocalePref deviceLocale() { - return LocalePref.fromString( - AppLocaleUtils.findDeviceLocale().languageCode, - ); - } - - TranslationsEn translations() { - final appLocale = AppLocaleUtils.parse(name); - return appLocale.build(); - } -} diff --git a/lib/core/theme/app_theme.dart b/lib/core/prefs/app_theme.dart similarity index 78% rename from lib/core/theme/app_theme.dart rename to lib/core/prefs/app_theme.dart index 11c6eced..8e6c8601 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/prefs/app_theme.dart @@ -1,12 +1,19 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; -import 'package:hiddify/core/theme/theme_prefs.dart'; // mostly exact copy of flex color scheme 7.1's fabulous 12 theme -extension AppTheme on ThemePrefs { - ThemeData light({ - String fontFamily = "Shabnam", - }) { +class AppTheme { + AppTheme( + this.mode, + this.trueBlack, + this.fontFamily, + ); + + final ThemeMode mode; + final bool trueBlack; + final String fontFamily; + + ThemeData light() { return FlexThemeData.light( scheme: FlexScheme.indigoM3, surfaceMode: FlexSurfaceMode.highScaffoldLowSurface, @@ -62,12 +69,13 @@ extension AppTheme on ThemePrefs { tones: FlexTones.jolly(Brightness.light), visualDensity: FlexColorScheme.comfortablePlatformDensity, fontFamily: fontFamily, + extensions: >{ + ConnectionButtonTheme.light, + }, ); } - ThemeData dark({ - String fontFamily = "Shabnam", - }) { + ThemeData dark() { return FlexThemeData.dark( scheme: FlexScheme.indigoM3, useMaterial3: true, @@ -124,6 +132,48 @@ extension AppTheme on ThemePrefs { // tones: FlexTones.jolly(Brightness.dark), visualDensity: FlexColorScheme.comfortablePlatformDensity, fontFamily: fontFamily, + extensions: >{ + ConnectionButtonTheme.light, + }, + ); + } +} + +class ConnectionButtonTheme extends ThemeExtension { + const ConnectionButtonTheme({ + this.idleColor, + this.connectedColor, + }); + + final Color? idleColor; + final Color? connectedColor; + + static const ConnectionButtonTheme light = ConnectionButtonTheme( + idleColor: Color(0xFF4a4d8b), + connectedColor: Color(0xFF44a334), + ); + + @override + ThemeExtension copyWith({ + Color? idleColor, + Color? connectedColor, + }) => + ConnectionButtonTheme( + idleColor: idleColor ?? this.idleColor, + connectedColor: connectedColor ?? this.connectedColor, + ); + + @override + ThemeExtension lerp( + covariant ThemeExtension? other, + double t, + ) { + if (other is! ConnectionButtonTheme) { + return this; + } + return ConnectionButtonTheme( + idleColor: Color.lerp(idleColor, other.idleColor, t), + connectedColor: Color.lerp(connectedColor, other.connectedColor, t), ); } } diff --git a/lib/core/prefs/locale_prefs.dart b/lib/core/prefs/locale_prefs.dart new file mode 100644 index 00000000..6c2961c0 --- /dev/null +++ b/lib/core/prefs/locale_prefs.dart @@ -0,0 +1,44 @@ +import 'package:dartx/dartx.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hiddify/gen/fonts.gen.dart'; +import 'package:hiddify/gen/translations.g.dart'; +import 'package:hiddify/utils/pref_notifier.dart'; + +export 'package:hiddify/gen/translations.g.dart'; + +final localeProvider = AlwaysAlivePrefNotifier.provider( + "locale", + AppLocale.deviceLocale(), + mapFrom: AppLocale.values.byName, + mapTo: (value) => value.name, +); + +enum AppLocale { + en, + fa; + + Locale get locale { + return Locale(name); + } + + static List get locales => + AppLocale.values.map((e) => e.locale).toList(); + + static AppLocale fromString(String e) { + return AppLocale.values.firstOrNullWhere((element) => element.name == e) ?? + AppLocale.en; + } + + static AppLocale deviceLocale() { + return AppLocale.fromString( + AppLocaleUtils.findDeviceLocale().languageCode, + ); + } + + TranslationsEn translations() { + final appLocale = AppLocaleUtils.parse(name); + return appLocale.build(); + } + + String get preferredFontFamily => this == fa ? FontFamily.shabnam : ""; +} diff --git a/lib/core/prefs/prefs.dart b/lib/core/prefs/prefs.dart index 9cce03db..2d8fa8fc 100644 --- a/lib/core/prefs/prefs.dart +++ b/lib/core/prefs/prefs.dart @@ -1 +1,4 @@ +export 'app_theme.dart'; export 'general_prefs.dart'; +export 'locale_prefs.dart'; +export 'theme_prefs.dart'; diff --git a/lib/core/prefs/theme_prefs.dart b/lib/core/prefs/theme_prefs.dart new file mode 100644 index 00000000..055a4852 --- /dev/null +++ b/lib/core/prefs/theme_prefs.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:hiddify/utils/pref_notifier.dart'; + +final themeModeProvider = AlwaysAlivePrefNotifier.provider( + "theme_mode", + ThemeMode.system, + mapFrom: ThemeMode.values.byName, + mapTo: (value) => value.name, +); + +final trueBlackThemeProvider = AlwaysAlivePrefNotifier.provider( + "true_black_theme", + false, +); diff --git a/lib/core/theme/constants.dart b/lib/core/theme/constants.dart deleted file mode 100644 index ed48e95b..00000000 --- a/lib/core/theme/constants.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter/widgets.dart'; - -abstract class ConnectionButtonColor { - static const connected = Color.fromRGBO(89, 140, 82, 1); - static const disconnected = Color.fromRGBO(74, 77, 139, 1); -} diff --git a/lib/core/theme/theme.dart b/lib/core/theme/theme.dart deleted file mode 100644 index d03eb006..00000000 --- a/lib/core/theme/theme.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'app_theme.dart'; -export 'constants.dart'; -export 'theme_controller.dart'; -export 'theme_prefs.dart'; diff --git a/lib/core/theme/theme_controller.dart b/lib/core/theme/theme_controller.dart deleted file mode 100644 index 2549661d..00000000 --- a/lib/core/theme/theme_controller.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hiddify/core/theme/theme_prefs.dart'; -import 'package:hiddify/data/data_providers.dart'; -import 'package:hiddify/utils/utils.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -part 'theme_controller.g.dart'; - -@Riverpod(keepAlive: true) -class ThemeController extends _$ThemeController with AppLogger { - @override - ThemePrefs build() { - return ThemePrefs( - themeMode: ThemeMode.values[_prefs.getInt(_themeModeKey) ?? 0], - trueBlack: _prefs.getBool(_trueBlackKey) ?? false, - ); - } - - SharedPreferences get _prefs => ref.read(sharedPreferencesProvider); - - static const _themeModeKey = "theme_mode"; - static const _trueBlackKey = "true_black"; - - Future change({ - ThemeMode? themeMode, - bool? trueBlack, - }) async { - loggy.debug('changing theme, mode=$themeMode, trueBlack=$trueBlack'); - if (themeMode != null) { - await _prefs.setInt(_themeModeKey, themeMode.index); - } - if (trueBlack != null) { - await _prefs.setBool(_trueBlackKey, trueBlack); - } - state = state.copyWith( - themeMode: themeMode ?? state.themeMode, - trueBlack: trueBlack ?? state.trueBlack, - ); - } -} diff --git a/lib/core/theme/theme_prefs.dart b/lib/core/theme/theme_prefs.dart deleted file mode 100644 index 9701d66e..00000000 --- a/lib/core/theme/theme_prefs.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'theme_prefs.freezed.dart'; - -@freezed -class ThemePrefs with _$ThemePrefs { - const ThemePrefs._(); - - const factory ThemePrefs({ - @Default(ThemeMode.system) ThemeMode themeMode, - @Default(false) bool trueBlack, - }) = _ThemePrefs; -} diff --git a/lib/domain/app/update_failure.dart b/lib/domain/app/update_failure.dart index 4b695405..97212b36 100644 --- a/lib/domain/app/update_failure.dart +++ b/lib/domain/app/update_failure.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/failures.dart'; part 'update_failure.freezed.dart'; diff --git a/lib/domain/connectivity/connection_failure.dart b/lib/domain/connectivity/connection_failure.dart index a623ba37..1c08e443 100644 --- a/lib/domain/connectivity/connection_failure.dart +++ b/lib/domain/connectivity/connection_failure.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/core_service_failure.dart'; import 'package:hiddify/domain/failures.dart'; @@ -27,9 +27,9 @@ sealed class ConnectionFailure with _$ConnectionFailure, Failure { @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - UnexpectedConnectionFailure() => ( + UnexpectedConnectionFailure(:final error) => ( type: t.failure.connectivity.unexpected, - message: null + message: t.mayPrintError(error), ), MissingVpnPermission(:final message) => ( type: t.failure.connectivity.missingVpnPermission, diff --git a/lib/domain/connectivity/connection_status.dart b/lib/domain/connectivity/connection_status.dart index b08a2fec..047883b1 100644 --- a/lib/domain/connectivity/connection_status.dart +++ b/lib/domain/connectivity/connection_status.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/connectivity/connection_failure.dart'; part 'connection_status.freezed.dart'; diff --git a/lib/domain/core_service_failure.dart b/lib/domain/core_service_failure.dart index adb71910..88a7b000 100644 --- a/lib/domain/core_service_failure.dart +++ b/lib/domain/core_service_failure.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/failures.dart'; part 'core_service_failure.freezed.dart'; @@ -49,9 +49,9 @@ sealed class CoreServiceFailure with _$CoreServiceFailure, Failure { @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - UnexpectedCoreServiceFailure() => ( + UnexpectedCoreServiceFailure(:final error) => ( type: t.failure.singbox.unexpected, - message: null + message: t.mayPrintError(error), ), CoreServiceNotRunning(:final message) => ( type: t.failure.singbox.serviceNotRunning, diff --git a/lib/domain/failures.dart b/lib/domain/failures.dart index f730faf6..433fcc78 100644 --- a/lib/domain/failures.dart +++ b/lib/domain/failures.dart @@ -1,19 +1,29 @@ -import 'package:hiddify/core/locale/locale.dart'; +import 'package:dio/dio.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; -// TODO: rewrite mixin Failure { ({String type, String? message}) present(TranslationsEn t); } extension ErrorPresenter on TranslationsEn { - String printError(Object error) { - if (error case Failure()) { - final err = error.present(this); - return err.type + (err.message == null ? "" : ": ${err.message}"); + String? _errorToMessage(Object error) { + switch (error) { + case Failure(): + final err = error.present(this); + return err.type + (err.message == null ? "" : ": ${err.message}"); + case DioException(): + return error.toString(); + default: + return null; } - return failure.unexpected; } + String printError(Object error) => + _errorToMessage(error) ?? failure.unexpected; + + String? mayPrintError(Object? error) => + error != null ? _errorToMessage(error) : null; + ({String type, String? message}) presentError(Object error) { if (error case Failure()) return error.present(this); return (type: failure.unexpected, message: null); diff --git a/lib/domain/profiles/profile_enums.dart b/lib/domain/profiles/profile_enums.dart index a8553edd..1ad081bd 100644 --- a/lib/domain/profiles/profile_enums.dart +++ b/lib/domain/profiles/profile_enums.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; enum ProfilesSort { lastUpdate, diff --git a/lib/domain/profiles/profiles_failure.dart b/lib/domain/profiles/profiles_failure.dart index cc0c1c66..11720b00 100644 --- a/lib/domain/profiles/profiles_failure.dart +++ b/lib/domain/profiles/profiles_failure.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/failures.dart'; part 'profiles_failure.freezed.dart'; @@ -21,9 +21,9 @@ sealed class ProfileFailure with _$ProfileFailure, Failure { @override ({String type, String? message}) present(TranslationsEn t) { return switch (this) { - ProfileUnexpectedFailure() => ( + ProfileUnexpectedFailure(:final error) => ( type: t.failure.profiles.unexpected, - message: null + message: t.mayPrintError(error), ), ProfileNotFoundFailure() => ( type: t.failure.profiles.notFound, diff --git a/lib/domain/singbox/config_options.dart b/lib/domain/singbox/config_options.dart index 63ee8870..a3578512 100644 --- a/lib/domain/singbox/config_options.dart +++ b/lib/domain/singbox/config_options.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/utils/platform_utils.dart'; part 'config_options.freezed.dart'; diff --git a/lib/features/common/profile_tile.dart b/lib/features/common/profile_tile.dart index 5a9b2b53..c2974583 100644 --- a/lib/features/common/profile_tile.dart +++ b/lib/features/common/profile_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; -import 'package:hiddify/core/locale/locale.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/core/router/routes/routes.dart'; import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/domain/profiles/profiles.dart'; diff --git a/lib/features/home/widgets/connection_button.dart b/lib/features/home/widgets/connection_button.dart index 81c5c433..127228e2 100644 --- a/lib/features/home/widgets/connection_button.dart +++ b/lib/features/home/widgets/connection_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/core_providers.dart'; -import 'package:hiddify/core/theme/theme.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/domain/connectivity/connectivity.dart'; import 'package:hiddify/domain/failures.dart'; import 'package:hiddify/features/common/connectivity/connectivity_controller.dart'; @@ -34,11 +34,13 @@ class ConnectionButton extends HookConsumerWidget { }, ); + final buttonTheme = Theme.of(context).extension()!; + switch (connectionStatus) { case AsyncData(value: final status): final Color connectionLogoColor = status.isConnected - ? ConnectionButtonColor.connected - : ConnectionButtonColor.disconnected; + ? buttonTheme.connectedColor! + : buttonTheme.idleColor!; return _ConnectionButton( onTap: () => ref @@ -55,7 +57,7 @@ class ConnectionButton extends HookConsumerWidget { .toggleConnection(), enabled: true, label: const Disconnected().present(t), - buttonColor: ConnectionButtonColor.disconnected, + buttonColor: buttonTheme.idleColor!, ); default: // HACK diff --git a/lib/features/settings/widgets/general_setting_tiles.dart b/lib/features/settings/widgets/general_setting_tiles.dart index bbde9d4a..3c9aaa6d 100644 --- a/lib/features/settings/widgets/general_setting_tiles.dart +++ b/lib/features/settings/widgets/general_setting_tiles.dart @@ -2,9 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/core_providers.dart'; -import 'package:hiddify/core/locale/locale.dart'; -import 'package:hiddify/core/prefs/general_prefs.dart'; -import 'package:hiddify/core/theme/theme.dart'; +import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/features/settings/widgets/theme_mode_switch_button.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -16,10 +14,9 @@ class GeneralSettingTiles extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); - final locale = ref.watch(localeControllerProvider); + final locale = ref.watch(localeProvider); - final theme = ref.watch(themeControllerProvider); - final themeController = ref.watch(themeControllerProvider.notifier); + final theme = ref.watch(themeProvider); return Column( children: [ @@ -31,12 +28,12 @@ class GeneralSettingTiles extends HookConsumerWidget { ), leading: const Icon(Icons.language), onTap: () async { - final selectedLocale = await showDialog( + final selectedLocale = await showDialog( context: context, builder: (context) { return SimpleDialog( title: Text(t.settings.general.locale), - children: LocalePref.values + children: AppLocale.values .map( (e) => RadioListTile( title: Text( @@ -54,42 +51,36 @@ class GeneralSettingTiles extends HookConsumerWidget { }, ); if (selectedLocale != null) { - await ref - .read(localeControllerProvider.notifier) - .change(selectedLocale); + await ref.read(localeProvider.notifier).update(selectedLocale); } }, ), ListTile( title: Text(t.settings.general.themeMode), subtitle: Text( - switch (theme.themeMode) { + switch (theme.mode) { ThemeMode.system => t.settings.general.themeModes.system, ThemeMode.light => t.settings.general.themeModes.light, ThemeMode.dark => t.settings.general.themeModes.dark, }, ), trailing: ThemeModeSwitch( - themeMode: theme.themeMode, - onChanged: (value) { - themeController.change(themeMode: value); - }, + themeMode: theme.mode, + onChanged: ref.read(themeModeProvider.notifier).update, ), leading: const Icon(Icons.light_mode), onTap: () async { - await themeController.change( - themeMode: Theme.of(context).brightness == Brightness.light - ? ThemeMode.dark - : ThemeMode.light, - ); + await ref.read(themeModeProvider.notifier).update( + Theme.of(context).brightness == Brightness.light + ? ThemeMode.dark + : ThemeMode.light, + ); }, ), SwitchListTile( title: Text(t.settings.general.trueBlack), value: theme.trueBlack, - onChanged: (value) { - themeController.change(trueBlack: value); - }, + onChanged: ref.read(trueBlackThemeProvider.notifier).update, ), if (PlatformUtils.isDesktop) ...[ SwitchListTile( diff --git a/lib/utils/alerts.dart b/lib/utils/alerts.dart index 8edc0913..b5b7b59a 100644 --- a/lib/utils/alerts.dart +++ b/lib/utils/alerts.dart @@ -30,7 +30,12 @@ class CustomAlertDialog extends StatelessWidget { return AlertDialog( title: title != null ? Text(title!) : null, - content: Text(message), + content: SingleChildScrollView( + child: SizedBox( + width: 468, + child: Text(message), + ), + ), actions: [ TextButton( onPressed: () { diff --git a/lib/utils/pref_notifier.dart b/lib/utils/pref_notifier.dart index 5a6bc761..d85d8f42 100644 --- a/lib/utils/pref_notifier.dart +++ b/lib/utils/pref_notifier.dart @@ -1,14 +1,25 @@ import 'package:hiddify/data/data_providers.dart'; +import 'package:hiddify/utils/custom_loggers.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class PrefNotifier extends AutoDisposeNotifier { - PrefNotifier(this._key, this._defaultValue, this._mapFrom, this._mapTo); +class PrefNotifier extends AutoDisposeNotifier + with _Prefs, InfraLogger { + PrefNotifier( + this.key, + this.defaultValue, + this.mapFrom, + this.mapTo, + ); - final String _key; - final T _defaultValue; - final T Function(String)? _mapFrom; - final String Function(T)? _mapTo; + @override + final String key; + @override + final T defaultValue; + @override + final T Function(String)? mapFrom; + @override + final String Function(T)? mapTo; static AutoDisposeNotifierProvider, T> provider( String key, @@ -16,39 +27,102 @@ class PrefNotifier extends AutoDisposeNotifier { T Function(String value)? mapFrom, String Function(T value)? mapTo, }) => - NotifierProvider.autoDispose( + AutoDisposeNotifierProvider( () => PrefNotifier(key, defaultValue, mapFrom, mapTo), ); - SharedPreferences get _prefs => ref.read(sharedPreferencesProvider); + @override + SharedPreferences get prefs => ref.read(sharedPreferencesProvider); - /// Updates the value asynchronously. + @override Future update(T value) async { - 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); - } - } + super.update(value); super.state = value; } @override - T build() { - if (_mapTo != null && _mapFrom != null) { - final persisted = _prefs.getString(_key); - return persisted != null ? _mapFrom!(persisted) : _defaultValue; + T build() => getValue(); +} + +class AlwaysAlivePrefNotifier extends Notifier + with _Prefs, InfraLogger { + AlwaysAlivePrefNotifier( + this.key, + this.defaultValue, + this.mapFrom, + this.mapTo, + ); + + @override + final String key; + @override + final T defaultValue; + @override + final T Function(String)? mapFrom; + @override + final String Function(T)? mapTo; + + static NotifierProvider, T> provider( + String key, + T defaultValue, { + T Function(String value)? mapFrom, + String Function(T value)? mapTo, + }) => + NotifierProvider( + () => AlwaysAlivePrefNotifier(key, defaultValue, mapFrom, mapTo), + ); + + @override + SharedPreferences get prefs => ref.read(sharedPreferencesProvider); + + @override + Future update(T value) async { + super.update(value); + super.state = value; + } + + @override + T build() => getValue(); +} + +mixin _Prefs implements LoggerMixin { + String get key; + T get defaultValue; + T Function(String)? get mapFrom; + String Function(T)? get mapTo; + + SharedPreferences get prefs; + + /// Updates the value asynchronously. + Future update(T value) async { + 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); + } + } + } + + T getValue() { + try { + 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; } - return _prefs.get(_key) as T? ?? _defaultValue; } }