From 7bf8cf569f7bc9c38898eb90c4a466c2827b8176 Mon Sep 17 00:00:00 2001 From: boa-z Date: Sat, 9 Nov 2024 11:04:34 +0800 Subject: [PATCH] experimental support for set new storage path on iOS --- ios/Runner.xcodeproj/project.pbxproj | 5 +++- ios/Runner/AppDelegate.swift | 7 ++++++ ios/Runner/DirectoryPicker.swift | 36 ++++++++++++++++++++++++++++ ios/Runner/Info.plist | 6 ++++- lib/pages/settings/app.dart | 9 +++++-- lib/utils/io.dart | 21 ++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 ios/Runner/DirectoryPicker.swift diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 28ca16d..1126638 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C0086D072CDEFE6E004596D9 /* DirectoryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0086D062CDEFE64004596D9 /* DirectoryPicker.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -59,6 +60,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0086D062CDEFE64004596D9 /* DirectoryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryPicker.swift; sourceTree = ""; }; C22B8A9F3177D4A68EB8F66B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -133,6 +135,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + C0086D062CDEFE64004596D9 /* DirectoryPicker.swift */, ); path = Runner; sourceTree = ""; @@ -144,7 +147,6 @@ 730F73FE38E23FCF3E461640 /* Pods-Runner.release.xcconfig */, 29B89F848F26E839605E1D88 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -336,6 +338,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C0086D072CDEFE6E004596D9 /* DirectoryPicker.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index ee6fbe4..407320b 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,12 +1,16 @@ import Flutter import UIKit import UniformTypeIdentifiers +import Foundation // 添加此行 @main @objc class AppDelegate: FlutterAppDelegate, UIDocumentPickerDelegate { var flutterResult: FlutterResult? var directoryPath: URL! + // 定义插件通道名称 + private var directoryPicker: DirectoryPicker? + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? @@ -42,6 +46,9 @@ import UniformTypeIdentifiers self.directoryPath?.stopAccessingSecurityScopedResource() self.directoryPath = nil result(nil) + } else if call.method == "selectDirectory" { + self.directoryPicker = DirectoryPicker() + self.directoryPicker?.selectDirectory(result: result) } else { result(FlutterMethodNotImplemented) } diff --git a/ios/Runner/DirectoryPicker.swift b/ios/Runner/DirectoryPicker.swift new file mode 100644 index 0000000..c3d44a3 --- /dev/null +++ b/ios/Runner/DirectoryPicker.swift @@ -0,0 +1,36 @@ +import UIKit +import Flutter + +class DirectoryPicker: NSObject, UIDocumentPickerDelegate { + private var result: FlutterResult? + + // 初始化选择目录方法 + func selectDirectory(result: @escaping FlutterResult) { + self.result = result + + // 配置 UIDocumentPicker 为目录选择模式 + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) + documentPicker.delegate = self + documentPicker.allowsMultipleSelection = false + + // 获取根视图控制器并显示选择器 + if let rootViewController = UIApplication.shared.keyWindow?.rootViewController { + rootViewController.present(documentPicker, animated: true, completion: nil) + } + } + + // 处理选择完成后的结果 + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + // 获取选中的路径 + if let url = urls.first { + result?(url.path) + } else { + result?(nil) + } + } + + // 处理取消选择情况 + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + result?(nil) + } +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 02592e6..93a0af2 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -46,6 +46,10 @@ UIApplicationSupportsIndirectInputEvents NSPhotoLibraryUsageDescription - Choose images + Choose images + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + diff --git a/lib/pages/settings/app.dart b/lib/pages/settings/app.dart index 81e4718..1e0a1e4 100644 --- a/lib/pages/settings/app.dart +++ b/lib/pages/settings/app.dart @@ -32,11 +32,16 @@ class _AppSettingsState extends State { title: "Set New Storage Path".tl, actionTitle: "Set".tl, callback: () async { - if (App.isMobile) { + var result; + if (App.isAndroid) { context.showMessage(message: "Not supported".tl); return; } - var result = await selectDirectory(); + else if (App.isIOS) { + result = await selectDirectoryIOS(); + } else { + result = await selectDirectory(); + } if (result == null) return; var loadingDialog = showLoadingDialog( App.rootContext, diff --git a/lib/utils/io.dart b/lib/utils/io.dart index a76353d..c714c69 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -160,6 +160,22 @@ class DirectoryPicker { } } +class IOSDirectoryPicker { + static const MethodChannel _channel = MethodChannel("venera/method_channel"); + + // 调用 iOS 目录选择方法 + static Future selectDirectory() async { + try { + final String? path = await _channel.invokeMethod('selectDirectory'); + return path; + } catch (e) { + print("Error selecting directory: $e"); + // 返回报错信息 + return e.toString(); + } + } +} + Future selectFile({required List ext}) async { file_selector.XTypeGroup typeGroup = file_selector.XTypeGroup( label: 'files', @@ -181,6 +197,11 @@ Future selectDirectory() async { return path; } +// selectDirectoryIOS +Future selectDirectoryIOS() async { + return IOSDirectoryPicker.selectDirectory(); +} + Future saveFile( {Uint8List? data, required String filename, File? file}) async { if (data == null && file == null) {