From e4eeffbaee5410905bedfd315c74eaeb0316f234 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Fri, 27 Sep 2024 09:44:43 +0200 Subject: [PATCH] ios: fix exporting files (CSV exports, log export, notes export) Similar to Android, we set `ExportsDir()` to `""` and let the backend environment `getSaveFilename()` construct the full path. Mobile handling of the dirs/files is a big hack and works on implicit assumptions, e.g. that `getSaveFilename()` is always called with `filepath.Join(exportsDir, filename)`, which is only the filename on mobile as `ExportsDir()` returns `""`. This should be cleaned up, but for now iOS is made to behave like Android in this regard. We use a temp directory for these files, so the files will be cleaned up automatically after a while. --- .../BitBoxApp/BitBoxApp/BitBoxAppApp.swift | 60 +++++++++++++++---- util/config/appdir.go | 4 +- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift b/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift index d488825dcc..679ba9375d 100644 --- a/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift +++ b/frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift @@ -38,10 +38,11 @@ protocol SetMessageHandlersProtocol { func setMessageHandlers(handlers: MessageHandlersProtocol) } -class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol { - func getSaveFilename(_ p0: String?) -> String { - // TODO - return "" +class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol, UIDocumentInteractionControllerDelegate { + func getSaveFilename(_ fileName: String?) -> String { + let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + let fileURL = tempDirectory.appendingPathComponent(fileName!) + return fileURL.path } func auth() { @@ -75,10 +76,47 @@ class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol { func setDarkTheme(_ p0: Bool) { } - func systemOpen(_ url: String?) throws { - guard let url = URL(string: url!) else { return } - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) + // Helper method to get the root view controller + private func getRootViewController() -> UIViewController? { + guard let scene = UIApplication.shared.connectedScenes + .filter({ $0.activationState == .foregroundActive }) + .first as? UIWindowScene else { + return nil + } + + return scene.windows.first(where: { $0.isKeyWindow })?.rootViewController + } + + func systemOpen(_ urlString: String?) throws { + guard let urlString = urlString else { return } + // Check if it's a local file path (not a URL) + var url: URL + if urlString.hasPrefix("/") { + // This is a local file path, construct a file URL + url = URL(fileURLWithPath: urlString) + } else if let potentialURL = URL(string: urlString), potentialURL.scheme != nil { + // This is already a valid URL with a scheme + url = potentialURL + } else { + // Invalid URL or path + return + } + // Ensure all UIKit actions run on the main thread + DispatchQueue.main.async { + if url.isFileURL { + // Local file path, use UIDocumentInteractionController + if let rootViewController = self.getRootViewController() { + //let documentController = UIDocumentInteractionController(url: url) + //documentController.delegate = self // Ensure GoEnvironment conforms to UIDocumentInteractionControllerDelegate + //documentController.presentOptionsMenu(from: rootViewController.view.frame, in: rootViewController.view, animated: true) + let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) + rootViewController.present(activityViewController, animated: true, completion: nil) + } + } else { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } } } @@ -91,7 +129,7 @@ class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol { class GoAPI: NSObject, MobileserverGoAPIInterfaceProtocol, SetMessageHandlersProtocol { var handlers: MessageHandlersProtocol? - + func pushNotify(_ msg: String?) { self.handlers?.pushNotificationHandler(msg: msg!) } @@ -99,7 +137,7 @@ class GoAPI: NSObject, MobileserverGoAPIInterfaceProtocol, SetMessageHandlersPro func respond(_ queryID: Int, response: String?) { self.handlers?.callResponseHandler(queryID: queryID, response: response!) } - + func setMessageHandlers(handlers: MessageHandlersProtocol) { self.handlers = handlers } @@ -123,7 +161,7 @@ struct BitBoxAppApp: App { } } } - + func setupGoAPI(goAPI: MobileserverGoAPIInterfaceProtocol) { let appSupportDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! do { diff --git a/util/config/appdir.go b/util/config/appdir.go index 625f448b8f..65afb09353 100644 --- a/util/config/appdir.go +++ b/util/config/appdir.go @@ -83,8 +83,8 @@ func AppDir() string { // ExportsDir returns the absolute path to the folder which can be used to export files. func ExportsDir() (string, error) { - if runtime.GOOS == "android" { - // Android apps are sandboxed, we don't need to specify a folder. + if runtime.GOOS == "android" || runtime.GOOS == "ios" { + // Android/iOS apps are sandboxed, we don't need to specify a folder. return "", nil } homeFolder := os.Getenv("HOME")