diff --git a/lib/ui/component/multi_window.dart b/lib/ui/component/multi_window.dart index d008504..462b6c7 100644 --- a/lib/ui/component/multi_window.dart +++ b/lib/ui/component/multi_window.dart @@ -18,6 +18,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:path_provider/path_provider.dart'; @@ -89,7 +90,7 @@ Widget multiWindow(int windowId, Map argument) { } if (argument['name'] == 'JavaScript') { - return const JavaScript(); + return JavaScript(windowId: windowId); } if (argument['name'] == 'RegExpPage') { @@ -215,6 +216,20 @@ void registerMethodHandler() { return 'done'; } + if (call.method == 'pickFiles') { + var extensions = call.arguments['allowedExtensions']; + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: extensions == null ? FileType.any : FileType.custom, + allowedExtensions: extensions == null ? null : List.from(extensions), + initialDirectory: "/Downloads"); + if (result == null || result.files.isEmpty) return null; + return result.files.single.path; + } + + if (call.method == 'saveFile') { + return await FilePicker.platform.saveFile(fileName: call.arguments['fileName']); + } + if (call.method == 'getApplicationSupportDirectory') { return getApplicationSupportDirectory().then((it) => it.path); } diff --git a/lib/ui/component/toolbox/js_run.dart b/lib/ui/component/toolbox/js_run.dart index 1a280f2..cc3124d 100644 --- a/lib/ui/component/toolbox/js_run.dart +++ b/lib/ui/component/toolbox/js_run.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -14,7 +15,9 @@ import 'package:proxypin/network/components/js/md5.dart'; import 'package:proxypin/network/components/js/xhr.dart'; class JavaScript extends StatefulWidget { - const JavaScript({super.key}); + final int? windowId; + + const JavaScript({super.key, this.windowId}); @override State createState() { @@ -88,10 +91,20 @@ class _JavaScriptState extends State { //选择文件 ElevatedButton.icon( onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['js']); - if (result != null) { - File file = File(result.files.single.path!); + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "pickFiles", { + "allowedExtensions": ['js'] + }); + WindowController.fromWindowId(widget.windowId!).show(); + } else { + FilePickerResult? result = + await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['js']); + path = result?.files.single.path; + } + + if (path != null) { + File file = File(path); String content = await file.readAsString(); code.text = content; setState(() {}); diff --git a/lib/ui/component/toolbox/qr_code_page.dart b/lib/ui/component/toolbox/qr_code_page.dart index 4f5590a..5d5fb1c 100644 --- a/lib/ui/component/toolbox/qr_code_page.dart +++ b/lib/ui/component/toolbox/qr_code_page.dart @@ -349,17 +349,22 @@ class _QrEncodeState extends State<_QrEncode> with AutomaticKeepAliveClientMixin return; } - if (Platforms.isDesktop()) { - String? path = (await FilePicker.platform.saveFile(fileName: "qrcode.png")); - if (path == null) return; + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "saveFile", {"fileName": "qrcode.png"}); + WindowController.fromWindowId(widget.windowId!).show(); + } else { + path = (await FilePicker.platform.saveFile(fileName: "qrcode.png")); + } - var imageBytes = await toImageBytes(); - if (imageBytes == null) return; + if (path == null) return; - await File(path).writeAsBytes(imageBytes); - if (mounted) { - FlutterToastr.show(localizations.saveSuccess, context, duration: 2); - } + var imageBytes = await toImageBytes(); + if (imageBytes == null) return; + + await File(path).writeAsBytes(imageBytes); + if (mounted) { + FlutterToastr.show(localizations.saveSuccess, context, duration: 2); } } diff --git a/lib/ui/desktop/toolbar/setting/filter.dart b/lib/ui/desktop/toolbar/setting/filter.dart index 0fa6b27..562fde3 100644 --- a/lib/ui/desktop/toolbar/setting/filter.dart +++ b/lib/ui/desktop/toolbar/setting/filter.dart @@ -161,8 +161,9 @@ class _DomainFilterState extends State { //导入 import() async { + final FilePickerResult? result = - await FilePicker.platform.pickFiles(allowedExtensions: ['config'], type: FileType.custom); + await FilePicker.platform.pickFiles(allowedExtensions: ['config'], type: FileType.custom, initialDirectory: "/Downloads"); var file = result?.files.single; if (file == null) { return; diff --git a/lib/ui/desktop/toolbar/setting/hosts.dart b/lib/ui/desktop/toolbar/setting/hosts.dart index d030022..f10cdd7 100644 --- a/lib/ui/desktop/toolbar/setting/hosts.dart +++ b/lib/ui/desktop/toolbar/setting/hosts.dart @@ -340,8 +340,8 @@ class _HostsDialogState extends State { //导入 import() async { - final FilePickerResult? result = - await FilePicker.platform.pickFiles(allowedExtensions: ['json'], type: FileType.custom); + final FilePickerResult? result = await FilePicker.platform + .pickFiles(allowedExtensions: ['json'], type: FileType.custom, initialDirectory: "/Downloads"); var file = result?.files.single; if (file == null) { return; diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index 4971f7f..ebee3ea 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -153,16 +153,24 @@ class RequestRewriteState extends State { //导入js import() async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['config', 'json']); - if (result == null || result.files.isEmpty) { + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "pickFiles", { + "allowedExtensions": ['config', 'json'] + }); + WindowController.fromWindowId(widget.windowId).show(); + } else { + FilePickerResult? result = + await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['config', 'json']); + path = result?.files.single.path; + } + + if (path == null) { return; } - var file = result.files.single; - try { - List json = jsonDecode(await File(file.path!).readAsString()); + List json = jsonDecode(await File(path).readAsString()); for (var item in json) { var rule = RequestRewriteRule.formJson(item); var items = (item['items'] as List).map((e) => RewriteItem.fromJson(e)).toList(); @@ -176,7 +184,7 @@ class RequestRewriteState extends State { } setState(() {}); } catch (e, t) { - logger.e('导入失败 $file', error: e, stackTrace: t); + logger.e('导入失败 $path', error: e, stackTrace: t); if (mounted) { FlutterToastr.show("${localizations.importFailed} $e", context); } @@ -362,7 +370,15 @@ class _RequestRuleListState extends State { if (indexes.isEmpty) return; String fileName = 'proxypin-rewrites.config'; - var path = await FilePicker.platform.saveFile(fileName: fileName); + + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "saveFile", {"fileName": fileName}); + WindowController.fromWindowId(widget.windowId).show(); + } else { + path = await FilePicker.platform.saveFile(fileName: fileName); + } + if (path == null) { return; } @@ -644,7 +660,7 @@ class _RewriteRuleEditState extends State { return DesktopRewriteUpdate(key: rewriteUpdateKey, items: items, ruleType: ruleType, request: widget.request); } - return DesktopRewriteReplace(key: rewriteReplaceKey, items: items, ruleType: ruleType); + return DesktopRewriteReplace(key: rewriteReplaceKey, items: items, ruleType: ruleType, windowId: widget.windowId); } Widget textField(String label, TextEditingController controller, String hint, diff --git a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart index faae18b..80cd515 100644 --- a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart +++ b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart @@ -14,6 +14,9 @@ * limitations under the License. */ +import 'dart:io'; + +import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -27,10 +30,11 @@ import 'package:proxypin/utils/lang.dart'; /// @author wanghongen /// 2023/10/8 class DesktopRewriteReplace extends StatefulWidget { + final int? windowId; final RuleType ruleType; final List? items; - const DesktopRewriteReplace({super.key, this.items, required this.ruleType}); + const DesktopRewriteReplace({super.key, this.items, required this.ruleType, this.windowId}); @override State createState() => RewriteReplaceState(); @@ -238,11 +242,15 @@ class RewriteReplaceState extends State { const SizedBox(width: 10), FilledButton( onPressed: () async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); - if (result == null || result.files.isEmpty) { - return; + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "pickFiles"); + if (widget.windowId != null) WindowController.fromWindowId(widget.windowId!).show(); + } else { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + path = result?.files.single.path; } - var path = result.files.first.path; + if (path == null) { return; } diff --git a/lib/ui/desktop/toolbar/setting/script.dart b/lib/ui/desktop/toolbar/setting/script.dart index 06b4a47..23d84ce 100644 --- a/lib/ui/desktop/toolbar/setting/script.dart +++ b/lib/ui/desktop/toolbar/setting/script.dart @@ -156,13 +156,23 @@ class _ScriptWidgetState extends State { //导入js import() async { - FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']); - if (result == null || result.files.isEmpty) { + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "pickFiles", { + "allowedExtensions": ['json'] + }); + WindowController.fromWindowId(widget.windowId).show(); + } else { + FilePickerResult? result = + await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']); + path = result?.files.single.path; + } + + if (path == null) { return; } - var file = result.files.single.path; try { - var json = jsonDecode(await File(file!).readAsString()); + var json = jsonDecode(await File(path).readAsString()); var scriptManager = (await ScriptManager.instance); if (json is List) { for (var item in json) { @@ -180,7 +190,7 @@ class _ScriptWidgetState extends State { } setState(() {}); } catch (e, t) { - logger.e('导入失败 $file', error: e, stackTrace: t); + logger.e('导入失败 $path', error: e, stackTrace: t); if (mounted) { FlutterToastr.show("${localizations.importFailed} $e", context); } @@ -626,7 +636,13 @@ class _ScriptListState extends State { if (indexes.isEmpty) return; //文件名称 String fileName = 'proxypin-scripts.json'; - String? path = await FilePicker.platform.saveFile(fileName: fileName); + String? path; + if (Platform.isMacOS) { + path = await DesktopMultiWindow.invokeMethod(0, "saveFile", {"fileName": fileName}); + WindowController.fromWindowId(widget.windowId).show(); + } else { + path = await FilePicker.platform.saveFile(fileName: fileName); + } if (path == null) { return; } diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 861fd2f..758fb59 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 5dc2fdd..8a88ce1 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -21,7 +21,10 @@ class AppDelegate: FlutterAppDelegate { return true } - + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } + override func applicationWillTerminate(_ notification: Notification) { AppLifecycleChannel.appDetached() NSLog("applicationWillTerminate") diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 95c1713..080e1e0 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -2,6 +2,7 @@ - + com.apple.security.files.user-selected.read-write + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 95c1713..080e1e0 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -2,6 +2,7 @@ - + com.apple.security.files.user-selected.read-write + diff --git a/macos/Runner/RunnerProfile.entitlements b/macos/Runner/RunnerProfile.entitlements index 9d11681..4e5d8cf 100644 --- a/macos/Runner/RunnerProfile.entitlements +++ b/macos/Runner/RunnerProfile.entitlements @@ -6,8 +6,6 @@ com.apple.security.cs.allow-jit - com.apple.security.files.user-selected.read-write - com.apple.security.network.client com.apple.security.network.server @@ -15,6 +13,6 @@ com.apple.security.scripting-targets com.apple.security.files.user-selected.read-write - + diff --git a/pubspec.yaml b/pubspec.yaml index 8388130..923308b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: url: https://gitee.com/wanghongenpin/flutter-plugins.git path: packages/desktop_multi_window path_provider: ^2.1.5 - file_picker: ^9.2.3 + file_picker: ^10.1.2 proxy_manager: ^0.0.3 permission_handler: ^11.3.1 flutter_toastr: ^1.0.3