secant-ios-wallet/secant/Features/Settings/SettingsStore.swift

159 lines
4.5 KiB
Swift

import ComposableArchitecture
import SwiftUI
typealias SettingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment>
typealias SettingsStore = Store<SettingsState, SettingsAction>
typealias SettingsViewStore = ViewStore<SettingsState, SettingsAction>
// MARK: - State
struct SettingsState: Equatable {
enum Route {
case backupPhrase
}
var phraseDisplayState: RecoveryPhraseDisplayState
var rescanDialog: ConfirmationDialogState<SettingsAction>?
var route: Route?
}
// MARK: - Action
enum SettingsAction: Equatable {
case backupWallet
case backupWalletAccessRequest
case cancelRescan
case fullRescan
case phraseDisplay(RecoveryPhraseDisplayAction)
case quickRescan
case rescanBlockchain
case updateRoute(SettingsState.Route?)
}
// MARK: - Environment
struct SettingsEnvironment {
let localAuthenticationHandler: LocalAuthenticationHandler
let mnemonic: WrappedMnemonic
let SDKSynchronizer: WrappedSDKSynchronizer
let userPreferencesStorage: UserPreferencesStorage
let walletStorage: WrappedWalletStorage
}
// MARK: - Reducer
extension SettingsReducer {
static let `default` = SettingsReducer.combine(
[
settingsReducer,
backupPhraseReducer
]
)
private static let settingsReducer = SettingsReducer { state, action, environment in
switch action {
case .backupWalletAccessRequest:
return .run { send in
if await environment.localAuthenticationHandler.authenticate() {
await send(.backupWallet)
}
}
case .backupWallet:
do {
let storedWallet = try environment.walletStorage.exportWallet()
let phraseWords = try environment.mnemonic.asWords(storedWallet.seedPhrase)
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
return Effect(value: .updateRoute(.backupPhrase))
} catch {
// TODO [#201]: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
return .none
}
case .cancelRescan, .quickRescan, .fullRescan:
state.rescanDialog = nil
return .none
case .rescanBlockchain:
state.rescanDialog = .init(
title: TextState("Rescan"),
message: TextState("Select the rescan you want"),
buttons: [
.default(TextState("Quick rescan"), action: .send(.quickRescan)),
.default(TextState("Full rescan"), action: .send(.fullRescan)),
.cancel(TextState("Cancel"))
]
)
return .none
case .phraseDisplay:
state.route = nil
return .none
case .updateRoute(let route):
state.route = route
return .none
}
}
private static let backupPhraseReducer: SettingsReducer = RecoveryPhraseDisplayReducer.default.pullback(
state: \SettingsState.phraseDisplayState,
action: /SettingsAction.phraseDisplay,
environment: { _ in .demo }
)
}
// MARK: - ViewStore
extension SettingsViewStore {
var routeBinding: Binding<SettingsState.Route?> {
self.binding(
get: \.route,
send: SettingsAction.updateRoute
)
}
var bindingForBackupPhrase: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .backupPhrase },
embed: { $0 ? .backupPhrase : nil }
)
}
}
// MARK: - Store
extension SettingsStore {
func backupPhraseStore() -> RecoveryPhraseDisplayStore {
self.scope(
state: \.phraseDisplayState,
action: SettingsAction.phraseDisplay
)
}
}
// MARK: Placeholders
extension SettingsState {
static let placeholder = SettingsState(
phraseDisplayState: RecoveryPhraseDisplayState(
phrase: .placeholder
)
)
}
extension SettingsStore {
static let placeholder = SettingsStore(
initialState: .placeholder,
reducer: .default,
environment: SettingsEnvironment(
localAuthenticationHandler: .live,
mnemonic: .live,
SDKSynchronizer: MockWrappedSDKSynchronizer(),
userPreferencesStorage: .live,
walletStorage: .live()
)
)
}