From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id D68F11FF185 for ; Mon, 21 Jul 2025 15:46:15 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8D1A210595; Mon, 21 Jul 2025 15:47:24 +0200 (CEST) From: Shan Shaji To: pve-devel@lists.proxmox.com Date: Mon, 21 Jul 2025 15:46:43 +0200 Message-Id: <20250721134643.62745-1-s.shaji@proxmox.com> X-Mailer: git-send-email 2.39.5 (Apple Git-154) MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1753105603672 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.230 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH pve_flutter_frontend v2] feat: ios: enable opening of virt-viewer (.vv) file with spice app X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" 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 --- 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