add warp config, update to flutter 1.22

This commit is contained in:
hiddify-com
2024-05-31 13:41:35 +02:00
parent 218c6d49bc
commit e2b67c317b
21 changed files with 423 additions and 385 deletions

View File

@@ -15,7 +15,7 @@ on:
env: env:
IS_GITHUB_ACTIONS: 1 IS_GITHUB_ACTIONS: 1
CHANNEL: "${{ inputs.channel }}" CHANNEL: "${{ inputs.channel }}"
FLUTTER_VERSION: '3.19.x' FLUTTER_VERSION: '3.22.x'
NDK_VERSION: r26b NDK_VERSION: r26b
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
TAG_NAME: "${{ inputs.tag-name }}" TAG_NAME: "${{ inputs.tag-name }}"

10
.vscode/launch.json vendored
View File

@@ -1,6 +1,16 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "go Package",
"type": "go",
"request": "launch",
"mode": "auto",
"cwd": "./libcore",
"program": "./libcore/cli/main.go",
"args": ["build","-c","a.txt","-d","b.txt","--full-config"] ,
"buildFlags": "-tags with_clash_api,with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server"
},
{ {
"name": "Hiddify Dev", "name": "Hiddify Dev",
"request": "launch", "request": "launch",

21
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"dart.lineLength": 250,
"[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.tabSize": 2,
"editor.rulers": [
250
],
"editor.detectIndentation": false,
"editor.selectionHighlight": false,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": "off"
},
"html.format.wrapLineLength": 250,
}

View File

