diff --git a/Sources/BraveWallet/Crypto/Onboarding/BackupRecoveryPhraseView.swift b/Sources/BraveWallet/Crypto/Onboarding/BackupRecoveryPhraseView.swift index aaf3505e37b6..60789c757912 100644 --- a/Sources/BraveWallet/Crypto/Onboarding/BackupRecoveryPhraseView.swift +++ b/Sources/BraveWallet/Crypto/Onboarding/BackupRecoveryPhraseView.swift @@ -171,6 +171,10 @@ struct BackupRecoveryPhraseView: View { recoveryWords = words } } + .onDisappear { + isViewRecoveryPermitted = false + hasCopied = false + } } } diff --git a/Sources/BraveWallet/Crypto/Onboarding/RestoreWalletView.swift b/Sources/BraveWallet/Crypto/Onboarding/RestoreWalletView.swift index 961b1f8fc7cb..fbd12b2d6713 100644 --- a/Sources/BraveWallet/Crypto/Onboarding/RestoreWalletView.swift +++ b/Sources/BraveWallet/Crypto/Onboarding/RestoreWalletView.swift @@ -66,19 +66,37 @@ private struct RestoreWalletView: View { ) } - private func handleRecoveryWordsChanged(_ value: [String]) { - for word in value { - let phrases = word.split(separator: " ") - if phrases.count > 1 { - let currentLength = recoveryWords.count - var newPhrases = Array(repeating: "", count: currentLength) - for (index, pastedWord) in phrases.enumerated() { - newPhrases[index] = String(pastedWord) + private func handleRecoveryWordsChanged(oldValue: [String], newValue: [String]) { + let indexOnDifference = zip(oldValue, newValue).enumerated().first(where: { $1.0 != $1.1 }).map { $0.0 } + if let indexOnDifference, + let oldInput = oldValue[safe: indexOnDifference], + let newInput = newValue[safe: indexOnDifference] { // there is a new input on `indexOnDifference` + if abs(newInput.count - oldInput.count) > 1 { // we consider this is a copy and paste from `UIPasteboard` + let phrases = newInput.split(separator: " ") + if (!isLegacyWallet && phrases.count == 12) || (isLegacyWallet && phrases.count == 24) { // user copies and pastes the entire recovery phrases, we will auto-fill in all the recovery phrases + let currentLength = recoveryWords.count + var newPhrases = Array(repeating: "", count: currentLength) + for (index, pastedWord) in phrases.enumerated() { + newPhrases[index] = String(pastedWord) + } + recoveryWords = newPhrases + } else { // user copy and paste some phrases, we will auto-fill in from the `indexOfDifference` (where user pastes) to the last input field. This also means, if user passtes more phrases than number of input fields remaining, we won't exceed and will stop pasting at the last input field. + var startIndex = indexOnDifference + var recoveryWordsCopy = recoveryWords + for phrase in phrases { + if startIndex < recoveryWordsCopy.count { + recoveryWordsCopy[startIndex] = String(phrase) + startIndex += 1 + } else { + break + } + } + recoveryWords = recoveryWordsCopy } - recoveryWords = newPhrases - break + resignFirstResponder() } } + } var body: some View { @@ -128,6 +146,7 @@ private struct RestoreWalletView: View { // or legacy(24) to regular(12) resignFirstResponder() recoveryWords = .init(repeating: "", count: isLegacyWallet ? .regularWalletRecoveryPhraseNumber : .legacyWalletRecoveryPhraseNumber) + isShowingPhraseError = false } label: { Text(isLegacyWallet ? Strings.Wallet.restoreWalletImportFromRegularBraveWallet : Strings.Wallet.restoreWalletImportFromLegacyBraveWallet) .fontWeight(.medium) @@ -166,7 +185,9 @@ private struct RestoreWalletView: View { } } .padding() - .onChange(of: recoveryWords, perform: handleRecoveryWordsChanged) + .onChange(of: recoveryWords) { [recoveryWords] newValue in + handleRecoveryWordsChanged(oldValue: recoveryWords, newValue: newValue) + } .sheet(isPresented: $isShowingCreateNewPassword) { NavigationView { CreateWalletContainerView( diff --git a/Sources/BraveWallet/Crypto/Onboarding/VerifyRecoveryPhraseView.swift b/Sources/BraveWallet/Crypto/Onboarding/VerifyRecoveryPhraseView.swift index e42e648f3de8..a9b5e8c275dc 100644 --- a/Sources/BraveWallet/Crypto/Onboarding/VerifyRecoveryPhraseView.swift +++ b/Sources/BraveWallet/Crypto/Onboarding/VerifyRecoveryPhraseView.swift @@ -59,25 +59,24 @@ struct VerifyRecoveryPhraseView: View { .focused($isFieldFocused) Divider() } - if isShowingError { - HStack(spacing: 12) { - Image(braveSystemName: "leo.warning.circle-filled") - .renderingMode(.template) - .foregroundColor(Color(.braveLighterOrange)) - Text(Strings.Wallet.verifyRecoveryPhraseError) - .multilineTextAlignment(.leading) - .font(.callout) - Spacer() - } - .padding(12) - .background( - Color(.braveErrorBackground) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - ) + HStack(spacing: 12) { + Image(braveSystemName: "leo.warning.circle-filled") + .renderingMode(.template) + .foregroundColor(Color(.braveLighterOrange)) + Text(Strings.Wallet.verifyRecoveryPhraseError) + .multilineTextAlignment(.leading) + .font(.callout) + Spacer() } + .padding(12) + .background( + Color(.braveErrorBackground) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + ) + .hidden(isHidden: !isShowingError) Button { let targetIndex = targetedRecoveryWordIndexes[activeCheckIndex] - if input == recoveryWords[safe: targetIndex]?.value { + if input.trimmingCharacters(in: .whitespaces) == recoveryWords[safe: targetIndex]?.value { isShowingError = false if activeCheckIndex == targetedRecoveryWordIndexes.count - 1 { // finished all checks if keyringStore.isOnboardingVisible { @@ -100,6 +99,7 @@ struct VerifyRecoveryPhraseView: View { .frame(maxWidth: .infinity) } .buttonStyle(BraveFilledButtonStyle(size: .large)) + .disabled(input.isEmpty) .padding(.top, 86) if keyringStore.isOnboardingVisible { Button(action: { @@ -141,6 +141,11 @@ struct VerifyRecoveryPhraseView: View { }) ) .transparentNavigationBar(backButtonDisplayMode: .generic) + .onChange(of: input, perform: { newValue in + if newValue.isEmpty { + isShowingError = false + } + }) .onAppear { isFieldFocused = true }