diff --git a/core/Clash.Meta b/core/Clash.Meta index 1c46eb8..9b94b9c 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit 1c46eb82bfa75828dcef7664816d2e8aab20df03 +Subproject commit 9b94b9c339038c733d128b8e2d6e91b8d4345753 diff --git a/lib/application.dart b/lib/application.dart index 435cf4e..ad9e2f6 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -51,7 +51,6 @@ class Application extends StatefulWidget { } class ApplicationState extends State { - late AppController appController; late SystemColorSchemes systemColorSchemes; ColorScheme _getAppColorScheme({ @@ -72,10 +71,10 @@ class ApplicationState extends State { @override void initState() { super.initState(); - appController = AppController(context); + globalState.appController = AppController(context); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - appController.afterInit(); - appController.initLink(); + globalState.appController.afterInit(); + globalState.appController.initLink(); _updateGroups(); }); } @@ -105,7 +104,7 @@ class ApplicationState extends State { ); WidgetsBinding.instance.addPostFrameCallback((_) { - appController.updateSystemColorSchemes(systemColorSchemes); + globalState.appController.updateSystemColorSchemes(systemColorSchemes); }); } @@ -114,11 +113,13 @@ class ApplicationState extends State { globalState.groupsUpdateTimer?.cancel(); globalState.groupsUpdateTimer = null; } - globalState.groupsUpdateTimer ??= - Timer.periodic(appConstant.httpTimeoutDuration, (timer) async { - await appController.updateGroups(); - appController.appState.sortNum++; - }); + globalState.groupsUpdateTimer ??= Timer.periodic( + appConstant.httpTimeoutDuration, + (timer) async { + await globalState.appController.updateGroups(); + globalState.appController.appState.sortNum++; + }, + ); } @override @@ -145,9 +146,9 @@ class ApplicationState extends State { GlobalWidgetsLocalizations.delegate ], title: appConstant.name, - locale: Other.getLocaleForString(state.locale), + locale: other.getLocaleForString(state.locale), supportedLocales: - AppLocalizations.delegate.supportedLocales, + AppLocalizations.delegate.supportedLocales, themeMode: state.themeMode, theme: ThemeData( useMaterial3: true, @@ -180,7 +181,7 @@ class ApplicationState extends State { @override Future dispose() async { linkManager.destroy(); - await appController.savePreferences(); + await globalState.appController.savePreferences(); super.dispose(); } } diff --git a/lib/common/common.dart b/lib/common/common.dart index a295542..8303f6d 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -8,7 +8,7 @@ export 'num.dart'; export 'navigation.dart'; export 'window.dart'; export 'system.dart'; -export 'file.dart'; +export 'picker.dart'; export 'android.dart'; export 'launch.dart'; export 'protocol.dart'; diff --git a/lib/common/constant.dart b/lib/common/constant.dart index 7c47786..656e9b0 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -2,6 +2,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +const appName = "FlClash"; + class AppConstant { final packageName = "com.follow.clash"; final name = "FlClash"; diff --git a/lib/common/context.dart b/lib/common/context.dart index 632c1c0..6aca2a5 100644 --- a/lib/common/context.dart +++ b/lib/common/context.dart @@ -1,15 +1,7 @@ -import 'package:fl_clash/application.dart'; -import 'package:fl_clash/controller.dart'; import 'package:fl_clash/widgets/scaffold.dart'; import 'package:flutter/material.dart'; extension BuildContextExtension on BuildContext { - AppController get appController { - final appController = - findAncestorStateOfType()?.appController; - assert(appController != null, "only use application environment"); - return appController!; - } CommonScaffoldState? get commonScaffoldState { return findAncestorStateOfType(); diff --git a/lib/common/other.dart b/lib/common/other.dart index 435da1f..f7f2c43 100644 --- a/lib/common/other.dart +++ b/lib/common/other.dart @@ -1,23 +1,27 @@ import 'dart:io'; +import 'dart:isolate'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:zxing2/qrcode.dart'; +import 'package:image/image.dart' as img; class Other { - static Color? getDelayColor(int? delay) { + Color? getDelayColor(int? delay) { if (delay == null) return null; if (delay < 0) return Colors.red; if (delay < 600) return Colors.green; return const Color(0xFFC57F0A); } - static String getDateStringLast2(int value) { + String getDateStringLast2(int value) { var valueRaw = "0$value"; return valueRaw.substring( valueRaw.length - 2, ); } - static String getTimeDifference(DateTime dateTime) { + String getTimeDifference(DateTime dateTime) { var currentDateTime = DateTime.now(); var difference = currentDateTime.difference(dateTime); var inHours = difference.inHours; @@ -27,7 +31,7 @@ class Other { return "${getDateStringLast2(inHours)}:${getDateStringLast2(inMinutes)}:${getDateStringLast2(inSeconds)}"; } - static String getTimeText(int? timeStamp) { + String getTimeText(int? timeStamp) { if (timeStamp == null) { return '00:00:00'; } @@ -39,7 +43,7 @@ class Other { return "${getDateStringLast2(inHours)}:${getDateStringLast2(inMinutes)}:${getDateStringLast2(inSeconds)}"; } - static Locale? getLocaleForString(String? localString) { + Locale? getLocaleForString(String? localString) { if (localString == null) return null; var localSplit = localString.split("_"); if (localSplit.length == 1) { @@ -57,7 +61,7 @@ class Other { return null; } - static int sortByChar(String a, String b) { + int sortByChar(String a, String b) { if (a.isEmpty && b.isEmpty) { return 0; } @@ -77,7 +81,7 @@ class Other { } } - static String getOverwriteLabel(String label) { + String getOverwriteLabel(String label) { final reg = RegExp(r'\((\d+)\)$'); final matches = reg.allMatches(label); if (matches.isNotEmpty) { @@ -89,21 +93,7 @@ class Other { } } - // static FutureOr Function(T p) debounce(void Function(T? p) func, - // {Duration? duration}) { - // Timer? timer; - // return ([T? p]) { - // if (timer != null) { - // timer?.cancel(); - // } - // timer = Timer(duration ?? const Duration(milliseconds: 300), () { - // func(p); - // }); - // }; - // } - - - static String getTrayIconPath() { + String getTrayIconPath() { if (Platform.isWindows) { return "assets/images/app_icon.ico"; } else { @@ -111,7 +101,7 @@ class Other { } } - static int compareVersions(String version1, String version2) { + int compareVersions(String version1, String version2) { List v1 = version1.split('+')[0].split('.'); List v2 = version2.split('+')[0].split('.'); int major1 = int.parse(v1[0]); @@ -133,4 +123,30 @@ class Other { int build2 = version2.contains('+') ? int.parse(version2.split('+')[1]) : 0; return build1.compareTo(build2); } + + Future parseQRCode(Uint8List? bytes) { + return Isolate.run(() { + if (bytes == null) return null; + img.Image? image = img.decodeImage(bytes); + LuminanceSource source = RGBLuminanceSource( + image!.width, + image.height, + image + .convert(numChannels: 4) + .getBytes(order: img.ChannelOrder.abgr) + .buffer + .asInt32List(), + ); + final bitmap = BinaryBitmap(GlobalHistogramBinarizer(source)); + final reader = QRCodeReader(); + try { + final result = reader.decode(bitmap); + return result.text; + } catch (_) { + return null; + } + }); + } } + +final other = Other(); diff --git a/lib/common/file.dart b/lib/common/picker.dart similarity index 53% rename from lib/common/file.dart rename to lib/common/picker.dart index 006370b..9684365 100644 --- a/lib/common/file.dart +++ b/lib/common/picker.dart @@ -1,11 +1,12 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; -import 'package:fl_clash/common/app_localizations.dart'; +import 'package:fl_clash/common/common.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:fl_clash/models/models.dart'; -class FileUtil { - static Future> pickerConfig() async { +class Picker { + Future> pickerConfigFile() async { FilePickerResult? filePickerResult; if (Platform.isAndroid) { filePickerResult = await FilePicker.platform.pickFiles( @@ -26,4 +27,17 @@ class FileUtil { } return Result.success(data: file); } + + Future> pickerConfigQRCode() async { + final xFile = await ImagePicker().pickImage(source: ImageSource.gallery); + final bytes = await xFile?.readAsBytes(); + if (bytes == null) return Result.error(); + final result = await other.parseQRCode(bytes); + if (result == null || !result.isUrl) { + return Result.error(message: appLocalizations.pleaseUploadValidQrcode); + } + return Result.success(data: result); + } } + +final picker = Picker(); diff --git a/lib/common/preferences.dart b/lib/common/preferences.dart index 664a965..e5b68ad 100644 --- a/lib/common/preferences.dart +++ b/lib/common/preferences.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter/cupertino.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/models.dart'; @@ -29,8 +28,7 @@ class Preferences { try { return ClashConfig.fromJson(clashConfigMap); } catch (e) { - debugPrint(e.toString()); - return null; + throw e.toString(); } } @@ -50,8 +48,7 @@ class Preferences { try { return Config.fromJson(configMap); } catch (e) { - debugPrint(e.toString()); - return null; + throw e.toString(); } } diff --git a/lib/common/request.dart b/lib/common/request.dart index 2be27a8..f7333f8 100644 --- a/lib/common/request.dart +++ b/lib/common/request.dart @@ -29,7 +29,7 @@ class Request { final packageInfo = await appPackage.packageInfoCompleter.future; final version = packageInfo.version; final hasUpdate = - Other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; + other.compareVersions(remoteVersion.replaceAll('v', ''), version) > 0; if (!hasUpdate) return Result.error(); return Result.success(data: body['body']); } diff --git a/lib/controller.dart b/lib/controller.dart index 75b4d81..ab52fa6 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -267,31 +267,6 @@ class AppController { } } - addProfileFormURL(String url) async { - globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); - toProfiles(); - final commonScaffoldState = globalState.homeScaffoldKey.currentState; - if (commonScaffoldState?.mounted != true) return; - commonScaffoldState?.loadingRun( - () async { - await Future.delayed(const Duration(milliseconds: 300)); - final profile = Profile( - url: url, - ); - final res = await profile.update(); - if (res.type == ResultType.success) { - addProfile(profile); - } else { - debugPrint(res.message); - globalState.showMessage( - title: "${appLocalizations.add}${appLocalizations.profile}", - message: TextSpan(text: res.message!), - ); - } - }, - ); - } - initLink() { linkManager.initAppLinksListen( (url) { @@ -321,8 +296,33 @@ class AppController { ); } + addProfileFormURL(String url) async { + globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); + toProfiles(); + final commonScaffoldState = globalState.homeScaffoldKey.currentState; + if (commonScaffoldState?.mounted != true) return; + commonScaffoldState?.loadingRun( + () async { + await Future.delayed(const Duration(milliseconds: 300)); + final profile = Profile( + url: url, + ); + final res = await profile.update(); + if (res.type == ResultType.success) { + addProfile(profile); + } else { + debugPrint(res.message); + globalState.showMessage( + title: "${appLocalizations.add}${appLocalizations.profile}", + message: TextSpan(text: res.message!), + ); + } + }, + ); + } + addProfileFormFile() async { - final result = await FileUtil.pickerConfig(); + final result = await picker.pickerConfigFile(); if (result.type == ResultType.error) return; if (!context.mounted) return; globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); @@ -351,6 +351,22 @@ class AppController { ); } + addProfileFormQrCode() async { + final result = await picker.pickerConfigQRCode(); + if (result.type == ResultType.error) { + if(result.message != null){ + globalState.showMessage( + title: appLocalizations.tip, + message: TextSpan( + text: result.message, + ), + ); + } + return; + } + addProfileFormURL(result.data!); + } + clearShowProxyDelay() { final showProxyDelay = appState.getRealProxyName(appState.showProxyName); if (showProxyDelay != null) { diff --git a/lib/fragments/access.dart b/lib/fragments/access.dart index bf2534f..814fb92 100644 --- a/lib/fragments/access.dart +++ b/lib/fragments/access.dart @@ -2,6 +2,7 @@ import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -292,9 +293,9 @@ class AccessFragment extends StatelessWidget { @override Widget build(BuildContext context) { - if (context.appController.appState.packages.isEmpty) { + if (globalState.appController.appState.packages.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { - context.appController.updatePackages(); + globalState.appController.updatePackages(); }); } return Selector( diff --git a/lib/fragments/application_setting.dart b/lib/fragments/application_setting.dart index 63aa18d..06bdd81 100644 --- a/lib/fragments/application_setting.dart +++ b/lib/fragments/application_setting.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -39,13 +40,13 @@ class ApplicationSettingFragment extends StatelessWidget { selector: (_, config) => config.isCompatible, builder: (_, isCompatible, __) { return ListItem.switchItem( - leading: const Icon(Icons.device_hub), - title: const Text("兼容模式"), - subtitle: const Text("开启将失去部分应用能力,获得全量的Clash的支持"), + leading: const Icon(Icons.expand), + title: Text(appLocalizations.compatible), + subtitle: Text(appLocalizations.compatibleDesc), delegate: SwitchDelegate( value: isCompatible, onChanged: (bool value) async { - final appController = context.appController; + final appController = globalState.appController; appController.config.isCompatible = value; await appController.updateClashConfig(isPatch: false); await appController.updateGroups(); @@ -120,7 +121,7 @@ class ApplicationSettingFragment extends StatelessWidget { onChanged: (bool value) { final config = context.read(); config.openLogs = value; - context.appController.updateLogStatus(); + globalState.appController.updateLogStatus(); }, ), ); diff --git a/lib/fragments/config.dart b/lib/fragments/config.dart index 536a499..61873a3 100644 --- a/lib/fragments/config.dart +++ b/lib/fragments/config.dart @@ -24,8 +24,8 @@ class _ConfigFragmentState extends State { try { final mixedPort = int.parse(port); if (mixedPort < 1024 || mixedPort > 49151) throw "Invalid port"; - context.appController.clashConfig.mixedPort = mixedPort; - context.appController.updateClashConfigDebounce(); + globalState.appController.clashConfig.mixedPort = mixedPort; + globalState.appController.updateClashConfigDebounce(); } catch (e) { globalState.showMessage( title: appLocalizations.proxyPort, @@ -39,9 +39,9 @@ class _ConfigFragmentState extends State { _updateLoglevel(LogLevel? logLevel) { if (logLevel == null || - logLevel == context.appController.clashConfig.logLevel) return; - context.appController.clashConfig.logLevel = logLevel; - context.appController.updateClashConfigDebounce(); + logLevel == globalState.appController.clashConfig.logLevel) return; + globalState.appController.clashConfig.logLevel = logLevel; + globalState.appController.updateClashConfigDebounce(); } @override @@ -59,12 +59,31 @@ class _ConfigFragmentState extends State { onChanged: (bool value) async { final clashConfig = context.read(); clashConfig.allowLan = value; - context.appController.updateClashConfigDebounce(); + globalState.appController.updateClashConfigDebounce(); }, ), ); }, ), + if (system.isDesktop) + Selector( + selector: (_, clashConfig) => clashConfig.tun.enable, + builder: (_, tunEnable, __) { + return ListItem.switchItem( + leading: const Icon(Icons.support), + title: Text(appLocalizations.tun), + subtitle: Text(appLocalizations.tunDesc), + delegate: SwitchDelegate( + value: tunEnable, + onChanged: (bool value) async { + final clashConfig = context.read(); + clashConfig.tun = Tun(enable: value); + globalState.appController.updateClashConfigDebounce(); + }, + ), + ); + }, + ), Selector( selector: (_, clashConfig) => clashConfig.mixedPort, builder: (_, mixedPort, __) { diff --git a/lib/fragments/dashboard/network_detection.dart b/lib/fragments/dashboard/network_detection.dart index 240503b..93ab679 100644 --- a/lib/fragments/dashboard/network_detection.dart +++ b/lib/fragments/dashboard/network_detection.dart @@ -82,7 +82,7 @@ class _NetworkDetectionState extends State { if (!isCurrent || currentProxyName == null || !isInit) return; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (delay == null) { - context.appController.setDelay( + globalState.appController.setDelay( Delay( name: currentProxyName, value: 0, @@ -162,7 +162,7 @@ class _NetworkDetectionState extends State { ), Flexible( child: Container( - height: context.appController.measure.titleLargeHeight, + height: globalState.appController.measure.titleLargeHeight, alignment: Alignment.centerLeft, child: FadeBox( child: _buildDescription( diff --git a/lib/fragments/dashboard/network_speed.dart b/lib/fragments/dashboard/network_speed.dart index 76ba1dc..5e5c26a 100644 --- a/lib/fragments/dashboard/network_speed.dart +++ b/lib/fragments/dashboard/network_speed.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -68,7 +69,7 @@ class _NetworkSpeedState extends State { style: bodyMedium, maxLines: 1, ); - final size = context.appController.measure.computeTextSize(valueText); + final size = globalState.appController.measure.computeTextSize(valueText); return Column( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/fragments/dashboard/outbound_mode.dart b/lib/fragments/dashboard/outbound_mode.dart index 22ea84a..ca779ef 100644 --- a/lib/fragments/dashboard/outbound_mode.dart +++ b/lib/fragments/dashboard/outbound_mode.dart @@ -1,6 +1,7 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -10,7 +11,7 @@ class OutboundMode extends StatelessWidget { const OutboundMode({super.key}); _changeMode(BuildContext context, Mode? value) async { - final appController = context.appController; + final appController = globalState.appController; final clashConfig = appController.clashConfig; final config = appController.config; if (value == null || clashConfig.mode == value) return; diff --git a/lib/fragments/dashboard/start_button.dart b/lib/fragments/dashboard/start_button.dart index f5ed28f..1b0bf55 100644 --- a/lib/fragments/dashboard/start_button.dart +++ b/lib/fragments/dashboard/start_button.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -48,7 +49,7 @@ class _StartButtonState extends State } updateSystemProxy() async { - final appController = context.appController; + final appController = globalState.appController; await appController.updateSystemProxy(isStart); if (isStart && mounted) { appController.clearShowProxyDelay(); @@ -66,10 +67,10 @@ class _StartButtonState extends State if (!state.isInit || !state.hasProfile) { return Container(); } - final textWidth = context.appController.measure + final textWidth = globalState.appController.measure .computeTextSize( Text( - Other.getTimeDifference( + other.getTimeDifference( DateTime.now(), ), style: @@ -133,7 +134,7 @@ class _StartButtonState extends State child: Selector( selector: (_, appState) => appState.runTime, builder: (_, int? value, __) { - final text = Other.getTimeText(value); + final text = other.getTimeText(value); return Text( text, style: Theme.of(context).textTheme.titleMedium?.toSoftBold(), diff --git a/lib/fragments/profiles/add_profile.dart b/lib/fragments/profiles/add_profile.dart index a033c53..0623599 100644 --- a/lib/fragments/profiles/add_profile.dart +++ b/lib/fragments/profiles/add_profile.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/pages/scan.dart'; import 'package:fl_clash/state.dart'; @@ -8,17 +7,21 @@ import 'package:flutter/material.dart'; class AddProfile extends StatelessWidget { final BuildContext context; - const AddProfile({super.key, required this.context}); + const AddProfile({super.key, required this.context,}); _handleAddProfileFormFile() async { - context.appController.addProfileFormFile(); + globalState.appController.addProfileFormFile(); } _handleAddProfileFormURL(String url) async { - context.appController.addProfileFormURL(url); + globalState.appController.addProfileFormURL(url); } _toScan() async { + if(system.isDesktop){ + globalState.appController.addProfileFormQrCode(); + return; + } final url = await Navigator.of(context) .push(MaterialPageRoute(builder: (_) => const ScanPage())); if (url != null) { @@ -39,7 +42,6 @@ class AddProfile extends StatelessWidget { Widget build(context) { return ListView( children: [ - if (Platform.isAndroid) ListItem( leading: const Icon(Icons.qr_code), title: Text(appLocalizations.qrcode), diff --git a/lib/fragments/profiles/edit_profile.dart b/lib/fragments/profiles/edit_profile.dart index f753097..57279e8 100644 --- a/lib/fragments/profiles/edit_profile.dart +++ b/lib/fragments/profiles/edit_profile.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -38,7 +39,7 @@ class _EditProfileState extends State { _handleConfirm() { if (!_formKey.currentState!.validate()) return; - final config = context.read(); + final config = widget.context.read(); final hasUpdate = widget.profile.url != urlController.text; widget.profile.url = urlController.text; widget.profile.label = labelController.text; @@ -48,7 +49,7 @@ class _EditProfileState extends State { config.setProfile(widget.profile); if (hasUpdate) { widget.context.findAncestorStateOfType()?.loadingRun( - () => context.appController.updateProfile( + () => globalState.appController.updateProfile( widget.profile.id, ), ); diff --git a/lib/fragments/profiles/profiles.dart b/lib/fragments/profiles/profiles.dart index 879abb4..e40c20b 100644 --- a/lib/fragments/profiles/profiles.dart +++ b/lib/fragments/profiles/profiles.dart @@ -1,6 +1,7 @@ import 'package:fl_clash/fragments/profiles/edit_profile.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; @@ -24,7 +25,6 @@ class ProfilesFragment extends StatefulWidget { } class _ProfilesFragmentState extends State { - String _getLastUpdateTimeDifference(DateTime lastDateTime) { final currentDateTime = DateTime.now(); final difference = currentDateTime.difference(lastDateTime); @@ -50,12 +50,12 @@ class _ProfilesFragmentState extends State { } _handleDeleteProfile(String id) async { - context.appController.deleteProfile(id); + globalState.appController.deleteProfile(id); } _handleUpdateProfile(String id) async { context.findAncestorStateOfType()?.loadingRun( - () => context.appController.updateProfile(id), + () => globalState.appController.updateProfile(id), ); } @@ -175,9 +175,9 @@ class _ProfilesFragmentState extends State { _handleShowAddExtendPage() { showExtendPage( - context, + globalState.navigatorKey.currentState!.context, body: AddProfile( - context: context, + context: globalState.navigatorKey.currentState!.context, ), title: "${appLocalizations.add}${appLocalizations.profile}", ); @@ -210,7 +210,7 @@ class _ProfilesFragmentState extends State { child: _profileItem( profile: profile, groupValue: state.currentProfileId, - onChanged: context.appController.changeProfileDebounce, + onChanged: globalState.appController.changeProfileDebounce, ), ), ], diff --git a/lib/fragments/proxies.dart b/lib/fragments/proxies.dart index 71c9a2a..f9cefe2 100644 --- a/lib/fragments/proxies.dart +++ b/lib/fragments/proxies.dart @@ -140,7 +140,7 @@ class ProxiesTabView extends StatelessWidget { List _sortOfName(List proxies) { return List.of(proxies) ..sort( - (a, b) => Other.sortByChar(a.name, b.name), + (a, b) => other.sortByChar(a.name, b.name), ); } @@ -178,7 +178,7 @@ class ProxiesTabView extends StatelessWidget { } double _getItemHeight(BuildContext context) { - final measure = context.appController.measure; + final measure = globalState.appController.measure; return 12 * 2 + measure.bodyMediumHeight * 2 + measure.bodySmallHeight + @@ -192,7 +192,7 @@ class ProxiesTabView extends StatelessWidget { required bool isSelected, required Proxy proxy, }) { - final measure = context.appController.measure; + final measure = globalState.appController.measure; return CommonCard( isSelected: isSelected, onPressed: onPressed, @@ -276,7 +276,7 @@ class ProxiesTabView extends StatelessWidget { delay > 0 ? '$delay ms' : "Timeout", style: context.textTheme.labelSmall?.copyWith( overflow: TextOverflow.ellipsis, - color: Other.getDelayColor( + color: other.getDelayColor( delay, ), ), @@ -325,21 +325,21 @@ class ProxiesTabView extends StatelessWidget { context, isSelected: state.isSelected, onPressed: () { - final appController = context.appController; + final appController = globalState.appController; final group = appController.appState.getGroupWithName(groupName)!; if (group.type != GroupType.Selector) { globalState.showSnackBar( context, - message: "当前代理组无法选择", + message: appLocalizations.notSelectedTip, ); return; } - context.appController.config.updateCurrentSelectedMap( + globalState.appController.config.updateCurrentSelectedMap( groupName, proxy.name, ); - context.appController.changeProxy(); + globalState.appController.changeProxy(); }, proxy: proxy, ); @@ -422,7 +422,7 @@ class _DelayTestButtonContainerState extends State _healthcheck() async { _controller.forward(); - context.appController.healthcheck(); + globalState.appController.healthcheck(); await Future.delayed( appConstant.httpTimeoutDuration + appConstant.moreDuration, ); diff --git a/lib/fragments/theme.dart b/lib/fragments/theme.dart index 534c38f..c654db4 100644 --- a/lib/fragments/theme.dart +++ b/lib/fragments/theme.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -28,7 +29,7 @@ class ThemeFragment extends StatelessWidget { return CommonCard( isSelected: isSelected, onPressed: () { - context.appController.config.themeMode = themeModeItem.themeMode; + globalState.appController.config.themeMode = themeModeItem.themeMode; }, child: Padding( padding: const EdgeInsets.symmetric(horizontal:16), @@ -62,7 +63,7 @@ class ThemeFragment extends StatelessWidget { isSelected: isSelected, primaryColor: color, onPressed: () { - context.appController.config.primaryColor = color?.value; + globalState.appController.config.primaryColor = color?.value; }, ); } diff --git a/lib/fragments/tools.dart b/lib/fragments/tools.dart index a2f36b1..50c713d 100644 --- a/lib/fragments/tools.dart +++ b/lib/fragments/tools.dart @@ -114,7 +114,7 @@ class _ToolboxFragmentState extends State { selector: (_, config) => config.locale, builder: (_, localeString, __) { final subTitle = localeString ?? appLocalizations.defaultText; - final currentLocale = Other.getLocaleForString(localeString); + final currentLocale = other.getLocaleForString(localeString); return ListTile( leading: const Icon(Icons.language_outlined), title: Text(appLocalizations.language), @@ -211,31 +211,23 @@ class _ToolboxFragmentState extends State { @override Widget build(BuildContext context) { final items = [ - LayoutBuilder(builder: (context, container) { - final isMobile = context.isMobile; - if (!isMobile) { - return Container( - margin: const EdgeInsets.only(top: 18), + Selector>( + selector: (_, appState) => appState.navigationItems, + builder: (_, navigationItems, __) { + final moreNavigationItems = navigationItems + .where( + (element) => element.modes.contains(NavigationItemMode.more), + ) + .toList(); + if (moreNavigationItems.isEmpty) { + return Container(); + } + return _buildSection( + title: appLocalizations.more, + content: _buildNavigationMenu(moreNavigationItems), ); - } - return Selector>( - selector: (_, appState) => appState.navigationItems, - builder: (_, navigationItems, __) { - final moreNavigationItems = navigationItems - .where( - (element) => element.modes.contains(NavigationItemMode.more), - ) - .toList(); - if (moreNavigationItems.isEmpty) { - return Container(); - } - return _buildSection( - title: appLocalizations.more, - content: _buildNavigationMenu(moreNavigationItems), - ); - }, - ); - }), + }, + ), _buildSection( title: appLocalizations.settings, content: _getSettingList(), diff --git a/lib/l10n/arb/intl_en.arb b/lib/l10n/arb/intl_en.arb index 90311cb..3e5a074 100644 --- a/lib/l10n/arb/intl_en.arb +++ b/lib/l10n/arb/intl_en.arb @@ -35,7 +35,7 @@ "overrideDesc": "Override Proxy related config", "allowLan": "AllowLan", "allowLanDesc": "Allow access proxy through the LAN", - "tun": "Tun", + "tun": "Tun mode", "tunDesc": "only effective in administrator mode", "minimizeOnExit": "Minimize on exit", "minimizeOnExitDesc": "Modify the default system exit event", @@ -92,6 +92,7 @@ "delaySort": "Sort by delay", "nameSort": "Sort by name", "pleaseUploadFile": "Please upload file", + "pleaseUploadValidQrcode": "Please upload a valid QR code", "blacklistMode": "Blacklist mode", "whitelistMode": "Whitelist mode", "filterSystemApp": "Filter system app", @@ -119,5 +120,9 @@ "desc": "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.", "startVpn": "Staring VPN...", "stopVpn": "Stopping VPN...", - "discovery": "Discovery a new version" + "discovery": "Discovery a new version", + "compatible": "Compatibility mode", + "compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.", + "notSelectedTip": "The current proxy group cannot be selected.", + "tip": "tip" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_zh_CN.arb b/lib/l10n/arb/intl_zh_CN.arb index e271eef..3af974a 100644 --- a/lib/l10n/arb/intl_zh_CN.arb +++ b/lib/l10n/arb/intl_zh_CN.arb @@ -35,7 +35,7 @@ "overrideDesc": "覆写代理相关配置", "allowLan": "局域网代理", "allowLanDesc": "允许通过局域网访问代理", - "tun": "虚拟网络设备", + "tun": "Tun模式", "tunDesc": "仅在管理员模式生效", "minimizeOnExit": "退出时最小化", "minimizeOnExitDesc": "修改系统默认退出事件", @@ -92,6 +92,7 @@ "delaySort": "按延迟排序", "nameSort": "按名称排序", "pleaseUploadFile": "请上传文件", + "pleaseUploadValidQrcode": "请上传有效的二维码", "blacklistMode": "黑名单模式", "whitelistMode": "白名单模式", "filterSystemApp": "过滤系统应用", @@ -119,5 +120,9 @@ "desc": "基于ClashMeta的多平台代理客户端,简单易用,开源无广告。", "startVpn": "正在启动VPN...", "stopVpn": "正在停止VPN...", - "discovery": "发现新版本" + "discovery": "发现新版本", + "compatible": "兼容模式", + "compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持", + "notSelectedTip": "当前代理组无法选中", + "tip": "提示" } \ No newline at end of file diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index b0d003d..f3143c9 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -56,6 +56,10 @@ class MessageLookup extends MessageLookupByLibrary { "cancelSelectAll": MessageLookupByLibrary.simpleMessage("Cancel select all"), "checkUpdate": MessageLookupByLibrary.simpleMessage("Check update"), + "compatible": + MessageLookupByLibrary.simpleMessage("Compatibility mode"), + "compatibleDesc": MessageLookupByLibrary.simpleMessage( + "Opening it will lose part of its application ability and gain the support of full amount of Clash."), "confirm": MessageLookupByLibrary.simpleMessage("Confirm"), "core": MessageLookupByLibrary.simpleMessage("Core"), "coreInfo": MessageLookupByLibrary.simpleMessage("Core info"), @@ -112,6 +116,8 @@ class MessageLookup extends MessageLookupByLibrary { "noProxy": MessageLookupByLibrary.simpleMessage("No proxy"), "noProxyDesc": MessageLookupByLibrary.simpleMessage( "Please create a profile or add a valid profile"), + "notSelectedTip": MessageLookupByLibrary.simpleMessage( + "The current proxy group cannot be selected."), "nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("Unable to obtain core info"), "nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"), @@ -124,6 +130,8 @@ class MessageLookup extends MessageLookupByLibrary { "Override Proxy related config"), "pleaseUploadFile": MessageLookupByLibrary.simpleMessage("Please upload file"), + "pleaseUploadValidQrcode": MessageLookupByLibrary.simpleMessage( + "Please upload a valid QR code"), "port": MessageLookupByLibrary.simpleMessage("Port"), "preview": MessageLookupByLibrary.simpleMessage("Preview"), "profile": MessageLookupByLibrary.simpleMessage("Profile"), @@ -169,9 +177,10 @@ class MessageLookup extends MessageLookupByLibrary { "themeDesc": MessageLookupByLibrary.simpleMessage( "Set dark mode,adjust the color"), "themeMode": MessageLookupByLibrary.simpleMessage("Theme mode"), + "tip": MessageLookupByLibrary.simpleMessage("tip"), "tools": MessageLookupByLibrary.simpleMessage("Tools"), "trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"), - "tun": MessageLookupByLibrary.simpleMessage("Tun"), + "tun": MessageLookupByLibrary.simpleMessage("Tun mode"), "tunDesc": MessageLookupByLibrary.simpleMessage( "only effective in administrator mode"), "unableToUpdateCurrentProfileDesc": diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index 551df26..faa25e6 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -49,6 +49,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("取消过滤系统应用"), "cancelSelectAll": MessageLookupByLibrary.simpleMessage("取消全选"), "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"), + "compatible": MessageLookupByLibrary.simpleMessage("兼容模式"), + "compatibleDesc": + MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"), "confirm": MessageLookupByLibrary.simpleMessage("确定"), "core": MessageLookupByLibrary.simpleMessage("内核"), "coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"), @@ -97,6 +100,7 @@ class MessageLookup extends MessageLookupByLibrary { "noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"), "noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"), + "notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"), "nullCoreInfoDesc": MessageLookupByLibrary.simpleMessage("无法获取内核信息"), "nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"), "nullProfileDesc": @@ -106,6 +110,8 @@ class MessageLookup extends MessageLookupByLibrary { "override": MessageLookupByLibrary.simpleMessage("覆写"), "overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"), "pleaseUploadFile": MessageLookupByLibrary.simpleMessage("请上传文件"), + "pleaseUploadValidQrcode": + MessageLookupByLibrary.simpleMessage("请上传有效的二维码"), "port": MessageLookupByLibrary.simpleMessage("端口"), "preview": MessageLookupByLibrary.simpleMessage("预览"), "profile": MessageLookupByLibrary.simpleMessage("配置"), @@ -146,9 +152,10 @@ class MessageLookup extends MessageLookupByLibrary { "themeColor": MessageLookupByLibrary.simpleMessage("主题色彩"), "themeDesc": MessageLookupByLibrary.simpleMessage("设置深色模式,调整色彩"), "themeMode": MessageLookupByLibrary.simpleMessage("主题模式"), + "tip": MessageLookupByLibrary.simpleMessage("提示"), "tools": MessageLookupByLibrary.simpleMessage("工具"), "trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"), - "tun": MessageLookupByLibrary.simpleMessage("虚拟网络设备"), + "tun": MessageLookupByLibrary.simpleMessage("Tun模式"), "tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"), "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage("无法更新当前配置文件"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index e90f971..1cd7c7d 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -410,10 +410,10 @@ class AppLocalizations { ); } - /// `Tun` + /// `Tun mode` String get tun { return Intl.message( - 'Tun', + 'Tun mode', name: 'tun', desc: '', args: [], @@ -980,6 +980,16 @@ class AppLocalizations { ); } + /// `Please upload a valid QR code` + String get pleaseUploadValidQrcode { + return Intl.message( + 'Please upload a valid QR code', + name: 'pleaseUploadValidQrcode', + desc: '', + args: [], + ); + } + /// `Blacklist mode` String get blacklistMode { return Intl.message( @@ -1259,6 +1269,46 @@ class AppLocalizations { args: [], ); } + + /// `Compatibility mode` + String get compatible { + return Intl.message( + 'Compatibility mode', + name: 'compatible', + desc: '', + args: [], + ); + } + + /// `Opening it will lose part of its application ability and gain the support of full amount of Clash.` + String get compatibleDesc { + return Intl.message( + 'Opening it will lose part of its application ability and gain the support of full amount of Clash.', + name: 'compatibleDesc', + desc: '', + args: [], + ); + } + + /// `The current proxy group cannot be selected.` + String get notSelectedTip { + return Intl.message( + 'The current proxy group cannot be selected.', + name: 'notSelectedTip', + desc: '', + args: [], + ); + } + + /// `tip` + String get tip { + return Intl.message( + 'tip', + name: 'tip', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/main.dart b/lib/main.dart index a11ae2d..3e55956 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,7 +62,7 @@ Future vpnService() async { ); final appLocalizations = await AppLocalizations.load( - Other.getLocaleForString(config.locale) ?? + other.getLocaleForString(config.locale) ?? WidgetsBinding.instance.platformDispatcher.locale, ); diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart index 849e6ba..dfb245a 100644 --- a/lib/models/clash_config.dart +++ b/lib/models/clash_config.dart @@ -1,37 +1,28 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/constant.dart'; import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import '../enum/enum.dart'; part 'generated/clash_config.g.dart'; -@JsonSerializable() -class Tun { - bool enable; - String device; - TunStack stack; - @JsonKey(name: "dns-hijack") - List dnsHijack; +part 'generated/clash_config.freezed.dart'; - Tun() : enable = false, - stack = TunStack.gvisor, - dnsHijack = ["any:53"], - device = appConstant.name; - factory Tun.fromJson(Map json) { - return _$TunFromJson(json); - } +@freezed +class Tun with _$Tun { + const factory Tun({ + @Default(false) bool enable, + @Default(appName) String device, + @Default(TunStack.gvisor) TunStack stack, + @JsonKey(name: "dns-hijack") @Default(["any:53"]) + List dnsHijack, + }) = _Tun; - Map toJson() { - return _$TunToJson(this); - } - - // Tun copyWith({bool? enable, int? fileDescriptor}) { - // return Tun( - // enable: enable ?? this.enable, - // ); - // } + factory Tun.fromJson(Map json) => _$TunFromJson(json); } @JsonSerializable() @@ -137,7 +128,7 @@ class ClashConfig extends ChangeNotifier { _mode = mode ?? Mode.rule, _allowLan = allowLan ?? false, _logLevel = logLevel ?? LogLevel.info, - _tun = tun ?? Tun(), + _tun = tun ?? const Tun(), _dns = dns ?? Dns(), _rules = rules ?? []; @@ -225,4 +216,4 @@ class ClashConfig extends ChangeNotifier { allowLan: allowLan, ); } -} +} \ No newline at end of file diff --git a/lib/models/config.dart b/lib/models/config.dart index 1cebc7c..6765033 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -112,7 +112,7 @@ class Config extends ChangeNotifier { (element) => element.label == label && element.id != id) != -1; if (hasDup) { - return _getLabel(Other.getOverwriteLabel(label!), id); + return _getLabel(other.getOverwriteLabel(label!), id); } else { return label; } diff --git a/lib/models/generated/clash_config.freezed.dart b/lib/models/generated/clash_config.freezed.dart new file mode 100644 index 0000000..7a4ec1a --- /dev/null +++ b/lib/models/generated/clash_config.freezed.dart @@ -0,0 +1,222 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of '../clash_config.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Tun _$TunFromJson(Map json) { + return _Tun.fromJson(json); +} + +/// @nodoc +mixin _$Tun { + bool get enable => throw _privateConstructorUsedError; + String get device => throw _privateConstructorUsedError; + TunStack get stack => throw _privateConstructorUsedError; + @JsonKey(name: "dns-hijack") + List get dnsHijack => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TunCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TunCopyWith<$Res> { + factory $TunCopyWith(Tun value, $Res Function(Tun) then) = + _$TunCopyWithImpl<$Res, Tun>; + @useResult + $Res call( + {bool enable, + String device, + TunStack stack, + @JsonKey(name: "dns-hijack") List dnsHijack}); +} + +/// @nodoc +class _$TunCopyWithImpl<$Res, $Val extends Tun> implements $TunCopyWith<$Res> { + _$TunCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? enable = null, + Object? device = null, + Object? stack = null, + Object? dnsHijack = null, + }) { + return _then(_value.copyWith( + enable: null == enable + ? _value.enable + : enable // ignore: cast_nullable_to_non_nullable + as bool, + device: null == device + ? _value.device + : device // ignore: cast_nullable_to_non_nullable + as String, + stack: null == stack + ? _value.stack + : stack // ignore: cast_nullable_to_non_nullable + as TunStack, + dnsHijack: null == dnsHijack + ? _value.dnsHijack + : dnsHijack // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TunImplCopyWith<$Res> implements $TunCopyWith<$Res> { + factory _$$TunImplCopyWith(_$TunImpl value, $Res Function(_$TunImpl) then) = + __$$TunImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool enable, + String device, + TunStack stack, + @JsonKey(name: "dns-hijack") List dnsHijack}); +} + +/// @nodoc +class __$$TunImplCopyWithImpl<$Res> extends _$TunCopyWithImpl<$Res, _$TunImpl> + implements _$$TunImplCopyWith<$Res> { + __$$TunImplCopyWithImpl(_$TunImpl _value, $Res Function(_$TunImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? enable = null, + Object? device = null, + Object? stack = null, + Object? dnsHijack = null, + }) { + return _then(_$TunImpl( + enable: null == enable + ? _value.enable + : enable // ignore: cast_nullable_to_non_nullable + as bool, + device: null == device + ? _value.device + : device // ignore: cast_nullable_to_non_nullable + as String, + stack: null == stack + ? _value.stack + : stack // ignore: cast_nullable_to_non_nullable + as TunStack, + dnsHijack: null == dnsHijack + ? _value._dnsHijack + : dnsHijack // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TunImpl implements _Tun { + const _$TunImpl( + {this.enable = false, + this.device = appName, + this.stack = TunStack.gvisor, + @JsonKey(name: "dns-hijack") + final List dnsHijack = const ["any:53"]}) + : _dnsHijack = dnsHijack; + + factory _$TunImpl.fromJson(Map json) => + _$$TunImplFromJson(json); + + @override + @JsonKey() + final bool enable; + @override + @JsonKey() + final String device; + @override + @JsonKey() + final TunStack stack; + final List _dnsHijack; + @override + @JsonKey(name: "dns-hijack") + List get dnsHijack { + if (_dnsHijack is EqualUnmodifiableListView) return _dnsHijack; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_dnsHijack); + } + + @override + String toString() { + return 'Tun(enable: $enable, device: $device, stack: $stack, dnsHijack: $dnsHijack)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TunImpl && + (identical(other.enable, enable) || other.enable == enable) && + (identical(other.device, device) || other.device == device) && + (identical(other.stack, stack) || other.stack == stack) && + const DeepCollectionEquality() + .equals(other._dnsHijack, _dnsHijack)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, enable, device, stack, + const DeepCollectionEquality().hash(_dnsHijack)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TunImplCopyWith<_$TunImpl> get copyWith => + __$$TunImplCopyWithImpl<_$TunImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TunImplToJson( + this, + ); + } +} + +abstract class _Tun implements Tun { + const factory _Tun( + {final bool enable, + final String device, + final TunStack stack, + @JsonKey(name: "dns-hijack") final List dnsHijack}) = _$TunImpl; + + factory _Tun.fromJson(Map json) = _$TunImpl.fromJson; + + @override + bool get enable; + @override + String get device; + @override + TunStack get stack; + @override + @JsonKey(name: "dns-hijack") + List get dnsHijack; + @override + @JsonKey(ignore: true) + _$$TunImplCopyWith<_$TunImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/generated/clash_config.g.dart b/lib/models/generated/clash_config.g.dart index 0e612c5..e3c9b8d 100644 --- a/lib/models/generated/clash_config.g.dart +++ b/lib/models/generated/clash_config.g.dart @@ -6,26 +6,6 @@ part of '../clash_config.dart'; // JsonSerializableGenerator // ************************************************************************** -Tun _$TunFromJson(Map json) => Tun() - ..enable = json['enable'] as bool - ..device = json['device'] as String - ..stack = $enumDecode(_$TunStackEnumMap, json['stack']) - ..dnsHijack = - (json['dns-hijack'] as List).map((e) => e as String).toList(); - -Map _$TunToJson(Tun instance) => { - 'enable': instance.enable, - 'device': instance.device, - 'stack': _$TunStackEnumMap[instance.stack]!, - 'dns-hijack': instance.dnsHijack, - }; - -const _$TunStackEnumMap = { - TunStack.gvisor: 'gvisor', - TunStack.system: 'system', - TunStack.mixed: 'mixed', -}; - Dns _$DnsFromJson(Map json) => Dns() ..enable = json['enable'] as bool ..ipv6 = json['ipv6'] as bool @@ -94,3 +74,27 @@ const _$LogLevelEnumMap = { LogLevel.error: 'error', LogLevel.silent: 'silent', }; + +_$TunImpl _$$TunImplFromJson(Map json) => _$TunImpl( + enable: json['enable'] as bool? ?? false, + device: json['device'] as String? ?? appName, + stack: $enumDecodeNullable(_$TunStackEnumMap, json['stack']) ?? + TunStack.gvisor, + dnsHijack: (json['dns-hijack'] as List?) + ?.map((e) => e as String) + .toList() ?? + const ["any:53"], + ); + +Map _$$TunImplToJson(_$TunImpl instance) => { + 'enable': instance.enable, + 'device': instance.device, + 'stack': _$TunStackEnumMap[instance.stack]!, + 'dns-hijack': instance.dnsHijack, + }; + +const _$TunStackEnumMap = { + TunStack.gvisor: 'gvisor', + TunStack.system: 'system', + TunStack.mixed: 'mixed', +}; diff --git a/lib/pages/home.dart b/lib/pages/home.dart index cd21c51..79c9045 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,4 +1,3 @@ -import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; @@ -28,7 +27,7 @@ class HomePage extends StatelessWidget { builder: (context, currentIndex, __) { if (globalState.pageController != null) { WidgetsBinding.instance.addPostFrameCallback((_) { - context.appController.toPage(currentIndex, hasAnimate: true); + globalState.appController.toPage(currentIndex, hasAnimate: true); }); } else { globalState.pageController = PageController( @@ -67,7 +66,7 @@ class HomePage extends StatelessWidget { }, builder: (context, state, __) { return AdaptiveScaffold.standardNavigationRail( - onDestinationSelected: context.appController.toPage, + onDestinationSelected: globalState.appController.toPage, destinations: navigationItems .map( (e) => NavigationRailDestination( @@ -113,7 +112,7 @@ class HomePage extends StatelessWidget { .toList(); return AdaptiveScaffold.standardBottomNavigationBar( destinations: mobileDestinations, - onDestinationSelected: context.appController.toPage, + onDestinationSelected: globalState.appController.toPage, currentIndex: state.currentIndex, ); }, diff --git a/lib/pages/scan.dart b/lib/pages/scan.dart index e48d252..5647028 100644 --- a/lib/pages/scan.dart +++ b/lib/pages/scan.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; @@ -90,6 +91,12 @@ class _ScanPageState extends State with WidgetsBindingObserver { }, icon: const Icon(Icons.close), ), + actions: [ + IconButton( + onPressed: globalState.appController.addProfileFormQrCode, + icon: const Icon(Icons.add_photo_alternate_outlined), + ) + ], ), Container( margin: const EdgeInsets.only(bottom: 32), @@ -115,7 +122,7 @@ class _ScanPageState extends State with WidgetsBindingObserver { icon: icon, style: ButtonStyle( foregroundColor: - const MaterialStatePropertyAll(Colors.white), + const MaterialStatePropertyAll(Colors.white), backgroundColor: MaterialStatePropertyAll(backgroundColor), ), padding: const EdgeInsets.all(16), @@ -197,4 +204,4 @@ class ScannerOverlay extends CustomPainter { return scanWindow != oldDelegate.scanWindow || borderRadius != oldDelegate.borderRadius; } -} \ No newline at end of file +} diff --git a/lib/state.dart b/lib/state.dart index b39560a..476b740 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -9,6 +9,7 @@ import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/widgets/scaffold.dart'; import 'package:flutter/material.dart'; +import 'controller.dart'; import 'models/models.dart'; import 'common/common.dart'; @@ -20,6 +21,7 @@ class GlobalState { PageController? pageController; final navigatorKey = GlobalKey(); final Map packageNameMap = {}; + late AppController appController; GlobalKey homeScaffoldKey = GlobalKey(); List updateFunctionLists = []; List currentNavigationItems = []; diff --git a/lib/widgets/android_container.dart b/lib/widgets/android_container.dart index 50fe1af..6e64f1a 100644 --- a/lib/widgets/android_container.dart +++ b/lib/widgets/android_container.dart @@ -1,4 +1,4 @@ -import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -27,7 +27,7 @@ class _AndroidContainerState extends State Future didChangeAppLifecycleState(AppLifecycleState state) async { final isPaused = state == AppLifecycleState.paused; if (isPaused) { - await context.appController.savePreferences(); + await globalState.appController.savePreferences(); } } diff --git a/lib/widgets/app_state_container.dart b/lib/widgets/app_state_container.dart index a955302..214d6f3 100644 --- a/lib/widgets/app_state_container.dart +++ b/lib/widgets/app_state_container.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -35,7 +36,7 @@ class AppStateContainer extends StatelessWidget { builder: (context, state, child) { WidgetsBinding.instance.addPostFrameCallback( (_) { - context.appController.appState.navigationItems = + globalState.appController.appState.navigationItems = navigation.getItems( openLogs: state.openLogs, hasProxies: state.hasProxies, diff --git a/lib/widgets/clash_message_container.dart b/lib/widgets/clash_message_container.dart index ac4d5c2..c8ee8d5 100644 --- a/lib/widgets/clash_message_container.dart +++ b/lib/widgets/clash_message_container.dart @@ -38,14 +38,14 @@ class _ClashMessageContainerState extends State @override void onDelay(Delay delay) { - final appController = context.appController; + final appController = globalState.appController; appController.setDelay(delay); super.onDelay(delay); } @override void onLog(Log log) { - context.appController.appState.addLog(log); + globalState.appController.appState.addLog(log); super.onLog(log); } diff --git a/lib/widgets/color_scheme_box.dart b/lib/widgets/color_scheme_box.dart index 4ee8856..f0ef08b 100644 --- a/lib/widgets/color_scheme_box.dart +++ b/lib/widgets/color_scheme_box.dart @@ -1,4 +1,4 @@ -import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'card.dart'; import 'grid.dart'; @@ -25,7 +25,7 @@ class ColorSchemeBox extends StatelessWidget { ); } else { return Theme.of(context).copyWith( - colorScheme: context.appController.appState.systemColorSchemes + colorScheme: globalState.appController.appState.systemColorSchemes .getSystemColorSchemeForBrightness(Theme.of(context).brightness), ); } diff --git a/lib/widgets/extend_page.dart b/lib/widgets/extend_page.dart index 5e4d7a2..d940010 100644 --- a/lib/widgets/extend_page.dart +++ b/lib/widgets/extend_page.dart @@ -16,28 +16,6 @@ showExtendPage( key: globalKey, child: body, ); - - // Flexible( - // flex: 0, - // child: Row( - // children: [ - // Expanded( - // child: Padding( - // padding: kTabLabelPadding, - // child: Text( - // title, - // style: Theme.of(context).textTheme.titleMedium, - // ), - // ), - // ), - // const SizedBox( - // height: kToolbarHeight, - // width: kToolbarHeight, - // child: CloseButton(), - // ), - // ], - // ), - // ) navigator.push( ModalSideSheetRoute( modalBarrierColor: Colors.black38, diff --git a/lib/widgets/pop_container.dart b/lib/widgets/pop_container.dart index 6334a35..7b4f3a4 100644 --- a/lib/widgets/pop_container.dart +++ b/lib/widgets/pop_container.dart @@ -1,7 +1,7 @@ import 'dart:io'; -import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/widgets.dart'; class PopContainer extends StatefulWidget { @@ -24,7 +24,7 @@ class _PopContainerState extends State { if (canPop) { Navigator.pop(context); } else { - await context.appController.handleBackOrExit(); + await globalState.appController.handleBackOrExit(); } }, child: widget.child, diff --git a/lib/widgets/scaffold.dart b/lib/widgets/scaffold.dart index 333c6e8..a14c093 100644 --- a/lib/widgets/scaffold.dart +++ b/lib/widgets/scaffold.dart @@ -1,3 +1,4 @@ +import 'package:fl_clash/common/system.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -69,8 +70,10 @@ class CommonScaffoldState extends State { } } - @override - Widget build(BuildContext context) { + _platformContainer({required Widget child}) { + if (system.isDesktop) { + return child; + } return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, @@ -80,6 +83,13 @@ class CommonScaffoldState extends State { systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, ), + child: child, + ); + } + + @override + Widget build(BuildContext context) { + return _platformContainer( child: Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), @@ -114,3 +124,23 @@ class CommonScaffoldState extends State { ); } } + +class AppIcon extends StatelessWidget { + const AppIcon({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + width: 30, + height: 30, + child: const CircleAvatar( + foregroundImage: AssetImage("assets/images/launch_icon.png"), + backgroundColor: Colors.transparent, + ), + ); + } +} diff --git a/lib/widgets/text.dart b/lib/widgets/text.dart index c68a739..72db3be 100644 --- a/lib/widgets/text.dart +++ b/lib/widgets/text.dart @@ -1,6 +1,7 @@ -import 'package:fl_clash/common/common.dart'; import 'package:flutter/material.dart'; +import '../state.dart'; + class TooltipText extends StatelessWidget { final Text text; @@ -14,7 +15,7 @@ class TooltipText extends StatelessWidget { return LayoutBuilder( builder: (context, container) { final maxWidth = container.maxWidth; - final size = context.appController.measure.computeTextSize( + final size = globalState.appController.measure.computeTextSize( text, ); if (maxWidth < size.width) { diff --git a/lib/widgets/tile_container.dart b/lib/widgets/tile_container.dart index 0e9a4a0..7494e3a 100644 --- a/lib/widgets/tile_container.dart +++ b/lib/widgets/tile_container.dart @@ -1,5 +1,5 @@ -import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/plugins/tile.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; class TileContainer extends StatefulWidget { @@ -24,13 +24,13 @@ class _TileContainerState extends State with TileListener { @override void onStart() { - context.appController.updateSystemProxy(true); + globalState.appController.updateSystemProxy(true); super.onStart(); } @override void onStop() { - context.appController.updateSystemProxy(false); + globalState.appController.updateSystemProxy(false); super.onStop(); } diff --git a/lib/widgets/tray_container.dart b/lib/widgets/tray_container.dart index 12c37f1..53e05fd 100644 --- a/lib/widgets/tray_container.dart +++ b/lib/widgets/tray_container.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -32,7 +33,7 @@ class _TrayContainerState extends State with TrayListener { _updateOtherTray() async { if (isTrayInit == false) { await trayManager.setIcon( - Other.getTrayIconPath(), + other.getTrayIconPath(), ); isTrayInit = true; } @@ -41,7 +42,7 @@ class _TrayContainerState extends State with TrayListener { _updateLinuxTray() async { await trayManager.destroy(); await trayManager.setIcon( - Other.getTrayIconPath(), + other.getTrayIconPath(), ); } @@ -68,11 +69,11 @@ class _TrayContainerState extends State with TrayListener { // label: proxy.name, // checked: isCurrentGroup && isCurrentProxy, // onClick: (_) { - // final config = context.appController.config; + // final config = globalState.appController.config; // config.currentProfile?.groupName = group.name; // config.currentProfile?.proxyName = proxy.name; // config.update(); - // context.appController.changeProxy(); + // globalState.appController.changeProxy(); // }), // ); // } @@ -93,7 +94,7 @@ class _TrayContainerState extends State with TrayListener { MenuItem.checkbox( label: Intl.message(mode.name), onClick: (_) { - context.appController.clashConfig.mode = mode; + globalState.appController.clashConfig.mode = mode; }, checked: mode == state.mode, ), @@ -103,7 +104,7 @@ class _TrayContainerState extends State with TrayListener { final proxyMenuItem = MenuItem.checkbox( label: appLocalizations.systemProxy, onClick: (_) async { - context.appController.updateSystemProxy(!state.isRun); + globalState.appController.updateSystemProxy(!state.isRun); }, checked: state.isRun, ); @@ -111,8 +112,8 @@ class _TrayContainerState extends State with TrayListener { final autoStartMenuItem = MenuItem.checkbox( label: appLocalizations.autoLaunch, onClick: (_) async { - context.appController.config.autoLaunch = - !context.appController.config.autoLaunch; + globalState.appController.config.autoLaunch = + !globalState.appController.config.autoLaunch; }, checked: state.autoLaunch, ); @@ -121,7 +122,7 @@ class _TrayContainerState extends State with TrayListener { final exitMenuItem = MenuItem( label: appLocalizations.exit, onClick: (_) async { - await context.appController.handleExit(); + await globalState.appController.handleExit(); }, ); menuItems.add(exitMenuItem); diff --git a/lib/widgets/window_container.dart b/lib/widgets/window_container.dart index 3d7ee51..869c144 100644 --- a/lib/widgets/window_container.dart +++ b/lib/widgets/window_container.dart @@ -1,4 +1,4 @@ -import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; @@ -29,13 +29,13 @@ class _WindowContainerState extends State @override void onWindowClose() async { - await context.appController.handleBackOrExit(); + await globalState.appController.handleBackOrExit(); super.onWindowClose(); } @override void onWindowMinimize() async { - await context.appController.savePreferences(); + await globalState.appController.savePreferences(); super.onWindowMinimize(); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 9474aa0..a47e9c9 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 38732ad..794fd2e 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + file_selector_linux gtk screen_retriever tray_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5048eea..8745e66 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import app_links import dynamic_color +import file_selector_macos import mobile_scanner import package_info_plus import path_provider_foundation @@ -19,6 +20,7 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 1056bc0..9a783ca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.5.1" + archive: + dependency: transitive + description: + name: archive + sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.5.1" args: dependency: "direct dev" description: @@ -129,6 +137,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -177,6 +193,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -241,6 +265,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.2.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+1" fixnum: dependency: transitive description: @@ -279,10 +335,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: "592dc01a18961a51c24ae5d963b724b2b7fa4a95c100fe8eb6ca8a5a4732cadf" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.19" + version: "2.0.18" flutter_test: dependency: "direct dev" description: flutter @@ -365,6 +421,78 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.2" + image: + dependency: "direct main" + description: + name: image + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.7" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "79455f6cff4cbef583b2b524bbf0d4ec424e5959f4d464e36ef5323715b98370" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.12" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "6a1704fdd75022272e7e7a897a9068e9c2ff3cd6a66820bf3ded810633eac954" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: cb0db0ec0d3e2cd49674f2e6053be25ccdb959832607c1cbd215dd6cf10fb0dd + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.11" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -589,6 +717,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -672,10 +808,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.2" + version: "2.2.1" shared_preferences_foundation: dependency: transitive description: @@ -985,6 +1121,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: @@ -1001,6 +1145,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" + zxing2: + dependency: "direct main" + description: + name: zxing2 + sha256: "6cf995abd3c86f01ba882968dedffa7bc130185e382f2300239d2e857fc7912c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.3" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index c8c2609..bb8a498 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fl_clash description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. publish_to: 'none' -version: 0.8.0 +version: 0.8.1 environment: sdk: '>=3.1.0 <4.0.0' @@ -35,6 +35,9 @@ dependencies: url_launcher: ^6.2.6 flutter_adaptive_scaffold: ^0.1.10+1 freezed_annotation: ^2.4.1 + image_picker: ^1.1.1 + zxing2: ^0.2.3 + image: ^4.1.7 dev_dependencies: flutter_test: sdk: flutter diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index aa445ae..115b973 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AppLinksPluginCApi")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); ProxyPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProxyPluginCApi")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index daf5348..0877746 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links dynamic_color + file_selector_windows proxy screen_retriever tray_manager