From c89cbba5a4d375d8ebd1e2d5db86f743fae3c289 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Tue, 14 Jan 2025 03:57:18 +0500 Subject: [PATCH 01/12] [trello.com/c/avWMqc8I] moved local authentication to async/await --- Adamant.xcodeproj/project.pbxproj | 34 ++--- Adamant/Debug.entitlements | 4 +- .../AccountViewController+StayIn.swift | 132 +++++++++--------- .../Login/LoginViewController+Pinpad.swift | 43 +++--- .../SecurityViewController+StayIn.swift | 115 +++++++-------- Adamant/Release.entitlements | 4 +- Adamant/ServiceProtocols/AccountService.swift | 4 +- .../LocalAuthentication.swift | 3 +- Adamant/Services/AdamantAccountService.swift | 19 +-- Adamant/Services/AdamantAuthentication.swift | 97 ++++++------- .../Debug.entitlements | 4 +- .../Release.entitlements | 4 +- .../Debug.entitlements | 4 +- .../Release.entitlements | 4 +- Podfile.lock | 2 +- .../Debug.entitlements | 4 +- .../Release.entitlements | 4 +- 17 files changed, 224 insertions(+), 257 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 19cfcd91d..f8a68d4db 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -2862,8 +2862,8 @@ isa = PBXNativeTarget; buildConfigurationList = E913C9001FFFA51E001A83F7 /* Build configuration list for PBXNativeTarget "Adamant" */; buildPhases = ( - 9372E0412C9BC178006DF0B3 /* Run Script - Git Data */, 47866E9AB7D201F2CED0064C /* [CP] Check Pods Manifest.lock */, + 9372E0412C9BC178006DF0B3 /* Run Script - Git Data */, E913C8EA1FFFA51D001A83F7 /* Sources */, E913C8EB1FFFA51D001A83F7 /* Frameworks */, E913C8EC1FFFA51D001A83F7 /* Resources */, @@ -3917,7 +3917,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = MessageNotificationContentExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3928,7 +3928,7 @@ MARKETING_VERSION = 3.10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.MessageNotificationContentExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev-random1818.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -3947,7 +3947,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = MessageNotificationContentExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3957,7 +3957,7 @@ ); MARKETING_VERSION = 3.10.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.MessageNotificationContentExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-random1818.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -4101,7 +4101,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; DISPLAY_NAME = ADM.Dev; EXCLUDED_SOURCE_FILE_NAMES = ""; GCC_NO_COMMON_BLOCKS = YES; @@ -4112,7 +4112,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 3.10.0; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev-random1818"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTS_MACCATALYST = NO; @@ -4132,7 +4132,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; DISPLAY_NAME = Adamant; EXCLUDED_SOURCE_FILE_NAMES = Debug.xcassets; GCC_NO_COMMON_BLOCKS = YES; @@ -4143,7 +4143,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 3.10.0; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-random1818"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTS_MACCATALYST = NO; @@ -4162,7 +4162,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = TransferNotificationContentExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -4173,7 +4173,7 @@ MARKETING_VERSION = 3.10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.TransferNotificationContentExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev-random1818.TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -4192,7 +4192,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = TransferNotificationContentExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -4202,7 +4202,7 @@ ); MARKETING_VERSION = 3.10.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.TransferNotificationContentExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-random1818TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -4223,7 +4223,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -4234,7 +4234,7 @@ MARKETING_VERSION = 3.10.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.NotificationServiceExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev-random1818.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -4253,7 +4253,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = J2L77FMN46; + DEVELOPMENT_TEAM = 23VNGWAPN4; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -4263,7 +4263,7 @@ ); MARKETING_VERSION = 3.10.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.NotificationServiceExtension"; + PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-random1818.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; diff --git a/Adamant/Debug.entitlements b/Adamant/Debug.entitlements index 8e1de5348..cf6fd6632 100644 --- a/Adamant/Debug.entitlements +++ b/Adamant/Debug.entitlements @@ -22,7 +22,7 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.device.camera @@ -32,7 +32,7 @@ keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger-dev + $(AppIdentifierPrefix)im.adamant.messenger-dev-random1818 diff --git a/Adamant/Modules/Account/AccountViewController+StayIn.swift b/Adamant/Modules/Account/AccountViewController+StayIn.swift index 2f170945c..b65310cb2 100644 --- a/Adamant/Modules/Account/AccountViewController+StayIn.swift +++ b/Adamant/Modules/Account/AccountViewController+StayIn.swift @@ -41,22 +41,25 @@ extension AccountViewController { } // MARK: Use biometry - func setBiometry(enabled: Bool) { + func setBiometry(enabled: Bool) { guard showLoggedInOptions, accountService.hasStayInAccount, accountService.useBiometry != enabled else { return } - let reason = enabled ? String.adamant.security.biometryOnReason : String.adamant.security.biometryOffReason - localAuth.authorizeUser(reason: reason) { result in - Task { @MainActor [weak self] in + + Task { @MainActor [weak self] in + guard let self else { return } + let reason = enabled ? String.adamant.security.biometryOnReason : String.adamant.security.biometryOffReason + let result = await self.localAuth.authorizeUser(reason: reason) + switch result { case .success: - self?.dialogService.showSuccess(withMessage: String.adamant.alert.done) - self?.accountService.updateUseBiometry(enabled) + self.dialogService.showSuccess(withMessage: String.adamant.alert.done) + self.accountService.updateUseBiometry(enabled) case .cancel: - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - row.value = self?.accountService.useBiometry + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = self.accountService.useBiometry row.updateCell() } @@ -65,35 +68,31 @@ extension AccountViewController { if enabled { pinpad.commentLabel.text = String.adamant.security.biometryOnReason - self?.pinpadRequest = .turnOnBiometry + self.pinpadRequest = .turnOnBiometry } else { pinpad.commentLabel.text = String.adamant.security.biometryOffReason - self?.pinpadRequest = .turnOffBiometry + self.pinpadRequest = .turnOffBiometry } pinpad.commentLabel.isHidden = false pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - self?.setColors(for: pinpad) - self?.present(pinpad, animated: true, completion: nil) + self.setColors(for: pinpad) + self.present(pinpad, animated: true, completion: nil) case .failed: - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - if let value = self?.accountService.useBiometry { - row.value = value - } else { - row.value = false - } - + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = self.accountService.useBiometry row.updateCell() row.evaluateHidden() } - if let row = self?.form.rowBy(tag: Rows.notifications.tag) { + if let row = self.form.rowBy(tag: Rows.notifications.tag) { row.evaluateHidden() } + case .biometryLockout: + return } - } } } @@ -132,26 +131,25 @@ extension AccountViewController: PinpadViewControllerDelegate { break } - accountService.setStayLoggedIn(pin: pin) { [weak self] result in - Task { @MainActor in - switch result { - case .success: - self?.pinpadRequest = nil - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - row.value = false - row.updateCell() - row.evaluateHidden() - } - - if let row = self?.form.rowBy(tag: Rows.notifications.tag) { - row.evaluateHidden() - } - - pinpad.dismiss(animated: true, completion: nil) - - case .failure(let error): - self?.dialogService.showRichError(error: error) + let result = accountService.setStayLoggedIn(pin: pin) + Task { @MainActor in + switch result { + case .success: + self.pinpadRequest = nil + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = false + row.updateCell() + row.evaluateHidden() + } + + if let row = self.form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() } + + pinpad.dismiss(animated: true, completion: nil) + + case .failure(let error): + self.dialogService.showRichError(error: error) } } @@ -195,44 +193,40 @@ extension AccountViewController: PinpadViewControllerDelegate { } } - nonisolated func pinpadDidTapBiometryButton(_ pinpad: PinpadViewController) { - MainActor.assumeIsolatedSafe { + func pinpadDidTapBiometryButton(_ pinpad: PinpadViewController) { + Task { @MainActor in switch pinpadRequest { - - // MARK: User wants to turn of StayIn with his face. Or finger. + // MARK: User wants to turn of StayIn with his face. Or finger. case .turnOffPin?: - localAuth.authorizeUser(reason: String.adamant.security.stayInTurnOff, completion: { [weak self] result in - switch result { - case .success: - self?.accountService.dropSavedAccount() - - DispatchQueue.main.async { - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - row.value = false - row.updateCell() - row.evaluateHidden() - } - - if let row = self?.form.rowBy(tag: Rows.notifications.tag) { - row.evaluateHidden() - } - - pinpad.dismiss(animated: true, completion: nil) - } - - case .cancel: break - case .fallback: break - case .failed: break + let result = await localAuth.authorizeUser(reason: String.adamant.security.stayInTurnOff) + switch result { + case .success: + self.accountService.dropSavedAccount() + + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = false + row.updateCell() + row.evaluateHidden() } - }) - + + if let row = self.form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() + } + + pinpad.dismiss(animated: true, completion: nil) + + case .cancel: break + case .fallback: break + case .failed: break + case .biometryLockout: break + } default: return } } } - nonisolated func pinpadDidCancel(_ pinpad: PinpadViewController) { + func pinpadDidCancel(_ pinpad: PinpadViewController) { MainActor.assumeIsolatedSafe { switch pinpadRequest { diff --git a/Adamant/Modules/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift index 7a634aad1..68291a46f 100644 --- a/Adamant/Modules/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Modules/Login/LoginViewController+Pinpad.swift @@ -43,21 +43,17 @@ extension LoginViewController { return } - localAuth.authorizeUser(reason: .adamant.login.loginIntoPrevAccount) { result in - Task { @MainActor [weak self] in - switch result { - case .success: - self?.loginIntoSavedAccount() - - case .fallback: - self?.loginWithPinpad() - - case .cancel: - break - - case .failed: - break - } + Task { @MainActor in + let result = await localAuth.authorizeUser(reason: .adamant.login.loginIntoPrevAccount) + switch result { + case .success: + self.loginIntoSavedAccount() + + case .fallback: + self.loginWithPinpad() + + case .cancel, .failed, .biometryLockout: + break } } } @@ -133,15 +129,14 @@ extension LoginViewController: PinpadViewControllerDelegate { nonisolated func pinpadDidTapBiometryButton(_ pinpad: PinpadViewController) { Task { @MainActor in - localAuth.authorizeUser(reason: String.adamant.login.loginIntoPrevAccount, completion: { [weak self] result in - switch result { - case .success: - self?.loginIntoSavedAccount() - - case .fallback, .cancel, .failed: - break - } - }) + let result = await localAuth.authorizeUser(reason: String.adamant.login.loginIntoPrevAccount) + switch result { + case .success: + self.loginIntoSavedAccount() + + case .fallback, .cancel, .failed, .biometryLockout: + break + } } } diff --git a/Adamant/Modules/Settings/SecurityViewController+StayIn.swift b/Adamant/Modules/Settings/SecurityViewController+StayIn.swift index af2f0db0a..1c33d73c1 100644 --- a/Adamant/Modules/Settings/SecurityViewController+StayIn.swift +++ b/Adamant/Modules/Settings/SecurityViewController+StayIn.swift @@ -44,11 +44,12 @@ extension SecurityViewController { } let reason = enabled ? String.adamant.security.biometryOnReason : String.adamant.security.biometryOffReason - localAuth.authorizeUser(reason: reason) { [weak self] result in + Task { @MainActor in + let result = await localAuth.authorizeUser(reason: reason) switch result { case .success: - self?.dialogService.showSuccess(withMessage: String.adamant.alert.done) - self?.accountService.updateUseBiometry(enabled) + self.dialogService.showSuccess(withMessage: String.adamant.alert.done) + self.accountService.updateUseBiometry(enabled) case .cancel: DispatchQueue.main.async { [weak self] in @@ -63,10 +64,10 @@ extension SecurityViewController { if enabled { pinpad.commentLabel.text = String.adamant.security.biometryOnReason - self?.pinpadRequest = .turnOnBiometry + self.pinpadRequest = .turnOnBiometry } else { pinpad.commentLabel.text = String.adamant.security.biometryOffReason - self?.pinpadRequest = .turnOffBiometry + self.pinpadRequest = .turnOffBiometry } pinpad.commentLabel.isHidden = false @@ -74,26 +75,23 @@ extension SecurityViewController { DispatchQueue.main.async { pinpad.modalPresentationStyle = .overFullScreen - self?.present(pinpad, animated: true, completion: nil) + self.present(pinpad, animated: true, completion: nil) } case .failed: DispatchQueue.main.async { - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - if let value = self?.accountService.useBiometry { - row.value = value - } else { - row.value = false - } - + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = self.accountService.useBiometry row.updateCell() row.evaluateHidden() } - if let section = self?.form.sectionBy(tag: Sections.notifications.tag) { + if let section = self.form.sectionBy(tag: Sections.notifications.tag) { section.evaluateHidden() } } + case .biometryLockout: + break } } } @@ -120,30 +118,29 @@ extension SecurityViewController: PinpadViewControllerDelegate { break } - accountService.setStayLoggedIn(pin: pin) { [weak self] result in - Task { @MainActor in - switch result { - case .success: - self?.pinpadRequest = nil - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - row.value = false - row.updateCell() - row.evaluateHidden() - } - - if let section = self?.form.sectionBy(tag: Sections.notifications.tag) { - section.evaluateHidden() - } - - if let section = self?.form.sectionBy(tag: Sections.aboutNotificationTypes.tag) { - section.evaluateHidden() - } - - pinpad.dismiss(animated: true, completion: nil) - - case .failure(let error): - self?.dialogService.showRichError(error: error) + let result = accountService.setStayLoggedIn(pin: pin) + Task { @MainActor in + switch result { + case .success: + self.pinpadRequest = nil + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = false + row.updateCell() + row.evaluateHidden() + } + + if let section = self.form.sectionBy(tag: Sections.notifications.tag) { + section.evaluateHidden() } + + if let section = self.form.sectionBy(tag: Sections.aboutNotificationTypes.tag) { + section.evaluateHidden() + } + + pinpad.dismiss(animated: true, completion: nil) + + case .failure(let error): + self.dialogService.showRichError(error: error) } } @@ -188,38 +185,34 @@ extension SecurityViewController: PinpadViewControllerDelegate { } nonisolated func pinpadDidTapBiometryButton(_ pinpad: PinpadViewController) { - MainActor.assumeIsolatedSafe { + Task { @MainActor in switch pinpadRequest { - // MARK: User wants to turn of StayIn with his face. Or finger. + // MARK: User wants to turn of StayIn with his face. Or finger. case .turnOffPin?: - localAuth.authorizeUser(reason: String.adamant.security.stayInTurnOff, completion: { [weak self] result in - switch result { - case .success: - self?.accountService.dropSavedAccount() + let result = await localAuth.authorizeUser(reason: String.adamant.security.stayInTurnOff) + switch result { + case .success: + self.accountService.dropSavedAccount() + + DispatchQueue.main.async { + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = false + row.updateCell() + row.evaluateHidden() + } - DispatchQueue.main.async { - if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - row.value = false - row.updateCell() - row.evaluateHidden() - } - - if let section = self?.form.sectionBy(tag: Sections.notifications.tag) { - section.evaluateHidden() - } - - pinpad.dismiss(animated: true, completion: nil) + if let section = self.form.sectionBy(tag: Sections.notifications.tag) { + section.evaluateHidden() } - case .cancel: break - case .fallback: break - case .failed: break + pinpad.dismiss(animated: true, completion: nil) } - }) - + + case .cancel, .fallback, .failed, .biometryLockout: break + } default: - return + break } } } diff --git a/Adamant/Release.entitlements b/Adamant/Release.entitlements index 85b260349..10349a3bc 100644 --- a/Adamant/Release.entitlements +++ b/Adamant/Release.entitlements @@ -22,7 +22,7 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.device.camera @@ -32,7 +32,7 @@ keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger + $(AppIdentifierPrefix)im.adamant.messenger-random1818 diff --git a/Adamant/ServiceProtocols/AccountService.swift b/Adamant/ServiceProtocols/AccountService.swift index edf5c780c..06017c9ae 100644 --- a/Adamant/ServiceProtocols/AccountService.swift +++ b/Adamant/ServiceProtocols/AccountService.swift @@ -185,8 +185,8 @@ protocol AccountService: AnyObject, Sendable { /// /// - Parameters: /// - pin: pincode to login - /// - completion: completion handler - func setStayLoggedIn(pin: String, completion: @escaping @Sendable (AccountServiceResult) -> Void) + /// - Returns:AccountServiceResult with either success or failure + func setStayLoggedIn(pin: String) -> AccountServiceResult /// Remove stored data func dropSavedAccount() diff --git a/Adamant/ServiceProtocols/LocalAuthentication.swift b/Adamant/ServiceProtocols/LocalAuthentication.swift index 11f32ea2a..c415b0bce 100644 --- a/Adamant/ServiceProtocols/LocalAuthentication.swift +++ b/Adamant/ServiceProtocols/LocalAuthentication.swift @@ -22,6 +22,7 @@ enum BiometryType { enum AuthenticationResult { case success + case biometryLockout case cancel case fallback case failed @@ -30,5 +31,5 @@ enum AuthenticationResult { protocol LocalAuthentication: AnyObject { var biometryType: BiometryType { get } - func authorizeUser(reason: String, completion: @escaping (AuthenticationResult) -> Void) + func authorizeUser(reason: String) async -> AuthenticationResult } diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 36872bdb3..b827ef844 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -89,15 +89,13 @@ final class AdamantAccountService: AccountService, @unchecked Sendable { // MARK: - Saved data extension AdamantAccountService { - func setStayLoggedIn(pin: String, completion: @escaping @Sendable (AccountServiceResult) -> Void) { + func setStayLoggedIn(pin: String) -> AccountServiceResult { guard let account = account, let keypair = keypair else { - completion(.failure(.userNotLogged)) - return + return .failure(.userNotLogged) } if hasStayInAccount { - completion(.failure(.internalError(message: "Already has account", error: nil))) - return + return .failure(.internalError(message: "Already has account", error: nil)) } securedStore.set(pin, for: .pin) @@ -111,7 +109,7 @@ extension AdamantAccountService { hasStayInAccount = true NotificationCenter.default.post(name: Notification.Name.AdamantAccountService.stayInChanged, object: self, userInfo: [AdamantUserInfoKey.AccountService.newStayInState : true]) - completion(.success(account: account, alert: nil)) + return .success(account: account, alert: nil) } func validatePin(_ pin: String) -> Bool { @@ -397,7 +395,7 @@ extension AdamantAccountService { self.state = .loggedIn return account - } catch let error as ApiServiceError { + } catch let error { self.state = .notLogged switch error { @@ -407,8 +405,6 @@ extension AdamantAccountService { default: throw AccountServiceError.apiError(error: error) } - } catch { - throw AccountServiceError.internalError(message: error.localizedDescription, error: error) } } @@ -427,10 +423,9 @@ extension AdamantAccountService { return await withTaskGroup(of: WalletAccount?.self) { group in for wallet in walletServiceCompose.getWallets() { group.addTask { - let result = try? await wallet.core.initWallet( + try? await wallet.core.initWallet( withPassphrase: passphrase ) - return result } } @@ -495,7 +490,7 @@ private extension SecuredStore { } func get(_ key: Key) -> String? { - return get(key.stringValue) + get(key.stringValue) } func remove(_ key: Key) { diff --git a/Adamant/Services/AdamantAuthentication.swift b/Adamant/Services/AdamantAuthentication.swift index d3aac41b2..38e92459c 100644 --- a/Adamant/Services/AdamantAuthentication.swift +++ b/Adamant/Services/AdamantAuthentication.swift @@ -15,21 +15,11 @@ final class AdamantAuthentication: LocalAuthentication { var error: NSError? let available: Bool - if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { - available = true - } else if let errorCode = error?.code { - let lockoutCode = LAError.biometryLockout.rawValue - - if errorCode == lockoutCode { - available = true - } else { - available = false - } - } else { - available = false - } - - if available { + available = context.canEvaluatePolicy( + .deviceOwnerAuthenticationWithBiometrics, + error: &error + ) + if available || error?.code == LAError.biometryLockout.rawValue { switch context.biometryType { case .none, .opticID: return .none @@ -47,48 +37,47 @@ final class AdamantAuthentication: LocalAuthentication { } } - func authorizeUser(reason: String, completion: @escaping (AuthenticationResult) -> Void) { + func authorizeUser(reason: String) async -> AuthenticationResult { let context = LAContext() - context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { (success, error) in - if success { - completion(.success) - return - } - - guard let error = error as? LAError else { - completion(.failed) - return - } - - if error.code == LAError.userFallback { - completion(.fallback) - return - } - - if error.code == LAError.userCancel { - completion(.cancel) - return + let result = await authorizeUser( + context: context, + policy: .deviceOwnerAuthenticationWithBiometrics, + reason: reason + ) + if result == .biometryLockout { + return await authorizeUser( + context: context, + policy: .deviceOwnerAuthentication, + reason: reason + ) + } + return result + } + + private func authorizeUser( + context: LAContext, + policy: LAPolicy, + reason: String + ) async -> AuthenticationResult { + do { + let result = try await context.evaluatePolicy(policy, localizedReason: reason) + if result { + return .success } - - let tryDeviceOwner = error.code == LAError.biometryLockout - - if tryDeviceOwner { - context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, error) in - let result: AuthenticationResult - - if success { - result = .success - } else if let error = error as? LAError, error.code == LAError.userCancel { - result = .cancel - } else { - result = .failed - } - - completion(result) - } - } else { - completion(.failed) + } catch let error as LAError { + switch error.code { + case .userFallback: + return .fallback + case .biometryLockout: + return .biometryLockout + case .userCancel: + return .cancel + default: + return .failed } + } catch { + return .failed } + return .failed } } diff --git a/MessageNotificationContentExtension/Debug.entitlements b/MessageNotificationContentExtension/Debug.entitlements index 630574fd2..64a8011fc 100644 --- a/MessageNotificationContentExtension/Debug.entitlements +++ b/MessageNotificationContentExtension/Debug.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger-dev + $(AppIdentifierPrefix)im.adamant.messenger-dev-random1818 diff --git a/MessageNotificationContentExtension/Release.entitlements b/MessageNotificationContentExtension/Release.entitlements index 125fd8842..d9e9e0a25 100644 --- a/MessageNotificationContentExtension/Release.entitlements +++ b/MessageNotificationContentExtension/Release.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger + $(AppIdentifierPrefix)im.adamant.messenger-random1818 diff --git a/NotificationServiceExtension/Debug.entitlements b/NotificationServiceExtension/Debug.entitlements index 630574fd2..64a8011fc 100644 --- a/NotificationServiceExtension/Debug.entitlements +++ b/NotificationServiceExtension/Debug.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger-dev + $(AppIdentifierPrefix)im.adamant.messenger-dev-random1818 diff --git a/NotificationServiceExtension/Release.entitlements b/NotificationServiceExtension/Release.entitlements index 125fd8842..d9e9e0a25 100644 --- a/NotificationServiceExtension/Release.entitlements +++ b/NotificationServiceExtension/Release.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger + $(AppIdentifierPrefix)im.adamant.messenger-random1818 diff --git a/Podfile.lock b/Podfile.lock index 4e31a8ce2..2bf7bda9d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a30619b79caa4b5a7497b0600d449f34b5620eec -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/TransferNotificationContentExtension/Debug.entitlements b/TransferNotificationContentExtension/Debug.entitlements index 630574fd2..64a8011fc 100644 --- a/TransferNotificationContentExtension/Debug.entitlements +++ b/TransferNotificationContentExtension/Debug.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger-dev + $(AppIdentifierPrefix)im.adamant.messenger-dev-random1818 diff --git a/TransferNotificationContentExtension/Release.entitlements b/TransferNotificationContentExtension/Release.entitlements index 125fd8842..d9e9e0a25 100644 --- a/TransferNotificationContentExtension/Release.entitlements +++ b/TransferNotificationContentExtension/Release.entitlements @@ -6,13 +6,13 @@ com.apple.security.application-groups - group.adamant.adamant-messenger + group.adamant.adamant-messenger-random1818 com.apple.security.network.client keychain-access-groups - $(AppIdentifierPrefix)im.adamant.messenger + $(AppIdentifierPrefix)im.adamant.messenger-random1818 From 31786f22473de82a7ee0bfde91fb3db4747cb3ca Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Sun, 26 Jan 2025 20:03:47 +0400 Subject: [PATCH 02/12] Squashed commit of the following: commit 0d141e26002d89ac65399c52bfef6e308bd69a1a Author: Ruslan Alikhamov Date: Fri Jan 24 17:08:21 2025 +0400 [trello.com/c/80YNMlYD] Implemented PinPad View in SwiftUI commit 1396bb61e7af81974cbfd0710b42284c499eb126 Merge: 01371819 58e3b378 Author: Vladimir <130711953+vovameister@users.noreply.github.com> Date: Fri Jan 24 12:50:33 2025 +0100 Merge pull request #659 from Adamant-im/trello.com/c/sI4VXuJa [trello.com/c/sI4VXuJa] update snackbar commit 58e3b3782c24ac4fe039bf2556ffb2a758f9b5b9 Author: Vladimir Klevtsov Date: Tue Jan 21 14:38:16 2025 +0100 [trello.com/c/sI4VXuJa] update snackbar autoDismiss 4sec -> 3 sec commit 01371819b26ae2a795777435e04e64773e7ef1ab Merge: 3b81e1b1 16f4c262 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Fri Jan 24 08:25:08 2025 -0300 Merge pull request #662 from Adamant-im/master Master -> Develop commit 3b81e1b1627310868f6011e48deb5c60ec2271c3 Author: Christian Date: Thu Jan 23 16:06:46 2025 +0300 [trello.com/c/G77vaoVI]: handle keyboard 'Go' button tap and start login instantly (#639) * [trello.com/c/G77vaoVI]: handle keyboard 'Go' button tap and start login instantly * [trello.com/c/G77vaoVI]: added paste button to make login process more convenient * [trello.com/c/G77vaoVI]: fixed spacing and imageInsets * [trello.com/c/G77vaoVI]: review fixes * [trello.com/c/G77vaoVI]: access modifiers fix commit 16f4c262204d55e5d40a55284a371e50c7d97003 Merge: 01feef5f 8b761819 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Tue Jan 21 02:15:54 2025 -0300 Merge pull request #632 from Adamant-im/release/3.10.0 Release 3.10.0 commit 8b7618190843a6b78140b4d4eaa1ee1946eb2272 Merge: f45fa99f 0e555d87 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Tue Jan 21 02:14:49 2025 -0300 Merge pull request #649 from Adamant-im/dev/trello.com/c/etjLStc6 [trello.com/c/etjLStc6] KVS-related code refactoring commit 0e555d87a26a9bbb6a44b239ab163781931868e2 Author: just-software-dev Date: Wed Jan 15 19:35:21 2025 -0300 [trello.com/c/etjLStc6] KVS-related code refactoring commit f45fa99f60e60698c76f7d1c2c6dc68831c8a613 Merge: c281ccbc d5e9f3b7 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Sat Jan 11 01:35:41 2025 -0300 Merge pull request #641 from Adamant-im/dev/trello.com/c/AmlmMsUi [trello.com/c/AmlmMsUi] Network layer fixes commit c281ccbc268bfbf1f05c37f0fece593d315c2a64 Merge: 39d01075 8dc331c4 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Sat Jan 11 01:35:18 2025 -0300 Merge pull request #642 from Adamant-im/dev/trello.com/c/jcTggeWE [trello.com/c/jcTggeWE] adamant-wallets updates commit 8dc331c4bf600df9bc964cf9e517ab8abbabade7 Author: just-software-dev Date: Sat Jan 11 01:31:33 2025 -0300 [trello.com/c/jcTggeWE] adamant-wallets updates commit d5e9f3b7b313f08fb5b5af787dae5f396bcaeb5f Author: just-software-dev Date: Sat Jan 11 01:19:40 2025 -0300 [trello.com/c/AmlmMsUi] Network layer fixes commit 39d01075cdb8843b87d3d5df53c6facf9d5d2edf Merge: 57d16acd c47e1086 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Wed Jan 8 12:13:55 2025 -0300 Merge pull request #629 from Adamant-im/dev/trello.com/c/ysg4LpCL commit c47e1086ba7d8fb8c8a9f007a066f8f047d41719 Author: just-software-dev Date: Fri Jan 3 15:58:52 2025 -0300 [trello.com/c/ysg4LpCL] Vibro workaround commit ba4ae3deace328909bbb4cf9965f86e1c39b3057 Merge: f809bfbf 57d16acd Author: just-software-dev Date: Fri Jan 3 07:54:33 2025 -0300 Merge branch 'release/3.10.0' into dev/trello.com/c/ysg4LpCL commit f809bfbf2e5b3903a2ff09344ed01831f1a77ff7 Author: just-software-dev Date: Mon Dec 30 08:27:40 2024 -0300 [trello.com/c/ysg4LpCL] Balance vibro fix commit 01feef5f47f980cf530193956dea9f6fcc4b9d78 Merge: ec4b606c 88034433 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Mon Sep 30 19:03:34 2024 +0300 Merge pull request #551 from Adamant-im/release/3.9.0 Release 3.9.0 commit ec4b606cf929530fe092e9ea770ef784986c7386 Merge: f6d0b769 2fecd440 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Mon Aug 26 14:42:10 2024 +0300 Merge pull request #528 from Adamant-im/dev/trello.com/c/Qa3Dx6EI [trello.com/c/Qa3Dx6EI] Release 3.8.0 commit f6d0b76920e18d8cd123f79f2c18760c61259a43 Merge: 30401281 3bfaa581 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Mon Jul 15 11:56:18 2024 +0300 Merge pull request #499 from Adamant-im/develop Release 3.7.0 commit 30401281d26130fbc3ff4c7daebfb118c2d112a8 Merge: b038d03c 0863b389 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Thu Jun 13 18:38:44 2024 +0300 Merge pull request #494 from Adamant-im/develop Release 3.6.2 commit b038d03c55a218c4a7fa81224085398028e5eac6 Merge: c187d78e d927ac39 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Wed May 15 12:02:32 2024 +0300 Merge pull request #487 from Adamant-im/develop Release 3.6.1 commit c187d78e3a4c20f4ece650d8c097451e9f50d1f0 Merge: a6a40174 9e4d308b Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Wed Mar 13 17:34:00 2024 +0200 Merge pull request #469 from Adamant-im/develop Release 3.6.0 commit a6a4017433f8886d98f8b095997a0df11f6829a8 Merge: 6a5c8d7b b78409f8 Author: Dev <31865236+just-software-dev@users.noreply.github.com> Date: Sun Feb 4 07:16:44 2024 -0300 Merge pull request #437 from Adamant-im/develop Release 3.5.0 commit 6a5c8d7b108d9b9df3ad7a5067fa8085a01a2bb2 Merge: ff2cc617 873478d9 Author: StanislavDevIOS <99971092+StanislavDevIOS@users.noreply.github.com> Date: Fri Jan 19 13:29:41 2024 +0200 Merge pull request #420 from Adamant-im/develop Release 3.4.0 --- Adamant.xcodeproj/project.pbxproj | 4 + Adamant/Helpers/MyLittlePinpad+adamant.swift | 2 + Adamant/Helpers/PinPadView.swift | 158 ++++++++++++++++++ Adamant/Helpers/UITextField+adamant.swift | 32 +++- .../ChatsList/ChatListViewController.swift | 2 +- .../Modules/Login/LoginViewController.swift | 60 ++++++- .../AdmWalletService+DynamicConstants.swift | 2 +- .../BtcWalletService+DynamicConstants.swift | 4 +- .../Wallets/Bitcoin/BtcWalletService.swift | 68 ++++---- .../Wallets/Dash/DashWalletService.swift | 54 +++--- .../Wallets/Doge/DogeWalletService.swift | 54 +++--- .../Wallets/ERC20/ERC20WalletService.swift | 19 +-- .../Wallets/Ethereum/EthWalletService.swift | 50 +++--- .../WalletService/KlyWalletService.swift | 55 +++--- .../DataProviders/DataProvider.swift | 10 +- .../Services/AdamantAddressBookService.swift | 18 +- .../DataProviders/AdamantChatsProvider.swift | 8 +- .../AdamantTransfersProvider.swift | 6 +- .../Buttons/clipboard.imageset/Contents.json | 23 +++ .../Buttons/clipboard.imageset/clipboard.png | Bin 0 -> 1307 bytes .../clipboard.imageset/clipboard@2x.png | Bin 0 -> 2613 bytes .../clipboard.imageset/clipboard@3x.png | Bin 0 -> 3964 bytes .../CommonKit/Models/KVSValueModel.swift | 22 +++ .../Protocols/AdamantApiServiceProtocol.swift | 8 +- .../ApiService/AdamantApi+States.swift | 22 +-- .../BlockchainHealthCheckWrapper.swift | 36 ++-- .../HealthCheck/HealthCheckWrapper.swift | 50 ++++-- .../Services/AutoDismissManager.swift | 2 +- .../Views/NotificationPresenterView.swift | 120 ++++++------- .../Views/NotificationView.swift | 4 +- 30 files changed, 610 insertions(+), 283 deletions(-) create mode 100644 Adamant/Helpers/PinPadView.swift create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard@3x.png create mode 100644 CommonKit/Sources/CommonKit/Models/KVSValueModel.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index d18d16117..967c4f5b1 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -465,6 +465,7 @@ A5F929AF262C857D00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929AE262C857D00C3E60A /* MarkdownKit */; }; A5F929B6262C858700C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B5262C858700C3E60A /* MarkdownKit */; }; A5F929B8262C858F00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B7262C858F00C3E60A /* MarkdownKit */; }; + D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA7882D42745D0069BC73 /* PinPadView.swift */; }; E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */; }; E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F620EC200900D0CB2D /* SecurityViewController.swift */; }; E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */; }; @@ -1108,6 +1109,7 @@ A5E0422A282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcUnspentTransactionResponse.swift; sourceTree = ""; }; AD258997F050B24C0051CC8D /* Pods-Adamant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.release.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.release.xcconfig"; sourceTree = ""; }; ADDFD2FA17E41CCBD11A1733 /* Pods-Adamant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.debug.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.debug.xcconfig"; sourceTree = ""; }; + D39AA7882D42745D0069BC73 /* PinPadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinPadView.swift; sourceTree = ""; }; E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; E90055F620EC200900D0CB2D /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecurityViewController+StayIn.swift"; sourceTree = ""; }; @@ -2404,6 +2406,7 @@ E96D64C72295C44400CA5587 /* Data+utilites.swift */, 64A223D520F760BB005157CB /* Localization.swift */, E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, + D39AA7882D42745D0069BC73 /* PinPadView.swift */, E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, 6414C18D217DF43100373FA6 /* String+adamant.swift */, @@ -3618,6 +3621,7 @@ E908471B2196FE590095825D /* Adamant.xcdatamodeld in Sources */, E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */, 93294B902AAD2C6B00911109 /* SwiftyOnboardOverlay.swift in Sources */, + D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */, E948E03B20235E2300975D6B /* SettingsFactory.swift in Sources */, 4186B3302941E642006594A3 /* AdmWalletService+DynamicConstants.swift in Sources */, E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index 34c06bda6..ed3348e5d 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -81,3 +81,5 @@ extension PinpadViewController { return pinpad } } + + diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift new file mode 100644 index 000000000..21545c240 --- /dev/null +++ b/Adamant/Helpers/PinPadView.swift @@ -0,0 +1,158 @@ +// +// StyledPinpadView.swift +// Adamant +// +// Created by Brian on 23/01/2025. +// Copyright © 2025 Adamant. All rights reserved. +// + +import SwiftUI + +struct PinPadViewRepresentable: UIViewRepresentable { + @Binding var enteredPin: String + let pinLength: Int + let validatePin: (String) -> Bool + let onSuccess: () -> Void + let onCancel: () -> Void + + func makeUIView(context: Context) -> UIView { + let hostingController = UIHostingController( + rootView: PinPadView( + enteredPin: $enteredPin, + pinLength: pinLength, + validatePin: validatePin, + onSuccess: onSuccess, + onCancel: onCancel + ) + ) + return hostingController.view + } + + func updateUIView(_ uiView: UIView, context: Context) { + // Handle updates to the view if needed + } +} + +// swiftlint:disable multiple_closures_with_trailing_closure +struct PinPadView: View { + @Binding var enteredPin: String + @State var isPinpadVisible: Bool = true + let pinLength: Int + let validatePin: (String) -> Bool + let onSuccess: () -> Void + let onCancel: () -> Void + + var body: some View { + VStack { + Spacer() + Text("Login into ADAMANT") + .foregroundColor(.white) + .textCase(nil) + .font(.body) + .padding(.top, 30) + + HStack(spacing: 10) { + ForEach(0.. some View { + HStack { + if showsDeleteButton { + Circle() + .frame(width: 75, height: 75) + .foregroundColor(.clear) + } + ForEach(from...to, id: \.self) { number in + Button(action: { + handlePinInput("\(number)") + }) { + Circle() + .frame(width: 75, height: 75) + .overlay( + Text("\(number)") + .foregroundColor(.white) + .font(.title) + ) + .foregroundColor(.clear) + .overlay(Circle().stroke(Color.white, lineWidth: 1)) + } + } + if showsDeleteButton { + Button(action: deleteLastDigit) { + Circle() + .frame(width: 75, height: 75) + .overlay( + Image(systemName: "delete.left") + .foregroundColor(.white) + .font(.title) + ) + .foregroundColor(.clear) + .overlay(Circle().stroke(Color.white, lineWidth: 1)) + } + } + } + } + + private func handlePinInput(_ digit: String) { + guard enteredPin.count < pinLength else { return } + enteredPin.append(digit) + if enteredPin.count == pinLength { + if validatePin(enteredPin) { + onSuccess() + isPinpadVisible = false + } else { + enteredPin.removeAll() + } + } + } + + private func deleteLastDigit() { + guard !enteredPin.isEmpty else { return } + enteredPin.removeLast() + } +} + +#if DEBUG + +private struct Placeholder { + @State var enteredPin: String = "" +} + +#Preview { + PinPadView( + enteredPin: Placeholder().$enteredPin, + pinLength: 6 + ) { _ in + true + } onSuccess: { + + } onCancel: { + + } +} + +#endif diff --git a/Adamant/Helpers/UITextField+adamant.swift b/Adamant/Helpers/UITextField+adamant.swift index 9081e2101..5c67d49e6 100644 --- a/Adamant/Helpers/UITextField+adamant.swift +++ b/Adamant/Helpers/UITextField+adamant.swift @@ -144,24 +144,38 @@ extension UITextField { } extension UITextField { + + // MARK: - Password toggle button + + static var buttonContainerHeight: CGFloat { 28 } + static var buttonImageEdgeInsets: UIEdgeInsets { + UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3) + } + func enablePasswordToggle() { - let button = UIButton(type: .custom) - updatePasswordToggleImage(button) - button.addTarget(self, action: #selector(togglePasswordView(_:)), for: .touchUpInside) + let button = makePasswordButton() - let contanerView = UIView() - contanerView.addSubview(button) + let containerView = UIView() + containerView.addSubview(button) button.snp.makeConstraints { make in - make.directionalEdges.equalToSuperview().inset(3) + make.directionalEdges.equalToSuperview() } - contanerView.snp.makeConstraints { make in - make.size.equalTo(28) + containerView.snp.makeConstraints { make in + make.size.equalTo(UITextField.buttonContainerHeight) } - rightView = contanerView + rightView = containerView rightViewMode = .always } + func makePasswordButton() -> UIButton { + let button = UIButton(type: .custom) + button.imageEdgeInsets = UITextField.buttonImageEdgeInsets + updatePasswordToggleImage(button) + button.addTarget(self, action: #selector(togglePasswordView(_:)), for: .touchUpInside) + return button + } + private func updatePasswordToggleImage(_ button: UIButton) { let imageName = isSecureTextEntry ? "eye_close" : "eye_open" button.setImage(.asset(named: imageName), for: .normal) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 6783bb9f6..b7b33ebc5 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1528,7 +1528,7 @@ extension ChatListViewController { } } -private extension State { +private extension DataProviderState { var isUpdating: Bool { switch self { case .updating: true diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 29199bcb3..fb5be3ee3 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -230,7 +230,7 @@ final class LoginViewController: FormViewController { $0.tag = Rows.passphrase.tag $0.placeholder = Rows.passphrase.localized $0.placeholderColor = UIColor.adamant.secondary - $0.cell.textField.enablePasswordToggle() + $0.cell.textField.enablePasteButtonAndPasswordToggle() $0.keyboardReturnType = KeyboardReturnTypeConfiguration(nextKeyboardType: .go, defaultKeyboardType: .go) } @@ -414,6 +414,17 @@ final class LoginViewController: FormViewController { versionFooterView.sizeToFit() } + // MARK: - FormViewController + + override func textInputShouldReturn(_ textInput: UITextInput, cell: Cell) -> Bool { + let result = super.textInputShouldReturn(textInput, cell: cell) + if cell.row.tag == Rows.passphrase.tag, let passphrase = cell.row.value as? String { + loginWith(passphrase: passphrase) + } + + return result + } + // MARK: - Other private func setColors() { @@ -514,3 +525,50 @@ extension LoginViewController: ButtonsStripeViewDelegate { } } } + +// MARK: UITextField + extensions + +private extension UITextField { + func enablePasteButtonAndPasswordToggle() { + let passwordToggleButton = makePasswordButton() + let pasteButton = makePasteButton() + + let containerView = UIView() + let buttonStack = UIStackView(arrangedSubviews: [pasteButton, passwordToggleButton]) + buttonStack.axis = .horizontal + buttonStack.spacing = 4 + containerView.addSubview(buttonStack) + buttonStack.snp.makeConstraints { make in + make.directionalEdges.equalToSuperview() + } + + containerView.snp.makeConstraints { make in + make.height.equalTo(UITextField.buttonContainerHeight) + } + + pasteButton.snp.makeConstraints { make in + make.width.equalTo(pasteButton.snp.height) + } + + passwordToggleButton.snp.makeConstraints { make in + make.width.equalTo(passwordToggleButton.snp.height) + } + + rightView = containerView + rightViewMode = .always + } + + func makePasteButton() -> UIButton { + let button = UIButton(type: .custom) + button.imageEdgeInsets = UITextField.buttonImageEdgeInsets + button.setImage(.asset(named: "clipboard"), for: .normal) + button.addTarget(self, action: #selector(pasteFromPasteboard(_:)), for: .touchUpInside) + return button + } + + @objc func pasteFromPasteboard(_ sender: UIButton) { + if let pasteboardText = UIPasteboard.general.string { + self.text = pasteboardText + } + } +} diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index e8b4fbac7..c4db39344 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -120,7 +120,7 @@ extension AdmWalletService { [ Node.makeDefaultNode( url: URL(string: "https://info.adamant.im")!, - altUrl: URL(string: "http://88.198.156.44:44099")! + altUrl: URL(string: "http://5.161.98.136:33088")! ), Node.makeDefaultNode( url: URL(string: "https://info2.adm.im")!, diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index 0f1f603cb..652e18048 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -71,8 +71,8 @@ extension BtcWalletService { 8 } - static let explorerTx = "https://explorer.btc.com/btc/transaction/" - static let explorerAddress = "https://explorer.btc.com/btc/address/" + static let explorerTx = "https://bitcoinexplorer.org/tx/" + static let explorerAddress = "https://bitcoinexplorer.org/address/" static var nodes: [Node] { [ diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 94be6f35e..8863452ba 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -266,6 +266,7 @@ final class BtcWalletService: WalletCoreProtocol, @unchecked Sendable { } } + @MainActor func update() async { guard let wallet = btcWallet else { return @@ -282,13 +283,12 @@ final class BtcWalletService: WalletCoreProtocol, @unchecked Sendable { setState(.updating) if let balance = try? await getBalance() { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { - await vibroService.applyVibration(.success) + vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -363,12 +363,12 @@ final class BtcWalletService: WalletCoreProtocol, @unchecked Sendable { return output } - private func markBalanceAsFresh() { - btcWallet?.isBalanceInitialized = true + private func markBalanceAsFresh(_ wallet: BtcWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = btcWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( @@ -451,6 +451,7 @@ extension BtcWalletService { addressConverter: addressConverter ) self.btcWallet = eWallet + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -467,9 +468,9 @@ extension BtcWalletService { let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) - if address != eWallet.address { - service.save(btcAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(btcAddress: eWallet.address, result: result) + if address != eWallet.address, let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } throw WalletServiceError.accountNotFound } @@ -492,8 +493,10 @@ extension BtcWalletService { await service.update() } - service.save(btcAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(btcAddress: eWallet.address, result: result) + if let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) + } } return eWallet @@ -536,16 +539,11 @@ extension BtcWalletService { } func getBalance(address: String) async throws -> Decimal { - do { - let response: BtcBalanceResponse = try await btcApiService.request(waitsForConnectivity: false) { api, origin in - await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.balance(for: address)) - }.get() - - return response.value / BtcWalletService.multiplier - } catch { - print("--debug", error.localizedDescription) - return 0 - } + let response: BtcBalanceResponse = try await btcApiService.request(waitsForConnectivity: false) { api, origin in + await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.balance(for: address)) + }.get() + + return response.value / BtcWalletService.multiplier } func getFeeRate() async throws -> Decimal { @@ -568,8 +566,8 @@ extension BtcWalletService { /// - btcAddress: Bitcoin address to save into KVS /// - adamantAddress: Owner of BTC address /// - completion: success - private func save(btcAddress: String, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { - guard let adamant = accountService.account, let keypair = accountService.keypair else { + private func save(_ model: KVSValueModel, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { + guard let adamant = accountService.account else { completion(.failure(error: .notLogged)) return } @@ -580,13 +578,7 @@ extension BtcWalletService { } Task { - let result = await apiService.store( - key: BtcWalletService.kvsAddress, - value: btcAddress, - type: .keyValue, - sender: adamant.address, - keypair: keypair - ) + let result = await apiService.store(model) switch result { case .success: @@ -599,7 +591,7 @@ extension BtcWalletService { } /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save - private func kvsSaveCompletionRecursion(btcAddress: String, result: WalletServiceSimpleResult) { + private func kvsSaveCompletionRecursion(_ model: KVSValueModel, result: WalletServiceSimpleResult) { if let observer = balanceObserver { NotificationCenter.default.removeObserver(observer) balanceObserver = nil @@ -618,8 +610,8 @@ extension BtcWalletService { return } - self?.save(btcAddress: btcAddress) { [weak self] result in - self?.kvsSaveCompletionRecursion(btcAddress: btcAddress, result: result) + self?.save(model) { [weak self] result in + self?.kvsSaveCompletionRecursion(model, result: result) } } @@ -660,6 +652,16 @@ extension BtcWalletService { } } } + + private func makeKVSAddressModel(wallet: WalletAccount) -> KVSValueModel? { + guard let keypair = accountService.keypair else { return nil } + + return .init( + key: Self.kvsAddress, + value: wallet.address, + keypair: keypair + ) + } } // MARK: - Transactions diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 646994f55..560992f08 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -239,6 +239,7 @@ final class DashWalletService: WalletCoreProtocol, @unchecked Sendable { } } + @MainActor func update() async { guard let wallet = dashWallet else { return @@ -255,13 +256,12 @@ final class DashWalletService: WalletCoreProtocol, @unchecked Sendable { setState(.updating) if let balance = try? await getBalance() { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { - await vibroService.applyVibration(.success) + vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -284,12 +284,12 @@ final class DashWalletService: WalletCoreProtocol, @unchecked Sendable { } } - private func markBalanceAsFresh() { - dashWallet?.isBalanceInitialized = true + private func markBalanceAsFresh(_ wallet: DashWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = dashWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( @@ -331,6 +331,7 @@ extension DashWalletService { ) self.dashWallet = eWallet + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -347,9 +348,9 @@ extension DashWalletService { do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) let service = self - if address != eWallet.address { - service.save(dashAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(dashAddress: eWallet.address, result: result) + if address != eWallet.address, let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } @@ -371,9 +372,12 @@ extension DashWalletService { await service.update() } - service.save(dashAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(dashAddress: eWallet.address, result: result) + if let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) + } } + service.setState(.upToDate) return eWallet @@ -521,8 +525,8 @@ extension DashWalletService { /// - dashAddress: DASH address to save into KVS /// - adamantAddress: Owner of Dash address /// - completion: success - private func save(dashAddress: String, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { - guard let adamant = accountService.account, let keypair = accountService.keypair else { + private func save(_ model: KVSValueModel, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { + guard let adamant = accountService.account else { completion(.failure(error: .notLogged)) return } @@ -533,13 +537,7 @@ extension DashWalletService { } Task { @Sendable in - let result = await apiService.store( - key: DashWalletService.kvsAddress, - value: dashAddress, - type: .keyValue, - sender: adamant.address, - keypair: keypair - ) + let result = await apiService.store(model) switch result { case .success: @@ -552,7 +550,7 @@ extension DashWalletService { } /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save - private func kvsSaveCompletionRecursion(dashAddress: String, result: WalletServiceSimpleResult) { + private func kvsSaveCompletionRecursion(_ model: KVSValueModel, result: WalletServiceSimpleResult) { if let observer = balanceObserver { NotificationCenter.default.removeObserver(observer) balanceObserver = nil @@ -571,8 +569,8 @@ extension DashWalletService { return } - self?.save(dashAddress: dashAddress) { [weak self] result in - self?.kvsSaveCompletionRecursion(dashAddress: dashAddress, result: result) + self?.save(model) { [weak self] result in + self?.kvsSaveCompletionRecursion(model, result: result) } } @@ -584,6 +582,16 @@ extension DashWalletService { } } } + + private func makeKVSAddressModel(wallet: WalletAccount) -> KVSValueModel? { + guard let keypair = accountService.keypair else { return nil } + + return .init( + key: Self.kvsAddress, + value: wallet.address, + keypair: keypair + ) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 99a9aebd9..480dda84c 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -236,6 +236,7 @@ final class DogeWalletService: WalletCoreProtocol, @unchecked Sendable { } } + @MainActor func update() async { guard let wallet = dogeWallet else { return @@ -252,13 +253,12 @@ final class DogeWalletService: WalletCoreProtocol, @unchecked Sendable { setState(.updating) if let balance = try? await getBalance() { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { - await vibroService.applyVibration(.success) + vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -281,12 +281,12 @@ final class DogeWalletService: WalletCoreProtocol, @unchecked Sendable { } } - private func markBalanceAsFresh() { - dogeWallet?.isBalanceInitialized = true + private func markBalanceAsFresh(_ wallet: DogeWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = dogeWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( @@ -326,6 +326,7 @@ extension DogeWalletService { addressConverter: addressConverter ) self.dogeWallet = eWallet + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -342,9 +343,9 @@ extension DogeWalletService { let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) - if address != eWallet.address { - service.save(dogeAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(dogeAddress: eWallet.address, result: result) + if address != eWallet.address, let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } @@ -366,9 +367,12 @@ extension DogeWalletService { await service.update() } - service.save(dogeAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(dogeAddress: eWallet.address, result: result) + if let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) + } } + service.setState(.upToDate) return eWallet @@ -455,8 +459,8 @@ extension DogeWalletService { /// - dogeAddress: DOGE address to save into KVS /// - adamantAddress: Owner of Doge address /// - completion: success - private func save(dogeAddress: String, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { - guard let adamant = accountService.account, let keypair = accountService.keypair else { + private func save(_ model: KVSValueModel, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { + guard let adamant = accountService.account else { completion(.failure(error: .notLogged)) return } @@ -467,13 +471,7 @@ extension DogeWalletService { } Task { @Sendable in - let result = await apiService.store( - key: DogeWalletService.kvsAddress, - value: dogeAddress, - type: .keyValue, - sender: adamant.address, - keypair: keypair - ) + let result = await apiService.store(model) switch result { case .success: @@ -486,7 +484,7 @@ extension DogeWalletService { } /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save - private func kvsSaveCompletionRecursion(dogeAddress: String, result: WalletServiceSimpleResult) { + private func kvsSaveCompletionRecursion(_ model: KVSValueModel, result: WalletServiceSimpleResult) { if let observer = balanceObserver { NotificationCenter.default.removeObserver(observer) balanceObserver = nil @@ -505,8 +503,8 @@ extension DogeWalletService { return } - self?.save(dogeAddress: dogeAddress) { [weak self] result in - self?.kvsSaveCompletionRecursion(dogeAddress: dogeAddress, result: result) + self?.save(model) { [weak self] result in + self?.kvsSaveCompletionRecursion(model, result: result) } } @@ -518,6 +516,16 @@ extension DogeWalletService { } } } + + private func makeKVSAddressModel(wallet: WalletAccount) -> KVSValueModel? { + guard let keypair = accountService.keypair else { return nil } + + return .init( + key: Self.kvsAddress, + value: wallet.address, + keypair: keypair + ) + } } // MARK: - Transactions diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 6a78f4e92..7c4a86c1a 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -208,13 +208,6 @@ final class ERC20WalletService: WalletCoreProtocol, @unchecked Sendable { } .store(in: &subscriptions) - NotificationCenter.default - .notifications(named: .AdamantAccountService.accountDataUpdated, object: nil) - .sink { @MainActor [weak self] _ in - self?.update() - } - .store(in: &subscriptions) - NotificationCenter.default .notifications(named: .AdamantAccountService.userLoggedOut, object: nil) .sink { @MainActor [weak self] _ in @@ -245,6 +238,7 @@ final class ERC20WalletService: WalletCoreProtocol, @unchecked Sendable { } } + @MainActor func update() async { guard let wallet = ethWallet else { return @@ -261,13 +255,12 @@ final class ERC20WalletService: WalletCoreProtocol, @unchecked Sendable { setState(.updating) if let balance = try? await getBalance(forAddress: wallet.ethAddress) { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { - await vibroService.applyVibration(.success) + vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -348,12 +341,12 @@ final class ERC20WalletService: WalletCoreProtocol, @unchecked Sendable { }.get() } - private func markBalanceAsFresh() { - ethWallet?.isBalanceInitialized = true + private func markBalanceAsFresh(_ wallet: EthWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = ethWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 9ab46ed99..aeda63dc5 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -291,13 +291,12 @@ final class EthWalletService: WalletCoreProtocol, @unchecked Sendable { setState(.updating) if let balance = try? await getBalance(forAddress: wallet.ethAddress) { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -310,12 +309,12 @@ final class EthWalletService: WalletCoreProtocol, @unchecked Sendable { await calculateFee() } - private func markBalanceAsFresh() { - ethWallet?.isBalanceInitialized = true + private func markBalanceAsFresh(_ wallet: EthWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = ethWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( @@ -429,6 +428,7 @@ extension EthWalletService { // MARK: 3. Update ethWallet = eWallet + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -445,9 +445,9 @@ extension EthWalletService { let service = self do { let address = try await getWalletAddress(byAdamantAddress: adamant.address) - if eWallet.address.caseInsensitiveCompare(address) != .orderedSame { - service.save(ethAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(ethAddress: eWallet.address.lowercased(), result: result) + if eWallet.address.caseInsensitiveCompare(address) != .orderedSame, let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) } } @@ -469,8 +469,10 @@ extension EthWalletService { await service.update() } - service.save(ethAddress: eWallet.address) { result in - service.kvsSaveCompletionRecursion(ethAddress: eWallet.address, result: result) + if let kvsAddressModel { + service.save(kvsAddressModel) { result in + service.kvsSaveCompletionRecursion(kvsAddressModel, result: result) + } } return eWallet @@ -488,7 +490,7 @@ extension EthWalletService { } /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save - private func kvsSaveCompletionRecursion(ethAddress: String, result: WalletServiceSimpleResult) { + private func kvsSaveCompletionRecursion(_ model: KVSValueModel, result: WalletServiceSimpleResult) { if let observer = balanceObserver { NotificationCenter.default.removeObserver(observer) balanceObserver = nil @@ -507,8 +509,8 @@ extension EthWalletService { return } - self?.save(ethAddress: ethAddress) { [weak self] result in - self?.kvsSaveCompletionRecursion(ethAddress: ethAddress, result: result) + self?.save(model) { [weak self] result in + self?.kvsSaveCompletionRecursion(model, result: result) } } @@ -585,8 +587,8 @@ extension EthWalletService { /// - ethAddress: Ethereum address to save into KVS /// - adamantAddress: Owner of Ethereum address /// - completion: success - private func save(ethAddress: String, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { - guard let adamant = accountService?.account, let keypair = accountService?.keypair else { + private func save(_ model: KVSValueModel, completion: @escaping @Sendable (WalletServiceSimpleResult) -> Void) { + guard let adamant = accountService?.account else { completion(.failure(error: .notLogged)) return } @@ -597,13 +599,7 @@ extension EthWalletService { } Task { - let result = await apiService.store( - key: EthWalletService.kvsAddress, - value: ethAddress, - type: .keyValue, - sender: adamant.address, - keypair: keypair - ) + let result = await apiService.store(model) switch result { case .success: @@ -614,6 +610,16 @@ extension EthWalletService { } } } + + private func makeKVSAddressModel(wallet: WalletAccount) -> KVSValueModel? { + guard let keypair = accountService?.keypair else { return nil } + + return .init( + key: Self.kvsAddress, + value: wallet.address.lowercased(), + keypair: keypair + ) + } } // MARK: - Transactions diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift index 8bc1e616e..045164a30 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift @@ -229,6 +229,7 @@ private extension KlyWalletService { .store(in: &subscriptions) } + @MainActor func update() async { guard let wallet = klyWallet else { return @@ -245,13 +246,12 @@ private extension KlyWalletService { setState(.updating) if let balance = try? await getBalance() { - markBalanceAsFresh() - if wallet.balance < balance, wallet.isBalanceInitialized { - await vibroService.applyVibration(.success) + vibroService.applyVibration(.success) } wallet.balance = balance + markBalanceAsFresh(wallet) NotificationCenter.default.post( name: walletUpdatedNotification, @@ -275,12 +275,12 @@ private extension KlyWalletService { setState(.upToDate) } - func markBalanceAsFresh() { - klyWallet?.isBalanceInitialized = true + func markBalanceAsFresh(_ wallet: KlyWallet) { + wallet.isBalanceInitialized = true balanceInvalidationSubscription = Task { [weak self] in try await Task.sleep(interval: Self.balanceLifetime, pauseInBackground: true) - guard let self, let wallet = klyWallet else { return } + guard let self else { return } wallet.isBalanceInitialized = false NotificationCenter.default.post( @@ -442,7 +442,10 @@ private extension KlyWalletService { NotificationCenter.default.post(name: serviceEnabledChanged, object: self) } - guard let eWallet = self.klyWallet else { + guard + let eWallet = klyWallet, + let kvsAddressModel = makeKVSAddressModel(wallet: eWallet) + else { throw WalletServiceError.accountNotFound } @@ -452,7 +455,7 @@ private extension KlyWalletService { let address = try await getWalletAddress(byAdamantAddress: adamant.address) if address != eWallet.address { - updateKvsAddress(eWallet.address) + updateKvsAddress(kvsAddressModel) } setState(.upToDate) @@ -473,7 +476,7 @@ private extension KlyWalletService { await update() } - updateKvsAddress(eWallet.address) + updateKvsAddress(kvsAddressModel) return eWallet default: @@ -483,13 +486,13 @@ private extension KlyWalletService { } } - func updateKvsAddress(_ address: String) { + func updateKvsAddress(_ model: KVSValueModel) { Task { do { - try await save(klyAddress: address) + try await save(model) } catch { kvsSaveProcessError( - klyAddress: address, + model, error: error ) } @@ -498,7 +501,7 @@ private extension KlyWalletService { /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save func kvsSaveProcessError( - klyAddress: String, + _ model: KVSValueModel, error: Error ) { guard let error = error as? WalletServiceError, @@ -517,7 +520,7 @@ private extension KlyWalletService { guard let self = self else { return } Task { - try await self.save(klyAddress: klyAddress) + try await self.save(model) self.balanceObserver?.cancel() } } @@ -526,10 +529,8 @@ private extension KlyWalletService { /// - Parameters: /// - klyAddress: Klayr address to save into KVS /// - adamantAddress: Owner of Klayr address - func save(klyAddress: String) async throws { - guard let adamant = accountService.account, - let keypair = accountService.keypair - else { + func save(_ model: KVSValueModel) async throws { + guard let adamant = accountService.account else { throw WalletServiceError.notLogged } @@ -537,13 +538,7 @@ private extension KlyWalletService { throw WalletServiceError.notEnoughMoney } - let result = await apiService.store( - key: KlyWalletService.kvsAddress, - value: klyAddress, - type: .keyValue, - sender: adamant.address, - keypair: keypair - ) + let result = await apiService.store(model) guard case .failure(let error) = result else { return @@ -552,6 +547,16 @@ private extension KlyWalletService { throw WalletServiceError.apiError(error) } + func makeKVSAddressModel(wallet: WalletAccount) -> KVSValueModel? { + guard let keypair = accountService.keypair else { return nil } + + return .init( + key: Self.kvsAddress, + value: wallet.address, + keypair: keypair + ) + } + func getKlyWalletAddress( byAdamantAddress address: String ) async throws -> String { diff --git a/Adamant/ServiceProtocols/DataProviders/DataProvider.swift b/Adamant/ServiceProtocols/DataProviders/DataProvider.swift index 395453c24..1272b84f6 100644 --- a/Adamant/ServiceProtocols/DataProviders/DataProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/DataProvider.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -enum State { +enum DataProviderState { case empty case updating case upToDate @@ -17,8 +17,8 @@ enum State { } protocol DataProvider: AnyObject, Actor { - var state: State { get } - var stateObserver: AnyObservable { get } + var state: DataProviderState { get } + var stateObserver: AnyObservable { get } var isInitiallySynced: Bool { get } func reload() async @@ -26,10 +26,10 @@ protocol DataProvider: AnyObject, Actor { } // MARK: - Status Equatable -extension State: Equatable { +extension DataProviderState: Equatable { /// Simple equatable function. Does not checks associated values. - static func ==(lhs: State, rhs: State) -> Bool { + static func ==(lhs: DataProviderState, rhs: DataProviderState) -> Bool { switch (lhs, rhs) { case (.empty, .empty): return true case (.updating, .updating): return true diff --git a/Adamant/Services/AdamantAddressBookService.swift b/Adamant/Services/AdamantAddressBookService.swift index 088d35be9..5b346af36 100644 --- a/Adamant/Services/AdamantAddressBookService.swift +++ b/Adamant/Services/AdamantAddressBookService.swift @@ -90,7 +90,7 @@ final class AdamantAddressBookService: AddressBookService { // MARK: - Observer Actions private func userWillLogOut() async { - guard hasChanges else { + guard hasChanges, let keypair = accountService.keypair else { return } @@ -99,7 +99,7 @@ final class AdamantAddressBookService: AddressBookService { self.savingBookOnLogoutTaskId = .invalid } - _ = try? await saveAddressBook(self.addressBook) + _ = try? await saveAddressBook(self.addressBook, keypair: keypair) UIApplication.shared.endBackgroundTask(savingBookOnLogoutTaskId) savingBookOnLogoutTaskId = .invalid @@ -214,7 +214,7 @@ final class AdamantAddressBookService: AddressBookService { // MARK: - Saving func saveIfNeeded() async { - guard hasChanges else { + guard hasChanges, let keypair = accountService.keypair else { return } @@ -224,7 +224,7 @@ final class AdamantAddressBookService: AddressBookService { self.savingBookTaskId = .invalid } - guard let id = try? await saveAddressBook(addressBook) else { + guard let id = try? await saveAddressBook(addressBook, keypair: keypair) else { return } @@ -249,8 +249,8 @@ final class AdamantAddressBookService: AddressBookService { self.savingBookTaskId = .invalid } - private func saveAddressBook(_ book: [String: String]) async throws -> UInt64 { - guard let loggedAccount = accountService.account, let keypair = accountService.keypair else { + private func saveAddressBook(_ book: [String: String], keypair: Keypair) async throws -> UInt64 { + guard let loggedAccount = accountService.account else { throw AddressBookServiceError.notLogged } @@ -279,13 +279,11 @@ final class AdamantAddressBookService: AddressBookService { // MARK: 2. Submit to KVS do { - let id = try await apiService.store( + let id = try await apiService.store(.init( key: addressBookKey, value: value, - type: .keyValue, - sender: address, keypair: keypair - ).get() + )).get() return id } catch let error { diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index e75b27790..c2b78f1de 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -28,10 +28,10 @@ actor AdamantChatsProvider: ChatsProvider { let stack: CoreDataStack // MARK: Properties - @ObservableValue private var stateNotifier: State = .empty - var stateObserver: AnyObservable { $stateNotifier.eraseToAnyPublisher() } + @ObservableValue private var stateNotifier: DataProviderState = .empty + var stateObserver: AnyObservable { $stateNotifier.eraseToAnyPublisher() } - private(set) var state: State = .empty + private(set) var state: DataProviderState = .empty private(set) var receivedLastHeight: Int64? private(set) var readedLastHeight: Int64? private let apiTransactions = 100 @@ -228,7 +228,7 @@ actor AdamantChatsProvider: ChatsProvider { // MARK: Tools /// Free stateSemaphore before calling this method, or you will deadlock. - private func setState(_ state: State, previous prevState: State, notify: Bool = true) { + private func setState(_ state: DataProviderState, previous prevState: DataProviderState, notify: Bool = true) { self.state = state guard notify else { return } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index c64361867..158ef01fb 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -25,8 +25,8 @@ actor AdamantTransfersProvider: TransfersProvider { private let transactionService: ChatTransactionService weak var chatsProvider: ChatsProvider? - @ObservableValue private(set) var state: State = .empty - var stateObserver: AnyObservable { $state.eraseToAnyPublisher() } + @ObservableValue private(set) var state: DataProviderState = .empty + var stateObserver: AnyObservable { $state.eraseToAnyPublisher() } private(set) var isInitiallySynced: Bool = false private(set) var receivedLastHeight: Int64? private(set) var readedLastHeight: Int64? @@ -41,7 +41,7 @@ actor AdamantTransfersProvider: TransfersProvider { // MARK: Tools /// Free stateSemaphore before calling this method, or you will deadlock. - private func setState(_ state: State, previous prevState: State, notify: Bool = false) { + private func setState(_ state: DataProviderState, previous prevState: DataProviderState, notify: Bool = false) { self.state = state if notify { diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/Contents.json new file mode 100644 index 000000000..092b5c8bf --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "clipboard.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "clipboard@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "clipboard@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..a95528e0c2d3d4b1585dc662ec77af5279d8093d GIT binary patch literal 1307 zcmV+$1?2jPP)KuaB}j;LK$WE+DHEiC=&~V* zHjf_&KJmSo$zyGts20rx)97|C8+YoR}AS^H>h+S-criTyEoH{_9HSSOhW za`QS8cgaGZ;9kVRp-jCBbyMw&flC{U($5{}MvgbUl*$++u3H;M@}iGsuAi6kp0%3@ z!%ZZ0Ol8g)DJP{r=dHb$WAw1wD1kweh@V+Ikg{uXe7ehh6NsDVCc3mSC+DtZ`t?kX z(^U(D(3o5}lc|7q)Y_VyZ{^%OhcHrkF4mIszq|B{_D#-rbx*~#Ty#ID5`9Y3Xcl?i zr1n_OPZd?NA{U*@sf44qEZ4k1GZ;rQYDaVWbrQ+Q=R)1%K!K6Cy60;YgyKL9N&Oqr z-no)~Y2UYyj+REsWfbEFoZB)SkHNVpeSH*hVBkh;QR-(q*QMPes6slT2hs{9okz;> z1uhgbWt>9dqN}K&911Xm?GbMl?m>DU>$NwtZnTrn#XN88O8{Gqsk3c$!R3)SUZdUfYNGF`Z9v@yjOXd zLFZWC(Ld-6oTwC$tCE|M@(g+>x5{9UsGu?xwTY^3$;wEOE7>#y1@R#=$38?&_$;S0ppaMxE}`%0p`C!sZ=6z{LQVLPyC5Yc zSR1{?JQAsQ8Z{APBV=IVL+-Pbocvl#7}z%RT-_ZW;;)q4<=Gsl?1ydoMZhoyXhWj{ z=xx8?NbT2y*?`iKas>Yu)MK{2^I39VrK~+M>@#%9W7~>&mYn97er;3vfGSA`<4yWR zmRwtmKHTr^swzlttGz+SExUawYv7j5jr?aWW8~tl3M1Kis%#2;*c$2?4@gQQG|_=u ztm;&$%)mW~jR7}Ccl`)&2g<&2HgM94yDP_$O#Ka-MS3ZNuD2xR6bhC&{sB^>a<0za RK41U<002ovPDHLkV1m->Q5^sP literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/clipboard.imageset/clipboard@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7ec9d3415a86ecb7bbf5bb2e04569a60a3ef0736 GIT binary patch literal 2613 zcmV-53d;3~P)=IycPxRb$uH!FEPkx0qc7#3+SIZ|>T@q6)BB1VQ1Z4^hP@Z|O@2~q6R zn_zhGzJvvTixA(F%StYxAx%sr6?Po)fcct|C5(>PIs}OC+0TgY;Sw59S8*X?ulX`! z1$M+1E4vWwHg7SOyt>$;Gp(UZ_OOMSsk@d+jO-^J5{wnPZs&Nzb9`YjPg=oQf@nzR5 zoCM{)7ALX{zZAxpm#Pz}q2}}NqS`%^R;ucQqD7v>-ijxoJCXCKPLQ1VO}JB5lVQ(! z@jbK5WLEZS&Y_U9&Pj-A6UWZN`>4)#B|nL}571vv$`bMqCwq`LP#xK+E4*K2!+}tg zucIC}G@s#xbvbZ%MSy%H*sQUejw_SD#sk!cPuy8i$H9D`l*elcMP1X-3u&PWTU^;YZcUV%&Pvl}^Ii9rsHbO~ z9tGzSQw`yR%`t8$>Wc>4Rp7n4mnj#9NffoXK$P2fF8J9mk$(c&^4N0=F@J>6pF6G6 z0W{|srtft+rIiyx99< z>!TSmeeqqAn#Hr`L1Gz<2Cdfnz`Gs zu(q#j#W6ne8~7C`X#@5^)829?bfq}<1WzjY0>flSBD69ly(<6H)jN*yB12VL7P|F1 zNwd*Ki(?S!ZcY6J27)|2CqUi_O^3o= zhnRL6M;MmUNQC&Bv$Gm}=WmBWOe;?I9w!)<(o!NNKZrV`J!{j7ljF21D_i=CT#jXQ z^-vX0Pi0>sOKmqa}$V2%1OyBNnYr^&CFo&a4!!|Vb*K$pm^HC=R0c%sKJ zLW+WcThmpql3sBx$?_p}p)1m$qE2Sw7GeI-Ex+P}$yl4uU;$f-qbZR} zn;>?NRY@c0BU}PTvma*a*z!2j-T5jWTc%P*d~xAEr}yVoX@uvlOCWOu7OoeA=RL7jI}M$bt%biSs}hT`OUuK@kZn$Av2wufnDF{BN} z@gn53eV=wel-FWqF{Uk3DP2O$D#Ub30p=IBG+o}43$TE#aU2!LAwjlxxhIh)n9qD( zfu@e*H!FTio1PO60d(GzDoJ4G0nQdQNp;u=o4ra?xvqS~WJ;(?*d7j~;SBSZltxFI z|0GRTWA%Y_H6NFq!}4KAk0v;^96ro|4>=O7F{9^XPVp_X5Qht&!>>@jG*#7)FdYBi z2456MMbXRd>^E?b}G)Lg<)5*C9OIq zktlCMcQBoV^@V{g;JG@_mQu zA>M3V!j{L$M6bfLJ%-9+&yhjE`=EDf&_rIE^+i<`N0y*hUKT1Z-A$M+G0o}t6wlpd z+5h*21x=Ysp}%!wA{+;ALa_&m?=!C2T|oyhE`-kKwNZ@9DlEm^EMrul33;5{OY3Xk znKfYrqY~@LD&p|<5G|w#>1X3CvNUm`NO&WGdYRkAv0m4gZOA)5R zD3Sc!1Sv&gB%GG(5T!|xwYya0{xfgb-N!p~@7#|!ANTyE(X;2heYN@)Fcom>&wVHdc4IrjdsmzCtEMbr#hDZTZpvSqavfHjKiu${A2j8^u-BJ8C0R;Z zNwcB;c2k~58QkBLKfqegb-0fCBnserD1!>}+TH6Y2U_`eg!kA>Jc0r!MU+yv{WT}xD7g13v$)TcDl0!wQB**uW7H0K& zfI@aBGQg}}Yb3(9MQLD{Z}@r&c3|$DZXaCxoQiUK432NjvdwE8Iu_r-p@Z?(?#I-k zq|nYbmmIgSf9TIRFJu2VDBUEC+{m= z1S?Fy*rKdpyMr5;=nqzsfjuDaGw=5TBQlmK4eTFy18G^r`%xw*h5FDgb&SP!18Y4m zW2AdOhA1o8@Pi8RsD=8+Q6|!L)d*wE9iRn9GD<^5`P%9a3As4#+tR;r7qYX&qX^@c zP*4BEhfx;1CP|dH5QcQ$c#keL$eL?|r5zFQENF=RKgIdJf?a z$^YqVDGk~q>F6|YzA2BvT1C2Ct$Tgn6@IqQlXgj{XCr=y{KuEh!B-N*y9no$P`}=T z@xIY&@_s(GdiA*kQka~b8}y*BDBHShK4nkCcGEuoF4UhwIJ6+rK>tir{s?Q8a~o%J zQu9_*{sRA4a*1%HhyP3X;eDkzFL*vCy9hKep`xrHj}!RILuP7@D?NV@8hjcVM<-OL7=q^ z73Ho_Pcgoaeq0yoEA$&Eb6m+-l}jiaUf%-!s34CgucIFnrDT*|L;xY8tRRb|@6$>) z&B`6S=nwyWC1Z=K^h*G)`UvynpBrChXupI)9 zFS!uUn@G#DM1C%D{Ck(MOFqx7$`buCkS(llHr)lE7AkZkWbwQb>aSop6y;mOdzWEv z#1NJ!BzwdNd@R({N{$iz$?kvC($%x*NLNc~AX}_4E`;5;w5D01-%MQjE}Y!>QrnCX zBk#M9oo3-(MJZhfP4!s8{_HxWvE^tUC^N=1WlwmIR&tCU&wZD4A3N>T4He~>!T!7$ zX>94Xt@=`IN z4E%y5V|UvQ$HiEy9?KbQ*|!jN!?ZKP>_1MNeEJxmZ$ml+fz z*C5#rZQGA+UOiLuP}>K(h|&o4cQHL~Al^1RG_VsOCr*QTKrynX;B~);_Awi~>um{A zn(VZWE4yqnN?$T{_wArC&#!*pTP>e8IGmj?oAL>)8`~0P;4)aS(DSAW_}L4Xw#!~e z>b?zH7$VATpR&LPr7+({w+O`w$8VYoE~}9r2L7#HA1+pEi1*J{NiFMI8|=o}xU`tB z1paKNHL}8BckSE0os!-&@F8Q$ZFS$9e(!m-N7T(3KDZom0s25v0S_aIw~C^2OAy^VFszBqxpB@l|zc|)H>ez2`6MxU=@F_h4K zcY!z3S(M@|OKIdh(NPLTMX4l*ic(1q6{Rv2(&mam3o1$_IZ~W`bNS{R>{{a!C^wmY zOt>1sHBN3iaJ@6B?b?(?>5_wnI$9{<@IiN*3xqpDQ^o59lS=pfa~x*cRkjbeXPLVC z86-doqNH`CW3G17abZtIVMaF$u|-J>+2&2IY!`-AM9m8yQmc}kMYwTvZ7 z-z<*}HJ3#Cv01{V;NAoUc?J=PB}(aH_fcMs^3+*E?vbJ_5ykAjH{l!SDAOUGr11nI z5KEL}ox-Y&gVgS`8dFN-c5;I<0kK5cJJL;=1S8Z(i3f@$N-6o#>ri6gJM3xe#1f?x zyv1~O2Ff-ukkcskJrZvau|z2)L)B9twu^yHuqrX;=^vemX(^=(W+E!)G+#iOlC$t< z{H2d&6#vSYPQG+1JuSRvVBOi6ps9u~sE>((eem0QqiyE0JVs1Wx(pUR*EItjW5$5S zAet9?>~%_ta_obJlwnMHYD6beSUhJczi3^Ba*S=Kpx#I-J34F#O@bN^u17MbWfI0A zrESKeNl-Nj)%3WeU)->G8VR*|igDjA2`X`}Q#q#ZOO@o)3Z|Y4P}BF#5KY^nB!`Mp zNsinz`O*pX0VwOlI!Lj1wSk z;U>9LmU8#~wouR5aON9gqt{S2!Q=vqru^FTnPl^VrYcI`Nwi$zSVHLndlE^Q$)5I` zswkxyVh!8SN_W^^Qs$ABn2}d zRZ&WNv<6Bac7*yQJzW2$raQ_s-omh zgVeQfiDL&!7x(~6OK7x`NxHJ7JkgP|G;p%R1X#>9WT0$<5A8+RgAF92X2I~0^4>R{ zQt1aMEe|bLrG{=dI-E0xY55$(C6Y~-le(7>1LfO1g$&eUhTyVcBunb+lpOcG17$2Y zPJaW5hehPDc+PHyPqyY68iuowNiG8&C6^r?&c@lGnl`W>oYXvz3$i4e+<0Q%)Z_;5U+c9GsC5nvjLG=QG((`brcfZ`3~K!5-vPHm@R6auubNn2DBgAM;{Q zsOMxS%b9qtqLg-OZeo5xWPA{p!h11~bdl~+jTUkhr6#~S&K;pX%cY^xyoBbO0IR4| z*K!r5wA675^TY`CSssViMU>LAO3bIzmutviUKq%W>BWlju23IyyuzR;$s^_>=EY^9 zo`TfH^DT2ls6UI0x7Y&U_?d#Ng!ex{T%^h9U9^z1D1D8-*OaedcRfm*{P23XTg4<_ z3*j@glAj_?T6fvH?0u&=rX*~5I=4#a!5COe!g7EzwjA3|6LTW?Vk7CJ)i@04zEfG4O+I1M7U2Z&JkmhwNk?&1&-6ovU6Bfn%dgbZVhK#qiautHOipUw|EJCV%L9 z5BT6Qi9VObl01pF;m1OKt0+&P9Rs_(vJRinr;!IG1(oH}6Z}i4e+VtY95VaPDbQah zLfgeR^B!eP>HDI&hqWr3k^DGobfD`*r$*Q>SLTg0j=zHrbQR?nLcJy{RW>om3A?K( zr7wF1it=3~OSu(Mo`#|vhm#yh7d1K%BFY2d4^V5V97WPYjgc+of#(a)Kv52PL8xD% zBO%+2p9u8~de;O~3ULW*H*pgk2@&PKP)|Y5H7}%c9LcwpJ?%AgBt(?Hpm&N=7t-gA z&q`8|ot><^?0p}lYRV1aJw_P*49Xl~Z^=x`l3{uj0hvoeUDuZIrwEx_B%xvj>B}Iz zkHAzqp_Y;uZ1x&*(4}gQevNa4UG|5N7t7gYKZ!t5-iL7bix?&;Ed`xiy+?;rJ=C*Y zIFI-}{9DbxL63-lK9^;p!$lm@daC8jA2*qyAlsG!nK!dnywDI)GCGixo}G@L zcZU(NP^@Z2(}yDDmf=rfcWSzNg?$7$CcTv*q9iU$!&&sh9p~m!jNCi&f5)bLI9cY% ziNr}y9j)C1I6R{I;nn{iM}H5nL4q8Uy{3GlW%ebN@ZN7Ru(R@98c@r4aVir6`ZGbEKSFz0M`KPtG`( z#V=N`OKjnwgKdd&6Ye-SzM9qRBdhn!k ApiServiceResult + func store(_ model: KVSValueModel) async -> ApiServiceResult func get( key: String, diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift index 705d51a1d..d68340d7d 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift @@ -20,27 +20,27 @@ public extension ApiCommands { extension AdamantApiService { public static let KvsFee: Decimal = 0.001 - public func store( - key: String, - value: String, - type: StateType, - sender: String, - keypair: Keypair - ) async -> ApiServiceResult { + public func store(_ model: KVSValueModel) async -> ApiServiceResult { let transaction = NormalizedTransaction( type: .state, amount: .zero, - senderPublicKey: keypair.publicKey, + senderPublicKey: model.keypair.publicKey, requesterPublicKey: nil, date: .now, recipientId: nil, - asset: TransactionAsset(state: StateAsset(key: key, value: value, type: .keyValue)) + asset: TransactionAsset(state: StateAsset( + key: model.key, + value: model.value, + type: .keyValue + )) ) guard let transaction = adamantCore.makeSignedTransaction( transaction: transaction, - senderId: sender, - keypair: keypair + senderId: AdamantUtilities.generateAddress( + publicKey: model.keypair.publicKey + ), + keypair: model.keypair ) else { return .failure(.internalError(error: InternalAPIError.signTransactionFailed)) } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 10833013f..cea132917 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -48,28 +48,26 @@ public final class BlockchainHealthCheckWrapper< } } - public override func healthCheckInternal() { - super.healthCheckInternal() + public override func healthCheckInternal() async { + await super.healthCheckInternal() + updateNodesAvailability(update: nil) - Task { @HealthCheckActor in - updateNodesAvailability(update: nil) - - await withTaskGroup(of: Void.self, returning: Void.self) { group in - nodes.filter { $0.isEnabled }.forEach { node in - group.addTask { @HealthCheckActor [weak self] in - guard let self, !currentRequests.contains(node.id) else { return } - - currentRequests.insert(node.id) - defer { currentRequests.remove(node.id) } - - let update = await updateNodeStatusInfo(node: node) - updateNodesAvailability(update: update) - } + try? await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in + nodes.filter { $0.isEnabled }.forEach { node in + group.addTask { @HealthCheckActor [weak self] in + guard let self, !currentRequests.contains(node.id) else { return } + + currentRequests.insert(node.id) + defer { currentRequests.remove(node.id) } + + let update = await updateNodeStatusInfo(node: node) + try Task.checkCancellation() + updateNodesAvailability(update: update) } - - await group.waitForAll() - healthCheckPostProcessing() } + + try await group.waitForAll() + healthCheckPostProcessing() } } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 7835fc2f6..fefbffad6 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -45,7 +45,9 @@ open class HealthCheckWrapper: S private let crucialUpdateInterval: TimeInterval private let isActive: Bool private var healthCheckTimerSubscription: AnyCancellable? - private var previousAppState: UIApplication.State? + private var healthCheckSubscriptions = Set() + private var appState: AppState = .active + private var performHealthCheckWhenBecomeActive = false private var lastUpdateTime: Date? public nonisolated init( @@ -112,17 +114,38 @@ open class HealthCheckWrapper: S nonisolated public func healthCheck() { Task { @HealthCheckActor in - guard isActive else { return } + guard canPerformHealthCheck else { return } lastUpdateTime = .now updateHealthCheckTimerSubscription() - healthCheckInternal() + + Task { + await healthCheckInternal() + guard Task.isCancelled else { return } + performHealthCheckWhenBecomeActive = true + }.store(in: &healthCheckSubscriptions) } } - open func healthCheckInternal() {} + open func healthCheckInternal() async {} } private extension HealthCheckWrapper { + private enum AppState { + case active + case background + } + + var canPerformHealthCheck: Bool { + guard isActive else { return false } + + switch appState { + case .active: + return true + case .background: + return false + } + } + func configure(nodes: AnyObservable<[Node]>, connection: AnyObservable) { let connection = connection .removeDuplicates() @@ -149,7 +172,7 @@ private extension HealthCheckWrapper { NotificationCenter.default .notifications(named: UIApplication.willResignActiveNotification, object: nil) - .sink { @HealthCheckActor [weak self] _ in self?.previousAppState = .background } + .sink { @HealthCheckActor [weak self] _ in self?.willResignActiveAction() } .store(in: &subscriptions) } @@ -172,17 +195,22 @@ private extension HealthCheckWrapper { } func didBecomeActiveAction() { - defer { previousAppState = .active } + guard appState != .active else { return } + appState = .active - guard - previousAppState == .background, - let timeToUpdate = lastUpdateTime?.addingTimeInterval(normalUpdateInterval / 3), - Date.now > timeToUpdate - else { return } + let timeToUpdate = lastUpdateTime?.addingTimeInterval(normalUpdateInterval / 3) + ?? .adamantNullDate + guard performHealthCheckWhenBecomeActive || Date.now >= timeToUpdate else { return } + performHealthCheckWhenBecomeActive = false healthCheck() } + func willResignActiveAction() { + appState = .background + healthCheckSubscriptions = .init() + } + func updateNodes(_ newNodes: [Node]) { nodes = newNodes updateSortedNodes() diff --git a/PopupKit/Sources/PopupKit/Implementation/Services/AutoDismissManager.swift b/PopupKit/Sources/PopupKit/Implementation/Services/AutoDismissManager.swift index 4a0e67b8c..2f625f49d 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Services/AutoDismissManager.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Services/AutoDismissManager.swift @@ -49,4 +49,4 @@ private extension AutoDismissManager { } } -private let autoDismissTimeInterval: TimeInterval = 4 +private let autoDismissTimeInterval: TimeInterval = 3 diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationPresenterView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationPresenterView.swift index 75aeefe07..2bb645388 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationPresenterView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationPresenterView.swift @@ -13,95 +13,103 @@ struct NotificationPresenterView: View { case vertical case horizontal } - - @State private var verticalDragTranslation: CGFloat = .zero - @State private var horizontalDragTranslation: CGFloat = .zero - @State private var minTranslationYForDismiss: CGFloat = .infinity - @State private var minTranslationXForDismiss: CGFloat = .infinity @State private var isTextLimited: Bool = true @State private var dismissEdge: Edge = .top - @State private var dragDirection: DragDirection? + @State private var dragDirection: DragDirection? + @State private var dynamicHeight: CGFloat = 0 + @State private var notificationHeight: CGFloat = 0 + @State private var offset: CGSize = .zero let model: NotificationModel let safeAreaInsets: EdgeInsets let dismissAction: () -> Void var body: some View { - NotificationView( - isTextLimited: $isTextLimited, - model: model - ) - .padding([.leading, .trailing], 10) - .padding([.top, .bottom], 10) + VStack { + NotificationView( + isTextLimited: $isTextLimited, + model: model + ) + .padding(10) + .padding([.top, .bottom], 10) + .background( + GeometryReader { geometry in + Color.clear + .onAppear { + notificationHeight = geometry.size.height + print("onappier") + } + .onChange(of: geometry.size.height) { newValue in + notificationHeight = newValue + } + } + ) + } + .frame(minHeight: notificationHeight + dynamicHeight) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.init(uiColor:.adamant.chatInputBarBorderColor), lineWidth: 1) ) .background(GeometryReader(content: processGeometry)) - .expanded(axes: .horizontal) - .offset(y: verticalDragTranslation < .zero ? verticalDragTranslation : .zero) - .offset(x: horizontalDragTranslation < .zero ? horizontalDragTranslation : .zero) - .gesture(dragGesture) .onTapGesture(perform: onTap) + .gesture(dragGesture) .cornerRadius(10) .padding(.horizontal, 15) .padding(.top, safeAreaInsets.top) + .offset(offset) + .animation(.interactiveSpring(), value: offset) .transition(.move(edge: dismissEdge)) } } - +private extension NotificationPresenterView { + func processGeometry(_ geometry: GeometryProxy) -> some View { + return Color.init(uiColor: .adamant.swipeBlockColor) + .cornerRadius(10) + } + func onTap() { + model.tapHandler?.value() + dismissAction() + dismissEdge = .top + } +} private extension NotificationPresenterView { var dragGesture: some Gesture { DragGesture() - .onChanged { - if dragDirection == nil { - dragDirection = abs($0.translation.height) > abs($0.translation.width) ? .vertical : .horizontal + .onChanged { value in + if dragDirection == nil || (abs(value.translation.width) <= 5 && abs(value.translation.height) <= 5) { + detectDragDirection(value: value) } - switch dragDirection { - case .vertical: - verticalDragTranslation = $0.translation.height - case .horizontal: - horizontalDragTranslation = $0.translation.width - case .none: - break + if dragDirection == .vertical && isTextLimited { + dynamicHeight = max(0, min(value.translation.height, 30)) + } + if dragDirection == .vertical, value.translation.height < 0 { + offset = CGSize(width: 0, height: value.translation.height) + } else if dragDirection == .horizontal, value.translation.width < 0 { + offset = CGSize(width: value.translation.width, height: 0) } } - .onEnded { - if $0.velocity.height < -100 || -$0.translation.height > minTranslationYForDismiss { - dismissEdge = .top - Task { dismissAction() } - } else if $0.velocity.width < -100 || $0.translation.width > minTranslationXForDismiss { - dismissEdge = .leading - Task { dismissAction() } - } else if $0.velocity.height > -100 || -$0.translation.height < minTranslationYForDismiss { - withAnimation { - horizontalDragTranslation = .zero + .onEnded { value in + if dragDirection == .vertical { + if value.translation.height > 25 { + model.cancelAutoDismiss?.value() isTextLimited = false + } else if value.translation.height < -30 { + Task { dismissAction() } } - model.cancelAutoDismiss?.value() - } else { - withAnimation { - verticalDragTranslation = .zero - horizontalDragTranslation = .zero + } else if dragDirection == .horizontal { + if value.translation.width < -100 { + dismissEdge = .leading + Task { dismissAction() } } } dragDirection = nil + dynamicHeight = 0 + offset = .zero } } - func processGeometry(_ geometry: GeometryProxy) -> some View { - DispatchQueue.main.async { - minTranslationYForDismiss = geometry.size.height / 2 - minTranslationXForDismiss = geometry.size.width / 2 - } - - return Color.init(uiColor: .adamant.swipeBlockColor) - .cornerRadius(10) - } - - func onTap() { - model.tapHandler?.value() - dismissAction() - dismissEdge = .top + func detectDragDirection(value: DragGesture.Value) { + let horizontalDistance = abs(value.translation.width), verticalDistance = abs(value.translation.height) + dragDirection = verticalDistance > horizontalDistance ? .vertical : .horizontal } } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift index d30848285..bf2515df2 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift @@ -1,6 +1,6 @@ // // NotificationView.swift -// +// // // Created by Andrey Golubenko on 06.12.2022. // @@ -21,7 +21,6 @@ struct NotificationView: View { textStack Spacer(minLength: .zero) } - Image(systemName: isTextLimited ? pullDownIcon : pullUpIcon) .font(.title) .foregroundColor(.gray) @@ -50,7 +49,6 @@ private extension NotificationView { Text(description) .font(.system(size: 13)) .lineLimit(isTextLimited ? 3 : nil) - } } } From 181a54d163331ab6b63c66de86a1855b325098f1 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Fri, 31 Jan 2025 20:56:00 +0500 Subject: [PATCH 03/12] [trello.com/c/avWMqc8I] integrating PinPadView WIP --- Adamant.xcodeproj/project.pbxproj | 4 + .../Helpers/LocalAuthenticationHandler.swift | 108 ++++++++++++++ Adamant/Helpers/MyLittlePinpad+adamant.swift | 138 ++++++++++-------- Adamant/Helpers/PinPadView.swift | 11 +- .../AccountViewController+StayIn.swift | 45 +++--- .../Login/LoginViewController+Pinpad.swift | 37 ++--- .../SecurityViewController+StayIn.swift | 38 ++--- .../VisibleWalletsService.swift | 2 +- 8 files changed, 254 insertions(+), 129 deletions(-) create mode 100644 Adamant/Helpers/LocalAuthenticationHandler.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 967c4f5b1..1d90bf501 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -466,6 +466,7 @@ A5F929B6262C858700C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B5262C858700C3E60A /* MarkdownKit */; }; A5F929B8262C858F00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B7262C858F00C3E60A /* MarkdownKit */; }; D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA7882D42745D0069BC73 /* PinPadView.swift */; }; + D39AA78B2D47A0D80069BC73 /* LocalAuthenticationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */; }; E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */; }; E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F620EC200900D0CB2D /* SecurityViewController.swift */; }; E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */; }; @@ -1110,6 +1111,7 @@ AD258997F050B24C0051CC8D /* Pods-Adamant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.release.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.release.xcconfig"; sourceTree = ""; }; ADDFD2FA17E41CCBD11A1733 /* Pods-Adamant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.debug.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.debug.xcconfig"; sourceTree = ""; }; D39AA7882D42745D0069BC73 /* PinPadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinPadView.swift; sourceTree = ""; }; + D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationHandler.swift; sourceTree = ""; }; E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; E90055F620EC200900D0CB2D /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecurityViewController+StayIn.swift"; sourceTree = ""; }; @@ -2407,6 +2409,7 @@ 64A223D520F760BB005157CB /* Localization.swift */, E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, D39AA7882D42745D0069BC73 /* PinPadView.swift */, + D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */, E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, 6414C18D217DF43100373FA6 /* String+adamant.swift */, @@ -3573,6 +3576,7 @@ E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, E90847332196FEA80095825D /* TransferTransaction+CoreDataProperties.swift in Sources */, 9366588D2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift in Sources */, + D39AA78B2D47A0D80069BC73 /* LocalAuthenticationHandler.swift in Sources */, E99818942120892F0018C84C /* WalletViewControllerBase.swift in Sources */, 3AA6DF462BA9BEB700EA2E16 /* MediaContentView.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, diff --git a/Adamant/Helpers/LocalAuthenticationHandler.swift b/Adamant/Helpers/LocalAuthenticationHandler.swift new file mode 100644 index 000000000..0e520596a --- /dev/null +++ b/Adamant/Helpers/LocalAuthenticationHandler.swift @@ -0,0 +1,108 @@ +// +// LocalAuthenticationHandler.swift +// Adamant +// +// Created by Brian on 27/01/2025. +// Copyright © 2025 Adamant. All rights reserved. +// + +import UIKit + +protocol LocalAuthenticationHandler { + func didEnter(pin: String, viewController: UIViewController) + func didTapBiometryButton(viewController: UIViewController) + func didTapCancel(viewController: UIViewController) +} + +final class LocalAuthenticationHandlerImpl: LocalAuthenticationHandler { + func didEnter(pin: String, viewController: UIViewController) { + switch pinpadRequest { + + // MARK: User has entered new pin first time. Request re-enter pin + case .createPin?: + pinpadRequest = .reenterPin(pin: pin) + pinpad.commentLabel.text = String.adamant.pinpad.reenterPin + pinpad.clearPin() + return + + // MARK: User has reentered pin. Save pin. + case .reenterPin(let pinToVerify)?: + guard pin == pinToVerify else { + pinpad.playWrongPinAnimation() + pinpad.clearPin() + break + } + + let result = accountService.setStayLoggedIn(pin: pin) + Task { @MainActor in + switch result { + case .success: + self.pinpadRequest = nil + if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { + row.value = false + row.updateCell() + row.evaluateHidden() + } + + if let section = self.form.sectionBy(tag: Sections.notifications.tag) { + section.evaluateHidden() + } + + if let section = self.form.sectionBy(tag: Sections.aboutNotificationTypes.tag) { + section.evaluateHidden() + } + + pinpad.dismiss(animated: true, completion: nil) + + case .failure(let error): + self.dialogService.showRichError(error: error) + } + } + + // MARK: Users want to turn off the pin. Validate and turn off. + case .turnOffPin?: + guard accountService.validatePin(pin) else { + pinpad.playWrongPinAnimation() + pinpad.clearPin() + break + } + + accountService.dropSavedAccount() + + pinpad.dismiss(animated: true, completion: nil) + + // MARK: User wants to turn on biometry + case .turnOnBiometry?: + guard accountService.validatePin(pin) else { + pinpad.playWrongPinAnimation() + pinpad.clearPin() + break + } + + accountService.updateUseBiometry(true) + pinpad.dismiss(animated: true, completion: nil) + + // MARK: User wants to turn off biometry + case .turnOffBiometry?: + guard accountService.validatePin(pin) else { + pinpad.playWrongPinAnimation() + pinpad.clearPin() + break + } + + accountService.updateUseBiometry(false) + pinpad.dismiss(animated: true, completion: nil) + + default: + pinpad.dismiss(animated: true, completion: nil) + } + + } + + func didTapBiometryButton(viewController: UIViewController) { + } + + func didTapCancel(viewController: UIViewController) { + } +} + diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index ed3348e5d..d511ed73a 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -7,7 +7,8 @@ // import Foundation -import MyLittlePinpad +//import MyLittlePinpad +import SwiftUI extension String.adamant { enum pinpad { @@ -20,65 +21,86 @@ extension String.adamant { } } -extension PinpadBiometryButtonType { - var localAuthType: BiometryType { - switch self { - case .hidden: - return .none - - case .faceID: - return .faceID - - case .touchID: - return .touchID - } - } -} +//extension PinpadBiometryButtonType { +// var localAuthType: BiometryType { +// switch self { +// case .hidden: +// return .none +// +// case .faceID: +// return .faceID +// +// case .touchID: +// return .touchID +// } +// } +//} -extension BiometryType { - var pinpadButtonType: PinpadBiometryButtonType { - switch self { - case .none: - return .hidden - - case .faceID: - return .faceID - - case .touchID: - return .touchID - } - } -} +//extension BiometryType { +// var pinpadButtonType: PinpadBiometryButtonType { +// switch self { +// case .none: +// return .hidden +// +// case .faceID: +// return .faceID +// +// case .touchID: +// return .touchID +// } +// } +//} -extension PinpadViewController { - static func adamantPinpad(biometryButton: PinpadBiometryButtonType) -> PinpadViewController { - let pinpad = PinpadViewController.instantiateFromResourceNib() - - pinpad.bordersColor = UIColor.adamant.secondary - pinpad.setColor(UIColor.adamant.primary, for: .normal) - pinpad.buttonsHighlightedColor = UIColor.adamant.pinpadHighlightButton - pinpad.buttonsFont = UIFont.adamantPrimary(ofSize: pinpad.buttonsFont.pointSize, weight: .light) - - pinpad.placeholdersSize = 15 - - if pinpad.view.frame.height > 600 { - pinpad.buttonsSize = 75 - pinpad.buttonsSpacing = 20 - pinpad.placeholderViewHeight = 50 - } else {// iPhone 5 - pinpad.buttonsSize = 70 - pinpad.buttonsSpacing = 15 - pinpad.placeholderViewHeight = 25 - pinpad.bottomSpacing = 24 - pinpad.pinpadToCancelSpacing = 14 - } - - pinpad.placeholderActiveColor = UIColor.adamant.pinpadHighlightButton - pinpad.biometryButtonType = biometryButton - pinpad.cancelButton.setTitle(String.adamant.alert.cancel, for: .normal) - pinpad.pinDigits = 6 - - return pinpad +extension UIViewController { + func adamantPinpad(biometryButton: BiometryType, onSuccess: (String) -> UIViewController { + let pinPadView = PinPadViewRepresentable( + pinLength: 6, + validatePin: { $0 == "123456" }, + onSuccess: { print("Pin validated!") }, + onCancel: { print("Pin entry canceled.") } + ) + + let hostingController = UIHostingController(rootView: pinPadView) + addChild(hostingController) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingController.view) + + NSLayoutConstraint.activate([ + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + hostingController.didMove(toParent: self) + return hostingController +// let pinpad = PinpadViewController.instantiateFromResourceNib() +// +// pinpad.bordersColor = UIColor.adamant.secondary +// pinpad.setColor(UIColor.adamant.primary, for: .normal) +// pinpad.buttonsHighlightedColor = UIColor.adamant.pinpadHighlightButton +// pinpad.buttonsFont = UIFont.adamantPrimary(ofSize: pinpad.buttonsFont.pointSize, weight: .light) +// +// pinpad.placeholdersSize = 15 +// +// if pinpad.view.frame.height > 600 { +// pinpad.buttonsSize = 75 +// pinpad.buttonsSpacing = 20 +// pinpad.placeholderViewHeight = 50 +// } else {// iPhone 5 +// pinpad.buttonsSize = 70 +// pinpad.buttonsSpacing = 15 +// pinpad.placeholderViewHeight = 25 +// pinpad.bottomSpacing = 24 +// pinpad.pinpadToCancelSpacing = 14 +// } +// +// pinpad.placeholderActiveColor = UIColor.adamant.pinpadHighlightButton +// pinpad.biometryButtonType = biometryButton +// pinpad.cancelButton.setTitle(String.adamant.alert.cancel, for: .normal) +// pinpad.pinDigits = 6 +// +// return pinpad } } diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 21545c240..6148b08b6 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -9,7 +9,6 @@ import SwiftUI struct PinPadViewRepresentable: UIViewRepresentable { - @Binding var enteredPin: String let pinLength: Int let validatePin: (String) -> Bool let onSuccess: () -> Void @@ -18,7 +17,6 @@ struct PinPadViewRepresentable: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let hostingController = UIHostingController( rootView: PinPadView( - enteredPin: $enteredPin, pinLength: pinLength, validatePin: validatePin, onSuccess: onSuccess, @@ -35,7 +33,7 @@ struct PinPadViewRepresentable: UIViewRepresentable { // swiftlint:disable multiple_closures_with_trailing_closure struct PinPadView: View { - @Binding var enteredPin: String + @State var enteredPin: String = "" @State var isPinpadVisible: Bool = true let pinLength: Int let validatePin: (String) -> Bool @@ -137,14 +135,8 @@ struct PinPadView: View { } #if DEBUG - -private struct Placeholder { - @State var enteredPin: String = "" -} - #Preview { PinPadView( - enteredPin: Placeholder().$enteredPin, pinLength: 6 ) { _ in true @@ -154,5 +146,4 @@ private struct Placeholder { } } - #endif diff --git a/Adamant/Modules/Account/AccountViewController/AccountViewController+StayIn.swift b/Adamant/Modules/Account/AccountViewController/AccountViewController+StayIn.swift index b65310cb2..80d18d5c4 100644 --- a/Adamant/Modules/Account/AccountViewController/AccountViewController+StayIn.swift +++ b/Adamant/Modules/Account/AccountViewController/AccountViewController+StayIn.swift @@ -8,7 +8,7 @@ import Foundation import Eureka -import MyLittlePinpad +//import MyLittlePinpad import CommonKit extension AccountViewController { @@ -19,24 +19,24 @@ extension AccountViewController { if enabled { // Create pin and turn on Stay In pinpadRequest = .createPin - let pinpad = PinpadViewController.adamantPinpad(biometryButton: .hidden) - pinpad.commentLabel.text = String.adamant.pinpad.createPin - pinpad.commentLabel.isHidden = false - pinpad.delegate = self + let pinpad = adamantPinpad(biometryButton: .none) +// pinpad.commentLabel.text = String.adamant.pinpad.createPin +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - pinpad.backgroundView.backgroundColor = UIColor.adamant.backgroundColor - setColors(for: pinpad) - present(pinpad, animated: true, completion: nil) +// pinpad.backgroundView.backgroundColor = UIColor.adamant.backgroundColor +// setColors(for: pinpad) +// present(pinpad, animated: true, completion: nil) } else { // Validate pin and turn off Stay In pinpadRequest = .turnOffPin - let biometryButton: PinpadBiometryButtonType = accountService.useBiometry ? localAuth.biometryType.pinpadButtonType : .hidden - let pinpad = PinpadViewController.adamantPinpad(biometryButton: biometryButton) - pinpad.commentLabel.text = String.adamant.security.stayInTurnOff - pinpad.commentLabel.isHidden = false - pinpad.delegate = self + let biometryButton: BiometryType = accountService.useBiometry ? localAuth.biometryType : .none + let pinpad = adamantPinpad(biometryButton: biometryButton) +// pinpad.commentLabel.text = String.adamant.security.stayInTurnOff +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - setColors(for: pinpad) - present(pinpad, animated: true, completion: nil) +// setColors(for: pinpad) +// present(pinpad, animated: true, completion: nil) } } @@ -46,7 +46,6 @@ extension AccountViewController { return } - Task { @MainActor [weak self] in guard let self else { return } let reason = enabled ? String.adamant.security.biometryOnReason : String.adamant.security.biometryOffReason @@ -64,21 +63,21 @@ extension AccountViewController { } case .fallback: - let pinpad = PinpadViewController.adamantPinpad(biometryButton: .hidden) + let pinpad = adamantPinpad(biometryButton: .none) if enabled { - pinpad.commentLabel.text = String.adamant.security.biometryOnReason +// pinpad.commentLabel.text = String.adamant.security.biometryOnReason self.pinpadRequest = .turnOnBiometry } else { - pinpad.commentLabel.text = String.adamant.security.biometryOffReason +// pinpad.commentLabel.text = String.adamant.security.biometryOffReason self.pinpadRequest = .turnOffBiometry } - pinpad.commentLabel.isHidden = false - pinpad.delegate = self +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - self.setColors(for: pinpad) - self.present(pinpad, animated: true, completion: nil) +// self.setColors(for: pinpad) +// self.present(pinpad, animated: true, completion: nil) case .failed: if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { diff --git a/Adamant/Modules/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift index 68291a46f..efccbe52b 100644 --- a/Adamant/Modules/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Modules/Login/LoginViewController+Pinpad.swift @@ -13,26 +13,27 @@ import CommonKit extension LoginViewController { /// Shows pinpad in main.async queue func loginWithPinpad() { - let button: PinpadBiometryButtonType = accountService.useBiometry ? localAuth.biometryType.pinpadButtonType : .hidden + let button: BiometryType = accountService.useBiometry ? localAuth.biometryType : .none - DispatchQueue.main.async { [weak self] in - let pinpad = PinpadViewController.adamantPinpad(biometryButton: button) - pinpad.commentLabel.text = String.adamant.login.loginIntoPrevAccount - pinpad.commentLabel.isHidden = false - pinpad.delegate = self +// DispatchQueue.main.async { [weak self] in +// guard let self else { return } + let pinpad = adamantPinpad(biometryButton: button) +// pinpad.commentLabel.text = String.adamant.login.loginIntoPrevAccount +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - pinpad.backgroundView.backgroundColor = UIColor.adamant.backgroundColor - pinpad.buttonsBackgroundColor = UIColor.adamant.backgroundColor - pinpad.view.subviews.forEach { view in - view.subviews.forEach { _view in - if _view.backgroundColor == .white { - _view.backgroundColor = UIColor.adamant.backgroundColor - } - } - } - pinpad.commentLabel.backgroundColor = UIColor.adamant.backgroundColor - self?.present(pinpad, animated: true, completion: nil) - } +// pinpad.backgroundView.backgroundColor = UIColor.adamant.backgroundColor +// pinpad.buttonsBackgroundColor = UIColor.adamant.backgroundColor +// pinpad.view.subviews.forEach { view in +// view.subviews.forEach { _view in +// if _view.backgroundColor == .white { +// _view.backgroundColor = UIColor.adamant.backgroundColor +// } +// } +// } +// pinpad.commentLabel.backgroundColor = UIColor.adamant.backgroundColor +// present(pinpad, animated: true, completion: nil) +// } } /// Request user biometry authentication diff --git a/Adamant/Modules/Settings/SecurityViewController+StayIn.swift b/Adamant/Modules/Settings/SecurityViewController+StayIn.swift index 1c33d73c1..ed529d060 100644 --- a/Adamant/Modules/Settings/SecurityViewController+StayIn.swift +++ b/Adamant/Modules/Settings/SecurityViewController+StayIn.swift @@ -19,21 +19,21 @@ extension SecurityViewController { if enabled { // Create pin and turn on Stay In pinpadRequest = .createPin - let pinpad = PinpadViewController.adamantPinpad(biometryButton: .hidden) - pinpad.commentLabel.text = String.adamant.pinpad.createPin - pinpad.commentLabel.isHidden = false - pinpad.delegate = self + let pinpad = adamantPinpad(biometryButton: .none) +// pinpad.commentLabel.text = String.adamant.pinpad.createPin +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - present(pinpad, animated: true, completion: nil) +// present(pinpad, animated: true, completion: nil) } else { // Validate pin and turn off Stay In pinpadRequest = .turnOffPin - let biometryButton: PinpadBiometryButtonType = accountService.useBiometry ? localAuth.biometryType.pinpadButtonType : .hidden - let pinpad = PinpadViewController.adamantPinpad(biometryButton: biometryButton) - pinpad.commentLabel.text = String.adamant.security.stayInTurnOff - pinpad.commentLabel.isHidden = false - pinpad.delegate = self + let biometryButton: BiometryType = accountService.useBiometry ? localAuth.biometryType : .none + let pinpad = adamantPinpad(biometryButton: biometryButton) +// pinpad.commentLabel.text = String.adamant.security.stayInTurnOff +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self pinpad.modalPresentationStyle = .overFullScreen - present(pinpad, animated: true, completion: nil) +// present(pinpad, animated: true, completion: nil) } } @@ -60,23 +60,23 @@ extension SecurityViewController { } case .fallback: - let pinpad = PinpadViewController.adamantPinpad(biometryButton: .hidden) + let pinpad = adamantPinpad(biometryButton: .none) if enabled { - pinpad.commentLabel.text = String.adamant.security.biometryOnReason +// pinpad.commentLabel.text = String.adamant.security.biometryOnReason self.pinpadRequest = .turnOnBiometry } else { - pinpad.commentLabel.text = String.adamant.security.biometryOffReason +// pinpad.commentLabel.text = String.adamant.security.biometryOffReason self.pinpadRequest = .turnOffBiometry } - pinpad.commentLabel.isHidden = false - pinpad.delegate = self +// pinpad.commentLabel.isHidden = false +// pinpad.delegate = self - DispatchQueue.main.async { +// DispatchQueue.main.async { pinpad.modalPresentationStyle = .overFullScreen - self.present(pinpad, animated: true, completion: nil) - } +// self.present(pinpad, animated: true, completion: nil) +// } case .failed: DispatchQueue.main.async { diff --git a/Adamant/ServiceProtocols/VisibleWalletsService.swift b/Adamant/ServiceProtocols/VisibleWalletsService.swift index bb53182f4..ca4220060 100644 --- a/Adamant/ServiceProtocols/VisibleWalletsService.swift +++ b/Adamant/ServiceProtocols/VisibleWalletsService.swift @@ -17,7 +17,7 @@ extension Notification.Name { } } protocol VisibleWalletsService: AnyObject, Sendable { - func addToInvisibleWallets(_ wallet: WalletCoreProtocol) + func addToInvisibleWallets(_ wallet: §) func removeFromInvisibleWallets(_ wallet: WalletCoreProtocol) func getInvisibleWallets() -> [String] func isInvisible(_ wallet: WalletCoreProtocol) -> Bool From 0d16d0561ee2def5d56ff23305c73e1b3df0c889 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Mon, 3 Feb 2025 22:06:44 +0500 Subject: [PATCH 04/12] [trello.com/c/avWMqc8I] fixed compilation error WIP --- Adamant/Helpers/MyLittlePinpad+adamant.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index d511ed73a..883cb84a9 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -52,7 +52,7 @@ extension String.adamant { //} extension UIViewController { - func adamantPinpad(biometryButton: BiometryType, onSuccess: (String) -> UIViewController { + func adamantPinpad(biometryButton: BiometryType, onSuccess: (String) -> Void) -> UIViewController { let pinPadView = PinPadViewRepresentable( pinLength: 6, validatePin: { $0 == "123456" }, From 2dd373eb070f8e831a97c86a0a6cc4fc772783b0 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Fri, 7 Feb 2025 00:12:19 +0500 Subject: [PATCH 05/12] [trello.com/c/avWMqc8I] fixed compilation error WIP --- Adamant/ServiceProtocols/VisibleWalletsService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/ServiceProtocols/VisibleWalletsService.swift b/Adamant/ServiceProtocols/VisibleWalletsService.swift index ca4220060..bb53182f4 100644 --- a/Adamant/ServiceProtocols/VisibleWalletsService.swift +++ b/Adamant/ServiceProtocols/VisibleWalletsService.swift @@ -17,7 +17,7 @@ extension Notification.Name { } } protocol VisibleWalletsService: AnyObject, Sendable { - func addToInvisibleWallets(_ wallet: §) + func addToInvisibleWallets(_ wallet: WalletCoreProtocol) func removeFromInvisibleWallets(_ wallet: WalletCoreProtocol) func getInvisibleWallets() -> [String] func isInvisible(_ wallet: WalletCoreProtocol) -> Bool From 478a19fd0365873b09b4c3756815260a3b20b4d6 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Wed, 12 Feb 2025 22:10:04 +0500 Subject: [PATCH 06/12] [trello.com/c/avWMqc8I] updating authentication service WIP --- Adamant.xcodeproj/project.pbxproj | 8 ++-- ...swift => LocalAuthenticationService.swift} | 6 ++- Adamant/Helpers/MyLittlePinpad+adamant.swift | 38 +++++++++---------- .../Login/LoginViewController+Pinpad.swift | 2 +- 4 files changed, 28 insertions(+), 26 deletions(-) rename Adamant/Helpers/{LocalAuthenticationHandler.swift => LocalAuthenticationService.swift} (96%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 1d90bf501..61a3968bf 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -466,7 +466,7 @@ A5F929B6262C858700C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B5262C858700C3E60A /* MarkdownKit */; }; A5F929B8262C858F00C3E60A /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5F929B7262C858F00C3E60A /* MarkdownKit */; }; D39AA7892D42745D0069BC73 /* PinPadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA7882D42745D0069BC73 /* PinPadView.swift */; }; - D39AA78B2D47A0D80069BC73 /* LocalAuthenticationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */; }; + D39AA78B2D47A0D80069BC73 /* LocalAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationService.swift */; }; E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */; }; E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F620EC200900D0CB2D /* SecurityViewController.swift */; }; E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */; }; @@ -1111,7 +1111,7 @@ AD258997F050B24C0051CC8D /* Pods-Adamant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.release.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.release.xcconfig"; sourceTree = ""; }; ADDFD2FA17E41CCBD11A1733 /* Pods-Adamant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adamant.debug.xcconfig"; path = "Target Support Files/Pods-Adamant/Pods-Adamant.debug.xcconfig"; sourceTree = ""; }; D39AA7882D42745D0069BC73 /* PinPadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinPadView.swift; sourceTree = ""; }; - D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationHandler.swift; sourceTree = ""; }; + D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationService.swift; sourceTree = ""; }; E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; E90055F620EC200900D0CB2D /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecurityViewController+StayIn.swift"; sourceTree = ""; }; @@ -2409,7 +2409,7 @@ 64A223D520F760BB005157CB /* Localization.swift */, E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, D39AA7882D42745D0069BC73 /* PinPadView.swift */, - D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationHandler.swift */, + D39AA78A2D47A0CC0069BC73 /* LocalAuthenticationService.swift */, E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, 6414C18D217DF43100373FA6 /* String+adamant.swift */, @@ -3576,7 +3576,7 @@ E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, E90847332196FEA80095825D /* TransferTransaction+CoreDataProperties.swift in Sources */, 9366588D2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift in Sources */, - D39AA78B2D47A0D80069BC73 /* LocalAuthenticationHandler.swift in Sources */, + D39AA78B2D47A0D80069BC73 /* LocalAuthenticationService.swift in Sources */, E99818942120892F0018C84C /* WalletViewControllerBase.swift in Sources */, 3AA6DF462BA9BEB700EA2E16 /* MediaContentView.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, diff --git a/Adamant/Helpers/LocalAuthenticationHandler.swift b/Adamant/Helpers/LocalAuthenticationService.swift similarity index 96% rename from Adamant/Helpers/LocalAuthenticationHandler.swift rename to Adamant/Helpers/LocalAuthenticationService.swift index 0e520596a..c7b13c21b 100644 --- a/Adamant/Helpers/LocalAuthenticationHandler.swift +++ b/Adamant/Helpers/LocalAuthenticationService.swift @@ -8,13 +8,15 @@ import UIKit -protocol LocalAuthenticationHandler { +protocol LocalAuthenticationService { func didEnter(pin: String, viewController: UIViewController) func didTapBiometryButton(viewController: UIViewController) func didTapCancel(viewController: UIViewController) } -final class LocalAuthenticationHandlerImpl: LocalAuthenticationHandler { +final class LocalAuthenticationHandlerImpl: LocalAuthenticationService { + + func didEnter(pin: String, viewController: UIViewController) { switch pinpadRequest { diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index 883cb84a9..cf3c581ae 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -54,25 +54,25 @@ extension String.adamant { extension UIViewController { func adamantPinpad(biometryButton: BiometryType, onSuccess: (String) -> Void) -> UIViewController { let pinPadView = PinPadViewRepresentable( - pinLength: 6, - validatePin: { $0 == "123456" }, - onSuccess: { print("Pin validated!") }, - onCancel: { print("Pin entry canceled.") } - ) - - let hostingController = UIHostingController(rootView: pinPadView) - addChild(hostingController) - hostingController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(hostingController.view) - - NSLayoutConstraint.activate([ - hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - - hostingController.didMove(toParent: self) + pinLength: 6, + validatePin: { $0 == "123456" }, + onSuccess: { print("Pin validated!") }, + onCancel: { print("Pin entry canceled.") } + ) + + let hostingController = UIHostingController(rootView: pinPadView) + addChild(hostingController) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingController.view) + + NSLayoutConstraint.activate([ + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + hostingController.didMove(toParent: self) return hostingController // let pinpad = PinpadViewController.instantiateFromResourceNib() // diff --git a/Adamant/Modules/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift index efccbe52b..18f85d841 100644 --- a/Adamant/Modules/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Modules/Login/LoginViewController+Pinpad.swift @@ -17,7 +17,7 @@ extension LoginViewController { // DispatchQueue.main.async { [weak self] in // guard let self else { return } - let pinpad = adamantPinpad(biometryButton: button) + let pinpad = adamantPinpad(biometryButton: button) { _ in } // pinpad.commentLabel.text = String.adamant.login.loginIntoPrevAccount // pinpad.commentLabel.isHidden = false // pinpad.delegate = self From 0b645430d185169c9dc08bb35d88cd8992515956 Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Wed, 12 Feb 2025 23:15:47 +0500 Subject: [PATCH 07/12] [trello.com/c/avWMqc8I] updating authentication service WIP --- .../Helpers/LocalAuthenticationService.swift | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Adamant/Helpers/LocalAuthenticationService.swift b/Adamant/Helpers/LocalAuthenticationService.swift index c7b13c21b..67fb65465 100644 --- a/Adamant/Helpers/LocalAuthenticationService.swift +++ b/Adamant/Helpers/LocalAuthenticationService.swift @@ -9,26 +9,31 @@ import UIKit protocol LocalAuthenticationService { - func didEnter(pin: String, viewController: UIViewController) + func didEnter(pin: String, pinpadRequest: inout SecurityViewController.PinpadRequest, viewController: UIViewController) func didTapBiometryButton(viewController: UIViewController) func didTapCancel(viewController: UIViewController) } -final class LocalAuthenticationHandlerImpl: LocalAuthenticationService { +final class LocalAuthenticationServiceImpl: LocalAuthenticationService { + let accountService: AccountService - func didEnter(pin: String, viewController: UIViewController) { + init(accountService: AccountService) { + self.accountService = accountService + } + + func didEnter(pin: String, pinpadRequest: inout SecurityViewController.PinpadRequest, viewController: UIViewController) { switch pinpadRequest { // MARK: User has entered new pin first time. Request re-enter pin - case .createPin?: + case .createPin: pinpadRequest = .reenterPin(pin: pin) pinpad.commentLabel.text = String.adamant.pinpad.reenterPin pinpad.clearPin() return // MARK: User has reentered pin. Save pin. - case .reenterPin(let pinToVerify)?: + case .reenterPin(let pinToVerify): guard pin == pinToVerify else { pinpad.playWrongPinAnimation() pinpad.clearPin() @@ -62,7 +67,7 @@ final class LocalAuthenticationHandlerImpl: LocalAuthenticationService { } // MARK: Users want to turn off the pin. Validate and turn off. - case .turnOffPin?: + case .turnOffPin: guard accountService.validatePin(pin) else { pinpad.playWrongPinAnimation() pinpad.clearPin() @@ -74,7 +79,7 @@ final class LocalAuthenticationHandlerImpl: LocalAuthenticationService { pinpad.dismiss(animated: true, completion: nil) // MARK: User wants to turn on biometry - case .turnOnBiometry?: + case .turnOnBiometry: guard accountService.validatePin(pin) else { pinpad.playWrongPinAnimation() pinpad.clearPin() @@ -85,7 +90,7 @@ final class LocalAuthenticationHandlerImpl: LocalAuthenticationService { pinpad.dismiss(animated: true, completion: nil) // MARK: User wants to turn off biometry - case .turnOffBiometry?: + case .turnOffBiometry: guard accountService.validatePin(pin) else { pinpad.playWrongPinAnimation() pinpad.clearPin() From c1c605eff43e428dc3f16200f94cd7d70f6db2ea Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Sat, 15 Feb 2025 12:09:07 +0500 Subject: [PATCH 08/12] [trello.com/c/avWMqc8I] refactoring of pinpadview --- .../Helpers/LocalAuthenticationService.swift | 143 ++++++-- Adamant/Helpers/PinPadView.swift | 94 +++-- .../AccountViewController+StayIn.swift | 338 +++++++++--------- .../SecurityViewController+StayIn.swift | 326 ++++++++--------- 4 files changed, 503 insertions(+), 398 deletions(-) diff --git a/Adamant/Helpers/LocalAuthenticationService.swift b/Adamant/Helpers/LocalAuthenticationService.swift index 67fb65465..67b836811 100644 --- a/Adamant/Helpers/LocalAuthenticationService.swift +++ b/Adamant/Helpers/LocalAuthenticationService.swift @@ -10,16 +10,18 @@ import UIKit protocol LocalAuthenticationService { func didEnter(pin: String, pinpadRequest: inout SecurityViewController.PinpadRequest, viewController: UIViewController) - func didTapBiometryButton(viewController: UIViewController) - func didTapCancel(viewController: UIViewController) + func didTapBiometryButton(pinpadRequest: SecurityViewController.PinpadRequest, viewController: UIViewController) + func didTapCancel(pinpadRequest: SecurityViewController.PinpadRequest, viewController: UIViewController) } final class LocalAuthenticationServiceImpl: LocalAuthenticationService { let accountService: AccountService + let localAuth: LocalAuthentication - init(accountService: AccountService) { + init(accountService: AccountService, localAuth: LocalAuthentication) { self.accountService = accountService + self.localAuth = localAuth } func didEnter(pin: String, pinpadRequest: inout SecurityViewController.PinpadRequest, viewController: UIViewController) { @@ -28,88 +30,157 @@ final class LocalAuthenticationServiceImpl: LocalAuthenticationService { // MARK: User has entered new pin first time. Request re-enter pin case .createPin: pinpadRequest = .reenterPin(pin: pin) - pinpad.commentLabel.text = String.adamant.pinpad.reenterPin - pinpad.clearPin() +// pinpad.commentLabel.text = String.adamant.pinpad.reenterPin +// pinpad.clearPin() return // MARK: User has reentered pin. Save pin. case .reenterPin(let pinToVerify): guard pin == pinToVerify else { - pinpad.playWrongPinAnimation() - pinpad.clearPin() +// pinpad.playWrongPinAnimation() +// pinpad.clearPin() break } let result = accountService.setStayLoggedIn(pin: pin) Task { @MainActor in switch result { - case .success: - self.pinpadRequest = nil - if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { - row.value = false - row.updateCell() - row.evaluateHidden() - } + case .success: return +// self.pinpadRequest = nil +// if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { +// row.value = false +// row.updateCell() +// row.evaluateHidden() +// } - if let section = self.form.sectionBy(tag: Sections.notifications.tag) { - section.evaluateHidden() - } +// if let section = self.form.sectionBy(tag: Sections.notifications.tag) { +// section.evaluateHidden() +// } - if let section = self.form.sectionBy(tag: Sections.aboutNotificationTypes.tag) { - section.evaluateHidden() - } +// if let section = self.form.sectionBy(tag: Sections.aboutNotificationTypes.tag) { +// section.evaluateHidden() +// } - pinpad.dismiss(animated: true, completion: nil) +// pinpad.dismiss(animated: true, completion: nil) - case .failure(let error): - self.dialogService.showRichError(error: error) + case .failure(let error): return +// self.dialogService.showRichError(error: error) } } // MARK: Users want to turn off the pin. Validate and turn off. case .turnOffPin: guard accountService.validatePin(pin) else { - pinpad.playWrongPinAnimation() - pinpad.clearPin() +// pinpad.playWrongPinAnimation() +// pinpad.clearPin() break } accountService.dropSavedAccount() - pinpad.dismiss(animated: true, completion: nil) +// pinpad.dismiss(animated: true, completion: nil) // MARK: User wants to turn on biometry case .turnOnBiometry: guard accountService.validatePin(pin) else { - pinpad.playWrongPinAnimation() - pinpad.clearPin() +// pinpad.playWrongPinAnimation() +// pinpad.clearPin() break } accountService.updateUseBiometry(true) - pinpad.dismiss(animated: true, completion: nil) +// pinpad.dismiss(animated: true, completion: nil) // MARK: User wants to turn off biometry case .turnOffBiometry: guard accountService.validatePin(pin) else { - pinpad.playWrongPinAnimation() - pinpad.clearPin() +// pinpad.playWrongPinAnimation() +// pinpad.clearPin() break } accountService.updateUseBiometry(false) - pinpad.dismiss(animated: true, completion: nil) +// pinpad.dismiss(animated: true, completion: nil) - default: - pinpad.dismiss(animated: true, completion: nil) + default: return +// pinpad.dismiss(animated: true, completion: nil) } } - func didTapBiometryButton(viewController: UIViewController) { + func didTapBiometryButton(pinpadRequest: SecurityViewController.PinpadRequest, viewController: UIViewController) { + Task { + switch pinpadRequest { + // MARK: User wants to turn of StayIn with his face. Or finger. + case .turnOffPin: + let result = await localAuth.authorizeUser(reason: String.adamant.security.stayInTurnOff) + switch result { + case .success: + self.accountService.dropSavedAccount() + +// if let row: SwitchRow = self.form.rowBy(tag: Rows.biometry.tag) { +// row.value = false +// row.updateCell() +// row.evaluateHidden() +// } + +// if let row = self.form.rowBy(tag: Rows.notifications.tag) { +// row.evaluateHidden() +// } + +// pinpad.dismiss(animated: true, completion: nil) + + case .cancel: break + case .fallback: break + case .failed: break + case .biometryLockout: break + } + default: + return + } + } } - func didTapCancel(viewController: UIViewController) { + func didTapCancel(pinpadRequest: SecurityViewController.PinpadRequest, viewController: UIViewController) { +// MainActor.assumeIsolatedSafe { + switch pinpadRequest { + + // MARK: User canceled turning on StayIn + case .createPin, .reenterPin(pin: _): + return +// if let row: SwitchRow = form.rowBy(tag: Rows.stayIn.tag) { +// row.value = false +// row.updateCell() +// } + + // MARK: User canceled turning off StayIn + case .turnOffPin: +// if let row: SwitchRow = form.rowBy(tag: Rows.stayIn.tag) { +// row.value = true +// row.updateCell() +// } + return + // MARK: User canceled Biometry On + case .turnOnBiometry: +// if let row: SwitchRow = form.rowBy(tag: Rows.biometry.tag) { +// row.value = false +// row.updateCell() +// } + return + // MARK: User canceled Biometry Off + case .turnOffBiometry: +// if let row: SwitchRow = form.rowBy(tag: Rows.biometry.tag) { +// row.value = true +// row.updateCell() +// } + return + default: + break + } + +// pinpadRequest = nil +// pinpad.dismiss(animated: true, completion: nil) +// } } } diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 6148b08b6..1b034efd3 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -17,10 +17,13 @@ struct PinPadViewRepresentable: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let hostingController = UIHostingController( rootView: PinPadView( - pinLength: pinLength, - validatePin: validatePin, - onSuccess: onSuccess, - onCancel: onCancel + viewModel: + PinPadViewModel( + pinLength: pinLength, + validatePin: validatePin, + onSuccess: onSuccess, + onCancel: onCancel + ) ) ) return hostingController.view @@ -31,15 +34,43 @@ struct PinPadViewRepresentable: UIViewRepresentable { } } -// swiftlint:disable multiple_closures_with_trailing_closure -struct PinPadView: View { - @State var enteredPin: String = "" - @State var isPinpadVisible: Bool = true +class PinPadViewModel: ObservableObject { + @Published var enteredPin: String = "" let pinLength: Int let validatePin: (String) -> Bool let onSuccess: () -> Void let onCancel: () -> Void + init( + pinLength: Int, + validatePin: @escaping (String) -> Bool, + onSuccess: @escaping () -> Void, + onCancel: @escaping () -> Void + ) { + self.pinLength = pinLength + self.validatePin = validatePin + self.onSuccess = onSuccess + self.onCancel = onCancel + } + + func append(_ text: String) { + enteredPin.append(text) + } +} + +// swiftlint:disable multiple_closures_with_trailing_closure +struct PinPadView: View { + + enum PinPadViewMode { + case createPin + case reenterPin(pin: String) + case turnOffPin + case turnOnBiometry + case turnOffBiometry + } + + @StateObject var viewModel: PinPadViewModel + var body: some View { VStack { Spacer() @@ -50,10 +81,10 @@ struct PinPadView: View { .padding(.top, 30) HStack(spacing: 10) { - ForEach(0.. Date: Sun, 16 Feb 2025 22:27:26 +0500 Subject: [PATCH 09/12] [trello.com/c/avWMqc8I] wrapped api into viewModel --- Adamant/Helpers/PinPadView.swift | 197 +++++++++++++++++++++++++++---- 1 file changed, 171 insertions(+), 26 deletions(-) diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 1b034efd3..459282550 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -35,14 +35,31 @@ struct PinPadViewRepresentable: UIViewRepresentable { } class PinPadViewModel: ObservableObject { - @Published var enteredPin: String = "" + enum Mode { + case enterPin + case createPin + case wrongPinEntered + case pinNotMatching + case reenterPin + case pinCreated + case pinValidated + case turnOffPin + case turnOnBiometry + case turnOffBiometry + } + @Published private(set) var mode: Mode + @Published private var enteredPin: String = "" + @Published private var previousEntry: String = "" + @Published private(set) var title: String = "" + @Published private(set) var titleColor: Color = .white let pinLength: Int - let validatePin: (String) -> Bool + private let validatePin: (String) -> Bool let onSuccess: () -> Void let onCancel: () -> Void init( pinLength: Int, + mode: Mode, validatePin: @escaping (String) -> Bool, onSuccess: @escaping () -> Void, onCancel: @escaping () -> Void @@ -51,31 +68,121 @@ class PinPadViewModel: ObservableObject { self.validatePin = validatePin self.onSuccess = onSuccess self.onCancel = onCancel + self.mode = mode + update(mode: mode) + } + + func append(_ character: String) { + enteredPin.append(character) + } + + func removeLast() { + enteredPin.removeLast() + } + + func update(mode: Mode) { + let title: String + let titleColor: Color + switch mode { + case .createPin: + title = "Create new PIN" + titleColor = .white + case .enterPin: + title = "Login into ADAMANT" + titleColor = .white + case .reenterPin: + title = "Re-enter new PIN" + titleColor = .white + case .wrongPinEntered: + title = "Wrong PIN entered!" + titleColor = .red + case .pinNotMatching: + title = "PIN doesn't match!" + titleColor = .red + case .pinValidated: + title = "Success!" + titleColor = .green + case .pinCreated: + title = "PIN created!" + titleColor = .green + case .turnOffBiometry, .turnOffPin, .turnOnBiometry: + title = "" + titleColor = .white + } + self.title = title + self.titleColor = titleColor + self.mode = mode + } + + func updateCache(mode: Mode) { + switch mode { + case .createPin: + enteredPin.removeAll() + previousEntry.removeAll() + case .enterPin: + return + case .reenterPin: + previousEntry = enteredPin + enteredPin.removeAll() + case .wrongPinEntered: + enteredPin.removeAll() + case .pinNotMatching: + enteredPin.removeAll() + previousEntry.removeAll() + case .pinValidated: + return + case .pinCreated: + return + case .turnOffBiometry, .turnOffPin, .turnOnBiometry: + return + } + self.title = title + self.titleColor = titleColor + } + + func isValid(mode: Mode) -> Bool { + switch mode { + case .reenterPin: + previousEntry == enteredPin + case .wrongPinEntered, + .createPin, + .enterPin, + .pinNotMatching, + .pinValidated, + .pinCreated, + .turnOffBiometry, + .turnOffPin, + .turnOnBiometry: + true + } + } + + var isEmpty: Bool { + enteredPin.isEmpty + } + + var isPinEnteredCompletely: Bool { + enteredPin.count == pinLength + } + + func hasNumber(at index: Int) -> Bool { + index < enteredPin.count } - func append(_ text: String) { - enteredPin.append(text) + var isPinValid: Bool { + validatePin(enteredPin) } } // swiftlint:disable multiple_closures_with_trailing_closure struct PinPadView: View { - - enum PinPadViewMode { - case createPin - case reenterPin(pin: String) - case turnOffPin - case turnOnBiometry - case turnOffBiometry - } - @StateObject var viewModel: PinPadViewModel var body: some View { VStack { Spacer() - Text("Login into ADAMANT") - .foregroundColor(.white) + Text(viewModel.title) + .foregroundColor(viewModel.titleColor) .textCase(nil) .font(.body) .padding(.top, 30) @@ -84,7 +191,7 @@ struct PinPadView: View { ForEach(0.. Date: Mon, 17 Feb 2025 02:54:33 +0500 Subject: [PATCH 10/12] [trello.com/c/avWMqc8I] setting style to new pinpad view --- Adamant/Helpers/MyLittlePinpad+adamant.swift | 1 + Adamant/Helpers/PinPadView.swift | 29 +++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Adamant/Helpers/MyLittlePinpad+adamant.swift b/Adamant/Helpers/MyLittlePinpad+adamant.swift index cf3c581ae..6a7874d00 100644 --- a/Adamant/Helpers/MyLittlePinpad+adamant.swift +++ b/Adamant/Helpers/MyLittlePinpad+adamant.swift @@ -55,6 +55,7 @@ extension UIViewController { func adamantPinpad(biometryButton: BiometryType, onSuccess: (String) -> Void) -> UIViewController { let pinPadView = PinPadViewRepresentable( pinLength: 6, + mode: .createPin, validatePin: { $0 == "123456" }, onSuccess: { print("Pin validated!") }, onCancel: { print("Pin entry canceled.") } diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 459282550..66af7e697 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -10,6 +10,7 @@ import SwiftUI struct PinPadViewRepresentable: UIViewRepresentable { let pinLength: Int + let mode: PinPadViewModel.Mode let validatePin: (String) -> Bool let onSuccess: () -> Void let onCancel: () -> Void @@ -20,6 +21,7 @@ struct PinPadViewRepresentable: UIViewRepresentable { viewModel: PinPadViewModel( pinLength: pinLength, + mode: mode, validatePin: validatePin, onSuccess: onSuccess, onCancel: onCancel @@ -85,13 +87,13 @@ class PinPadViewModel: ObservableObject { let titleColor: Color switch mode { case .createPin: - title = "Create new PIN" + title = String.adamant.pinpad.createPin titleColor = .white case .enterPin: title = "Login into ADAMANT" titleColor = .white case .reenterPin: - title = "Re-enter new PIN" + title = String.adamant.pinpad.reenterPin titleColor = .white case .wrongPinEntered: title = "Wrong PIN entered!" @@ -105,8 +107,14 @@ class PinPadViewModel: ObservableObject { case .pinCreated: title = "PIN created!" titleColor = .green - case .turnOffBiometry, .turnOffPin, .turnOnBiometry: - title = "" + case .turnOffPin: + title = String.adamant.security.stayInTurnOff + titleColor = .white + case .turnOffBiometry: + title = String.adamant.security.biometryOffReason + titleColor = .white + case .turnOnBiometry: + title = String.adamant.security.biometryOnReason titleColor = .white } self.title = title @@ -191,7 +199,7 @@ struct PinPadView: View { ForEach(0.. some View { @@ -229,11 +237,12 @@ struct PinPadView: View { .frame(width: 75, height: 75) .overlay( Text("\(number)") - .foregroundColor(.white) - .font(.title) + .foregroundColor(Color(UIColor.adamant.primary)) + .font(Font(UIFont.adamantPrimary(ofSize: 35, weight: .light))) + ) .foregroundColor(.clear) - .overlay(Circle().stroke(Color.white, lineWidth: 1)) + .overlay(Circle().stroke(Color(UIColor.adamant.secondary), lineWidth: 1)) } } if showsDeleteButton { From fc9a19dd7140aef3f2f9addb01fe4c2ddce0241b Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Tue, 18 Feb 2025 05:19:02 +0500 Subject: [PATCH 11/12] [trello.com/c/avWMqc8I] adjusted fonts, colors and sizes --- Adamant/Helpers/PinPadView.swift | 42 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 66af7e697..6b3e90304 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -88,13 +88,13 @@ class PinPadViewModel: ObservableObject { switch mode { case .createPin: title = String.adamant.pinpad.createPin - titleColor = .white + titleColor = .primary case .enterPin: title = "Login into ADAMANT" - titleColor = .white + titleColor = .primary case .reenterPin: title = String.adamant.pinpad.reenterPin - titleColor = .white + titleColor = .primary case .wrongPinEntered: title = "Wrong PIN entered!" titleColor = .red @@ -109,13 +109,13 @@ class PinPadViewModel: ObservableObject { titleColor = .green case .turnOffPin: title = String.adamant.security.stayInTurnOff - titleColor = .white + titleColor = .primary case .turnOffBiometry: title = String.adamant.security.biometryOffReason - titleColor = .white + titleColor = .primary case .turnOnBiometry: title = String.adamant.security.biometryOnReason - titleColor = .white + titleColor = .primary } self.title = title self.titleColor = titleColor @@ -195,16 +195,17 @@ struct PinPadView: View { .font(.body) .padding(.top, 30) - HStack(spacing: 10) { + HStack(spacing: 18) { ForEach(0.. some View { - HStack { + HStack(spacing: 25) { if showsDeleteButton { Circle() - .frame(width: 75, height: 75) + .frame(width: 80, height: 80) .foregroundColor(.clear) } ForEach(from...to, id: \.self) { number in @@ -234,31 +236,31 @@ struct PinPadView: View { handlePinInput("\(number)") }) { Circle() - .frame(width: 75, height: 75) + .frame(width: 80, height: 80) .overlay( Text("\(number)") .foregroundColor(Color(UIColor.adamant.primary)) .font(Font(UIFont.adamantPrimary(ofSize: 35, weight: .light))) - ) .foregroundColor(.clear) - .overlay(Circle().stroke(Color(UIColor.adamant.secondary), lineWidth: 1)) + .overlay(Circle().stroke(Color(UIColor.adamant.secondary), lineWidth: 0.5)) } } if showsDeleteButton { Button(action: deleteLastDigit) { Circle() - .frame(width: 75, height: 75) + .frame(width: 80, height: 80) .overlay( Image(systemName: "delete.left") - .foregroundColor(.white) + .foregroundColor(Color(UIColor.adamant.primary)) .font(.title) ) .foregroundColor(.clear) - .overlay(Circle().stroke(Color.white, lineWidth: 1)) + .overlay(Circle().stroke(Color(UIColor.adamant.secondary), lineWidth: 0.5)) } } } + .frame(height: 90) } private func handlePinInput(_ digit: String) { From f455e7de181039b906f633ef6c09adb1ba6362ee Mon Sep 17 00:00:00 2001 From: Ruslan Alikhamov Date: Tue, 18 Feb 2025 23:43:41 +0500 Subject: [PATCH 12/12] [trello.com/c/avWMqc8I] removed redundant Task.detached calls --- Adamant/Helpers/PinPadView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Adamant/Helpers/PinPadView.swift b/Adamant/Helpers/PinPadView.swift index 6b3e90304..8bb9fc606 100644 --- a/Adamant/Helpers/PinPadView.swift +++ b/Adamant/Helpers/PinPadView.swift @@ -273,28 +273,28 @@ struct PinPadView: View { viewModel.onSuccess() } else { viewModel.update(mode: .wrongPinEntered) - Task.detached { @MainActor in + Task { @MainActor in viewModel.update(mode: .enterPin) viewModel.updateCache(mode: .enterPin) } } case .createPin: viewModel.update(mode: .reenterPin) - Task.detached { @MainActor in + Task { @MainActor in try await Task.sleep(nanoseconds: 300_000_000) viewModel.updateCache(mode: .reenterPin) } case .reenterPin: if viewModel.isValid(mode: .reenterPin) { viewModel.update(mode: .pinCreated) - Task.detached { @MainActor in + Task { @MainActor in try await Task.sleep(nanoseconds: 100_000_000) viewModel.updateCache(mode: .pinCreated) viewModel.onSuccess() } } else { viewModel.update(mode: .pinNotMatching) - Task.detached { @MainActor in + Task { @MainActor in try await Task.sleep(nanoseconds: 500_000_000) viewModel.update(mode: .createPin) viewModel.updateCache(mode: .createPin)