Refactor preferences
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TranslationsEn>(
|
||||
(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,
|
||||
);
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export 'locale_controller.dart';
|
||||
export 'locale_pref.dart';
|
||||
@@ -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<void> change(LocalePref locale) async {
|
||||
loggy.debug('changing locale to [$locale]');
|
||||
await _prefs.setInt(_localeKey, locale.index);
|
||||
state = locale;
|
||||
}
|
||||
}
|
||||
@@ -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<Locale> 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();
|
||||
}
|
||||
}
|
||||
@@ -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: <ThemeExtension<dynamic>>{
|
||||
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: <ThemeExtension<dynamic>>{
|
||||
ConnectionButtonTheme.light,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionButtonTheme extends ThemeExtension<ConnectionButtonTheme> {
|
||||
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<ConnectionButtonTheme> copyWith({
|
||||
Color? idleColor,
|
||||
Color? connectedColor,
|
||||
}) =>
|
||||
ConnectionButtonTheme(
|
||||
idleColor: idleColor ?? this.idleColor,
|
||||
connectedColor: connectedColor ?? this.connectedColor,
|
||||
);
|
||||
|
||||
@override
|
||||
ThemeExtension<ConnectionButtonTheme> lerp(
|
||||
covariant ThemeExtension<ConnectionButtonTheme>? 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
44
lib/core/prefs/locale_prefs.dart
Normal file
44
lib/core/prefs/locale_prefs.dart
Normal file
@@ -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<Locale> 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 : "";
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
export 'app_theme.dart';
|
||||
export 'general_prefs.dart';
|
||||
export 'locale_prefs.dart';
|
||||
export 'theme_prefs.dart';
|
||||
|
||||
14
lib/core/prefs/theme_prefs.dart
Normal file
14
lib/core/prefs/theme_prefs.dart
Normal file
@@ -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,
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export 'app_theme.dart';
|
||||
export 'constants.dart';
|
||||
export 'theme_controller.dart';
|
||||
export 'theme_prefs.dart';
|
||||
@@ -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<void> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<ConnectionButtonTheme>()!;
|
||||
|
||||
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
|
||||
|
||||
@@ -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<LocalePref>(
|
||||
final selectedLocale = await showDialog<AppLocale>(
|
||||
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(
|
||||
|
||||
@@ -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: () {
|
||||
|
||||
@@ -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<T> extends AutoDisposeNotifier<T> {
|
||||
PrefNotifier(this._key, this._defaultValue, this._mapFrom, this._mapTo);
|
||||
class PrefNotifier<T> extends AutoDisposeNotifier<T>
|
||||
with _Prefs<T>, 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<PrefNotifier<T>, T> provider<T>(
|
||||
String key,
|
||||
@@ -16,39 +27,102 @@ class PrefNotifier<T> extends AutoDisposeNotifier<T> {
|
||||
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<void> 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<String> _:
|
||||
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<T> extends Notifier<T>
|
||||
with _Prefs<T>, 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<AlwaysAlivePrefNotifier<T>, T> provider<T>(
|
||||
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<void> update(T value) async {
|
||||
super.update(value);
|
||||
super.state = value;
|
||||
}
|
||||
|
||||
@override
|
||||
T build() => getValue();
|
||||
}
|
||||
|
||||
mixin _Prefs<T> 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<void> 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<String> _:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user