Refactor preferences
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/http_client/dio_http_client.dart';
|
||||
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'http_client_provider.g.dart';
|
||||
@@ -15,9 +15,9 @@ DioHttpClient httpClient(HttpClientRef ref) {
|
||||
);
|
||||
|
||||
ref.listen(
|
||||
configOptionNotifierProvider.selectAsync((data) => data.mixedPort),
|
||||
ConfigOptions.mixedPort,
|
||||
(_, next) async {
|
||||
client.setProxyPort(await next);
|
||||
client.setProxyPort(next);
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
|
||||
@@ -16,9 +16,9 @@ class OptionalRange with OptionalRangeMappable {
|
||||
String present(TranslationsEn t) =>
|
||||
format().isEmpty ? t.general.notSet : format();
|
||||
|
||||
factory OptionalRange._fromString(
|
||||
factory OptionalRange.parse(
|
||||
String input, {
|
||||
bool allowEmpty = true,
|
||||
bool allowEmpty = false,
|
||||
}) =>
|
||||
switch (input.split("-")) {
|
||||
[final String val] when val.isEmpty && allowEmpty =>
|
||||
@@ -36,7 +36,7 @@ class OptionalRange with OptionalRangeMappable {
|
||||
bool allowEmpty = false,
|
||||
}) {
|
||||
try {
|
||||
return OptionalRange._fromString(input);
|
||||
return OptionalRange.parse(input, allowEmpty: allowEmpty);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
@@ -48,7 +48,8 @@ class OptionalRangeJsonConverter
|
||||
const OptionalRangeJsonConverter();
|
||||
|
||||
@override
|
||||
OptionalRange fromJson(String json) => OptionalRange._fromString(json);
|
||||
OptionalRange fromJson(String json) =>
|
||||
OptionalRange.parse(json, allowEmpty: true);
|
||||
|
||||
@override
|
||||
String toJson(OptionalRange object) => object.format();
|
||||
|
||||
@@ -3,203 +3,111 @@ import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||
import 'package:hiddify/core/model/environment.dart';
|
||||
import 'package:hiddify/core/model/region.dart';
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||
import 'package:hiddify/features/per_app_proxy/model/per_app_proxy_mode.dart';
|
||||
import 'package:hiddify/utils/platform_utils.dart';
|
||||
import 'package:hiddify/utils/pref_notifier.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'general_preferences.g.dart';
|
||||
|
||||
// TODO refactor
|
||||
|
||||
bool _debugIntroPage = false;
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class IntroCompleted extends _$IntroCompleted {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
abstract class Preferences {
|
||||
static final introCompleted = PreferencesNotifier.create(
|
||||
"intro_completed",
|
||||
false,
|
||||
overrideValue: _debugIntroPage && kDebugMode ? false : null,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() {
|
||||
if (_debugIntroPage && kDebugMode) return false;
|
||||
return _pref.getValue();
|
||||
}
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class RegionNotifier extends _$RegionNotifier {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
static final region = PreferencesNotifier.create<Region, String>(
|
||||
"region",
|
||||
Region.other,
|
||||
mapFrom: Region.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
@override
|
||||
Region build() => _pref.getValue();
|
||||
|
||||
Future<void> update(Region value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SilentStartNotifier extends _$SilentStartNotifier {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
static final silentStart = PreferencesNotifier.create<bool, bool>(
|
||||
"silent_start",
|
||||
false,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class DisableMemoryLimit extends _$DisableMemoryLimit {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
static final disableMemoryLimit = PreferencesNotifier.create<bool, bool>(
|
||||
"disable_memory_limit",
|
||||
// disable memory limit on desktop by default
|
||||
PlatformUtils.isDesktop,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class DebugModeNotifier extends _$DebugModeNotifier {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"debug_mode",
|
||||
ref.read(environmentProvider) == Environment.dev,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PerAppProxyModeNotifier extends _$PerAppProxyModeNotifier {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
static final perAppProxyMode =
|
||||
PreferencesNotifier.create<PerAppProxyMode, String>(
|
||||
"per_app_proxy_mode",
|
||||
PerAppProxyMode.off,
|
||||
mapFrom: PerAppProxyMode.values.byName,
|
||||
mapTo: (value) => value.name,
|
||||
);
|
||||
|
||||
@override
|
||||
PerAppProxyMode build() => _pref.getValue();
|
||||
static final markNewProfileActive = PreferencesNotifier.create<bool, bool>(
|
||||
"mark_new_profile_active",
|
||||
true,
|
||||
);
|
||||
|
||||
Future<void> update(PerAppProxyMode value) {
|
||||
static final dynamicNotification = PreferencesNotifier.create<bool, bool>(
|
||||
"dynamic_notification",
|
||||
true,
|
||||
);
|
||||
|
||||
static final autoCheckIp = PreferencesNotifier.create<bool, bool>(
|
||||
"auto_check_ip",
|
||||
true,
|
||||
);
|
||||
|
||||
static final startedByUser = PreferencesNotifier.create<bool, bool>(
|
||||
"started_by_user",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class DebugModeNotifier extends _$DebugModeNotifier {
|
||||
late final _pref = PreferencesEntry(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
key: "debug_mode",
|
||||
defaultValue: ref.read(environmentProvider) == Environment.dev,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.read();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
return _pref.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PerAppProxyList extends _$PerAppProxyList {
|
||||
late final _include = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"per_app_proxy_include_list",
|
||||
<String>[],
|
||||
late final _include = PreferencesEntry(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
key: "per_app_proxy_include_list",
|
||||
defaultValue: <String>[],
|
||||
);
|
||||
|
||||
late final _exclude = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"per_app_proxy_exclude_list",
|
||||
<String>[],
|
||||
late final _exclude = PreferencesEntry(
|
||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||
key: "per_app_proxy_exclude_list",
|
||||
defaultValue: <String>[],
|
||||
);
|
||||
|
||||
@override
|
||||
List<String> build() =>
|
||||
ref.watch(perAppProxyModeNotifierProvider) == PerAppProxyMode.include
|
||||
? _include.getValue()
|
||||
: _exclude.getValue();
|
||||
ref.watch(Preferences.perAppProxyMode) == PerAppProxyMode.include
|
||||
? _include.read()
|
||||
: _exclude.read();
|
||||
|
||||
Future<void> update(List<String> value) {
|
||||
state = value;
|
||||
if (ref.read(perAppProxyModeNotifierProvider) == PerAppProxyMode.include) {
|
||||
return _include.update(value);
|
||||
if (ref.read(Preferences.perAppProxyMode) == PerAppProxyMode.include) {
|
||||
return _include.write(value);
|
||||
}
|
||||
return _exclude.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class MarkNewProfileActive extends _$MarkNewProfileActive {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"mark_new_profile_active",
|
||||
true,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class DynamicNotification extends _$DynamicNotification {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"dynamic_notification",
|
||||
true,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AutoCheckIp extends _$AutoCheckIp {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"auto_check_ip",
|
||||
true,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
return _exclude.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/utils/pref_notifier.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'service_preferences.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class StartedByUser extends _$StartedByUser with AppLogger {
|
||||
late final _pref = Pref(
|
||||
ref.watch(sharedPreferencesProvider).requireValue,
|
||||
"started_by_user",
|
||||
false,
|
||||
);
|
||||
|
||||
@override
|
||||
bool build() => _pref.getValue();
|
||||
|
||||
Future<void> update(bool value) {
|
||||
state = value;
|
||||
return _pref.update(value);
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class RouterListenable extends _$RouterListenable
|
||||
|
||||
@override
|
||||
Future<void> build() async {
|
||||
_introCompleted = ref.watch(introCompletedProvider);
|
||||
_introCompleted = ref.watch(Preferences.introCompleted);
|
||||
|
||||
ref.listenSelf((_, __) {
|
||||
if (state.isLoading) return;
|
||||
|
||||
156
lib/core/utils/preferences_utils.dart
Normal file
156
lib/core/utils/preferences_utils.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class PreferencesEntry<T, P> with InfraLogger {
|
||||
PreferencesEntry({
|
||||
required this.preferences,
|
||||
required this.key,
|
||||
required this.defaultValue,
|
||||
this.mapFrom,
|
||||
this.mapTo,
|
||||
this.validator,
|
||||
});
|
||||
|
||||
final SharedPreferences preferences;
|
||||
final String key;
|
||||
final T defaultValue;
|
||||
final T Function(P value)? mapFrom;
|
||||
final P Function(T value)? mapTo;
|
||||
final bool Function(T value)? validator;
|
||||
|
||||
T read() {
|
||||
try {
|
||||
loggy.debug("getting persisted preference [$key]($T)");
|
||||
final T value;
|
||||
if (mapFrom != null) {
|
||||
final persisted = preferences.get(key) as P?;
|
||||
if (persisted == null) {
|
||||
value = defaultValue;
|
||||
} else {
|
||||
value = mapFrom!(persisted);
|
||||
}
|
||||
} else if (T == List<String>) {
|
||||
value = preferences.getStringList(key) as T? ?? defaultValue;
|
||||
} else {
|
||||
value = preferences.get(key) as T? ?? defaultValue;
|
||||
}
|
||||
|
||||
if (validator?.call(value) ?? true) return value;
|
||||
return defaultValue;
|
||||
} catch (e, stackTrace) {
|
||||
loggy.warning("error getting preference[$key]: $e", e, stackTrace);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> write(T value) async {
|
||||
Object? mapped = value;
|
||||
if (mapTo != null) {
|
||||
mapped = mapTo!(value);
|
||||
}
|
||||
loggy.debug("updating preference [$key]($T) to [$mapped]");
|
||||
try {
|
||||
if (!(validator?.call(value) ?? true)) {
|
||||
loggy.warning("invalid value [$value] for preference [$key]($T)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (mapped) {
|
||||
final String value => await preferences.setString(key, value),
|
||||
final bool value => await preferences.setBool(key, value),
|
||||
final int value => await preferences.setInt(key, value),
|
||||
final double value => await preferences.setDouble(key, value),
|
||||
final List<String> value => await preferences.setStringList(key, value),
|
||||
_ => throw const FormatException("Invalid Type"),
|
||||
};
|
||||
} catch (e, stackTrace) {
|
||||
loggy.warning("error updating preference[$key]: $e", e, stackTrace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> remove() async {
|
||||
try {
|
||||
await preferences.remove(key);
|
||||
} catch (e, stackTrace) {
|
||||
loggy.warning("error removing preference[$key]: $e", e, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PreferencesNotifier<T, P> extends StateNotifier<T> {
|
||||
PreferencesNotifier._({
|
||||
required Ref ref,
|
||||
required this.entry,
|
||||
this.overrideValue,
|
||||
}) : _ref = ref,
|
||||
super(overrideValue ?? entry.read());
|
||||
|
||||
final Ref _ref;
|
||||
final PreferencesEntry<T, P> entry;
|
||||
final T? overrideValue;
|
||||
|
||||
static StateNotifierProvider<PreferencesNotifier<T, P>, T> create<T, P>(
|
||||
String key,
|
||||
T defaultValue, {
|
||||
T Function(P value)? mapFrom,
|
||||
P Function(T value)? mapTo,
|
||||
bool Function(T value)? validator,
|
||||
T? overrideValue,
|
||||
}) =>
|
||||
StateNotifierProvider(
|
||||
(ref) => PreferencesNotifier._(
|
||||
ref: ref,
|
||||
entry: PreferencesEntry<T, P>(
|
||||
preferences: ref.read(sharedPreferencesProvider).requireValue,
|
||||
key: key,
|
||||
defaultValue: defaultValue,
|
||||
mapFrom: mapFrom,
|
||||
mapTo: mapTo,
|
||||
validator: validator,
|
||||
),
|
||||
overrideValue: overrideValue,
|
||||
),
|
||||
);
|
||||
|
||||
static AutoDisposeStateNotifierProvider<PreferencesNotifier<T, P>, T>
|
||||
createAutoDispose<T, P>(
|
||||
String key,
|
||||
T defaultValue, {
|
||||
T Function(P value)? mapFrom,
|
||||
P Function(T value)? mapTo,
|
||||
bool Function(T value)? validator,
|
||||
T? overrideValue,
|
||||
}) =>
|
||||
StateNotifierProvider.autoDispose(
|
||||
(ref) => PreferencesNotifier._(
|
||||
ref: ref,
|
||||
entry: PreferencesEntry<T, P>(
|
||||
preferences: ref.read(sharedPreferencesProvider).requireValue,
|
||||
key: key,
|
||||
defaultValue: defaultValue,
|
||||
mapFrom: mapFrom,
|
||||
mapTo: mapTo,
|
||||
validator: validator,
|
||||
),
|
||||
overrideValue: overrideValue,
|
||||
),
|
||||
);
|
||||
|
||||
P raw() {
|
||||
final value = overrideValue ?? state;
|
||||
if (entry.mapTo != null) return entry.mapTo!(value);
|
||||
return value as P;
|
||||
}
|
||||
|
||||
Future<void> update(T value) async {
|
||||
if (await entry.write(value)) state = value;
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
await entry.remove();
|
||||
_ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user