secant-ios-wallet/modules/Sources/Features/Root/RootDestination.swift

169 lines
6.2 KiB
Swift

//
// RootDestination.swift
// secant-testnet
//
// Created by Lukáš Korba on 01.12.2022.
//
import Foundation
import ComposableArchitecture
import ZcashLightClientKit
import Deeplink
import DerivationTool
/// In this file is a collection of helpers that control all state and action related operations
/// for the `RootReducer` with a connection to the UI navigation.
extension RootReducer {
public struct DestinationState: Equatable {
public enum Destination: Equatable {
case home
case onboarding
case phraseDisplay
case phraseValidation
case sandbox
case startup
case welcome
}
public var internalDestination: Destination = .welcome
public var previousDestination: Destination?
public var destination: Destination {
get { internalDestination }
set {
previousDestination = internalDestination
internalDestination = newValue
}
}
}
public enum DestinationAction: Equatable {
case deeplink(URL)
case deeplinkHome
case deeplinkSend(Zatoshi, String, String)
case deeplinkFailed(URL, ZcashError)
case updateDestination(RootReducer.DestinationState.Destination)
}
// swiftlint:disable:next cyclomatic_complexity
public func destinationReduce() -> Reduce<RootReducer.State, RootReducer.Action> {
Reduce { state, action in
switch action {
case let .destination(.updateDestination(destination)):
state.destinationState.destination = destination
case .sandbox(.reset):
state.destinationState.destination = .startup
case .phraseValidation(.proceedToHome):
state.destinationState.destination = .home
case .phraseValidation(.displayBackedUpPhrase):
state.destinationState.destination = .phraseDisplay
case .phraseDisplay(.finishedPressed):
// user is still supposed to do the backup phrase validation test
if (state.destinationState.previousDestination == .welcome
|| state.destinationState.previousDestination == .onboarding
|| state.destinationState.previousDestination == .startup)
&& state.walletConfig.isEnabled(.testBackupPhraseFlow) {
state.destinationState.destination = .phraseValidation
}
// user wanted to see the backup phrase once again (at validation finished screen)
if state.destinationState.previousDestination == .phraseValidation
|| !state.walletConfig.isEnabled(.testBackupPhraseFlow) {
state.destinationState.destination = .home
}
case .destination(.deeplink(let url)):
// get the latest synchronizer state
let synchronizerStatus = sdkSynchronizer.latestState().syncStatus
// process the deeplink only if app is initialized and synchronizer synced
guard state.appInitializationState == .initialized && synchronizerStatus == .upToDate else {
// TODO: [#370] There are many different states and edge cases we need to handle here
// (https://github.com/zcash/secant-ios-wallet/issues/370)
return .none
}
return .run { send in
do {
await send(
try await process(
url: url,
deeplink: deeplink,
derivationTool: derivationTool
)
)
} catch {
await send(.destination(.deeplinkFailed(url, error.toZcashError())))
}
}
case .destination(.deeplinkHome):
state.destinationState.destination = .home
state.homeState.destination = nil
return .none
case let .destination(.deeplinkSend(amount, address, memo)):
state.destinationState.destination = .home
state.homeState.destination = .send
state.homeState.sendState.amount = amount
state.homeState.sendState.address = address
state.homeState.sendState.memoState.text = memo.redacted
return .none
case let .destination(.deeplinkFailed(url, error)):
state.alert = AlertState.failedToProcessDeeplink(url, error)
return .none
case .home(.walletEvents(.replyTo(let address))):
guard let url = URL(string: "zcash:\(address)") else {
return .none
}
return EffectTask(value: .destination(.deeplink(url)))
case .home, .initialization, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .updateStateAfterConfigUpdate, .alert,
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .dismissAlert, .exportLogs:
return .none
}
return .none
}
}
}
private extension RootReducer {
public func process(
url: URL,
deeplink: DeeplinkClient,
derivationTool: DerivationToolClient
) async throws -> RootReducer.Action {
let deeplink = try deeplink.resolveDeeplinkURL(url, zcashNetwork.networkType, derivationTool)
switch deeplink {
case .home:
return .destination(.deeplinkHome)
case let .send(amount, address, memo):
return .destination(.deeplinkSend(Zatoshi(Int64(amount)), address, memo))
}
}
}
extension RootViewStore {
public func goToDestination(_ destination: RootReducer.DestinationState.Destination) {
send(.destination(.updateDestination(destination)))
}
public func goToDeeplink(_ deeplink: URL) {
send(.destination(.deeplink(deeplink)))
}
}
// MARK: Placeholders
extension RootReducer.DestinationState {
public static var placeholder: Self {
.init()
}
}