@@ -78,8 +78,10 @@
"permissionRequest": "Permission to camera to scan QR Code" "permissionRequest": "Permission to camera to scan QR Code"
}, },
"manually": "Manual Entry", "manually": "Manual Entry",
"addWarp": "Add Warp",
"addingProfileMsg": "Adding Profile", "addingProfileMsg": "Adding Profile",
"failureMsg": "Failed to Add Profile" "failureMsg": "Failed to Add Profile"
}, },
"update": { "update": {
"buttonTxt": "Update", "buttonTxt": "Update",
@@ -162,8 +164,8 @@
"requiresRestartMsg": "For this to take effect restart the app", "requiresRestartMsg": "For this to take effect restart the app",
"experimental": "Experimental", "experimental": "Experimental",
"experimentalMsg": "Features with Experimental flag are still in development and might cause issues.", "experimentalMsg": "Features with Experimental flag are still in development and might cause issues.",
"exportOptions": "Export Options to Clipboard", "exportOptions": "Copy Anonymous Options to Clipboard",
"exportAllOptions": "Export Options to Clipboard (Debug)", "exportAllOptions": "Copy All Options to Clipboard",
"importOptions": "Import Options From Clipboard", "importOptions": "Import Options From Clipboard",
"importOptionsMsg": "This will rewrite all config options with provided values. Are you sure?", "importOptionsMsg": "This will rewrite all config options with provided values. Are you sure?",
"general": { "general": {

View File

@@ -162,8 +162,8 @@
"requiresRestartMsg": "برای اعمال این تنظیم، برنامه را دوباره راه‌اندازی کنید", "requiresRestartMsg": "برای اعمال این تنظیم، برنامه را دوباره راه‌اندازی کنید",
"experimental": "آزمایشی", "experimental": "آزمایشی",
"experimentalMsg": "تنظیماتی که عنوان آزمایشی دارند، هم‌چنان در دست توسعه هستند و فعال‌سازی آن‌ها می‌تواند باعث بروز مشکلاتی شود. ", "experimentalMsg": "تنظیماتی که عنوان آزمایشی دارند، هم‌چنان در دست توسعه هستند و فعال‌سازی آن‌ها می‌تواند باعث بروز مشکلاتی شود. ",
"exportOptions": "صادر کردن تنظیمات به کلیپ‌بورد", "exportOptions": "کپی تنظیمات ساده به کلیپ‌بورد",
"exportAllOptions": "صادر کردن تنظیمات به کلیپ‌بورد (اشکال‌زدایی)", "exportAllOptions": "کپی همه تنظیمات به کلیپ‌بورد",
"importOptions": "وارد کردن تنظیمات از کلیپ‌بورد", "importOptions": "وارد کردن تنظیمات از کلیپ‌بورد",
"importOptionsMsg": "این اقدام همه‌ی تنظیمات پیکربندی را با مقادیر اولیه بازنویسی می‌کند. آیا مطمئن هستید؟", "importOptionsMsg": "این اقدام همه‌ی تنظیمات پیکربندی را با مقادیر اولیه بازنویسی می‌کند. آیا مطمئن هستید؟",
"general": { "general": {

View File

@@ -162,8 +162,7 @@
"requiresRestartMsg": "Чтобы применить изменения, перезапустите приложение.", "requiresRestartMsg": "Чтобы применить изменения, перезапустите приложение.",
"experimental": "Экспериментальный", "experimental": "Экспериментальный",
"experimentalMsg": "Функции с флагом «Экспериментально» все еще находятся в разработке и могут вызвать проблемы.", "experimentalMsg": "Функции с флагом «Экспериментально» все еще находятся в разработке и могут вызвать проблемы.",
"exportOptions": "Экспорт параметров в буфер обмена",
"exportAllOptions": "Экспорт параметров в буфер обмена (отладка)",
"importOptions": "Импорт параметров из буфера обмена", "importOptions": "Импорт параметров из буфера обмена",
"importOptionsMsg": "Это перезапишет все параметры конфига предоставленными значениями. Вы уверены?", "importOptionsMsg": "Это перезапишет все параметры конфига предоставленными значениями. Вы уверены?",
"general": { "general": {

View File

@@ -162,8 +162,7 @@
"requiresRestartMsg": "要使其生效,请重新启动应用程序", "requiresRestartMsg": "要使其生效,请重新启动应用程序",
"experimental": "实验性选项", "experimental": "实验性选项",
"experimentalMsg": "带有实验标志的功能仍在开发中,可能会出现问题。", "experimentalMsg": "带有实验标志的功能仍在开发中,可能会出现问题。",
"exportOptions": "导出选项到剪切板",
"exportAllOptions": "导出选项到剪切板(用于调试)",
"importOptions": "从剪切板导入选项", "importOptions": "从剪切板导入选项",
"importOptionsMsg": "这将使用提供的值重写所有配置选项。您确定吗?", "importOptionsMsg": "这将使用提供的值重写所有配置选项。您确定吗?",
"general": { "general": {

View File

@@ -23,8 +23,7 @@ class AnalyticsController extends _$AnalyticsController with AppLogger {
return _preferences.getBool(enableAnalyticsPrefKey) ?? true; return _preferences.getBool(enableAnalyticsPrefKey) ?? true;
} }
SharedPreferences get _preferences => SharedPreferences get _preferences => ref.read(sharedPreferencesProvider).requireValue;
ref.read(sharedPreferencesProvider).requireValue;
Future<void> enableAnalytics() async { Future<void> enableAnalytics() async {
if (state case AsyncData(value: final enabled)) { if (state case AsyncData(value: final enabled)) {

View File

@@ -51,8 +51,7 @@ abstract class ConfigOptions {
validator: (value) => value.isNotBlank, validator: (value) => value.isNotBlank,
); );
static final remoteDnsDomainStrategy = static final remoteDnsDomainStrategy = PreferencesNotifier.create<DomainStrategy, String>(
PreferencesNotifier.create<DomainStrategy, String>(
"remote-dns-domain-strategy", "remote-dns-domain-strategy",
DomainStrategy.auto, DomainStrategy.auto,
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value), mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
@@ -65,8 +64,7 @@ abstract class ConfigOptions {
validator: (value) => value.isNotBlank, validator: (value) => value.isNotBlank,
); );
static final directDnsDomainStrategy = static final directDnsDomainStrategy = PreferencesNotifier.create<DomainStrategy, String>(
PreferencesNotifier.create<DomainStrategy, String>(
"direct-dns-domain-strategy", "direct-dns-domain-strategy",
DomainStrategy.auto, DomainStrategy.auto,
mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value), mapFrom: (value) => DomainStrategy.values.firstWhere((e) => e.key == value),
@@ -91,8 +89,7 @@ abstract class ConfigOptions {
validator: (value) => isPort(value.toString()), validator: (value) => isPort(value.toString()),
); );
static final tunImplementation = static final tunImplementation = PreferencesNotifier.create<TunImplementation, String>(
PreferencesNotifier.create<TunImplementation, String>(
"tun-implementation", "tun-implementation",
TunImplementation.mixed, TunImplementation.mixed,
mapFrom: TunImplementation.values.byName, mapFrom: TunImplementation.values.byName,
@@ -101,8 +98,7 @@ abstract class ConfigOptions {
static final mtu = PreferencesNotifier.create<int, int>("mtu", 9000); static final mtu = PreferencesNotifier.create<int, int>("mtu", 9000);
static final strictRoute = static final strictRoute = PreferencesNotifier.create<bool, bool>("strict-route", true);
PreferencesNotifier.create<bool, bool>("strict-route", true);
static final connectionTestUrl = PreferencesNotifier.create<String, String>( static final connectionTestUrl = PreferencesNotifier.create<String, String>(
"connection-test-url", "connection-test-url",
@@ -128,8 +124,7 @@ abstract class ConfigOptions {
validator: (value) => isPort(value.toString()), validator: (value) => isPort(value.toString()),
); );
static final bypassLan = static final bypassLan = PreferencesNotifier.create<bool, bool>("bypass-lan", false);
PreferencesNotifier.create<bool, bool>("bypass-lan", false);
static final allowConnectionFromLan = PreferencesNotifier.create<bool, bool>( static final allowConnectionFromLan = PreferencesNotifier.create<bool, bool>(
"allow-connection-from-lan", "allow-connection-from-lan",
@@ -156,16 +151,14 @@ abstract class ConfigOptions {
false, false,
); );
static final tlsFragmentSize = static final tlsFragmentSize = PreferencesNotifier.create<OptionalRange, String>(
PreferencesNotifier.create<OptionalRange, String>(
"tls-fragment-size", "tls-fragment-size",
const OptionalRange(min: 1, max: 500), const OptionalRange(min: 1, max: 500),
mapFrom: OptionalRange.parse, mapFrom: OptionalRange.parse,
mapTo: const OptionalRangeJsonConverter().toJson, mapTo: const OptionalRangeJsonConverter().toJson,
); );
static final tlsFragmentSleep = static final tlsFragmentSleep = PreferencesNotifier.create<OptionalRange, String>(
PreferencesNotifier.create<OptionalRange, String>(
"tls-fragment-sleep", "tls-fragment-sleep",
const OptionalRange(min: 0, max: 500), const OptionalRange(min: 0, max: 500),
mapFrom: OptionalRange.parse, mapFrom: OptionalRange.parse,
@@ -182,8 +175,7 @@ abstract class ConfigOptions {
false, false,
); );
static final tlsPaddingSize = static final tlsPaddingSize = PreferencesNotifier.create<OptionalRange, String>(
PreferencesNotifier.create<OptionalRange, String>(
"tls-padding-size", "tls-padding-size",
const OptionalRange(min: 1, max: 1500), const OptionalRange(min: 1, max: 1500),
mapFrom: OptionalRange.parse, mapFrom: OptionalRange.parse,
@@ -218,8 +210,7 @@ abstract class ConfigOptions {
false, false,
); );
static final warpDetourMode = static final warpDetourMode = PreferencesNotifier.create<WarpDetourMode, String>(
PreferencesNotifier.create<WarpDetourMode, String>(
"warp-detour-mode", "warp-detour-mode",
WarpDetourMode.proxyOverWarp, WarpDetourMode.proxyOverWarp,
mapFrom: WarpDetourMode.values.byName, mapFrom: WarpDetourMode.values.byName,
@@ -230,16 +221,28 @@ abstract class ConfigOptions {
"warp-license-key", "warp-license-key",
"", "",
); );
static final warp2LicenseKey = PreferencesNotifier.create<String, String>(
"warp2s-license-key",
"",
);
static final warpAccountId = PreferencesNotifier.create<String, String>( static final warpAccountId = PreferencesNotifier.create<String, String>(
"warp-account-id", "warp-account-id",
"", "",
); );
static final warp2AccountId = PreferencesNotifier.create<String, String>(
"warp2-account-id",
"",
);
static final warpAccessToken = PreferencesNotifier.create<String, String>( static final warpAccessToken = PreferencesNotifier.create<String, String>(
"warp-access-token", "warp-access-token",
"", "",
); );
static final warp2AccessToken = PreferencesNotifier.create<String, String>(
"warp2-access-token",
"",
);
static final warpCleanIp = PreferencesNotifier.create<String, String>( static final warpCleanIp = PreferencesNotifier.create<String, String>(
"warp-clean-ip", "warp-clean-ip",
@@ -259,8 +262,7 @@ abstract class ConfigOptions {
mapTo: const OptionalRangeJsonConverter().toJson, mapTo: const OptionalRangeJsonConverter().toJson,
); );
static final warpNoiseDelay = static final warpNoiseDelay = PreferencesNotifier.create<OptionalRange, String>(
PreferencesNotifier.create<OptionalRange, String>(
"warp-noise-delay", "warp-noise-delay",
const OptionalRange(min: 20, max: 200), const OptionalRange(min: 20, max: 200),
mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true), mapFrom: (value) => OptionalRange.parse(value, allowEmpty: true),
@@ -271,6 +273,10 @@ abstract class ConfigOptions {
"warp-wireguard-config", "warp-wireguard-config",
"", "",
); );
static final warp2WireguardConfig = PreferencesNotifier.create<String, String>(
"warp2-wireguard-config",
"",
);
static final hasExperimentalFeatures = Provider.autoDispose<bool>( static final hasExperimentalFeatures = Provider.autoDispose<bool>(
(ref) { (ref) {
@@ -278,13 +284,7 @@ abstract class ConfigOptions {
if (PlatformUtils.isDesktop && mode == ServiceMode.tun) { if (PlatformUtils.isDesktop && mode == ServiceMode.tun) {
return true; return true;
} }
if (ref.watch(enableTlsFragment) || if (ref.watch(enableTlsFragment) || ref.watch(enableTlsMixedSniCase) || ref.watch(enableTlsPadding) || ref.watch(enableMux) || ref.watch(enableWarp) || ref.watch(bypassLan) || ref.watch(allowConnectionFromLan)) {
ref.watch(enableTlsMixedSniCase) ||
ref.watch(enableTlsPadding) ||
ref.watch(enableMux) ||
ref.watch(enableWarp) ||
ref.watch(bypassLan) ||
ref.watch(allowConnectionFromLan)) {
return true; return true;
} }
@@ -298,10 +298,13 @@ abstract class ConfigOptions {
"warp.access-token", "warp.access-token",
"warp.account-id", "warp.account-id",
"warp.wireguard-config", "warp.wireguard-config",
"warp2.license-key",
"warp2.access-token",
"warp2.account-id",
"warp2.wireguard-config",
}; };
static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>> static final Map<String, StateNotifierProvider<PreferencesNotifier, dynamic>> preferences = {
preferences = {
"service-mode": serviceMode, "service-mode": serviceMode,
"log-level": logLevel, "log-level": logLevel,
"resolve-destination": resolveDestination, "resolve-destination": resolveDestination,
@@ -348,6 +351,10 @@ abstract class ConfigOptions {
"warp.noise": warpNoise, "warp.noise": warpNoise,
"warp.noise-delay": warpNoiseDelay, "warp.noise-delay": warpNoiseDelay,
"warp.wireguard-config": warpWireguardConfig, "warp.wireguard-config": warpWireguardConfig,
"warp2.license-key": warp2LicenseKey,
"warp2.account-id": warp2AccountId,
"warp2.access-token": warp2AccessToken,
"warp2.wireguard-config": warp2WireguardConfig,
}; };
static final singboxConfigOptions = FutureProvider<SingboxConfigOption>( static final singboxConfigOptions = FutureProvider<SingboxConfigOption>(
@@ -386,8 +393,7 @@ abstract class ConfigOptions {
}; };
final geoAssetsRepo = await ref.watch(geoAssetRepositoryProvider.future); final geoAssetsRepo = await ref.watch(geoAssetRepositoryProvider.future);
final geoAssets = final geoAssets = await geoAssetsRepo.getActivePair().getOrElse((l) => throw l).run();
await geoAssetsRepo.getActivePair().getOrElse((l) => throw l).run();
final mode = ref.watch(serviceMode); final mode = ref.watch(serviceMode);
return SingboxConfigOption( return SingboxConfigOption(
@@ -443,6 +449,18 @@ abstract class ConfigOptions {
noise: ref.watch(warpNoise), noise: ref.watch(warpNoise),
noiseDelay: ref.watch(warpNoiseDelay), noiseDelay: ref.watch(warpNoiseDelay),
), ),
warp2: SingboxWarpOption(
enable: ref.watch(enableWarp),
mode: ref.watch(warpDetourMode),
wireguardConfig: ref.watch(warp2WireguardConfig),
licenseKey: ref.watch(warp2LicenseKey),
accountId: ref.watch(warp2AccountId),
accessToken: ref.watch(warp2AccessToken),
cleanIp: ref.watch(warpCleanIp),
cleanPort: ref.watch(warpPort),
noise: ref.watch(warpNoise),
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,
@@ -470,8 +488,7 @@ class ConfigOptionRepository with ExceptionHandler, InfraLogger {
final GeoAssetRepository geoAssetRepository; final GeoAssetRepository geoAssetRepository;
final GeoAssetPathResolver geoAssetPathResolver; final GeoAssetPathResolver geoAssetPathResolver;
TaskEither<ConfigOptionFailure, SingboxConfigOption> TaskEither<ConfigOptionFailure, SingboxConfigOption> getFullSingboxConfigOption() {
getFullSingboxConfigOption() {
return exceptionHandler( return exceptionHandler(
() async { () async {
return right(await getConfigOptions()); return right(await getConfigOptions());

View File

@@ -26,20 +26,14 @@ class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
return WarpOptions( return WarpOptions(
consentGiven: consent, consentGiven: consent,
configGeneration: hasWarpConfig configGeneration: hasWarpConfig ? const AsyncValue.data("") : AsyncError(const MissingWarpConfigFailure(), StackTrace.current),
? const AsyncValue.data("")
: AsyncError(const MissingWarpConfigFailure(), StackTrace.current),
); );
} }
SharedPreferences get _prefs => SharedPreferences get _prefs => ref.read(sharedPreferencesProvider).requireValue;
ref.read(sharedPreferencesProvider).requireValue;
Future<void> agree() async { Future<void> agree() async {
await ref await ref.read(sharedPreferencesProvider).requireValue.setBool(warpConsentGiven, true);
.read(sharedPreferencesProvider)
.requireValue
.setBool(warpConsentGiven, true);
state = state.copyWith(consentGiven: true); state = state.copyWith(consentGiven: true);
await generateWarpConfig(); await generateWarpConfig();
} }
@@ -59,15 +53,33 @@ class WarpOptionNotifier extends _$WarpOptionNotifier with AppLogger {
.getOrElse((l) => throw l) .getOrElse((l) => throw l)
.run(); .run();
await ref await ref.read(ConfigOptions.warpAccountId.notifier).update(warp.accountId);
.read(ConfigOptions.warpAccountId.notifier) await ref.read(ConfigOptions.warpAccessToken.notifier).update(warp.accessToken);
.update(warp.accountId); await ref.read(ConfigOptions.warpWireguardConfig.notifier).update(warp.wireguardConfig);
await ref return warp.log;
.read(ConfigOptions.warpAccessToken.notifier) });
.update(warp.accessToken);
await ref state = state.copyWith(configGeneration: result);
.read(ConfigOptions.warpWireguardConfig.notifier) }
.update(warp.wireguardConfig);
Future<void> generateWarp2Config() async {
if (state.configGeneration.isLoading) return;
state = state.copyWith(configGeneration: const AsyncLoading());
final result = await AsyncValue.guard(() async {
final warp = await ref
.read(singboxServiceProvider)
.generateWarpConfig(
licenseKey: ref.read(ConfigOptions.warpLicenseKey),
previousAccountId: ref.read(ConfigOptions.warp2AccountId),
previousAccessToken: ref.read(ConfigOptions.warp2AccessToken),
)
.getOrElse((l) => throw l)
.run();
await ref.read(ConfigOptions.warp2AccountId.notifier).update(warp.accountId);
await ref.read(ConfigOptions.warp2AccessToken.notifier).update(warp.accessToken);
await ref.read(ConfigOptions.warp2WireguardConfig.notifier).update(warp.wireguardConfig);
return warp.log; return warp.log;
}); });

View File

@@ -23,17 +23,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:humanizer/humanizer.dart'; import 'package:humanizer/humanizer.dart';
enum ConfigOptionSection { enum ConfigOptionSection {
warp; warp,
fragment;
static final _warpKey = GlobalKey(debugLabel: "warp-section-key"); static final _warpKey = GlobalKey(debugLabel: "warp-section-key");
static final _fragmentKey = GlobalKey(debugLabel: "fragment-section-key");
GlobalKey get key => switch (this) { _ => _warpKey }; GlobalKey get key => switch (this) {
ConfigOptionSection.warp => _warpKey,
ConfigOptionSection.fragment => _fragmentKey,
};
} }
class ConfigOptionsPage extends HookConsumerWidget { class ConfigOptionsPage extends HookConsumerWidget {
ConfigOptionsPage({super.key, String? section}) ConfigOptionsPage({super.key, String? section}) : section = section != null ? ConfigOptionSection.values.byName(section) : null;
: section =
section != null ? ConfigOptionSection.values.byName(section) : null;
final ConfigOptionSection? section; final ConfigOptionSection? section;
@@ -47,14 +50,10 @@ class ConfigOptionsPage extends HookConsumerWidget {
if (section != null) { if (section != null) {
WidgetsBinding.instance.addPostFrameCallback( WidgetsBinding.instance.addPostFrameCallback(
(_) { (_) {
final box = final box = section!.key.currentContext?.findRenderObject() as RenderBox?;
section!.key.currentContext?.findRenderObject() as RenderBox?;
final offset = box?.localToGlobal(Offset.zero); final offset = box?.localToGlobal(Offset.zero);
if (offset == null) return; if (offset == null) return;
final height = scrollController.offset + final height = scrollController.offset + offset.dy - MediaQueryData.fromView(View.of(context)).padding.top - kToolbarHeight;
offset.dy -
MediaQueryData.fromView(View.of(context)).padding.top -
kToolbarHeight;
scrollController.animateTo( scrollController.animateTo(
height, height,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
@@ -83,36 +82,26 @@ class ConfigOptionsPage extends HookConsumerWidget {
itemBuilder: (context) { itemBuilder: (context) {
return [ return [
PopupMenuItem( PopupMenuItem(
onTap: () async => ref onTap: () async => ref.read(configOptionNotifierProvider.notifier).exportJsonToClipboard().then((success) {
.read(configOptionNotifierProvider.notifier)
.exportJsonToClipboard()
.then((success) {
if (success) { if (success) {
ref ref.read(inAppNotificationControllerProvider).showSuccessToast(
.read(inAppNotificationControllerProvider)
.showSuccessToast(
t.general.clipboardExportSuccessMsg, t.general.clipboardExportSuccessMsg,
); );
} }
}), }),
child: Text(t.settings.exportOptions), child: Text(t.settings.exportOptions),
), ),
if (ref.watch(debugModeNotifierProvider)) // if (ref.watch(debugModeNotifierProvider))
PopupMenuItem( PopupMenuItem(
onTap: () async => ref onTap: () async => ref.read(configOptionNotifierProvider.notifier).exportJsonToClipboard(excludePrivate: false).then((success) {
.read(configOptionNotifierProvider.notifier) if (success) {
.exportJsonToClipboard(excludePrivate: false) ref.read(inAppNotificationControllerProvider).showSuccessToast(
.then((success) { t.general.clipboardExportSuccessMsg,
if (success) { );
ref }
.read(inAppNotificationControllerProvider) }),
.showSuccessToast( child: Text(t.settings.exportAllOptions),
t.general.clipboardExportSuccessMsg, ),
);
}
}),
child: Text(t.settings.exportAllOptions),
),
PopupMenuItem( PopupMenuItem(
onTap: () async { onTap: () async {
final shouldImport = await showConfirmationDialog( final shouldImport = await showConfirmationDialog(
@@ -121,9 +110,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
message: t.settings.importOptionsMsg, message: t.settings.importOptionsMsg,
); );
if (shouldImport) { if (shouldImport) {
await ref await ref.read(configOptionNotifierProvider.notifier).importFromClipboard();
.read(configOptionNotifierProvider.notifier)
.importFromClipboard();
} }
}, },
child: Text(t.settings.importOptions), child: Text(t.settings.importOptions),
@@ -131,9 +118,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
PopupMenuItem( PopupMenuItem(
child: Text(t.config.resetBtn), child: Text(t.config.resetBtn),
onTap: () async { onTap: () async {
await ref await ref.read(configOptionNotifierProvider.notifier).resetOption();
.read(configOptionNotifierProvider.notifier)
.resetOption();
}, },
), ),
]; ];
@@ -158,15 +143,12 @@ class ConfigOptionsPage extends HookConsumerWidget {
SwitchListTile( SwitchListTile(
title: Text(experimental(t.config.bypassLan)), title: Text(experimental(t.config.bypassLan)),
value: ref.watch(ConfigOptions.bypassLan), value: ref.watch(ConfigOptions.bypassLan),
onChanged: onChanged: ref.watch(ConfigOptions.bypassLan.notifier).update,
ref.watch(ConfigOptions.bypassLan.notifier).update,
), ),
SwitchListTile( SwitchListTile(
title: Text(t.config.resolveDestination), title: Text(t.config.resolveDestination),
value: ref.watch(ConfigOptions.resolveDestination), value: ref.watch(ConfigOptions.resolveDestination),
onChanged: ref onChanged: ref.watch(ConfigOptions.resolveDestination.notifier).update,
.watch(ConfigOptions.resolveDestination.notifier)
.update,
), ),
ChoicePreferenceWidget( ChoicePreferenceWidget(
selected: ref.watch(ConfigOptions.ipv6Mode), selected: ref.watch(ConfigOptions.ipv6Mode),
@@ -179,28 +161,24 @@ class ConfigOptionsPage extends HookConsumerWidget {
SettingsSection(t.config.section.dns), SettingsSection(t.config.section.dns),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.remoteDnsAddress), value: ref.watch(ConfigOptions.remoteDnsAddress),
preferences: preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier),
ref.watch(ConfigOptions.remoteDnsAddress.notifier),
title: t.config.remoteDnsAddress, title: t.config.remoteDnsAddress,
), ),
ChoicePreferenceWidget( ChoicePreferenceWidget(
selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy), selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy),
preferences: ref preferences: ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
.watch(ConfigOptions.remoteDnsDomainStrategy.notifier),
choices: DomainStrategy.values, choices: DomainStrategy.values,
title: t.config.remoteDnsDomainStrategy, title: t.config.remoteDnsDomainStrategy,
presentChoice: (value) => value.displayName, presentChoice: (value) => value.displayName,
), ),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.directDnsAddress), value: ref.watch(ConfigOptions.directDnsAddress),
preferences: preferences: ref.watch(ConfigOptions.directDnsAddress.notifier),
ref.watch(ConfigOptions.directDnsAddress.notifier),
title: t.config.directDnsAddress, title: t.config.directDnsAddress,
), ),
ChoicePreferenceWidget( ChoicePreferenceWidget(
selected: ref.watch(ConfigOptions.directDnsDomainStrategy), selected: ref.watch(ConfigOptions.directDnsDomainStrategy),
preferences: ref preferences: ref.watch(ConfigOptions.directDnsDomainStrategy.notifier),
.watch(ConfigOptions.directDnsDomainStrategy.notifier),
choices: DomainStrategy.values, choices: DomainStrategy.values,
title: t.config.directDnsDomainStrategy, title: t.config.directDnsDomainStrategy,
presentChoice: (value) => value.displayName, presentChoice: (value) => value.displayName,
@@ -208,9 +186,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
SwitchListTile( SwitchListTile(
title: Text(t.config.enableDnsRouting), title: Text(t.config.enableDnsRouting),
value: ref.watch(ConfigOptions.enableDnsRouting), value: ref.watch(ConfigOptions.enableDnsRouting),
onChanged: ref onChanged: ref.watch(ConfigOptions.enableDnsRouting.notifier).update,
.watch(ConfigOptions.enableDnsRouting.notifier)
.update,
), ),
// const SettingsDivider(), // const SettingsDivider(),
// SettingsSection(experimental(t.config.section.mux)), // SettingsSection(experimental(t.config.section.mux)),
@@ -247,13 +223,11 @@ class ConfigOptionsPage extends HookConsumerWidget {
SwitchListTile( SwitchListTile(
title: Text(t.config.strictRoute), title: Text(t.config.strictRoute),
value: ref.watch(ConfigOptions.strictRoute), value: ref.watch(ConfigOptions.strictRoute),
onChanged: onChanged: ref.watch(ConfigOptions.strictRoute.notifier).update,
ref.watch(ConfigOptions.strictRoute.notifier).update,
), ),
ChoicePreferenceWidget( ChoicePreferenceWidget(
selected: ref.watch(ConfigOptions.tunImplementation), selected: ref.watch(ConfigOptions.tunImplementation),
preferences: preferences: ref.watch(ConfigOptions.tunImplementation.notifier),
ref.watch(ConfigOptions.tunImplementation.notifier),
choices: TunImplementation.values, choices: TunImplementation.values,
title: t.config.tunImplementation, title: t.config.tunImplementation,
presentChoice: (value) => value.name, presentChoice: (value) => value.name,
@@ -287,25 +261,21 @@ class ConfigOptionsPage extends HookConsumerWidget {
experimental(t.config.allowConnectionFromLan), experimental(t.config.allowConnectionFromLan),
), ),
value: ref.watch(ConfigOptions.allowConnectionFromLan), value: ref.watch(ConfigOptions.allowConnectionFromLan),
onChanged: ref onChanged: ref.read(ConfigOptions.allowConnectionFromLan.notifier).update,
.read(ConfigOptions.allowConnectionFromLan.notifier)
.update,
), ),
const SettingsDivider(), const SettingsDivider(),
SettingsSection( SettingsSection(
experimental(t.config.section.tlsTricks), experimental(t.config.section.tlsTricks),
key: ConfigOptionSection._fragmentKey,
), ),
SwitchListTile( SwitchListTile(
title: Text(t.config.enableTlsFragment), title: Text(t.config.enableTlsFragment),
value: ref.watch(ConfigOptions.enableTlsFragment), value: ref.watch(ConfigOptions.enableTlsFragment),
onChanged: ref onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
.watch(ConfigOptions.enableTlsFragment.notifier)
.update,
), ),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.tlsFragmentSize), value: ref.watch(ConfigOptions.tlsFragmentSize),
preferences: preferences: ref.watch(ConfigOptions.tlsFragmentSize.notifier),
ref.watch(ConfigOptions.tlsFragmentSize.notifier),
title: t.config.tlsFragmentSize, title: t.config.tlsFragmentSize,
inputToValue: OptionalRange.tryParse, inputToValue: OptionalRange.tryParse,
presentValue: (value) => value.present(t), presentValue: (value) => value.present(t),
@@ -313,8 +283,7 @@ class ConfigOptionsPage extends HookConsumerWidget {
), ),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.tlsFragmentSleep), value: ref.watch(ConfigOptions.tlsFragmentSleep),
preferences: preferences: ref.watch(ConfigOptions.tlsFragmentSleep.notifier),
ref.watch(ConfigOptions.tlsFragmentSleep.notifier),
title: t.config.tlsFragmentSleep, title: t.config.tlsFragmentSleep,
inputToValue: OptionalRange.tryParse, inputToValue: OptionalRange.tryParse,
presentValue: (value) => value.present(t), presentValue: (value) => value.present(t),
@@ -323,21 +292,16 @@ class ConfigOptionsPage extends HookConsumerWidget {
SwitchListTile( SwitchListTile(
title: Text(t.config.enableTlsMixedSniCase), title: Text(t.config.enableTlsMixedSniCase),
value: ref.watch(ConfigOptions.enableTlsMixedSniCase), value: ref.watch(ConfigOptions.enableTlsMixedSniCase),
onChanged: ref onChanged: ref.watch(ConfigOptions.enableTlsMixedSniCase.notifier).update,
.watch(ConfigOptions.enableTlsMixedSniCase.notifier)
.update,
), ),
SwitchListTile( SwitchListTile(
title: Text(t.config.enableTlsPadding), title: Text(t.config.enableTlsPadding),
value: ref.watch(ConfigOptions.enableTlsPadding), value: ref.watch(ConfigOptions.enableTlsPadding),
onChanged: ref onChanged: ref.watch(ConfigOptions.enableTlsPadding.notifier).update,
.watch(ConfigOptions.enableTlsPadding.notifier)
.update,
), ),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.tlsPaddingSize), value: ref.watch(ConfigOptions.tlsPaddingSize),
preferences: preferences: ref.watch(ConfigOptions.tlsPaddingSize.notifier),
ref.watch(ConfigOptions.tlsPaddingSize.notifier),
title: t.config.tlsPaddingSize, title: t.config.tlsPaddingSize,
inputToValue: OptionalRange.tryParse, inputToValue: OptionalRange.tryParse,
presentValue: (value) => value.format(), presentValue: (value) => value.format(),
@@ -350,38 +314,26 @@ class ConfigOptionsPage extends HookConsumerWidget {
SettingsSection(t.config.section.misc), SettingsSection(t.config.section.misc),
ValuePreferenceWidget( ValuePreferenceWidget(
value: ref.watch(ConfigOptions.connectionTestUrl), value: ref.watch(ConfigOptions.connectionTestUrl),
preferences: preferences: ref.watch(ConfigOptions.connectionTestUrl.notifier),
ref.watch(ConfigOptions.connectionTestUrl.notifier),
title: t.config.connectionTestUrl, title: t.config.connectionTestUrl,
), ),
ListTile( ListTile(
title: Text(t.config.urlTestInterval), title: Text(t.config.urlTestInterval),
subtitle: Text( subtitle: Text(
ref ref.watch(ConfigOptions.urlTestInterval).toApproximateTime(isRelativeToNow: false),
.watch(ConfigOptions.urlTestInterval)
.toApproximateTime(isRelativeToNow: false),
), ),
onTap: () async { onTap: () async {
final urlTestInterval = await SettingsSliderDialog( final urlTestInterval = await SettingsSliderDialog(
title: t.config.urlTestInterval, title: t.config.urlTestInterval,
initialValue: ref initialValue: ref.watch(ConfigOptions.urlTestInterval).inMinutes.coerceIn(0, 60).toDouble(),
.watch(ConfigOptions.urlTestInterval) onReset: ref.read(ConfigOptions.urlTestInterval.notifier).reset,
.inMinutes
.coerceIn(0, 60)
.toDouble(),
onReset: ref
.read(ConfigOptions.urlTestInterval.notifier)
.reset,
min: 1, min: 1,
max: 60, max: 60,
divisions: 60, divisions: 60,
labelGen: (value) => Duration(minutes: value.toInt()) labelGen: (value) => Duration(minutes: value.toInt()).toApproximateTime(isRelativeToNow: false),
.toApproximateTime(isRelativeToNow: false),
).show(context); ).show(context);
if (urlTestInterval == null) return; if (urlTestInterval == null) return;
await ref await ref.read(ConfigOptions.urlTestInterval.notifier).update(Duration(minutes: urlTestInterval.toInt()));
.read(ConfigOptions.urlTestInterval.notifier)
.update(Duration(minutes: urlTestInterval.toInt()));
}, },
), ),
ValuePreferenceWidget( ValuePreferenceWidget(

View File

@@ -63,17 +63,15 @@ class WarpOptionsTiles extends HookConsumerWidget {
AsyncLoading() => const LinearProgressIndicator(), AsyncLoading() => const LinearProgressIndicator(),
AsyncError() => Text( AsyncError() => Text(
t.config.missingWarpConfig, t.config.missingWarpConfig,
style: style: TextStyle(color: Theme.of(context).colorScheme.error),
TextStyle(color: Theme.of(context).colorScheme.error),
), ),
_ => null, _ => null,
} }
: null, : null,
enabled: canChangeOptions, enabled: canChangeOptions,
onTap: () async { onTap: () async {
await ref await ref.read(warpOptionNotifierProvider.notifier).generateWarpConfig();
.read(warpOptionNotifierProvider.notifier) await ref.read(warpOptionNotifierProvider.notifier).generateWarp2Config();
.generateWarpConfig();
}, },
), ),
ChoicePreferenceWidget( ChoicePreferenceWidget(
@@ -111,8 +109,7 @@ class WarpOptionsTiles extends HookConsumerWidget {
preferences: ref.watch(ConfigOptions.warpNoise.notifier), preferences: ref.watch(ConfigOptions.warpNoise.notifier),
enabled: canChangeOptions, enabled: canChangeOptions,
title: t.config.warpNoise, title: t.config.warpNoise,
inputToValue: (input) => inputToValue: (input) => OptionalRange.tryParse(input, allowEmpty: true),
OptionalRange.tryParse(input, allowEmpty: true),
presentValue: (value) => value.present(t), presentValue: (value) => value.present(t),
formatInputValue: (value) => value.format(), formatInputValue: (value) => value.format(),
), ),
@@ -121,8 +118,7 @@ class WarpOptionsTiles extends HookConsumerWidget {
preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier), preferences: ref.watch(ConfigOptions.warpNoiseDelay.notifier),
enabled: canChangeOptions, enabled: canChangeOptions,
title: t.config.warpNoiseDelay, title: t.config.warpNoiseDelay,
inputToValue: (input) => inputToValue: (input) => OptionalRange.tryParse(input, allowEmpty: true),
OptionalRange.tryParse(input, allowEmpty: true),
presentValue: (value) => value.present(t), presentValue: (value) => value.present(t),
formatInputValue: (value) => value.format(), formatInputValue: (value) => value.format(),
), ),

View File

@@ -16,8 +16,7 @@ class QuickSettingsModal 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 warpPrefaceCompleted = final warpPrefaceCompleted = ref.watch(warpOptionNotifierProvider).consentGiven;
ref.watch(warpOptionNotifierProvider).consentGiven;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
@@ -33,37 +32,41 @@ class QuickSettingsModal extends HookConsumerWidget {
e.presentShort(t), e.presentShort(t),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
tooltip: tooltip: e.isExperimental ? t.settings.experimental : null,
e.isExperimental ? t.settings.experimental : null,
), ),
) )
.toList(), .toList(),
selected: {ref.watch(ConfigOptions.serviceMode)}, selected: {ref.watch(ConfigOptions.serviceMode)},
onSelectionChanged: (newSet) => ref onSelectionChanged: (newSet) => ref.read(ConfigOptions.serviceMode.notifier).update(newSet.first),
.read(ConfigOptions.serviceMode.notifier)
.update(newSet.first),
), ),
), ),
const Gap(8), const Gap(8),
if (warpPrefaceCompleted) if (warpPrefaceCompleted)
SwitchListTile( GestureDetector(
value: ref.watch(ConfigOptions.enableWarp), onLongPress: () {
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update, ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context);
title: Text(t.config.enableWarp), },
child: SwitchListTile(
value: ref.watch(ConfigOptions.enableWarp),
onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update,
title: Text(t.config.enableWarp),
),
) )
else else
ListTile( ListTile(
title: Text(t.config.setupWarp), title: Text(t.config.setupWarp),
trailing: const Icon(FluentIcons.chevron_right_24_regular), trailing: const Icon(FluentIcons.chevron_right_24_regular),
onTap: () => onTap: () => ConfigOptionsRoute(section: ConfigOptionSection.warp.name).go(context),
ConfigOptionsRoute(section: ConfigOptionSection.warp.name) ),
.go(context), GestureDetector(
onLongPress: () {
ConfigOptionsRoute(section: ConfigOptionSection.fragment.name).go(context);
},
child: SwitchListTile(
value: ref.watch(ConfigOptions.enableTlsFragment),
onChanged: ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
title: Text(t.config.enableTlsFragment),
), ),
SwitchListTile(
value: ref.watch(ConfigOptions.enableTlsFragment),
onChanged:
ref.watch(ConfigOptions.enableTlsFragment.notifier).update,
title: Text(t.config.enableTlsFragment),
), ),
// SwitchListTile( // SwitchListTile(
// value: ref.watch(ConfigOptions.enableMux), // value: ref.watch(ConfigOptions.enableMux),

View File

@@ -5,11 +5,16 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/preferences/preferences_provider.dart';
import 'package:hiddify/core/router/router.dart'; import 'package:hiddify/core/router/router.dart';
import 'package:hiddify/features/common/qr_code_scanner_screen.dart'; import 'package:hiddify/features/common/qr_code_scanner_screen.dart';
import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart';
import 'package:hiddify/features/config_option/overview/config_options_page.dart';
import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart';
import 'package:hiddify/features/profile/notifier/profile_notifier.dart'; import 'package:hiddify/features/profile/notifier/profile_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:shared_preferences/shared_preferences.dart';
class AddProfileModal extends HookConsumerWidget { class AddProfileModal extends HookConsumerWidget {
const AddProfileModal({ const AddProfileModal({
@@ -17,7 +22,7 @@ class AddProfileModal extends HookConsumerWidget {
this.url, this.url,
this.scrollController, this.scrollController,
}); });
static const warpConsentGiven = "warp_consent_given";
final String? url; final String? url;
final ScrollController? scrollController; final ScrollController? scrollController;
@@ -58,8 +63,7 @@ class AddProfileModal extends HookConsumerWidget {
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
// temporary solution, aspect ratio widget relies on height and in a row there no height! // temporary solution, aspect ratio widget relies on height and in a row there no height!
final buttonWidth = final buttonWidth = constraints.maxWidth / 2 - (buttonsPadding + (buttonsGap / 2));
constraints.maxWidth / 2 - (buttonsPadding + (buttonsGap / 2));
return AnimatedCrossFade( return AnimatedCrossFade(
firstChild: SizedBox( firstChild: SizedBox(
@@ -93,8 +97,7 @@ class AddProfileModal extends HookConsumerWidget {
secondChild: Column( secondChild: Column(
children: [ children: [
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: buttonsPadding),
const EdgeInsets.symmetric(horizontal: buttonsPadding),
child: Row( child: Row(
children: [ children: [
_Button( _Button(
@@ -103,13 +106,9 @@ class AddProfileModal extends HookConsumerWidget {
icon: FluentIcons.clipboard_paste_24_regular, icon: FluentIcons.clipboard_paste_24_regular,
size: buttonWidth, size: buttonWidth,
onTap: () async { onTap: () async {
final captureResult = final captureResult = await Clipboard.getData(Clipboard.kTextPlain).then((value) => value?.text ?? '');
await Clipboard.getData(Clipboard.kTextPlain)
.then((value) => value?.text ?? '');
if (addProfileState.isLoading) return; if (addProfileState.isLoading) return;
ref ref.read(addProfileProvider.notifier).add(captureResult);
.read(addProfileProvider.notifier)
.add(captureResult);
}, },
), ),
const Gap(buttonsGap), const Gap(buttonsGap),
@@ -120,8 +119,7 @@ class AddProfileModal extends HookConsumerWidget {
icon: FluentIcons.qr_code_24_regular, icon: FluentIcons.qr_code_24_regular,
size: buttonWidth, size: buttonWidth,
onTap: () async { onTap: () async {
final cr = final cr = await QRCodeScannerScreen().open(context);
await QRCodeScannerScreen().open(context);
if (cr == null) return; if (cr == null) return;
if (addProfileState.isLoading) return; if (addProfileState.isLoading) return;
@@ -142,56 +140,118 @@ class AddProfileModal extends HookConsumerWidget {
], ],
), ),
), ),
if (!PlatformUtils.isDesktop) Padding(
Padding( padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( horizontal: buttonsPadding,
horizontal: buttonsPadding, vertical: 16,
vertical: 16, ),
), child: Column(
child: Semantics( children: [
button: true, Semantics(
child: SizedBox( button: true,
height: 36, child: SizedBox(
child: Material( height: 36,
key: const ValueKey("add_manually_button"), child: Material(
elevation: 8, key: const ValueKey("add_warp_button"),
color: theme.colorScheme.surface, elevation: 8,
surfaceTintColor: theme.colorScheme.surfaceTint, color: theme.colorScheme.surface,
shadowColor: Colors.transparent, surfaceTintColor: theme.colorScheme.surfaceTint,
borderRadius: BorderRadius.circular(8), shadowColor: Colors.transparent,
clipBehavior: Clip.antiAlias, borderRadius: BorderRadius.circular(8),
child: InkWell( clipBehavior: Clip.antiAlias,
onTap: () async { child: InkWell(
context.pop(); onTap: () async {
await const NewProfileRoute().push(context); Future.microtask(() async {
}, context.pop();
child: Row( final _prefs = ref.read(sharedPreferencesProvider).requireValue;
mainAxisAlignment: MainAxisAlignment.center, final consent = _prefs.getBool(warpConsentGiven) ?? false;
children: [ if (!consent) {
Icon( final agreed = await showDialog<bool>(
FluentIcons.add_24_regular, context: context,
color: theme.colorScheme.primary, builder: (context) => const WarpLicenseAgreementModal(),
), );
const Gap(8),
Text( if (agreed ?? false) {
t.profile.add.manually, await ref.read(warpOptionNotifierProvider.notifier).agree();
style: theme.textTheme.labelLarge?.copyWith( }
}
final accountId = _prefs.getString("warp2-account-id");
final accessToken = _prefs.getString("warp2-access-token");
final hasWarp2Config = accountId != null && accessToken != null;
if (!hasWarp2Config) {
await ref.read(warpOptionNotifierProvider.notifier).generateWarp2Config();
}
await ref.read(addProfileProvider.notifier).add("#profile-title: Hiddify WARP\nwarp://p2@auto#Remote&&detour=warp://p1@auto#Local"); //
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FluentIcons.add_24_regular,
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
), ),
), const SizedBox(width: 8),
], Text(
t.profile.add.addWarp,
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.primary,
),
),
],
),
), ),
), ),
), ),
), ),
), if (!PlatformUtils.isDesktop) const SizedBox(height: 16), // Spacing between the buttons
if (!PlatformUtils.isDesktop)
Semantics(
button: true,
child: SizedBox(
height: 36,
child: Material(
key: const ValueKey("add_manually_button"),
elevation: 8,
color: theme.colorScheme.surface,
surfaceTintColor: theme.colorScheme.surfaceTint,
shadowColor: Colors.transparent,
borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () async {
context.pop();
await const NewProfileRoute().push(context);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FluentIcons.add_24_regular,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
Text(
t.profile.add.manually,
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.primary,
),
),
],
),
),
),
),
),
],
), ),
),
const Gap(24), const Gap(24),
], ],
), ),
crossFadeState: addProfileState.isLoading crossFadeState: addProfileState.isLoading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
); );
}, },

View File

@@ -47,8 +47,7 @@ class AddProfile extends _$AddProfile with AppLogger {
return const AsyncData(null); return const AsyncData(null);
} }
ProfileRepository get _profilesRepo => ProfileRepository get _profilesRepo => ref.read(profileRepositoryProvider).requireValue;
ref.read(profileRepositoryProvider).requireValue;
CancelToken? _cancelToken; CancelToken? _cancelToken;
Future<void> add(String rawInput) async { Future<void> add(String rawInput) async {
@@ -57,8 +56,7 @@ class AddProfile extends _$AddProfile with AppLogger {
state = await AsyncValue.guard( state = await AsyncValue.guard(
() async { () async {
final activeProfile = await ref.read(activeProfileProvider.future); final activeProfile = await ref.read(activeProfileProvider.future);
final markAsActive = final markAsActive = activeProfile == null || ref.read(Preferences.markNewProfileActive);
activeProfile == null || ref.read(Preferences.markNewProfileActive);
final TaskEither<ProfileFailure, Unit> task; final TaskEither<ProfileFailure, Unit> task;
if (LinkParser.parse(rawInput) case (final link)?) { if (LinkParser.parse(rawInput) case (final link)?) {
loggy.debug("adding profile, url: [${link.url}]"); loggy.debug("adding profile, url: [${link.url}]");
@@ -70,7 +68,11 @@ class AddProfile extends _$AddProfile with AppLogger {
} else if (LinkParser.protocol(rawInput) case (final parsed)?) { } else if (LinkParser.protocol(rawInput) case (final parsed)?) {
loggy.debug("adding profile, content"); loggy.debug("adding profile, content");
var name = parsed.name; var name = parsed.name;
var oldItem = await _profilesRepo.getByName(name);
if (name == "Hiddify WARP" && oldItem != null) {
_profilesRepo.setAsActive(oldItem.id).run();
return unit;
}
while (await _profilesRepo.getByName(name) != null) { while (await _profilesRepo.getByName(name) != null) {
name += '${randomInt(0, 9).run()}'; name += '${randomInt(0, 9).run()}';
} }
@@ -122,8 +124,7 @@ class UpdateProfile extends _$UpdateProfile with AppLogger {
return const AsyncData(null); return const AsyncData(null);
} }
ProfileRepository get _profilesRepo => ProfileRepository get _profilesRepo => ref.read(profileRepositoryProvider).requireValue;
ref.read(profileRepositoryProvider).requireValue;
Future<void> updateProfile(RemoteProfileEntity profile) async { Future<void> updateProfile(RemoteProfileEntity profile) async {
if (state.isLoading) return; if (state.isLoading) return;
@@ -143,9 +144,7 @@ class UpdateProfile extends _$UpdateProfile with AppLogger {
await ref.read(activeProfileProvider.future).then((active) async { await ref.read(activeProfileProvider.future).then((active) async {
if (active != null && active.id == profile.id) { if (active != null && active.id == profile.id) {
await ref await ref.read(connectionNotifierProvider.notifier).reconnect(profile);
.read(connectionNotifierProvider.notifier)
.reconnect(profile);
} }
}); });
return unit; return unit;

View File

@@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:combine/combine.dart';
import 'package:dartx/dartx.dart'; import 'package:dartx/dartx.dart';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/core/haptic/haptic_service.dart'; import 'package:hiddify/core/haptic/haptic_service.dart';
import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/preferences/preferences_provider.dart'; import 'package:hiddify/core/preferences/preferences_provider.dart';
@@ -85,46 +85,41 @@ class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger {
List<ProxyGroupEntity> proxies, List<ProxyGroupEntity> proxies,
ProxiesSort sortBy, ProxiesSort sortBy,
) async { ) async {
return CombineWorker().execute( final groupWithSelected = {
() { for (final o in proxies) o.tag: o.selected,
final groupWithSelected = { };
for (final o in proxies) o.tag: o.selected, final sortedProxies = <ProxyGroupEntity>[];
}; for (final group in proxies) {
final sortedProxies = <ProxyGroupEntity>[]; final sortedItems = switch (sortBy) {
for (final group in proxies) { ProxiesSort.name => group.items.sortedWith((a, b) {
final sortedItems = switch (sortBy) { if (a.type.isGroup && !b.type.isGroup) return -1;
ProxiesSort.name => group.items.sortedWith((a, b) { if (!a.type.isGroup && b.type.isGroup) return 1;
if (a.type.isGroup && !b.type.isGroup) return -1; return a.tag.compareTo(b.tag);
if (!a.type.isGroup && b.type.isGroup) return 1; }),
return a.tag.compareTo(b.tag); ProxiesSort.delay => group.items.sortedWith((a, b) {
}), if (a.type.isGroup && !b.type.isGroup) return -1;
ProxiesSort.delay => group.items.sortedWith((a, b) { if (!a.type.isGroup && b.type.isGroup) return 1;
if (a.type.isGroup && !b.type.isGroup) return -1;
if (!a.type.isGroup && b.type.isGroup) return 1;
final ai = a.urlTestDelay; final ai = a.urlTestDelay;
final bi = b.urlTestDelay; final bi = b.urlTestDelay;
if (ai == 0 && bi == 0) return -1; if (ai == 0 && bi == 0) return -1;
if (ai == 0 && bi > 0) return 1; if (ai == 0 && bi > 0) return 1;
if (ai > 0 && bi == 0) return -1; if (ai > 0 && bi == 0) return -1;
return ai.compareTo(bi); return ai.compareTo(bi);
}), }),
ProxiesSort.unsorted => group.items, ProxiesSort.unsorted => group.items,
}; };
final items = <ProxyItemEntity>[]; final items = <ProxyItemEntity>[];
for (final item in sortedItems) { for (final item in sortedItems) {
if (groupWithSelected.keys.contains(item.tag)) { if (groupWithSelected.keys.contains(item.tag)) {
items items.add(item.copyWith(selectedTag: groupWithSelected[item.tag]));
.add(item.copyWith(selectedTag: groupWithSelected[item.tag])); } else {
} else { items.add(item);
items.add(item);
}
}
sortedProxies.add(group.copyWith(items: items));
} }
return sortedProxies; }
}, sortedProxies.add(group.copyWith(items: items));
); }
return sortedProxies;
} }
Future<void> changeProxy(String groupTag, String outboundTag) async { Future<void> changeProxy(String groupTag, String outboundTag) async {

View File

@@ -48,6 +48,7 @@ class SingboxConfigOption with _$SingboxConfigOption {
required SingboxMuxOption mux, required SingboxMuxOption mux,
required SingboxTlsTricks tlsTricks, required SingboxTlsTricks tlsTricks,
required SingboxWarpOption warp, required SingboxWarpOption warp,
required SingboxWarpOption warp2,
}) = _SingboxConfigOption; }) = _SingboxConfigOption;
String format() { String format() {
@@ -55,8 +56,7 @@ class SingboxConfigOption with _$SingboxConfigOption {
return encoder.convert(toJson()); return encoder.convert(toJson());
} }
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) => factory SingboxConfigOption.fromJson(Map<String, dynamic> json) => _$SingboxConfigOptionFromJson(json);
_$SingboxConfigOptionFromJson(json);
} }
@freezed @freezed
@@ -75,8 +75,7 @@ class SingboxWarpOption with _$SingboxWarpOption {
@OptionalRangeJsonConverter() required OptionalRange noiseDelay, @OptionalRangeJsonConverter() required OptionalRange noiseDelay,
}) = _SingboxWarpOption; }) = _SingboxWarpOption;
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => factory SingboxWarpOption.fromJson(Map<String, dynamic> json) => _$SingboxWarpOptionFromJson(json);
_$SingboxWarpOptionFromJson(json);
} }
@freezed @freezed
@@ -89,8 +88,7 @@ class SingboxMuxOption with _$SingboxMuxOption {
required MuxProtocol protocol, required MuxProtocol protocol,
}) = _SingboxMuxOption; }) = _SingboxMuxOption;
factory SingboxMuxOption.fromJson(Map<String, dynamic> json) => factory SingboxMuxOption.fromJson(Map<String, dynamic> json) => _$SingboxMuxOptionFromJson(json);
_$SingboxMuxOptionFromJson(json);
} }
@freezed @freezed
@@ -105,6 +103,5 @@ class SingboxTlsTricks with _$SingboxTlsTricks {
@OptionalRangeJsonConverter() required OptionalRange paddingSize, @OptionalRangeJsonConverter() required OptionalRange paddingSize,
}) = _SingboxTlsTricks; }) = _SingboxTlsTricks;
factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) => factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) => _$SingboxTlsTricksFromJson(json);
_$SingboxTlsTricksFromJson(json);
} }

View File

@@ -4,7 +4,6 @@ import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:combine/combine.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:fpdart/fpdart.dart'; import 'package:fpdart/fpdart.dart';
import 'package:hiddify/core/model/directories.dart'; import 'package:hiddify/core/model/directories.dart';
@@ -52,10 +51,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
Future<void> init() async { Future<void> init() async {
loggy.debug("initializing"); loggy.debug("initializing");
_statusReceiver = ReceivePort('service status receiver'); _statusReceiver = ReceivePort('service status receiver');
final source = _statusReceiver final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
.asBroadcastStream()
.map((event) => jsonDecode(event as String))
.map(SingboxStatus.fromEvent);
_status = ValueConnectableStream.seeded( _status = ValueConnectableStream.seeded(
source, source,
const SingboxStopped(), const SingboxStopped(),
@@ -69,8 +65,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
) { ) {
final port = _statusReceiver.sendPort.nativePort; final port = _statusReceiver.sendPort.nativePort;
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
_box.setupOnce(NativeApi.initializeApiDLData); _box.setupOnce(NativeApi.initializeApiDLData);
final err = _box final err = _box
.setup( .setup(
@@ -98,8 +94,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
bool debug, bool debug,
) { ) {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box final err = _box
.parse( .parse(
path.toNativeUtf8().cast(), path.toNativeUtf8().cast(),
@@ -120,13 +116,10 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) { TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final json = jsonEncode(options.toJson()); final json = jsonEncode(options.toJson());
final err = _box final err = _box.changeConfigOptions(json.toNativeUtf8().cast()).cast<Utf8>().toDartString();
.changeConfigOptions(json.toNativeUtf8().cast())
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
return left(err); return left(err);
} }
@@ -141,8 +134,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
String path, String path,
) { ) {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final response = _box final response = _box
.generateConfig( .generateConfig(
path.toNativeUtf8().cast(), path.toNativeUtf8().cast(),
@@ -166,8 +159,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
) { ) {
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]"); loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box final err = _box
.start( .start(
configPath.toNativeUtf8().cast(), configPath.toNativeUtf8().cast(),
@@ -187,8 +180,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
TaskEither<String, Unit> stop() { TaskEither<String, Unit> stop() {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box.stop().cast<Utf8>().toDartString(); final err = _box.stop().cast<Utf8>().toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
return left(err); return left(err);
@@ -207,8 +200,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
) { ) {
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]"); loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box final err = _box
.restart( .restart(
configPath.toNativeUtf8().cast(), configPath.toNativeUtf8().cast(),
@@ -265,10 +258,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}, },
); );
final err = _box final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
.startCommandClient(1, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
loggy.error("error starting status command: $err"); loggy.error("error starting status command: $err");
throw err; throw err;
@@ -310,10 +300,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
); );
try { try {
final err = _box final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
.startCommandClient(5, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
logger.error("error starting group command: $err"); logger.error("error starting group command: $err");
throw err; throw err;
@@ -357,10 +344,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
); );
try { try {
final err = _box final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
.startCommandClient(13, receiver.sendPort.nativePort)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
logger.error("error starting: $err"); logger.error("error starting: $err");
throw err; throw err;
@@ -376,8 +360,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) { TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box final err = _box
.selectOutbound( .selectOutbound(
groupTag.toNativeUtf8().cast(), groupTag.toNativeUtf8().cast(),
@@ -397,12 +381,9 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
TaskEither<String, Unit> urlTest(String groupTag) { TaskEither<String, Unit> urlTest(String groupTag) {
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final err = _box final err = _box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
.urlTest(groupTag.toNativeUtf8().cast())
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) { if (err.isNotEmpty) {
return left(err); return left(err);
} }
@@ -418,9 +399,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
Stream<List<String>> watchLogs(String path) async* { Stream<List<String>> watchLogs(String path) async* {
yield await _readLogFile(File(path)); yield await _readLogFile(File(path));
yield* Watcher(path, pollingDelay: const Duration(seconds: 1)) yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
.events
.asyncMap((event) async {
if (event.type == ChangeType.MODIFY) { if (event.type == ChangeType.MODIFY) {
await _readLogFile(File(path)); await _readLogFile(File(path));
} }
@@ -431,17 +410,18 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override @override
TaskEither<String, Unit> clearLogs() { TaskEither<String, Unit> clearLogs() {
return TaskEither( return TaskEither(
() async { () => Future.microtask(
_logBuffer.clear(); () async {
return right(unit); _logBuffer.clear();
}, return right(unit);
},
),
); );
} }
Future<List<String>> _readLogFile(File file) async { Future<List<String>> _readLogFile(File file) async {
if (_logFilePosition == 0 && file.lengthSync() == 0) return []; if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
final content = final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
await file.openRead(_logFilePosition).transform(utf8.decoder).join();
_logFilePosition = file.lengthSync(); _logFilePosition = file.lengthSync();
final lines = const LineSplitter().convert(content); final lines = const LineSplitter().convert(content);
if (lines.length > 300) { if (lines.length > 300) {
@@ -464,8 +444,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
}) { }) {
loggy.debug("generating warp config"); loggy.debug("generating warp config");
return TaskEither( return TaskEither(
() => CombineWorker().execute( () => Future.microtask(
() { () async {
final response = _box final response = _box
.generateWarpConfig( .generateWarpConfig(
licenseKey.toNativeUtf8().cast(), licenseKey.toNativeUtf8().cast(),

Submodule libcore updated: b265886306...bc48ec07a8

View File

@@ -218,14 +218,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
combine:
dependency: "direct main"
description:
name: combine
sha256: "8b52083c822a614a448fdd307e78c05266080e9747604b61fca5ddfe736a6c1e"
url: "https://pub.dev"
source: hosted
version: "0.5.7-0.1.pre"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@@ -801,11 +793,12 @@ packages:
humanizer: humanizer:
dependency: "direct main" dependency: "direct main"
description: description:
name: humanizer path: "."
sha256: "08728a4b6d62accd7d09e668bd54e81e6e09a82c8cfda30553224b3eb868d4f2" ref: up-version
url: "https://pub.dev" resolved-ref: "8ae61d68357fae197be7ee71d67ccb9498b9d5c7"
source: hosted url: "https://github.com/alex-relov/humanizer"
version: "2.2.0" source: git
version: "2.3.0"
iconsax_flutter: iconsax_flutter:
dependency: transitive dependency: transitive
description: description:
@@ -850,10 +843,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.19.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@@ -922,26 +915,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "10.0.4"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.3"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.1"
lint: lint:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -1002,10 +995,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.12.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1703,26 +1696,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test name: test
sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.24.9" version: "1.25.2"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.0"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.9" version: "0.6.0"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1935,10 +1928,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "14.2.1"
watcher: watcher:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -12,7 +12,7 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
intl: ^0.18.1 intl: ^0.19.0
slang: ^3.30.1 slang: ^3.30.1
slang_flutter: ^3.30.0 slang_flutter: ^3.30.0
fpdart: ^1.1.0 fpdart: ^1.1.0
@@ -40,7 +40,7 @@ dependencies:
launch_at_startup: ^0.2.2 launch_at_startup: ^0.2.2
sentry_flutter: ^7.16.1 sentry_flutter: ^7.16.1
sentry_dart_plugin: ^1.7.1 sentry_dart_plugin: ^1.7.1
combine: ^0.5.7-0.1.pre
path: ^1.8.3 path: ^1.8.3
loggy: ^2.0.3 loggy: ^2.0.3
flutter_loggy: ^2.0.2 flutter_loggy: ^2.0.2
@@ -58,7 +58,11 @@ dependencies:
percent_indicator: ^4.2.3 percent_indicator: ^4.2.3
sliver_tools: ^0.2.12 sliver_tools: ^0.2.12
flutter_adaptive_scaffold: ^0.1.8 flutter_adaptive_scaffold: ^0.1.8
humanizer: ^2.2.0 # humanizer: ^2.2.0
humanizer:
git:
url: https://github.com/alex-relov/humanizer
ref: up-version
upgrader: ^9.0.0 upgrader: ^9.0.0
toastification: ^1.2.1 toastification: ^1.2.1
version: ^3.0.2 version: ^3.0.2