[#1036] Wipe wallet in advanced settings

- Wipe wallet feature integrated into advanced settings
This commit is contained in:
Lukas Korba 2024-03-27 16:20:56 +01:00
parent ecc65f9a83
commit da3687b9d1
8 changed files with 236 additions and 6 deletions

View File

@ -19,6 +19,7 @@ let package = Package(
.library(name: "DatabaseFiles", targets: ["DatabaseFiles"]), .library(name: "DatabaseFiles", targets: ["DatabaseFiles"]),
.library(name: "Date", targets: ["Date"]), .library(name: "Date", targets: ["Date"]),
.library(name: "Deeplink", targets: ["Deeplink"]), .library(name: "Deeplink", targets: ["Deeplink"]),
.library(name: "DeleteWallet", targets: ["DeleteWallet"]),
.library(name: "DerivationTool", targets: ["DerivationTool"]), .library(name: "DerivationTool", targets: ["DerivationTool"]),
.library(name: "DiskSpaceChecker", targets: ["DiskSpaceChecker"]), .library(name: "DiskSpaceChecker", targets: ["DiskSpaceChecker"]),
.library(name: "ExportLogs", targets: ["ExportLogs"]), .library(name: "ExportLogs", targets: ["ExportLogs"]),
@ -172,6 +173,18 @@ let package = Package(
], ],
path: "Sources/Dependencies/Deeplink" path: "Sources/Dependencies/Deeplink"
), ),
.target(
name: "DeleteWallet",
dependencies: [
"Generated",
"SDKSynchronizer",
"UIComponents",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
],
path: "Sources/Features/DeleteWallet"
),
.target( .target(
name: "DerivationTool", name: "DerivationTool",
dependencies: [ dependencies: [
@ -504,6 +517,7 @@ let package = Package(
name: "Settings", name: "Settings",
dependencies: [ dependencies: [
"AppVersion", "AppVersion",
"DeleteWallet",
"Generated", "Generated",
"LocalAuthenticationHandler", "LocalAuthenticationHandler",
"Models", "Models",

View File

@ -0,0 +1,54 @@
//
// DeleteWalletStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 03-27-2024
//
import ComposableArchitecture
import Generated
import SDKSynchronizer
import Utils
import ZcashLightClientKit
@Reducer
public struct DeleteWallet {
@ObservableState
public struct State: Equatable {
public var isAcknowledged: Bool = false
public var isProcessing: Bool = false
public init(
isAcknowledged: Bool = false,
isProcessing: Bool = false
) {
self.isAcknowledged = isAcknowledged
self.isProcessing = isProcessing
}
}
public enum Action: BindableAction, Equatable {
case binding(BindingAction<DeleteWallet.State>)
case deleteTapped
}
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
public init() { }
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .none
case .deleteTapped:
state.isProcessing = true
return .none
}
}
}
}

View File

@ -0,0 +1,87 @@
//
// DeleteWalletView.swift
// secant-testnet
//
// Created by Lukáš Korba on 03-27-2024
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct DeleteWalletView: View {
@Perception.Bindable var store: StoreOf<DeleteWallet>
public init(store: StoreOf<DeleteWallet>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
ScrollView {
Group {
ZashiIcon()
Text(L10n.DeleteWallet.title)
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
.padding(.bottom, 15)
VStack(alignment: .leading) {
Text(L10n.DeleteWallet.message1)
.font(.custom(FontFamily.Inter.bold.name, size: 16))
Text(L10n.DeleteWallet.message2)
.font(.custom(FontFamily.Inter.medium.name, size: 16))
.padding(.top, 20)
}
HStack {
Toggle(isOn: $store.isAcknowledged, label: {
Text(L10n.DeleteWallet.iUnderstand)
.font(.custom(FontFamily.Inter.medium.name, size: 14))
})
.toggleStyle(CheckboxToggleStyle())
Spacer()
}
.padding(.top, 30)
Button(L10n.DeleteWallet.actionButtonTitle.uppercased()) {
store.send(.deleteTapped)
}
.zcashStyle()
.disabled(!store.isAcknowledged || store.isProcessing)
.padding(.vertical, 50)
}
.padding(.horizontal, 60)
}
.padding(.vertical, 1)
.zashiBack(store.isProcessing)
}
.navigationBarTitleDisplayMode(.inline)
.applyScreenBackground(withPattern: true)
}
}
// MARK: - Previews
#Preview {
DeleteWalletView(store: DeleteWallet.demo)
}
// MARK: - Store
extension DeleteWallet {
public static var demo = StoreOf<DeleteWallet>(
initialState: .initial
) {
DeleteWallet()
}
}
// MARK: - Placeholders
extension DeleteWallet.State {
public static let initial = DeleteWallet.State()
}

View File

@ -295,7 +295,7 @@ extension RootReducer {
state.alert = AlertState.wipeRequest() state.alert = AlertState.wipeRequest()
return .none return .none
case .initialization(.nukeWallet): case .initialization(.nukeWallet), .tabs(.settings(.advancedSettings(.deleteWallet(.deleteTapped)))):
guard let wipePublisher = sdkSynchronizer.wipe() else { guard let wipePublisher = sdkSynchronizer.wipe() else {
return Effect.send(.nukeWalletFailed) return Effect.send(.nukeWalletFailed)
} }

View File

@ -2,6 +2,7 @@ import SwiftUI
import ComposableArchitecture import ComposableArchitecture
import MessageUI import MessageUI
import DeleteWallet
import Generated import Generated
import LocalAuthenticationHandler import LocalAuthenticationHandler
import Models import Models
@ -18,10 +19,12 @@ public struct AdvancedSettingsReducer: Reducer {
public struct State: Equatable { public struct State: Equatable {
public enum Destination { public enum Destination {
case backupPhrase case backupPhrase
case deleteWallet
case privateDataConsent case privateDataConsent
case serverSetup case serverSetup
} }
public var deleteWallet: DeleteWallet.State
public var destination: Destination? public var destination: Destination?
public var isRestoringWallet = false public var isRestoringWallet = false
public var phraseDisplayState: RecoveryPhraseDisplay.State public var phraseDisplayState: RecoveryPhraseDisplay.State
@ -29,12 +32,14 @@ public struct AdvancedSettingsReducer: Reducer {
public var serverSetupState: ServerSetup.State public var serverSetupState: ServerSetup.State
public init( public init(
deleteWallet: DeleteWallet.State,
destination: Destination? = nil, destination: Destination? = nil,
isRestoringWallet: Bool = false, isRestoringWallet: Bool = false,
phraseDisplayState: RecoveryPhraseDisplay.State, phraseDisplayState: RecoveryPhraseDisplay.State,
privateDataConsentState: PrivateDataConsentReducer.State, privateDataConsentState: PrivateDataConsentReducer.State,
serverSetupState: ServerSetup.State serverSetupState: ServerSetup.State
) { ) {
self.deleteWallet = deleteWallet
self.destination = destination self.destination = destination
self.isRestoringWallet = isRestoringWallet self.isRestoringWallet = isRestoringWallet
self.phraseDisplayState = phraseDisplayState self.phraseDisplayState = phraseDisplayState
@ -45,6 +50,7 @@ public struct AdvancedSettingsReducer: Reducer {
public enum Action: Equatable { public enum Action: Equatable {
case backupWalletAccessRequest case backupWalletAccessRequest
case deleteWallet(DeleteWallet.Action)
case phraseDisplay(RecoveryPhraseDisplay.Action) case phraseDisplay(RecoveryPhraseDisplay.Action)
case privateDataConsent(PrivateDataConsentReducer.Action) case privateDataConsent(PrivateDataConsentReducer.Action)
case restoreWalletTask case restoreWalletTask
@ -67,7 +73,10 @@ public struct AdvancedSettingsReducer: Reducer {
await send(.updateDestination(.backupPhrase)) await send(.updateDestination(.backupPhrase))
} }
} }
case .deleteWallet:
return .none
case .phraseDisplay(.finishedPressed): case .phraseDisplay(.finishedPressed):
state.destination = nil state.destination = nil
return .none return .none
@ -122,6 +131,10 @@ public struct AdvancedSettingsReducer: Reducer {
Scope(state: \.serverSetupState, action: /Action.serverSetup) { Scope(state: \.serverSetupState, action: /Action.serverSetup) {
ServerSetup() ServerSetup()
} }
Scope(state: \.deleteWallet, action: /Action.deleteWallet) {
DeleteWallet()
}
} }
} }
@ -134,14 +147,14 @@ extension AdvancedSettingsViewStore {
send: AdvancedSettingsReducer.Action.updateDestination send: AdvancedSettingsReducer.Action.updateDestination
) )
} }
var bindingForBackupPhrase: Binding<Bool> { var bindingForBackupPhrase: Binding<Bool> {
self.destinationBinding.map( self.destinationBinding.map(
extract: { $0 == .backupPhrase }, extract: { $0 == .backupPhrase },
embed: { $0 ? .backupPhrase : nil } embed: { $0 ? .backupPhrase : nil }
) )
} }
var bindingForPrivateDataConsent: Binding<Bool> { var bindingForPrivateDataConsent: Binding<Bool> {
self.destinationBinding.map( self.destinationBinding.map(
extract: { $0 == .privateDataConsent }, extract: { $0 == .privateDataConsent },
@ -155,6 +168,13 @@ extension AdvancedSettingsViewStore {
embed: { $0 ? .serverSetup : nil } embed: { $0 ? .serverSetup : nil }
) )
} }
var bindingDeleteWallet: Binding<Bool> {
self.destinationBinding.map(
extract: { $0 == .deleteWallet },
embed: { $0 ? .deleteWallet : nil }
)
}
} }
// MARK: - Store // MARK: - Store
@ -180,12 +200,20 @@ extension AdvancedSettingsStore {
action: AdvancedSettingsReducer.Action.serverSetup action: AdvancedSettingsReducer.Action.serverSetup
) )
} }
func deleteWalletStore() -> StoreOf<DeleteWallet> {
self.scope(
state: \.deleteWallet,
action: AdvancedSettingsReducer.Action.deleteWallet
)
}
} }
// MARK: Placeholders // MARK: Placeholders
extension AdvancedSettingsReducer.State { extension AdvancedSettingsReducer.State {
public static let initial = AdvancedSettingsReducer.State( public static let initial = AdvancedSettingsReducer.State(
deleteWallet: .initial,
phraseDisplayState: RecoveryPhraseDisplay.State( phraseDisplayState: RecoveryPhraseDisplay.State(
phrase: nil, phrase: nil,
showBackButton: false, showBackButton: false,
@ -205,6 +233,7 @@ extension AdvancedSettingsStore {
public static let demo = AdvancedSettingsStore( public static let demo = AdvancedSettingsStore(
initialState: .init( initialState: .init(
deleteWallet: .initial,
phraseDisplayState: RecoveryPhraseDisplay.State( phraseDisplayState: RecoveryPhraseDisplay.State(
phrase: nil, phrase: nil,
birthday: nil birthday: nil

View File

@ -7,6 +7,8 @@
import SwiftUI import SwiftUI
import ComposableArchitecture import ComposableArchitecture
import DeleteWallet
import Generated import Generated
import RecoveryPhraseDisplay import RecoveryPhraseDisplay
import UIComponents import UIComponents
@ -49,26 +51,45 @@ public struct AdvancedSettingsView: View {
ServerSetupView(store: store.serverSetupStore()) ServerSetupView(store: store.serverSetupStore())
} }
) )
.navigationLinkEmpty(
isActive: viewStore.bindingDeleteWallet,
destination: {
DeleteWalletView(store: store.deleteWalletStore())
}
)
.onAppear { .onAppear {
isRestoringWalletBadgeOn = viewStore.isRestoringWallet isRestoringWalletBadgeOn = viewStore.isRestoringWallet
} }
.onChange(of: viewStore.isRestoringWallet) { isRestoringWalletBadgeOn = $0 } .onChange(of: viewStore.isRestoringWallet) { isRestoringWalletBadgeOn = $0 }
.padding(.horizontal, 70)
Button(L10n.Settings.exportPrivateData.uppercased()) { Button(L10n.Settings.exportPrivateData.uppercased()) {
viewStore.send(.updateDestination(.privateDataConsent)) viewStore.send(.updateDestination(.privateDataConsent))
} }
.zcashStyle() .zcashStyle()
.padding(.bottom, 25) .padding(.bottom, 25)
.padding(.horizontal, 70)
Button(L10n.Settings.chooseServer.uppercased()) { Button(L10n.Settings.chooseServer.uppercased()) {
viewStore.send(.updateDestination(.serverSetup)) viewStore.send(.updateDestination(.serverSetup))
} }
.zcashStyle() .zcashStyle()
.padding(.bottom, 80) .padding(.horizontal, 70)
Spacer() Spacer()
Button(L10n.Settings.deleteZashi.uppercased()) {
viewStore.send(.updateDestination(.deleteWallet))
}
.zcashStyle()
.padding(.bottom, 20)
.padding(.horizontal, 70)
Text(L10n.Settings.deleteZashiWarning)
.font(.custom(FontFamily.Inter.medium.name, size: 11))
.padding(.bottom, 50)
.padding(.horizontal, 20)
} }
.padding(.horizontal, 70)
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.applyScreenBackground() .applyScreenBackground()

View File

@ -84,6 +84,18 @@ public enum L10n {
public static let message = L10n.tr("Localizable", "balances.hintBox.message", fallback: "Zashi uses the latest network upgrade and does not support sending transparent (unshielded) ZEC. Use the Shield and Consolidate button to shield your funds, which will add to your available balance and make your ZEC spendable.") public static let message = L10n.tr("Localizable", "balances.hintBox.message", fallback: "Zashi uses the latest network upgrade and does not support sending transparent (unshielded) ZEC. Use the Shield and Consolidate button to shield your funds, which will add to your available balance and make your ZEC spendable.")
} }
} }
public enum DeleteWallet {
/// Delete Zashi
public static let actionButtonTitle = L10n.tr("Localizable", "deleteWallet.actionButtonTitle", fallback: "Delete Zashi")
/// I understand
public static let iUnderstand = L10n.tr("Localizable", "deleteWallet.iUnderstand", fallback: "I understand")
/// Please don't delete this app unless you're sure you understand the effects.
public static let message1 = L10n.tr("Localizable", "deleteWallet.message1", fallback: "Please don't delete this app unless you're sure you understand the effects.")
/// Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.
public static let message2 = L10n.tr("Localizable", "deleteWallet.message2", fallback: "Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.")
/// Delete Zashi
public static let title = L10n.tr("Localizable", "deleteWallet.title", fallback: "Delete Zashi")
}
public enum Error { public enum Error {
/// possible roll back /// possible roll back
public static let rollBack = L10n.tr("Localizable", "error.rollBack", fallback: "possible roll back") public static let rollBack = L10n.tr("Localizable", "error.rollBack", fallback: "possible roll back")
@ -609,6 +621,10 @@ public enum L10n {
public static let advanced = L10n.tr("Localizable", "settings.advanced", fallback: "Advanced") public static let advanced = L10n.tr("Localizable", "settings.advanced", fallback: "Advanced")
/// Choose a server /// Choose a server
public static let chooseServer = L10n.tr("Localizable", "settings.chooseServer", fallback: "Choose a server") public static let chooseServer = L10n.tr("Localizable", "settings.chooseServer", fallback: "Choose a server")
/// Delete Zashi
public static let deleteZashi = L10n.tr("Localizable", "settings.deleteZashi", fallback: "Delete Zashi")
/// (You will be asked to confirm on next screen)
public static let deleteZashiWarning = L10n.tr("Localizable", "settings.deleteZashiWarning", fallback: "(You will be asked to confirm on next screen)")
/// Export logs only /// Export logs only
public static let exportLogsOnly = L10n.tr("Localizable", "settings.exportLogsOnly", fallback: "Export logs only") public static let exportLogsOnly = L10n.tr("Localizable", "settings.exportLogsOnly", fallback: "Export logs only")
/// Export private data /// Export private data

View File

@ -169,6 +169,13 @@
"send.alert.failure.title" = "Failed to send funds"; "send.alert.failure.title" = "Failed to send funds";
"send.alert.failure.message" = "Error: %@"; "send.alert.failure.message" = "Error: %@";
// MARK: - Delete Wallet
"deleteWallet.title" = "Delete Zashi";
"deleteWallet.message1" = "Please don't delete this app unless you're sure you understand the effects.";
"deleteWallet.message2" = "Deleting the Zashi app will delete the database and cached data. Any funds you have in this wallet will be lost and can only be recovered by using your Zashi secret recovery phrase in Zashi or another Zcash wallet.";
"deleteWallet.iUnderstand" = "I understand";
"deleteWallet.actionButtonTitle" = "Delete Zashi";
// MARK: - Settings // MARK: - Settings
"settings.feedback" = "Send us feedback"; "settings.feedback" = "Send us feedback";
"settings.advanced" = "Advanced"; "settings.advanced" = "Advanced";
@ -177,6 +184,8 @@
"settings.exportPrivateData" = "Export private data"; "settings.exportPrivateData" = "Export private data";
"settings.chooseServer" = "Choose a server"; "settings.chooseServer" = "Choose a server";
"settings.exportLogsOnly" = "Export logs only"; "settings.exportLogsOnly" = "Export logs only";
"settings.deleteZashi" = "Delete Zashi";
"settings.deleteZashiWarning" = "(You will be asked to confirm on next screen)";
"settings.about.info" = "Send and receive ZEC on Zashi! "settings.about.info" = "Send and receive ZEC on Zashi!
Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly."; Zashi is a minimal-design, self-custody, ZEC-only shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in user-feedback mechanism to enable more features, more quickly.";
"settings.version" = "Version %@ (%@)"; "settings.version" = "Version %@ (%@)";