From 1d27ce5ad2e088c46c0515a75591336e4ca1b73c Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 10:36:27 +0300 Subject: [PATCH 01/10] Fixed preview target --- .../AppContext/PreviewCoinRecordsService.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios-preview/AppContext/PreviewCoinRecordsService.swift b/unstoppable-ios-app/domains-manager-ios-preview/AppContext/PreviewCoinRecordsService.swift index f1cca47b7..e5f5379f5 100644 --- a/unstoppable-ios-app/domains-manager-ios-preview/AppContext/PreviewCoinRecordsService.swift +++ b/unstoppable-ios-app/domains-manager-ios-preview/AppContext/PreviewCoinRecordsService.swift @@ -9,6 +9,10 @@ import Foundation import Combine final class CoinRecordsService: CoinRecordsServiceProtocol { + func refreshCurrencies() { + + } + private(set) var eventsPublisher = PassthroughSubject() func getCurrencies() async -> [CoinRecord] { @@ -25,8 +29,4 @@ final class CoinRecordsService: CoinRecordsServiceProtocol { expandedTicker: "crypto.BTC.address", regexPattern: BlockchainType.Bitcoin.regexPattern)] } - - func refreshCurrencies(version: String) { - - } } From 5d26f3662cf0affd56006ebcdb1792425f6bfb37 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 10:36:39 +0300 Subject: [PATCH 02/10] Updated recovery view subtitle --- .../project.pbxproj | 8 ++++++++ .../Extensions/Extension-String+Preview.swift | 1 + .../MPCEnterCredentialsView.swift | 3 ++- .../MPCForgotPasswordView.swift | 20 +++++++++++++++++-- .../Localization/en.lproj/Localizable.strings | 5 +++-- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index f9e1852e4..4d24704d5 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -7190,6 +7190,13 @@ path = "domains-manager-ios-preview"; sourceTree = ""; }; + C69CA5CB2CB5140200C30DF7 /* Recovery */ = { + isa = PBXGroup; + children = ( + ); + path = Recovery; + sourceTree = ""; + }; C69F990E2A9F1264004B1958 /* CommonViews */ = { isa = PBXGroup; children = ( @@ -7795,6 +7802,7 @@ C6BF801B2BA2B53F00CC4CB3 /* MPC */ = { isa = PBXGroup; children = ( + C69CA5CB2CB5140200C30DF7 /* Recovery */, C640F3712C082C2E009EB0F9 /* Activate */, C6952A5B2BC6648900F4B475 /* PurchaseMPCFlow */, C6952A322BC4EE2D00F4B475 /* MPCWalletsService */, diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift index d5af45d81..f60e638b2 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift @@ -1312,6 +1312,7 @@ extension String { static let sendCrypto = "SEND_CRYPTO" static let mpcForgotPasswordTitle = "MPC_FORGOT_PASSWORD_TITLE" static let mpcForgotPasswordSubtitle = "MPC_FORGOT_PASSWORD_SUBTITLE" + static let mpcForgotPasswordSubtitleHighlights = "MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" // Send crypto first time static let sendCryptoFirstTimePullUpTitle = "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift index 601cf27a3..bc2f9a960 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift @@ -205,5 +205,6 @@ extension MPCEnterCredentialsView { // // return nav - MPCEnterCredentialsView(analyticsName: .addEmail, credentialsCallback: { _ in }) + MPCEnterCredentialsView(analyticsName: .addEmail, credentialsCallback: { _ in }, + forgotPasswordCallback: { }) } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift index 8b42f6f62..bc307b51d 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift @@ -28,13 +28,29 @@ struct MPCForgotPasswordView: View, ViewAnalyticsLogger { // MARK: - Private methods private extension MPCForgotPasswordView { + var subtitleAttributesList: [AttributedText.AttributesList] { + let subtitleHighlights = String.Constants.mpcForgotPasswordSubtitleHighlights.localized() + let stringsToUpdate = subtitleHighlights.components(separatedBy: "\n") + let attributesList: [AttributedText.AttributesList] = stringsToUpdate.map { + .init(text: $0, + font: .currentFont(withSize: 16, + weight: .medium), + textColor: .foregroundDefault) + } + + return attributesList + } + @ViewBuilder func headerView() -> some View { VStack(spacing: 16) { Text(String.Constants.mpcForgotPasswordTitle.localized()) .titleText() - Text(String.Constants.mpcForgotPasswordSubtitle.localized()) - .subtitleText() + AttributedText(attributesList: .init(text: String.Constants.mpcForgotPasswordSubtitle.localized(), + font: .currentFont(withSize: 16), + textColor: .foregroundSecondary, + alignment: .center), + updatedAttributesList: subtitleAttributesList) } .multilineTextAlignment(.center) } diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings index c13a386f9..778eaae7e 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings @@ -1204,8 +1204,9 @@ More tabs are coming in the next updates."; "CONTACT_SUPPORT" = "Contact Support"; "MPC_WALLET_DEFAULT_NAME" = "Lite Wallet"; "SEND_CRYPTO" = "Send Crypto"; -"MPC_FORGOT_PASSWORD_TITLE" = "Reset password\nusing the recovery link"; -"MPC_FORGOT_PASSWORD_SUBTITLE" = "Find the email with the recovery link we sent you, when you created your wallet. This is the only way to restore access to the wallet if you forgot your password."; +"MPC_FORGOT_PASSWORD_TITLE" = "Reset password using\nthe recovery link"; +"MPC_FORGOT_PASSWORD_SUBTITLE" = "Check your email for a message:\n'Welcome to Unstoppable Lite Wallet.' from\nno-reply@ud.me. It contains the recovery link we sent when you created your wallet. This is the only way to restore access if you forget your password."; +"MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" = "'Welcome to Unstoppable Lite Wallet.'\nno-reply@ud.me"; // Send crypto first time "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" = "Sending cryptocurrency for the first time?"; From 23ba39bbd4408aa6d096328f62f2b44f25db9f4e Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 10:40:12 +0300 Subject: [PATCH 03/10] Updated recovery illustration --- .../resetULWPasswordIllustration.svg | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/ULW/resetULWPasswordIllustration.imageset/resetULWPasswordIllustration.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/ULW/resetULWPasswordIllustration.imageset/resetULWPasswordIllustration.svg index 3369ffcfe..09b247193 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/ULW/resetULWPasswordIllustration.imageset/resetULWPasswordIllustration.svg +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/ULW/resetULWPasswordIllustration.imageset/resetULWPasswordIllustration.svg @@ -1,124 +1,124 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - + + - - + + - - - - - - - + + + + + + + - + - - + + - + - - + + - + - + - + - + - - + + - + - - + + - + - + - + - + - - + + - - + + - - + + From 6938f2b94daf82ae97bf4f606be60fe4234f38b5 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 10:56:10 +0300 Subject: [PATCH 04/10] Added forgot password button if failed during activation --- .../Entities/PreviewMPCConnector.swift | 2 ++ .../Extensions/Extension-String+Preview.swift | 1 + .../MPCActivateWalletEnterView.swift | 31 +++++++++++++++++-- .../Activating/MPCActivateWalletView.swift | 2 +- .../MPCEnterCredentialsView.swift | 2 +- .../Localization/en.lproj/Localizable.strings | 1 + 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift index 891fd2068..0363f5d6a 100644 --- a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift +++ b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift @@ -90,11 +90,13 @@ extension FB_UD_MPC { func submitBootstrapCode(_ code: String) async throws -> BootstrapCodeSubmitResponse { await Task.sleep(seconds: 0.5) +// throw MPCWalletError.incorrectCode return .init(accessToken: "sd", deviceId: "1") } func authNewDeviceWith(requestId: String, recoveryPhrase: String, accessToken: String) async throws { await Task.sleep(seconds: 0.5) +// throw MPCWalletError.incorrectPassword } func initTransactionWithNewKeyMaterials(accessToken: String) async throws -> SetupTokenResponse { diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift index f60e638b2..88030ef29 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift @@ -1313,6 +1313,7 @@ extension String { static let mpcForgotPasswordTitle = "MPC_FORGOT_PASSWORD_TITLE" static let mpcForgotPasswordSubtitle = "MPC_FORGOT_PASSWORD_SUBTITLE" static let mpcForgotPasswordSubtitleHighlights = "MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" + static let forgotPasswordTitle = "FORGOT_PASSWORD_TITLE" // Send crypto first time static let sendCryptoFirstTimePullUpTitle = "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift index a03def3fa..7e83379a5 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift @@ -17,19 +17,22 @@ struct MPCActivateWalletEnterView: View, ViewAnalyticsLogger { let confirmationCallback: (String)->() var changeEmailCallback: EmptyCallback? = nil @State private var input = "" + @State private var isPresentingForgotPasswordView = false var body: some View { VStack(spacing: isIPSE ? 16 : 24) { DismissIndicatorView() headerView() inputView() - if case .passcode(let resendAction) = dataType { - MPCResendCodeButton(email: email, resendAction: resendAction) - } + subActionView() actionButtonView() Spacer() } .padding() + .sheet(isPresented: $isPresentingForgotPasswordView) { + MPCForgotPasswordView() + .padding(.top, 32) + } } } @@ -116,6 +119,28 @@ private extension MPCActivateWalletEnterView { } } + @ViewBuilder + func subActionView() -> some View { + switch dataType { + case .passcode(let resendAction): + MPCResendCodeButton(email: email, resendAction: resendAction) + case .password: + forgotPasswordButtonView() + } + } + + @ViewBuilder + func forgotPasswordButtonView() -> some View { + UDButtonView(text: String.Constants.forgotPasswordTitle.localized(), + style: .large(.ghostPrimary), + callback: forgotPasswordButtonPressed) + } + + func forgotPasswordButtonPressed() { + logButtonPressedAnalyticEvents(button: .forgotPassword) + isPresentingForgotPasswordView = true + } + @ViewBuilder func actionButtonView() -> some View { UDButtonView(text: String.Constants.confirm.localized(), diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletView.swift index 26c88964b..d19375847 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletView.swift @@ -239,7 +239,7 @@ private extension MPCActivateWalletView { #Preview { MPCActivateWalletView(analyticsName: .mpcActivationOnboarding, - credentials: .init(email: "", + credentials: .init(email: "qq@qq.qq", password: ""), code: "", mpcWalletCreatedCallback: { _ in }, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift index bc2f9a960..04c6b2d52 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/EnterCredentials/MPCEnterCredentialsView.swift @@ -143,7 +143,7 @@ private extension MPCEnterCredentialsView { logButtonPressedAnalyticEvents(button: .forgotPassword) forgotPasswordCallback() } label: { - Text("Forgot password?") + Text(String.Constants.forgotPasswordTitle.localized()) .textAttributes(color: .foregroundAccent, fontSize: 13, fontWeight: .medium) diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings index 778eaae7e..0daf3e910 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings @@ -1207,6 +1207,7 @@ More tabs are coming in the next updates."; "MPC_FORGOT_PASSWORD_TITLE" = "Reset password using\nthe recovery link"; "MPC_FORGOT_PASSWORD_SUBTITLE" = "Check your email for a message:\n'Welcome to Unstoppable Lite Wallet.' from\nno-reply@ud.me. It contains the recovery link we sent when you created your wallet. This is the only way to restore access if you forget your password."; "MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" = "'Welcome to Unstoppable Lite Wallet.'\nno-reply@ud.me"; +"FORGOT_PASSWORD_TITLE" = "Forgot password?"; // Send crypto first time "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" = "Sending cryptocurrency for the first time?"; From 0f8faa1b9f41dad326e4f8aa24bc8fcd742d9e07 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 11:57:11 +0300 Subject: [PATCH 05/10] Implemented MPCRequestRecoveryView UI --- .../project.pbxproj | 12 ++ .../Entities/Mock/MockEntitiesFabric.swift | 5 +- .../Extensions/Extension-String+Preview.swift | 7 + .../Recovery/MPCRecoveryRequestedView.swift | 20 +++ .../MPC/Recovery/MPCRequestRecoveryView.swift | 123 ++++++++++++++++++ .../Modules/WalletDetails/WalletDetails.swift | 9 +- .../WalletDetails/WalletDetailsView.swift | 14 +- .../AnalyticsServiceEnvironment.swift | 4 +- .../Common/folderIcon.imageset/Contents.json | 15 +++ .../Common/folderIcon.imageset/folderIcon.svg | 3 + .../Localization/en.lproj/Localizable.strings | 7 + .../SwiftUI/Extensions/Image.swift | 1 + 12 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift create mode 100644 unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/Contents.json create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/folderIcon.svg diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index 4d24704d5..0fa6ae1d2 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -1477,6 +1477,10 @@ C69698982ACBCDDB0000738C /* PushWebSocketsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69698972ACBCDDB0000738C /* PushWebSocketsService.swift */; }; C696989A2ACBCE640000738C /* PushMessagingWebSocketsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69698992ACBCE640000738C /* PushMessagingWebSocketsService.swift */; }; C696989C2ACC043A0000738C /* MessagingCommunitiesChatDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C696989B2ACC043A0000738C /* MessagingCommunitiesChatDetails.swift */; }; + C69CA5D02CB5210D00C30DF7 /* MPCRequestRecoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69CA5CF2CB5210D00C30DF7 /* MPCRequestRecoveryView.swift */; }; + C69CA5D12CB5210D00C30DF7 /* MPCRequestRecoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69CA5CF2CB5210D00C30DF7 /* MPCRequestRecoveryView.swift */; }; + C69CA5D32CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69CA5D22CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift */; }; + C69CA5D42CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69CA5D22CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift */; }; C69F99202A9F1264004B1958 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69F99102A9F1264004B1958 /* Line.swift */; }; C69F99212A9F1264004B1958 /* HexagonShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69F99112A9F1264004B1958 /* HexagonShape.swift */; }; C69F99232A9F1264004B1958 /* AttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69F99142A9F1264004B1958 /* AttributedText.swift */; }; @@ -3733,6 +3737,8 @@ C69698972ACBCDDB0000738C /* PushWebSocketsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushWebSocketsService.swift; sourceTree = ""; }; C69698992ACBCE640000738C /* PushMessagingWebSocketsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagingWebSocketsService.swift; sourceTree = ""; }; C696989B2ACC043A0000738C /* MessagingCommunitiesChatDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingCommunitiesChatDetails.swift; sourceTree = ""; }; + C69CA5CF2CB5210D00C30DF7 /* MPCRequestRecoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCRequestRecoveryView.swift; sourceTree = ""; }; + C69CA5D22CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCRecoveryRequestedView.swift; sourceTree = ""; }; C69F99102A9F1264004B1958 /* Line.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; C69F99112A9F1264004B1958 /* HexagonShape.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HexagonShape.swift; sourceTree = ""; }; C69F99142A9F1264004B1958 /* AttributedText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedText.swift; sourceTree = ""; }; @@ -7193,6 +7199,8 @@ C69CA5CB2CB5140200C30DF7 /* Recovery */ = { isa = PBXGroup; children = ( + C69CA5CF2CB5210D00C30DF7 /* MPCRequestRecoveryView.swift */, + C69CA5D22CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift */, ); path = Recovery; sourceTree = ""; @@ -9294,6 +9302,7 @@ C6F3FD632AAA11E700208679 /* PullUpNamespace.swift in Sources */, C6C99582289D313D00367362 /* CNavigationBarContentView.swift in Sources */, C6D5DAE928374C7300379C38 /* WalletAddressFieldCollectionCell.swift in Sources */, + C69CA5D42CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift in Sources */, C6B65F942B5665F0006D1812 /* HomeTabView.swift in Sources */, C6BF80352BA2ED7100CC4CB3 /* FireblockJoinWalletHandlerImplementation.swift in Sources */, C628E37227FDE1D60044E408 /* UDSubtitleLabel.swift in Sources */, @@ -9527,6 +9536,7 @@ C6B65F7E2B55153E006D1812 /* HomeWalletView+Entities.swift in Sources */, C6D5DAD428374B4600379C38 /* ManageMultiChainDomainAddressesViewController.swift in Sources */, 29BECD22297ABF3C00662FC1 /* UDWalletWithPrivateSeed.swift in Sources */, + C69CA5D02CB5210D00C30DF7 /* MPCRequestRecoveryView.swift in Sources */, C60C298C2834E30000626851 /* GroupedCoinRecord.swift in Sources */, C66430FD28D8B85800A9C734 /* ConnectExternalWalletViewPresenter.swift in Sources */, C6D868192BF3218100EEBCFC /* EnterDomainEmailView.swift in Sources */, @@ -11111,6 +11121,7 @@ C68F15742BD69FC50049BFA2 /* MPCActivateWalletEnterDataType.swift in Sources */, C6D645AC2B1DBB5700D724AC /* CollectionReusableRoundedBackground.swift in Sources */, C6952A3B2BC5053800F4B475 /* FB_UD_MPCBlockchain.swift in Sources */, + C69CA5D32CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift in Sources */, C67213E32BAA967C0075B9C7 /* SendCryptoAssetRootView.swift in Sources */, C6C8F8E52B21836400A9834D /* ParkedDomainsFoundViewPresenter.swift in Sources */, C6C8F8662B21811000A9834D /* UDWalletBackUpError.swift in Sources */, @@ -11317,6 +11328,7 @@ C6D646462B1E084F00D724AC /* ViewPullUp.swift in Sources */, C67B1DE02BFEF7BA00C2A4DA /* ReconnectMPCWalletPromptView.swift in Sources */, C671CD1B2BC6809D005DA2FB /* DomainToPurchaseSuggestion.swift in Sources */, + C69CA5D12CB5210D00C30DF7 /* MPCRequestRecoveryView.swift in Sources */, C6C8F8E22B21836400A9834D /* ParkedDomainsFoundViewController.swift in Sources */, C6D647022B1ED7C300D724AC /* MessagingChatConversationState.swift in Sources */, C6D647542B1EDBAE00D724AC /* WalletInfoBadgeView.swift in Sources */, diff --git a/unstoppable-ios-app/domains-manager-ios/Entities/Mock/MockEntitiesFabric.swift b/unstoppable-ios-app/domains-manager-ios/Entities/Mock/MockEntitiesFabric.swift index 7de1d4015..c9fde841c 100644 --- a/unstoppable-ios-app/domains-manager-ios/Entities/Mock/MockEntitiesFabric.swift +++ b/unstoppable-ios-app/domains-manager-ios/Entities/Mock/MockEntitiesFabric.swift @@ -72,7 +72,10 @@ extension MockEntitiesFabric { type: .generatedLocally), .init(aliasName: "UD", address: "0xCeBF5440FE9C85e037A80fFB4dF0F6a9BAcb3d01", - type: .generatedLocally)] + type: .generatedLocally), + .init(aliasName: "MPC", + address: "0xCeBF5440FE9C85e037A80fFB4dF0F6a9BAcb3d02", + type: .mpc)] static func mockExternalWallet(hasRRDomain: Bool = true) -> WalletEntity { let address = "0x84585360d34f6c72BE438fdde7147D27d2A85f9f" diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift index 88030ef29..85f6c1c77 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift @@ -1314,6 +1314,13 @@ extension String { static let mpcForgotPasswordSubtitle = "MPC_FORGOT_PASSWORD_SUBTITLE" static let mpcForgotPasswordSubtitleHighlights = "MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" static let forgotPasswordTitle = "FORGOT_PASSWORD_TITLE" + static let mpcRequestRecoveryTitle = "MPC_REQUEST_RECOVERY_TITLE" + static let mpcRequestRecoverySubtitle = "MPC_REQUEST_RECOVERY_SUBTITLE" + static let mpcRecoveryRequestedTitle = "MPC_RECOVERY_REQUESTED_TITLE" + static let mpcRecoveryRequestedSubtitle = "MPC_RECOVERY_REQUESTED_SUBTITLE" + static let mpcRecoveryRequestedSubtitleHighlights = "MPC_RECOVERY_REQUESTED_SUBTITLE_HIGHLIGHTS" + static let mpcRecoveryRequestedHintNotShare = "MPC_RECOVERY_REQUESTED_HINT_NOT_SHARE" + static let mpcRecoveryRequestedHintPreviousInactive = "MPC_RECOVERY_REQUESTED_HINT_PREVIOUS_INACTIVE" // Send crypto first time static let sendCryptoFirstTimePullUpTitle = "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift new file mode 100644 index 000000000..e93c48546 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift @@ -0,0 +1,20 @@ +// +// MPCRecoveryRequestedView.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 8.10.2024. +// + +import SwiftUI + +struct MPCRecoveryRequestedView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + NavigationStack { + MPCRecoveryRequestedView() + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift new file mode 100644 index 000000000..429a94b19 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift @@ -0,0 +1,123 @@ +// +// MPCRequestRecoveryView.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 8.10.2024. +// + +import SwiftUI + +struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { + + @Environment(\.dismiss) var dismiss + + @State private var passwordInput: String = "" + @State private var isLoading: Bool = false + @State private var didRequestRecovery: Bool = false + @State private var isPresentingForgotPasswordView: Bool = false + + var analyticsName: Analytics.ViewName { .mpcRequestRecovery } + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: 32) { + headerView() + passwordInputView() + Spacer() + VStack(spacing: 16) { + forgotPasswordButtonView() + confirmButtonView() + } + } + .padding(.horizontal, 16) + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + CloseButtonView(closeCallback: closeButtonPressed) + } + } + .sheet(isPresented: $isPresentingForgotPasswordView) { + MPCForgotPasswordView() + .padding(.top, 32) + } + .navigationDestination(isPresented: $didRequestRecovery) { + MPCRecoveryRequestedView() + } + } + } + +} + +// MARK: - Private methods +private extension MPCRequestRecoveryView { + @ViewBuilder + func headerView() -> some View { + VStack(spacing: 20) { + Image.folderIcon + .resizable() + .squareFrame(56) + .foregroundStyle(Color.foregroundSecondary) + VStack(spacing: 16) { + Text(String.Constants.mpcRequestRecoveryTitle.localized()) + .titleText() + Text(String.Constants.mpcRequestRecoverySubtitle.localized()) + .subtitleText() + } + .multilineTextAlignment(.center) + } + } + + @ViewBuilder + func passwordInputView() -> some View { + UDTextFieldView(text: $passwordInput, + placeholder: String.Constants.password.localized(), + focusBehaviour: .activateOnAppear, + autocapitalization: .never, + autocorrectionDisabled: true, + isSecureInput: true) + } + + @ViewBuilder + func forgotPasswordButtonView() -> some View { + UDButtonView(text: String.Constants.forgotPasswordTitle.localized(), + style: .large(.ghostPrimary), + callback: forgotPasswordButtonPressed) + } + + @ViewBuilder + func confirmButtonView() -> some View { + UDButtonView(text: String.Constants.confirm.localized(), + style: .large(.raisedPrimary), + isLoading: isLoading, + callback: confirmButtonPressed) + } +} + +// MARK: - Private methods +private extension MPCRequestRecoveryView { + func closeButtonPressed() { + logButtonPressedAnalyticEvents(button: .close) + dismiss() + } + + func forgotPasswordButtonPressed() { + logButtonPressedAnalyticEvents(button: .forgotPassword) + isPresentingForgotPasswordView = true + } + + func confirmButtonPressed() { + logButtonPressedAnalyticEvents(button: .confirm) + + Task { + isLoading = true + await Task.sleep(seconds: 1) + didRequestRecovery = true + isLoading = false + } + } +} + +#Preview { + MPCRequestRecoveryView() +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetails.swift b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetails.swift index 928d1597b..6b3cd30e9 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetails.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetails.swift @@ -101,6 +101,7 @@ extension WalletDetails { case recoveryPhrase case removeWallet case disconnectWallet + case mpcRecoveryKit var title: String { switch self { @@ -112,6 +113,8 @@ extension WalletDetails { return String.Constants.removeWallet.localized() case .disconnectWallet: return String.Constants.disconnectWallet.localized() + case .mpcRecoveryKit: + return "Request recovery kit" } } @@ -121,12 +124,14 @@ extension WalletDetails { return Image.systemDocOnDoc case .removeWallet, .disconnectWallet: return Image.trashIcon + case .mpcRecoveryKit: + return Image(systemName: "list.bullet.rectangle.portrait") } } var isDestructive: Bool { switch self { - case .recoveryPhrase, .privateKey: + case .recoveryPhrase, .privateKey, .mpcRecoveryKit: return false case .removeWallet, .disconnectWallet: return true @@ -139,6 +144,8 @@ extension WalletDetails { return .walletRecoveryPhrase case .removeWallet, .disconnectWallet: return .walletRemove + case .mpcRecoveryKit: + return .mpcRecoveryKit } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift index 41f799fa9..132995a22 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift @@ -188,6 +188,9 @@ private extension WalletDetailsView { } } } + if wallet.udWallet.type == .mpc { + subActions.append(.mpcRecoveryKit) + } if wallet.displayInfo.isConnected { subActions.append(.disconnectWallet) @@ -256,6 +259,8 @@ private extension WalletDetailsView { revealRecoveryPhrase(recoveryType: .recoveryPhrase) case .removeWallet, .disconnectWallet: askToRemoveWallet() + case .mpcRecoveryKit: + requestMPCWalletRecoveryKit() } } @@ -288,6 +293,9 @@ private extension WalletDetailsView { } } + func requestMPCWalletRecoveryKit() { + + } } // MARK: - Domains list @@ -381,6 +389,8 @@ private extension WalletDetailsView { } #Preview { - WalletDetailsView(wallet: MockEntitiesFabric.Wallet.mockEntities()[0], - source: .settings) + let wallets = MockEntitiesFabric.Wallet.mockEntities() + + return WalletDetailsView(wallet: wallets[0], + source: .settings) } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift index e32763476..8a6222b62 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift @@ -269,6 +269,8 @@ extension Analytics { case fullMaintenance case mintingDomainsList case purchaseDomainsCart, purchaseDomainsFilters, purchaseDomainsCompleted + + case mpcRequestRecovery } } @@ -342,7 +344,7 @@ extension Analytics { case walletBackup, walletRecoveryPhrase, walletRename, walletDomainsList, walletRemove, showConnectedWalletInfo, walletReverseResolution, walletReconnect // Wallets list - case manageICloudBackups, walletInList, walletsMenu + case manageICloudBackups, walletInList, walletsMenu, mpcRecoveryKit // Web view case refreshPage, openBrowser, moveBack, moveForward diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/Contents.json new file mode 100644 index 000000000..8970fb67f --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "folderIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/folderIcon.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/folderIcon.svg new file mode 100644 index 000000000..338e137fb --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/folderIcon.imageset/folderIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings index 0daf3e910..d4abe37a1 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings @@ -1208,6 +1208,13 @@ More tabs are coming in the next updates."; "MPC_FORGOT_PASSWORD_SUBTITLE" = "Check your email for a message:\n'Welcome to Unstoppable Lite Wallet.' from\nno-reply@ud.me. It contains the recovery link we sent when you created your wallet. This is the only way to restore access if you forget your password."; "MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" = "'Welcome to Unstoppable Lite Wallet.'\nno-reply@ud.me"; "FORGOT_PASSWORD_TITLE" = "Forgot password?"; +"MPC_REQUEST_RECOVERY_TITLE" = "Request recovery kit"; +"MPC_REQUEST_RECOVERY_SUBTITLE" = "Receive an email with the recovery kit after confirming your current password."; +"MPC_RECOVERY_REQUESTED_TITLE" = "Recovery kit sent"; +"MPC_RECOVERY_REQUESTED_SUBTITLE" = "Check your email for a message:\n'Unstoppable Lite Wallet Recovery Kit'\nfrom unstoppabledomains.com"; +"MPC_RECOVERY_REQUESTED_SUBTITLE_HIGHLIGHTS" = "'Unstoppable Lite Wallet Recovery Kit'\nunstoppabledomains.com"; +"MPC_RECOVERY_REQUESTED_HINT_NOT_SHARE" = "Do not share your recovery kit\nwith anyone"; +"MPC_RECOVERY_REQUESTED_HINT_PREVIOUS_INACTIVE" = "Previous recovery kits will become inactive when a new one is created"; // Send crypto first time "SEND_CRYPTO_FIRST_TIME_PULL_UP_TITLE" = "Sending cryptocurrency for the first time?"; diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift index b8d593f88..8bc84fb04 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift @@ -135,6 +135,7 @@ extension Image { static let layoutGridTwo = Image("layoutGridTwo") static let twoCoinsIcon = Image("twoCoinsIcon") static let filter = Image("filter") + static let folderIcon = Image("folderIcon") static let systemDocOnDoc = Image(systemName: "doc.on.doc") static let systemAppBadgeCheckmark = Image(systemName: "app.badge.checkmark") From 1cffeab73f4cfdf4454cb9085c8801c4a24d0159 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 12:38:24 +0300 Subject: [PATCH 06/10] Implemented MPCRecoveryRequestedView UI --- .../Recovery/MPCRecoveryRequestedView.swift | 157 +++++++++++++++++- .../MPC/Recovery/MPCRequestRecoveryView.swift | 20 ++- .../AnalyticsServiceEnvironment.swift | 2 +- .../Common/brokenHeart.imageset/Contents.json | 15 ++ .../brokenHeart.imageset/brokenHeart.svg | 3 + .../Common/invite.imageset/Contents.json | 15 ++ .../Common/invite.imageset/invite.svg | 4 + .../SwiftUI/Extensions/Image.swift | 2 + 8 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/Contents.json create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/brokenHeart.svg create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/Contents.json create mode 100644 unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/invite.svg diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift index e93c48546..ecb08e1b4 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift @@ -7,14 +7,165 @@ import SwiftUI -struct MPCRecoveryRequestedView: View { +struct MPCRecoveryRequestedView: View, ViewAnalyticsLogger { + + @Environment(\.dismiss) var dismiss + + let email: String + var closeCallback: EmptyCallback? = nil + + var analyticsName: Analytics.ViewName { .mpcRecoveryRequested } + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + ScrollView { + VStack(spacing: 32) { + headerView() + VStack(spacing: 16) { + infoSection() + hintsSection() + } + Spacer() + VStack(spacing: 16) { + openMailButtonView() + doneButtonView() + } + } + .padding(.horizontal, 16) + } + .navigationBarBackButtonHidden() + .toolbar { + ToolbarItem(placement: .topBarLeading) { + CloseButtonView(closeCallback: closeButtonPressed) + } + } + } +} + +// MARK: - Private methods +private extension MPCRecoveryRequestedView { + var subtitleAttributesList: [AttributedText.AttributesList] { + let subtitleHighlights = String.Constants.mpcRecoveryRequestedSubtitleHighlights.localized() + let stringsToUpdate = subtitleHighlights.components(separatedBy: "\n") + let attributesList: [AttributedText.AttributesList] = stringsToUpdate.map { + .init(text: $0, + font: .currentFont(withSize: 16, + weight: .medium), + textColor: .foregroundDefault) + } + + return attributesList + } + + @ViewBuilder + func headerView() -> some View { + VStack(spacing: 20) { + Image.checkCircle + .resizable() + .squareFrame(56) + .foregroundStyle(Color.foregroundSuccess) + VStack(spacing: 16) { + Text(String.Constants.mpcRecoveryRequestedTitle.localized()) + .titleText() + + AttributedText(attributesList: .init(text: String.Constants.mpcRecoveryRequestedSubtitle.localized(), + font: .currentFont(withSize: 16), + textColor: .foregroundSecondary, + alignment: .center), + updatedAttributesList: subtitleAttributesList) + + } + } + } + + @ViewBuilder + func infoSection() -> some View { + sectionWith(items: [.init(title: String.Constants.sentToN.localized(email), + icon: .invite)]) + } + + @ViewBuilder + func hintsSection() -> some View { + sectionWith(items: [.init(title: String.Constants.mpcRecoveryRequestedHintNotShare.localized(), + icon: .crossWhite), + .init(title: String.Constants.mpcRecoveryRequestedHintPreviousInactive.localized(), + icon: .brokenHeart)]) + } + + @ViewBuilder + func sectionWith(items: [SectionItem]) -> some View { + UDCollectionSectionBackgroundView { + VStack(alignment: .center, spacing: 0) { + ForEach(items) { item in + listItemWith(sectionItem: item) + } + } + .padding(4) + } + } + + @ViewBuilder + func listItemWith(sectionItem: SectionItem) -> some View { + listItemWith(title: sectionItem.title, + icon: sectionItem.icon) + } + + @ViewBuilder + func listItemWith(title: String, icon: Image) -> some View { + UDListItemView(title: title, + imageType: .image(icon), + imageStyle: .centred()) + .padding(.vertical, 4) + .udListItemInCollectionButtonPadding() + } + + @ViewBuilder + func openMailButtonView() -> some View { + UDButtonView(text: String.Constants.openEmailApp.localized(), + style: .large(.ghostPrimary), + callback: openMailButtonPressed) + } + + @ViewBuilder + func doneButtonView() -> some View { + UDButtonView(text: String.Constants.doneButtonTitle.localized(), + style: .large(.raisedPrimary), + callback: doneButtonPressed) + } +} + +// MARK: - Private methods +private extension MPCRecoveryRequestedView { + func closeButtonPressed() { + logButtonPressedAnalyticEvents(button: .close) + close() + } + + func openMailButtonPressed() { + logButtonPressedAnalyticEvents(button: .openEmailApp) + openMailApp() + } + + func doneButtonPressed() { + logButtonPressedAnalyticEvents(button: .done) + close() + } + + func close() { + closeCallback?() ?? dismiss() + } +} + +// MARK: - Private methods +private extension MPCRecoveryRequestedView { + struct SectionItem: Identifiable { + let id = UUID() + let title: String + let icon: Image } } #Preview { NavigationStack { - MPCRecoveryRequestedView() + MPCRecoveryRequestedView(email: "qwerty@example.com") } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift index 429a94b19..67d6801b6 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift @@ -13,13 +13,13 @@ struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { @State private var passwordInput: String = "" @State private var isLoading: Bool = false - @State private var didRequestRecovery: Bool = false + @State private var path: [String] = [] @State private var isPresentingForgotPasswordView: Bool = false var analyticsName: Analytics.ViewName { .mpcRequestRecovery } var body: some View { - NavigationStack { + NavigationStack(path: $path) { ScrollView { VStack(spacing: 32) { headerView() @@ -41,9 +41,10 @@ struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { MPCForgotPasswordView() .padding(.top, 32) } - .navigationDestination(isPresented: $didRequestRecovery) { - MPCRecoveryRequestedView() - } + .navigationDestination(for: String.self, destination: { email in + MPCRecoveryRequestedView(email: email, + closeCallback: close) + }) } } @@ -98,7 +99,7 @@ private extension MPCRequestRecoveryView { private extension MPCRequestRecoveryView { func closeButtonPressed() { logButtonPressedAnalyticEvents(button: .close) - dismiss() + close() } func forgotPasswordButtonPressed() { @@ -112,10 +113,15 @@ private extension MPCRequestRecoveryView { Task { isLoading = true await Task.sleep(seconds: 1) - didRequestRecovery = true + let email = "qqq@qqq.qq" // Get from the response + path.append(email) isLoading = false } } + + func close() { + dismiss() + } } #Preview { diff --git a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift index 8a6222b62..9b4085fe8 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift @@ -270,7 +270,7 @@ extension Analytics { case mintingDomainsList case purchaseDomainsCart, purchaseDomainsFilters, purchaseDomainsCompleted - case mpcRequestRecovery + case mpcRequestRecovery, mpcRecoveryRequested } } diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/Contents.json new file mode 100644 index 000000000..2b819e39f --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "brokenHeart.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/brokenHeart.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/brokenHeart.svg new file mode 100644 index 000000000..63a40d180 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/brokenHeart.imageset/brokenHeart.svg @@ -0,0 +1,3 @@ + + + diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/Contents.json new file mode 100644 index 000000000..f0c89fe74 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "invite.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/invite.svg b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/invite.svg new file mode 100644 index 000000000..ad0372925 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/Common/invite.imageset/invite.svg @@ -0,0 +1,4 @@ + + + + diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift index 8bc84fb04..ff9365896 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift @@ -136,6 +136,8 @@ extension Image { static let twoCoinsIcon = Image("twoCoinsIcon") static let filter = Image("filter") static let folderIcon = Image("folderIcon") + static let brokenHeart = Image("brokenHeart") + static let invite = Image("invite") static let systemDocOnDoc = Image(systemName: "doc.on.doc") static let systemAppBadgeCheckmark = Image(systemName: "app.badge.checkmark") From 38d8f3845b2c866c04ebc55a1f4e9ae5e350b89f Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 14:17:26 +0300 Subject: [PATCH 07/10] Implemented API call to request recovery --- .../FB_UD_MPCConnectionService.swift | 10 ++++++++++ ...B_UD_DefaultMPCConnectionNetworkService.swift | 16 ++++++++++++++++ .../FB_UD_MPCConnectionNetworkService.swift | 2 ++ .../Fireblocks+UD/Network/FB_UD_MPCNetwork.swift | 2 ++ .../MPCWalletProviderSubServiceProtocol.swift | 4 ++++ .../MPCWalletsService/MPCWalletsService.swift | 7 +++++++ .../MPCWalletsServiceProtocol.swift | 4 ++++ .../MPC/Recovery/MPCRequestRecoveryView.swift | 10 +++++++--- 8 files changed, 52 insertions(+), 3 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift index 875259704..0672dec62 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift @@ -376,6 +376,16 @@ extension FB_UD_MPC.MPCConnectionService: MPCWalletProviderSubServiceProtocol { return account.createTokens() } + func requestRecovery(for walletMetadata: MPCWalletMetadata, + password: String) async throws -> String { + let connectedWalletDetails = try getConnectedWalletDetailsFor(walletMetadata: walletMetadata) + + return try await performAuthErrorCatchingBlock(connectedWalletDetails: connectedWalletDetails) { token in + try await networkService.requestRecovery(token, password: password) + return connectedWalletDetails.email + } + } + private func convertMPCMessageTypeToFBUDEncoding(_ type: MPCMessage.MPCMessageType) -> FB_UD_MPC.SignMessageEncoding { switch type { case .hex: diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_DefaultMPCConnectionNetworkService.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_DefaultMPCConnectionNetworkService.swift index 5d9c26a0e..62569f775 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_DefaultMPCConnectionNetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_DefaultMPCConnectionNetworkService.swift @@ -415,6 +415,22 @@ extension FB_UD_MPC { return response } + func requestRecovery(_ accessToken: String, + password: String) async throws { + struct RequestBody: Codable { + let recoveryPassphrase: String + } + + let url = MPCNetwork.URLSList.recoveryURL + let body = RequestBody(recoveryPassphrase: password) + let headers = buildAuthBearerHeader(token: accessToken) + let request = try APIRequest(urlString: url, + body: body, + method: .post, + headers: headers) + try await makeAPIRequest(request) + } + // MARK: - Private methods private func makeDecodableAPIRequest(_ apiRequest: APIRequest) async throws -> T { do { diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCConnectionNetworkService.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCConnectionNetworkService.swift index 0438ab365..b73d91579 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCConnectionNetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCConnectionNetworkService.swift @@ -60,5 +60,7 @@ extension FB_UD_MPC { assetId: String, destinationAddress: String, amount: String) async throws -> NetworkFeeResponse + func requestRecovery(_ accessToken: String, + password: String) async throws } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCNetwork.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCNetwork.swift index 6f87f61d3..f91cdad5a 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCNetwork.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/Network/FB_UD_MPCNetwork.swift @@ -58,6 +58,8 @@ extension FB_UD_MPC { static func operationURL(operationId: String) -> String { operationsURL.appendingURLPathComponents(operationId) } + + static var recoveryURL: String { v1URL.appendingURLPathComponents("recovery", "email") } } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/MPCWalletProviderSubServiceProtocol.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/MPCWalletProviderSubServiceProtocol.swift index 2db5a5b55..efc2ced32 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/MPCWalletProviderSubServiceProtocol.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/MPCWalletProviderSubServiceProtocol.swift @@ -40,4 +40,8 @@ protocol MPCWalletProviderSubServiceProtocol { chain: String, destinationAddress: String, by walletMetadata: MPCWalletMetadata) async throws -> Double + /// Request recovery kit for given wallet + /// - Returns: Email from attached to wallet account + func requestRecovery(for walletMetadata: MPCWalletMetadata, + password: String) async throws -> String } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsService.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsService.swift index 7a18628d6..52863fe45 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsService.swift @@ -139,6 +139,13 @@ extension MPCWalletsService: MPCWalletsServiceProtocol { destinationAddress: destinationAddress, by: walletMetadata) } + + func requestRecovery(password: String, + by walletMetadata: MPCWalletMetadata) async throws -> String { + let subService = try getSubServiceFor(provider: walletMetadata.provider) + + return try await subService.requestRecovery(for: walletMetadata, password: password) + } } // MARK: - Private methods diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsServiceProtocol.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsServiceProtocol.swift index 221376917..e22fd8b87 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsServiceProtocol.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletsServiceProtocol.swift @@ -37,6 +37,10 @@ protocol MPCWalletsServiceProtocol { chain: String, destinationAddress: String, by walletMetadata: MPCWalletMetadata) async throws -> Double + /// Request recovery kit for given wallet + /// - Returns: Email from attached to wallet account + func requestRecovery(password: String, + by walletMetadata: MPCWalletMetadata) async throws -> String } @MainActor diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift index 67d6801b6..3e0ecc84e 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift @@ -10,7 +10,9 @@ import SwiftUI struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { @Environment(\.dismiss) var dismiss - + @Environment(\.mpcWalletsService) private var mpcWalletsService + + let mpcWalletMetadata: MPCWalletMetadata @State private var passwordInput: String = "" @State private var isLoading: Bool = false @State private var path: [String] = [] @@ -113,7 +115,8 @@ private extension MPCRequestRecoveryView { Task { isLoading = true await Task.sleep(seconds: 1) - let email = "qqq@qqq.qq" // Get from the response + let email = try await mpcWalletsService.requestRecovery(password: passwordInput, + by: mpcWalletMetadata) path.append(email) isLoading = false } @@ -125,5 +128,6 @@ private extension MPCRequestRecoveryView { } #Preview { - MPCRequestRecoveryView() + MPCRequestRecoveryView(mpcWalletMetadata: MPCWalletMetadata(provider: MPCWalletProvider.fireblocksUD, + metadata: nil)) } From 08b3817f27afa526a6a184676b76f860cf5e9443 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 14:59:08 +0300 Subject: [PATCH 08/10] Display request recovery flow --- .../Entities/PreviewMPCConnector.swift | 4 ++++ .../project.pbxproj | 6 ++++++ .../Modules/Home/HomeTabRouter.swift | 2 ++ .../Modules/Home/HomeTabView.swift | 4 ++++ .../Subviews/HomeWalletActionsView.swift | 1 - .../Activating/MPCActivateWalletEnterView.swift | 3 +-- .../ForgotPassword/MPCForgotPasswordView.swift | 3 ++- .../FB_UD_MPCConnectionService.swift | 16 +++++++++++++--- .../MPC/Recovery/MPCRequestRecoveryView.swift | 17 ++++++++++------- .../MPCWalletMetadataDisplayInfo.swift | 13 +++++++++++++ .../WalletDetails/WalletDetailsView.swift | 6 ++++++ 11 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/MPCWalletMetadataDisplayInfo.swift diff --git a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift index 0363f5d6a..a23e2d069 100644 --- a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift +++ b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewMPCConnector.swift @@ -138,6 +138,10 @@ extension FB_UD_MPC { await Task.sleep(seconds: 0.5) return .init(items: []) } + + func requestRecovery(_ accessToken: String, password: String) async throws { + await Task.sleep(seconds: 0.5) + } } struct MPCWalletsDefaultDataStorage: MPCWalletsDataStorage { diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index 0fa6ae1d2..0e64e262c 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -1372,6 +1372,8 @@ C68F157A2BD76A550049BFA2 /* MPCWalletActivationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C68F15782BD76A550049BFA2 /* MPCWalletActivationState.swift */; }; C68F157C2BD76A650049BFA2 /* MPCWalletActivationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C68F157B2BD76A650049BFA2 /* MPCWalletActivationError.swift */; }; C68F157D2BD76A650049BFA2 /* MPCWalletActivationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C68F157B2BD76A650049BFA2 /* MPCWalletActivationError.swift */; }; + C6918C692CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6918C682CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift */; }; + C6918C6A2CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6918C682CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift */; }; C691CF6C28BE4DA80077C59F /* HelveticaNeue-Custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C691CF6B28BE4DA80077C59F /* HelveticaNeue-Custom.ttf */; }; C692C304282E012900C31393 /* WalletDataValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692C303282E012900C31393 /* WalletDataValidator.swift */; }; C692C309282E0BC300C31393 /* EnterBackupToBackupWalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C692C308282E0BC300C31393 /* EnterBackupToBackupWalletPresenter.swift */; }; @@ -3684,6 +3686,7 @@ C68F15752BD769830049BFA2 /* MPCWalletStateCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCWalletStateCardView.swift; sourceTree = ""; }; C68F15782BD76A550049BFA2 /* MPCWalletActivationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCWalletActivationState.swift; sourceTree = ""; }; C68F157B2BD76A650049BFA2 /* MPCWalletActivationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCWalletActivationError.swift; sourceTree = ""; }; + C6918C682CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPCWalletMetadataDisplayInfo.swift; sourceTree = ""; }; C691CF6B28BE4DA80077C59F /* HelveticaNeue-Custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeue-Custom.ttf"; sourceTree = ""; }; C692C303282E012900C31393 /* WalletDataValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletDataValidator.swift; sourceTree = ""; }; C692C308282E0BC300C31393 /* EnterBackupToBackupWalletPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterBackupToBackupWalletPresenter.swift; sourceTree = ""; }; @@ -4674,6 +4677,7 @@ C6A231FB2BEB494D0037E093 /* WalletDetailsDomainItemView.swift */, C6A231FE2BEB52CB0037E093 /* WalletSourceImageView.swift */, C6A232012BEB53310037E093 /* RenameWalletView.swift */, + C6918C682CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift */, ); path = WalletDetails; sourceTree = ""; @@ -9076,6 +9080,7 @@ C6526DD929D2DF6E00D6F2EB /* LoginWithEmailInAppViewPresenter.swift in Sources */, C63095D62B0DA61600205054 /* PublishingAppStorage.swift in Sources */, C6B65F9E2B57876F006D1812 /* HomeWalletNFTCellView.swift in Sources */, + C6918C692CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift in Sources */, 29BECD182979BE4000662FC1 /* Date.swift in Sources */, C61308922A7A22AD00E7CE21 /* SwiftUIZoomableContainer.swift in Sources */, C679B605291A579500F543A7 /* DomainProfileUpdatingRecordsSection.swift in Sources */, @@ -10398,6 +10403,7 @@ C61808302B19AD9D0032E543 /* PreviewStripeService.swift in Sources */, C6C837A22B5FF307000A6AF5 /* TabBarVisibleModifier.swift in Sources */, C6D645BC2B1DBCE400D724AC /* ScrollViewOffsetListener.swift in Sources */, + C6918C6A2CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift in Sources */, C621A43D2BB10D2900CB5CB9 /* SendCryptoQRWalletAddressScannerView.swift in Sources */, C6C8F8352B217E9600A9834D /* CreateLocalWalletRecoveryWordsPresenter.swift in Sources */, C632BE4B2BA5984100C95B2D /* PreviewMPCConnector.swift in Sources */, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift index 77ba92d26..893783ef0 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabRouter.swift @@ -24,6 +24,7 @@ final class HomeTabRouter: ObservableObject { @Published var exploreTabNavPath: NavigationPathWrapper = .init() @Published var activityTabNavPath: NavigationPathWrapper = .init() @Published var presentedNFT: NFTDisplayInfo? + @Published var requestingRecoveryMPC: MPCWalletMetadataDisplayInfo? @Published var presentedDomain: DomainPresentationDetails? @Published var presentedPublicDomain: PublicProfileViewConfiguration? @@ -304,6 +305,7 @@ extension HomeTabRouter { resolvingPrimaryDomainWallet = nil showingWalletInfo = nil sendCryptoInitialData = nil + requestingRecoveryMPC = nil walletViewNavPath.removeAll() chatTabNavPath.removeAll() exploreTabNavPath.removeAll() diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabView.swift index 262c109ff..d46527282 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/HomeTabView.swift @@ -42,6 +42,10 @@ struct HomeTabView: View { NFTDetailsView(nft: nft) .pullUpHandler(router) }) + .sheet(item: $router.requestingRecoveryMPC, content: { mpcWalletMetadataDisplayInfo in + MPCRequestRecoveryView(mpcWalletMetadata: mpcWalletMetadataDisplayInfo.walletMetadata) + .pullUpHandler(router) + }) .sheet(isPresented: $router.showingUpdatedToWalletGreetings, content: { UpdateToWalletGreetingsView() }) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Home/Wallet/HomeWalletView/Subviews/HomeWalletActionsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Home/Wallet/HomeWalletView/Subviews/HomeWalletActionsView.swift index dbee7c34e..17164bf93 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Home/Wallet/HomeWalletView/Subviews/HomeWalletActionsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Home/Wallet/HomeWalletView/Subviews/HomeWalletActionsView.swift @@ -50,7 +50,6 @@ private extension HomeWalletActionsView { icon: { subAction.icon } ) } - } } label: { walletActionButtonView(title: action.title, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift index 7e83379a5..810ca54bd 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/Activating/MPCActivateWalletEnterView.swift @@ -30,8 +30,7 @@ struct MPCActivateWalletEnterView: View, ViewAnalyticsLogger { } .padding() .sheet(isPresented: $isPresentingForgotPasswordView) { - MPCForgotPasswordView() - .padding(.top, 32) + MPCForgotPasswordView(isModallyPresented: true) } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift index bc307b51d..e10026540 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Activate/ForgotPassword/MPCForgotPasswordView.swift @@ -10,6 +10,7 @@ import SwiftUI struct MPCForgotPasswordView: View, ViewAnalyticsLogger { var analyticsName: Analytics.ViewName { .mpcForgotPassword } + var isModallyPresented: Bool = false var body: some View { VStack(spacing: 32) { @@ -19,7 +20,7 @@ struct MPCForgotPasswordView: View, ViewAnalyticsLogger { openMailButton() } .padding(.horizontal, 16) - .padding(.top, safeAreaInset.top) + .padding(.top, safeAreaInset.top + (isModallyPresented ? 32 : 0)) .padding(.bottom, safeAreaInset.bottom) .background(Color.backgroundDefault) .trackAppearanceAnalytics(analyticsLogger: self) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift index 0672dec62..c3a3ac8f3 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/MPCWalletsService/MPCWalletSubServices/Fireblocks+UD/FB_UD_MPCConnectionService.swift @@ -381,9 +381,19 @@ extension FB_UD_MPC.MPCConnectionService: MPCWalletProviderSubServiceProtocol { let connectedWalletDetails = try getConnectedWalletDetailsFor(walletMetadata: walletMetadata) return try await performAuthErrorCatchingBlock(connectedWalletDetails: connectedWalletDetails) { token in - try await networkService.requestRecovery(token, password: password) - return connectedWalletDetails.email - } + do { + try await networkService.requestRecovery(token, password: password) + return connectedWalletDetails.email + + } catch { + /// Temporary solution until clarified with the BE. + if case NetworkLayerError.badResponseOrStatusCode(let code, _, _) = error, + code == 500 { + return connectedWalletDetails.email + } + throw error + } + } } private func convertMPCMessageTypeToFBUDEncoding(_ type: MPCMessage.MPCMessageType) -> FB_UD_MPC.SignMessageEncoding { diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift index 3e0ecc84e..3a6000964 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift @@ -17,6 +17,7 @@ struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { @State private var isLoading: Bool = false @State private var path: [String] = [] @State private var isPresentingForgotPasswordView: Bool = false + @State private var error: Error? var analyticsName: Analytics.ViewName { .mpcRequestRecovery } @@ -26,7 +27,6 @@ struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { VStack(spacing: 32) { headerView() passwordInputView() - Spacer() VStack(spacing: 16) { forgotPasswordButtonView() confirmButtonView() @@ -40,9 +40,9 @@ struct MPCRequestRecoveryView: View, ViewAnalyticsLogger { } } .sheet(isPresented: $isPresentingForgotPasswordView) { - MPCForgotPasswordView() - .padding(.top, 32) + MPCForgotPasswordView(isModallyPresented: true) } + .displayError($error) .navigationDestination(for: String.self, destination: { email in MPCRecoveryRequestedView(email: email, closeCallback: close) @@ -114,10 +114,13 @@ private extension MPCRequestRecoveryView { Task { isLoading = true - await Task.sleep(seconds: 1) - let email = try await mpcWalletsService.requestRecovery(password: passwordInput, - by: mpcWalletMetadata) - path.append(email) + do { + let email = try await mpcWalletsService.requestRecovery(password: passwordInput, + by: mpcWalletMetadata) + path.append(email) + } catch { + self.error = error + } isLoading = false } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/MPCWalletMetadataDisplayInfo.swift b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/MPCWalletMetadataDisplayInfo.swift new file mode 100644 index 000000000..7b8287344 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/MPCWalletMetadataDisplayInfo.swift @@ -0,0 +1,13 @@ +// +// MPCWalletMetadataDisplayInfo.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 8.10.2024. +// + +import Foundation + +struct MPCWalletMetadataDisplayInfo: Identifiable { + let id: UUID = UUID() + let walletMetadata: MPCWalletMetadata +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift index 132995a22..a761d13de 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/WalletDetails/WalletDetailsView.swift @@ -294,7 +294,13 @@ private extension WalletDetailsView { } func requestMPCWalletRecoveryKit() { + guard let mpcMetadata = wallet.udWallet.mpcMetadata else { + Debugger.printFailure("Failed to get MPC metadata in wallet details for wallet: \(wallet.udWallet.address)", + critical: true) + return + } + tabRouter.requestingRecoveryMPC = MPCWalletMetadataDisplayInfo(walletMetadata: mpcMetadata) } } From dded2ea26c09f11c053a95fd64738594f713849b Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 15:52:29 +0300 Subject: [PATCH 09/10] Updated copies --- .../Localization/en.lproj/Localizable.strings | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings index d4abe37a1..5b9ca56fb 100644 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings +++ b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Localization/en.lproj/Localizable.strings @@ -1205,14 +1205,15 @@ More tabs are coming in the next updates."; "MPC_WALLET_DEFAULT_NAME" = "Lite Wallet"; "SEND_CRYPTO" = "Send Crypto"; "MPC_FORGOT_PASSWORD_TITLE" = "Reset password using\nthe recovery link"; -"MPC_FORGOT_PASSWORD_SUBTITLE" = "Check your email for a message:\n'Welcome to Unstoppable Lite Wallet.' from\nno-reply@ud.me. It contains the recovery link we sent when you created your wallet. This is the only way to restore access if you forget your password."; -"MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" = "'Welcome to Unstoppable Lite Wallet.'\nno-reply@ud.me"; +"MPC_FORGOT_PASSWORD_SUBTITLE" = "Check your email for a message:\n'Welcome to Unstoppable Lite Wallet'. +It contains the recovery link we sent when you created your wallet. This is the only way to restore access if you forget your password."; +"MPC_FORGOT_PASSWORD_SUBTITLE_HIGHLIGHTS" = "'Welcome to Unstoppable Lite Wallet'"; "FORGOT_PASSWORD_TITLE" = "Forgot password?"; "MPC_REQUEST_RECOVERY_TITLE" = "Request recovery kit"; "MPC_REQUEST_RECOVERY_SUBTITLE" = "Receive an email with the recovery kit after confirming your current password."; "MPC_RECOVERY_REQUESTED_TITLE" = "Recovery kit sent"; -"MPC_RECOVERY_REQUESTED_SUBTITLE" = "Check your email for a message:\n'Unstoppable Lite Wallet Recovery Kit'\nfrom unstoppabledomains.com"; -"MPC_RECOVERY_REQUESTED_SUBTITLE_HIGHLIGHTS" = "'Unstoppable Lite Wallet Recovery Kit'\nunstoppabledomains.com"; +"MPC_RECOVERY_REQUESTED_SUBTITLE" = "Check your email for a message:\n'Unstoppable Lite Wallet Recovery Kit'\nfrom no-reply@unstoppabledomains.com"; +"MPC_RECOVERY_REQUESTED_SUBTITLE_HIGHLIGHTS" = "'Unstoppable Lite Wallet Recovery Kit'\nno-reply@unstoppabledomains.com"; "MPC_RECOVERY_REQUESTED_HINT_NOT_SHARE" = "Do not share your recovery kit\nwith anyone"; "MPC_RECOVERY_REQUESTED_HINT_PREVIOUS_INACTIVE" = "Previous recovery kits will become inactive when a new one is created"; From 9c3000a4d3cc8ab8d952c3f7f5781803d80876c4 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 8 Oct 2024 16:16:01 +0300 Subject: [PATCH 10/10] Handle email truncation --- .../MPC/Recovery/MPCRecoveryRequestedView.swift | 13 +++++-------- .../SwiftUI/CommonViews/UDListItemView.swift | 2 ++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift index ecb08e1b4..7eef26178 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift @@ -80,6 +80,7 @@ private extension MPCRecoveryRequestedView { @ViewBuilder func infoSection() -> some View { sectionWith(items: [.init(title: String.Constants.sentToN.localized(email), + titleLineLimit: 1, icon: .invite)]) } @@ -105,14 +106,9 @@ private extension MPCRecoveryRequestedView { @ViewBuilder func listItemWith(sectionItem: SectionItem) -> some View { - listItemWith(title: sectionItem.title, - icon: sectionItem.icon) - } - - @ViewBuilder - func listItemWith(title: String, icon: Image) -> some View { - UDListItemView(title: title, - imageType: .image(icon), + UDListItemView(title: sectionItem.title, + titleLineLimit: sectionItem.titleLineLimit, + imageType: .image(sectionItem.icon), imageStyle: .centred()) .padding(.vertical, 4) .udListItemInCollectionButtonPadding() @@ -160,6 +156,7 @@ private extension MPCRecoveryRequestedView { struct SectionItem: Identifiable { let id = UUID() let title: String + var titleLineLimit: Int? = nil let icon: Image } } diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/CommonViews/UDListItemView.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/CommonViews/UDListItemView.swift index 01e5fe402..1b564fe83 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/CommonViews/UDListItemView.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/CommonViews/UDListItemView.swift @@ -15,6 +15,7 @@ struct UDListItemView: View { let title: String var titleColor: Color = .foregroundDefault + var titleLineLimit: Int? = nil var subtitle: String? = nil var subtitleIcon: ImageType? = nil var subtitleStyle: SubtitleStyle = .default @@ -99,6 +100,7 @@ private extension UDListItemView { Text(title) .font(.currentFont(size: 16, weight: .medium)) .foregroundStyle(titleColor) + .lineLimit(titleLineLimit) .frame(minHeight: 24) }