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) { - - } } 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..a23e2d069 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 { @@ -136,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 f9e1852e4..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 */; }; @@ -1477,6 +1479,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 */; }; @@ -3680,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 = ""; }; @@ -3733,6 +3740,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 = ""; }; @@ -4668,6 +4677,7 @@ C6A231FB2BEB494D0037E093 /* WalletDetailsDomainItemView.swift */, C6A231FE2BEB52CB0037E093 /* WalletSourceImageView.swift */, C6A232012BEB53310037E093 /* RenameWalletView.swift */, + C6918C682CB54EFD00036C63 /* MPCWalletMetadataDisplayInfo.swift */, ); path = WalletDetails; sourceTree = ""; @@ -7190,6 +7200,15 @@ path = "domains-manager-ios-preview"; sourceTree = ""; }; + C69CA5CB2CB5140200C30DF7 /* Recovery */ = { + isa = PBXGroup; + children = ( + C69CA5CF2CB5210D00C30DF7 /* MPCRequestRecoveryView.swift */, + C69CA5D22CB5211C00C30DF7 /* MPCRecoveryRequestedView.swift */, + ); + path = Recovery; + sourceTree = ""; + }; C69F990E2A9F1264004B1958 /* CommonViews */ = { isa = PBXGroup; children = ( @@ -7795,6 +7814,7 @@ C6BF801B2BA2B53F00CC4CB3 /* MPC */ = { isa = PBXGroup; children = ( + C69CA5CB2CB5140200C30DF7 /* Recovery */, C640F3712C082C2E009EB0F9 /* Activate */, C6952A5B2BC6648900F4B475 /* PurchaseMPCFlow */, C6952A322BC4EE2D00F4B475 /* MPCWalletsService */, @@ -9060,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 */, @@ -9286,6 +9307,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 */, @@ -9519,6 +9541,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 */, @@ -10380,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 */, @@ -11103,6 +11127,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 */, @@ -11309,6 +11334,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 d5af45d81..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 @@ -1312,6 +1312,15 @@ 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" + 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/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 a03def3fa..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 @@ -17,19 +17,21 @@ 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(isModallyPresented: true) + } } } @@ -116,6 +118,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 601cf27a3..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) @@ -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..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) @@ -28,13 +29,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/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..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 @@ -376,6 +376,26 @@ 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 + 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 { 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/MPCRecoveryRequestedView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift new file mode 100644 index 000000000..7eef26178 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRecoveryRequestedView.swift @@ -0,0 +1,168 @@ +// +// MPCRecoveryRequestedView.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 8.10.2024. +// + +import SwiftUI + +struct MPCRecoveryRequestedView: View, ViewAnalyticsLogger { + + @Environment(\.dismiss) var dismiss + + let email: String + var closeCallback: EmptyCallback? = nil + + var analyticsName: Analytics.ViewName { .mpcRecoveryRequested } + + var body: some View { + 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), + titleLineLimit: 1, + 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 { + UDListItemView(title: sectionItem.title, + titleLineLimit: sectionItem.titleLineLimit, + imageType: .image(sectionItem.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 + var titleLineLimit: Int? = nil + let icon: Image + } +} + +#Preview { + NavigationStack { + 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 new file mode 100644 index 000000000..3a6000964 --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/MPC/Recovery/MPCRequestRecoveryView.swift @@ -0,0 +1,136 @@ +// +// MPCRequestRecoveryView.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 8.10.2024. +// + +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] = [] + @State private var isPresentingForgotPasswordView: Bool = false + @State private var error: Error? + + var analyticsName: Analytics.ViewName { .mpcRequestRecovery } + + var body: some View { + NavigationStack(path: $path) { + ScrollView { + VStack(spacing: 32) { + headerView() + passwordInputView() + VStack(spacing: 16) { + forgotPasswordButtonView() + confirmButtonView() + } + } + .padding(.horizontal, 16) + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + CloseButtonView(closeCallback: closeButtonPressed) + } + } + .sheet(isPresented: $isPresentingForgotPasswordView) { + MPCForgotPasswordView(isModallyPresented: true) + } + .displayError($error) + .navigationDestination(for: String.self, destination: { email in + MPCRecoveryRequestedView(email: email, + closeCallback: close) + }) + } + } + +} + +// 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) + close() + } + + func forgotPasswordButtonPressed() { + logButtonPressedAnalyticEvents(button: .forgotPassword) + isPresentingForgotPasswordView = true + } + + func confirmButtonPressed() { + logButtonPressedAnalyticEvents(button: .confirm) + + Task { + isLoading = true + do { + let email = try await mpcWalletsService.requestRecovery(password: passwordInput, + by: mpcWalletMetadata) + path.append(email) + } catch { + self.error = error + } + isLoading = false + } + } + + func close() { + dismiss() + } +} + +#Preview { + MPCRequestRecoveryView(mpcWalletMetadata: MPCWalletMetadata(provider: MPCWalletProvider.fireblocksUD, + metadata: nil)) +} 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/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..a761d13de 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,15 @@ 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) + } } // MARK: - Domains list @@ -381,6 +395,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..9b4085fe8 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, mpcRecoveryRequested } } @@ -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/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/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/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/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 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - + + - - + + - - - - - - - + + + + + + + - + - - + + - + - - + + - + - + - + - + - - + + - + - - + + - + - + - + - + - - + + - - + + - - + + 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..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 @@ -1204,8 +1204,18 @@ 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'. +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 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"; // 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/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) } 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..ff9365896 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,9 @@ extension Image { static let layoutGridTwo = Image("layoutGridTwo") 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")