From 4fbcfbfeecb1fd7fc798f196929edd2c63b7287c Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 17 Oct 2024 11:19:29 +0200 Subject: [PATCH 01/21] Autofill Credential Provider extension created --- .../AutofillCredentialProvider.entitlements | 8 + .../Base.lproj/MainInterface.storyboard | 57 ++++ .../CredentialProviderViewController.swift | 70 +++++ DuckDuckGo.xcodeproj/project.pbxproj | 256 +++++++++++++++++- 4 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 AutofillCredentialProvider/AutofillCredentialProvider.entitlements create mode 100644 AutofillCredentialProvider/Base.lproj/MainInterface.storyboard create mode 100644 AutofillCredentialProvider/CredentialProviderViewController.swift diff --git a/AutofillCredentialProvider/AutofillCredentialProvider.entitlements b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements new file mode 100644 index 0000000000..b50ab55a01 --- /dev/null +++ b/AutofillCredentialProvider/AutofillCredentialProvider.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + + diff --git a/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard b/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000000..255319b482 --- /dev/null +++ b/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutofillCredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProviderViewController.swift new file mode 100644 index 0000000000..60fe9c3c8e --- /dev/null +++ b/AutofillCredentialProvider/CredentialProviderViewController.swift @@ -0,0 +1,70 @@ +// +// CredentialProviderViewController.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 AuthenticationServices + +class CredentialProviderViewController: ASCredentialProviderViewController { + + /* + Prepare your UI to list available credentials for the user to choose from. The items in + 'serviceIdentifiers' describe the service the user is logging in to, so your extension can + prioritize the most relevant credentials in the list. + */ + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + } + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + let databaseIsUnlocked = true + if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } else { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) + } + } + */ + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } + + @IBAction func passwordSelected(_ sender: AnyObject?) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } + +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0f19b3d6bc..4ce7430e7b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -908,6 +908,10 @@ C1E42C7B2C5CD8AE00509204 /* AutofillCredentialsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */; }; C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */; }; C1EA86622C74CB8B00E8604D /* SyncPromoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */; }; + C1EF5B232CC0457B002980E6 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */; }; + C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */; }; + C1EF5B292CC0457B002980E6 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C1EF5B282CC0457B002980E6 /* Base */; }; + C1EF5B2E2CC0457B002980E6 /* AutofillCredentialProvider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C42A6924000032057B /* EmailAddressPromptView.swift */; }; C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */; }; C1F341C92A6926920032057B /* EmailAddressPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */; }; @@ -1261,6 +1265,13 @@ remoteGlobalIDString = B6DFE6CE2BC7E47500A9CE59; remoteInfo = SwiftLintToolBundle; }; + C1EF5B2C2CC0457B002980E6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1EF5B202CC0457B002980E6; + remoteInfo = AutofillCredentialProvider; + }; F143C2E91E4A4CD400CFDE3A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; @@ -1277,6 +1288,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + C1EF5B2E2CC0457B002980E6 /* AutofillCredentialProvider.appex in Embed App Extensions */, 85482D942462DCD100EDEDD1 /* OpenAction.appex in Embed App Extensions */, 8512EA5D24ED30D30073EE19 /* WidgetsExtension.appex in Embed App Extensions */, 8390447620BDCE10006461CD /* ShareExtension.appex in Embed App Extensions */, @@ -2717,6 +2729,13 @@ C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillCredentialsDebugViewController.swift; sourceTree = ""; }; C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoView.swift; sourceTree = ""; }; C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoViewModel.swift; sourceTree = ""; }; + C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AutofillCredentialProvider.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; + C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; + C1EF5B282CC0457B002980E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; }; + C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AutofillCredentialProviderAlpha Debug.entitlements"; sourceTree = ""; }; C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; }; C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; }; @@ -3140,6 +3159,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1EF5B1E2CC0457B002980E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1EF5B232CC0457B002980E6 /* AuthenticationServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F143C2E01E4A4CD400CFDE3A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -4172,6 +4199,7 @@ 025CCFE32582601C001CD5BB /* FingerprintingUITests */, 9825F9D9293F2E5F00F220F2 /* PerformanceTests */, 85D33FCC25C97B6E002B91A6 /* IntegrationTests */, + C1EF5B242CC0457B002980E6 /* AutofillCredentialProvider */, F1AA545F1E48D90700223211 /* Frameworks */, 31E69A60280F4BAD00478327 /* LocalPackages */, 84E341931E2F7EFB00BDBA6F /* Products */, @@ -4198,6 +4226,7 @@ 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */, 02025662298818B100E694E7 /* PacketTunnelProvider.appex */, B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */, + C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */, ); name = Products; sourceTree = ""; @@ -5220,6 +5249,18 @@ name = Promotion; sourceTree = ""; }; + C1EF5B242CC0457B002980E6 /* AutofillCredentialProvider */ = { + isa = PBXGroup; + children = ( + C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, + C1EF5B272CC0457B002980E6 /* MainInterface.storyboard */, + C1EF5B2A2CC0457B002980E6 /* Info.plist */, + C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */, + C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */, + ); + path = AutofillCredentialProvider; + sourceTree = ""; + }; C1F341C32A6923D70032057B /* EmailAddressPrompt */ = { isa = PBXGroup; children = ( @@ -6090,6 +6131,7 @@ 8512EA4E24ED30D20073EE19 /* WidgetKit.framework */, 8512EA5024ED30D20073EE19 /* SwiftUI.framework */, 02025663298818B100E694E7 /* NetworkExtension.framework */, + C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */, ); name = Frameworks; sourceTree = ""; @@ -6529,6 +6571,7 @@ 85482D932462DCD100EDEDD1 /* PBXTargetDependency */, 8512EA5C24ED30D30073EE19 /* PBXTargetDependency */, 02FFD7BC2A1FC8BE007BD7D1 /* PBXTargetDependency */, + C1EF5B2D2CC0457B002980E6 /* PBXTargetDependency */, ); name = DuckDuckGo; packageProductDependencies = ( @@ -6715,6 +6758,23 @@ productReference = B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */; productType = "com.apple.product-type.bundle"; }; + C1EF5B202CC0457B002980E6 /* AutofillCredentialProvider */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1EF5B332CC0457B002980E6 /* Build configuration list for PBXNativeTarget "AutofillCredentialProvider" */; + buildPhases = ( + C1EF5B1D2CC0457B002980E6 /* Sources */, + C1EF5B1E2CC0457B002980E6 /* Frameworks */, + C1EF5B1F2CC0457B002980E6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AutofillCredentialProvider; + productName = AutofillCredentialProvider; + productReference = C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */; + productType = "com.apple.product-type.app-extension"; + }; F143C2E31E4A4CD400CFDE3A /* Core */ = { isa = PBXNativeTarget; buildConfigurationList = F143C2ED1E4A4CD400CFDE3A /* Build configuration list for PBXNativeTarget "Core" */; @@ -6760,7 +6820,7 @@ 84E3418A1E2F7EFB00BDBA6F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1420; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1500; ORGANIZATIONNAME = DuckDuckGo; TargetAttributes = { @@ -6819,6 +6879,9 @@ B6DFE6CE2BC7E47500A9CE59 = { CreatedOnToolsVersion = 15.3; }; + C1EF5B202CC0457B002980E6 = { + CreatedOnToolsVersion = 15.4; + }; F143C2E31E4A4CD400CFDE3A = { CreatedOnToolsVersion = 8.2.1; LastSwiftMigration = 1020; @@ -6887,6 +6950,7 @@ 85482D872462DCD100EDEDD1 /* OpenAction */, 8512EA4C24ED30D20073EE19 /* WidgetsExtension */, 02025661298818B100E694E7 /* PacketTunnelProvider */, + C1EF5B202CC0457B002980E6 /* AutofillCredentialProvider */, F143C2E31E4A4CD400CFDE3A /* Core */, 98A54A8022AFCB2C00E541F4 /* Instruments */, 85F21DAC210F5E32002631A6 /* AtbUITests */, @@ -7121,6 +7185,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1EF5B1F2CC0457B002980E6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1EF5B292CC0457B002980E6 /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F143C2E21E4A4CD400CFDE3A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -8190,6 +8262,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1EF5B1D2CC0457B002980E6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F143C2DF1E4A4CD400CFDE3A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -8381,6 +8461,11 @@ target = B6DFE6CE2BC7E47500A9CE59 /* SwiftLintToolBundle */; targetProxy = B6DFE6D72BC7E49800A9CE59 /* PBXContainerItemProxy */; }; + C1EF5B2D2CC0457B002980E6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1EF5B202CC0457B002980E6 /* AutofillCredentialProvider */; + targetProxy = C1EF5B2C2CC0457B002980E6 /* PBXContainerItemProxy */; + }; F143C2EA1E4A4CD400CFDE3A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F143C2E31E4A4CD400CFDE3A /* Core */; @@ -8973,6 +9058,14 @@ name = OmniBar.xib; sourceTree = ""; }; + C1EF5B272CC0457B002980E6 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + C1EF5B282CC0457B002980E6 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; CB1143DC2AF6D4B600C1CCD3 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -9911,6 +10004,156 @@ }; name = Release; }; + C1EF5B2F2CC0457B002980E6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProvider.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HKE973VLUW; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AutofillCredentialProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AutofillCredentialProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 DuckDuckGo. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.AutofillCredentialProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C1EF5B302CC0457B002980E6 /* Alpha Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HKE973VLUW; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AutofillCredentialProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AutofillCredentialProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 DuckDuckGo. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.AutofillCredentialProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Alpha Debug"; + }; + C1EF5B312CC0457B002980E6 /* Alpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProvider.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AutofillCredentialProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AutofillCredentialProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 DuckDuckGo. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.AutofillCredentialProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Alpha; + }; + C1EF5B322CC0457B002980E6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProvider.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AutofillCredentialProvider/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AutofillCredentialProvider; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 DuckDuckGo. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.AutofillCredentialProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; D664C7DE2B28A0FD00CBFA76 /* Alpha Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */; @@ -10919,6 +11162,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C1EF5B332CC0457B002980E6 /* Build configuration list for PBXNativeTarget "AutofillCredentialProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1EF5B2F2CC0457B002980E6 /* Debug */, + C1EF5B302CC0457B002980E6 /* Alpha Debug */, + C1EF5B312CC0457B002980E6 /* Alpha */, + C1EF5B322CC0457B002980E6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F143C2ED1E4A4CD400CFDE3A /* Build configuration list for PBXNativeTarget "Core" */ = { isa = XCConfigurationList; buildConfigurations = ( From ac0061420e1b7b9eb6d09b022732b77f1d2c705d Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 17 Oct 2024 11:20:13 +0200 Subject: [PATCH 02/21] App groups & keychain sharing enabled on alpha debug variant --- ...llCredentialProviderAlpha Debug.entitlements | 17 +++++++++++++++++ AutofillCredentialProvider/Info.plist | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements create mode 100644 AutofillCredentialProvider/Info.plist diff --git a/AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements b/AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements new file mode 100644 index 0000000000..9fd2beb2f7 --- /dev/null +++ b/AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements @@ -0,0 +1,17 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.application-groups + + $(GROUP_ID_PREFIX).vault + + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(VAULT_APP_GROUP) + + + diff --git a/AutofillCredentialProvider/Info.plist b/AutofillCredentialProvider/Info.plist new file mode 100644 index 0000000000..b49a6b752d --- /dev/null +++ b/AutofillCredentialProvider/Info.plist @@ -0,0 +1,17 @@ + + + + + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + + DuckDuckGoGroupIdentifierPrefix + $(GROUP_ID_PREFIX) + VAULT_APP_GROUP + $(AppIdentifierPrefix)$(VAULT_APP_GROUP) + + From 29802919613bfc32d21b0e857345a81dc4bda899 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 17 Oct 2024 11:42:47 +0200 Subject: [PATCH 03/21] Extension scheme created and set up pointing to Alpha Debug --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +- .../AutofillCredentialProvider.xcscheme | 101 ++++++++++++++++++ DuckDuckGo/DuckDuckGoAlpha Debug.entitlements | 30 ++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AutofillCredentialProvider.xcscheme create mode 100644 DuckDuckGo/DuckDuckGoAlpha Debug.entitlements diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4ce7430e7b..205dc7a545 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2736,6 +2736,7 @@ C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; }; C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AutofillCredentialProviderAlpha Debug.entitlements"; sourceTree = ""; }; + C1EF5B382CC110DD002980E6 /* DuckDuckGoAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "DuckDuckGoAlpha Debug.entitlements"; sourceTree = ""; }; C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; }; C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; }; @@ -4252,6 +4253,7 @@ D63FF8922C1B67D1006DE24D /* DuckPlayer */, F143C2C51E4A08F300CFDE3A /* DuckDuckGo.entitlements */, EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */, + C1EF5B382CC110DD002980E6 /* DuckDuckGoAlpha Debug.entitlements */, C159DF052A430B36007834BB /* EmailProtection */, 3157B43627F4C8380042D3D7 /* Favicons */, 839F119520DBC489007CD8C2 /* Feedback */, @@ -10221,7 +10223,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; - CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; + CODE_SIGN_ENTITLEMENTS = "DuckDuckGo/DuckDuckGoAlpha Debug.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 2; diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AutofillCredentialProvider.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AutofillCredentialProvider.xcscheme new file mode 100644 index 0000000000..02ee5661e7 --- /dev/null +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/AutofillCredentialProvider.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/DuckDuckGoAlpha Debug.entitlements b/DuckDuckGo/DuckDuckGoAlpha Debug.entitlements new file mode 100644 index 0000000000..446452b1fb --- /dev/null +++ b/DuckDuckGo/DuckDuckGoAlpha Debug.entitlements @@ -0,0 +1,30 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.developer.web-browser + + com.apple.security.application-groups + + $(GROUP_ID_PREFIX).app-configuration + $(GROUP_ID_PREFIX).bookmarks + $(GROUP_ID_PREFIX).contentblocker + $(GROUP_ID_PREFIX).database + $(GROUP_ID_PREFIX).netp + $(GROUP_ID_PREFIX).statistics + $(GROUP_ID_PREFIX).vault + + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + $(AppIdentifierPrefix)$(VAULT_APP_GROUP) + + + From e941cb3dafd23b54dbe712b1b7e940cd40640849 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 21 Oct 2024 15:45:26 +0200 Subject: [PATCH 04/21] Keychain access group for secure vault added --- Configuration/Configuration-Alpha.xcconfig | 3 +++ DuckDuckGo/Info.plist | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig index 73ad56cfdc..ba21ec9797 100644 --- a/Configuration/Configuration-Alpha.xcconfig +++ b/Configuration/Configuration-Alpha.xcconfig @@ -30,5 +30,8 @@ GROUP_ID_PREFIX = group.com.duckduckgo.alpha // The keychain access group for subscriptions SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha +// The keychain access group for secure vault +VAULT_APP_GROUP = com.duckduckgo.vault.alpha + // Prevents asserts from crashing alpha TF builds OTHER_SWIFT_FLAGS[config=Alpha][arch=*][sdk=*] = $(inherited) -assert-config Release diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 6d3941b0ca..6c99519208 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -244,5 +244,7 @@ https://duckduckgo.com UIViewControllerBasedStatusBarAppearance + VAULT_APP_GROUP + $(AppIdentifierPrefix)$(VAULT_APP_GROUP) From 9b13cc87797a72eb5a79b54c7e42ec5f525b444c Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 21 Oct 2024 16:11:37 +0200 Subject: [PATCH 05/21] Point to BSK commit --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/swiftpm/Package.resolved | 212 ------------------ 2 files changed, 2 insertions(+), 214 deletions(-) delete mode 100644 DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 205dc7a545..807e5f1578 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -11217,8 +11217,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 200.0.0; + kind = revision; + revision = 0565fcd75fffe71796bed2b4661787d94fb67576; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 3a477518cb..0000000000 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,212 +0,0 @@ -{ - "pins" : [ - { - "identity" : "apple-toolbox", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/apple-toolbox.git", - "state" : { - "revision" : "0c13c5f056805f2d403618ccc3bfb833c303c68d", - "version" : "3.1.2" - } - }, - { - "identity" : "barebonesbrowser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", - "state" : { - "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", - "version" : "0.1.0" - } - }, - { - "identity" : "bloom_cpp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/bloom_cpp.git", - "state" : { - "revision" : "8076199456290b61b4544bf2f4caf296759906a0", - "version" : "3.0.0" - } - }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", - "state" : { - "revision" : "9f62aacd878a0c05bff7256eb25b8776aa4e917f", - "version" : "200.0.0" - } - }, - { - "identity" : "content-scope-scripts", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/content-scope-scripts", - "state" : { - "revision" : "1ed569676555d493c9c5575eaed22aa02569aac9", - "version" : "6.19.0" - } - }, - { - "identity" : "designresourceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/DesignResourcesKit", - "state" : { - "revision" : "ad133f76501edcb2bfa841e33aebc0da5f92bb5c", - "version" : "3.3.0" - } - }, - { - "identity" : "duckduckgo-autofill", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", - "state" : { - "revision" : "945ac09a0189dc6736db617867fde193ea984b20", - "version" : "15.0.0" - } - }, - { - "identity" : "grdb.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/GRDB.swift.git", - "state" : { - "revision" : "4225b85c9a0c50544e413a1ea1e502c802b44b35", - "version" : "2.4.0" - } - }, - { - "identity" : "gzipswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/1024jp/GzipSwift.git", - "state" : { - "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", - "version" : "6.0.1" - } - }, - { - "identity" : "ios-js-support", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/ios-js-support", - "state" : { - "revision" : "6a6789ac8104a587316c58af75539753853b50d9", - "version" : "2.0.0" - } - }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", - "version" : "7.12.0" - } - }, - { - "identity" : "lottie-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm.git", - "state" : { - "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", - "version" : "4.4.3" - } - }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, - { - "identity" : "privacy-dashboard", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/privacy-dashboard", - "state" : { - "revision" : "9de2b2aa317a48d3ee31116dc15b0feeb2cc9414", - "version" : "5.3.0" - } - }, - { - "identity" : "punycodeswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gumob/PunycodeSwift.git", - "state" : { - "revision" : "30a462bdb4398ea835a3585472229e0d74b36ba5", - "version" : "3.0.0" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", - "state" : { - "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", - "version" : "1.4.0" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", - "version" : "509.1.1" - } - }, - { - "identity" : "swifter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/httpswift/swifter.git", - "state" : { - "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version" : "1.5.0" - } - }, - { - "identity" : "swiftsoup", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scinfu/SwiftSoup", - "state" : { - "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", - "version" : "2.7.2" - } - }, - { - "identity" : "sync_crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/sync_crypto", - "state" : { - "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", - "version" : "0.2.0" - } - }, - { - "identity" : "trackerradarkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", - "state" : { - "revision" : "5de0a610a7927b638a5fd463a53032c9934a2c3b", - "version" : "3.0.0" - } - }, - { - "identity" : "wireguard-apple", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/wireguard-apple", - "state" : { - "revision" : "13fd026384b1af11048451061cc1b21434990668", - "version" : "1.1.3" - } - }, - { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weichsel/ZIPFoundation.git", - "state" : { - "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version" : "0.9.19" - } - } - ], - "version" : 2 -} From 8e8f29aa702f48628a9eb2a128c49d9506a33945 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 21 Oct 2024 16:16:39 +0200 Subject: [PATCH 06/21] Update BSK reference --- .../xcshareddata/swiftpm/Package.resolved | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..2c48c29150 --- /dev/null +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,211 @@ +{ + "pins" : [ + { + "identity" : "apple-toolbox", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/apple-toolbox.git", + "state" : { + "revision" : "0c13c5f056805f2d403618ccc3bfb833c303c68d", + "version" : "3.1.2" + } + }, + { + "identity" : "barebonesbrowser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", + "state" : { + "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", + "version" : "0.1.0" + } + }, + { + "identity" : "bloom_cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/bloom_cpp.git", + "state" : { + "revision" : "8076199456290b61b4544bf2f4caf296759906a0", + "version" : "3.0.0" + } + }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", + "state" : { + "revision" : "0565fcd75fffe71796bed2b4661787d94fb67576" + } + }, + { + "identity" : "content-scope-scripts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/content-scope-scripts", + "state" : { + "revision" : "1ed569676555d493c9c5575eaed22aa02569aac9", + "version" : "6.19.0" + } + }, + { + "identity" : "designresourceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/DesignResourcesKit", + "state" : { + "revision" : "ad133f76501edcb2bfa841e33aebc0da5f92bb5c", + "version" : "3.3.0" + } + }, + { + "identity" : "duckduckgo-autofill", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state" : { + "revision" : "945ac09a0189dc6736db617867fde193ea984b20", + "version" : "15.0.0" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/GRDB.swift.git", + "state" : { + "revision" : "4225b85c9a0c50544e413a1ea1e502c802b44b35", + "version" : "2.4.0" + } + }, + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift.git", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "ios-js-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/ios-js-support", + "state" : { + "revision" : "6a6789ac8104a587316c58af75539753853b50d9", + "version" : "2.0.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", + "version" : "7.12.0" + } + }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", + "version" : "4.4.3" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "privacy-dashboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/privacy-dashboard", + "state" : { + "revision" : "9de2b2aa317a48d3ee31116dc15b0feeb2cc9414", + "version" : "5.3.0" + } + }, + { + "identity" : "punycodeswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gumob/PunycodeSwift.git", + "state" : { + "revision" : "30a462bdb4398ea835a3585472229e0d74b36ba5", + "version" : "3.0.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter.git", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup", + "state" : { + "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", + "version" : "2.7.2" + } + }, + { + "identity" : "sync_crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/sync_crypto", + "state" : { + "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version" : "0.2.0" + } + }, + { + "identity" : "trackerradarkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "state" : { + "revision" : "5de0a610a7927b638a5fd463a53032c9934a2c3b", + "version" : "3.0.0" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/wireguard-apple", + "state" : { + "revision" : "13fd026384b1af11048451061cc1b21434990668", + "version" : "1.1.3" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" + } + } + ], + "version" : 2 +} From 555417b1c8101d8a44f5beafaf9f0110ceb63144 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Sun, 24 Nov 2024 17:46:43 +0100 Subject: [PATCH 07/21] Update BSK reference --- DuckDuckGo.xcodeproj/project.pbxproj | 3 +-- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 38cc5a5e16..f24bcba3d2 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2767,7 +2767,6 @@ C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; }; C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AutofillCredentialProviderAlpha Debug.entitlements"; sourceTree = ""; }; - C1EF5B382CC110DD002980E6 /* DuckDuckGoAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "DuckDuckGoAlpha Debug.entitlements"; sourceTree = ""; }; C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; }; C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; }; @@ -11301,7 +11300,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = revision; - revision = 0565fcd75fffe71796bed2b4661787d94fb67576; + revision = 0cf4772295ec4cfab48f98cf1da0eefe89f78e2a; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 99e58d2d10..4f79b2f929 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "deacf613553334f35053a2092d4e062e4775544c", - "version" : "211.1.2" + "revision" : "0cf4772295ec4cfab48f98cf1da0eefe89f78e2a" } }, { From c7eaa44f72516f18a4e8ef8b9d3adf02551ca924 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 28 Nov 2024 08:51:01 +0100 Subject: [PATCH 08/21] Alpha entitlements file cleanup, deleting the now unnecessary debug version and adding vault to the entitlement groups --- DuckDuckGo/DuckDuckGoAlpha.entitlements | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DuckDuckGoAlpha.entitlements b/DuckDuckGo/DuckDuckGoAlpha.entitlements index b8debe8f31..60338d9de6 100644 --- a/DuckDuckGo/DuckDuckGoAlpha.entitlements +++ b/DuckDuckGo/DuckDuckGoAlpha.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.authentication-services.autofill-credential-provider + com.apple.developer.networking.networkextension packet-tunnel-provider @@ -10,17 +12,19 @@ com.apple.security.application-groups + group.com.duckduckgo.alpha.app-configuration group.com.duckduckgo.alpha.bookmarks group.com.duckduckgo.alpha.contentblocker group.com.duckduckgo.alpha.database group.com.duckduckgo.alpha.netp group.com.duckduckgo.alpha.statistics - group.com.duckduckgo.alpha.app-configuration + group.com.duckduckgo.alpha.vault keychain-access-groups $(AppIdentifierPrefix)$(APP_ID) $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + $(AppIdentifierPrefix)$(VAULT_APP_GROUP) From 7d5365299abaab9334387167a531eea9d79c41e0 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Thu, 28 Nov 2024 15:40:41 +0100 Subject: [PATCH 09/21] Extension entitlements tidy up for Alpha / Alpha Debug target --- ...=> AutofillCredentialProviderAlpha.entitlements} | 0 DuckDuckGo.xcodeproj/project.pbxproj | 13 +++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) rename AutofillCredentialProvider/{AutofillCredentialProviderAlpha Debug.entitlements => AutofillCredentialProviderAlpha.entitlements} (100%) diff --git a/AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements b/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements similarity index 100% rename from AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements rename to AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f24bcba3d2..78baef13d6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2753,6 +2753,7 @@ C1BF0BA429B63D7200482B73 /* AutofillLoginPromptHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptHelper.swift; sourceTree = ""; }; C1BF0BA729B63E1A00482B73 /* AutofillLoginPromptViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModelTests.swift; sourceTree = ""; }; C1BF26142C74D10F00F6405E /* SyncPromoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoManager.swift; sourceTree = ""; }; + C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProviderAlpha.entitlements; sourceTree = ""; }; C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManager.swift; sourceTree = ""; }; C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManagerTests.swift; sourceTree = ""; }; C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = ""; }; @@ -2766,7 +2767,6 @@ C1EF5B282CC0457B002980E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; }; - C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AutofillCredentialProviderAlpha Debug.entitlements"; sourceTree = ""; }; C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewModel.swift; sourceTree = ""; }; C1F341C82A6926920032057B /* EmailAddressPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptViewController.swift; sourceTree = ""; }; @@ -5344,10 +5344,10 @@ C1EF5B242CC0457B002980E6 /* AutofillCredentialProvider */ = { isa = PBXGroup; children = ( + C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */, C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, C1EF5B272CC0457B002980E6 /* MainInterface.storyboard */, C1EF5B2A2CC0457B002980E6 /* Info.plist */, - C1EF5B362CC04C1D002980E6 /* AutofillCredentialProviderAlpha Debug.entitlements */, C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */, ); path = AutofillCredentialProvider; @@ -10134,7 +10134,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = "AutofillCredentialProvider/AutofillCredentialProviderAlpha Debug.entitlements"; + CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -10152,7 +10152,7 @@ "@executable_path/../../Frameworks", ); MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.AutofillCredentialProvider; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.CredentialExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -10170,7 +10170,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProvider.entitlements; + CODE_SIGN_ENTITLEMENTS = AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; @@ -10191,8 +10191,9 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.AutofillCredentialProvider; + PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.alpha.CredentialExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AdHoc com.duckduckgo.mobile.ios.alpha.CredentialExtension"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; From 954d8eca8735ce66fecd7c5eea944e55ceae0c41 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 14:52:18 +0100 Subject: [PATCH 10/21] Get rid of storyboard + set up folder structure --- .../Base.lproj/MainInterface.storyboard | 57 ------------------- .../CredentialProviderViewController.swift | 6 ++ AutofillCredentialProvider/Info.plist | 13 +++-- DuckDuckGo.xcodeproj/project.pbxproj | 14 +---- 4 files changed, 17 insertions(+), 73 deletions(-) delete mode 100644 AutofillCredentialProvider/Base.lproj/MainInterface.storyboard rename AutofillCredentialProvider/{ => CredentialProvider}/CredentialProviderViewController.swift (96%) diff --git a/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard b/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard deleted file mode 100644 index 255319b482..0000000000 --- a/AutofillCredentialProvider/Base.lproj/MainInterface.storyboard +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AutofillCredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift similarity index 96% rename from AutofillCredentialProvider/CredentialProviderViewController.swift rename to AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift index 60fe9c3c8e..a5faaa9f39 100644 --- a/AutofillCredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -18,9 +18,15 @@ // import AuthenticationServices +import SwiftUI +import Core class CredentialProviderViewController: ASCredentialProviderViewController { + private struct Constants { + static let openPasswords = AppDeepLinkSchemes.openPasswords.url + } + /* Prepare your UI to list available credentials for the user to choose from. The items in 'serviceIdentifiers' describe the service the user is logging in to, so your extension can diff --git a/AutofillCredentialProvider/Info.plist b/AutofillCredentialProvider/Info.plist index b49a6b752d..9743c75265 100644 --- a/AutofillCredentialProvider/Info.plist +++ b/AutofillCredentialProvider/Info.plist @@ -2,15 +2,20 @@ + DuckDuckGoGroupIdentifierPrefix + $(GROUP_ID_PREFIX) NSExtension - NSExtensionMainStoryboard - MainInterface + NSExtensionAttributes + + ASCredentialProviderViewControllerClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController + NSExtensionPointIdentifier com.apple.authentication-services-credential-provider-ui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController - DuckDuckGoGroupIdentifierPrefix - $(GROUP_ID_PREFIX) VAULT_APP_GROUP $(AppIdentifierPrefix)$(VAULT_APP_GROUP) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 78baef13d6..ada52522e0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -938,7 +938,6 @@ C1EA86622C74CB8B00E8604D /* SyncPromoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */; }; C1EF5B232CC0457B002980E6 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */; }; C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */; }; - C1EF5B292CC0457B002980E6 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C1EF5B282CC0457B002980E6 /* Base */; }; C1EF5B2E2CC0457B002980E6 /* AutofillCredentialProvider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C42A6924000032057B /* EmailAddressPromptView.swift */; }; C1F341C72A6924100032057B /* EmailAddressPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C62A6924100032057B /* EmailAddressPromptViewModel.swift */; }; @@ -2764,7 +2763,6 @@ C1EF5B212CC0457B002980E6 /* AutofillCredentialProvider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AutofillCredentialProvider.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C1EF5B222CC0457B002980E6 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; - C1EF5B282CC0457B002980E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C1EF5B2A2CC0457B002980E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProvider.entitlements; sourceTree = ""; }; C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = ""; }; @@ -5344,9 +5342,8 @@ C1EF5B242CC0457B002980E6 /* AutofillCredentialProvider */ = { isa = PBXGroup; children = ( + C1CAAA6D2CF8BBBC00C37EE6 /* CredentialProvider */, C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */, - C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, - C1EF5B272CC0457B002980E6 /* MainInterface.storyboard */, C1EF5B2A2CC0457B002980E6 /* Info.plist */, C1EF5B2B2CC0457B002980E6 /* AutofillCredentialProvider.entitlements */, ); @@ -6860,6 +6857,7 @@ buildRules = ( ); dependencies = ( + C1CAAA7E2CF8C8ED00C37EE6 /* PBXTargetDependency */, ); name = AutofillCredentialProvider; productName = AutofillCredentialProvider; @@ -9144,14 +9142,6 @@ name = OmniBar.xib; sourceTree = ""; }; - C1EF5B272CC0457B002980E6 /* MainInterface.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C1EF5B282CC0457B002980E6 /* Base */, - ); - name = MainInterface.storyboard; - sourceTree = ""; - }; CB1143DC2AF6D4B600C1CCD3 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( From daf33dffbc7dea7ad9967d7e3f9daba5754936d6 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 14:54:24 +0100 Subject: [PATCH 11/21] Activation screen implemented --- .../CredentialProviderActivatedView.swift | 80 ++++++++++++++++++ ...CredentialProviderActivatedViewModel.swift | 39 +++++++++ .../CredentialProviderViewController.swift | 13 +++ .../Extensions/UIResponderExtension.swift | 39 +++++++++ .../UIViewControllerExtension.swift | 32 +++++++ .../Resources/Assets.xcassets/Contents.json | 6 ++ .../Contents.json | 12 +++ .../Passwords-DDG-96x96.svg | 26 ++++++ .../Resources/UserText.swift | 31 +++++++ AutofillCredentialProvider/Info.plist | 2 + DuckDuckGo.xcodeproj/project.pbxproj | 84 ++++++++++++++++++- DuckDuckGo/Info.plist | 1 + 12 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift new file mode 100644 index 0000000000..b275adc73b --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift @@ -0,0 +1,80 @@ +// +// CredentialProviderActivatedView.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 SwiftUI +import DesignResourcesKit +import DuckUI + +struct CredentialProviderActivatedView: View { + + let viewModel: CredentialProviderActivatedViewModel + @State private var imageAppeared = false + + var body: some View { + NavigationView { + + VStack(spacing: 0) { + + Image(.passwordsDDG96X96) + .padding(.top, 48) + .scaleEffect(imageAppeared ? 1 : 0.7) + .animation( + .interpolatingSpring(stiffness: 170, damping: 10) + .delay(0.1), + value: imageAppeared + ) + .onAppear { + imageAppeared = true + } + + Text(UserText.credentialProviderActivatedTitle) + .daxTitle2() + .foregroundColor(Color(designSystemColor: .textPrimary)) + .padding(.top, 16) + .multilineTextAlignment(.center) + + Text(UserText.credentialProviderActivatedSubtitle) + .daxBodyRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + .padding(.top, 8) + .multilineTextAlignment(.center) + + Spacer() + + Button { + viewModel.launchDDGApp() + } label: { + Text(UserText.credentialProviderActivatedButton) + } + .buttonStyle(PrimaryButtonStyle()) + .padding(.bottom, 12) + + } + .padding(.horizontal, 24) + .navigationBarItems(trailing: Button(UserText.actionCancel) { + viewModel.dismiss() + }) + } + } + +} + +//#Preview { +// CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel) +//} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift new file mode 100644 index 0000000000..759683d555 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedViewModel.swift @@ -0,0 +1,39 @@ +// +// CredentialProviderActivatedViewModel.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 Foundation + +struct CredentialProviderActivatedViewModel { + + typealias LaunchAppCompletion = (_ shouldLaunchApp: Bool) -> Void + + let completion: LaunchAppCompletion? + + init(completion: LaunchAppCompletion? = nil) { + self.completion = completion + } + + func dismiss() { + completion?(false) + } + + func launchDDGApp() { + completion?(true) + } +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift index a5faaa9f39..21050d9690 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -27,6 +27,19 @@ class CredentialProviderViewController: ASCredentialProviderViewController { static let openPasswords = AppDeepLinkSchemes.openPasswords.url } + override func prepareInterfaceForExtensionConfiguration() { + let viewModel = CredentialProviderActivatedViewModel { [weak self] shouldLaunchApp in + if shouldLaunchApp { + self?.openUrl(Constants.openPasswords) + } + self?.extensionContext.completeExtensionConfigurationRequest() + } + + let view = CredentialProviderActivatedView(viewModel: viewModel) + let hostingController = UIHostingController(rootView: view) + installChildViewController(hostingController) + } + /* Prepare your UI to list available credentials for the user to choose from. The items in 'serviceIdentifiers' describe the service the user is logging in to, so your extension can diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift new file mode 100644 index 0000000000..850bbab8e9 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift @@ -0,0 +1,39 @@ +// +// UIResponderExtension.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 UIKit + +extension UIResponder { + + @discardableResult + func openUrl(_ url: URL?) -> Bool { + guard let url = url else { return false } + + var responder: UIResponder? = self + while let r = responder { + if let application = r as? UIApplication { + application.open(url, options: [:], completionHandler: nil) + return true + } + responder = r.next + } + + return false + } +} diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift new file mode 100644 index 0000000000..83f5a78799 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift @@ -0,0 +1,32 @@ +// +// UIViewControllerExtension.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 UIKit + +extension UIViewController { + + public func installChildViewController(_ childController: UIViewController) { + addChild(childController) + childController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + childController.view.frame = view.bounds + view.addSubview(childController.view) + childController.didMove(toParent: self) + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json new file mode 100644 index 0000000000..608aeb0457 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Passwords-DDG-96x96.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg new file mode 100644 index 0000000000..18521b98ac --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-DDG-96x96.imageset/Passwords-DDG-96x96.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift new file mode 100644 index 0000000000..e728b72015 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift @@ -0,0 +1,31 @@ +// +// UserText.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 Foundation + +final class UserText { + + static let credentialProviderActivatedTitle = NSLocalizedString("credential.provider.activated.title", value: "Autofill activated!", comment: "The title of the screen confirming DuckDuckGo can now be used for autofilling passwords") + + static let credentialProviderActivatedSubtitle = NSLocalizedString("credential.provider.activated.subtitle", value: "You can now autofill your DuckDuckGo passwords from anywhere.", comment: "The subtitle of the screen confirming DuckDuckGo can now be used for autofilling passwords") + + static let credentialProviderActivatedButton = NSLocalizedString("credential.provider.activated.button", value: "Open DuckDuckGo", comment: "Title of button to launch the DuckDuckGo app") + + static let actionCancel = NSLocalizedString("action.button.cancel", value: "Cancel", comment: "Cancel button title") +} diff --git a/AutofillCredentialProvider/Info.plist b/AutofillCredentialProvider/Info.plist index 9743c75265..cb9ba8ea30 100644 --- a/AutofillCredentialProvider/Info.plist +++ b/AutofillCredentialProvider/Info.plist @@ -10,6 +10,8 @@ ASCredentialProviderViewControllerClass $(PRODUCT_MODULE_NAME).CredentialProviderViewController + ASCredentialProviderExtensionShowsConfigurationUI + NSExtensionPointIdentifier com.apple.authentication-services-credential-provider-ui diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ada52522e0..463c31e88b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -929,6 +929,15 @@ C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF0BA429B63D7200482B73 /* AutofillLoginPromptHelper.swift */; }; C1BF0BA929B63E2200482B73 /* AutofillLoginPromptViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF0BA729B63E1A00482B73 /* AutofillLoginPromptViewModelTests.swift */; }; C1BF26152C74D10F00F6405E /* SyncPromoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BF26142C74D10F00F6405E /* SyncPromoManager.swift */; }; + C1CAAA6A2CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA692CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift */; }; + C1CAAA712CF8BC0B00C37EE6 /* UIViewControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */; }; + C1CAAA732CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA722CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift */; }; + C1CAAA782CF8BDF200C37EE6 /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA772CF8BDF200C37EE6 /* UserText.swift */; }; + C1CAAA7A2CF8BE0200C37EE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */; }; + C1CAAA7B2CF8C8ED00C37EE6 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F143C2E41E4A4CD400CFDE3A /* Core.framework */; }; + C1CAAA812CF8C8F400C37EE6 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA802CF8C8F400C37EE6 /* DuckUI */; }; + C1CAAA832CF8C8FF00C37EE6 /* DesignResourcesKit in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA822CF8C8FF00C37EE6 /* DesignResourcesKit */; }; + C1CAAA852CF8C9EA00C37EE6 /* UIResponderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */; }; C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */; }; C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */; }; C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */; }; @@ -1291,6 +1300,13 @@ remoteGlobalIDString = B6DFE6CE2BC7E47500A9CE59; remoteInfo = SwiftLintToolBundle; }; + C1CAAA7D2CF8C8ED00C37EE6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = F143C2E31E4A4CD400CFDE3A; + remoteInfo = Core; + }; C1EF5B2C2CC0457B002980E6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84E3418A1E2F7EFB00BDBA6F /* Project object */; @@ -2753,6 +2769,12 @@ C1BF0BA729B63E1A00482B73 /* AutofillLoginPromptViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModelTests.swift; sourceTree = ""; }; C1BF26142C74D10F00F6405E /* SyncPromoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoManager.swift; sourceTree = ""; }; C1CAAA672CF8B74200C37EE6 /* AutofillCredentialProviderAlpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutofillCredentialProviderAlpha.entitlements; sourceTree = ""; }; + C1CAAA692CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderActivatedView.swift; sourceTree = ""; }; + C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtension.swift; sourceTree = ""; }; + C1CAAA722CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderActivatedViewModel.swift; sourceTree = ""; }; + C1CAAA772CF8BDF200C37EE6 /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; + C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIResponderExtension.swift; sourceTree = ""; }; C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManager.swift; sourceTree = ""; }; C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManagerTests.swift; sourceTree = ""; }; C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = ""; }; @@ -3187,7 +3209,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1CAAA812CF8C8F400C37EE6 /* DuckUI in Frameworks */, C1EF5B232CC0457B002980E6 /* AuthenticationServices.framework in Frameworks */, + C1CAAA7B2CF8C8ED00C37EE6 /* Core.framework in Frameworks */, + C1CAAA832CF8C8FF00C37EE6 /* DesignResourcesKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5329,6 +5354,44 @@ name = EmailSignup; sourceTree = ""; }; + C1CAAA682CF8B8C400C37EE6 /* CredentialProviderActivation */ = { + isa = PBXGroup; + children = ( + C1CAAA692CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift */, + C1CAAA722CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift */, + ); + path = CredentialProviderActivation; + sourceTree = ""; + }; + C1CAAA6D2CF8BBBC00C37EE6 /* CredentialProvider */ = { + isa = PBXGroup; + children = ( + C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, + C1CAAA682CF8B8C400C37EE6 /* CredentialProviderActivation */, + C1CAAA6E2CF8BBC900C37EE6 /* Extensions */, + C1CAAA742CF8BDC400C37EE6 /* Resources */, + ); + path = CredentialProvider; + sourceTree = ""; + }; + C1CAAA6E2CF8BBC900C37EE6 /* Extensions */ = { + isa = PBXGroup; + children = ( + C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */, + C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + C1CAAA742CF8BDC400C37EE6 /* Resources */ = { + isa = PBXGroup; + children = ( + C1CAAA772CF8BDF200C37EE6 /* UserText.swift */, + C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; C1EA865E2C74CB5500E8604D /* Promotion */ = { isa = PBXGroup; children = ( @@ -7282,7 +7345,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1EF5B292CC0457B002980E6 /* Base in Resources */, + C1CAAA7A2CF8BE0200C37EE6 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8385,7 +8448,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1CAAA712CF8BC0B00C37EE6 /* UIViewControllerExtension.swift in Sources */, + C1CAAA6A2CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift in Sources */, + C1CAAA732CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift in Sources */, C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */, + C1CAAA852CF8C9EA00C37EE6 /* UIResponderExtension.swift in Sources */, + C1CAAA782CF8BDF200C37EE6 /* UserText.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8577,6 +8645,11 @@ target = B6DFE6CE2BC7E47500A9CE59 /* SwiftLintToolBundle */; targetProxy = B6DFE6D72BC7E49800A9CE59 /* PBXContainerItemProxy */; }; + C1CAAA7E2CF8C8ED00C37EE6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F143C2E31E4A4CD400CFDE3A /* Core */; + targetProxy = C1CAAA7D2CF8C8ED00C37EE6 /* PBXContainerItemProxy */; + }; C1EF5B2D2CC0457B002980E6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1EF5B202CC0457B002980E6 /* AutofillCredentialProvider */; @@ -11516,6 +11589,15 @@ package = C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; + C1CAAA802CF8C8F400C37EE6 /* DuckUI */ = { + isa = XCSwiftPackageProductDependency; + productName = DuckUI; + }; + C1CAAA822CF8C8FF00C37EE6 /* DesignResourcesKit */ = { + isa = XCSwiftPackageProductDependency; + package = F42D541B29DCA40B004C4FF1 /* XCRemoteSwiftPackageReference "DesignResourcesKit" */; + productName = DesignResourcesKit; + }; CB6CC7E32CD2529000320907 /* BrokenSitePrompt */ = { isa = XCSwiftPackageProductDependency; productName = BrokenSitePrompt; diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 6c99519208..c317eea0e5 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -157,6 +157,7 @@ ddgFavorites ddgQuickLink ddgAddFavorite + ddgOpenPasswords http https From d0764241fe70836b3470e885b84e00ceebcbba4c Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 14:56:58 +0100 Subject: [PATCH 12/21] Delete boilerplate sample code --- .../CredentialProviderViewController.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift index 21050d9690..89976fe81e 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -76,14 +76,5 @@ class CredentialProviderViewController: ASCredentialProviderViewController { override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { } */ - - @IBAction func cancel(_ sender: AnyObject?) { - self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) - } - - @IBAction func passwordSelected(_ sender: AnyObject?) { - let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") - self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) - } - + } From 77a9c12802d405f74441f85fe688d88986dade61 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 15:15:16 +0100 Subject: [PATCH 13/21] Refactored existing app code to move parts needed by credential extension to Core --- Core/ AutofillLoginListSectionType.swift | 44 +++++++++ .../AutofillLoginItem.swift | 50 +++++----- Core/AutofillLoginListSorting.swift | 65 +++++++++++++ Core/UserAuthenticator.swift | 95 +++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 14 ++- .../AuthConfirmationPromptViewModel.swift | 3 +- ...tofillCredentialsDebugViewController.swift | 3 +- .../AutofillListItemTableViewCell.swift | 9 +- .../AutofillLoginDetailsViewController.swift | 3 +- DuckDuckGo/AutofillLoginListViewModel.swift | 89 ++--------------- ...ofillLoginSettingsListViewController.swift | 4 +- DuckDuckGo/SyncSettingsViewController.swift | 3 +- 12 files changed, 262 insertions(+), 120 deletions(-) create mode 100644 Core/ AutofillLoginListSectionType.swift rename DuckDuckGo/AutofillLoginListItemViewModel.swift => Core/AutofillLoginItem.swift (57%) create mode 100644 Core/AutofillLoginListSorting.swift create mode 100644 Core/UserAuthenticator.swift diff --git a/Core/ AutofillLoginListSectionType.swift b/Core/ AutofillLoginListSectionType.swift new file mode 100644 index 0000000000..5c05d2f0e3 --- /dev/null +++ b/Core/ AutofillLoginListSectionType.swift @@ -0,0 +1,44 @@ +// +// AutofillLoginListSectionType.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 Foundation + +public enum AutofillLoginListSectionType: Comparable { + + case enableAutofill + case suggestions(title: String, items: [AutofillLoginItem]) + case credentials(title: String, items: [AutofillLoginItem]) + + public static func < (lhs: AutofillLoginListSectionType, rhs: AutofillLoginListSectionType) -> Bool { + if case .credentials(let leftTitle, _) = lhs, + case .credentials(let rightTitle, _) = rhs { + if leftTitle == miscSectionHeading { + return false + } else if rightTitle == miscSectionHeading { + return true + } + + return leftTitle.localizedCaseInsensitiveCompare(rightTitle) == .orderedAscending + } + return true + } + + public static let miscSectionHeading = "#" + +} diff --git a/DuckDuckGo/AutofillLoginListItemViewModel.swift b/Core/AutofillLoginItem.swift similarity index 57% rename from DuckDuckGo/AutofillLoginListItemViewModel.swift rename to Core/AutofillLoginItem.swift index 8bbc5c3f6f..2eb5e3b71a 100644 --- a/DuckDuckGo/AutofillLoginListItemViewModel.swift +++ b/Core/AutofillLoginItem.swift @@ -1,8 +1,8 @@ // -// AutofillLoginListItemViewModel.swift +// AutofillLoginItem.swift // DuckDuckGo // -// Copyright © 2022 DuckDuckGo. All rights reserved. +// 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. @@ -19,40 +19,44 @@ import Foundation import BrowserServicesKit -import UIKit import Common -final class AutofillLoginListItemViewModel: Identifiable, Hashable { - - var preferredFaviconLetters: String { +public struct AutofillLoginItem: Identifiable, Hashable { + + public let id = UUID() + public let account: SecureVaultModels.WebsiteAccount + public let title: String + public let subtitle: String + + public var preferredFaviconLetters: String { let accountName = self.account.name(tld: tld, autofillDomainNameUrlMatcher: urlMatcher) let accountTitle = (account.title?.isEmpty == false) ? account.title! : "#" return tld.eTLDplus1(accountName) ?? accountTitle } - - let account: SecureVaultModels.WebsiteAccount - let title: String - let subtitle: String - let id = UUID() - let tld: TLD - let urlMatcher: AutofillDomainNameUrlMatcher - - internal init(account: SecureVaultModels.WebsiteAccount, - tld: TLD, - autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, - autofillDomainNameUrlSort: AutofillDomainNameUrlSort) { + + private let tld: TLD + private let urlMatcher: AutofillDomainNameUrlMatcher + + public init(account: SecureVaultModels.WebsiteAccount, + tld: TLD, + autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: AutofillDomainNameUrlSort) { self.account = account self.tld = tld + self.urlMatcher = autofillDomainNameUrlMatcher self.title = account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher) self.subtitle = account.username ?? "" - self.urlMatcher = autofillDomainNameUrlMatcher } - - static func == (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool { + + public static func == (lhs: AutofillLoginItem, rhs: AutofillLoginItem) -> Bool { lhs.account.id == rhs.account.id } - - func hash(into hasher: inout Hasher) { + + static func < (lhs: AutofillLoginItem, rhs: AutofillLoginItem) -> Bool { + lhs.title < rhs.title + } + + public func hash(into hasher: inout Hasher) { hasher.combine(account.id) } } diff --git a/Core/AutofillLoginListSorting.swift b/Core/AutofillLoginListSorting.swift new file mode 100644 index 0000000000..cf1f9e4955 --- /dev/null +++ b/Core/AutofillLoginListSorting.swift @@ -0,0 +1,65 @@ +// +// AutofillLoginListSorting.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 Foundation +import BrowserServicesKit +import Common + +public extension Array where Element == SecureVaultModels.WebsiteAccount { + + func groupedByFirstLetter(tld: TLD, + autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: AutofillDomainNameUrlSort) + -> [String: [AutofillLoginItem]] { + reduce(into: [String: [AutofillLoginItem]]()) { result, account in + + // Unfortunetly, folding doesn't produce perfect results despite respecting the system locale + // E.g. Romainian should treat letters with diacritics as seperate letters, but folding doesn't + // Apple's own apps (e.g. contacts) seem to suffer from the same problem + let key: String + if let firstChar = autofillDomainNameUrlSort.firstCharacterForGrouping(account, tld: tld), + let deDistinctionedChar = String(firstChar).folding(options: [.diacriticInsensitive, .caseInsensitive], locale: nil).first, + deDistinctionedChar.isLetter { + + key = String(deDistinctionedChar) + } else { + key = AutofillLoginListSectionType.miscSectionHeading + } + + return result[key, default: []].append(AutofillLoginItem(account: account, + tld: tld, + autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort)) + } + } +} + +public extension Dictionary where Key == String, Value == [AutofillLoginItem] { + + func sortedIntoSections(_ autofillDomainNameUrlSort: AutofillDomainNameUrlSort, + tld: TLD) -> [AutofillLoginListSectionType] { + map { dictionaryItem -> AutofillLoginListSectionType in + let sortedGroup = dictionaryItem.value.sorted(by: { + autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0.account, rhs: $1.account, tld: tld) == .orderedAscending + }) + return AutofillLoginListSectionType.credentials(title: dictionaryItem.key, + items: sortedGroup) + }.sorted() + } +} diff --git a/Core/UserAuthenticator.swift b/Core/UserAuthenticator.swift new file mode 100644 index 0000000000..efbeaf277d --- /dev/null +++ b/Core/UserAuthenticator.swift @@ -0,0 +1,95 @@ +// +// UserAuthenticator.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 Foundation +import LocalAuthentication +import os.log + +open class UserAuthenticator { + + public enum AuthError: Error, Equatable { + case noAuthAvailable + case failedToAuthenticate + } + + public enum AuthenticationState { + case loggedIn, loggedOut, notAvailable + } + + public struct Notifications { + public static let invalidateContext = Notification.Name("com.duckduckgo.app.UserAuthenticator.invalidateContext") + } + + private var context = LAContext() + private var reason: String + private var cancelTitle: String + @Published public private(set) var state = AuthenticationState.loggedOut + + public init(reason: String, cancelTitle: String) { + self.reason = reason + self.cancelTitle = cancelTitle + } + + public func logOut() { + state = .loggedOut + } + + public func canAuthenticate() -> Bool { + var error: NSError? + let canAuthenticate = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) + return canAuthenticate + } + + open func authenticate(completion: ((AuthError?) -> Void)? = nil) { + + if state == .loggedIn { + completion?(nil) + return + } + + context = LAContext() + context.localizedCancelTitle = cancelTitle + context.interactionNotAllowed = false + context.localizedReason = reason + + if canAuthenticate() { + let reason = reason + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { [weak self] success, error in + + DispatchQueue.main.async { + if success { + self?.state = .loggedIn + completion?(nil) + } else { + Logger.general.error("Failed to authenticate: \(error?.localizedDescription ?? "nil", privacy: .public)") + completion?(.failedToAuthenticate) + } + } + } + } else { + state = .notAvailable + completion?(.noAuthAvailable) + } + } + + public func invalidateContext() { + context.invalidate() + } + +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 463c31e88b..cc488f24f5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -193,7 +193,6 @@ 31DE43C42C2C60E800F8C51F /* DuckPlayerModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */; }; 31DE43C62C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = 31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */; }; 31E69A63280F4CB600478327 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 31E69A62280F4CB600478327 /* DuckUI */; }; - 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */; }; 3712091E2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */; }; 372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */; }; 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */; }; @@ -617,7 +616,6 @@ 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981FED75220464EF008488D7 /* AutoClearSettingsModel.swift */; }; 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820EAF422613CD30089094D /* WebProgressWorker.swift */; }; 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */; }; - 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */; }; 982123502B6D233E00F08C57 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234F2B6D233E00F08C57 /* UserSession.swift */; }; 9825F9DB293F2E8700F220F2 /* BookmarksTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */; }; 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982686AC2600C0850011A8D6 /* ActionMessageView.swift */; }; @@ -1546,7 +1544,6 @@ 31DE43C12C2C480D00F8C51F /* DuckPlayerFeaturePresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerFeaturePresentationView.swift; sourceTree = ""; }; 31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerModalPresenter.swift; sourceTree = ""; }; 31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "DuckPlayer-ModalAnimation.json"; sourceTree = ""; }; - 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListItemViewModel.swift; sourceTree = ""; }; 3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMessagingStoreErrorHandling.swift; sourceTree = ""; }; 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncMetricsEventsHandler.swift; sourceTree = ""; }; 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesDisplayModeStorage.swift; sourceTree = ""; }; @@ -1999,7 +1996,6 @@ 9820A5D522B1C0B20024E37C /* DDG Trace.tracetemplate */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "DDG Trace.tracetemplate"; sourceTree = ""; }; 9820EAF422613CD30089094D /* WebProgressWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebProgressWorker.swift; sourceTree = ""; }; 9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtension.swift; sourceTree = ""; }; - 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticator.swift; sourceTree = ""; }; 9821234F2B6D233E00F08C57 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTestData.swift; sourceTree = ""; }; @@ -3613,7 +3609,6 @@ children = ( 319A37132829A5450079FBCE /* Table */, 31584619281B08F5004ADB8B /* AutofillLoginListViewModel.swift */, - 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */, 311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */, ); name = List; @@ -6044,6 +6039,8 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( + C1CAAAA12CFCBBAC00C37EE6 /* Authenticator */, + C1CAAA962CFCAB8000C37EE6 /* Autofill */, 31B2F10D2C92FEB000CD30E3 /* MarketplaceAdPostback */, F1CE42A71ECA0A520074A8DF /* Bookmarks */, 837774491F8E1ECE00E17A29 /* ContentBlocker */, @@ -6385,7 +6382,6 @@ 983EABB7236198F6003948D1 /* DatabaseMigration.swift */, 853C5F6021C277C7001F7A05 /* global.swift */, 85C8E61C2B0E47380029A6BD /* BookmarksDatabaseSetup.swift */, - 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */, 9821234F2B6D233E00F08C57 /* UserSession.swift */, ); name = Application; @@ -7967,7 +7963,6 @@ 6FB2A6802C2EA950004D20C8 /* FavoritesViewModel.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, 9FE05CEE2C36424E00D9046B /* OnboardingPixelReporter.swift in Sources */, - 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, 310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */, 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */, CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */, @@ -8077,7 +8072,6 @@ D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */, 1DEAADF62BA4809400E25A97 /* CookiePopUpProtectionView.swift in Sources */, - 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, 3712091E2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */, @@ -8463,6 +8457,7 @@ files = ( 858479C92B8792D800D156C1 /* HistoryManager.swift in Sources */, F16393FF1ECCB9CC00DDD653 /* FileLoader.swift in Sources */, + C1CAAAA32CFCBBBD00C37EE6 /* UserAuthenticator.swift in Sources */, F1134EAB1F3E2C6A00B73467 /* StatisticsUserDefaults.swift in Sources */, 4B27FBB32C926E51007E21A7 /* PersistentPixel.swift in Sources */, CB258D1E29A52AF900DEBA24 /* FileStore.swift in Sources */, @@ -8479,6 +8474,7 @@ 9876B75E2232B36900D81D9F /* TabInstrumentation.swift in Sources */, 026DABA428242BC80089E0B5 /* MockUserAgent.swift in Sources */, 1E05D1D829C46EDA00BF9A1F /* TimedPixel.swift in Sources */, + C1CAAA9A2CFCAD3E00C37EE6 /* AutofillLoginItem.swift in Sources */, 6F9857342CD27FA2001BE9A0 /* BoolFileMarker.swift in Sources */, C14882DC27F2011C00D59F0C /* BookmarksImporter.swift in Sources */, CBAA195A27BFE15600A4BD49 /* NSManagedObjectContextExtension.swift in Sources */, @@ -8507,6 +8503,7 @@ EE9D68DE2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift in Sources */, 31B2F1112C92FEE000CD30E3 /* MarketplaceAdPostback.swift in Sources */, CB258D1F29A52B2500DEBA24 /* Configuration.swift in Sources */, + C1CAAA982CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift in Sources */, BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */, 98629D312C21765A001E6031 /* BookmarksStateValidation.swift in Sources */, 9847C00027A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift in Sources */, @@ -8516,6 +8513,7 @@ 830381C01F850AAF00863075 /* WKWebViewConfigurationExtension.swift in Sources */, 4B60ACA1252EC0B100E8D219 /* FullScreenVideoUserScript.swift in Sources */, F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */, + C1CAAA9C2CFCB39800C37EE6 /* AutofillLoginListSorting.swift in Sources */, 6F03CB072C32F173004179A8 /* PixelFiring.swift in Sources */, C14882DA27F2011C00D59F0C /* BookmarksExporter.swift in Sources */, 854858E32937BC550063610B /* CollectionExtension.swift in Sources */, diff --git a/DuckDuckGo/AuthConfirmationPromptViewModel.swift b/DuckDuckGo/AuthConfirmationPromptViewModel.swift index 0ac7cadc50..385f190d8d 100644 --- a/DuckDuckGo/AuthConfirmationPromptViewModel.swift +++ b/DuckDuckGo/AuthConfirmationPromptViewModel.swift @@ -29,7 +29,8 @@ protocol AuthConfirmationPromptViewModelDelegate: AnyObject { final class AuthConfirmationPromptViewModel: ObservableObject { weak var delegate: AuthConfirmationPromptViewModelDelegate? - private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillDeleteAllPasswordsAuthenticationReason) + private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillDeleteAllPasswordsAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) var contentHeight: CGFloat = AutofillViews.deleteAllPromptMinHeight { didSet { diff --git a/DuckDuckGo/AutofillCredentialsDebugViewController.swift b/DuckDuckGo/AutofillCredentialsDebugViewController.swift index 097c4d5e94..555d8971b6 100644 --- a/DuckDuckGo/AutofillCredentialsDebugViewController.swift +++ b/DuckDuckGo/AutofillCredentialsDebugViewController.swift @@ -87,7 +87,8 @@ class AutofillCredentialsDebugViewController: UITableViewController { private let tld: TLD = AppDependencyProvider.shared.storageCache.tld private let autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() private var credentials: [DisplayCredentials] = [] - private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) + private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) override func viewDidLoad() { super.viewDidLoad() diff --git a/DuckDuckGo/AutofillListItemTableViewCell.swift b/DuckDuckGo/AutofillListItemTableViewCell.swift index 209ed903d8..c1e7f90086 100644 --- a/DuckDuckGo/AutofillListItemTableViewCell.swift +++ b/DuckDuckGo/AutofillListItemTableViewCell.swift @@ -20,6 +20,7 @@ import UIKit import SwiftUI import DuckUI +import Core class AutofillListItemTableViewCell: UITableViewCell { @@ -78,12 +79,12 @@ class AutofillListItemTableViewCell: UITableViewCell { fatalError("init(coder:) has not been implemented") } - var viewModel: AutofillLoginListItemViewModel? { + var item: AutofillLoginItem? { didSet { - guard let viewModel = viewModel else { + guard let item = item else { return } - setupContentView(with: viewModel) + setupContentView(with: item) } } @@ -119,7 +120,7 @@ class AutofillListItemTableViewCell: UITableViewCell { ]) } - private func setupContentView(with item: AutofillLoginListItemViewModel) { + private func setupContentView(with item: AutofillLoginItem) { titleLabel.text = item.title subtitleLabel.text = item.subtitle iconImageView.loadFavicon(forDomain: item.account.domain, usingCache: .fireproof, preferredFakeFaviconLetters: item.preferredFaviconLetters) diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index 1a08045aa1..ae03ae302e 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -38,7 +38,8 @@ class AutofillLoginDetailsViewController: UIViewController { weak var delegate: AutofillLoginDetailsViewControllerDelegate? private let viewModel: AutofillLoginDetailsViewModel private var cancellables: Set = [] - private var authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) + private var authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) private let lockedView = AutofillItemsLockedView() private let noAuthAvailableView = AutofillNoAuthAvailableView() private var contentView: UIView? diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index c244f97752..714ce3333f 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -27,28 +27,6 @@ import DDGSync import PrivacyDashboard import os.log -internal enum AutofillLoginListSectionType: Comparable { - case enableAutofill - case suggestions(title: String, items: [AutofillLoginListItemViewModel]) - case credentials(title: String, items: [AutofillLoginListItemViewModel]) - - static func < (lhs: AutofillLoginListSectionType, rhs: AutofillLoginListSectionType) -> Bool { - if case .credentials(let leftTitle, _) = lhs, - case .credentials(let rightTitle, _) = rhs { - if leftTitle == miscSectionHeading { - return false - } else if rightTitle == miscSectionHeading { - return true - } - - return leftTitle.localizedCaseInsensitiveCompare(rightTitle) == .orderedAscending - } - return true - } - - static let miscSectionHeading = "#" -} - internal enum EnableAutofillRows: Int, CaseIterable { case toggleAutofill case resetNeverPromptWebsites @@ -69,7 +47,8 @@ final class AutofillLoginListViewModel: ObservableObject { static let tabUid = "com.duckduckgo.autofill.tab-uid" } - let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) + let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) var isSearching: Bool = false var isEditing: Bool = false { didSet { @@ -437,22 +416,22 @@ final class AutofillLoginListViewModel: ObservableObject { } if !accountsToSuggest.isEmpty { - let accountItems = accountsToSuggest.map { AutofillLoginListItemViewModel(account: $0, - tld: tld, - autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort) + let accountItems = accountsToSuggest.map { AutofillLoginItem(account: $0, + tld: tld, + autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) } newSections.append(.suggestions(title: UserText.autofillLoginListSuggested, items: accountItems)) } } - let viewModelsGroupedByFirstLetter = accounts.autofillLoginListItemViewModelsForAccountsGroupedByFirstLetter( + let viewModelsGroupedByFirstLetter = accounts.groupedByFirstLetter( tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, autofillDomainNameUrlSort: autofillDomainNameUrlSort) - let accountSections = viewModelsGroupedByFirstLetter.autofillLoginListSectionsForViewModelsSortedByTitle(autofillDomainNameUrlSort, - tld: tld) - + let accountSections = viewModelsGroupedByFirstLetter.sortedIntoSections(autofillDomainNameUrlSort, + tld: tld) + newSections.append(contentsOf: accountSections) return newSections } @@ -584,51 +563,3 @@ final class AutofillLoginListViewModel: ObservableObject { return true } } - -extension AutofillLoginListItemViewModel: Comparable { - static func < (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool { - lhs.title < rhs.title - } -} - -internal extension Array where Element == SecureVaultModels.WebsiteAccount { - - func autofillLoginListItemViewModelsForAccountsGroupedByFirstLetter(tld: TLD, - autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, - autofillDomainNameUrlSort: AutofillDomainNameUrlSort) - -> [String: [AutofillLoginListItemViewModel]] { - reduce(into: [String: [AutofillLoginListItemViewModel]]()) { result, account in - - // Unfortunetly, folding doesn't produce perfect results despite respecting the system locale - // E.g. Romainian should treat letters with diacritics as seperate letters, but folding doesn't - // Apple's own apps (e.g. contacts) seem to suffer from the same problem - let key: String - if let firstChar = autofillDomainNameUrlSort.firstCharacterForGrouping(account, tld: tld), - let deDistinctionedChar = String(firstChar).folding(options: [.diacriticInsensitive, .caseInsensitive], locale: nil).first, - deDistinctionedChar.isLetter { - - key = String(deDistinctionedChar) - } else { - key = AutofillLoginListSectionType.miscSectionHeading - } - - return result[key, default: []].append(AutofillLoginListItemViewModel(account: account, - tld: tld, - autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort)) - } - } -} - -internal extension Dictionary where Key == String, Value == [AutofillLoginListItemViewModel] { - - func autofillLoginListSectionsForViewModelsSortedByTitle(_ autofillDomainNameUrlSort: AutofillDomainNameUrlSort, tld: TLD) -> [AutofillLoginListSectionType] { - map { dictionaryItem -> AutofillLoginListSectionType in - let sortedGroup = dictionaryItem.value.sorted(by: { - autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0.account, rhs: $1.account, tld: tld) == .orderedAscending - }) - return AutofillLoginListSectionType.credentials(title: dictionaryItem.key, - items: sortedGroup) - }.sorted() - } -} diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index 49884b187b..2c0723b1dd 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -785,9 +785,9 @@ final class AutofillLoginSettingsListViewController: UIViewController { // MARK: Cell Methods - private func credentialCell(for tableView: UITableView, item: AutofillLoginListItemViewModel, indexPath: IndexPath) -> AutofillListItemTableViewCell { + private func credentialCell(for tableView: UITableView, item: AutofillLoginItem, indexPath: IndexPath) -> AutofillListItemTableViewCell { let cell = tableView.dequeueCell(ofType: AutofillListItemTableViewCell.self, for: indexPath) - cell.viewModel = item + cell.item = item cell.accessoryType = .disclosureIndicator cell.backgroundColor = UIColor(designSystemColor: .surface) return cell diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index 0c88d0719a..c71fc4d0f9 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -35,7 +35,8 @@ class SyncSettingsViewController: UIHostingController { let syncCredentialsAdapter: SyncCredentialsAdapter var connector: RemoteConnecting? - let userAuthenticator = UserAuthenticator(reason: UserText.syncUserUserAuthenticationReason) + let userAuthenticator = UserAuthenticator(reason: UserText.syncUserUserAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) let userSession = UserSession() var recoveryCode: String { From fa2c3c99d2b2233ca5897f43f8e0661445e61f8b Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 15:16:34 +0100 Subject: [PATCH 14/21] Support for credentials list with search, empty state, empty search state, authentication handling --- ...dentialProviderListItemTableViewCell.swift | 184 +++++++++ ...CredentialProviderListViewController.swift | 374 ++++++++++++++++++ .../CredentialProviderListViewModel.swift | 241 +++++++++++ .../EmptySearchView.swift | 92 +++++ .../CredentialProviderList/EmptyView.swift | 55 +++ .../CredentialProviderViewController.swift | 77 ++-- .../Extensions/UIColorExtension.swift | 67 ++++ .../UIViewControllerExtension.swift | 11 +- .../Extensions/UImageExtension.swift | 30 ++ .../AutofillLock.imageset/AutofillLock.pdf | Bin 0 -> 6605 bytes .../AutofillLock.imageset/Contents.json | 12 + .../Contents.json | 12 + .../Passwords-Add-96x96.svg | 19 + .../Resources/UserText.swift | 21 + .../Shared/AutofillPasswordFetcher.swift | 52 +++ .../Shared/LockScreenView.swift | 43 ++ .../Shared/SecureVaultReporter.swift | 44 +++ DuckDuckGo.xcodeproj/project.pbxproj | 84 +++- 18 files changed, 1378 insertions(+), 40 deletions(-) create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Contents.json create mode 100644 AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/Passwords-Add-96x96.imageset/Passwords-Add-96x96.svg create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/AutofillPasswordFetcher.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift create mode 100644 AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift new file mode 100644 index 0000000000..120a5e61d9 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift @@ -0,0 +1,184 @@ +// +// CredentialProviderListItemTableViewCell.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 UIKit +import Core +import DesignResourcesKit + +class CredentialProviderListItemTableViewCell: UITableViewCell { + + static var reuseIdentifier = "CredentialProviderListItemTableViewCell" + + private lazy var titleLabel: UILabel = { + let label = UILabel(frame: CGRect.zero) + label.font = .preferredFont(forTextStyle: .callout) + label.textColor = .init(designSystemColor: .textPrimary) + label.lineBreakMode = .byTruncatingMiddle + return label + }() + + private lazy var subtitleLabel: UILabel = { + let label = UILabel(frame: CGRect.zero) + label.font = .preferredFont(forTextStyle: .footnote) + label.textColor = .init(designSystemColor: .textPrimary) + label.lineBreakMode = .byTruncatingMiddle + return label + }() + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = true + imageView.layer.cornerRadius = 4 + return imageView + }() + + private lazy var textStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) + stackView.axis = .vertical + stackView.distribution = .fill + stackView.spacing = 3 + return stackView + }() + + private lazy var contentStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [iconImageView, textStackView]) + stackView.axis = .horizontal + stackView.spacing = 12 + stackView.alignment = .center + return stackView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + installSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var item: AutofillLoginItem? { + didSet { + guard let item = item else { + return + } + setupContentView(with: item) + } + } + + private func installSubviews() { + contentView.addSubview(contentStackView) + installConstraints() + } + + private func installConstraints() { + contentStackView.translatesAutoresizingMaskIntoConstraints = false + iconImageView.translatesAutoresizingMaskIntoConstraints = false + + let imageSize: CGFloat = 32 + let margins = contentView.layoutMarginsGuide + + NSLayoutConstraint.activate([ + iconImageView.widthAnchor.constraint(equalToConstant: imageSize), + iconImageView.heightAnchor.constraint(equalToConstant: imageSize), + + contentStackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor), + contentStackView.trailingAnchor.constraint(equalTo: margins.trailingAnchor), + contentStackView.topAnchor.constraint(equalTo: margins.topAnchor), + contentStackView.bottomAnchor.constraint(equalTo: margins.bottomAnchor) + ]) + } + + private func setupContentView(with item: AutofillLoginItem) { + titleLabel.text = item.title + subtitleLabel.text = item.subtitle + iconImageView.image = loadImageFromCache(forDomain: item.account.domain) + } + + override func layoutSubviews() { + super.layoutSubviews() + contentStackView.frame = contentView.bounds + + separatorInset = UIEdgeInsets(top: 0, left: contentView.layoutMargins.left + textStackView.frame.origin.x, bottom: 0, right: 0) + } + + + private func loadImageFromCache(forDomain domain: String?) -> UIImage? { + guard let domain = domain else { return nil } + + let key = FaviconHasher.createHash(ofDomain: domain) + guard let cacheUrl = FaviconsCacheType.fireproof.cacheLocation() else { return nil } + + // Slight leap here to avoid loading Kingisher as a library for the widgets. + // Once dependency management is fixed, link it and use Favicons directly. + let imageUrl = cacheUrl.appendingPathComponent("com.onevcat.Kingfisher.ImageCache.fireproof").appendingPathComponent(key) + + guard let data = (try? Data(contentsOf: imageUrl)) else { + let image = createFakeFavicon(forDomain: domain, size: 32, backgroundColor: UIColor.forDomain(domain), preferredFakeFaviconLetters: item?.preferredFaviconLetters) + return image + } + + return UIImage(data: data)?.toSRGB() + } + + private func createFakeFavicon(forDomain domain: String, + size: CGFloat = 192, + backgroundColor: UIColor = UIColor.red, + bold: Bool = true, + preferredFakeFaviconLetters: String? = nil, + letterCount: Int = 2) -> UIImage? { + + let cornerRadius = size * 0.125 + let imageRect = CGRect(x: 0, y: 0, width: size, height: size) + let padding = size * 0.16 + let labelFrame = CGRect(x: padding, y: padding, width: imageRect.width - (2 * padding), height: imageRect.height - (2 * padding)) + + let renderer = UIGraphicsImageRenderer(size: imageRect.size) + let icon = renderer.image { imageContext in + let context = imageContext.cgContext + + context.setFillColor(backgroundColor.cgColor) + context.addPath(CGPath(roundedRect: imageRect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)) + context.fillPath() + + let label = UILabel(frame: labelFrame) + label.numberOfLines = 1 + label.adjustsFontSizeToFitWidth = true + label.minimumScaleFactor = 0.1 + label.baselineAdjustment = .alignCenters + label.font = bold ? UIFont.boldSystemFont(ofSize: size) : UIFont.systemFont(ofSize: size) + label.textColor = .white + label.textAlignment = .center + + if let prefferedPrefix = preferredFakeFaviconLetters?.droppingWwwPrefix().prefix(letterCount).capitalized { + label.text = prefferedPrefix + } else { + label.text = item?.preferredFaviconLetters.capitalized ?? "#" + } + + context.translateBy(x: padding, y: padding) + + label.layer.draw(in: context) + } + + return icon.withRenderingMode(.alwaysOriginal) + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift new file mode 100644 index 0000000000..cd380b7fe7 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift @@ -0,0 +1,374 @@ +// +// CredentialProviderListViewController.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 UIKit +import AuthenticationServices +import BrowserServicesKit +import Combine +import Core +import SwiftUI + +class CredentialProviderListViewController: UIViewController { + + private let viewModel: CredentialProviderListViewModel + private let onRowSelected: (AutofillLoginItem) -> Void + private let onDismiss: () -> Void + private var cancellables: Set = [] + + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .insetGrouped) + tableView.delegate = self + tableView.dataSource = self + tableView.estimatedRowHeight = 60 + tableView.register(CredentialProviderListItemTableViewCell.self, forCellReuseIdentifier: CredentialProviderListItemTableViewCell.reuseIdentifier) + return tableView + }() + + private lazy var searchController: UISearchController = { + let searchController = UISearchController(searchResultsController: nil) + searchController.searchResultsUpdater = self + searchController.searchBar.delegate = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchBar.placeholder = UserText.autofillLoginListSearchPlaceholder + navigationItem.hidesSearchBarWhenScrolling = false + definesPresentationContext = true + + return searchController + }() + + private lazy var lockedView = { [weak self] in + let view = LockScreenView() + let hostingController = UIHostingController(rootView: view) + self?.installChildViewController(hostingController) + return hostingController.view ?? UIView() + }() + + private let emptySearchView = EmptySearchView() + + private lazy var emptyView: UIView = { [weak self] in + let emptyView = EmptyView() + + let hostingController = UIHostingController(rootView: emptyView) + self?.installChildViewController(hostingController) + hostingController.view.backgroundColor = .clear + return hostingController.view + }() + + private lazy var emptySearchViewCenterYConstraint: NSLayoutConstraint = { + NSLayoutConstraint(item: emptySearchView, + attribute: .centerY, + relatedBy: .equal, + toItem: tableView, + attribute: .top, + multiplier: 1, + constant: (tableView.frame.height / 2)) + }() + + + init(serviceIdentifiers: [ASCredentialServiceIdentifier], + secureVault: (any AutofillSecureVault)?, + onRowSelected: @escaping (AutofillLoginItem) -> Void, + onDismiss: @escaping () -> Void) { + self.viewModel = CredentialProviderListViewModel(serviceIdentifiers: serviceIdentifiers, secureVault: secureVault) + self.onRowSelected = onRowSelected + self.onDismiss = onDismiss + + super.init(nibName: nil, bundle: nil) + + authenticate() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = UserText.autofillLoginListTitle + + let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped)) + navigationItem.rightBarButtonItem = doneItem + + setupCancellables() + installSubviews() + installConstraints() + decorate() + updateViewState() + registerForKeyboardNotifications() + + navigationItem.searchController = searchController + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + viewModel.authenticateInvalidateContext() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { _ in + if !self.searchController.isActive { + self.navigationItem.searchController = nil + } + }, completion: { _ in + self.updateSearchController() + }) + } + + private func decorate() { + view.backgroundColor = UIColor(designSystemColor: .background) + tableView.backgroundColor = UIColor(designSystemColor: .background) + tableView.separatorColor = UIColor(designSystemColor: .lines) + tableView.sectionIndexColor = UIColor(designSystemColor: .accent) + + navigationController?.navigationBar.barTintColor = UIColor(designSystemColor: .panel) + navigationController?.navigationBar.tintColor = UIColor(designSystemColor: .textPrimary) + + let appearance = UINavigationBarAppearance() + appearance.shadowColor = .clear + appearance.backgroundColor = UIColor(designSystemColor: .background) + + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance + + tableView.reloadData() + } + + private func authenticate() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in + self?.viewModel.authenticate {[weak self] error in + guard let self = self else { return } + + if error != nil { + if error != .noAuthAvailable { + self.onDismiss() + } + } + } + } + } + + private func installSubviews() { + view.addSubview(tableView) + tableView.addSubview(emptySearchView) + view.addSubview(lockedView) + } + + private func installConstraints() { + tableView.translatesAutoresizingMaskIntoConstraints = false + emptySearchView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + tableView.leftAnchor.constraint(equalTo: view.leftAnchor), + tableView.rightAnchor.constraint(equalTo: view.rightAnchor), + tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + emptySearchView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor), + emptySearchViewCenterYConstraint, + emptySearchView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + emptySearchView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + ]) + } + + private func setupCancellables() { + viewModel.$viewState + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateViewState() + } + .store(in: &cancellables) + + viewModel.$sections + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.tableView.reloadData() + } + .store(in: &cancellables) + } + + private func updateViewState() { + + switch viewModel.viewState { + case .showItems: + tableView.isHidden = false + lockedView.isHidden = true + emptySearchView.isHidden = true + emptyView.isHidden = true + case .noAuthAvailable: + tableView.isHidden = true + lockedView.isHidden = true + emptySearchView.isHidden = true + emptyView.isHidden = true + case .authLocked: + tableView.isHidden = true + lockedView.isHidden = false + emptySearchView.isHidden = true + emptyView.isHidden = true + case .empty: + tableView.isHidden = true + lockedView.isHidden = true + emptySearchView.isHidden = true + emptyView.isHidden = false + case .searching: + tableView.isHidden = false + lockedView.isHidden = true + emptySearchView.isHidden = true + emptyView.isHidden = true + case .searchingNoResults: + tableView.isHidden = false + lockedView.isHidden = true + emptySearchView.isHidden = false + emptyView.isHidden = true + } + updateSearchController() + tableView.reloadData() + } + + private func updateSearchController() { + switch viewModel.viewState { + case .showItems: + if tableView.isEditing { + navigationItem.searchController = nil + } else { + navigationItem.searchController = searchController + } + case .searching, .searchingNoResults: + navigationItem.searchController = searchController + case .authLocked: + navigationItem.searchController = viewModel.hasAccountsSaved ? searchController : nil + case .empty, .noAuthAvailable: + navigationItem.searchController = nil + } + } + + @objc private func doneTapped() { + onDismiss() + } + +} + +extension CredentialProviderListViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + viewModel.sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + viewModel.rowsInSection(section) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch viewModel.sections[indexPath.section] { + case .suggestions(_, items: let items), .credentials(_, let items): + guard let cell = tableView.dequeueReusableCell(withIdentifier: CredentialProviderListItemTableViewCell.reuseIdentifier, + for: indexPath) as? CredentialProviderListItemTableViewCell else { + fatalError("Could not dequeue cell") + } + cell.item = items[indexPath.row] + cell.backgroundColor = UIColor(designSystemColor: .surface) + return cell + default: + return UITableViewCell() + } + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch viewModel.sections[section] { + case .suggestions(let title, _), .credentials(let title, _): + return title + default: + return nil + } + } + + func sectionIndexTitles(for tableView: UITableView) -> [String]? { + viewModel.viewState == .showItems ? UILocalizedIndexedCollation.current().sectionIndexTitles : [] + } + +} + +extension CredentialProviderListViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + switch viewModel.sections[indexPath.section] { + case .suggestions(_, items: let items), .credentials(_, let items): + let item = items[indexPath.row] + onRowSelected(item) + default: + break + } + } + +} + +extension CredentialProviderListViewController: UISearchResultsUpdating { + + func updateSearchResults(for searchController: UISearchController) { + viewModel.isSearching = searchController.isActive + + if let query = searchController.searchBar.text { + viewModel.filterData(with: query) + emptySearchView.query = query + tableView.reloadData() + } + } + +} + +extension CredentialProviderListViewController: UISearchBarDelegate { + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + viewModel.isSearching = false + + viewModel.filterData(with: "") + tableView.reloadData() + } + +} + +// MARK: Keyboard + +extension CredentialProviderListViewController { + + private func registerForKeyboardNotifications() { + NotificationCenter.default.addObserver(self, + selector: #selector(adjustForKeyboard), + name: UIResponder.keyboardWillChangeFrameNotification, + object: nil) + } + + @objc private func adjustForKeyboard(notification: NSNotification) { + guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { + return + } + + let keyboardScreenEndFrame = keyboardValue.cgRectValue + let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window) + + emptySearchViewCenterYConstraint.constant = min( + (keyboardViewEndFrame.minY + emptySearchView.frame.height) / 2 - searchController.searchBar.frame.height, + (tableView.frame.height / 2) - searchController.searchBar.frame.height + ) + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift new file mode 100644 index 0000000000..8a09eefea2 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift @@ -0,0 +1,241 @@ +// +// CredentialProviderListViewModel.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 Foundation +import AuthenticationServices +import BrowserServicesKit +import Combine +import Common +import Core +import os.log + +final class CredentialProviderListViewModel: ObservableObject { + + enum ViewState { + case authLocked + case noAuthAvailable + case empty + case showItems + case searching + case searchingNoResults + } + + var isSearching: Bool = false + var authenticationNotRequired = false + + private let serviceIdentifiers: [ASCredentialServiceIdentifier] + private let secureVault: (any AutofillSecureVault)? + private var accounts = [SecureVaultModels.WebsiteAccount]() + private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() + private var cancellables: Set = [] + private let tld: TLD = TLD() + private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() + private let autofillDomainNameUrlSort = AutofillDomainNameUrlSort() + + let authenticator = UserAuthenticator(reason: UserText.autofillLoginListAuthenticationReason, + cancelTitle: UserText.autofillLoginListAuthenticationCancelButton) + var hasAccountsSaved: Bool { + return !accounts.isEmpty + } + + @Published private(set) var viewState: CredentialProviderListViewModel.ViewState = .authLocked + @Published private(set) var sections = [AutofillLoginListSectionType]() { + didSet { + updateViewState() + } + } + + init(serviceIdentifiers: [ASCredentialServiceIdentifier], + secureVault: (any AutofillSecureVault)?) { + self.serviceIdentifiers = serviceIdentifiers + self.secureVault = secureVault + + if let count = getAccountsCount() { + authenticationNotRequired = count == 0 + } + updateData() + setupCancellables() + } + + private func getAccountsCount() -> Int? { + guard let secureVault = secureVault else { + return nil + } + + do { + return try secureVault.accountsCount() + } catch { + return nil + } + } + + private func fetchAccounts() -> [SecureVaultModels.WebsiteAccount] { + guard let secureVault = secureVault else { + return [] + } + + do { + let allAccounts = try secureVault.accounts() + return allAccounts + } catch { + Logger.autofill.error("Failed to fetch accounts \(error.localizedDescription, privacy: .public)") + return [] + } + } + + func updateData() { + self.accounts = fetchAccounts() + self.accountsToSuggest = fetchSuggestedAccounts() + self.sections = makeSections(with: accounts) + } + + func updateLastUsed(for account: SecureVaultModels.WebsiteAccount) { + guard let secureVault = secureVault, + let accountID = account.id, + let accountIdInt = Int64(accountID) else { + return + } + + try? secureVault.updateLastUsedFor(accountId: accountIdInt) + } + + private func setupCancellables() { + authenticator.$state + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateViewState() + } + .store(in: &cancellables) + } + + func authenticate(completion: @escaping (UserAuthenticator.AuthError?) -> Void) { + if !authenticator.canAuthenticate() { + viewState = .noAuthAvailable + completion(nil) + return + } + + if viewState != .authLocked { + completion(nil) + return + } + + authenticator.authenticate(completion: completion) + } + + func authenticateInvalidateContext() { + authenticator.invalidateContext() + } + + private func fetchSuggestedAccounts() -> [SecureVaultModels.WebsiteAccount] { + + var suggestedAccounts = [SecureVaultModels.WebsiteAccount]() + + serviceIdentifiers.compactMap { URL(string: $0.identifier) }.forEach { url in + suggestedAccounts += accounts.filter { account in + return autofillDomainNameUrlMatcher.isMatchingForAutofill( + currentSite: url.absoluteString, + savedSite: account.domain ?? "", + tld: tld + ) + } + } + + let sortedSuggestions = suggestedAccounts.sorted(by: { + autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0, rhs: $1, tld: tld) == .orderedAscending + }) + + return sortedSuggestions + } + + func filterData(with query: String? = nil) { + var filteredAccounts = self.accounts + + if let query = query, query.count > 0 { + filteredAccounts = filteredAccounts.filter { account in + if !account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher).lowercased().contains(query.lowercased()) && + !(account.domain ?? "").lowercased().contains(query.lowercased()) && + !(account.username ?? "").lowercased().contains(query.lowercased()) { + return false + } + return true + } + } + self.sections = makeSections(with: filteredAccounts) + } + + func rowsInSection(_ section: Int) -> Int { + switch self.sections[section] { + case .suggestions(_, let items): + return items.count + case .credentials(_, let items): + return items.count + default: + return 0 + } + } + + private func makeSections(with accounts: [SecureVaultModels.WebsiteAccount]) -> [AutofillLoginListSectionType] { + var newSections = [AutofillLoginListSectionType]() + + if !isSearching && !accountsToSuggest.isEmpty { + let accountItems = accountsToSuggest.map { AutofillLoginItem(account: $0, + tld: tld, + autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) + } + newSections.append(.suggestions(title: UserText.autofillLoginListSuggested, items: accountItems)) + } + + let viewModelsGroupedByFirstLetter = accounts.groupedByFirstLetter( + tld: tld, + autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) + let accountSections = viewModelsGroupedByFirstLetter.sortedIntoSections(autofillDomainNameUrlSort, + tld: tld) + + newSections.append(contentsOf: accountSections) + return newSections + } + + private func updateViewState() { + var newViewState: CredentialProviderListViewModel.ViewState + + if authenticator.state == .loggedOut && !authenticationNotRequired { + newViewState = .authLocked + } else if authenticator.state == .notAvailable { + newViewState = .noAuthAvailable + } else if isSearching { + if sections.count == 0 { + newViewState = .searchingNoResults + } else { + newViewState = .searching + } + } else { + newViewState = sections.count > 0 ? .showItems : .empty + } + + + // Avoid unnecessary updates + if newViewState != viewState { + viewState = newViewState + } + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift new file mode 100644 index 0000000000..5f0be33f2c --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptySearchView.swift @@ -0,0 +1,92 @@ +// +// EmptySearchView.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 UIKit +import DesignResourcesKit + +final class EmptySearchView: UIView { + + private lazy var title: UILabel = { + let label = UILabel(frame: CGRect.zero) + + label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2).pointSize * 1.091, weight: .regular) + label.text = UserText.autofillSearchNoResultTitle + label.numberOfLines = 0 + label.textAlignment = .center + label.lineBreakMode = .byWordWrapping + label.textColor = UIColor(designSystemColor: .textPrimary) + return label + }() + + private lazy var subtitle: UILabel = { + let label = UILabel(frame: CGRect.zero) + + label.font = .preferredFont(forTextStyle: .callout) + label.text = "" + label.numberOfLines = 0 + label.textAlignment = .center + label.lineBreakMode = .byWordWrapping + label.textColor = UIColor(designSystemColor: .textPrimary) + + return label + }() + + private lazy var stackContentView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [title, subtitle]) + stackView.axis = .vertical + stackView.spacing = 3 + return stackView + }() + + var query: String = "" { + didSet { + if query.count > 0 { + subtitle.text = UserText.autofillSearchNoResultSubtitle(for: query) + } else { + subtitle.text = "" + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + installSubviews() + installConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func installSubviews() { + addSubview(stackContentView) + } + + private func installConstraints() { + stackContentView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + stackContentView.centerXAnchor.constraint(equalTo: centerXAnchor), + stackContentView.centerYAnchor.constraint(equalTo: centerYAnchor), + heightAnchor.constraint(equalTo: stackContentView.heightAnchor), + widthAnchor.constraint(equalTo: stackContentView.widthAnchor) + ]) + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift new file mode 100644 index 0000000000..53bf6c0e9b --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/EmptyView.swift @@ -0,0 +1,55 @@ +// +// EmptyView.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 SwiftUI +import DesignResourcesKit + +struct EmptyView: View { + + var body: some View { + + VStack(spacing: 0) { + + Image(.passwordsAdd96X96) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 96, height: 96) + + Text(UserText.autofillEmptyViewTitle) + .daxTitle3() + .foregroundColor(Color(designSystemColor: .textPrimary)) + .padding(.top, 16) + .multilineTextAlignment(.center) + .lineLimit(nil) + + Text(UserText.autofillEmptyViewSubtitle) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .textSecondary)) + .multilineTextAlignment(.center) + .padding(.top, 8) + .lineLimit(nil) + + } + .frame(maxWidth: 300.0) + .padding(.bottom, 60) + }} + +#Preview { + EmptyView() +} diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift index 89976fe81e..e8cb10a3bf 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -19,14 +19,23 @@ import AuthenticationServices import SwiftUI +import BrowserServicesKit import Core class CredentialProviderViewController: ASCredentialProviderViewController { - + private struct Constants { static let openPasswords = AppDeepLinkSchemes.openPasswords.url } - + + private lazy var secureVault: (any AutofillSecureVault)? = { + return try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter()) + }() + + private lazy var passwordFetcher: CredentialFetcher = { + return CredentialFetcher(secureVault: secureVault) + }() + override func prepareInterfaceForExtensionConfiguration() { let viewModel = CredentialProviderActivatedViewModel { [weak self] shouldLaunchApp in if shouldLaunchApp { @@ -34,47 +43,41 @@ class CredentialProviderViewController: ASCredentialProviderViewController { } self?.extensionContext.completeExtensionConfigurationRequest() } - + let view = CredentialProviderActivatedView(viewModel: viewModel) let hostingController = UIHostingController(rootView: view) installChildViewController(hostingController) } - - /* - Prepare your UI to list available credentials for the user to choose from. The items in - 'serviceIdentifiers' describe the service the user is logging in to, so your extension can - prioritize the most relevant credentials in the list. - */ + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + loadCredentialsList(for: serviceIdentifiers) } - - /* - Implement this method if your extension supports showing credentials in the QuickType bar. - When the user selects a credential from your app, this method will be called with the - ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. - Provide the password by completing the extension request with the associated ASPasswordCredential. - If using the credential would require showing custom UI for authenticating the user, cancel - the request with error code ASExtensionError.userInteractionRequired. - - override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { - let databaseIsUnlocked = true - if (databaseIsUnlocked) { - let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") - self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) - } else { - self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) - } - } - */ - - /* - Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with - ASExtensionError.userInteractionRequired. In this case, the system may present your extension's - UI and call this method. Show appropriate UI for authenticating the user then provide the password - by completing the extension request with the associated ASPasswordCredential. - - override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + + private func loadCredentialsList(for serviceIdentifiers: [ASCredentialServiceIdentifier], returnString: Bool = false) { + let credentialProviderListViewController = CredentialProviderListViewController(serviceIdentifiers: serviceIdentifiers, + secureVault: secureVault, + onRowSelected: { [weak self] item in + guard let self = self else { + self?.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, + code: ASExtensionError.failed.rawValue)) + return + } + + let credential = self.passwordFetcher.fetchCredential(for: item.account) + self.extensionContext.completeRequest(withSelectedCredential: credential, completionHandler: nil) + + }, onDismiss: { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, + code: ASExtensionError.userCanceled.rawValue)) + }) + + let navigationController = UINavigationController(rootViewController: credentialProviderListViewController) + self.view.subviews.forEach { $0.removeFromSuperview() } + addChild(navigationController) + navigationController.view.frame = self.view.bounds + navigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.view.addSubview(navigationController.view) + navigationController.didMove(toParent: self) } - */ } diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift new file mode 100644 index 0000000000..e1fe4656f2 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIColorExtension.swift @@ -0,0 +1,67 @@ +// +// UIColorExtension.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 UIKit + +extension UIColor { + + convenience init(hex: String) { + var rgbValue: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&rgbValue) + + self.init( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: CGFloat(1.0) + ) + } + + static func forDomain(_ domain: String) -> UIColor { + var consistentHash: Int { + return domain.utf8 + .map { return $0 } + .reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) } + } + + let palette = [ + UIColor(hex: "94B3AF"), + UIColor(hex: "727998"), + UIColor(hex: "645468"), + UIColor(hex: "4D5F7F"), + UIColor(hex: "855DB6"), + UIColor(hex: "5E5ADB"), + UIColor(hex: "678FFF"), + UIColor(hex: "6BB4EF"), + UIColor(hex: "4A9BAE"), + UIColor(hex: "66C4C6"), + UIColor(hex: "55D388"), + UIColor(hex: "99DB7A"), + UIColor(hex: "ECCC7B"), + UIColor(hex: "E7A538"), + UIColor(hex: "DD6B4C"), + UIColor(hex: "D65D62") + ] + + let hash = consistentHash + let index = hash % palette.count + return palette[abs(index)] + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift index 83f5a78799..0bba578770 100644 --- a/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIViewControllerExtension.swift @@ -23,9 +23,16 @@ extension UIViewController { public func installChildViewController(_ childController: UIViewController) { addChild(childController) - childController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - childController.view.frame = view.bounds view.addSubview(childController.view) + + childController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + childController.view.topAnchor.constraint(equalTo: view.topAnchor), + childController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + childController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + childController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + childController.didMove(toParent: self) } diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift new file mode 100644 index 0000000000..56cda53ea2 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UImageExtension.swift @@ -0,0 +1,30 @@ +// +// UImageExtension.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 UIKit + +extension UIImage { + + func toSRGB() -> UIImage { + UIGraphicsImageRenderer(size: size).image { _ in + draw(in: CGRect(origin: .zero, size: size)) + } + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf b/AutofillCredentialProvider/CredentialProvider/Resources/Assets.xcassets/AutofillLock.imageset/AutofillLock.pdf new file mode 100644 index 0000000000000000000000000000000000000000..606eb7cbd1d0cb77f67ba9d4fd3fdca48ca63a2d GIT binary patch literal 6605 zcmeHMO^+Nk5WV-W@Fh}$L{IzMEk%(?vWXBN!iHPKVVF#kMYB7wGeLo0&wIAJ?A;CQ z<^l-OkrL;%yIdcy%4N^J7f+vkuE#hGT1{&I<4=Q94jc0Y@+D!e|GfkU$i%i>Q-Xc@CnSUlueTR(kMEQP zc*Y0z)0rSqJ;D8yAFH9Yn83VrZ zzD`d80}2SU+6+akcB(9d={sNn;!Oe;A<9lz+)_kTX|cgA13@-}9Ycw|U^XiQp^yZf zQdBmp3>{>>&0w-=&_VRt0gIfkoAzdOFw9c>YM~i5rP9>uhYj2rXlrWz+5t1fA}|`J zJ$zk5CWduLeMMPyD27221RxKJVeqo7R}4ceXiwFv7>1b9o~nH@3<`>c<&|?l7-IOL z2!P+|QCBNwM(k?I5nog@;&>Oc_2+GVhxRlQsc(+ka@gRHj!x2g`J z7x~5@AhM#pFir5<>rnU^i z?BEYZJQ3maI0IqikkoW3i=NS{t^_90rxRY!mSExHAVOtkFbqDfno46CY$daNdzH6%165LB|34ylHgbV3mncQE^An#@X(i#nM<(bsKN zp~cmNwx5vDQb26eU*3@&6IeQADR8a1 zmUr67HteK$++jl={lcAEVwv+H5^ZHy7QXokdtedJZ7gQArV6N%q}`Y4g0Mxb(*={k z%~v9T8&XwSw36wfuuK=^YK8Q7cCe9V%goEdxBFK@A?wq>b8(qq-QfBw5zzS;?cyS! zPH(y`stJpbV4Sh-YPL5nV9TEkde!V&scY8< zJPm5J)YCl8Mxhtgv(vLjGl`uu20bO5DFlUhJNcj$a%A>X$S1B4oY9zN$7sB9Ay*PN z2fj6WbaV1avw4a9qjVZw)RW;3pCPo4encx5UCa&50^&9L)5yNF%>-5L?9ej`R8!cq z>8hVLXo)pWPz_|q$2eXA#qBsz8w11H^yFwyN{cFsJmhp!dXYzP9{p@04jZ{;nQ*eA zUo@09&@bqiCa+8wqt|Jst0G$8Ib!ccGQ`;h_=WAxUlXF<6N<+V{Rp6Nw5)ZHN38UGLvO)wS7a_vJ9A8PLGMaw~U7M26iB~ zCW=DbIi~SY5+L)-D~i%I%rpQ%tbO^h0)0C0~*=P$;`5D%5kjUBN>c~6XL`(keM9v)Zyw4pyrt=SWu^f zkUmIGfB`8d6H(|ua2IDkcc_yKg0@P-3el+!4udiiS!k0%ErCb@p<40|1--3OtU|qS z>IjtY0E*&*@tUaGCs-MPMj~GjQKPGyO<~fr`39Fo8nx5HtsXT+a)FH8ZoMQG0PEn5p}vf z?B8E*wg<%@2~MlLC;N-z_5w@t27LivtS^BnT&!lOyJsiVeqZ?N>@{>%G~rY&>pa=N z$G7^10S6$RBwT@Zl4#*U7Q`9B zJ_-e-JZ#DGNT4ubx4aHTD8{GF!a?9Vgz_TL@p65(+g=X*!R5=RaL4__{(SpD-9LN% npg3i9y5Aoa4bBc;y*&GU3+?j{6YI-kHYeC*hkN% + + + + + + + + + + + + + + + + + + diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift index e728b72015..4a25114df3 100644 --- a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift +++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift @@ -28,4 +28,25 @@ final class UserText { static let credentialProviderActivatedButton = NSLocalizedString("credential.provider.activated.button", value: "Open DuckDuckGo", comment: "Title of button to launch the DuckDuckGo app") static let actionCancel = NSLocalizedString("action.button.cancel", value: "Cancel", comment: "Cancel button title") + + static let autofillLoginListTitle = NSLocalizedString("autofill.logins.list.title", value: "Passwords", comment: "Title for screen listing autofill logins") + + static let autofillLoginListSearchPlaceholder = NSLocalizedString("autofill.logins.list.search-placeholder", value: "Search passwords", comment: "Placeholder for search field on autofill login listing") + + static let autofillEmptyViewTitle = NSLocalizedString("autofill.logins.empty-view.title", value:"No passwords saved yet", comment: "Title for view displayed when autofill has no items") + + static let autofillEmptyViewSubtitle = NSLocalizedString("autofill.logins.list.enable.footer", value:"Passwords are stored securely on your device.", comment: "Footer label displayed below table section with option to enable autofill") + + static let autofillLoginListSuggested = NSLocalizedString("autofill.logins.list.suggested", value: "Suggested", comment: "Section title for group of suggested saved logins") + + static let autofillSearchNoResultTitle = NSLocalizedString("autofill.logins.search.no-results.title", value: "No Results", comment: "Title displayed when there are no results on Autofill search") + + static func autofillSearchNoResultSubtitle(for query: String) -> String { + let message = NSLocalizedString("autofill.logins.search.no-results.subtitle", value: "for '%@'", comment: "Subtitle displayed when there are no results on Autofill search, example : No Result (Title) for Duck (Subtitle)") + return message.format(arguments: query) + } + + static let autofillLoginListAuthenticationReason = NSLocalizedString("autofill.logins.list.auth.reason", value:"Unlock device to access passwords", comment: "Reason for auth when opening login list") + + static let autofillLoginListAuthenticationCancelButton = NSLocalizedString("autofill.logins.list.auth.cancel", value:"Cancel", comment: "Cancel button for auth when opening login list") } diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/AutofillPasswordFetcher.swift b/AutofillCredentialProvider/CredentialProvider/Shared/AutofillPasswordFetcher.swift new file mode 100644 index 0000000000..c3edbf2c3f --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Shared/AutofillPasswordFetcher.swift @@ -0,0 +1,52 @@ +// +// AutofillPasswordFetcher.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 Foundation +import AuthenticationServices +import BrowserServicesKit +import Core +import SecureStorage + +class CredentialFetcher { + + private let secureVault: (any AutofillSecureVault)? + + init(secureVault: (any AutofillSecureVault)?) { + self.secureVault = secureVault + } + + func fetchCredential(for account: SecureVaultModels.WebsiteAccount) -> ASPasswordCredential { + let password = fetchPassword(for: account) + return ASPasswordCredential(user: account.username ?? "", password: password) + } + + private func fetchPassword(for account: SecureVaultModels.WebsiteAccount) -> String { + do { + if let accountID = account.id, let accountIdInt = Int64(accountID), let vault = secureVault { + if let credential = try vault.websiteCredentialsFor(accountId: accountIdInt) { + return credential.password.flatMap { String(data: $0, encoding: .utf8) } ?? "" + } + } + } catch { + Pixel.fire(pixel: .secureVaultError, error: error) + } + + return "" + } +} diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift b/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift new file mode 100644 index 0000000000..05db061e8d --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Shared/LockScreenView.swift @@ -0,0 +1,43 @@ +// +// LockScreenView.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 SwiftUI +import DesignResourcesKit + +struct LockScreenView: View { + var body: some View { + GeometryReader { geometry in + VStack { + Spacer() + Image(.autofillLock) + .position(x: geometry.size.width / 2, + y: shouldCenterVerticallyInLandscape(on: geometry) ? geometry.size.height / 2 : geometry.size.height * 0.8) + } + } + .background(Color(designSystemColor: .background)) + } + + private func shouldCenterVerticallyInLandscape(on geometry: GeometryProxy) -> Bool { + return UIDevice.current.userInterfaceIdiom == .phone && geometry.size.width > geometry.size.height + } +} + +#Preview { + LockScreenView() +} diff --git a/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift b/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift new file mode 100644 index 0000000000..2a001a714f --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Shared/SecureVaultReporter.swift @@ -0,0 +1,44 @@ +// +// SecureVaultReporter.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 Core +import Foundation +import SecureStorage + +final class SecureVaultReporter: SecureVaultReporting { + + func secureVaultError(_ error: SecureStorage.SecureStorageError) { +#if DEBUG + guard !ProcessInfo().arguments.contains("testing") else { return } +#endif + let pixelParams = [PixelParameters.isBackgrounded: "false", + PixelParameters.appVersion: AppVersion.shared.versionAndBuildNumber] + + switch error { + case .initFailed(let error): + DailyPixel.fire(pixel: .secureVaultInitFailedError, error: error, withAdditionalParameters: pixelParams) + case .failedToOpenDatabase(let error): + DailyPixel.fire(pixel: .secureVaultFailedToOpenDatabaseError, error: error, withAdditionalParameters: pixelParams) + default: + DailyPixel.fire(pixel: .secureVaultError, error: error) + } + } + +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cc488f24f5..985b8dc32e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -905,6 +905,7 @@ C1641EB12BC2F52B0012607A /* ImportPasswordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1641EB02BC2F52B0012607A /* ImportPasswordsView.swift */; }; C1641EB32BC2F53C0012607A /* ImportPasswordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */; }; C174CE602BD6A6CE00AED2EA /* MockDDGSyncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C185ED652BD43A5500BAE9DC /* MockDDGSyncing.swift */; }; + C177D9F42CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D9F32CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift */; }; C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */; }; C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */; }; C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */; }; @@ -936,6 +937,19 @@ C1CAAA812CF8C8F400C37EE6 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA802CF8C8F400C37EE6 /* DuckUI */; }; C1CAAA832CF8C8FF00C37EE6 /* DesignResourcesKit in Frameworks */ = {isa = PBXBuildFile; productRef = C1CAAA822CF8C8FF00C37EE6 /* DesignResourcesKit */; }; C1CAAA852CF8C9EA00C37EE6 /* UIResponderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */; }; + C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */; }; + C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */; }; + C1CAAA922CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */; }; + C1CAAA982CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */; }; + C1CAAA9A2CFCAD3E00C37EE6 /* AutofillLoginItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */; }; + C1CAAA9C2CFCB39800C37EE6 /* AutofillLoginListSorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */; }; + C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */; }; + C1CAAAA02CFCB7C200C37EE6 /* UImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */; }; + C1CAAAA32CFCBBBD00C37EE6 /* UserAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA22CFCBBBD00C37EE6 /* UserAuthenticator.swift */; }; + C1CAAAA62CFCBD7900C37EE6 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */; }; + C1CAAAA82CFCBE4800C37EE6 /* EmptySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA72CFCBE4800C37EE6 /* EmptySearchView.swift */; }; + C1CAAAAA2CFCC13E00C37EE6 /* SecureVaultReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */; }; + C1CAAAAC2CFCC91D00C37EE6 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAAAB2CFCC91D00C37EE6 /* EmptyView.swift */; }; C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */; }; C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */; }; C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */; }; @@ -2739,6 +2753,7 @@ C1641EAE2BC2F5140012607A /* ImportPasswordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsViewController.swift; sourceTree = ""; }; C1641EB02BC2F52B0012607A /* ImportPasswordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsView.swift; sourceTree = ""; }; C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsViewModel.swift; sourceTree = ""; }; + C177D9F32CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPasswordFetcher.swift; sourceTree = ""; }; C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewModel.swift; sourceTree = ""; }; C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewController.swift; sourceTree = ""; }; C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptView.swift; sourceTree = ""; }; @@ -2771,6 +2786,19 @@ C1CAAA772CF8BDF200C37EE6 /* UserText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; C1CAAA792CF8BE0200C37EE6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIResponderExtension.swift; sourceTree = ""; }; + C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewController.swift; sourceTree = ""; }; + C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewModel.swift; sourceTree = ""; }; + C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListItemTableViewCell.swift; sourceTree = ""; }; + C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = " AutofillLoginListSectionType.swift"; sourceTree = ""; }; + C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginItem.swift; sourceTree = ""; }; + C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListSorting.swift; sourceTree = ""; }; + C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; + C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UImageExtension.swift; sourceTree = ""; }; + C1CAAAA22CFCBBBD00C37EE6 /* UserAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticator.swift; sourceTree = ""; }; + C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = ""; }; + C1CAAAA72CFCBE4800C37EE6 /* EmptySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySearchView.swift; sourceTree = ""; }; + C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureVaultReporter.swift; sourceTree = ""; }; + C1CAAAAB2CFCC91D00C37EE6 /* EmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = ""; }; C1CDA3152AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManager.swift; sourceTree = ""; }; C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManagerTests.swift; sourceTree = ""; }; C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = ""; }; @@ -5361,6 +5389,8 @@ C1CAAA6D2CF8BBBC00C37EE6 /* CredentialProvider */ = { isa = PBXGroup; children = ( + C1CAAAA42CFCBD6B00C37EE6 /* Shared */, + C1CAAA862CF9FFBE00C37EE6 /* CredentialProviderList */, C1EF5B252CC0457B002980E6 /* CredentialProviderViewController.swift */, C1CAAA682CF8B8C400C37EE6 /* CredentialProviderActivation */, C1CAAA6E2CF8BBC900C37EE6 /* Extensions */, @@ -5372,8 +5402,10 @@ C1CAAA6E2CF8BBC900C37EE6 /* Extensions */ = { isa = PBXGroup; children = ( - C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */, + C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */, + C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */, C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */, + C1CAAA702CF8BC0B00C37EE6 /* UIViewControllerExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -5387,6 +5419,46 @@ path = Resources; sourceTree = ""; }; + C1CAAA862CF9FFBE00C37EE6 /* CredentialProviderList */ = { + isa = PBXGroup; + children = ( + C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */, + C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */, + C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */, + C1CAAAA72CFCBE4800C37EE6 /* EmptySearchView.swift */, + C1CAAAAB2CFCC91D00C37EE6 /* EmptyView.swift */, + ); + path = CredentialProviderList; + sourceTree = ""; + }; + C1CAAA962CFCAB8000C37EE6 /* Autofill */ = { + isa = PBXGroup; + children = ( + C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */, + C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */, + C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */, + ); + name = Autofill; + sourceTree = ""; + }; + C1CAAAA12CFCBBAC00C37EE6 /* Authenticator */ = { + isa = PBXGroup; + children = ( + C1CAAAA22CFCBBBD00C37EE6 /* UserAuthenticator.swift */, + ); + name = Authenticator; + sourceTree = ""; + }; + C1CAAAA42CFCBD6B00C37EE6 /* Shared */ = { + isa = PBXGroup; + children = ( + C1CAAAA52CFCBD7900C37EE6 /* LockScreenView.swift */, + C1CAAAA92CFCC13E00C37EE6 /* SecureVaultReporter.swift */, + C177D9F32CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift */, + ); + path = Shared; + sourceTree = ""; + }; C1EA865E2C74CB5500E8604D /* Promotion */ = { isa = PBXGroup; children = ( @@ -8442,12 +8514,22 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1CAAA922CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift in Sources */, + C1CAAAAC2CFCC91D00C37EE6 /* EmptyView.swift in Sources */, C1CAAA712CF8BC0B00C37EE6 /* UIViewControllerExtension.swift in Sources */, C1CAAA6A2CF8BABF00C37EE6 /* CredentialProviderActivatedView.swift in Sources */, C1CAAA732CF8BD1C00C37EE6 /* CredentialProviderActivatedViewModel.swift in Sources */, C1EF5B262CC0457B002980E6 /* CredentialProviderViewController.swift in Sources */, C1CAAA852CF8C9EA00C37EE6 /* UIResponderExtension.swift in Sources */, C1CAAA782CF8BDF200C37EE6 /* UserText.swift in Sources */, + C1CAAAA62CFCBD7900C37EE6 /* LockScreenView.swift in Sources */, + C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */, + C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */, + C1CAAAA02CFCB7C200C37EE6 /* UImageExtension.swift in Sources */, + C177D9F42CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift in Sources */, + C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */, + C1CAAAAA2CFCC13E00C37EE6 /* SecureVaultReporter.swift in Sources */, + C1CAAAA82CFCBE4800C37EE6 /* EmptySearchView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 3264c58bab99a167c416e715034f94e76f119663 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 15:16:53 +0100 Subject: [PATCH 15/21] Entitlement updated for favicon access --- .../AutofillCredentialProviderAlpha.entitlements | 1 + 1 file changed, 1 insertion(+) diff --git a/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements b/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements index 9fd2beb2f7..f7959669d1 100644 --- a/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements +++ b/AutofillCredentialProvider/AutofillCredentialProviderAlpha.entitlements @@ -7,6 +7,7 @@ com.apple.security.application-groups $(GROUP_ID_PREFIX).vault + $(GROUP_ID_PREFIX).bookmarks keychain-access-groups From 0637a255a855d44af10ae1331eff07e60f47118a Mon Sep 17 00:00:00 2001 From: amddg44 Date: Fri, 29 Nov 2024 15:54:55 +0100 Subject: [PATCH 16/21] Handling for when no device passcode / biometrics has been enabled --- ...CredentialProviderListViewController.swift | 5 ++ .../CredentialProviderListViewModel.swift | 2 +- .../UIAlertControllerExtension.swift | 51 +++++++++++++++++++ .../Resources/UserText.swift | 11 ++++ DuckDuckGo.xcodeproj/project.pbxproj | 4 ++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift index cd380b7fe7..a35c8bcca8 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift @@ -160,6 +160,11 @@ class CredentialProviderListViewController: UIViewController { if error != nil { if error != .noAuthAvailable { self.onDismiss() + } else { + let alert = UIAlertController.makeDeviceAuthenticationAlert { [weak self] in + self?.onDismiss() + } + present(alert, animated: true) } } } diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift index 8a09eefea2..2341846737 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewModel.swift @@ -127,7 +127,7 @@ final class CredentialProviderListViewModel: ObservableObject { func authenticate(completion: @escaping (UserAuthenticator.AuthError?) -> Void) { if !authenticator.canAuthenticate() { viewState = .noAuthAvailable - completion(nil) + completion(.noAuthAvailable) return } diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift new file mode 100644 index 0000000000..2e43e745a0 --- /dev/null +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift @@ -0,0 +1,51 @@ +// +// UIAlertControllerExtension.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 UIKit + +extension UIAlertController { + + static func makeDeviceAuthenticationAlert(completion: @escaping () -> Void) -> UIAlertController { + + let deviceType: String + + switch UIDevice.current.userInterfaceIdiom { + case .pad: + deviceType = UserText.deviceTypeiPad + case .phone: + deviceType = UserText.deviceTypeiPhone + default: + deviceType = UserText.deviceTypeDefault + } + + let alertController = UIAlertController( + title: UserText.autofillNoDeviceAuthSetTitle, + message: String(format: UserText.autofillNoDeviceAuthSetMessage, deviceType) , + preferredStyle: .alert + ) + + let closeButton = UIAlertAction(title: UserText.actionClose, style: .default) { _ in + completion() + } + + alertController.addAction(closeButton) + return alertController + } + +} diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift index 4a25114df3..2c0e79e90b 100644 --- a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift +++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift @@ -29,6 +29,8 @@ final class UserText { static let actionCancel = NSLocalizedString("action.button.cancel", value: "Cancel", comment: "Cancel button title") + static let actionClose = NSLocalizedString("action.button.cancel", value: "Close", comment: "Close button title") + static let autofillLoginListTitle = NSLocalizedString("autofill.logins.list.title", value: "Passwords", comment: "Title for screen listing autofill logins") static let autofillLoginListSearchPlaceholder = NSLocalizedString("autofill.logins.list.search-placeholder", value: "Search passwords", comment: "Placeholder for search field on autofill login listing") @@ -49,4 +51,13 @@ final class UserText { static let autofillLoginListAuthenticationReason = NSLocalizedString("autofill.logins.list.auth.reason", value:"Unlock device to access passwords", comment: "Reason for auth when opening login list") static let autofillLoginListAuthenticationCancelButton = NSLocalizedString("autofill.logins.list.auth.cancel", value:"Cancel", comment: "Cancel button for auth when opening login list") + + static let autofillNoDeviceAuthSetTitle = NSLocalizedString("autofill.no-device-auth-set.title", value: "Device Passcode Required", comment: "Title for alert when device authentication is not set") + + static let autofillNoDeviceAuthSetMessage = NSLocalizedString("autofill.no-device-auth-set.message", value: "Set a passcode on %@ to autofill your DuckDuckGo passwords.", comment: "Message for alert when device authentication is not set, where %@ is iPhone|iPad|device") + + static let deviceTypeiPhone = NSLocalizedString("device.type.iphone", value:"iPhone", comment: "Device type is iPhone") + static let deviceTypeiPad = NSLocalizedString("device.type.pad", value:"iPad", comment: "Device type is iPad") + static let deviceTypeDefault = NSLocalizedString("device.type.default", value:"device", comment: "Default string used if users device is not iPhone or iPad") + } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 985b8dc32e..9af53d2590 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -906,6 +906,7 @@ C1641EB32BC2F53C0012607A /* ImportPasswordsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */; }; C174CE602BD6A6CE00AED2EA /* MockDDGSyncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C185ED652BD43A5500BAE9DC /* MockDDGSyncing.swift */; }; C177D9F42CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D9F32CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift */; }; + C177D9F62CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C177D9F52CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift */; }; C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */; }; C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */; }; C17B595B2A03AAD30055F2D1 /* PasswordGenerationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */; }; @@ -2754,6 +2755,7 @@ C1641EB02BC2F52B0012607A /* ImportPasswordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsView.swift; sourceTree = ""; }; C1641EB22BC2F53C0012607A /* ImportPasswordsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPasswordsViewModel.swift; sourceTree = ""; }; C177D9F32CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPasswordFetcher.swift; sourceTree = ""; }; + C177D9F52CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtension.swift; sourceTree = ""; }; C17B59562A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewModel.swift; sourceTree = ""; }; C17B59572A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptViewController.swift; sourceTree = ""; }; C17B59582A03AAD30055F2D1 /* PasswordGenerationPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerationPromptView.swift; sourceTree = ""; }; @@ -5402,6 +5404,7 @@ C1CAAA6E2CF8BBC900C37EE6 /* Extensions */ = { isa = PBXGroup; children = ( + C177D9F52CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift */, C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */, C1CAAA9F2CFCB7C200C37EE6 /* UImageExtension.swift */, C1CAAA842CF8C9EA00C37EE6 /* UIResponderExtension.swift */, @@ -8526,6 +8529,7 @@ C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */, C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */, C1CAAAA02CFCB7C200C37EE6 /* UImageExtension.swift in Sources */, + C177D9F62CFDDFEB0039CBF7 /* UIAlertControllerExtension.swift in Sources */, C177D9F42CFDDFEB0039CBF7 /* AutofillPasswordFetcher.swift in Sources */, C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */, C1CAAAAA2CFCC13E00C37EE6 /* SecureVaultReporter.swift in Sources */, From d7ba9fd53d3d4d4b3524077abd31e9aaceac5bf7 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 2 Dec 2024 17:01:19 +0100 Subject: [PATCH 17/21] Update tests --- .../AutofillLoginListViewModelTests.swift | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift index 0ff5c5ac3a..cdc7c8dabd 100644 --- a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift +++ b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift @@ -644,9 +644,9 @@ class AutofillLoginListItemViewModelTests: XCTestCase { let testData = [SecureVaultModels.WebsiteAccount(title: nil, username: "c", domain: domain), SecureVaultModels.WebsiteAccount(title: nil, username: "ç", domain: domain), SecureVaultModels.WebsiteAccount(title: nil, username: "C", domain: domain)] - let result = testData.autofillLoginListItemViewModelsForAccountsGroupedByFirstLetter(tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort) + let result = testData.groupedByFirstLetter(tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) // Diacritics should be grouped with the root letter (in most cases), and grouping should be case insensative XCTAssertEqual(result.count, 1) } @@ -662,9 +662,9 @@ class AutofillLoginListItemViewModelTests: XCTestCase { SecureVaultModels.WebsiteAccount(title: nil, username: "?????", domain: domain), SecureVaultModels.WebsiteAccount(title: nil, username: "&%$£$%", domain: domain), SecureVaultModels.WebsiteAccount(title: nil, username: "99999", domain: domain)] - let result = testData.autofillLoginListItemViewModelsForAccountsGroupedByFirstLetter(tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort) + let result = testData.groupedByFirstLetter(tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) // All non letters should be grouped together XCTAssertEqual(result.count, 1) } @@ -672,32 +672,32 @@ class AutofillLoginListItemViewModelTests: XCTestCase { func testWhenCreatingSectionsThenTitlesWithinASectionAreSortedCorrectly() { let domain = "whateverNotImportantForThisTest" let testData = ["e": [ - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: "elephant", username: "1", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort), - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: "elephants", username: "2", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort), - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: "Elephant", username: "3", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort), - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: "èlephant", username: "4", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort), - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: "è", username: "5", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort), - AutofillLoginListItemViewModel(account: SecureVaultModels.WebsiteAccount(title: nil, username: "ezy", domain: domain), - tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort)]] - let result = testData.autofillLoginListSectionsForViewModelsSortedByTitle(autofillDomainNameUrlSort, - tld: tld) + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: "elephant", username: "1", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort), + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: "elephants", username: "2", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort), + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: "Elephant", username: "3", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort), + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: "èlephant", username: "4", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort), + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: "è", username: "5", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort), + AutofillLoginItem(account: SecureVaultModels.WebsiteAccount(title: nil, username: "ezy", domain: domain), + tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort)]] + let result = testData.sortedIntoSections(autofillDomainNameUrlSort, + tld: tld) if case .credentials(_, let viewModels) = result[0] { XCTAssertEqual(viewModels[0].title, "è") XCTAssertEqual(viewModels[1].title, "elephant") @@ -719,9 +719,9 @@ class AutofillLoginListItemViewModelTests: XCTestCase { SecureVaultModels.WebsiteAccount(title: nil, username: "test", domain: "auth.test.example.com"), SecureVaultModels.WebsiteAccount(title: nil, username: "test", domain: "https://www.auth.example.com"), SecureVaultModels.WebsiteAccount(title: nil, username: "test", domain: "https://www.example.com")] - let result = testData.autofillLoginListItemViewModelsForAccountsGroupedByFirstLetter(tld: tld, - autofillDomainNameUrlMatcher: autofillUrlMatcher, - autofillDomainNameUrlSort: autofillDomainNameUrlSort) + let result = testData.groupedByFirstLetter(tld: tld, + autofillDomainNameUrlMatcher: autofillUrlMatcher, + autofillDomainNameUrlSort: autofillDomainNameUrlSort) // Diacritics should be grouped with the root letter (in most cases), and grouping should be case insensative XCTAssertEqual(result.count, 1) } From c8a0f1bee4d9ad9e266f8a30050906246d41b789 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 2 Dec 2024 17:01:35 +0100 Subject: [PATCH 18/21] Swiftlint updates --- .../CredentialProviderActivatedView.swift | 6 +++--- .../Extensions/UIAlertControllerExtension.swift | 2 +- .../CredentialProvider/Resources/UserText.swift | 14 +++++++------- ...pe.swift => AutofillLoginListSectionType.swift} | 0 Core/AutofillLoginListSorting.swift | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) rename Core/{ AutofillLoginListSectionType.swift => AutofillLoginListSectionType.swift} (100%) diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift index b275adc73b..eb624121d7 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift @@ -75,6 +75,6 @@ struct CredentialProviderActivatedView: View { } -//#Preview { -// CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel) -//} +#Preview { + CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel) +} diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift index 2e43e745a0..96a412197d 100644 --- a/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIAlertControllerExtension.swift @@ -36,7 +36,7 @@ extension UIAlertController { let alertController = UIAlertController( title: UserText.autofillNoDeviceAuthSetTitle, - message: String(format: UserText.autofillNoDeviceAuthSetMessage, deviceType) , + message: String(format: UserText.autofillNoDeviceAuthSetMessage, deviceType), preferredStyle: .alert ) diff --git a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift index 2c0e79e90b..493be11598 100644 --- a/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift +++ b/AutofillCredentialProvider/CredentialProvider/Resources/UserText.swift @@ -35,9 +35,9 @@ final class UserText { static let autofillLoginListSearchPlaceholder = NSLocalizedString("autofill.logins.list.search-placeholder", value: "Search passwords", comment: "Placeholder for search field on autofill login listing") - static let autofillEmptyViewTitle = NSLocalizedString("autofill.logins.empty-view.title", value:"No passwords saved yet", comment: "Title for view displayed when autofill has no items") + static let autofillEmptyViewTitle = NSLocalizedString("autofill.logins.empty-view.title", value: "No passwords saved yet", comment: "Title for view displayed when autofill has no items") - static let autofillEmptyViewSubtitle = NSLocalizedString("autofill.logins.list.enable.footer", value:"Passwords are stored securely on your device.", comment: "Footer label displayed below table section with option to enable autofill") + static let autofillEmptyViewSubtitle = NSLocalizedString("autofill.logins.list.enable.footer", value: "Passwords are stored securely on your device.", comment: "Footer label displayed below table section with option to enable autofill") static let autofillLoginListSuggested = NSLocalizedString("autofill.logins.list.suggested", value: "Suggested", comment: "Section title for group of suggested saved logins") @@ -48,16 +48,16 @@ final class UserText { return message.format(arguments: query) } - static let autofillLoginListAuthenticationReason = NSLocalizedString("autofill.logins.list.auth.reason", value:"Unlock device to access passwords", comment: "Reason for auth when opening login list") + static let autofillLoginListAuthenticationReason = NSLocalizedString("autofill.logins.list.auth.reason", value: "Unlock device to access passwords", comment: "Reason for auth when opening login list") - static let autofillLoginListAuthenticationCancelButton = NSLocalizedString("autofill.logins.list.auth.cancel", value:"Cancel", comment: "Cancel button for auth when opening login list") + static let autofillLoginListAuthenticationCancelButton = NSLocalizedString("autofill.logins.list.auth.cancel", value: "Cancel", comment: "Cancel button for auth when opening login list") static let autofillNoDeviceAuthSetTitle = NSLocalizedString("autofill.no-device-auth-set.title", value: "Device Passcode Required", comment: "Title for alert when device authentication is not set") static let autofillNoDeviceAuthSetMessage = NSLocalizedString("autofill.no-device-auth-set.message", value: "Set a passcode on %@ to autofill your DuckDuckGo passwords.", comment: "Message for alert when device authentication is not set, where %@ is iPhone|iPad|device") - static let deviceTypeiPhone = NSLocalizedString("device.type.iphone", value:"iPhone", comment: "Device type is iPhone") - static let deviceTypeiPad = NSLocalizedString("device.type.pad", value:"iPad", comment: "Device type is iPad") - static let deviceTypeDefault = NSLocalizedString("device.type.default", value:"device", comment: "Default string used if users device is not iPhone or iPad") + static let deviceTypeiPhone = NSLocalizedString("device.type.iphone", value: "iPhone", comment: "Device type is iPhone") + static let deviceTypeiPad = NSLocalizedString("device.type.pad", value: "iPad", comment: "Device type is iPad") + static let deviceTypeDefault = NSLocalizedString("device.type.default", value: "device", comment: "Default string used if users device is not iPhone or iPad") } diff --git a/Core/ AutofillLoginListSectionType.swift b/Core/AutofillLoginListSectionType.swift similarity index 100% rename from Core/ AutofillLoginListSectionType.swift rename to Core/AutofillLoginListSectionType.swift diff --git a/Core/AutofillLoginListSorting.swift b/Core/AutofillLoginListSorting.swift index cf1f9e4955..a58407efbe 100644 --- a/Core/AutofillLoginListSorting.swift +++ b/Core/AutofillLoginListSorting.swift @@ -24,8 +24,8 @@ import Common public extension Array where Element == SecureVaultModels.WebsiteAccount { func groupedByFirstLetter(tld: TLD, - autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, - autofillDomainNameUrlSort: AutofillDomainNameUrlSort) + autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher, + autofillDomainNameUrlSort: AutofillDomainNameUrlSort) -> [String: [AutofillLoginItem]] { reduce(into: [String: [AutofillLoginItem]]()) { result, account in @@ -53,7 +53,7 @@ public extension Array where Element == SecureVaultModels.WebsiteAccount { public extension Dictionary where Key == String, Value == [AutofillLoginItem] { func sortedIntoSections(_ autofillDomainNameUrlSort: AutofillDomainNameUrlSort, - tld: TLD) -> [AutofillLoginListSectionType] { + tld: TLD) -> [AutofillLoginListSectionType] { map { dictionaryItem -> AutofillLoginListSectionType in let sortedGroup = dictionaryItem.value.sorted(by: { autofillDomainNameUrlSort.compareAccountsForSortingAutofill(lhs: $0.account, rhs: $1.account, tld: tld) == .orderedAscending From 51bdd5d42fb754d47dac77a3819726baa9cb7f0c Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 2 Dec 2024 19:58:23 +0100 Subject: [PATCH 19/21] Fix filename with sprurious space character --- .../CredentialProviderActivatedView.swift | 2 +- DuckDuckGo.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift index eb624121d7..1f6a32564b 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderActivation/CredentialProviderActivatedView.swift @@ -76,5 +76,5 @@ struct CredentialProviderActivatedView: View { } #Preview { - CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel) + CredentialProviderActivatedView(viewModel: CredentialProviderActivatedViewModel()) } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 9af53d2590..1341aaf336 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -921,6 +921,7 @@ C1935A222C89CA9F001AD72D /* AutofillSurveyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1935A212C89CA9F001AD72D /* AutofillSurveyManagerTests.swift */; }; C1935A242C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1935A232C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift */; }; C1963863283794A000298D4D /* BookmarksCachingSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1963862283794A000298D4D /* BookmarksCachingSearch.swift */; }; + C19D90D12CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */; }; C1B7B51C28941E980098FD6A /* HomeMessageViewModelBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B51B28941E980098FD6A /* HomeMessageViewModelBuilder.swift */; }; C1B7B52528941F2A0098FD6A /* RemoteMessagingClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B52128941F2A0098FD6A /* RemoteMessagingClient.swift */; }; C1B7B52D2894469D0098FD6A /* DefaultVariantManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7B52C2894469D0098FD6A /* DefaultVariantManager.swift */; }; @@ -941,7 +942,6 @@ C1CAAA882CF9FFE100C37EE6 /* CredentialProviderListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */; }; C1CAAA8A2CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */; }; C1CAAA922CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */; }; - C1CAAA982CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */; }; C1CAAA9A2CFCAD3E00C37EE6 /* AutofillLoginItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */; }; C1CAAA9C2CFCB39800C37EE6 /* AutofillLoginListSorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */; }; C1CAAA9E2CFCB78700C37EE6 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */; }; @@ -2772,6 +2772,7 @@ C1935A212C89CA9F001AD72D /* AutofillSurveyManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSurveyManagerTests.swift; sourceTree = ""; }; C1935A232C89CC6D001AD72D /* AutofillHeaderViewFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillHeaderViewFactoryTests.swift; sourceTree = ""; }; C1963862283794A000298D4D /* BookmarksCachingSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksCachingSearch.swift; sourceTree = ""; }; + C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListSectionType.swift; sourceTree = ""; }; C1B0F6412AB08BE9001EAF05 /* MockPrivacyConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrivacyConfiguration.swift; sourceTree = ""; }; C1B7B51B28941E980098FD6A /* HomeMessageViewModelBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModelBuilder.swift; sourceTree = ""; }; C1B7B52128941F2A0098FD6A /* RemoteMessagingClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteMessagingClient.swift; sourceTree = ""; }; @@ -2791,7 +2792,6 @@ C1CAAA872CF9FFE100C37EE6 /* CredentialProviderListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewController.swift; sourceTree = ""; }; C1CAAA892CF9FFF300C37EE6 /* CredentialProviderListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListViewModel.swift; sourceTree = ""; }; C1CAAA912CFCAA0500C37EE6 /* CredentialProviderListItemTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderListItemTableViewCell.swift; sourceTree = ""; }; - C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = " AutofillLoginListSectionType.swift"; sourceTree = ""; }; C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginItem.swift; sourceTree = ""; }; C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListSorting.swift; sourceTree = ""; }; C1CAAA9D2CFCB78700C37EE6 /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; @@ -5437,7 +5437,7 @@ C1CAAA962CFCAB8000C37EE6 /* Autofill */ = { isa = PBXGroup; children = ( - C1CAAA972CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift */, + C19D90D02CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift */, C1CAAA992CFCAD3E00C37EE6 /* AutofillLoginItem.swift */, C1CAAA9B2CFCB39800C37EE6 /* AutofillLoginListSorting.swift */, ); @@ -8589,7 +8589,6 @@ EE9D68DE2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift in Sources */, 31B2F1112C92FEE000CD30E3 /* MarketplaceAdPostback.swift in Sources */, CB258D1F29A52B2500DEBA24 /* Configuration.swift in Sources */, - C1CAAA982CFCAB9C00C37EE6 /* AutofillLoginListSectionType.swift in Sources */, BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */, 98629D312C21765A001E6031 /* BookmarksStateValidation.swift in Sources */, 9847C00027A2DDBB00DB07AA /* AppPrivacyConfigurationDataProvider.swift in Sources */, @@ -8645,6 +8644,7 @@ 1E05D1D629C46EBB00BF9A1F /* DailyPixel.swift in Sources */, 1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */, 379E877429E97C8D001C8BB0 /* BookmarksCleanupErrorHandling.swift in Sources */, + C19D90D12CFE3A7F00D17DF3 /* AutofillLoginListSectionType.swift in Sources */, 988F3DCF237D5C0F00AEE34C /* SchemeHandler.swift in Sources */, 9875E00722316B8400B1373F /* Instruments.swift in Sources */, 85E065BA2C73A4DF00D73E2A /* UsageSegmentation.swift in Sources */, From e29a8b40ea643dc02f5db96f789c7b0e9db3764b Mon Sep 17 00:00:00 2001 From: amddg44 Date: Wed, 4 Dec 2024 14:23:43 +0100 Subject: [PATCH 20/21] Updated BSK commit --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d4527d2a7e..4a4188203a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -11715,7 +11715,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = revision; - revision = 0cf4772295ec4cfab48f98cf1da0eefe89f78e2a; + revision = f99c7177385798611b62e8c98db92f3e390a1ce4; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 29c74c0668..a44c5fefde 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,7 +32,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "0cf4772295ec4cfab48f98cf1da0eefe89f78e2a" + "revision" : "f99c7177385798611b62e8c98db92f3e390a1ce4" } }, { From 6d05447ae9b58f86e32f7f9d7d23c9367b0d3201 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Sat, 7 Dec 2024 18:24:40 +0100 Subject: [PATCH 21/21] Addresses PR feedback --- .../CredentialProviderListItemTableViewCell.swift | 8 ++++---- .../CredentialProviderListViewController.swift | 2 +- .../CredentialProviderViewController.swift | 2 +- .../Extensions/UIResponderExtension.swift | 3 +++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift index 120a5e61d9..2e659cc21e 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListItemTableViewCell.swift @@ -121,11 +121,11 @@ class CredentialProviderListItemTableViewCell: UITableViewCell { private func loadImageFromCache(forDomain domain: String?) -> UIImage? { - guard let domain = domain else { return nil } - + guard let domain = domain, + let cacheUrl = FaviconsCacheType.fireproof.cacheLocation() else { return nil } + let key = FaviconHasher.createHash(ofDomain: domain) - guard let cacheUrl = FaviconsCacheType.fireproof.cacheLocation() else { return nil } - + // Slight leap here to avoid loading Kingisher as a library for the widgets. // Once dependency management is fixed, link it and use Favicons directly. let imageUrl = cacheUrl.appendingPathComponent("com.onevcat.Kingfisher.ImageCache.fireproof").appendingPathComponent(key) diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift index a35c8bcca8..72c13ae9ee 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderList/CredentialProviderListViewController.swift @@ -24,7 +24,7 @@ import Combine import Core import SwiftUI -class CredentialProviderListViewController: UIViewController { +final class CredentialProviderListViewController: UIViewController { private let viewModel: CredentialProviderListViewModel private let onRowSelected: (AutofillLoginItem) -> Void diff --git a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift index e8cb10a3bf..c41709f917 100644 --- a/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift +++ b/AutofillCredentialProvider/CredentialProvider/CredentialProviderViewController.swift @@ -22,7 +22,7 @@ import SwiftUI import BrowserServicesKit import Core -class CredentialProviderViewController: ASCredentialProviderViewController { +final class CredentialProviderViewController: ASCredentialProviderViewController { private struct Constants { static let openPasswords = AppDeepLinkSchemes.openPasswords.url diff --git a/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift index 850bbab8e9..65aa041a9c 100644 --- a/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift +++ b/AutofillCredentialProvider/CredentialProvider/Extensions/UIResponderExtension.swift @@ -21,6 +21,9 @@ import UIKit extension UIResponder { + /// Attempts to open a URL using the UIApplication instance in the responder chain. + /// This is required as the CredentialProvider extension context cannot directly launch the host app. + /// - Returns: True if the URL was opened successfully, false otherwise. @discardableResult func openUrl(_ url: URL?) -> Bool { guard let url = url else { return false }