From f5401af115994ac943302d439fc344556ba87d3e Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 19 Jan 2024 17:28:18 +0000 Subject: [PATCH] support multiple passes (#2349) --- DuckDuckGo.xcodeproj/project.pbxproj | 21 ++++++ .../xcshareddata/swiftpm/Package.resolved | 11 +++- DuckDuckGo/FilePreviewHelper.swift | 4 +- DuckDuckGo/MIMEType.swift | 1 + DuckDuckGo/PassKitPreviewHelper.swift | 2 +- DuckDuckGo/ZippedPassKitPreviewHelper.swift | 65 +++++++++++++++++++ 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 DuckDuckGo/ZippedPassKitPreviewHelper.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8239f3286e..38136f576a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -365,6 +365,7 @@ 85058370219F424500ED4EDB /* SearchBarExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C3451E4AA32D00CFDE3A /* SearchBarExtension.swift */; }; 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850ABD002AC3961100A733DF /* MainViewController+Segues.swift */; }; 850ABD032AC4D46C00A733DF /* SuggestionTray.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 850ABD022AC4D46C00A733DF /* SuggestionTray.storyboard */; }; + 850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850F93DA2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift */; }; 8512EA4F24ED30D20073EE19 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8512EA5124ED30D20073EE19 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8512EA5024ED30D20073EE19 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8512EA5424ED30D20073EE19 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512EA5324ED30D20073EE19 /* Widgets.swift */; }; @@ -406,6 +407,7 @@ 853A717820F645FB00FE60BC /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853A717720F645FB00FE60BC /* PixelTests.swift */; }; 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F5A21BFF0AE001F7A05 /* HomeCollectionView.swift */; }; 853C5F6121C277C7001F7A05 /* global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F6021C277C7001F7A05 /* global.swift */; }; + 854007E72B57FC000001BD98 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 854007E62B57FC000001BD98 /* ZIPFoundation */; }; 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BBA12440857A00017FE4 /* PreserveLoginsWorker.swift */; }; 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */; }; 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5323D8D5080057FDD2 /* PreserveLoginsAlert.swift */; }; @@ -1447,6 +1449,7 @@ 85058367219C49E000ED4EDB /* HomeViewSectionRenderers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewSectionRenderers.swift; sourceTree = ""; }; 850ABD002AC3961100A733DF /* MainViewController+Segues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+Segues.swift"; sourceTree = ""; }; 850ABD022AC4D46C00A733DF /* SuggestionTray.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SuggestionTray.storyboard; sourceTree = ""; }; + 850F93DA2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZippedPassKitPreviewHelper.swift; sourceTree = ""; }; 8512BCBF2061B6110085E862 /* global.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = global.swift; sourceTree = ""; }; 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; @@ -2680,6 +2683,7 @@ F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */, + 854007E72B57FC000001BD98 /* ZIPFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3313,6 +3317,7 @@ 3132FA2527A0784600DD7A12 /* FilePreviewHelper.swift */, 3132FA2927A0788F00DD7A12 /* QuickLookPreviewHelper.swift */, 3132FA2727A0788400DD7A12 /* PassKitPreviewHelper.swift */, + 850F93DA2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift */, ); name = FilePreview; sourceTree = ""; @@ -5689,6 +5694,7 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, + 854007E62B57FC000001BD98 /* ZIPFoundation */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5991,6 +5997,7 @@ 0202568C29881E4300E694E7 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */, 0238E44D29C0FAA100615E30 /* XCRemoteSwiftPackageReference "ios-js-support" */, 4B2754EA29E8C7DF00394032 /* XCRemoteSwiftPackageReference "lottie-ios" */, + 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, ); productRefGroup = 84E341931E2F7EFB00BDBA6F /* Products */; projectDirPath = ""; @@ -6772,6 +6779,7 @@ 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */, 1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */, 1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */, + 850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */, 4BCD14672B05B682000B1E4C /* NetworkProtectionTermsAndConditionsStore.swift in Sources */, 020108A129A5610C00644F9D /* AppTPActivityHostingViewController.swift in Sources */, C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */, @@ -9913,6 +9921,14 @@ version = 3.3.0; }; }; + 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; + requirement = { + kind = exactVersion; + version = 0.9.17; + }; + }; 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; @@ -10045,6 +10061,11 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = RemoteMessaging; }; + 854007E62B57FC000001BD98 /* ZIPFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; + productName = ZIPFoundation; + }; 85875B6029912A9900115F05 /* SyncUI */ = { isa = XCSwiftPackageProductDependency; productName = SyncUI; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3746e44125..182990508e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -156,7 +156,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" @@ -170,6 +170,15 @@ "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", "version" : "1.1.1" } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "a3f5c2bae0f04b0bce9ef3c4ba6bd1031a0564c4", + "version" : "0.9.17" + } } ], "version" : 2 diff --git a/DuckDuckGo/FilePreviewHelper.swift b/DuckDuckGo/FilePreviewHelper.swift index f8f4009311..41bd3824e8 100644 --- a/DuckDuckGo/FilePreviewHelper.swift +++ b/DuckDuckGo/FilePreviewHelper.swift @@ -26,6 +26,8 @@ struct FilePreviewHelper { switch download.mimeType { case .passbook: return PassKitPreviewHelper(filePath, viewController: viewController) + case .multipass: + return ZippedPassKitPreviewHelper(filePath, viewController: viewController) default: return QuickLookPreviewHelper(filePath, viewController: viewController) } @@ -33,7 +35,7 @@ struct FilePreviewHelper { static func canAutoPreviewMIMEType(_ mimeType: MIMEType) -> Bool { switch mimeType { - case .passbook: + case .passbook, .multipass: return UIDevice.current.userInterfaceIdiom == .phone case .reality, .usdz, .calendar: diff --git a/DuckDuckGo/MIMEType.swift b/DuckDuckGo/MIMEType.swift index 863d5fe55e..29a7c47813 100644 --- a/DuckDuckGo/MIMEType.swift +++ b/DuckDuckGo/MIMEType.swift @@ -21,6 +21,7 @@ import Foundation enum MIMEType: String { case passbook = "application/vnd.apple.pkpass" + case multipass = "application/vnd.apple.pkpasses" case usdz = "model/vnd.usdz+zip" case reality = "model/vnd.reality" case octetStream = "application/octet-stream" diff --git a/DuckDuckGo/PassKitPreviewHelper.swift b/DuckDuckGo/PassKitPreviewHelper.swift index 3a734eee03..588f355d54 100644 --- a/DuckDuckGo/PassKitPreviewHelper.swift +++ b/DuckDuckGo/PassKitPreviewHelper.swift @@ -38,7 +38,7 @@ class PassKitPreviewHelper: FilePreview { viewController?.present(controller, animated: true) } } catch { - os_log("Can't present passkit: %s", type: .debug, error.localizedDescription) + os_log("Can't present passkit: %{public}s", type: .error, error.localizedDescription) } } } diff --git a/DuckDuckGo/ZippedPassKitPreviewHelper.swift b/DuckDuckGo/ZippedPassKitPreviewHelper.swift new file mode 100644 index 0000000000..0213ccc7d2 --- /dev/null +++ b/DuckDuckGo/ZippedPassKitPreviewHelper.swift @@ -0,0 +1,65 @@ +// +// ZippedPassKitPreviewHelper.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Common +import Foundation +import UIKit +import PassKit +import ZIPFoundation + +class ZippedPassKitPreviewHelper: FilePreview { + private weak var viewController: UIViewController? + private let filePath: URL + + required init(_ filePath: URL, viewController: UIViewController) { + self.filePath = filePath + self.viewController = viewController + } + + func preview() { + do { + let passes: [PKPass] = try extractDataEntriesFromZipAtFilePath(self.filePath).compactMap({ try? PKPass(data: $0) }) + if passes.count > 0, + let controller = PKAddPassesViewController(passes: passes) { + viewController?.present(controller, animated: true) + } else { + os_log("Can't present passkit: No valid passes in passes file", type: .error) + } + } catch { + os_log("Can't present passkit: %{public}s", type: .error, error.localizedDescription) + } + } + + func extractDataEntriesFromZipAtFilePath(_ zipPath: URL) throws -> [Data] { + var dataObjects = [Data]() + let archive = try Archive(url: zipPath, accessMode: .read) + try archive.forEach { entry in + var passData = Data() + _ = try archive.extract(entry, skipCRC32: true) { data in + passData.append(data) + } + + if passData.count > 0 { + dataObjects.append(passData) + } + } + + return dataObjects + } +}