From 8e1178e9023ac00c1f35127e6aca49fdf7f9447b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 9 Jan 2025 17:43:34 +0100 Subject: [PATCH 01/13] Add and call view controller for app picking (#2450) # Conflicts: # deltachat-ios/Chat/ChatViewController.swift --- deltachat-ios.xcodeproj/project.pbxproj | 4 +++ .../Chat/AppPickerViewController.swift | 24 +++++++++++++++++ deltachat-ios/Chat/ChatViewController.swift | 27 ++++++++++++++++--- deltachat-ios/Helper/MediaPicker.swift | 18 +++++-------- 4 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 deltachat-ios/Chat/AppPickerViewController.swift diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index dced94cbf..1cd7a4641 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -224,6 +224,7 @@ D84AED272B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84AED262B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift */; }; D85DF9782C4A96CB00A01408 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF9772C4A96CB00A01408 /* UserDefaults+Extensions.swift */; }; D85DF9802C5250E200A01408 /* ProgressAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85DF97F2C5250E200A01408 /* ProgressAlertHandler.swift */; }; + D86DF20C2D30318B00141B3E /* AppPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86DF20B2D30318B00141B3E /* AppPickerViewController.swift */; }; D878C4FD2CF72AA0009AF551 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D878C4FC2CF72AA0009AF551 /* WidgetKit.framework */; }; D878C4FF2CF72AA0009AF551 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D878C4FE2CF72AA0009AF551 /* SwiftUI.framework */; }; D878C50C2CF72AA1009AF551 /* DcWidget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D878C4FB2CF72AA0009AF551 /* DcWidget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -632,6 +633,7 @@ D84AED262B566C0700D753F6 /* ReactionsOverviewTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsOverviewTableViewCell.swift; sourceTree = ""; }; D85DF9772C4A96CB00A01408 /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; }; D85DF97F2C5250E200A01408 /* ProgressAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressAlertHandler.swift; sourceTree = ""; }; + D86DF20B2D30318B00141B3E /* AppPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPickerViewController.swift; sourceTree = ""; }; D878C4FB2CF72AA0009AF551 /* DcWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DcWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; D878C4FC2CF72AA0009AF551 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; D878C4FE2CF72AA0009AF551 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; @@ -865,6 +867,7 @@ 3080A00D277DDA1400E74565 /* InputBarAccessoryView */, 30FDB6B524D193DD0066C48D /* Views */, 303492942565AABC00A523D0 /* DraftModel.swift */, + D86DF20B2D30318B00141B3E /* AppPickerViewController.swift */, ); path = Chat; sourceTree = ""; @@ -1676,6 +1679,7 @@ 30F4E2942859213400ACA0D8 /* ChatListEditingBar.swift in Sources */, AE57C0802552BBD0003CFE70 /* GalleryItem.swift in Sources */, AE25F09022807AD800CDEA66 /* AvatarSelectionCell.swift in Sources */, + D86DF20C2D30318B00141B3E /* AppPickerViewController.swift in Sources */, 30DDCBE928FCA1FA00465D22 /* PartialScreenPresentationController.swift in Sources */, 30A4149724F6EFBE00EC91EB /* InfoMessageCell.swift in Sources */, 302B84C6239676F0001C261F /* AvatarHelper.swift in Sources */, diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift new file mode 100644 index 000000000..25d7b7bd1 --- /dev/null +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -0,0 +1,24 @@ +import UIKit +import WebKit + +protocol AppPickerViewControllerDelegate: AnyObject { + +} + +class AppPickerViewController: UIViewController { + // Web view + // Context + weak var delegate: AppPickerViewControllerDelegate? + + init() { + super.init(nibName: nil, bundle: nil) + view.backgroundColor = .systemGroupedBackground + title = String.localized("webxdc_apps") + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } +} + +extension AppPickerViewController: WKNavigationDelegate { + // intercept download, store in core and call delegate +} diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index dda30103e..2a4272e1c 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1216,9 +1216,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let galleryImage = if #available(iOS 16, *) { "photo.stack" } else { "photo" } actions.append(action(localized: "gallery", systemImage: galleryImage, handler: showPhotoVideoLibrary)) actions.append(action(localized: "files", systemImage: "folder", handler: showDocumentLibrary)) - if dcContext.hasWebxdc() { - actions.append(action(localized: "webxdc_apps", systemImage: "square.grid.2x2", handler: showWebxdcSelector)) - } + actions.append(action(localized: "webxdc_apps", systemImage: "square.grid.2x2", handler: showWebxdcSelector)) actions.append(action(localized: "voice_message", systemImage: "mic", handler: showVoiceMessageRecorder)) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { let videoChatImage = if #available(iOS 17, *) { "video.bubble" } else { "video" } @@ -1252,6 +1250,8 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let documentAction = action(localized: "files", handler: showDocumentLibrary) let voiceMessageAction = action(localized: "voice_message", handler: showVoiceMessageRecorder) let sendContactAction = action(localized: "contact", handler: showContactList) + let appPickerAction = action(localized: "webxdc_apps", handler: showAppPicker) + let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId) let locationStreamingAction = action( localized: isLocationStreaming ? "stop_sharing_location" : "location", @@ -1261,6 +1261,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { alert.addAction(cameraAction) alert.addAction(galleryAction) + alert.addAction(appPickerAction) alert.addAction(documentAction) let webxdcAction = action(localized: "webxdc_apps", handler: showWebxdcSelector) @@ -1528,6 +1529,20 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { navigationController?.present(alert, animated: true, completion: nil) } + private func showAppPicker() { + let appPicker = AppPickerViewController() + + let navigationController = UINavigationController(rootViewController: appPicker) + if #available(iOS 15.0, *) { + if let sheet = navigationController.sheetPresentationController { + sheet.detents = [.large()] + sheet.preferredCornerRadius = 20 + } + } + + present(navigationController, animated: true) + } + private func showContactList() { let contactList = SendContactViewController(dcContext: dcContext) contactList.delegate = self @@ -2748,3 +2763,9 @@ extension ChatViewController: BackButtonUpdateable { } } } + +// MARK: - AppPickerViewControllerDelegate + +extension ChatViewController: AppPickerViewControllerDelegate { + +} diff --git a/deltachat-ios/Helper/MediaPicker.swift b/deltachat-ios/Helper/MediaPicker.swift index 5e6a52ba0..1faed2130 100644 --- a/deltachat-ios/Helper/MediaPicker.swift +++ b/deltachat-ios/Helper/MediaPicker.swift @@ -13,18 +13,12 @@ protocol MediaPickerDelegate: AnyObject { } extension MediaPickerDelegate { - func onImageSelected(image: UIImage) { - } - func onImageSelected(url: NSURL) { - } - func onVideoSelected(url: NSURL) { - } - func onVoiceMessageRecorded(url: NSURL) { - } - func onVoiceMessageRecorderClosed() { - } - func onDocumentSelected(url: NSURL) { - } + func onImageSelected(image: UIImage) { } + func onImageSelected(url: NSURL) { } + func onVideoSelected(url: NSURL) { } + func onVoiceMessageRecorded(url: NSURL) { } + func onVoiceMessageRecorderClosed() { } + func onDocumentSelected(url: NSURL) { } } class MediaPicker: NSObject, UINavigationControllerDelegate { From e356861cc8f0a0ba5478b737a309645df0bb3c7d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 10 Jan 2025 12:31:38 +0100 Subject: [PATCH 02/13] Don't use a navigation controller (#2450) --- deltachat-ios/Chat/ChatViewController.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index 2a4272e1c..0da59c89e 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1531,16 +1531,14 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { private func showAppPicker() { let appPicker = AppPickerViewController() - - let navigationController = UINavigationController(rootViewController: appPicker) if #available(iOS 15.0, *) { - if let sheet = navigationController.sheetPresentationController { + if let sheet = appPicker.sheetPresentationController { sheet.detents = [.large()] sheet.preferredCornerRadius = 20 } } - present(navigationController, animated: true) + present(appPicker, animated: true) } private func showContactList() { @@ -2767,5 +2765,5 @@ extension ChatViewController: BackButtonUpdateable { // MARK: - AppPickerViewControllerDelegate extension ChatViewController: AppPickerViewControllerDelegate { - + // stage message } From f72c898be27f3a7a52012d94356966a726ae0794 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 10 Jan 2025 12:31:58 +0100 Subject: [PATCH 03/13] Add basic webview to show store (#2450) --- .../Chat/AppPickerViewController.swift | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 25d7b7bd1..6d4c9214b 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -9,16 +9,43 @@ class AppPickerViewController: UIViewController { // Web view // Context weak var delegate: AppPickerViewControllerDelegate? + let webView: WKWebView + + init(url: URL = URL(string: "https://webxdc.org/apps/")!) { + webView = WKWebView(frame: .zero) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.load(URLRequest(url: url)) - init() { super.init(nibName: nil, bundle: nil) + + webView.navigationDelegate = self + view.addSubview(webView) view.backgroundColor = .systemGroupedBackground + setupConstraints() + title = String.localized("webxdc_apps") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: webView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: webView.bottomAnchor), + ] + + NSLayoutConstraint.activate(constraints) + } } extension AppPickerViewController: WKNavigationDelegate { - // intercept download, store in core and call delegate + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) { + decisionHandler(.allow) + + // if url ends with .xdc -> download and store in core and call delegate + // else if host = webxdc -> allow + // else -> open in browser and .deny + } } From 427d9df07d393de369a2cee71eabbadeadf64a11 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 10 Jan 2025 16:32:31 +0100 Subject: [PATCH 04/13] Download and attach xdc (#2450) --- .../Chat/AppPickerViewController.swift | 27 ++++++++++++++++--- deltachat-ios/Chat/ChatViewController.swift | 9 ++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 6d4c9214b..b0cc4567a 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -2,7 +2,7 @@ import UIKit import WebKit protocol AppPickerViewControllerDelegate: AnyObject { - + func pickedAnDownloadedApp(_ viewController: AppPickerViewController, fileURL: URL) } class AppPickerViewController: UIViewController { @@ -42,10 +42,29 @@ class AppPickerViewController: UIViewController { extension AppPickerViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) { - decisionHandler(.allow) + guard let url = navigationAction.request.url else { + return decisionHandler(.cancel) + } // if url ends with .xdc -> download and store in core and call delegate - // else if host = webxdc -> allow - // else -> open in browser and .deny + if url.pathExtension == "xdc" { + // TODO: Add loading indicator + Task { + guard let (data, _) = try? await URLSession.shared.data(from: url), + let filepath = FileHelper.saveData(data: data, name: url.lastPathComponent) + else { return decisionHandler(.cancel) } + + let fileURL = NSURL(fileURLWithPath: filepath) + delegate?.pickedAnDownloadedApp(self, fileURL: fileURL as URL) + decisionHandler(.cancel) + } + } else if url.host == "webxdc.org" { + decisionHandler(.allow) + } else if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + decisionHandler(.cancel) + } else { + decisionHandler(.cancel) + } } } diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index 0da59c89e..14a27b9c0 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1531,6 +1531,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { private func showAppPicker() { let appPicker = AppPickerViewController() + appPicker.delegate = self if #available(iOS 15.0, *) { if let sheet = appPicker.sheetPresentationController { sheet.detents = [.large()] @@ -2765,5 +2766,11 @@ extension ChatViewController: BackButtonUpdateable { // MARK: - AppPickerViewControllerDelegate extension ChatViewController: AppPickerViewControllerDelegate { - // stage message + func pickedAnDownloadedApp(_ viewController: AppPickerViewController, fileURL url: URL) { + draft.setAttachment(viewType: DC_MSG_WEBXDC, path: url.relativePath) + configureDraftArea(draft: draft) + focusInputTextView() + FileHelper.deleteFile(atPath: url.relativePath) + viewController.dismiss(animated: true) + } } From c2003dce4967e59acd1d1e90331ba2924f24f5b6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 10 Jan 2025 16:32:39 +0100 Subject: [PATCH 05/13] minor cleanup --- deltachat-ios/DC/DcMsg.swift | 2 +- deltachat-ios/Helper/FileHelper.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deltachat-ios/DC/DcMsg.swift b/deltachat-ios/DC/DcMsg.swift index 99460180e..cf1bf894a 100644 --- a/deltachat-ios/DC/DcMsg.swift +++ b/deltachat-ios/DC/DcMsg.swift @@ -129,7 +129,7 @@ public class DcMsg { } public var fileURL: URL? { - if let file = self.file { + if let file { return URL(fileURLWithPath: file, isDirectory: false) } return nil diff --git a/deltachat-ios/Helper/FileHelper.swift b/deltachat-ios/Helper/FileHelper.swift index 24d926297..3ac918af0 100644 --- a/deltachat-ios/Helper/FileHelper.swift +++ b/deltachat-ios/Helper/FileHelper.swift @@ -39,17 +39,17 @@ public class FileHelper { } // add file name to path - if let name = name { - if let suffix = suffix { + if let name { + if let suffix { path = subdirectoryURL.appendingPathComponent("\(name).\(suffix)") } else { path = subdirectoryURL.appendingPathComponent(name) } - } else if let suffix = suffix { + } else if let suffix { let timestamp = Double(Date().timeIntervalSince1970) path = subdirectoryURL.appendingPathComponent("\(timestamp).\(suffix)") } - guard let path = path else { return nil } + guard let path else { return nil } // write data do { From 87a617d22a7855be29f62c4c3e0b4fe92d1e21be Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Jan 2025 08:46:22 +0100 Subject: [PATCH 06/13] Remove existing app-selector (#2450) --- deltachat-ios.xcodeproj/project.pbxproj | 4 - deltachat-ios/Chat/ChatViewController.swift | 45 +--- deltachat-ios/Controller/WebxdcSelector.swift | 237 ------------------ 3 files changed, 1 insertion(+), 285 deletions(-) delete mode 100644 deltachat-ios/Controller/WebxdcSelector.swift diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 1cd7a4641..a0e16f733 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ 30152CA025A5D97900377714 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */; }; 3015634423A003BA00E9DEF4 /* AudioRecorderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3015634323A003BA00E9DEF4 /* AudioRecorderController.swift */; }; 3022E6BE22E8768800763272 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3022E6C022E8768800763272 /* InfoPlist.strings */; }; - 30238CFB28A501C300EF14AC /* WebxdcSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30238CFA28A501C300EF14AC /* WebxdcSelector.swift */; }; 30238CFD28A5028300EF14AC /* WebxdcGridCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30238CFC28A5028300EF14AC /* WebxdcGridCell.swift */; }; 30238CFF28A5554C00EF14AC /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30238CFE28A5554C00EF14AC /* FileHelper.swift */; }; 30238D0028A557E800EF14AC /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30238CFE28A5554C00EF14AC /* FileHelper.swift */; }; @@ -328,7 +327,6 @@ 3022E6D022E8769D00763272 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 3022E6D122E8769E00763272 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; }; 3022E6D322E876A100763272 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; - 30238CFA28A501C300EF14AC /* WebxdcSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebxdcSelector.swift; sourceTree = ""; }; 30238CFC28A5028300EF14AC /* WebxdcGridCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebxdcGridCell.swift; sourceTree = ""; }; 30238CFE28A5554C00EF14AC /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; }; 302589FE2452FA280086C1CD /* ShareAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAttachment.swift; sourceTree = ""; }; @@ -1077,7 +1075,6 @@ 30149D9222F21129003C12B5 /* QrViewController.swift */, 30B0ACF924AB5B99004D5E29 /* EphemeralMessagesViewController.swift */, AE8F503424753DFE007FEE0B /* GalleryViewController.swift */, - 30238CFA28A501C300EF14AC /* WebxdcSelector.swift */, AED423D2249F578B00B6B2BB /* AddGroupMembersViewController.swift */, AE39D322249CFC1A007346A1 /* FilesViewController.swift */, AE57C083255310BB003CFE70 /* ContextMenuController.swift */, @@ -1648,7 +1645,6 @@ 3080A027277DE12D00E74565 /* InputBarButtonItem.swift in Sources */, D85DF9782C4A96CB00A01408 /* UserDefaults+Extensions.swift in Sources */, D8C19DD02C1C9FFE00B32F6D /* ContactCardPreview.swift in Sources */, - 30238CFB28A501C300EF14AC /* WebxdcSelector.swift in Sources */, AE57C084255310BB003CFE70 /* ContextMenuController.swift in Sources */, 304219D92440734A00516852 /* DcMsg+Extension.swift in Sources */, AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */, diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index 14a27b9c0..cb9848ee8 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1216,7 +1216,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let galleryImage = if #available(iOS 16, *) { "photo.stack" } else { "photo" } actions.append(action(localized: "gallery", systemImage: galleryImage, handler: showPhotoVideoLibrary)) actions.append(action(localized: "files", systemImage: "folder", handler: showDocumentLibrary)) - actions.append(action(localized: "webxdc_apps", systemImage: "square.grid.2x2", handler: showWebxdcSelector)) + actions.append(action(localized: "webxdc_apps", systemImage: "square.grid.2x2", handler: showAppPicker)) actions.append(action(localized: "voice_message", systemImage: "mic", handler: showVoiceMessageRecorder)) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { let videoChatImage = if #available(iOS 17, *) { "video.bubble" } else { "video" } @@ -1263,9 +1263,6 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { alert.addAction(galleryAction) alert.addAction(appPickerAction) alert.addAction(documentAction) - - let webxdcAction = action(localized: "webxdc_apps", handler: showWebxdcSelector) - alert.addAction(webxdcAction) alert.addAction(voiceMessageAction) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { @@ -1435,20 +1432,6 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } } - private func showWebxdcSelector() { - let webxdcSelector = WebxdcSelector(context: dcContext) - webxdcSelector.delegate = self - let webxdcSelectorNavigationController = UINavigationController(rootViewController: webxdcSelector) - if #available(iOS 15.0, *) { - if let sheet = webxdcSelectorNavigationController.sheetPresentationController { - sheet.detents = [.medium()] - sheet.preferredCornerRadius = 20 - } - } - - self.present(webxdcSelectorNavigationController, animated: true) - } - private func showDocumentLibrary() { mediaPicker?.showDocumentLibrary() } @@ -2666,32 +2649,6 @@ extension ChatViewController: ChatInputTextViewPasteDelegate { } } - -extension ChatViewController: WebxdcSelectorDelegate { - func onWebxdcFromFilesSelected(url: NSURL) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - self.becomeFirstResponder() - self.onDocumentSelected(url: url) - } - } - - func onWebxdcSelected(msgId: Int) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - let message = self.dcContext.getMessage(id: msgId) - if let filename = message.fileURL { - let nsdata = NSData(contentsOf: filename) - guard let data = nsdata as? Data else { return } - let url = FileHelper.saveData(data: data, suffix: "xdc", directory: .cachesDirectory) - self.draft.setAttachment(viewType: DC_MSG_WEBXDC, path: url) - self.configureDraftArea(draft: self.draft) - self.focusInputTextView() - } - } - } -} - // MARK: - ChatDropInteractionDelegate extension ChatViewController: ChatDropInteractionDelegate { func onImageDragAndDropped(image: UIImage) { diff --git a/deltachat-ios/Controller/WebxdcSelector.swift b/deltachat-ios/Controller/WebxdcSelector.swift deleted file mode 100644 index a9aaeb054..000000000 --- a/deltachat-ios/Controller/WebxdcSelector.swift +++ /dev/null @@ -1,237 +0,0 @@ -import UIKit -import DcCore -import QuickLook - -protocol WebxdcSelectorDelegate: AnyObject { - func onWebxdcSelected(msgId: Int) - func onWebxdcFromFilesSelected(url: NSURL) -} - -class WebxdcSelector: UIViewController { - - private let dcContext: DcContext - // MARK: - data - private var deduplicatedMessageIds: [Int] = [] - private var items: [Int: GalleryItem] = [:] - - // MARK: - subview specs - private let gridDefaultSpacing: CGFloat = 5 - weak var delegate: WebxdcSelectorDelegate? - - private lazy var gridLayout: GridCollectionViewFlowLayout = { - let layout = GridCollectionViewFlowLayout() - layout.minimumLineSpacing = gridDefaultSpacing - layout.minimumInteritemSpacing = gridDefaultSpacing - layout.format = .rect(ratio: 1.3) - return layout - }() - - private lazy var grid: UICollectionView = { - let collection = UICollectionView(frame: .zero, collectionViewLayout: gridLayout) - collection.dataSource = self - collection.delegate = self - collection.register(WebxdcGridCell.self, forCellWithReuseIdentifier: WebxdcGridCell.reuseIdentifier) - collection.contentInset = UIEdgeInsets(top: gridDefaultSpacing, left: gridDefaultSpacing, bottom: gridDefaultSpacing, right: gridDefaultSpacing) - collection.backgroundColor = DcColors.defaultBackgroundColor - collection.delaysContentTouches = false - collection.alwaysBounceVertical = true - collection.isPrefetchingEnabled = true - collection.prefetchDataSource = self - return collection - }() - - private lazy var emptyStateView: EmptyStateLabel = { - let label = EmptyStateLabel() - label.text = String.localized("one_moment") - return label - }() - - private lazy var cancelButton: UIBarButtonItem = { - let btn = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel, - target: self, - action: #selector(cancelAction)) - return btn - }() - - private lazy var filesButton: UIBarButtonItem = { - let btn = UIBarButtonItem(title: String.localized("files"), - style: .plain, - target: self, - action: #selector(filesAction)) - return btn - }() - - private lazy var mediaPicker: MediaPicker? = { - let mediaPicker = MediaPicker(dcContext: dcContext, navigationController: navigationController) - mediaPicker.delegate = self - return mediaPicker - }() - - init(context: DcContext) { - self.dcContext = context - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - lifecycle - override func viewDidLoad() { - super.viewDidLoad() - setupSubviews() - title = String.localized("webxdc_apps") - navigationItem.setLeftBarButton(filesButton, animated: false) - navigationItem.setRightBarButton(cancelButton, animated: false) - deduplicateWebxdcs() - } - - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - self.reloadCollectionViewLayout() - } - - // MARK: - setup - private func setupSubviews() { - view.addSubview(grid) - grid.translatesAutoresizingMaskIntoConstraints = false - grid.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true - grid.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - grid.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true - grid.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - emptyStateView.addCenteredTo(parentView: view) - } - - func deduplicateWebxdcs() { - var deduplicatedMessageHashes: [String: Int] = [:] - DispatchQueue.global(qos: .userInteractive).async { [weak self] in - guard let self else { return } - let mediaMessageIds = dcContext.getChatMedia(chatId: 0, messageType: DC_MSG_WEBXDC, messageType2: 0, messageType3: 0).reversed() - for id in mediaMessageIds { - guard let filename = self.dcContext.getMessage(id: id).fileURL else { continue } - if let hash = try? NSData(contentsOf: filename).sha1() { - DispatchQueue.main.async { - if deduplicatedMessageHashes[hash] == nil { - deduplicatedMessageHashes[hash] = id - self.deduplicatedMessageIds.append(id) - } - } - } - } - - DispatchQueue.main.async { - if self.deduplicatedMessageIds.isEmpty { - self.emptyStateView.text = String.localized("webxdc_selector_empty_hint") - } else { - self.emptyStateView.isHidden = true - } - self.grid.reloadData() - } - } - } - - @objc func cancelAction() { - dismiss(animated: true, completion: nil) - } - - @objc func filesAction() { - mediaPicker?.showDocumentLibrary() - } -} - -extension WebxdcSelector: UICollectionViewDataSourcePrefetching { - func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { - indexPaths.forEach { if items[$0.row] == nil { - let message = dcContext.getMessage(id: deduplicatedMessageIds[$0.row]) - let item = GalleryItem(msg: message) - items[$0.row] = item - }} - } -} - -// MARK: - UICollectionViewDataSource, UICollectionViewDelegate -extension WebxdcSelector: UICollectionViewDataSource, UICollectionViewDelegate { - - func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return deduplicatedMessageIds.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let webxdcGridCell = collectionView.dequeueReusableCell( - withReuseIdentifier: WebxdcGridCell.reuseIdentifier, - for: indexPath) as? WebxdcGridCell else { - return UICollectionViewCell() - } - - let msgId = deduplicatedMessageIds[indexPath.row] - var item: GalleryItem - if let galleryItem = items[indexPath.row] { - item = galleryItem - } else { - let message = dcContext.getMessage(id: msgId) - let galleryItem = GalleryItem(msg: message) - items[indexPath.row] = galleryItem - item = galleryItem - } - webxdcGridCell.update(item: item) - return webxdcGridCell - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let msgId = deduplicatedMessageIds[indexPath.row] - delegate?.onWebxdcSelected(msgId: msgId) - collectionView.deselectItem(at: indexPath, animated: true) - self.dismiss(animated: true, completion: nil) - } -} - -// MARK: - grid layout + updates -private extension WebxdcSelector { - func reloadCollectionViewLayout() { - guard let orientation = UIApplication.shared.orientation else { return } - - // columns specification - let phonePortrait = 3 - let phoneLandscape = 4 - let padPortrait = 5 - let padLandscape = 8 - - let deviceType = UIDevice.current.userInterfaceIdiom - - var gridDisplay: GridDisplay? - if deviceType == .phone { - if orientation.isPortrait { - gridDisplay = .grid(columns: phonePortrait) - } else { - gridDisplay = .grid(columns: phoneLandscape) - } - } else if deviceType == .pad { - if orientation.isPortrait { - gridDisplay = .grid(columns: padPortrait) - } else { - gridDisplay = .grid(columns: padLandscape) - } - } - - if let gridDisplay = gridDisplay { - gridLayout.display = gridDisplay - } else { - safe_fatalError("undefined format") - } - let containerWidth = view.bounds.width - view.safeAreaInsets.left - view.safeAreaInsets.right - 2 * gridDefaultSpacing - gridLayout.containerWidth = containerWidth - } -} - -extension WebxdcSelector: MediaPickerDelegate { - func onImageSelected(image: UIImage) {} - - func onDocumentSelected(url: NSURL) { - delegate?.onWebxdcFromFilesSelected(url: url) - dismiss(animated: true) - } -} From b5c064116f9707d25fb4f8a4e2e436cc8b2c2c15 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Jan 2025 17:23:34 +0100 Subject: [PATCH 07/13] Bring back navigation controller but with close-button (#2450) --- deltachat-ios/Chat/AppPickerViewController.swift | 11 +++++++++-- deltachat-ios/Chat/ChatViewController.swift | 14 ++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index b0cc4567a..171596a78 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -20,8 +20,9 @@ class AppPickerViewController: UIViewController { webView.navigationDelegate = self view.addSubview(webView) - view.backgroundColor = .systemGroupedBackground + view.backgroundColor = .systemBackground setupConstraints() + navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(AppPickerViewController.close(_:))) title = String.localized("webxdc_apps") } @@ -30,7 +31,7 @@ class AppPickerViewController: UIViewController { private func setupConstraints() { let constraints = [ - webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + webView.topAnchor.constraint(equalTo: view.topAnchor), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), view.trailingAnchor.constraint(equalTo: webView.trailingAnchor), view.bottomAnchor.constraint(equalTo: webView.bottomAnchor), @@ -38,6 +39,12 @@ class AppPickerViewController: UIViewController { NSLayoutConstraint.activate(constraints) } + + // MARK: - Actions + + @objc func close(_ sender: Any) { + dismiss(animated: true) + } } extension AppPickerViewController: WKNavigationDelegate { diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index cb9848ee8..fdeae10fa 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1515,14 +1515,16 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { private func showAppPicker() { let appPicker = AppPickerViewController() appPicker.delegate = self - if #available(iOS 15.0, *) { - if let sheet = appPicker.sheetPresentationController { - sheet.detents = [.large()] - sheet.preferredCornerRadius = 20 - } + let navigationController = UINavigationController(rootViewController: appPicker) + navigationController.navigationBar.standardAppearance.backgroundEffect = UIBlurEffect(style: .extraLight) + navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance + + if #available(iOS 15.0, *), let sheet = navigationController.sheetPresentationController { + sheet.detents = [.large()] + sheet.preferredCornerRadius = 20 } - present(appPicker, animated: true) + present(navigationController, animated: true) } private func showContactList() { From 9b0673da8f7b4136f0e08eb22038c6014fdd3be8 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Jan 2025 19:15:38 +0100 Subject: [PATCH 08/13] [wip] add some kind of loading indicator (#2450) --- .../Chat/AppPickerViewController.swift | 29 ++++++++++++++++--- deltachat-ios/Chat/ChatViewController.swift | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 171596a78..8cc2baa9b 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -10,6 +10,7 @@ class AppPickerViewController: UIViewController { // Context weak var delegate: AppPickerViewControllerDelegate? let webView: WKWebView + var defaultCloseButton: UIBarButtonItem? init(url: URL = URL(string: "https://webxdc.org/apps/")!) { webView = WKWebView(frame: .zero) @@ -22,9 +23,11 @@ class AppPickerViewController: UIViewController { view.addSubview(webView) view.backgroundColor = .systemBackground setupConstraints() - navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(AppPickerViewController.close(_:))) + let closeButton = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(AppPickerViewController.close(_:))) title = String.localized("webxdc_apps") + navigationItem.leftBarButtonItem = closeButton + self.defaultCloseButton = closeButton } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -55,14 +58,32 @@ extension AppPickerViewController: WKNavigationDelegate { // if url ends with .xdc -> download and store in core and call delegate if url.pathExtension == "xdc" { - // TODO: Add loading indicator - Task { + Task { [weak self] in + guard let self else { return } + // show spinner instead of close-button + await MainActor.run { + let activityIndicator = UIActivityIndicatorView(style: .medium) + activityIndicator.startAnimating() + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: activityIndicator) + } + guard let (data, _) = try? await URLSession.shared.data(from: url), let filepath = FileHelper.saveData(data: data, name: url.lastPathComponent) - else { return decisionHandler(.cancel) } + else { + await MainActor.run { self.navigationItem.leftBarButtonItem = self.defaultCloseButton } + return decisionHandler(.cancel) + } + + if #available(iOS 16.0, *) { + try await Task.sleep(for: .seconds(5)) + } let fileURL = NSURL(fileURLWithPath: filepath) delegate?.pickedAnDownloadedApp(self, fileURL: fileURL as URL) + await MainActor.run { + self.dismiss(animated: true) + } + decisionHandler(.cancel) } } else if url.host == "webxdc.org" { diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index fdeae10fa..baeaea396 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1518,6 +1518,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let navigationController = UINavigationController(rootViewController: appPicker) navigationController.navigationBar.standardAppearance.backgroundEffect = UIBlurEffect(style: .extraLight) navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance + navigationController.isModalInPresentation = true if #available(iOS 15.0, *), let sheet = navigationController.sheetPresentationController { sheet.detents = [.large()] @@ -2730,6 +2731,5 @@ extension ChatViewController: AppPickerViewControllerDelegate { configureDraftArea(draft: draft) focusInputTextView() FileHelper.deleteFile(atPath: url.relativePath) - viewController.dismiss(animated: true) } } From a71a6e35cdd55ef449c1f9fc8bc211c350292b43 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Jan 2025 19:41:22 +0100 Subject: [PATCH 09/13] Add loading indicator (#2450) --- .../Chat/AppPickerViewController.swift | 67 +++++++++++++++++-- deltachat-ios/Chat/ChatViewController.swift | 2 - 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 8cc2baa9b..4b20043dd 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -11,16 +11,22 @@ class AppPickerViewController: UIViewController { weak var delegate: AppPickerViewControllerDelegate? let webView: WKWebView var defaultCloseButton: UIBarButtonItem? + let downloadingView: DownloadingView init(url: URL = URL(string: "https://webxdc.org/apps/")!) { webView = WKWebView(frame: .zero) webView.translatesAutoresizingMaskIntoConstraints = false webView.load(URLRequest(url: url)) + downloadingView = DownloadingView() + downloadingView.translatesAutoresizingMaskIntoConstraints = false + downloadingView.isHidden = true + super.init(nibName: nil, bundle: nil) webView.navigationDelegate = self view.addSubview(webView) + view.addSubview(downloadingView) view.backgroundColor = .systemBackground setupConstraints() let closeButton = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(AppPickerViewController.close(_:))) @@ -38,6 +44,11 @@ class AppPickerViewController: UIViewController { webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), view.trailingAnchor.constraint(equalTo: webView.trailingAnchor), view.bottomAnchor.constraint(equalTo: webView.bottomAnchor), + + downloadingView.topAnchor.constraint(equalTo: view.topAnchor), + downloadingView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: downloadingView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: downloadingView.bottomAnchor), ] NSLayoutConstraint.activate(constraints) @@ -48,6 +59,21 @@ class AppPickerViewController: UIViewController { @objc func close(_ sender: Any) { dismiss(animated: true) } + + @objc func showLoading() { + title = String.localized("Downloading...") + downloadingView.isHidden = false + downloadingView.activityIndicator.startAnimating() + downloadingView.activityIndicator.hidesWhenStopped = true + navigationItem.leftBarButtonItem = nil + } + + @objc func hideLoading() { + title = String.localized("webxdc_apps") + downloadingView.isHidden = true + downloadingView.activityIndicator.stopAnimating() + navigationItem.leftBarButtonItem = defaultCloseButton + } } extension AppPickerViewController: WKNavigationDelegate { @@ -62,20 +88,20 @@ extension AppPickerViewController: WKNavigationDelegate { guard let self else { return } // show spinner instead of close-button await MainActor.run { - let activityIndicator = UIActivityIndicatorView(style: .medium) - activityIndicator.startAnimating() - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: activityIndicator) + self.showLoading() } guard let (data, _) = try? await URLSession.shared.data(from: url), let filepath = FileHelper.saveData(data: data, name: url.lastPathComponent) else { - await MainActor.run { self.navigationItem.leftBarButtonItem = self.defaultCloseButton } + await MainActor.run { + self.hideLoading() + } return decisionHandler(.cancel) } if #available(iOS 16.0, *) { - try await Task.sleep(for: .seconds(5)) + try await Task.sleep(for: .seconds(2)) } let fileURL = NSURL(fileURLWithPath: filepath) @@ -96,3 +122,34 @@ extension AppPickerViewController: WKNavigationDelegate { } } } + +class DownloadingView: UIView { + let activityIndicator: UIActivityIndicatorView + private let blurView: UIVisualEffectView + + init() { + activityIndicator = UIActivityIndicatorView(style: .large) + activityIndicator.color = .white + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + + blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + blurView.translatesAutoresizingMaskIntoConstraints = false + + super.init(frame: .zero) + + addSubview(blurView) + addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), + + blurView.topAnchor.constraint(equalTo: topAnchor), + blurView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: blurView.trailingAnchor), + bottomAnchor.constraint(equalTo: blurView.bottomAnchor), + ]) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } +} diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index baeaea396..fde43c7f4 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1516,8 +1516,6 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { let appPicker = AppPickerViewController() appPicker.delegate = self let navigationController = UINavigationController(rootViewController: appPicker) - navigationController.navigationBar.standardAppearance.backgroundEffect = UIBlurEffect(style: .extraLight) - navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance navigationController.isModalInPresentation = true if #available(iOS 15.0, *), let sheet = navigationController.sheetPresentationController { From c9764477d5ddac782cf491acf9a033879696076e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Jan 2025 09:28:52 +0100 Subject: [PATCH 10/13] Bug fixes and improvements (#2450) --- deltachat-ios/Chat/AppPickerViewController.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 4b20043dd..7534e332e 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -100,10 +100,6 @@ extension AppPickerViewController: WKNavigationDelegate { return decisionHandler(.cancel) } - if #available(iOS 16.0, *) { - try await Task.sleep(for: .seconds(2)) - } - let fileURL = NSURL(fileURLWithPath: filepath) delegate?.pickedAnDownloadedApp(self, fileURL: fileURL as URL) await MainActor.run { @@ -129,7 +125,7 @@ class DownloadingView: UIView { init() { activityIndicator = UIActivityIndicatorView(style: .large) - activityIndicator.color = .white + activityIndicator.color = .label activityIndicator.translatesAutoresizingMaskIntoConstraints = false blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) From eb548a85cfa8bf0202ee3962bc9d1e04b66004aa Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Jan 2025 09:29:57 +0100 Subject: [PATCH 11/13] Make ci happy (#2450) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c03d643a..33be1a608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fix scrolling issue when cancelling a screen (#2504) - Fix layout of 'No contacts found' message (#2517) - Don't hide the Apps-tab anymore (#2526) +- Use the new App-picker to share apps (#2450) ## v1.50.4 2025-01 From 43071ea34fa3ac6423826384e0b35a2dd828db53 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Jan 2025 10:04:17 +0100 Subject: [PATCH 12/13] Make loading nicer (#2450) --- deltachat-ios/Chat/AppPickerViewController.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 7534e332e..1e15f971e 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -128,8 +128,10 @@ class DownloadingView: UIView { activityIndicator.color = .label activityIndicator.translatesAutoresizingMaskIntoConstraints = false - blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) blurView.translatesAutoresizingMaskIntoConstraints = false + blurView.layer.cornerRadius = 10 + blurView.layer.masksToBounds = true super.init(frame: .zero) @@ -140,10 +142,10 @@ class DownloadingView: UIView { activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), - blurView.topAnchor.constraint(equalTo: topAnchor), - blurView.leadingAnchor.constraint(equalTo: leadingAnchor), - trailingAnchor.constraint(equalTo: blurView.trailingAnchor), - bottomAnchor.constraint(equalTo: blurView.bottomAnchor), + activityIndicator.topAnchor.constraint(equalTo: blurView.topAnchor, constant: 20), + activityIndicator.leadingAnchor.constraint(equalTo: blurView.leadingAnchor, constant: 20), + blurView.trailingAnchor.constraint(equalTo: activityIndicator.trailingAnchor, constant: 20), + blurView.bottomAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 20), ]) } From d32380d3f1624c37056430064ff47488106403a2 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Jan 2025 14:32:45 +0100 Subject: [PATCH 13/13] Use proper cancel button as discussed (#2450) --- deltachat-ios/Chat/AppPickerViewController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deltachat-ios/Chat/AppPickerViewController.swift b/deltachat-ios/Chat/AppPickerViewController.swift index 1e15f971e..a321093e1 100644 --- a/deltachat-ios/Chat/AppPickerViewController.swift +++ b/deltachat-ios/Chat/AppPickerViewController.swift @@ -6,8 +6,6 @@ protocol AppPickerViewControllerDelegate: AnyObject { } class AppPickerViewController: UIViewController { - // Web view - // Context weak var delegate: AppPickerViewControllerDelegate? let webView: WKWebView var defaultCloseButton: UIBarButtonItem? @@ -29,7 +27,7 @@ class AppPickerViewController: UIViewController { view.addSubview(downloadingView) view.backgroundColor = .systemBackground setupConstraints() - let closeButton = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(AppPickerViewController.close(_:))) + let closeButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel, target: self, action: #selector(AppPickerViewController.close(_:))) title = String.localized("webxdc_apps") navigationItem.leftBarButtonItem = closeButton