Skip to content

Commit

Permalink
Fix brave/brave-ios#8345, brave/brave-ios#8425: Add 'Creating Wallet.…
Browse files Browse the repository at this point in the history
…..' step to onboarding (brave/brave-ios#8509)

* Check for wallet existing before creating Wallet to prevent possible race condition with multi-window.

* Add 'Creating Wallet...' step to onboarding for when asynchronous create wallet / restore wallet functions are setting up the wallet and fetching wallet data files.

* Fix Wallet re-opened before create/restore wallet callback returns.

* Use old api for `ToolbarItemPlacement` as CI is using older Xcode version.

* Address PR comments; Fix backup banner showing until network call to fetch wallet data returns after wallet restore. Banner would only be visible if wallet is dismissed and re-opened prior to callback. Fix wallet dismiss on wallet lock.

* Address PR comment; Fix `CreatingWalletView` not covering fullscreen by overlaying on ScrollView instead of only on contents.
  • Loading branch information
StephenHeaps authored Dec 7, 2023
1 parent 86b49a0 commit 43a5c6e
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 153 deletions.
4 changes: 2 additions & 2 deletions Sources/BraveWallet/Crypto/CryptoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public struct CryptoView: View {
}
case .unlock:
UIKitNavigationView {
UnlockWalletView(keyringStore: keyringStore)
UnlockWalletView(keyringStore: keyringStore, dismissAction: dismissAction)
.toolbar {
dismissButtonToolbarContents
}
Expand All @@ -252,7 +252,7 @@ public struct CryptoView: View {
.zIndex(2) // Needed or the dismiss animation messes up
} else {
UIKitNavigationView {
SetupCryptoView(keyringStore: keyringStore)
SetupCryptoView(keyringStore: keyringStore, dismissAction: dismissAction)
.toolbar {
ToolbarItemGroup(placement: .destructiveAction) {
Button(action: {
Expand Down
226 changes: 131 additions & 95 deletions Sources/BraveWallet/Crypto/Onboarding/CreateWalletView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,11 @@

import Foundation
import SwiftUI
import BraveUI
import DesignSystem
import Strings
import struct Shared.AppConstants

struct RestorePackage {
let recoveryWords: [String]
let onRestoreCompleted: (_ status: Bool, _ validPassword: String) -> Void
var isLegacyWallet: Bool {
recoveryWords.count == .legacyWalletRecoveryPhraseNumber
}
}

struct CreateWalletContainerView: View {
@ObservedObject var keyringStore: KeyringStore
var restorePackage: RestorePackage?

init(keyringStore: KeyringStore, restorePackage: RestorePackage? = nil) {
self.keyringStore = keyringStore
self.restorePackage = restorePackage
}

var body: some View {
ScrollView(.vertical) {
CreateWalletView(
keyringStore: keyringStore,
restorePackage: restorePackage
)
.background(Color(.braveBackground))
}
.background(Color(.braveBackground).edgesIgnoringSafeArea(.all))
.transparentNavigationBar(backButtonTitle: Strings.Wallet.createWalletBackButtonTitle, backButtonDisplayMode: .generic)
}
}

private enum ValidationError: LocalizedError, Equatable {
case requirementsNotMet
case inputsDontMatch
Expand All @@ -53,35 +24,52 @@ private enum ValidationError: LocalizedError, Equatable {
}
}

private struct CreateWalletView: View {
struct CreateWalletView: View {
@ObservedObject var keyringStore: KeyringStore
var restorePackage: RestorePackage?
let setupOption: OnboardingSetupOption
let onValidPasswordEntered: ((_ validPassword: String) -> Void)?
// Used to dismiss all of Wallet
let dismissAction: () -> Void

@State private var password: String = ""
@State private var repeatedPassword: String = ""
@State private var validationError: ValidationError?
@State private var isNewWalletCreated: Bool = false
@State private var passwordStatus: PasswordStatus = .none
@State private var isInputsMatch: Bool = false
/// If this view is showing `Creating Wallet...` overlay, blocking input fields.
/// Using a local flag for the view instead of `keyringStore.isCreatingWallet` so we
/// only show `CreatingWalletView` on the `RestoreWalletView` when restoring.
@State private var isShowingCreatingWallet: Bool = false

@FocusState private var isFieldFocused: Bool

init(
keyringStore: KeyringStore,
setupOption: OnboardingSetupOption,
onValidPasswordEntered: ((_ validPassword: String) -> Void)? = nil,
dismissAction: @escaping () -> Void
) {
self.keyringStore = keyringStore
self.setupOption = setupOption
self.onValidPasswordEntered = onValidPasswordEntered
self.dismissAction = dismissAction
}

private func createWallet() {
if let restorePackage {
// restore wallet with recovery phrases and a new password
keyringStore.restoreWallet(
words: restorePackage.recoveryWords,
password: password,
isLegacyBraveWallet: restorePackage.isLegacyWallet
) { success in
restorePackage.onRestoreCompleted(success, password)
}
} else {
switch setupOption {
case .new:
isShowingCreatingWallet = true
keyringStore.createWallet(password: password) { mnemonic in
defer { self.isShowingCreatingWallet = false }
if let mnemonic, !mnemonic.isEmpty {
isNewWalletCreated = true
}
}
case .restore:
if isInputsMatch {
onValidPasswordEntered?(password)
}
}
}

Expand Down Expand Up @@ -127,7 +115,7 @@ private struct CreateWalletView: View {
}
}

func errorLabel(_ error: ValidationError?) -> some View {
private func errorLabel(_ error: ValidationError?) -> some View {
HStack(spacing: 12) {
Image(braveSystemName: "leo.warning.circle-filled")
.renderingMode(.template)
Expand All @@ -151,65 +139,67 @@ private struct CreateWalletView: View {
}

var body: some View {
VStack(spacing: 16) {
VStack {
Text(Strings.Wallet.createWalletTitle)
.font(.title)
.padding(.bottom)
.multilineTextAlignment(.center)
.foregroundColor(Color(uiColor: WalletV2Design.textPrimary))
Text(Strings.Wallet.createWalletSubTitle)
.font(.subheadline)
.padding(.bottom)
.multilineTextAlignment(.center)
.foregroundColor(Color(uiColor: WalletV2Design.textSecondary))
}
VStack(alignment: .leading, spacing: 20) {
VStack(spacing: 30) {
VStack(alignment: .leading, spacing: 10) {
Text(Strings.Wallet.newPasswordPlaceholder)
.foregroundColor(Color(uiColor: WalletV2Design.textPrimary))
HStack(spacing: 8) {
SecureField(Strings.Wallet.newPasswordPlaceholder, text: $password)
.textContentType(.newPassword)
.focused($isFieldFocused)
Spacer()
if passwordStatus != .none {
passwordStatusView(passwordStatus)
ScrollView(.vertical) {
VStack(spacing: 16) {
VStack {
Text(Strings.Wallet.createWalletTitle)
.font(.title)
.padding(.bottom)
.multilineTextAlignment(.center)
.foregroundColor(Color(uiColor: WalletV2Design.textPrimary))
Text(Strings.Wallet.createWalletSubTitle)
.font(.subheadline)
.padding(.bottom)
.multilineTextAlignment(.center)
.foregroundColor(Color(uiColor: WalletV2Design.textSecondary))
}
VStack(alignment: .leading, spacing: 20) {
VStack(spacing: 30) {
VStack(alignment: .leading, spacing: 10) {
Text(Strings.Wallet.newPasswordPlaceholder)
.foregroundColor(Color(uiColor: WalletV2Design.textPrimary))
HStack(spacing: 8) {
SecureField(Strings.Wallet.newPasswordPlaceholder, text: $password)
.textContentType(.newPassword)
.focused($isFieldFocused)
Spacer()
if passwordStatus != .none {
passwordStatusView(passwordStatus)
}
}
Divider()
}
Divider()
}
VStack(alignment: .leading, spacing: 12) {
Text(Strings.Wallet.repeatedPasswordPlaceholder)
.foregroundColor(.primary)
HStack(spacing: 8) {
SecureField(Strings.Wallet.repeatedPasswordPlaceholder, text: $repeatedPassword, onCommit: createWallet)
.textContentType(.newPassword)
Spacer()
if isInputsMatch {
Text("\(Image(braveSystemName: "leo.check.normal")) \(Strings.Wallet.repeatedPasswordMatch)")
.multilineTextAlignment(.trailing)
.font(.footnote)
.foregroundColor(.secondary)
VStack(alignment: .leading, spacing: 12) {
Text(Strings.Wallet.repeatedPasswordPlaceholder)
.foregroundColor(.primary)
HStack(spacing: 8) {
SecureField(Strings.Wallet.repeatedPasswordPlaceholder, text: $repeatedPassword, onCommit: createWallet)
.textContentType(.newPassword)
Spacer()
if isInputsMatch {
Text("\(Image(braveSystemName: "leo.check.normal")) \(Strings.Wallet.repeatedPasswordMatch)")
.multilineTextAlignment(.trailing)
.font(.footnote)
.foregroundColor(.secondary)
}
}
Divider()
}
Divider()
}
.font(.subheadline)
errorLabel(validationError)
}
.font(.subheadline)
errorLabel(validationError)
}
Button(action: createWallet) {
Text(Strings.Wallet.continueButtonTitle)
.frame(maxWidth: .infinity)
Button(action: createWallet) {
Text(Strings.Wallet.continueButtonTitle)
.frame(maxWidth: .infinity)
}
.buttonStyle(BraveFilledButtonStyle(size: .large))
.disabled(isContinueDisabled)
.padding(.top, 60)
}
.buttonStyle(BraveFilledButtonStyle(size: .large))
.disabled(isContinueDisabled)
.padding(.top, 60)
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
.background(Color(.braveBackground).edgesIgnoringSafeArea(.all))
.background(
NavigationLink(
Expand All @@ -224,20 +214,66 @@ private struct CreateWalletView: View {
)
.onChange(of: password, perform: handleInputChange)
.onChange(of: repeatedPassword, perform: handleInputChange)
.navigationBarBackButtonHidden(isShowingCreatingWallet)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
if isShowingCreatingWallet {
CreatingWalletView()
.ignoresSafeArea()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
if isShowingCreatingWallet {
Button(action: dismissAction) { // dismiss all of wallet
Image("wallet-dismiss", bundle: .module)
.renderingMode(.template)
.foregroundColor(Color(.braveBlurpleTint))
}
}
}
})
.onAppear {
isFieldFocused = true
}
.transparentNavigationBar(
backButtonTitle: Strings.Wallet.createWalletBackButtonTitle,
backButtonDisplayMode: .generic
)
}
}

#if DEBUG
struct CreateWalletView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
CreateWalletContainerView(keyringStore: .previewStore)
CreateWalletView(
keyringStore: .previewStore,
setupOption: .new,
dismissAction: {}
)
}
.previewLayout(.sizeThatFits)
.previewColorSchemes()
}
}
#endif

/// View shown as an overlay over `CreateWalletView` or `RestoreWalletView`
/// when waiting for Wallet to be created & wallet data files downloaded.
struct CreatingWalletView: View {

var body: some View {
VStack(spacing: 24) {
Spacer()
ProgressView()
.progressViewStyle(.braveCircular(size: .normal, tint: .braveBlurpleTint))
Text(Strings.Wallet.creatingWallet)
.font(.title)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(braveSystemName: .containerBackground))
}
}
26 changes: 17 additions & 9 deletions Sources/BraveWallet/Crypto/Onboarding/LegalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ import DesignSystem

struct LegalView: View {
@ObservedObject var keyringStore: KeyringStore
var setupOption: SetupOption
let setupOption: OnboardingSetupOption
// Used to dismiss all of Wallet
let dismissAction: () -> Void

@State private var isResponsibilityCheckboxChecked: Bool = false
@State private var isTermsCheckboxChecked: Bool = false
@State private var isShowingCreateNewWallet: Bool = false
@State private var isShowingRestoreExistedWallet: Bool = false

enum SetupOption {
case new
case restore
}

private var isContinueDisabled: Bool {
!isResponsibilityCheckboxChecked || !isTermsCheckboxChecked
}
Expand Down Expand Up @@ -79,7 +76,11 @@ struct LegalView: View {
.padding()
.background(
NavigationLink(
destination: CreateWalletContainerView(keyringStore: keyringStore),
destination: CreateWalletView(
keyringStore: keyringStore,
setupOption: setupOption,
dismissAction: dismissAction
),
isActive: $isShowingCreateNewWallet,
label: {
EmptyView()
Expand All @@ -88,7 +89,10 @@ struct LegalView: View {
)
.background(
NavigationLink(
destination: RestoreWalletContainerView(keyringStore: keyringStore),
destination: RestoreWalletView(
keyringStore: keyringStore,
dismissAction: dismissAction
),
isActive: $isShowingRestoreExistedWallet,
label: {
EmptyView()
Expand Down Expand Up @@ -118,7 +122,11 @@ struct LegalCheckbox: View {
#if DEBUG
struct LegalView_Previews: PreviewProvider {
static var previews: some View {
LegalView(keyringStore: .previewStore, setupOption: .new)
LegalView(
keyringStore: .previewStore,
setupOption: .new,
dismissAction: {}
)
}
}
#endif
Loading

0 comments on commit 43a5c6e

Please sign in to comment.