The feature to open the spice connection file was disabled in iOS and was only available in Android. To support iOS, a new native channel implementation for iOS has been added.
For the iOS implementation, use the `UIActivityViewController` [0] to show the share sheet with the suggested apps that can open the file. Alternatively, users can also save the file to the device storage. The `getExternalChacheDirectories` function has been replaced with `getTemporaryDirectory` as the external cache directories function is not supported [1] in iOS. [0] - https://developer.apple.com/documentation/uikit/uiactivityviewcontroller [1] - https://developer.apple.com/documentation/bundleresources/information-property-list/utimportedtypedeclarations References: - https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures#Trailing-Closures - https://medium.com/@dinesh.kachhot/different-ways-to-share-data-between-apps-de75a0a46d4a - https://docs.flutter.dev/platform-integration/platform-channels#step-4-add-an-ios-platform-specific-implementation - https://stackoverflow.com/questions/25644054/uiactivityviewcontroller-crashing-on-ios-8-ipads Signed-off-by: Shan Shaji <s.sh...@proxmox.com> --- changes since v1: - Fixed commit message Tested: - The above changes are tested on iPad simulator and a real iPhone in debug mode. ios/Runner/AppDelegate.swift | 64 +++++++++++++++++++++--- lib/widgets/pve_console_menu_widget.dart | 10 ++-- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..115e1fd 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,11 +3,61 @@ import UIKit @main @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + let channel: FlutterMethodChannel = FlutterMethodChannel( + name: "com.proxmox.app.pve_flutter_frontend/filesharing", + binaryMessenger: controller.binaryMessenger) + + channel.setMethodCallHandler({ + [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + + guard call.method == "shareFile" else { + result(FlutterMethodNotImplemented) + return + } + + let arguments = call.arguments as? [String: Any] + let path = arguments?["path"] as? String + let type = arguments?["type"] as? String + + if let filePath = path, let _ = type { + self?.shareFile(atPath: filePath, from: controller, result: result) + } else { + result(FlutterError(code: "FileNotFoundException", message: "File not found", details: nil)) + } + }) + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + private func shareFile(atPath path: String, from controller: UIViewController, result: @escaping FlutterResult) { + let fileURL = URL(fileURLWithPath: path) + let activityVC = UIActivityViewController( + activityItems: [fileURL], + applicationActivities: nil, + ) + + // To avoid crashing in iPad + if let popover = activityVC.popoverPresentationController { + popover.sourceView = controller.view + popover.sourceRect = CGRect( + x: controller.view.bounds.midX, + y: controller.view.bounds.midY, + width: 0, + height: 0, + ) + } + + + controller.present(activityVC, animated: true) { + result(nil) + } + } + + } diff --git a/lib/widgets/pve_console_menu_widget.dart b/lib/widgets/pve_console_menu_widget.dart index cd8c314..8fa5538 100644 --- a/lib/widgets/pve_console_menu_widget.dart +++ b/lib/widgets/pve_console_menu_widget.dart @@ -38,7 +38,7 @@ class PveConsoleMenu extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (Platform.isAndroid && (allowSpice ?? true)) + if ((Platform.isAndroid || Platform.isIOS) && (allowSpice ?? true)) ListTile( title: const Text( "SPICE", @@ -47,8 +47,7 @@ class PveConsoleMenu extends StatelessWidget { subtitle: const Text("Open SPICE connection file with external App"), onTap: () async { - if (Platform.isAndroid) { - final tempDir = await getExternalCacheDirectories(); + final tempDir = await getTemporaryDirectory(); String apiPath; if (['qemu', 'lxc'].contains(type)) { @@ -67,7 +66,7 @@ class PveConsoleMenu extends StatelessWidget { } return; } - var filePath = await writeSpiceFile(data, tempDir![0].path); + var filePath = await writeSpiceFile(data, tempDir.path); try { await platform.invokeMethod('shareFile', { @@ -98,9 +97,6 @@ class PveConsoleMenu extends StatelessWidget { } } } - } else { - print('not implemented for current platform'); - } }, ), if (Platform.isAndroid) // web_view is only available for mobile :( -- 2.39.5 (Apple Git-154) _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel