diff --git a/.github/release_message.md b/.github/release_message.md index a8aa9d8e..9d281a37 100644 --- a/.github/release_message.md +++ b/.github/release_message.md @@ -1,8 +1,6 @@ [![Release Downloads](https://img.shields.io/github/downloads/hiddify/hiddify-next/RELEASE_TAG/total?style=flat-square&logo=github)](https://img.shields.io/github/downloads/hiddify/hiddify-next/RELEASE_TAG/) - - - + **Release Highlights:** @@ -27,6 +25,8 @@ + +
@@ -45,14 +45,16 @@ - - - + @@ -63,6 +65,7 @@
Windows
- +
Windows +
+
+
macOS (v10.15+)
+
Linux
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cec7dc8..bac14463 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ on: env: IS_GITHUB_ACTIONS: 1 CHANNEL: "${{ inputs.channel }}" - FLUTTER_VERSION: '3.16.x' + FLUTTER_VERSION: '3.19.x' NDK_VERSION: r26b UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" TAG_NAME: "${{ inputs.tag-name }}" diff --git a/Makefile b/Makefile index ee84238c..c310634f 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,6 @@ build-linux-libs: build-macos-libs: make -C libcore -f Makefile macos-universal - mv $(BINDIR)/$(SRV_NAME) $(DESKTOP_OUT)/ build-ios-libs: rf -rf $(IOS_OUT)/Libcore.xcframework diff --git a/android/app/build.gradle b/android/app/build.gradle index 869d8e28..cd56b934 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - boolean hasKeyStore = false def keystoreProperties = new Properties() @@ -39,10 +40,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { namespace "com.hiddify.hiddify" compileSdkVersion 34 @@ -127,7 +124,6 @@ flutter { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'com.google.code.gson:gson:2.10.1' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2' diff --git a/android/build.gradle b/android/build.gradle index d02fac26..bc157bd1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.21' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf..cf00f3cb 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.4.2" apply false + id "org.jetbrains.kotlin.android" version "1.8.21" apply false +} + +include ":app" diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index 3aee9c91..2bb34ce8 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -33,6 +33,7 @@ "connecting": "Connecting", "disconnecting": "Disconnecting", "connected": "Connected", + "reconnect": "Reconnect", "experimentalNotice": "Experimental Features In Use", "experimentalNoticeMsg": "You've enabled some experimental features which might affect connection quality and cause unexpected errors. You can always change or reset these options from Config options page.", "disableExperimentalNotice": "Don't show again" @@ -228,12 +229,21 @@ "reconnectMsg": "Reconnect for changes to take effect", "reconnectBtn": "Reconnect", "serviceMode": "Service Mode", + "quickSettings": "Quick Settings", + "setupWarp": "Setup WARP", + "allOptions": "All Config Options", "serviceModes": { "proxy": "Proxy Service Only", "systemProxy": "Set System Proxy", "tun": "VPN", "tunService": "VPN Service" }, + "shortServiceModes": { + "proxy": "Proxy", + "systemProxy": "System Proxy", + "tun": "VPN", + "tunService": "VPN Service" + }, "section": { "route": "Route Options", "dns": "DNS Options", diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index b57726fc..c9abc2fb 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -93,7 +93,6 @@ Future lazyBootstrap( final debug = container.read(debugModeNotifierProvider) || kDebugMode; if (PlatformUtils.isDesktop) { - Logger.bootstrap.info("Starting Window Contoller"); await _init( "window controller", () => container.read(windowNotifierProvider.future), @@ -107,61 +106,50 @@ Future lazyBootstrap( } else { Logger.bootstrap.debug("silent start, remain hidden accessible via tray"); } - Logger.bootstrap.info("Starting Auto Start Service"); await _init( "auto start service", () => container.read(autoStartNotifierProvider.future), ); } - Logger.bootstrap.info("Starting Log Repository"); await _init( "logs repository", () => container.read(logRepositoryProvider.future), ); - Logger.bootstrap.info("Starting Logger Contoller"); await _init("logger controller", () => LoggerController.postInit(debug)); Logger.bootstrap.info(appInfo.format()); - Logger.bootstrap.info("Starting GeoAssets"); await _init( "geo assets repository", () => container.read(geoAssetRepositoryProvider.future), ); - Logger.bootstrap.info("Starting Profile Repository"); await _init( "profile repository", () => container.read(profileRepositoryProvider.future), ); - Logger.bootstrap.info("Starting Active Profile"); await _safeInit( "active profile", () => container.read(activeProfileProvider.future), timeout: 1000, ); - Logger.bootstrap.info("Starting Deep Link Service"); await _safeInit( "deep link service", () => container.read(deepLinkNotifierProvider.future), timeout: 1000, ); - Logger.bootstrap.info("Starting Singbox Service Provider"); await _init( "sing-box", () => container.read(singboxServiceProvider).init(), ); if (PlatformUtils.isDesktop) { - Logger.bootstrap.info("Starting System Tray"); await _safeInit( "system tray", () => container.read(systemTrayNotifierProvider.future), timeout: 1000, ); - Logger.bootstrap.info("System Tray initialized"); } if (Platform.isAndroid) { - Logger.bootstrap.info("Starting FlutterDisplayMode.setHighRefreshRate"); await _safeInit( "android display mode", () async { @@ -191,13 +179,14 @@ Future _init( int? timeout, }) async { final stopWatch = Stopwatch()..start(); + Logger.bootstrap.info("initializing [$name]"); Future func() => timeout != null ? initializer().timeout(Duration(milliseconds: timeout)) : initializer(); try { final result = await func(); - Logger.bootstrap.debug( - "[$name] initialized in ${stopWatch.elapsedMilliseconds}ms $result"); + Logger.bootstrap + .debug("[$name] initialized in ${stopWatch.elapsedMilliseconds}ms"); return result; } catch (e, stackTrace) { Logger.bootstrap.error("[$name] error initializing", e, stackTrace); diff --git a/lib/core/database/connection/database_connection.dart b/lib/core/database/connection/database_connection.dart index 046a44f4..16cffb43 100644 --- a/lib/core/database/connection/database_connection.dart +++ b/lib/core/database/connection/database_connection.dart @@ -9,6 +9,6 @@ LazyDatabase openConnection() { return LazyDatabase(() async { final dbDir = await AppDirectories.getDatabaseDirectory(); final file = File(p.join(dbDir.path, 'db.sqlite')); - return NativeDatabase.createInBackground(file); + return NativeDatabase(file); }); } diff --git a/lib/core/router/routes.dart b/lib/core/router/routes.dart index f7a1f349..0cfce287 100644 --- a/lib/core/router/routes.dart +++ b/lib/core/router/routes.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:hiddify/core/router/app_router.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/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/intro/widget/intro_page.dart'; @@ -47,6 +48,10 @@ GlobalKey? _dynamicRootKey = path: "config-options", name: ConfigOptionsRoute.name, ), + TypedGoRoute( + path: "quick-settings", + name: QuickSettingsRoute.name, + ), TypedGoRoute( path: "settings", name: SettingsRoute.name, @@ -108,6 +113,10 @@ class MobileWrapperRoute extends ShellRouteData { path: "profiles/:id", name: ProfileDetailsRoute.name, ), + TypedGoRoute( + path: "quick-settings", + name: QuickSettingsRoute.name, + ), ], ), TypedGoRoute( @@ -277,6 +286,22 @@ class LogsOverviewRoute extends GoRouteData { } } +class QuickSettingsRoute extends GoRouteData { + const QuickSettingsRoute(); + static const name = "Quick Settings"; + + static final GlobalKey $parentNavigatorKey = rootNavigatorKey; + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return BottomSheetPage( + fixed: true, + name: name, + builder: (controller) => const QuickSettingsModal(), + ); + } +} + class SettingsRoute extends GoRouteData { const SettingsRoute(); static const name = "Settings"; @@ -296,7 +321,8 @@ class SettingsRoute extends GoRouteData { } class ConfigOptionsRoute extends GoRouteData { - const ConfigOptionsRoute(); + const ConfigOptionsRoute({this.section}); + final String? section; static const name = "Config Options"; static final GlobalKey? $parentNavigatorKey = _dynamicRootKey; @@ -304,12 +330,15 @@ class ConfigOptionsRoute extends GoRouteData { @override Page buildPage(BuildContext context, GoRouterState state) { if (useMobileRouter) { - return const MaterialPage( + return MaterialPage( name: name, - child: ConfigOptionsPage(), + child: ConfigOptionsPage(section: section), ); } - return const NoTransitionPage(name: name, child: ConfigOptionsPage()); + return NoTransitionPage( + name: name, + child: ConfigOptionsPage(section: section), + ); } } diff --git a/lib/features/config_option/notifier/config_option_notifier.dart b/lib/features/config_option/notifier/config_option_notifier.dart index 4c04e621..cd7793b5 100644 --- a/lib/features/config_option/notifier/config_option_notifier.dart +++ b/lib/features/config_option/notifier/config_option_notifier.dart @@ -24,7 +24,7 @@ class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger { if (next case AsyncData(:final value) when next != previous) { if (_lastUpdate == null || DateTime.now().difference(_lastUpdate!) > - const Duration(seconds: 3)) { + const Duration(milliseconds: 100)) { _lastUpdate = DateTime.now(); state = AsyncData(value != serviceSingboxOptions); } diff --git a/lib/features/config_option/overview/config_options_page.dart b/lib/features/config_option/overview/config_options_page.dart index a916cad1..bc9764ff 100644 --- a/lib/features/config_option/overview/config_options_page.dart +++ b/lib/features/config_option/overview/config_options_page.dart @@ -1,5 +1,6 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/model/optional_range.dart'; @@ -21,12 +22,49 @@ import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:humanizer/humanizer.dart'; +enum ConfigOptionSection { + warp; + + static final _warpKey = GlobalKey(debugLabel: "warp-section-key"); + + GlobalKey get key => switch (this) { _ => _warpKey }; +} + class ConfigOptionsPage extends HookConsumerWidget { - const ConfigOptionsPage({super.key}); + ConfigOptionsPage({super.key, String? section}) + : section = + section != null ? ConfigOptionSection.values.byName(section) : null; + + final ConfigOptionSection? section; @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); + final scrollController = useScrollController(); + + useMemoized( + () { + if (section != null) { + WidgetsBinding.instance.addPostFrameCallback( + (_) { + final box = + section!.key.currentContext?.findRenderObject() as RenderBox?; + final offset = box?.localToGlobal(Offset.zero); + if (offset == null) return; + final height = scrollController.offset + + offset.dy - + MediaQueryData.fromView(View.of(context)).padding.top - + kToolbarHeight; + scrollController.animateTo( + height, + duration: const Duration(milliseconds: 500), + curve: Curves.decelerate, + ); + }, + ); + } + }, + ); String experimental(String txt) { return "$txt (${t.settings.experimental})"; @@ -34,6 +72,8 @@ class ConfigOptionsPage extends HookConsumerWidget { return Scaffold( body: CustomScrollView( + controller: scrollController, + shrinkWrap: true, slivers: [ NestedAppBar( title: Text(t.settings.config.pageTitle), @@ -101,235 +141,255 @@ class ConfigOptionsPage extends HookConsumerWidget { ), ], ), - SliverList.list( - children: [ - TipCard(message: t.settings.experimentalMsg), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.logLevel), - preferences: ref.watch(ConfigOptions.logLevel.notifier), - choices: LogLevel.choices, - title: t.settings.config.logLevel, - presentChoice: (value) => value.name.toUpperCase(), + SliverToBoxAdapter( + child: SingleChildScrollView( + child: Column( + children: [ + TipCard(message: t.settings.experimentalMsg), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.logLevel), + preferences: ref.watch(ConfigOptions.logLevel.notifier), + choices: LogLevel.choices, + title: t.settings.config.logLevel, + presentChoice: (value) => value.name.toUpperCase(), + ), + const SettingsDivider(), + SettingsSection(t.settings.config.section.route), + SwitchListTile( + title: Text(experimental(t.settings.config.bypassLan)), + value: ref.watch(ConfigOptions.bypassLan), + onChanged: + ref.watch(ConfigOptions.bypassLan.notifier).update, + ), + SwitchListTile( + title: Text(t.settings.config.resolveDestination), + value: ref.watch(ConfigOptions.resolveDestination), + onChanged: ref + .watch(ConfigOptions.resolveDestination.notifier) + .update, + ), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.ipv6Mode), + preferences: ref.watch(ConfigOptions.ipv6Mode.notifier), + choices: IPv6Mode.values, + title: t.settings.config.ipv6Mode, + presentChoice: (value) => value.present(t), + ), + const SettingsDivider(), + SettingsSection(t.settings.config.section.dns), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.remoteDnsAddress), + preferences: + ref.watch(ConfigOptions.remoteDnsAddress.notifier), + title: t.settings.config.remoteDnsAddress, + ), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy), + preferences: ref + .watch(ConfigOptions.remoteDnsDomainStrategy.notifier), + choices: DomainStrategy.values, + title: t.settings.config.remoteDnsDomainStrategy, + presentChoice: (value) => value.displayName, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.directDnsAddress), + preferences: + ref.watch(ConfigOptions.directDnsAddress.notifier), + title: t.settings.config.directDnsAddress, + ), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.directDnsDomainStrategy), + preferences: ref + .watch(ConfigOptions.directDnsDomainStrategy.notifier), + choices: DomainStrategy.values, + title: t.settings.config.directDnsDomainStrategy, + presentChoice: (value) => value.displayName, + ), + SwitchListTile( + title: Text(t.settings.config.enableDnsRouting), + value: ref.watch(ConfigOptions.enableDnsRouting), + onChanged: ref + .watch(ConfigOptions.enableDnsRouting.notifier) + .update, + ), + const SettingsDivider(), + SettingsSection(experimental(t.settings.config.section.mux)), + SwitchListTile( + title: Text(t.settings.config.enableMux), + value: ref.watch(ConfigOptions.enableMux), + onChanged: + ref.watch(ConfigOptions.enableMux.notifier).update, + ), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.muxProtocol), + preferences: ref.watch(ConfigOptions.muxProtocol.notifier), + choices: MuxProtocol.values, + title: t.settings.config.muxProtocol, + presentChoice: (value) => value.name, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.muxMaxStreams), + preferences: + ref.watch(ConfigOptions.muxMaxStreams.notifier), + title: t.settings.config.muxMaxStreams, + inputToValue: int.tryParse, + digitsOnly: true, + ), + const SettingsDivider(), + SettingsSection(t.settings.config.section.inbound), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.serviceMode), + preferences: ref.watch(ConfigOptions.serviceMode.notifier), + choices: ServiceMode.choices, + title: t.settings.config.serviceMode, + presentChoice: (value) => value.present(t), + ), + SwitchListTile( + title: Text(t.settings.config.strictRoute), + value: ref.watch(ConfigOptions.strictRoute), + onChanged: + ref.watch(ConfigOptions.strictRoute.notifier).update, + ), + ChoicePreferenceWidget( + selected: ref.watch(ConfigOptions.tunImplementation), + preferences: + ref.watch(ConfigOptions.tunImplementation.notifier), + choices: TunImplementation.values, + title: t.settings.config.tunImplementation, + presentChoice: (value) => value.name, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.mixedPort), + preferences: ref.watch(ConfigOptions.mixedPort.notifier), + title: t.settings.config.mixedPort, + inputToValue: int.tryParse, + digitsOnly: true, + validateInput: isPort, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.localDnsPort), + preferences: ref.watch(ConfigOptions.localDnsPort.notifier), + title: t.settings.config.localDnsPort, + inputToValue: int.tryParse, + digitsOnly: true, + validateInput: isPort, + ), + SwitchListTile( + title: Text( + experimental(t.settings.config.allowConnectionFromLan), + ), + value: ref.watch(ConfigOptions.allowConnectionFromLan), + onChanged: ref + .read(ConfigOptions.allowConnectionFromLan.notifier) + .update, + ), + const SettingsDivider(), + SettingsSection(t.settings.config.section.tlsTricks), + SwitchListTile( + title: + Text(experimental(t.settings.config.enableTlsFragment)), + value: ref.watch(ConfigOptions.enableTlsFragment), + onChanged: ref + .watch(ConfigOptions.enableTlsFragment.notifier) + .update, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.tlsFragmentSize), + preferences: + ref.watch(ConfigOptions.tlsFragmentSize.notifier), + title: t.settings.config.tlsFragmentSize, + inputToValue: OptionalRange.tryParse, + presentValue: (value) => value.present(t), + formatInputValue: (value) => value.format(), + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.tlsFragmentSleep), + preferences: + ref.watch(ConfigOptions.tlsFragmentSleep.notifier), + title: t.settings.config.tlsFragmentSleep, + inputToValue: OptionalRange.tryParse, + presentValue: (value) => value.present(t), + formatInputValue: (value) => value.format(), + ), + SwitchListTile( + title: Text( + experimental(t.settings.config.enableTlsMixedSniCase), + ), + value: ref.watch(ConfigOptions.enableTlsMixedSniCase), + onChanged: ref + .watch(ConfigOptions.enableTlsMixedSniCase.notifier) + .update, + ), + SwitchListTile( + title: + Text(experimental(t.settings.config.enableTlsPadding)), + value: ref.watch(ConfigOptions.enableTlsPadding), + onChanged: ref + .watch(ConfigOptions.enableTlsPadding.notifier) + .update, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.tlsPaddingSize), + preferences: + ref.watch(ConfigOptions.tlsPaddingSize.notifier), + title: t.settings.config.tlsPaddingSize, + inputToValue: OptionalRange.tryParse, + presentValue: (value) => value.format(), + formatInputValue: (value) => value.format(), + ), + const SettingsDivider(), + SettingsSection(experimental(t.settings.config.section.warp)), + WarpOptionsTiles(key: ConfigOptionSection._warpKey), + const SettingsDivider(), + SettingsSection(t.settings.config.section.misc), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.connectionTestUrl), + preferences: + ref.watch(ConfigOptions.connectionTestUrl.notifier), + title: t.settings.config.connectionTestUrl, + ), + ListTile( + title: Text(t.settings.config.urlTestInterval), + subtitle: Text( + ref + .watch(ConfigOptions.urlTestInterval) + .toApproximateTime(isRelativeToNow: false), + ), + onTap: () async { + final urlTestInterval = await SettingsSliderDialog( + title: t.settings.config.urlTestInterval, + initialValue: ref + .watch(ConfigOptions.urlTestInterval) + .inMinutes + .coerceIn(0, 60) + .toDouble(), + onReset: ref + .read(ConfigOptions.urlTestInterval.notifier) + .reset, + min: 1, + max: 60, + divisions: 60, + labelGen: (value) => Duration(minutes: value.toInt()) + .toApproximateTime(isRelativeToNow: false), + ).show(context); + if (urlTestInterval == null) return; + await ref + .read(ConfigOptions.urlTestInterval.notifier) + .update(Duration(minutes: urlTestInterval.toInt())); + }, + ), + ValuePreferenceWidget( + value: ref.watch(ConfigOptions.clashApiPort), + preferences: ref.watch(ConfigOptions.clashApiPort.notifier), + title: t.settings.config.clashApiPort, + validateInput: isPort, + digitsOnly: true, + inputToValue: int.tryParse, + ), + const Gap(24), + ], ), - const SettingsDivider(), - SettingsSection(t.settings.config.section.route), - SwitchListTile( - title: Text(experimental(t.settings.config.bypassLan)), - value: ref.watch(ConfigOptions.bypassLan), - onChanged: ref.watch(ConfigOptions.bypassLan.notifier).update, - ), - SwitchListTile( - title: Text(t.settings.config.resolveDestination), - value: ref.watch(ConfigOptions.resolveDestination), - onChanged: - ref.watch(ConfigOptions.resolveDestination.notifier).update, - ), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.ipv6Mode), - preferences: ref.watch(ConfigOptions.ipv6Mode.notifier), - choices: IPv6Mode.values, - title: t.settings.config.ipv6Mode, - presentChoice: (value) => value.present(t), - ), - const SettingsDivider(), - SettingsSection(t.settings.config.section.dns), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.remoteDnsAddress), - preferences: ref.watch(ConfigOptions.remoteDnsAddress.notifier), - title: t.settings.config.remoteDnsAddress, - ), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.remoteDnsDomainStrategy), - preferences: - ref.watch(ConfigOptions.remoteDnsDomainStrategy.notifier), - choices: DomainStrategy.values, - title: t.settings.config.remoteDnsDomainStrategy, - presentChoice: (value) => value.displayName, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.directDnsAddress), - preferences: ref.watch(ConfigOptions.directDnsAddress.notifier), - title: t.settings.config.directDnsAddress, - ), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.directDnsDomainStrategy), - preferences: - ref.watch(ConfigOptions.directDnsDomainStrategy.notifier), - choices: DomainStrategy.values, - title: t.settings.config.directDnsDomainStrategy, - presentChoice: (value) => value.displayName, - ), - SwitchListTile( - title: Text(t.settings.config.enableDnsRouting), - value: ref.watch(ConfigOptions.enableDnsRouting), - onChanged: - ref.watch(ConfigOptions.enableDnsRouting.notifier).update, - ), - const SettingsDivider(), - SettingsSection(experimental(t.settings.config.section.mux)), - SwitchListTile( - title: Text(t.settings.config.enableMux), - value: ref.watch(ConfigOptions.enableMux), - onChanged: ref.watch(ConfigOptions.enableMux.notifier).update, - ), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.muxProtocol), - preferences: ref.watch(ConfigOptions.muxProtocol.notifier), - choices: MuxProtocol.values, - title: t.settings.config.muxProtocol, - presentChoice: (value) => value.name, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.muxMaxStreams), - preferences: ref.watch(ConfigOptions.muxMaxStreams.notifier), - title: t.settings.config.muxMaxStreams, - inputToValue: int.tryParse, - digitsOnly: true, - ), - const SettingsDivider(), - SettingsSection(t.settings.config.section.inbound), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.serviceMode), - preferences: ref.watch(ConfigOptions.serviceMode.notifier), - choices: ServiceMode.choices, - title: t.settings.config.serviceMode, - presentChoice: (value) => value.present(t), - ), - SwitchListTile( - title: Text(t.settings.config.strictRoute), - value: ref.watch(ConfigOptions.strictRoute), - onChanged: ref.watch(ConfigOptions.strictRoute.notifier).update, - ), - ChoicePreferenceWidget( - selected: ref.watch(ConfigOptions.tunImplementation), - preferences: - ref.watch(ConfigOptions.tunImplementation.notifier), - choices: TunImplementation.values, - title: t.settings.config.tunImplementation, - presentChoice: (value) => value.name, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.mixedPort), - preferences: ref.watch(ConfigOptions.mixedPort.notifier), - title: t.settings.config.mixedPort, - inputToValue: int.tryParse, - digitsOnly: true, - validateInput: isPort, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.localDnsPort), - preferences: ref.watch(ConfigOptions.localDnsPort.notifier), - title: t.settings.config.localDnsPort, - inputToValue: int.tryParse, - digitsOnly: true, - validateInput: isPort, - ), - SwitchListTile( - title: Text( - experimental(t.settings.config.allowConnectionFromLan), - ), - value: ref.watch(ConfigOptions.allowConnectionFromLan), - onChanged: ref - .read(ConfigOptions.allowConnectionFromLan.notifier) - .update, - ), - const SettingsDivider(), - SettingsSection(t.settings.config.section.tlsTricks), - SwitchListTile( - title: Text(experimental(t.settings.config.enableTlsFragment)), - value: ref.watch(ConfigOptions.enableTlsFragment), - onChanged: - ref.watch(ConfigOptions.enableTlsFragment.notifier).update, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.tlsFragmentSize), - preferences: ref.watch(ConfigOptions.tlsFragmentSize.notifier), - title: t.settings.config.tlsFragmentSize, - inputToValue: OptionalRange.tryParse, - presentValue: (value) => value.present(t), - formatInputValue: (value) => value.format(), - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.tlsFragmentSleep), - preferences: ref.watch(ConfigOptions.tlsFragmentSleep.notifier), - title: t.settings.config.tlsFragmentSleep, - inputToValue: OptionalRange.tryParse, - presentValue: (value) => value.present(t), - formatInputValue: (value) => value.format(), - ), - SwitchListTile( - title: Text( - experimental(t.settings.config.enableTlsMixedSniCase), - ), - value: ref.watch(ConfigOptions.enableTlsMixedSniCase), - onChanged: ref - .watch(ConfigOptions.enableTlsMixedSniCase.notifier) - .update, - ), - SwitchListTile( - title: Text(experimental(t.settings.config.enableTlsPadding)), - value: ref.watch(ConfigOptions.enableTlsPadding), - onChanged: - ref.watch(ConfigOptions.enableTlsPadding.notifier).update, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.tlsPaddingSize), - preferences: ref.watch(ConfigOptions.tlsPaddingSize.notifier), - title: t.settings.config.tlsPaddingSize, - inputToValue: OptionalRange.tryParse, - presentValue: (value) => value.format(), - formatInputValue: (value) => value.format(), - ), - const SettingsDivider(), - SettingsSection(experimental(t.settings.config.section.warp)), - const WarpOptionsTiles(), - const SettingsDivider(), - SettingsSection(t.settings.config.section.misc), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.connectionTestUrl), - preferences: - ref.watch(ConfigOptions.connectionTestUrl.notifier), - title: t.settings.config.connectionTestUrl, - ), - ListTile( - title: Text(t.settings.config.urlTestInterval), - subtitle: Text( - ref - .watch(ConfigOptions.urlTestInterval) - .toApproximateTime(isRelativeToNow: false), - ), - onTap: () async { - final urlTestInterval = await SettingsSliderDialog( - title: t.settings.config.urlTestInterval, - initialValue: ref - .watch(ConfigOptions.urlTestInterval) - .inMinutes - .coerceIn(0, 60) - .toDouble(), - onReset: - ref.read(ConfigOptions.urlTestInterval.notifier).reset, - min: 1, - max: 60, - divisions: 60, - labelGen: (value) => Duration(minutes: value.toInt()) - .toApproximateTime(isRelativeToNow: false), - ).show(context); - if (urlTestInterval == null) return; - await ref - .read(ConfigOptions.urlTestInterval.notifier) - .update(Duration(minutes: urlTestInterval.toInt())); - }, - ), - ValuePreferenceWidget( - value: ref.watch(ConfigOptions.clashApiPort), - preferences: ref.watch(ConfigOptions.clashApiPort.notifier), - title: t.settings.config.clashApiPort, - validateInput: isPort, - digitsOnly: true, - inputToValue: int.tryParse, - ), - const Gap(24), - ], + ), ), ], ), diff --git a/lib/features/config_option/widget/quick_settings_modal.dart b/lib/features/config_option/widget/quick_settings_modal.dart new file mode 100644 index 00000000..e546091c --- /dev/null +++ b/lib/features/config_option/widget/quick_settings_modal.dart @@ -0,0 +1,84 @@ +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:hiddify/core/localization/translations.dart'; +import 'package:hiddify/core/router/router.dart'; +import 'package:hiddify/features/config_option/data/config_option_repository.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/singbox/model/singbox_config_enum.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class QuickSettingsModal extends HookConsumerWidget { + const QuickSettingsModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + + final warpPrefaceCompleted = + ref.watch(warpOptionNotifierProvider).consentGiven; + + return SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SegmentedButton( + segments: ServiceMode.choices + .map( + (e) => ButtonSegment( + value: e, + label: Text( + e.presentShort(t), + overflow: TextOverflow.ellipsis, + ), + tooltip: + e.isExperimental ? t.settings.experimental : null, + ), + ) + .toList(), + selected: {ref.watch(ConfigOptions.serviceMode)}, + onSelectionChanged: (newSet) => ref + .read(ConfigOptions.serviceMode.notifier) + .update(newSet.first), + ), + ), + const Gap(8), + if (warpPrefaceCompleted) + SwitchListTile( + value: ref.watch(ConfigOptions.enableWarp), + onChanged: ref.watch(ConfigOptions.enableWarp.notifier).update, + title: Text(t.settings.config.enableWarp), + ) + else + ListTile( + title: Text(t.settings.config.setupWarp), + trailing: const Icon(FluentIcons.chevron_right_24_regular), + onTap: () => + ConfigOptionsRoute(section: ConfigOptionSection.warp.name) + .go(context), + ), + SwitchListTile( + value: ref.watch(ConfigOptions.enableTlsFragment), + onChanged: + ref.watch(ConfigOptions.enableTlsFragment.notifier).update, + title: Text(t.settings.config.enableTlsFragment), + ), + SwitchListTile( + value: ref.watch(ConfigOptions.enableMux), + onChanged: ref.watch(ConfigOptions.enableMux.notifier).update, + title: Text(t.settings.config.enableMux), + ), + ListTile( + title: Text(t.settings.config.allOptions), + trailing: const Icon(FluentIcons.chevron_right_24_regular), + dense: true, + onTap: () => const ConfigOptionsRoute().go(context), + ), + const Gap(16), + ], + ), + ); + } +} diff --git a/lib/features/home/widget/connection_button.dart b/lib/features/home/widget/connection_button.dart index 7ea7d0bf..d7074460 100644 --- a/lib/features/home/widget/connection_button.dart +++ b/lib/features/home/widget/connection_button.dart @@ -5,9 +5,11 @@ import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/model/failures.dart'; import 'package:hiddify/core/theme/theme_extensions.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:hiddify/features/connection/model/connection_status.dart'; import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/connection/widget/experimental_feature_notice.dart'; +import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart'; import 'package:hiddify/gen/assets.gen.dart'; import 'package:hiddify/utils/alerts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -20,6 +22,8 @@ class ConnectionButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); final connectionStatus = ref.watch(connectionNotifierProvider); + final requiresReconnect = + ref.watch(configOptionNotifierProvider).valueOrNull; ref.listen( connectionNotifierProvider, @@ -37,55 +41,58 @@ class ConnectionButton extends HookConsumerWidget { final buttonTheme = Theme.of(context).extension()!; - switch (connectionStatus) { - case AsyncData(value: final status): - final Color connectionLogoColor = status.isConnected - ? buttonTheme.connectedColor! - : buttonTheme.idleColor!; + Future showExperimentalNotice() async { + final hasExperimental = ref.read(ConfigOptions.hasExperimentalFeatures); + final canShowNotice = !ref.read(disableExperimentalFeatureNoticeProvider); + if (hasExperimental && canShowNotice && context.mounted) { + return await const ExperimentalFeatureNoticeDialog().show(context) ?? + true; + } + return true; + } - return _ConnectionButton( - onTap: () async { - var canConnect = true; - if (status case Disconnected()) { - final hasExperimental = - ref.read(ConfigOptions.hasExperimentalFeatures); - final canShowNotice = - !ref.read(disableExperimentalFeatureNoticeProvider); - - if (hasExperimental && canShowNotice && context.mounted) { - canConnect = await const ExperimentalFeatureNoticeDialog() - .show(context) ?? - true; - } - } - - if (canConnect) { - await ref + return _ConnectionButton( + onTap: switch (connectionStatus) { + AsyncData(value: Disconnected()) || AsyncError() => () async { + if (await showExperimentalNotice()) { + return await ref .read(connectionNotifierProvider.notifier) .toggleConnection(); } }, - enabled: !status.isSwitching, - label: status.present(t), - buttonColor: connectionLogoColor, - ); - case AsyncError(): - return _ConnectionButton( - onTap: () => - ref.read(connectionNotifierProvider.notifier).toggleConnection(), - enabled: true, - label: const Disconnected().present(t), - buttonColor: buttonTheme.idleColor!, - ); - default: - // HACK - return _ConnectionButton( - onTap: () {}, - enabled: false, - label: "", - buttonColor: Colors.red, - ); - } + AsyncData(value: Connected()) => () async { + if (requiresReconnect == true && await showExperimentalNotice()) { + return await ref + .read(connectionNotifierProvider.notifier) + .reconnect(await ref.read(activeProfileProvider.future)); + } + return await ref + .read(connectionNotifierProvider.notifier) + .toggleConnection(); + }, + _ => () {}, + }, + enabled: switch (connectionStatus) { + AsyncData(value: Connected()) || + AsyncData(value: Disconnected()) || + AsyncError() => + true, + _ => false, + }, + label: switch (connectionStatus) { + AsyncData(value: Connected()) when requiresReconnect == true => + t.home.connection.reconnect, + AsyncData(value: final status) => status.present(t), + _ => "", + }, + buttonColor: switch (connectionStatus) { + AsyncData(value: Connected()) when requiresReconnect == true => + Colors.teal, + AsyncData(value: Connected()) => buttonTheme.connectedColor!, + AsyncData(value: _) => buttonTheme.idleColor!, + _ => Colors.red, + }, + ); } } @@ -132,11 +139,17 @@ class _ConnectionButton extends StatelessWidget { onTap: onTap, child: Padding( padding: const EdgeInsets.all(36), - child: Assets.images.logo.svg( - colorFilter: ColorFilter.mode( - buttonColor, - BlendMode.srcIn, - ), + child: TweenAnimationBuilder( + tween: ColorTween(end: buttonColor), + duration: const Duration(milliseconds: 250), + builder: (context, value, child) { + return Assets.images.logo.svg( + colorFilter: ColorFilter.mode( + value!, + BlendMode.srcIn, + ), + ); + }, ), ), ), @@ -147,9 +160,25 @@ class _ConnectionButton extends StatelessWidget { ), const Gap(16), ExcludeSemantics( - child: Text( - label, - style: Theme.of(context).textTheme.titleMedium, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (child, animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, -0.2), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: Text( + label, + key: ValueKey(label), + style: Theme.of(context).textTheme.titleMedium, + ), ), ), ], diff --git a/lib/features/home/widget/home_page.dart b/lib/features/home/widget/home_page.dart index 13b20d30..0bf6d4a5 100644 --- a/lib/features/home/widget/home_page.dart +++ b/lib/features/home/widget/home_page.dart @@ -45,6 +45,11 @@ class HomePage extends HookConsumerWidget { ), ), actions: [ + IconButton( + onPressed: () => const QuickSettingsRoute().push(context), + icon: const Icon(FluentIcons.options_24_filled), + tooltip: t.settings.config.quickSettings, + ), IconButton( onPressed: () => const AddProfileRoute().push(context), icon: const Icon(FluentIcons.add_circle_24_filled), diff --git a/lib/singbox/model/singbox_config_enum.dart b/lib/singbox/model/singbox_config_enum.dart index 49c157a7..82990e8a 100644 --- a/lib/singbox/model/singbox_config_enum.dart +++ b/lib/singbox/model/singbox_config_enum.dart @@ -29,13 +29,26 @@ enum ServiceMode { return [proxy, tun]; } + bool get isExperimental => switch (this) { + tun => PlatformUtils.isDesktop, + tunService => PlatformUtils.isDesktop, + _ => false, + }; + String present(TranslationsEn t) => switch (this) { proxy => t.settings.config.serviceModes.proxy, systemProxy => t.settings.config.serviceModes.systemProxy, tun => - "${t.settings.config.serviceModes.tun}${PlatformUtils.isDesktop ? " (${t.settings.experimental})" : ""}", + "${t.settings.config.serviceModes.tun}${isExperimental ? " (${t.settings.experimental})" : ""}", tunService => - "${t.settings.config.serviceModes.tunService}${PlatformUtils.isDesktop ? " (${t.settings.experimental})" : ""}", + "${t.settings.config.serviceModes.tunService}${isExperimental ? " (${t.settings.experimental})" : ""}", + }; + + String presentShort(TranslationsEn t) => switch (this) { + proxy => t.settings.config.shortServiceModes.proxy, + systemProxy => t.settings.config.shortServiceModes.systemProxy, + tun => t.settings.config.shortServiceModes.tun, + tunService => t.settings.config.shortServiceModes.tunService, }; } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 6bdb9749..613ba947 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -3,6 +3,8 @@ PODS: - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS + - flutter_timezone (0.1.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - mobile_scanner (3.5.6): - FlutterMacOS @@ -52,6 +54,7 @@ PODS: DEPENDENCIES: - cupertino_http (from `Flutter/ephemeral/.symlinks/plugins/cupertino_http/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) @@ -77,6 +80,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/cupertino_http/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + flutter_timezone: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos FlutterMacOS: :path: Flutter/ephemeral mobile_scanner: @@ -107,6 +112,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: cupertino_http: afa11b9e2786b62da2671e4ddd32caf792503748 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + flutter_timezone: 6b906d1740654acb16e50b639835628fea851037 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 mobile_scanner: 54ceceae0c8da2457e26a362a6be5c61154b1829 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce diff --git a/pubspec.lock b/pubspec.lock index 71c3cdda..a568bb9a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -221,10 +221,10 @@ packages: dependency: "direct main" description: name: combine - sha256: c400552a66ab05ec98a01927b78cc548233804b6e3240cff3b038204ffda7bee + sha256: "8b52083c822a614a448fdd307e78c05266080e9747604b61fca5ddfe736a6c1e" url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.5.7-0.1.pre" convert: dependency: transitive description: @@ -245,10 +245,10 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+1" crypto: dependency: transitive description: @@ -309,18 +309,18 @@ packages: dependency: "direct main" description: name: dart_mappable - sha256: "7b6d38ae95f1ae8ffa65df9a5464f14b56c2de94699a035202ca4cd3a0ba249e" + sha256: f9f272f2af6c11adf4abc22574eb946df110251052a0d00c03519ecf2442defc url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.2.1" dart_mappable_builder: dependency: "direct dev" description: name: dart_mappable_builder - sha256: "98c058f7e80a98ea42d357d888ed1648d96bedac8b16872b58fc7024faefcdfe" + sha256: dd42e99b7e605ad6cf4a5c241a777c0f4f76a9769f27c439795355c444283074 url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.2.1" dart_style: dependency: transitive description: @@ -389,18 +389,18 @@ packages: dependency: "direct main" description: name: drift - sha256: b50a8342c6ddf05be53bda1d246404cbad101b64dc73e8d6d1ac1090d119b4e2 + sha256: "3b276c838ff7f8e19aac18a51f9b388715268f3534eaaf8047c8455ef3c1738d" url: "https://pub.dev" source: hosted - version: "2.15.0" + version: "2.16.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: c037d9431b6f8dc633652b1469e5f53aaec6e4eb405ed29dd232fa888ef10d88 + sha256: "66cf3e397448f855523d7b6b7b3789db232b211db96543a42285464d05f3bf72" url: "https://pub.dev" source: hosted - version: "2.15.0" + version: "2.16.0" equatable: dependency: transitive description: @@ -421,10 +421,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" ffigen: dependency: "direct dev" description: @@ -469,10 +469,10 @@ packages: dependency: "direct main" description: name: fluentui_system_icons - sha256: abe7c343e2151e0ad6544653e0b6601686b993bc436ccde72b88cea677db0c0a + sha256: bb2dd7b5dc2c14c8a18cab6405a67c4697825d2662553b1b22d1f7ddc1057235 url: "https://pub.dev" source: hosted - version: "1.1.226" + version: "1.1.229" flutter: dependency: "direct main" description: flutter @@ -623,10 +623,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -745,10 +745,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -869,6 +869,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lint: dependency: "direct dev" description: @@ -897,18 +921,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" maybe_just_nothing: dependency: transitive description: @@ -929,10 +953,10 @@ packages: dependency: "direct main" description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -1001,10 +1025,10 @@ packages: dependency: "direct main" description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -1393,10 +1417,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -1454,26 +1478,26 @@ packages: dependency: "direct main" description: name: slang - sha256: "95dee03eb3fd1b36c99f365d4eace270a0d83c6148f8e7d1057806ef60cfaf12" + sha256: "5e08ac915ac27a3508863f37734280d30c3713d56746cd2e4a5da77413da4b95" url: "https://pub.dev" source: hosted - version: "3.29.0" + version: "3.30.1" slang_build_runner: dependency: "direct dev" description: name: slang_build_runner - sha256: "929ea4bf24f11e09afd2b01abd658f550da7eb4039ae83d91bc220f942e18cb3" + sha256: "2daff2deb2ab8d557a2e7de5405c0ee1376afba5d0231570c2d2c3c56da8a692" url: "https://pub.dev" source: hosted - version: "3.29.0" + version: "3.30.0" slang_flutter: dependency: "direct main" description: name: slang_flutter - sha256: "34c7cf297c608e24d3957a29e75c6790f4dbbfb1a4783d261a6c1e33ede7ad0f" + sha256: "9ee040b0d364d3a4d692e4af536acff6ef513870689403494ebc6d59b0dccea6" url: "https://pub.dev" source: hosted - version: "3.29.0" + version: "3.30.0" sliver_tools: dependency: "direct main" description: @@ -1542,10 +1566,10 @@ packages: dependency: transitive description: name: sqlite3 - sha256: c4a4c5a4b2a32e2d0f6837b33d7c91a67903891a5b7dbe706cf4b1f6b0c798c5 + sha256: "072128763f1547e3e9b4735ce846bfd226d68019ccda54db4cd427b12dfdedc9" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" sqlite3_flutter_libs: dependency: "direct main" description: @@ -1734,10 +1758,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.5" url_launcher_android: dependency: transitive description: @@ -1782,10 +1806,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" url_launcher_windows: dependency: transitive description: @@ -1814,26 +1838,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1867,13 +1891,13 @@ packages: source: hosted version: "1.1.0" web: - dependency: transitive + dependency: "direct overridden" description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: @@ -1955,5 +1979,5 @@ packages: source: hosted version: "2.1.1" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index ab48500a..2dc27de1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 0.16.16+1616 environment: - sdk: ">=3.2.0 <4.0.0" + sdk: ">=3.3.0 <4.0.0" dependencies: flutter: @@ -13,8 +13,8 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.6 intl: ^0.18.1 - slang: ^3.29.0 - slang_flutter: ^3.29.0 + slang: ^3.30.1 + slang_flutter: ^3.30.0 fpdart: ^1.1.0 freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 @@ -22,11 +22,11 @@ dependencies: flutter_hooks: ^0.20.5 riverpod_annotation: ^2.3.4 rxdart: ^0.27.7 - drift: ^2.15.0 + drift: ^2.16.0 sqlite3_flutter_libs: ^0.5.20 shared_preferences: ^2.2.2 dio: ^5.4.1 - ffi: ^2.1.0 + ffi: ^2.1.2 path_provider: ^2.1.1 mobile_scanner: ^4.0.0 protocol_handler: ^0.2.0 @@ -35,12 +35,12 @@ dependencies: window_manager: ^0.3.8 tray_manager: ^0.2.1 package_info_plus: ^5.0.1 - url_launcher: ^6.2.4 + url_launcher: ^6.2.5 vclibs: ^0.1.2 launch_at_startup: ^0.2.2 sentry_flutter: ^7.16.1 sentry_dart_plugin: ^1.7.1 - combine: ^0.5.6 + combine: ^0.5.7-0.1.pre path: ^1.8.3 loggy: ^2.0.3 flutter_loggy: ^2.0.2 @@ -54,7 +54,7 @@ dependencies: go_router: ^13.2.0 flex_color_scheme: ^7.3.1 flutter_animate: ^4.5.0 - flutter_svg: ^2.0.9 + flutter_svg: ^2.0.10+1 gap: ^3.0.1 percent_indicator: ^4.2.3 sliver_tools: ^0.2.12 @@ -71,8 +71,8 @@ dependencies: dio_smart_retry: ^6.0.0 cupertino_http: ^1.3.0 wolt_modal_sheet: ^0.4.1 - dart_mappable: ^4.2.0 - fluentui_system_icons: ^1.1.226 + dart_mappable: ^4.2.1 + fluentui_system_icons: ^1.1.229 circle_flags: ^4.0.2 http: ^1.2.0 timezone_to_country: ^2.1.0 @@ -86,13 +86,17 @@ dev_dependencies: json_serializable: ^6.7.1 freezed: ^2.4.7 riverpod_generator: ^2.3.11 - drift_dev: ^2.15.0 + drift_dev: ^2.16.0 ffigen: ^8.0.2 - slang_build_runner: ^3.29.0 + slang_build_runner: ^3.30.0 flutter_gen_runner: ^5.4.0 go_router_builder: ^2.4.1 dependency_validator: ^3.2.3 - dart_mappable_builder: ^4.2.0 + dart_mappable_builder: ^4.2.1 + +dependency_overrides: + # drift & package_info_plus are not compatible + web: ^0.5.1 flutter: uses-material-design: true