add basic routing options, auto update routing assets,use ruleset, remove geo assets
This commit is contained in:
5
Makefile
5
Makefile
@@ -190,8 +190,9 @@ ios-libs: #not tested
|
|||||||
curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)"
|
curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)"
|
||||||
|
|
||||||
get-geo-assets:
|
get-geo-assets:
|
||||||
curl -L https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db -o $(GEO_ASSETS_DIR)/geoip.db
|
echo ""
|
||||||
curl -L https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db -o $(GEO_ASSETS_DIR)/geosite.db
|
# curl -L https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db -o $(GEO_ASSETS_DIR)/geoip.db
|
||||||
|
# curl -L https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db -o $(GEO_ASSETS_DIR)/geosite.db
|
||||||
|
|
||||||
build-headers:
|
build-headers:
|
||||||
make -C libcore -f Makefile headers && mv $(BINDIR)/$(CORE_NAME)-headers.h $(BINDIR)/libcore.h
|
make -C libcore -f Makefile headers && mv $(BINDIR)/$(CORE_NAME)-headers.h $(BINDIR)/libcore.h
|
||||||
|
|||||||
@@ -361,6 +361,7 @@
|
|||||||
"warpConfigGenerated": "WARP Config Generated",
|
"warpConfigGenerated": "WARP Config Generated",
|
||||||
"pageTitle": "Config Options",
|
"pageTitle": "Config Options",
|
||||||
"logLevel": "Log Level",
|
"logLevel": "Log Level",
|
||||||
|
"blockAds": "Block Advertisements",
|
||||||
"resolveDestination": "Resolve Destination",
|
"resolveDestination": "Resolve Destination",
|
||||||
"ipv6Mode": "IPv6 Route",
|
"ipv6Mode": "IPv6 Route",
|
||||||
"ipv6Modes": {
|
"ipv6Modes": {
|
||||||
|
|||||||
@@ -361,6 +361,7 @@
|
|||||||
"warpConfigGenerated": "پیکربندی WARP ایجاد شد",
|
"warpConfigGenerated": "پیکربندی WARP ایجاد شد",
|
||||||
"pageTitle": "تنظیمات پیکربندی",
|
"pageTitle": "تنظیمات پیکربندی",
|
||||||
"logLevel": "سطح گزارش",
|
"logLevel": "سطح گزارش",
|
||||||
|
"blockAds": "مسدود سازی تبلیغات",
|
||||||
"resolveDestination": "جایگذاری مقصد",
|
"resolveDestination": "جایگذاری مقصد",
|
||||||
"ipv6Mode": "مسیریابی IPv6",
|
"ipv6Mode": "مسیریابی IPv6",
|
||||||
"ipv6Modes": {
|
"ipv6Modes": {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import 'package:hiddify/core/preferences/preferences_provider.dart';
|
|||||||
import 'package:hiddify/features/app/widget/app.dart';
|
import 'package:hiddify/features/app/widget/app.dart';
|
||||||
import 'package:hiddify/features/auto_start/notifier/auto_start_notifier.dart';
|
import 'package:hiddify/features/auto_start/notifier/auto_start_notifier.dart';
|
||||||
import 'package:hiddify/features/deep_link/notifier/deep_link_notifier.dart';
|
import 'package:hiddify/features/deep_link/notifier/deep_link_notifier.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
|
||||||
import 'package:hiddify/features/log/data/log_data_providers.dart';
|
import 'package:hiddify/features/log/data/log_data_providers.dart';
|
||||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||||
@@ -36,8 +36,7 @@ Future<void> lazyBootstrap(
|
|||||||
|
|
||||||
LoggerController.preInit();
|
LoggerController.preInit();
|
||||||
FlutterError.onError = Logger.logFlutterError;
|
FlutterError.onError = Logger.logFlutterError;
|
||||||
WidgetsBinding.instance.platformDispatcher.onError =
|
WidgetsBinding.instance.platformDispatcher.onError = Logger.logPlatformDispatcherError;
|
||||||
Logger.logPlatformDispatcherError;
|
|
||||||
|
|
||||||
final stopWatch = Stopwatch()..start();
|
final stopWatch = Stopwatch()..start();
|
||||||
|
|
||||||
@@ -62,14 +61,11 @@ Future<void> lazyBootstrap(
|
|||||||
() => container.read(sharedPreferencesProvider.future),
|
() => container.read(sharedPreferencesProvider.future),
|
||||||
);
|
);
|
||||||
|
|
||||||
final enableAnalytics =
|
final enableAnalytics = await container.read(analyticsControllerProvider.future);
|
||||||
await container.read(analyticsControllerProvider.future);
|
|
||||||
if (enableAnalytics) {
|
if (enableAnalytics) {
|
||||||
await _init(
|
await _init(
|
||||||
"analytics",
|
"analytics",
|
||||||
() => container
|
() => container.read(analyticsControllerProvider.notifier).enableAnalytics(),
|
||||||
.read(analyticsControllerProvider.notifier)
|
|
||||||
.enableAnalytics(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +74,7 @@ Future<void> lazyBootstrap(
|
|||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
await PreferencesMigration(
|
await PreferencesMigration(
|
||||||
sharedPreferences:
|
sharedPreferences: container.read(sharedPreferencesProvider).requireValue,
|
||||||
container.read(sharedPreferencesProvider).requireValue,
|
|
||||||
).migrate();
|
).migrate();
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
Logger.bootstrap.error("preferences migration failed", e, stackTrace);
|
Logger.bootstrap.error("preferences migration failed", e, stackTrace);
|
||||||
@@ -99,8 +94,7 @@ Future<void> lazyBootstrap(
|
|||||||
);
|
);
|
||||||
|
|
||||||
final silentStart = container.read(Preferences.silentStart);
|
final silentStart = container.read(Preferences.silentStart);
|
||||||
Logger.bootstrap
|
Logger.bootstrap.debug("silent start [${silentStart ? "Enabled" : "Disabled"}]");
|
||||||
.debug("silent start [${silentStart ? "Enabled" : "Disabled"}]");
|
|
||||||
if (!silentStart) {
|
if (!silentStart) {
|
||||||
await container.read(windowNotifierProvider.notifier).open(focus: false);
|
await container.read(windowNotifierProvider.notifier).open(focus: false);
|
||||||
} else {
|
} else {
|
||||||
@@ -118,10 +112,7 @@ Future<void> lazyBootstrap(
|
|||||||
await _init("logger controller", () => LoggerController.postInit(debug));
|
await _init("logger controller", () => LoggerController.postInit(debug));
|
||||||
|
|
||||||
Logger.bootstrap.info(appInfo.format());
|
Logger.bootstrap.info(appInfo.format());
|
||||||
await _init(
|
|
||||||
"geo assets repository",
|
|
||||||
() => container.read(geoAssetRepositoryProvider.future),
|
|
||||||
);
|
|
||||||
await _init(
|
await _init(
|
||||||
"profile repository",
|
"profile repository",
|
||||||
() => container.read(profileRepositoryProvider.future),
|
() => container.read(profileRepositoryProvider.future),
|
||||||
@@ -180,13 +171,10 @@ Future<T> _init<T>(
|
|||||||
}) async {
|
}) async {
|
||||||
final stopWatch = Stopwatch()..start();
|
final stopWatch = Stopwatch()..start();
|
||||||
Logger.bootstrap.info("initializing [$name]");
|
Logger.bootstrap.info("initializing [$name]");
|
||||||
Future<T> func() => timeout != null
|
Future<T> func() => timeout != null ? initializer().timeout(Duration(milliseconds: timeout)) : initializer();
|
||||||
? initializer().timeout(Duration(milliseconds: timeout))
|
|
||||||
: initializer();
|
|
||||||
try {
|
try {
|
||||||
final result = await func();
|
final result = await func();
|
||||||
Logger.bootstrap
|
Logger.bootstrap.debug("[$name] initialized in ${stopWatch.elapsedMilliseconds}ms");
|
||||||
.debug("[$name] initialized in ${stopWatch.elapsedMilliseconds}ms");
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
Logger.bootstrap.error("[$name] error initializing", e, stackTrace);
|
Logger.bootstrap.error("[$name] error initializing", e, stackTrace);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import 'package:hiddify/core/database/connection/database_connection.dart';
|
|||||||
import 'package:hiddify/core/database/converters/duration_converter.dart';
|
import 'package:hiddify/core/database/converters/duration_converter.dart';
|
||||||
import 'package:hiddify/core/database/schema_versions.dart';
|
import 'package:hiddify/core/database/schema_versions.dart';
|
||||||
import 'package:hiddify/core/database/tables/database_tables.dart';
|
import 'package:hiddify/core/database/tables/database_tables.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
// import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
@@ -60,11 +60,11 @@ class AppDatabase extends _$AppDatabase with InfraLogger {
|
|||||||
Future<void> _prePopulateGeoAssets() async {
|
Future<void> _prePopulateGeoAssets() async {
|
||||||
loggy.debug("populating default geo assets");
|
loggy.debug("populating default geo assets");
|
||||||
await transaction(() async {
|
await transaction(() async {
|
||||||
final geoAssets = defaultGeoAssets.map((e) => e.toEntry());
|
// final geoAssets = defaultGeoAssets.map((e) => e.toEntry());
|
||||||
for (final geoAsset in geoAssets) {
|
// for (final geoAsset in geoAssets) {
|
||||||
await into(geoAssetEntries)
|
// await into(geoAssetEntries)
|
||||||
.insert(geoAsset, mode: InsertMode.insertOrIgnore);
|
// .insert(geoAsset, mode: InsertMode.insertOrIgnore);
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
import 'package:hiddify/core/app_info/app_info_provider.dart';
|
||||||
import 'package:hiddify/core/model/environment.dart';
|
import 'package:hiddify/core/model/environment.dart';
|
||||||
import 'package:hiddify/core/model/region.dart';
|
// import 'package:hiddify/core/model/region.dart';
|
||||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||||
import 'package:hiddify/core/utils/preferences_utils.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/features/per_app_proxy/model/per_app_proxy_mode.dart';
|
||||||
@@ -19,13 +19,6 @@ abstract class Preferences {
|
|||||||
overrideValue: _debugIntroPage && kDebugMode ? false : null,
|
overrideValue: _debugIntroPage && kDebugMode ? false : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final region = PreferencesNotifier.create<Region, String>(
|
|
||||||
"region",
|
|
||||||
Region.other,
|
|
||||||
mapFrom: Region.values.byName,
|
|
||||||
mapTo: (value) => value.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
static final silentStart = PreferencesNotifier.create<bool, bool>(
|
static final silentStart = PreferencesNotifier.create<bool, bool>(
|
||||||
"silent_start",
|
"silent_start",
|
||||||
false,
|
false,
|
||||||
@@ -37,8 +30,7 @@ abstract class Preferences {
|
|||||||
PlatformUtils.isDesktop,
|
PlatformUtils.isDesktop,
|
||||||
);
|
);
|
||||||
|
|
||||||
static final perAppProxyMode =
|
static final perAppProxyMode = PreferencesNotifier.create<PerAppProxyMode, String>(
|
||||||
PreferencesNotifier.create<PerAppProxyMode, String>(
|
|
||||||
"per_app_proxy_mode",
|
"per_app_proxy_mode",
|
||||||
PerAppProxyMode.off,
|
PerAppProxyMode.off,
|
||||||
mapFrom: PerAppProxyMode.values.byName,
|
mapFrom: PerAppProxyMode.values.byName,
|
||||||
@@ -103,10 +95,7 @@ class PerAppProxyList extends _$PerAppProxyList {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> build() =>
|
List<String> build() => ref.watch(Preferences.perAppProxyMode) == PerAppProxyMode.include ? _include.read() : _exclude.read();
|
||||||
ref.watch(Preferences.perAppProxyMode) == PerAppProxyMode.include
|
|
||||||
? _include.read()
|
|
||||||
: _exclude.read();
|
|
||||||
|
|
||||||
Future<void> update(List<String> value) {
|
Future<void> update(List<String> value) {
|
||||||
state = value;
|
state = value;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:hiddify/core/router/app_router.dart';
|
|||||||
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
|
import 'package:hiddify/features/common/adaptive_root_scaffold.dart';
|
||||||
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
|
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
|
||||||
import 'package:hiddify/features/config_option/widget/quick_settings_modal.dart';
|
import 'package:hiddify/features/config_option/widget/quick_settings_modal.dart';
|
||||||
import 'package:hiddify/features/geo_asset/overview/geo_assets_overview_page.dart';
|
|
||||||
import 'package:hiddify/features/home/widget/home_page.dart';
|
import 'package:hiddify/features/home/widget/home_page.dart';
|
||||||
import 'package:hiddify/features/intro/widget/intro_page.dart';
|
import 'package:hiddify/features/intro/widget/intro_page.dart';
|
||||||
import 'package:hiddify/features/log/overview/logs_overview_page.dart';
|
import 'package:hiddify/features/log/overview/logs_overview_page.dart';
|
||||||
@@ -19,8 +19,7 @@ import 'package:hiddify/utils/utils.dart';
|
|||||||
|
|
||||||
part 'routes.g.dart';
|
part 'routes.g.dart';
|
||||||
|
|
||||||
GlobalKey<NavigatorState>? _dynamicRootKey =
|
GlobalKey<NavigatorState>? _dynamicRootKey = useMobileRouter ? rootNavigatorKey : null;
|
||||||
useMobileRouter ? rootNavigatorKey : null;
|
|
||||||
|
|
||||||
@TypedShellRoute<MobileWrapperRoute>(
|
@TypedShellRoute<MobileWrapperRoute>(
|
||||||
routes: [
|
routes: [
|
||||||
@@ -60,10 +59,6 @@ GlobalKey<NavigatorState>? _dynamicRootKey =
|
|||||||
path: "per-app-proxy",
|
path: "per-app-proxy",
|
||||||
name: PerAppProxyRoute.name,
|
name: PerAppProxyRoute.name,
|
||||||
),
|
),
|
||||||
TypedGoRoute<GeoAssetsRoute>(
|
|
||||||
path: "routing-assets",
|
|
||||||
name: GeoAssetsRoute.name,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TypedGoRoute<LogsOverviewRoute>(
|
TypedGoRoute<LogsOverviewRoute>(
|
||||||
@@ -130,12 +125,7 @@ class MobileWrapperRoute extends ShellRouteData {
|
|||||||
TypedGoRoute<SettingsRoute>(
|
TypedGoRoute<SettingsRoute>(
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
name: SettingsRoute.name,
|
name: SettingsRoute.name,
|
||||||
routes: [
|
routes: [],
|
||||||
TypedGoRoute<GeoAssetsRoute>(
|
|
||||||
path: "routing-assets",
|
|
||||||
name: GeoAssetsRoute.name,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
TypedGoRoute<LogsOverviewRoute>(
|
TypedGoRoute<LogsOverviewRoute>(
|
||||||
path: "/logs",
|
path: "/logs",
|
||||||
@@ -229,8 +219,7 @@ class ProfilesOverviewRoute extends GoRouteData {
|
|||||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
return BottomSheetPage(
|
return BottomSheetPage(
|
||||||
name: name,
|
name: name,
|
||||||
builder: (controller) =>
|
builder: (controller) => ProfilesOverviewModal(scrollController: controller),
|
||||||
ProfilesOverviewModal(scrollController: controller),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,28 +347,6 @@ class PerAppProxyRoute extends GoRouteData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeoAssetsRoute extends GoRouteData {
|
|
||||||
const GeoAssetsRoute();
|
|
||||||
static const name = "Routing Assets";
|
|
||||||
|
|
||||||
static final GlobalKey<NavigatorState>? $parentNavigatorKey = _dynamicRootKey;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
|
||||||
if (useMobileRouter) {
|
|
||||||
return const MaterialPage(
|
|
||||||
name: name,
|
|
||||||
child: GeoAssetsOverviewPage(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const MaterialPage(
|
|
||||||
fullscreenDialog: true,
|
|
||||||
name: name,
|
|
||||||
child: GeoAssetsOverviewPage(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AboutRoute extends GoRouteData {
|
class AboutRoute extends GoRouteData {
|
||||||
const AboutRoute();
|
const AboutRoute();
|
||||||
static const name = "About";
|
static const name = "About";
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ class App extends HookConsumerWidget with PresLogger {
|
|||||||
return WindowWrapper(
|
return WindowWrapper(
|
||||||
TrayWrapper(
|
TrayWrapper(
|
||||||
ShortcutWrapper(
|
ShortcutWrapper(
|
||||||
ConnectionWrapper(DynamicColorBuilder(
|
ConnectionWrapper(
|
||||||
builder:
|
DynamicColorBuilder(
|
||||||
(ColorScheme? lightColorScheme, ColorScheme? darkColorScheme) {
|
builder: (ColorScheme? lightColorScheme, ColorScheme? darkColorScheme) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
routerConfig: router,
|
routerConfig: router,
|
||||||
locale: locale.flutterLocale,
|
locale: locale.flutterLocale,
|
||||||
@@ -68,7 +68,8 @@ class App extends HookConsumerWidget with PresLogger {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import 'package:hiddify/core/localization/locale_preferences.dart';
|
|||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/region.dart';
|
import 'package:hiddify/core/model/region.dart';
|
||||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||||
|
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||||
|
import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
class LocalePrefTile extends HookConsumerWidget {
|
class LocalePrefTile extends HookConsumerWidget {
|
||||||
@@ -41,9 +43,7 @@ class LocalePrefTile extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (selectedLocale != null) {
|
if (selectedLocale != null) {
|
||||||
await ref
|
await ref.read(localePreferencesProvider.notifier).changeLocale(selectedLocale);
|
||||||
.read(localePreferencesProvider.notifier)
|
|
||||||
.changeLocale(selectedLocale);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -57,7 +57,7 @@ class RegionPrefTile extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
final region = ref.watch(Preferences.region);
|
final region = ref.watch(ConfigOptions.region);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(t.settings.general.region),
|
title: Text(t.settings.general.region),
|
||||||
@@ -83,7 +83,16 @@ class RegionPrefTile extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (selectedRegion != null) {
|
if (selectedRegion != null) {
|
||||||
await ref.read(Preferences.region.notifier).update(selectedRegion);
|
// await ref.read(Preferences.region.notifier).update(selectedRegion);
|
||||||
|
await ref.watch(ConfigOptions.region.notifier).update(selectedRegion);
|
||||||
|
|
||||||
|
// await ref.read(configOptionNotifierProvider.notifier).build();
|
||||||
|
// await ref.watch(ConfigOptions.resolveDestination.notifier).update(!ref.watch(ConfigOptions.resolveDestination.notifier).raw());
|
||||||
|
//for reload config
|
||||||
|
// final tmp = ref.watch(ConfigOptions.resolveDestination.notifier).raw();
|
||||||
|
// await ref.watch(ConfigOptions.resolveDestination.notifier).update(!tmp);
|
||||||
|
// await ref.watch(ConfigOptions.resolveDestination.notifier).update(tmp);
|
||||||
|
//TODO: fix it
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -117,13 +126,9 @@ class EnableAnalyticsPrefTile extends HookConsumerWidget {
|
|||||||
return onChanged!(value);
|
return onChanged!(value);
|
||||||
}
|
}
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
await ref
|
await ref.read(analyticsControllerProvider.notifier).disableAnalytics();
|
||||||
.read(analyticsControllerProvider.notifier)
|
|
||||||
.disableAnalytics();
|
|
||||||
} else {
|
} else {
|
||||||
await ref
|
await ref.read(analyticsControllerProvider.notifier).enableAnalytics();
|
||||||
.read(analyticsControllerProvider.notifier)
|
|
||||||
.enableAnalytics();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
import 'package:hiddify/core/preferences/preferences_provider.dart';
|
||||||
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
import 'package:hiddify/features/config_option/data/config_option_repository.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'config_option_data_providers.g.dart';
|
part 'config_option_data_providers.g.dart';
|
||||||
@@ -12,7 +12,5 @@ ConfigOptionRepository configOptionRepository(
|
|||||||
return ConfigOptionRepository(
|
return ConfigOptionRepository(
|
||||||
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
preferences: ref.watch(sharedPreferencesProvider).requireValue,
|
||||||
getConfigOptions: () => ref.read(ConfigOptions.singboxConfigOptions.future),
|
getConfigOptions: () => ref.read(ConfigOptions.singboxConfigOptions.future),
|
||||||
geoAssetRepository: ref.watch(geoAssetRepositoryProvider).requireValue,
|
|
||||||
geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import 'package:fpdart/fpdart.dart';
|
|||||||
import 'package:hiddify/core/model/optional_range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
import 'package:hiddify/core/model/region.dart';
|
import 'package:hiddify/core/model/region.dart';
|
||||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
import 'package:hiddify/core/preferences/general_preferences.dart';
|
||||||
|
|
||||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||||
import 'package:hiddify/core/utils/json_converters.dart';
|
import 'package:hiddify/core/utils/json_converters.dart';
|
||||||
import 'package:hiddify/core/utils/preferences_utils.dart';
|
import 'package:hiddify/core/utils/preferences_utils.dart';
|
||||||
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
import 'package:hiddify/features/config_option/model/config_option_failure.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
|
||||||
import 'package:hiddify/features/log/model/log_level.dart';
|
import 'package:hiddify/features/log/model/log_level.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
import 'package:hiddify/singbox/model/singbox_config_enum.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||||
@@ -26,6 +25,17 @@ abstract class ConfigOptions {
|
|||||||
mapTo: (value) => value.key,
|
mapTo: (value) => value.key,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static final region = PreferencesNotifier.create<Region, String>(
|
||||||
|
"region",
|
||||||
|
Region.other,
|
||||||
|
mapFrom: Region.values.byName,
|
||||||
|
mapTo: (value) => value.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
static final blockAds = PreferencesNotifier.create<bool, bool>(
|
||||||
|
"block-ads",
|
||||||
|
false,
|
||||||
|
);
|
||||||
static final logLevel = PreferencesNotifier.create<LogLevel, String>(
|
static final logLevel = PreferencesNotifier.create<LogLevel, String>(
|
||||||
"log-level",
|
"log-level",
|
||||||
LogLevel.warn,
|
LogLevel.warn,
|
||||||
@@ -305,6 +315,8 @@ abstract class ConfigOptions {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>> preferences = {
|
static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>> preferences = {
|
||||||
|
"region": region,
|
||||||
|
"block-ads": blockAds,
|
||||||
"service-mode": serviceMode,
|
"service-mode": serviceMode,
|
||||||
"log-level": logLevel,
|
"log-level": logLevel,
|
||||||
"resolve-destination": resolveDestination,
|
"resolve-destination": resolveDestination,
|
||||||
@@ -359,44 +371,46 @@ abstract class ConfigOptions {
|
|||||||
|
|
||||||
static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
|
static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
|
||||||
(ref) async {
|
(ref) async {
|
||||||
final region = ref.watch(Preferences.region);
|
// final region = ref.watch(Preferences.region);
|
||||||
final rules = switch (region) {
|
final rules = <SingboxRule>[];
|
||||||
Region.ir => [
|
// final rules = switch (region) {
|
||||||
const SingboxRule(
|
// Region.ir => [
|
||||||
domains: "domain:.ir,geosite:ir",
|
// const SingboxRule(
|
||||||
ip: "geoip:ir",
|
// domains: "domain:.ir,geosite:ir",
|
||||||
outbound: RuleOutbound.bypass,
|
// ip: "geoip:ir",
|
||||||
),
|
// outbound: RuleOutbound.bypass,
|
||||||
],
|
// ),
|
||||||
Region.cn => [
|
// ],
|
||||||
const SingboxRule(
|
// Region.cn => [
|
||||||
domains: "domain:.cn,geosite:cn",
|
// const SingboxRule(
|
||||||
ip: "geoip:cn",
|
// domains: "domain:.cn,geosite:cn",
|
||||||
outbound: RuleOutbound.bypass,
|
// ip: "geoip:cn",
|
||||||
),
|
// outbound: RuleOutbound.bypass,
|
||||||
],
|
// ),
|
||||||
Region.ru => [
|
// ],
|
||||||
const SingboxRule(
|
// Region.ru => [
|
||||||
domains: "domain:.ru",
|
// const SingboxRule(
|
||||||
ip: "geoip:ru",
|
// domains: "domain:.ru",
|
||||||
outbound: RuleOutbound.bypass,
|
// ip: "geoip:ru",
|
||||||
),
|
// outbound: RuleOutbound.bypass,
|
||||||
],
|
// ),
|
||||||
Region.af => [
|
// ],
|
||||||
const SingboxRule(
|
// Region.af => [
|
||||||
domains: "domain:.af,geosite:af",
|
// const SingboxRule(
|
||||||
ip: "geoip:af",
|
// domains: "domain:.af,geosite:af",
|
||||||
outbound: RuleOutbound.bypass,
|
// ip: "geoip:af",
|
||||||
),
|
// outbound: RuleOutbound.bypass,
|
||||||
],
|
// ),
|
||||||
_ => <SingboxRule>[],
|
// ],
|
||||||
};
|
// _ => <SingboxRule>[],
|
||||||
|
// };
|
||||||
final geoAssetsRepo = await ref.watch(geoAssetRepositoryProvider.future);
|
|
||||||
final geoAssets = await geoAssetsRepo.getActivePair().getOrElse((l) => throw l).run();
|
|
||||||
|
|
||||||
final mode = ref.watch(serviceMode);
|
final mode = ref.watch(serviceMode);
|
||||||
|
// final reg = ref.watch(Preferences.region.notifier).raw();
|
||||||
|
|
||||||
return SingboxConfigOption(
|
return SingboxConfigOption(
|
||||||
|
region: ref.watch(region).name,
|
||||||
|
blockAds: ref.watch(blockAds),
|
||||||
executeConfigAsIs: false,
|
executeConfigAsIs: false,
|
||||||
logLevel: ref.watch(logLevel),
|
logLevel: ref.watch(logLevel),
|
||||||
resolveDestination: ref.watch(resolveDestination),
|
resolveDestination: ref.watch(resolveDestination),
|
||||||
@@ -461,14 +475,14 @@ abstract class ConfigOptions {
|
|||||||
noise: ref.watch(warpNoise),
|
noise: ref.watch(warpNoise),
|
||||||
noiseDelay: ref.watch(warpNoiseDelay),
|
noiseDelay: ref.watch(warpNoiseDelay),
|
||||||
),
|
),
|
||||||
geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
// geoipPath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
||||||
geoAssets.geoip.providerName,
|
// geoAssets.geoip.providerName,
|
||||||
geoAssets.geoip.fileName,
|
// geoAssets.geoip.fileName,
|
||||||
),
|
// ),
|
||||||
geositePath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
// geositePath: ref.watch(geoAssetPathResolverProvider).relativePath(
|
||||||
geoAssets.geosite.providerName,
|
// geoAssets.geosite.providerName,
|
||||||
geoAssets.geosite.fileName,
|
// geoAssets.geosite.fileName,
|
||||||
),
|
// ),
|
||||||
rules: rules,
|
rules: rules,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -479,14 +493,10 @@ class ConfigOptionRepository with ExceptionHandler, InfraLogger {
|
|||||||
ConfigOptionRepository({
|
ConfigOptionRepository({
|
||||||
required this.preferences,
|
required this.preferences,
|
||||||
required this.getConfigOptions,
|
required this.getConfigOptions,
|
||||||
required this.geoAssetRepository,
|
|
||||||
required this.geoAssetPathResolver,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final SharedPreferences preferences;
|
final SharedPreferences preferences;
|
||||||
final Future<SingboxConfigOption> Function() getConfigOptions;
|
final Future<SingboxConfigOption> Function() getConfigOptions;
|
||||||
final GeoAssetRepository geoAssetRepository;
|
|
||||||
final GeoAssetPathResolver geoAssetPathResolver;
|
|
||||||
|
|
||||||
TaskEither<ConfigOptionFailure, SingboxConfigOption> getFullSingboxConfigOption() {
|
TaskEither<ConfigOptionFailure, SingboxConfigOption> getFullSingboxConfigOption() {
|
||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
|
|||||||
@@ -15,16 +15,13 @@ class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger {
|
|||||||
@override
|
@override
|
||||||
Future<bool> build() async {
|
Future<bool> build() async {
|
||||||
final serviceRunning = await ref.watch(serviceRunningProvider.future);
|
final serviceRunning = await ref.watch(serviceRunningProvider.future);
|
||||||
final serviceSingboxOptions =
|
final serviceSingboxOptions = ref.read(connectionRepositoryProvider).configOptionsSnapshot;
|
||||||
ref.read(connectionRepositoryProvider).configOptionsSnapshot;
|
|
||||||
ref.listen(
|
ref.listen(
|
||||||
ConfigOptions.singboxConfigOptions,
|
ConfigOptions.singboxConfigOptions,
|
||||||
(previous, next) async {
|
(previous, next) async {
|
||||||
if (!serviceRunning || serviceSingboxOptions == null) return;
|
if (!serviceRunning || serviceSingboxOptions == null) return;
|
||||||
if (next case AsyncData(:final value) when next != previous) {
|
if (next case AsyncData(:final value) when next != previous) {
|
||||||
if (_lastUpdate == null ||
|
if (_lastUpdate == null || DateTime.now().difference(_lastUpdate!) > const Duration(milliseconds: 100)) {
|
||||||
DateTime.now().difference(_lastUpdate!) >
|
|
||||||
const Duration(milliseconds: 100)) {
|
|
||||||
_lastUpdate = DateTime.now();
|
_lastUpdate = DateTime.now();
|
||||||
state = AsyncData(value != serviceSingboxOptions);
|
state = AsyncData(value != serviceSingboxOptions);
|
||||||
}
|
}
|
||||||
@@ -63,8 +60,7 @@ class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger {
|
|||||||
|
|
||||||
Future<bool> importFromClipboard() async {
|
Future<bool> importFromClipboard() async {
|
||||||
try {
|
try {
|
||||||
final input =
|
final input = await Clipboard.getData("text/plain").then((value) => value?.text);
|
||||||
await Clipboard.getData("text/plain").then((value) => value?.text);
|
|
||||||
if (input == null) return false;
|
if (input == null) return false;
|
||||||
if (jsonDecode(input) case final Map<String, dynamic> map) {
|
if (jsonDecode(input) case final Map<String, dynamic> map) {
|
||||||
for (final option in ConfigOptions.preferences.entries) {
|
for (final option in ConfigOptions.preferences.entries) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/optional_range.dart';
|
import 'package:hiddify/core/model/optional_range.dart';
|
||||||
|
import 'package:hiddify/core/model/region.dart';
|
||||||
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
|
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
|
||||||
import 'package:hiddify/core/preferences/general_preferences.dart';
|
|
||||||
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
||||||
import 'package:hiddify/core/widget/tip_card.dart';
|
import 'package:hiddify/core/widget/tip_card.dart';
|
||||||
import 'package:hiddify/features/common/confirmation_dialogs.dart';
|
import 'package:hiddify/features/common/confirmation_dialogs.dart';
|
||||||
@@ -140,6 +140,18 @@ class ConfigOptionsPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SettingsDivider(),
|
const SettingsDivider(),
|
||||||
SettingsSection(t.config.section.route),
|
SettingsSection(t.config.section.route),
|
||||||
|
ChoicePreferenceWidget(
|
||||||
|
selected: ref.watch(ConfigOptions.region),
|
||||||
|
preferences: ref.watch(ConfigOptions.region.notifier),
|
||||||
|
choices: Region.values,
|
||||||
|
title: t.settings.general.region,
|
||||||
|
presentChoice: (value) => value.present(t),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(experimental(t.config.blockAds)),
|
||||||
|
value: ref.watch(ConfigOptions.blockAds),
|
||||||
|
onChanged: ref.watch(ConfigOptions.blockAds.notifier).update,
|
||||||
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(experimental(t.config.bypassLan)),
|
title: Text(experimental(t.config.bypassLan)),
|
||||||
value: ref.watch(ConfigOptions.bypassLan),
|
value: ref.watch(ConfigOptions.bypassLan),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:hiddify/core/directories/directories_provider.dart';
|
|||||||
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
import 'package:hiddify/features/config_option/data/config_option_data_providers.dart';
|
||||||
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
||||||
import 'package:hiddify/features/connection/data/connection_repository.dart';
|
import 'package:hiddify/features/connection/data/connection_repository.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
|
||||||
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
|
||||||
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
import 'package:hiddify/singbox/service/singbox_service_provider.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -19,6 +19,5 @@ ConnectionRepository connectionRepository(
|
|||||||
singbox: ref.watch(singboxServiceProvider),
|
singbox: ref.watch(singboxServiceProvider),
|
||||||
platformSource: ConnectionPlatformSourceImpl(),
|
platformSource: ConnectionPlatformSourceImpl(),
|
||||||
profilePathResolver: ref.watch(profilePathResolverProvider),
|
profilePathResolver: ref.watch(profilePathResolverProvider),
|
||||||
geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fpdart/fpdart.dart';
|
import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/core/model/directories.dart';
|
import 'package:hiddify/core/model/directories.dart';
|
||||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
import 'package:hiddify/core/utils/exception_handler.dart';
|
||||||
@@ -7,7 +5,7 @@ import 'package:hiddify/features/config_option/data/config_option_repository.dar
|
|||||||
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
import 'package:hiddify/features/connection/data/connection_platform_source.dart';
|
||||||
import 'package:hiddify/features/connection/model/connection_failure.dart';
|
import 'package:hiddify/features/connection/model/connection_failure.dart';
|
||||||
import 'package:hiddify/features/connection/model/connection_status.dart';
|
import 'package:hiddify/features/connection/model/connection_status.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
|
||||||
import 'package:hiddify/features/profile/data/profile_path_resolver.dart';
|
import 'package:hiddify/features/profile/data/profile_path_resolver.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
||||||
import 'package:hiddify/singbox/model/singbox_status.dart';
|
import 'package:hiddify/singbox/model/singbox_status.dart';
|
||||||
@@ -33,16 +31,13 @@ abstract interface class ConnectionRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionRepositoryImpl
|
class ConnectionRepositoryImpl with ExceptionHandler, InfraLogger implements ConnectionRepository {
|
||||||
with ExceptionHandler, InfraLogger
|
|
||||||
implements ConnectionRepository {
|
|
||||||
ConnectionRepositoryImpl({
|
ConnectionRepositoryImpl({
|
||||||
required this.directories,
|
required this.directories,
|
||||||
required this.singbox,
|
required this.singbox,
|
||||||
required this.platformSource,
|
required this.platformSource,
|
||||||
required this.configOptionRepository,
|
required this.configOptionRepository,
|
||||||
required this.profilePathResolver,
|
required this.profilePathResolver,
|
||||||
required this.geoAssetPathResolver,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Directories directories;
|
final Directories directories;
|
||||||
@@ -50,7 +45,6 @@ class ConnectionRepositoryImpl
|
|||||||
final ConnectionPlatformSource platformSource;
|
final ConnectionPlatformSource platformSource;
|
||||||
final ConfigOptionRepository configOptionRepository;
|
final ConfigOptionRepository configOptionRepository;
|
||||||
final ProfilePathResolver profilePathResolver;
|
final ProfilePathResolver profilePathResolver;
|
||||||
final GeoAssetPathResolver geoAssetPathResolver;
|
|
||||||
|
|
||||||
SingboxConfigOption? _configOptionsSnapshot;
|
SingboxConfigOption? _configOptionsSnapshot;
|
||||||
@override
|
@override
|
||||||
@@ -64,16 +58,10 @@ class ConnectionRepositoryImpl
|
|||||||
(event) => switch (event) {
|
(event) => switch (event) {
|
||||||
SingboxStopped(:final alert?, :final message) => Disconnected(
|
SingboxStopped(:final alert?, :final message) => Disconnected(
|
||||||
switch (alert) {
|
switch (alert) {
|
||||||
SingboxAlert.emptyConfiguration =>
|
SingboxAlert.emptyConfiguration => ConnectionFailure.invalidConfig(message),
|
||||||
ConnectionFailure.invalidConfig(message),
|
SingboxAlert.requestNotificationPermission => ConnectionFailure.missingNotificationPermission(message),
|
||||||
SingboxAlert.requestNotificationPermission =>
|
SingboxAlert.requestVPNPermission => ConnectionFailure.missingVpnPermission(message),
|
||||||
ConnectionFailure.missingNotificationPermission(message),
|
SingboxAlert.startCommandServer || SingboxAlert.createService || SingboxAlert.startService => ConnectionFailure.unexpected(message),
|
||||||
SingboxAlert.requestVPNPermission =>
|
|
||||||
ConnectionFailure.missingVpnPermission(message),
|
|
||||||
SingboxAlert.startCommandServer ||
|
|
||||||
SingboxAlert.createService ||
|
|
||||||
SingboxAlert.startService =>
|
|
||||||
ConnectionFailure.unexpected(message),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SingboxStopped() => const Disconnected(),
|
SingboxStopped() => const Disconnected(),
|
||||||
@@ -89,21 +77,19 @@ class ConnectionRepositoryImpl
|
|||||||
return TaskEither<ConnectionFailure, SingboxConfigOption>.Do(
|
return TaskEither<ConnectionFailure, SingboxConfigOption>.Do(
|
||||||
($) async {
|
($) async {
|
||||||
final options = await $(
|
final options = await $(
|
||||||
configOptionRepository
|
configOptionRepository.getFullSingboxConfigOption().mapLeft((l) => const InvalidConfigOption()),
|
||||||
.getFullSingboxConfigOption()
|
|
||||||
.mapLeft((l) => const InvalidConfigOption()),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return $(
|
return $(
|
||||||
TaskEither(
|
TaskEither(
|
||||||
() async {
|
() async {
|
||||||
final geoip = geoAssetPathResolver.resolvePath(options.geoipPath);
|
// final geoip = geoAssetPathResolver.resolvePath(options.geoipPath);
|
||||||
final geosite =
|
// final geosite =
|
||||||
geoAssetPathResolver.resolvePath(options.geositePath);
|
// geoAssetPathResolver.resolvePath(options.geositePath);
|
||||||
if (!await File(geoip).exists() ||
|
// if (!await File(geoip).exists() ||
|
||||||
!await File(geosite).exists()) {
|
// !await File(geosite).exists()) {
|
||||||
return left(const ConnectionFailure.missingGeoAssets());
|
// return left(const ConnectionFailure.missingGeoAssets());
|
||||||
}
|
// }
|
||||||
return right(options);
|
return right(options);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -119,10 +105,7 @@ class ConnectionRepositoryImpl
|
|||||||
return exceptionHandler(
|
return exceptionHandler(
|
||||||
() {
|
() {
|
||||||
_configOptionsSnapshot = options;
|
_configOptionsSnapshot = options;
|
||||||
return singbox
|
return singbox.changeOptions(options).mapLeft(InvalidConfigOption.new).run();
|
||||||
.changeOptions(options)
|
|
||||||
.mapLeft(InvalidConfigOption.new)
|
|
||||||
.run();
|
|
||||||
},
|
},
|
||||||
UnexpectedConnectionFailure.new,
|
UnexpectedConnectionFailure.new,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import 'package:hiddify/features/connection/model/connection_status.dart';
|
|||||||
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
import 'package:hiddify/features/profile/model/profile_entity.dart';
|
||||||
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
|
||||||
|
|
||||||
part 'connection_notifier.g.dart';
|
part 'connection_notifier.g.dart';
|
||||||
|
|
||||||
@@ -32,8 +32,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
|
|||||||
if (next case AsyncData(value: final Connected _)) {
|
if (next case AsyncData(value: final Connected _)) {
|
||||||
await ref.read(hapticServiceProvider.notifier).heavyImpact();
|
await ref.read(hapticServiceProvider.notifier).heavyImpact();
|
||||||
|
|
||||||
if (Platform.isAndroid &&
|
if (Platform.isAndroid && !ref.read(Preferences.storeReviewedByUser)) {
|
||||||
!ref.read(Preferences.storeReviewedByUser)) {
|
|
||||||
if (await InAppReview.instance.isAvailable()) {
|
if (await InAppReview.instance.isAvailable()) {
|
||||||
InAppReview.instance.requestReview();
|
InAppReview.instance.requestReview();
|
||||||
ref.read(Preferences.storeReviewedByUser.notifier).update(true);
|
ref.read(Preferences.storeReviewedByUser.notifier).update(true);
|
||||||
@@ -55,16 +54,14 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
yield* _connectionRepo.watchConnectionStatus().doOnData((event) {
|
yield* _connectionRepo.watchConnectionStatus().doOnData((event) {
|
||||||
if (event case Disconnected(connectionFailure: final _?)
|
if (event case Disconnected(connectionFailure: final _?) when PlatformUtils.isDesktop) {
|
||||||
when PlatformUtils.isDesktop) {
|
|
||||||
ref.read(Preferences.startedByUser.notifier).update(false);
|
ref.read(Preferences.startedByUser.notifier).update(false);
|
||||||
}
|
}
|
||||||
loggy.info("connection status: ${event.format()}");
|
loggy.info("connection status: ${event.format()}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionRepository get _connectionRepo =>
|
ConnectionRepository get _connectionRepo => ref.read(connectionRepositoryProvider);
|
||||||
ref.read(connectionRepositoryProvider);
|
|
||||||
|
|
||||||
Future<void> mayConnect() async {
|
Future<void> mayConnect() async {
|
||||||
if (state case AsyncData(:final value)) {
|
if (state case AsyncData(:final value)) {
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
import 'package:hiddify/core/database/database_provider.dart';
|
// import 'package:hiddify/core/database/database_provider.dart';
|
||||||
import 'package:hiddify/core/directories/directories_provider.dart';
|
// import 'package:hiddify/core/directories/directories_provider.dart';
|
||||||
import 'package:hiddify/core/http_client/http_client_provider.dart';
|
// import 'package:hiddify/core/http_client/http_client_provider.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
// import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'geo_asset_data_providers.g.dart';
|
// part 'geo_asset_data_providers.g.dart';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
// @Riverpod(keepAlive: true)
|
||||||
Future<GeoAssetRepository> geoAssetRepository(GeoAssetRepositoryRef ref) async {
|
// Future<GeoAssetRepository> geoAssetRepository(GeoAssetRepositoryRef ref) async {
|
||||||
final repo = GeoAssetRepositoryImpl(
|
// final repo = GeoAssetRepositoryImpl(
|
||||||
geoAssetDataSource: ref.watch(geoAssetDataSourceProvider),
|
// geoAssetDataSource: ref.watch(geoAssetDataSourceProvider),
|
||||||
geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider),
|
// geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider),
|
||||||
httpClient: ref.watch(httpClientProvider),
|
// httpClient: ref.watch(httpClientProvider),
|
||||||
);
|
// );
|
||||||
await repo.init().getOrElse((l) => throw l).run();
|
// await repo.init().getOrElse((l) => throw l).run();
|
||||||
return repo;
|
// return repo;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
// @Riverpod(keepAlive: true)
|
||||||
GeoAssetDataSource geoAssetDataSource(GeoAssetDataSourceRef ref) {
|
// GeoAssetDataSource geoAssetDataSource(GeoAssetDataSourceRef ref) {
|
||||||
return GeoAssetsDao(ref.watch(appDatabaseProvider));
|
// return GeoAssetsDao(ref.watch(appDatabaseProvider));
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
// @Riverpod(keepAlive: true)
|
||||||
GeoAssetPathResolver geoAssetPathResolver(GeoAssetPathResolverRef ref) {
|
// GeoAssetPathResolver geoAssetPathResolver(GeoAssetPathResolverRef ref) {
|
||||||
return GeoAssetPathResolver(
|
// return GeoAssetPathResolver(
|
||||||
ref.watch(appDirectoriesProvider).requireValue.workingDir,
|
// ref.watch(appDirectoriesProvider).requireValue.workingDir,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,232 +1,232 @@
|
|||||||
import 'dart:io';
|
// import 'dart:io';
|
||||||
|
|
||||||
import 'package:dartx/dartx_io.dart';
|
// import 'package:dartx/dartx_io.dart';
|
||||||
import 'package:drift/drift.dart';
|
// import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/services.dart';
|
// import 'package:flutter/services.dart';
|
||||||
import 'package:fpdart/fpdart.dart';
|
// import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/core/database/app_database.dart';
|
// import 'package:hiddify/core/database/app_database.dart';
|
||||||
import 'package:hiddify/core/http_client/dio_http_client.dart';
|
// import 'package:hiddify/core/http_client/dio_http_client.dart';
|
||||||
import 'package:hiddify/core/utils/exception_handler.dart';
|
// import 'package:hiddify/core/utils/exception_handler.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
// import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_failure.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_failure.dart';
|
||||||
import 'package:hiddify/gen/assets.gen.dart';
|
// import 'package:hiddify/gen/assets.gen.dart';
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
// import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
// import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:watcher/watcher.dart';
|
// import 'package:watcher/watcher.dart';
|
||||||
|
|
||||||
abstract interface class GeoAssetRepository {
|
// abstract interface class GeoAssetRepository {
|
||||||
/// populate bundled geo assets directory with bundled files if needed
|
// /// populate bundled geo assets directory with bundled files if needed
|
||||||
TaskEither<GeoAssetFailure, Unit> init();
|
// TaskEither<GeoAssetFailure, Unit> init();
|
||||||
TaskEither<GeoAssetFailure, ({GeoAssetEntity geoip, GeoAssetEntity geosite})>
|
// TaskEither<GeoAssetFailure, ({GeoAssetEntity geoip, GeoAssetEntity geosite})>
|
||||||
getActivePair();
|
// getActivePair();
|
||||||
Stream<Either<GeoAssetFailure, List<GeoAssetWithFileSize>>> watchAll();
|
// Stream<Either<GeoAssetFailure, List<GeoAssetWithFileSize>>> watchAll();
|
||||||
TaskEither<GeoAssetFailure, Unit> update(GeoAssetEntity geoAsset);
|
// TaskEither<GeoAssetFailure, Unit> update(GeoAssetEntity geoAsset);
|
||||||
TaskEither<GeoAssetFailure, Unit> markAsActive(GeoAssetEntity geoAsset);
|
// TaskEither<GeoAssetFailure, Unit> markAsActive(GeoAssetEntity geoAsset);
|
||||||
TaskEither<GeoAssetFailure, Unit> addRecommended();
|
// TaskEither<GeoAssetFailure, Unit> addRecommended();
|
||||||
}
|
// }
|
||||||
|
|
||||||
class GeoAssetRepositoryImpl
|
// class GeoAssetRepositoryImpl
|
||||||
with ExceptionHandler, InfraLogger
|
// with ExceptionHandler, InfraLogger
|
||||||
implements GeoAssetRepository {
|
// implements GeoAssetRepository {
|
||||||
GeoAssetRepositoryImpl({
|
// GeoAssetRepositoryImpl({
|
||||||
required this.geoAssetDataSource,
|
// required this.geoAssetDataSource,
|
||||||
required this.geoAssetPathResolver,
|
// required this.geoAssetPathResolver,
|
||||||
required this.httpClient,
|
// required this.httpClient,
|
||||||
});
|
// });
|
||||||
|
|
||||||
final GeoAssetDataSource geoAssetDataSource;
|
// final GeoAssetDataSource geoAssetDataSource;
|
||||||
final GeoAssetPathResolver geoAssetPathResolver;
|
// final GeoAssetPathResolver geoAssetPathResolver;
|
||||||
final DioHttpClient httpClient;
|
// final DioHttpClient httpClient;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
TaskEither<GeoAssetFailure, Unit> init() {
|
// TaskEither<GeoAssetFailure, Unit> init() {
|
||||||
return exceptionHandler(
|
// return exceptionHandler(
|
||||||
() async {
|
// () async {
|
||||||
loggy.debug("initializing");
|
// loggy.debug("initializing");
|
||||||
final geoipFile = geoAssetPathResolver.file(
|
// final geoipFile = geoAssetPathResolver.file(
|
||||||
defaultGeoip.providerName,
|
// defaultGeoip.providerName,
|
||||||
defaultGeoip.fileName,
|
// defaultGeoip.fileName,
|
||||||
);
|
// );
|
||||||
final geositeFile = geoAssetPathResolver.file(
|
// final geositeFile = geoAssetPathResolver.file(
|
||||||
defaultGeosite.providerName,
|
// defaultGeosite.providerName,
|
||||||
defaultGeosite.fileName,
|
// defaultGeosite.fileName,
|
||||||
);
|
// );
|
||||||
|
|
||||||
final dirExists = await geoAssetPathResolver.directory.exists();
|
// final dirExists = await geoAssetPathResolver.directory.exists();
|
||||||
if (!dirExists) {
|
// if (!dirExists) {
|
||||||
await geoAssetPathResolver.directory.create(recursive: true);
|
// await geoAssetPathResolver.directory.create(recursive: true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!dirExists || !await geoipFile.exists()) {
|
// if (!dirExists || !await geoipFile.exists()) {
|
||||||
final bundledGeoip = await rootBundle.load(Assets.core.geoip);
|
// final bundledGeoip = await rootBundle.load(Assets.core.geoip);
|
||||||
await geoipFile.writeAsBytes(bundledGeoip.buffer.asInt8List());
|
// await geoipFile.writeAsBytes(bundledGeoip.buffer.asInt8List());
|
||||||
}
|
// }
|
||||||
if (!dirExists || !await geositeFile.exists()) {
|
// if (!dirExists || !await geositeFile.exists()) {
|
||||||
final bundledGeosite = await rootBundle.load(Assets.core.geosite);
|
// final bundledGeosite = await rootBundle.load(Assets.core.geosite);
|
||||||
await geositeFile.writeAsBytes(bundledGeosite.buffer.asInt8List());
|
// await geositeFile.writeAsBytes(bundledGeosite.buffer.asInt8List());
|
||||||
}
|
// }
|
||||||
return right(unit);
|
// return right(unit);
|
||||||
},
|
// },
|
||||||
GeoAssetUnexpectedFailure.new,
|
// GeoAssetUnexpectedFailure.new,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
TaskEither<GeoAssetFailure, ({GeoAssetEntity geoip, GeoAssetEntity geosite})>
|
// TaskEither<GeoAssetFailure, ({GeoAssetEntity geoip, GeoAssetEntity geosite})>
|
||||||
getActivePair() {
|
// getActivePair() {
|
||||||
return exceptionHandler(
|
// return exceptionHandler(
|
||||||
() async {
|
// () async {
|
||||||
final geoip =
|
// final geoip =
|
||||||
await geoAssetDataSource.getActiveAssetByType(GeoAssetType.geoip);
|
// await geoAssetDataSource.getActiveAssetByType(GeoAssetType.geoip);
|
||||||
final geosite =
|
// final geosite =
|
||||||
await geoAssetDataSource.getActiveAssetByType(GeoAssetType.geosite);
|
// await geoAssetDataSource.getActiveAssetByType(GeoAssetType.geosite);
|
||||||
if (geoip == null || geosite == null) {
|
// if (geoip == null || geosite == null) {
|
||||||
return left(const GeoAssetFailure.activeAssetNotFound());
|
// return left(const GeoAssetFailure.activeAssetNotFound());
|
||||||
}
|
// }
|
||||||
return right((geoip: geoip.toEntity(), geosite: geosite.toEntity()));
|
// return right((geoip: geoip.toEntity(), geosite: geosite.toEntity()));
|
||||||
},
|
// },
|
||||||
GeoAssetUnexpectedFailure.new,
|
// GeoAssetUnexpectedFailure.new,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Stream<Either<GeoAssetFailure, List<GeoAssetWithFileSize>>> watchAll() {
|
// Stream<Either<GeoAssetFailure, List<GeoAssetWithFileSize>>> watchAll() {
|
||||||
final persistedStream = geoAssetDataSource
|
// final persistedStream = geoAssetDataSource
|
||||||
.watchAll()
|
// .watchAll()
|
||||||
.map((event) => event.map((e) => e.toEntity()));
|
// .map((event) => event.map((e) => e.toEntity()));
|
||||||
final filesStream = _watchGeoFiles();
|
// final filesStream = _watchGeoFiles();
|
||||||
|
|
||||||
return Rx.combineLatest2(
|
// return Rx.combineLatest2(
|
||||||
persistedStream,
|
// persistedStream,
|
||||||
filesStream,
|
// filesStream,
|
||||||
(assets, files) => assets.map(
|
// (assets, files) => assets.map(
|
||||||
(e) {
|
// (e) {
|
||||||
final path =
|
// final path =
|
||||||
geoAssetPathResolver.file(e.providerName, e.fileName).path;
|
// geoAssetPathResolver.file(e.providerName, e.fileName).path;
|
||||||
final file = files.firstOrNullWhere((e) => e.path == path);
|
// final file = files.firstOrNullWhere((e) => e.path == path);
|
||||||
final stat = file?.statSync();
|
// final stat = file?.statSync();
|
||||||
return (e, stat?.size);
|
// return (e, stat?.size);
|
||||||
},
|
// },
|
||||||
).toList(),
|
// ).toList(),
|
||||||
).handleExceptions(GeoAssetUnexpectedFailure.new);
|
// ).handleExceptions(GeoAssetUnexpectedFailure.new);
|
||||||
}
|
// }
|
||||||
|
|
||||||
Iterable<File> _geoFiles = [];
|
// Iterable<File> _geoFiles = [];
|
||||||
Stream<Iterable<File>> _watchGeoFiles() async* {
|
// Stream<Iterable<File>> _watchGeoFiles() async* {
|
||||||
yield await _readGeoFiles();
|
// yield await _readGeoFiles();
|
||||||
yield* Watcher(
|
// yield* Watcher(
|
||||||
geoAssetPathResolver.directory.path,
|
// geoAssetPathResolver.directory.path,
|
||||||
pollingDelay: const Duration(seconds: 1),
|
// pollingDelay: const Duration(seconds: 1),
|
||||||
).events.asyncMap((event) async {
|
// ).events.asyncMap((event) async {
|
||||||
await _readGeoFiles();
|
// await _readGeoFiles();
|
||||||
return _geoFiles;
|
// return _geoFiles;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<Iterable<File>> _readGeoFiles() async {
|
// Future<Iterable<File>> _readGeoFiles() async {
|
||||||
return _geoFiles = Directory(geoAssetPathResolver.directory.path)
|
// return _geoFiles = Directory(geoAssetPathResolver.directory.path)
|
||||||
.listSync()
|
// .listSync()
|
||||||
.whereType<File>()
|
// .whereType<File>()
|
||||||
.where((e) => e.extension == '.db');
|
// .where((e) => e.extension == '.db');
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
TaskEither<GeoAssetFailure, Unit> update(GeoAssetEntity geoAsset) {
|
// TaskEither<GeoAssetFailure, Unit> update(GeoAssetEntity geoAsset) {
|
||||||
return exceptionHandler(
|
// return exceptionHandler(
|
||||||
() async {
|
// () async {
|
||||||
loggy.debug(
|
// loggy.debug(
|
||||||
"checking latest release of [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
// "checking latest release of [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
||||||
);
|
// );
|
||||||
final response = await httpClient.get<Map>(geoAsset.repositoryUrl);
|
// final response = await httpClient.get<Map>(geoAsset.repositoryUrl);
|
||||||
if (response.statusCode != 200 || response.data == null) {
|
// if (response.statusCode != 200 || response.data == null) {
|
||||||
return left(
|
// return left(
|
||||||
GeoAssetUnexpectedFailure.new(
|
// GeoAssetUnexpectedFailure.new(
|
||||||
"invalid response",
|
// "invalid response",
|
||||||
StackTrace.current,
|
// StackTrace.current,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
final file =
|
// final file =
|
||||||
geoAssetPathResolver.file(geoAsset.providerName, geoAsset.name);
|
// geoAssetPathResolver.file(geoAsset.providerName, geoAsset.name);
|
||||||
final tagName = response.data!['tag_name'] as String;
|
// final tagName = response.data!['tag_name'] as String;
|
||||||
loggy.debug("latest release of [${geoAsset.name}]: [$tagName]");
|
// loggy.debug("latest release of [${geoAsset.name}]: [$tagName]");
|
||||||
if (tagName == geoAsset.version && await file.exists()) {
|
// if (tagName == geoAsset.version && await file.exists()) {
|
||||||
await geoAssetDataSource.patch(
|
// await geoAssetDataSource.patch(
|
||||||
geoAsset.id,
|
// geoAsset.id,
|
||||||
GeoAssetEntriesCompanion(lastCheck: Value(DateTime.now())),
|
// GeoAssetEntriesCompanion(lastCheck: Value(DateTime.now())),
|
||||||
);
|
// );
|
||||||
return left(const GeoAssetFailure.noUpdateAvailable());
|
// return left(const GeoAssetFailure.noUpdateAvailable());
|
||||||
}
|
// }
|
||||||
|
|
||||||
final assets = (response.data!['assets'] as List)
|
// final assets = (response.data!['assets'] as List)
|
||||||
.whereType<Map<String, dynamic>>();
|
// .whereType<Map<String, dynamic>>();
|
||||||
final asset =
|
// final asset =
|
||||||
assets.firstOrNullWhere((e) => e["name"] == geoAsset.name);
|
// assets.firstOrNullWhere((e) => e["name"] == geoAsset.name);
|
||||||
if (asset == null) {
|
// if (asset == null) {
|
||||||
return left(
|
// return left(
|
||||||
GeoAssetUnexpectedFailure.new(
|
// GeoAssetUnexpectedFailure.new(
|
||||||
"couldn't find [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
// "couldn't find [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
||||||
StackTrace.current,
|
// StackTrace.current,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
final downloadUrl = asset["browser_download_url"] as String;
|
// final downloadUrl = asset["browser_download_url"] as String;
|
||||||
loggy.debug("[${geoAsset.name}] download url: [$downloadUrl]");
|
// loggy.debug("[${geoAsset.name}] download url: [$downloadUrl]");
|
||||||
final tempPath = "${file.path}.tmp";
|
// final tempPath = "${file.path}.tmp";
|
||||||
await file.parent.create(recursive: true);
|
// await file.parent.create(recursive: true);
|
||||||
await httpClient.download(downloadUrl, tempPath);
|
// await httpClient.download(downloadUrl, tempPath);
|
||||||
await File(tempPath).rename(file.path);
|
// await File(tempPath).rename(file.path);
|
||||||
|
|
||||||
await geoAssetDataSource.patch(
|
// await geoAssetDataSource.patch(
|
||||||
geoAsset.id,
|
// geoAsset.id,
|
||||||
GeoAssetEntriesCompanion(
|
// GeoAssetEntriesCompanion(
|
||||||
version: Value(tagName),
|
// version: Value(tagName),
|
||||||
lastCheck: Value(DateTime.now()),
|
// lastCheck: Value(DateTime.now()),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
return right(unit);
|
// return right(unit);
|
||||||
},
|
// },
|
||||||
GeoAssetUnexpectedFailure.new,
|
// GeoAssetUnexpectedFailure.new,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
TaskEither<GeoAssetFailure, Unit> markAsActive(GeoAssetEntity geoAsset) {
|
// TaskEither<GeoAssetFailure, Unit> markAsActive(GeoAssetEntity geoAsset) {
|
||||||
return exceptionHandler(
|
// return exceptionHandler(
|
||||||
() async {
|
// () async {
|
||||||
await geoAssetDataSource.patch(
|
// await geoAssetDataSource.patch(
|
||||||
geoAsset.id,
|
// geoAsset.id,
|
||||||
const GeoAssetEntriesCompanion(active: Value(true)),
|
// const GeoAssetEntriesCompanion(active: Value(true)),
|
||||||
);
|
// );
|
||||||
return right(unit);
|
// return right(unit);
|
||||||
},
|
// },
|
||||||
GeoAssetUnexpectedFailure.new,
|
// GeoAssetUnexpectedFailure.new,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
TaskEither<GeoAssetFailure, Unit> addRecommended() {
|
// TaskEither<GeoAssetFailure, Unit> addRecommended() {
|
||||||
return exceptionHandler(
|
// return exceptionHandler(
|
||||||
() async {
|
// () async {
|
||||||
final persistedIds = await geoAssetDataSource
|
// final persistedIds = await geoAssetDataSource
|
||||||
.watchAll()
|
// .watchAll()
|
||||||
.first
|
// .first
|
||||||
.then((value) => value.map((e) => e.id));
|
// .then((value) => value.map((e) => e.id));
|
||||||
final missing =
|
// final missing =
|
||||||
recommendedGeoAssets.where((e) => !persistedIds.contains(e.id));
|
// recommendedGeoAssets.where((e) => !persistedIds.contains(e.id));
|
||||||
for (final geoAsset in missing) {
|
// for (final geoAsset in missing) {
|
||||||
await geoAssetDataSource.insert(geoAsset.toEntry());
|
// await geoAssetDataSource.insert(geoAsset.toEntry());
|
||||||
}
|
// }
|
||||||
return right(unit);
|
// return right(unit);
|
||||||
},
|
// },
|
||||||
GeoAssetUnexpectedFailure.new,
|
// GeoAssetUnexpectedFailure.new,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
|
|
||||||
/// default geoip asset bundled with the app
|
// /// default geoip asset bundled with the app
|
||||||
const defaultGeoip = GeoAssetEntity(
|
// const defaultGeoip = GeoAssetEntity(
|
||||||
id: "sing-box-geoip",
|
// id: "sing-box-geoip",
|
||||||
name: "geoip.db",
|
// name: "geoip.db",
|
||||||
type: GeoAssetType.geoip,
|
// type: GeoAssetType.geoip,
|
||||||
active: true,
|
// active: true,
|
||||||
providerName: "SagerNet/sing-geoip",
|
// providerName: "SagerNet/sing-geoip",
|
||||||
);
|
// );
|
||||||
|
|
||||||
/// default geosite asset bundled with the app
|
// /// default geosite asset bundled with the app
|
||||||
const defaultGeosite = GeoAssetEntity(
|
// const defaultGeosite = GeoAssetEntity(
|
||||||
id: "sing-box-geosite",
|
// id: "sing-box-geosite",
|
||||||
name: "geosite.db",
|
// name: "geosite.db",
|
||||||
type: GeoAssetType.geosite,
|
// type: GeoAssetType.geosite,
|
||||||
active: true,
|
// active: true,
|
||||||
providerName: "SagerNet/sing-geosite",
|
// providerName: "SagerNet/sing-geosite",
|
||||||
);
|
// );
|
||||||
|
|
||||||
const defaultGeoAssets = [defaultGeoip, defaultGeosite];
|
// const defaultGeoAssets = [defaultGeoip, defaultGeosite];
|
||||||
|
|
||||||
const recommendedGeoAssets = [
|
// const recommendedGeoAssets = [
|
||||||
...defaultGeoAssets,
|
// ...defaultGeoAssets,
|
||||||
GeoAssetEntity(
|
// GeoAssetEntity(
|
||||||
id: "chocolate4U-geoip",
|
// id: "chocolate4U-geoip",
|
||||||
name: "geoip.db",
|
// name: "geoip.db",
|
||||||
type: GeoAssetType.geoip,
|
// type: GeoAssetType.geoip,
|
||||||
active: false,
|
// active: false,
|
||||||
providerName: "Chocolate4U/Iran-sing-box-rules",
|
// providerName: "Chocolate4U/Iran-sing-box-rules",
|
||||||
),
|
// ),
|
||||||
GeoAssetEntity(
|
// GeoAssetEntity(
|
||||||
id: "chocolate4U-geosite",
|
// id: "chocolate4U-geosite",
|
||||||
name: "geosite.db",
|
// name: "geosite.db",
|
||||||
type: GeoAssetType.geosite,
|
// type: GeoAssetType.geosite,
|
||||||
active: false,
|
// active: false,
|
||||||
providerName: "Chocolate4U/Iran-sing-box-rules",
|
// providerName: "Chocolate4U/Iran-sing-box-rules",
|
||||||
),
|
// ),
|
||||||
GeoAssetEntity(
|
// GeoAssetEntity(
|
||||||
id: "soffchen-geoip",
|
// id: "soffchen-geoip",
|
||||||
name: "geoip.db",
|
// name: "geoip.db",
|
||||||
type: GeoAssetType.geoip,
|
// type: GeoAssetType.geoip,
|
||||||
active: false,
|
// active: false,
|
||||||
providerName: "soffchen/sing-geoip",
|
// providerName: "soffchen/sing-geoip",
|
||||||
),
|
// ),
|
||||||
GeoAssetEntity(
|
// GeoAssetEntity(
|
||||||
id: "soffchen-geosite",
|
// id: "soffchen-geosite",
|
||||||
name: "geosite.db",
|
// name: "geosite.db",
|
||||||
type: GeoAssetType.geosite,
|
// type: GeoAssetType.geosite,
|
||||||
active: false,
|
// active: false,
|
||||||
providerName: "soffchen/sing-geosite",
|
// providerName: "soffchen/sing-geosite",
|
||||||
),
|
// ),
|
||||||
];
|
// ];
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import 'package:fpdart/fpdart.dart';
|
// import 'package:fpdart/fpdart.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
// import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
import 'package:hiddify/utils/riverpod_utils.dart';
|
// import 'package:hiddify/utils/riverpod_utils.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
// import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'geo_asset_notifier.g.dart';
|
// part 'geo_asset_notifier.g.dart';
|
||||||
|
|
||||||
@riverpod
|
// @riverpod
|
||||||
class FetchGeoAsset extends _$FetchGeoAsset with AppLogger {
|
// class FetchGeoAsset extends _$FetchGeoAsset with AppLogger {
|
||||||
@override
|
// @override
|
||||||
Future<Unit?> build(String id) async {
|
// Future<Unit?> build(String id) async {
|
||||||
ref.disposeDelay(const Duration(seconds: 10));
|
// ref.disposeDelay(const Duration(seconds: 10));
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<void> fetch(GeoAssetEntity geoAsset) async {
|
// Future<void> fetch(GeoAssetEntity geoAsset) async {
|
||||||
state = const AsyncLoading();
|
// state = const AsyncLoading();
|
||||||
state = await AsyncValue.guard(
|
// state = await AsyncValue.guard(
|
||||||
() => ref
|
// () => ref
|
||||||
.read(geoAssetRepositoryProvider)
|
// .read(geoAssetRepositoryProvider)
|
||||||
.requireValue
|
// .requireValue
|
||||||
.update(geoAsset)
|
// .update(geoAsset)
|
||||||
.getOrElse(
|
// .getOrElse(
|
||||||
(failure) {
|
// (failure) {
|
||||||
loggy.warning("error updating geo asset $failure", failure);
|
// loggy.warning("error updating geo asset $failure", failure);
|
||||||
throw failure;
|
// throw failure;
|
||||||
},
|
// },
|
||||||
).run(),
|
// ).run(),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,55 +1,55 @@
|
|||||||
import 'package:dartx/dartx.dart';
|
// import 'package:dartx/dartx.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
||||||
import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
// import 'package:hiddify/features/geo_asset/data/geo_asset_repository.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
import 'package:hiddify/utils/custom_loggers.dart';
|
// import 'package:hiddify/utils/custom_loggers.dart';
|
||||||
import 'package:hiddify/utils/riverpod_utils.dart';
|
// import 'package:hiddify/utils/riverpod_utils.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
// import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'geo_assets_overview_notifier.g.dart';
|
// part 'geo_assets_overview_notifier.g.dart';
|
||||||
|
|
||||||
typedef GroupedRoutingAssets = ({
|
// typedef GroupedRoutingAssets = ({
|
||||||
List<GeoAssetWithFileSize> geoip,
|
// List<GeoAssetWithFileSize> geoip,
|
||||||
List<GeoAssetWithFileSize> geosite,
|
// List<GeoAssetWithFileSize> geosite,
|
||||||
});
|
// });
|
||||||
|
|
||||||
@riverpod
|
// @riverpod
|
||||||
class GeoAssetsOverviewNotifier extends _$GeoAssetsOverviewNotifier
|
// class GeoAssetsOverviewNotifier extends _$GeoAssetsOverviewNotifier
|
||||||
with AppLogger {
|
// with AppLogger {
|
||||||
@override
|
// @override
|
||||||
Stream<GroupedRoutingAssets> build() {
|
// Stream<GroupedRoutingAssets> build() {
|
||||||
ref.disposeDelay(const Duration(seconds: 5));
|
// ref.disposeDelay(const Duration(seconds: 5));
|
||||||
return ref.watch(geoAssetRepositoryProvider).requireValue.watchAll().map(
|
// return ref.watch(geoAssetRepositoryProvider).requireValue.watchAll().map(
|
||||||
(event) {
|
// (event) {
|
||||||
final grouped = event
|
// final grouped = event
|
||||||
.getOrElse((l) => throw l)
|
// .getOrElse((l) => throw l)
|
||||||
.groupBy((element) => element.$1.type);
|
// .groupBy((element) => element.$1.type);
|
||||||
return (
|
// return (
|
||||||
geoip: grouped.getOrElse(GeoAssetType.geoip, () => []),
|
// geoip: grouped.getOrElse(GeoAssetType.geoip, () => []),
|
||||||
geosite: grouped.getOrElse(GeoAssetType.geosite, () => []),
|
// geosite: grouped.getOrElse(GeoAssetType.geosite, () => []),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
GeoAssetRepository get _geoAssetRepo =>
|
// GeoAssetRepository get _geoAssetRepo =>
|
||||||
ref.read(geoAssetRepositoryProvider).requireValue;
|
// ref.read(geoAssetRepositoryProvider).requireValue;
|
||||||
|
|
||||||
Future<void> markAsActive(GeoAssetEntity geoAsset) async {
|
// Future<void> markAsActive(GeoAssetEntity geoAsset) async {
|
||||||
await _geoAssetRepo.markAsActive(geoAsset).getOrElse(
|
// await _geoAssetRepo.markAsActive(geoAsset).getOrElse(
|
||||||
(f) {
|
// (f) {
|
||||||
loggy.warning("error marking geo asset as active", f);
|
// loggy.warning("error marking geo asset as active", f);
|
||||||
throw f;
|
// throw f;
|
||||||
},
|
// },
|
||||||
).run();
|
// ).run();
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<void> addRecommended() async {
|
// Future<void> addRecommended() async {
|
||||||
await _geoAssetRepo.addRecommended().getOrElse(
|
// await _geoAssetRepo.addRecommended().getOrElse(
|
||||||
(f) {
|
// (f) {
|
||||||
loggy.warning("error adding recommended geo assets", f);
|
// loggy.warning("error adding recommended geo assets", f);
|
||||||
throw f;
|
// throw f;
|
||||||
},
|
// },
|
||||||
).run();
|
// ).run();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,102 +1,102 @@
|
|||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
// import 'package:gap/gap.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
// import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
// import 'package:hiddify/core/widget/adaptive_icon.dart';
|
||||||
import 'package:hiddify/core/widget/animated_visibility.dart';
|
// import 'package:hiddify/core/widget/animated_visibility.dart';
|
||||||
import 'package:hiddify/core/widget/tip_card.dart';
|
// import 'package:hiddify/core/widget/tip_card.dart';
|
||||||
import 'package:hiddify/features/geo_asset/overview/geo_assets_overview_notifier.dart';
|
// import 'package:hiddify/features/geo_asset/overview/geo_assets_overview_notifier.dart';
|
||||||
import 'package:hiddify/features/geo_asset/widget/geo_asset_tile.dart';
|
// import 'package:hiddify/features/geo_asset/widget/geo_asset_tile.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:sliver_tools/sliver_tools.dart';
|
// import 'package:sliver_tools/sliver_tools.dart';
|
||||||
|
|
||||||
class GeoAssetsOverviewPage extends HookConsumerWidget {
|
// class GeoAssetsOverviewPage extends HookConsumerWidget {
|
||||||
const GeoAssetsOverviewPage({super.key});
|
// const GeoAssetsOverviewPage({super.key});
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
// Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
// final t = ref.watch(translationsProvider);
|
||||||
final state = ref.watch(geoAssetsOverviewNotifierProvider);
|
// final state = ref.watch(geoAssetsOverviewNotifierProvider);
|
||||||
|
|
||||||
return Scaffold(
|
// return Scaffold(
|
||||||
body: CustomScrollView(
|
// body: CustomScrollView(
|
||||||
slivers: [
|
// slivers: [
|
||||||
SliverAppBar(
|
// SliverAppBar(
|
||||||
title: Text(t.settings.geoAssets.pageTitle),
|
// title: Text(t.settings.geoAssets.pageTitle),
|
||||||
pinned: true,
|
// pinned: true,
|
||||||
actions: [
|
// actions: [
|
||||||
PopupMenuButton(
|
// PopupMenuButton(
|
||||||
icon: Icon(AdaptiveIcon(context).more),
|
// icon: Icon(AdaptiveIcon(context).more),
|
||||||
itemBuilder: (context) {
|
// itemBuilder: (context) {
|
||||||
return [
|
// return [
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
child: Text(t.settings.geoAssets.addRecommended),
|
// child: Text(t.settings.geoAssets.addRecommended),
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
ref
|
// ref
|
||||||
.read(geoAssetsOverviewNotifierProvider.notifier)
|
// .read(geoAssetsOverviewNotifierProvider.notifier)
|
||||||
.addRecommended();
|
// .addRecommended();
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
];
|
// ];
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
if (state case AsyncData(value: (:final geoip, :final geosite)))
|
// if (state case AsyncData(value: (:final geoip, :final geosite)))
|
||||||
SliverPinnedHeader(
|
// SliverPinnedHeader(
|
||||||
child: AnimatedVisibility(
|
// child: AnimatedVisibility(
|
||||||
visible: (geoip + geosite)
|
// visible: (geoip + geosite)
|
||||||
.where((e) => e.$1.active && e.$2 == null)
|
// .where((e) => e.$1.active && e.$2 == null)
|
||||||
.isNotEmpty,
|
// .isNotEmpty,
|
||||||
axis: Axis.vertical,
|
// axis: Axis.vertical,
|
||||||
child:
|
// child:
|
||||||
TipCard(message: t.settings.geoAssets.missingGeoAssetsMsg),
|
// TipCard(message: t.settings.geoAssets.missingGeoAssetsMsg),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
switch (state) {
|
// switch (state) {
|
||||||
AsyncData(value: (:final geoip, :final geosite)) => MultiSliver(
|
// AsyncData(value: (:final geoip, :final geosite)) => MultiSliver(
|
||||||
children: [
|
// children: [
|
||||||
ListTile(
|
// ListTile(
|
||||||
title: Text("${t.settings.geoAssets.geoip} ›"),
|
// title: Text("${t.settings.geoAssets.geoip} ›"),
|
||||||
titleTextStyle: Theme.of(context).textTheme.headlineSmall,
|
// titleTextStyle: Theme.of(context).textTheme.headlineSmall,
|
||||||
dense: true,
|
// dense: true,
|
||||||
),
|
// ),
|
||||||
SliverList.builder(
|
// SliverList.builder(
|
||||||
itemBuilder: (context, index) {
|
// itemBuilder: (context, index) {
|
||||||
final geoAsset = geoip[index];
|
// final geoAsset = geoip[index];
|
||||||
return GeoAssetTile(
|
// return GeoAssetTile(
|
||||||
geoAsset,
|
// geoAsset,
|
||||||
onMarkAsActive: () => ref
|
// onMarkAsActive: () => ref
|
||||||
.read(geoAssetsOverviewNotifierProvider.notifier)
|
// .read(geoAssetsOverviewNotifierProvider.notifier)
|
||||||
.markAsActive(geoAsset.$1),
|
// .markAsActive(geoAsset.$1),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
itemCount: geoip.length,
|
// itemCount: geoip.length,
|
||||||
),
|
// ),
|
||||||
const Divider(indent: 16, endIndent: 16),
|
// const Divider(indent: 16, endIndent: 16),
|
||||||
ListTile(
|
// ListTile(
|
||||||
title: Text("${t.settings.geoAssets.geosite} ›"),
|
// title: Text("${t.settings.geoAssets.geosite} ›"),
|
||||||
titleTextStyle: Theme.of(context).textTheme.headlineSmall,
|
// titleTextStyle: Theme.of(context).textTheme.headlineSmall,
|
||||||
dense: true,
|
// dense: true,
|
||||||
),
|
// ),
|
||||||
SliverList.builder(
|
// SliverList.builder(
|
||||||
itemBuilder: (context, index) {
|
// itemBuilder: (context, index) {
|
||||||
final geoAsset = geosite[index];
|
// final geoAsset = geosite[index];
|
||||||
return GeoAssetTile(
|
// return GeoAssetTile(
|
||||||
geoAsset,
|
// geoAsset,
|
||||||
onMarkAsActive: () => ref
|
// onMarkAsActive: () => ref
|
||||||
.read(geoAssetsOverviewNotifierProvider.notifier)
|
// .read(geoAssetsOverviewNotifierProvider.notifier)
|
||||||
.markAsActive(geoAsset.$1),
|
// .markAsActive(geoAsset.$1),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
itemCount: geosite.length,
|
// itemCount: geosite.length,
|
||||||
),
|
// ),
|
||||||
const Gap(16),
|
// const Gap(16),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
_ => const SliverToBoxAdapter(),
|
// _ => const SliverToBoxAdapter(),
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,116 +1,116 @@
|
|||||||
import 'package:dartx/dartx.dart';
|
// import 'package:dartx/dartx.dart';
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:hiddify/core/localization/translations.dart';
|
// import 'package:hiddify/core/localization/translations.dart';
|
||||||
import 'package:hiddify/core/model/failures.dart';
|
// import 'package:hiddify/core/model/failures.dart';
|
||||||
import 'package:hiddify/core/widget/adaptive_icon.dart';
|
// import 'package:hiddify/core/widget/adaptive_icon.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||||
import 'package:hiddify/features/geo_asset/model/geo_asset_failure.dart';
|
// import 'package:hiddify/features/geo_asset/model/geo_asset_failure.dart';
|
||||||
import 'package:hiddify/features/geo_asset/notifier/geo_asset_notifier.dart';
|
// import 'package:hiddify/features/geo_asset/notifier/geo_asset_notifier.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
// import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
// import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:humanizer/humanizer.dart';
|
// import 'package:humanizer/humanizer.dart';
|
||||||
|
|
||||||
class GeoAssetTile extends HookConsumerWidget {
|
// class GeoAssetTile extends HookConsumerWidget {
|
||||||
GeoAssetTile(
|
// GeoAssetTile(
|
||||||
GeoAssetWithFileSize geoAssetWithFileSize, {
|
// GeoAssetWithFileSize geoAssetWithFileSize, {
|
||||||
super.key,
|
// super.key,
|
||||||
required this.onMarkAsActive,
|
// required this.onMarkAsActive,
|
||||||
}) : geoAsset = geoAssetWithFileSize.$1,
|
// }) : geoAsset = geoAssetWithFileSize.$1,
|
||||||
size = geoAssetWithFileSize.$2;
|
// size = geoAssetWithFileSize.$2;
|
||||||
|
|
||||||
final GeoAssetEntity geoAsset;
|
// final GeoAssetEntity geoAsset;
|
||||||
final int? size;
|
// final int? size;
|
||||||
final VoidCallback onMarkAsActive;
|
// final VoidCallback onMarkAsActive;
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
// Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final t = ref.watch(translationsProvider);
|
// final t = ref.watch(translationsProvider);
|
||||||
final fetchState = ref.watch(fetchGeoAssetProvider(geoAsset.id));
|
// final fetchState = ref.watch(fetchGeoAssetProvider(geoAsset.id));
|
||||||
final fileMissing = size == null;
|
// final fileMissing = size == null;
|
||||||
|
|
||||||
ref.listen(
|
// ref.listen(
|
||||||
fetchGeoAssetProvider(geoAsset.id),
|
// fetchGeoAssetProvider(geoAsset.id),
|
||||||
(_, next) {
|
// (_, next) {
|
||||||
switch (next) {
|
// switch (next) {
|
||||||
case AsyncError(:final error):
|
// case AsyncError(:final error):
|
||||||
if (error case GeoAssetNoUpdateAvailable()) {
|
// if (error case GeoAssetNoUpdateAvailable()) {
|
||||||
return CustomToast(t.failure.geoAssets.notUpdate).show(context);
|
// return CustomToast(t.failure.geoAssets.notUpdate).show(context);
|
||||||
}
|
// }
|
||||||
CustomAlertDialog.fromErr(t.presentError(error)).show(context);
|
// CustomAlertDialog.fromErr(t.presentError(error)).show(context);
|
||||||
case AsyncData(value: final _?):
|
// case AsyncData(value: final _?):
|
||||||
CustomToast.success(t.settings.geoAssets.successMsg).show(context);
|
// CustomToast.success(t.settings.geoAssets.successMsg).show(context);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
return ListTile(
|
// return ListTile(
|
||||||
title: Text.rich(
|
// title: Text.rich(
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
children: [
|
// children: [
|
||||||
TextSpan(text: geoAsset.name),
|
// TextSpan(text: geoAsset.name),
|
||||||
if (geoAsset.providerName.isNotBlank)
|
// if (geoAsset.providerName.isNotBlank)
|
||||||
TextSpan(text: " (${geoAsset.providerName})"),
|
// TextSpan(text: " (${geoAsset.providerName})"),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
isThreeLine: true,
|
// isThreeLine: true,
|
||||||
subtitle: fetchState.isLoading
|
// subtitle: fetchState.isLoading
|
||||||
? const LinearProgressIndicator()
|
// ? const LinearProgressIndicator()
|
||||||
: Row(
|
// : Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
if (geoAsset.version.isNotNullOrBlank)
|
// if (geoAsset.version.isNotNullOrBlank)
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
// padding: const EdgeInsetsDirectional.only(end: 8),
|
||||||
child: Text(
|
// child: Text(
|
||||||
t.settings.geoAssets.version(version: geoAsset.version!),
|
// t.settings.geoAssets.version(version: geoAsset.version!),
|
||||||
overflow: TextOverflow.ellipsis,
|
// overflow: TextOverflow.ellipsis,
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
else
|
// else
|
||||||
const SizedBox(),
|
// const SizedBox(),
|
||||||
Flexible(
|
// Flexible(
|
||||||
child: Text.rich(
|
// child: Text.rich(
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
children: [
|
// children: [
|
||||||
if (fileMissing)
|
// if (fileMissing)
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
text: t.settings.geoAssets.fileMissing,
|
// text: t.settings.geoAssets.fileMissing,
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.error,
|
// color: Theme.of(context).colorScheme.error,
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
else
|
// else
|
||||||
TextSpan(text: size?.bytes().toString()),
|
// TextSpan(text: size?.bytes().toString()),
|
||||||
if (geoAsset.lastCheck != null) ...[
|
// if (geoAsset.lastCheck != null) ...[
|
||||||
const TextSpan(text: " • "),
|
// const TextSpan(text: " • "),
|
||||||
TextSpan(text: geoAsset.lastCheck!.format()),
|
// TextSpan(text: geoAsset.lastCheck!.format()),
|
||||||
],
|
// ],
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
overflow: TextOverflow.ellipsis,
|
// overflow: TextOverflow.ellipsis,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
selected: geoAsset.active,
|
// selected: geoAsset.active,
|
||||||
onTap: onMarkAsActive,
|
// onTap: onMarkAsActive,
|
||||||
trailing: PopupMenuButton(
|
// trailing: PopupMenuButton(
|
||||||
icon: Icon(AdaptiveIcon(context).more),
|
// icon: Icon(AdaptiveIcon(context).more),
|
||||||
itemBuilder: (context) {
|
// itemBuilder: (context) {
|
||||||
return [
|
// return [
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
enabled: !fetchState.isLoading,
|
// enabled: !fetchState.isLoading,
|
||||||
onTap: () => ref
|
// onTap: () => ref
|
||||||
.read(FetchGeoAssetProvider(geoAsset.id).notifier)
|
// .read(FetchGeoAssetProvider(geoAsset.id).notifier)
|
||||||
.fetch(geoAsset),
|
// .fetch(geoAsset),
|
||||||
child: fileMissing
|
// child: fileMissing
|
||||||
? Text(t.settings.geoAssets.download)
|
// ? Text(t.settings.geoAssets.download)
|
||||||
: Text(t.settings.geoAssets.update),
|
// : Text(t.settings.geoAssets.update),
|
||||||
),
|
// ),
|
||||||
];
|
// ];
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ class AdvancedSettingTiles extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const RegionPrefTile(),
|
// const RegionPrefTile(),
|
||||||
ListTile(
|
// ListTile(
|
||||||
title: Text(t.settings.geoAssets.pageTitle),
|
// title: Text(t.settings.geoAssets.pageTitle),
|
||||||
leading: const Icon(
|
// leading: const Icon(
|
||||||
FluentIcons.arrow_routing_rectangle_multiple_24_regular,
|
// FluentIcons.arrow_routing_rectangle_multiple_24_regular,
|
||||||
),
|
// ),
|
||||||
onTap: () async {
|
// onTap: () async {
|
||||||
await const GeoAssetsRoute().push(context);
|
// // await const GeoAssetsRoute().push(context);
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
if (Platform.isAndroid) ...[
|
if (Platform.isAndroid) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.network.perAppProxyPageTitle),
|
title: Text(t.settings.network.perAppProxyPageTitle),
|
||||||
@@ -40,11 +40,8 @@ class AdvancedSettingTiles extends HookConsumerWidget {
|
|||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: perAppProxy,
|
value: perAppProxy,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
final newMode =
|
final newMode = perAppProxy ? PerAppProxyMode.off : PerAppProxyMode.exclude;
|
||||||
perAppProxy ? PerAppProxyMode.off : PerAppProxyMode.exclude;
|
await ref.read(Preferences.perAppProxyMode.notifier).update(newMode);
|
||||||
await ref
|
|
||||||
.read(Preferences.perAppProxyMode.notifier)
|
|
||||||
.update(newMode);
|
|
||||||
if (!perAppProxy && context.mounted) {
|
if (!perAppProxy && context.mounted) {
|
||||||
await const PerAppProxyRoute().push(context);
|
await const PerAppProxyRoute().push(context);
|
||||||
}
|
}
|
||||||
@@ -52,9 +49,7 @@ class AdvancedSettingTiles extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!perAppProxy) {
|
if (!perAppProxy) {
|
||||||
await ref
|
await ref.read(Preferences.perAppProxyMode.notifier).update(PerAppProxyMode.exclude);
|
||||||
.read(Preferences.perAppProxyMode.notifier)
|
|
||||||
.update(PerAppProxyMode.exclude);
|
|
||||||
}
|
}
|
||||||
if (context.mounted) await const PerAppProxyRoute().push(context);
|
if (context.mounted) await const PerAppProxyRoute().push(context);
|
||||||
},
|
},
|
||||||
@@ -66,9 +61,7 @@ class AdvancedSettingTiles extends HookConsumerWidget {
|
|||||||
value: !disableMemoryLimit,
|
value: !disableMemoryLimit,
|
||||||
secondary: const Icon(FluentIcons.developer_board_24_regular),
|
secondary: const Icon(FluentIcons.developer_board_24_regular),
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await ref
|
await ref.read(Preferences.disableMemoryLimit.notifier).update(!value);
|
||||||
.read(Preferences.disableMemoryLimit.notifier)
|
|
||||||
.update(!value);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (Platform.isIOS)
|
if (Platform.isIOS)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class GeneralSettingTiles extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const LocalePrefTile(),
|
// const LocalePrefTile(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.settings.general.themeMode),
|
title: Text(t.settings.general.themeMode),
|
||||||
subtitle: Text(themeMode.present(t)),
|
subtitle: Text(themeMode.present(t)),
|
||||||
@@ -48,9 +48,7 @@ class GeneralSettingTiles extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (selectedThemeMode != null) {
|
if (selectedThemeMode != null) {
|
||||||
await ref
|
await ref.read(themePreferencesProvider.notifier).changeThemeMode(selectedThemeMode);
|
||||||
.read(themePreferencesProvider.notifier)
|
|
||||||
.changeThemeMode(selectedThemeMode);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -67,17 +65,14 @@ class GeneralSettingTiles extends HookConsumerWidget {
|
|||||||
secondary: const Icon(FluentIcons.top_speed_24_regular),
|
secondary: const Icon(FluentIcons.top_speed_24_regular),
|
||||||
value: ref.watch(Preferences.dynamicNotification),
|
value: ref.watch(Preferences.dynamicNotification),
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await ref
|
await ref.read(Preferences.dynamicNotification.notifier).update(value);
|
||||||
.read(Preferences.dynamicNotification.notifier)
|
|
||||||
.update(value);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(t.settings.general.hapticFeedback),
|
title: Text(t.settings.general.hapticFeedback),
|
||||||
secondary: const Icon(FluentIcons.phone_vibrate_24_regular),
|
secondary: const Icon(FluentIcons.phone_vibrate_24_regular),
|
||||||
value: ref.watch(hapticServiceProvider),
|
value: ref.watch(hapticServiceProvider),
|
||||||
onChanged:
|
onChanged: ref.read(hapticServiceProvider.notifier).updatePreference,
|
||||||
ref.read(hapticServiceProvider.notifier).updatePreference,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (PlatformUtils.isDesktop) ...[
|
if (PlatformUtils.isDesktop) ...[
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
|||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||||
const factory SingboxConfigOption({
|
const factory SingboxConfigOption({
|
||||||
|
required String region,
|
||||||
|
required bool blockAds,
|
||||||
required bool executeConfigAsIs,
|
required bool executeConfigAsIs,
|
||||||
required LogLevel logLevel,
|
required LogLevel logLevel,
|
||||||
required bool resolveDestination,
|
required bool resolveDestination,
|
||||||
@@ -42,8 +44,8 @@ class SingboxConfigOption with _$SingboxConfigOption {
|
|||||||
required bool enableFakeDns,
|
required bool enableFakeDns,
|
||||||
required bool enableDnsRouting,
|
required bool enableDnsRouting,
|
||||||
required bool independentDnsCache,
|
required bool independentDnsCache,
|
||||||
required String geoipPath,
|
// required String geoipPath,
|
||||||
required String geositePath,
|
// required String geositePath,
|
||||||
required List<SingboxRule> rules,
|
required List<SingboxRule> rules,
|
||||||
required SingboxMuxOption mux,
|
required SingboxMuxOption mux,
|
||||||
required SingboxTlsTricks tlsTricks,
|
required SingboxTlsTricks tlsTricks,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class SingboxRule with _$SingboxRule {
|
|||||||
|
|
||||||
@JsonSerializable(fieldRename: FieldRename.kebab)
|
@JsonSerializable(fieldRename: FieldRename.kebab)
|
||||||
const factory SingboxRule({
|
const factory SingboxRule({
|
||||||
|
String? ruleSetUrl,
|
||||||
String? domains,
|
String? domains,
|
||||||
String? ip,
|
String? ip,
|
||||||
String? port,
|
String? port,
|
||||||
@@ -17,8 +18,7 @@ class SingboxRule with _$SingboxRule {
|
|||||||
@Default(RuleOutbound.proxy) RuleOutbound outbound,
|
@Default(RuleOutbound.proxy) RuleOutbound outbound,
|
||||||
}) = _SingboxRule;
|
}) = _SingboxRule;
|
||||||
|
|
||||||
factory SingboxRule.fromJson(Map<String, dynamic> json) =>
|
factory SingboxRule.fromJson(Map<String, dynamic> json) => _$SingboxRuleFromJson(json);
|
||||||
_$SingboxRuleFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RuleOutbound { proxy, bypass, block }
|
enum RuleOutbound { proxy, bypass, block }
|
||||||
|
|||||||
Reference in New Issue
Block a user