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

303 lines
13 KiB
Swift

//
// RootCoordinator.swift
// modules
//
// Created by Lukáš Korba on 07.03.2025.
//
import ComposableArchitecture
import ZcashLightClientKit
extension Root {
public func coordinatorReduce() -> Reduce<Root.State, Root.Action> {
Reduce { state, action in
switch action {
// MARK: - Accounts
case .home(.walletAccountTapped(let walletAccount)):
guard state.selectedWalletAccount != walletAccount else {
return .none
}
state.$selectedWalletAccount.withLock { $0 = walletAccount }
state.homeState.transactionListState.isInvalidated = true
return .merge(
.send(.home(.smartBanner(.walletAccountChanged))),
.send(.home(.walletBalances(.updateBalances))),
.send(.loadContacts),
.send(.resolveMetadataEncryptionKeys),
.send(.loadUserMetadata),
.send(.fetchTransactionsForTheSelectedAccount)
)
// MARK: - Add Keystone HW Wallet Coord Flow
case .addKeystoneHWWalletCoordFlow(.path(.element(id: _, action: .accountHWWalletSelection(.forgetThisDeviceTapped)))):
state.path = nil
return .none
case .addKeystoneHWWalletCoordFlow(.path(.element(id: _, action: .accountHWWalletSelection(.accountImportSucceeded)))):
state.path = nil
return .merge(
.send(.loadContacts),
.send(.resolveMetadataEncryptionKeys),
.send(.loadUserMetadata),
.send(.fetchTransactionsForTheSelectedAccount)
)
// MARK: - Add Keystone HW Wallet from Settings
case .settings(.path(.element(id: _, action: .accountHWWalletSelection(.accountImportSucceeded)))):
state.path = nil
return .merge(
.send(.loadContacts),
.send(.resolveMetadataEncryptionKeys),
.send(.loadUserMetadata),
.send(.fetchTransactionsForTheSelectedAccount)
)
// MARK: - Flexa
case .flexaOpenRequest:
flexaHandler.open()
return .publisher {
flexaHandler.onTransactionRequest()
.map(Root.Action.flexaOnTransactionRequest)
.receive(on: mainQueue)
}
.cancellable(id: CancelFlexaId, cancelInFlight: true)
// MARK: - Currency Conversion Setup
case .currencyConversionSetup(.skipTapped), .currencyConversionSetup(.enableTapped):
state.path = nil
state.homeState.isRateEducationEnabled = false
return .send(.home(.smartBanner(.closeAndCleanupBanner)))
// MARK: - Home
case .home(.settingsTapped):
state.settingsState = .initial
state.path = .settings
return .none
case .home(.receiveTapped):
state.receiveState = .initial
state.path = .receive
return .none
case .home(.sendTapped):
state.sendCoordFlowState = .initial
state.path = .sendCoordFlow
exchangeRate.refreshExchangeRateUSD()
return .none
case .home(.scanTapped):
state.scanCoordFlowState = .initial
state.path = .scanCoordFlow
return .none
case .home(.getSomeZecTapped):
state.requestZecCoordFlowState = .initial
state.path = .requestZecCoordFlow
return .none
case .home(.flexaTapped):
return .send(.flexaOpenRequest)
case .home(.addKeystoneHWWalletTapped):
state.addKeystoneHWWalletCoordFlowState = .initial
state.path = .addKeystoneHWWalletCoordFlow
return .none
case .home(.transactionList(.transactionTapped(let txId))):
state.transactionsCoordFlowState = .initial
state.transactionsCoordFlowState.transactionToOpen = txId
if let index = state.transactions.index(id: txId) {
state.transactionsCoordFlowState.transactionDetailsState.transaction = state.transactions[index]
}
state.path = .transactionsCoordFlow
return .none
case .home(.seeAllTransactionsTapped):
state.transactionsCoordFlowState = .initial
state.path = .transactionsCoordFlow
return .none
case .home(.currencyConversionSetupTapped):
state.currencyConversionSetupState = .initial
state.path = .currencyConversionSetup
return .none
case .home(.smartBanner(.walletBackupTapped)):
state.walletBackupCoordFlowState = .initial
state.path = .walletBackup
return .none
// MARK: - Integrations
case .settings(.path(.element(id: _, action: .integrations(.flexaTapped)))):
return .send(.flexaOpenRequest)
// MARK: - Keystone
case .sendCoordFlow(.path(.element(id: _, action: .confirmWithKeystone(.rejectTapped)))),
.signWithKeystoneCoordFlow(.sendConfirmation(.rejectTapped)):
state.path = nil
return .none
case .signWithKeystoneRequested:
//state.path = .signWithKeystoneCoordFlow
state.signWithKeystoneCoordFlowBinding = true
return .send(.signWithKeystoneCoordFlow(.sendConfirmation(.resolvePCZT)))
// MARK: - Request Zec
case .requestZecCoordFlow(.path(.element(id: _, action: .requestZecSummary(.cancelRequestTapped)))):
state.path = nil
return .none
// MARK: - Reset Zashi
case .settings(.path(.element(id: _, action: .resetZashi(.deleteTapped)))):
return .send(.initialization(.resetZashiRequest))
// MARK: - Restore Wallet Coord Flow from Onboarding
case .onboarding(.restoreWalletCoordFlow(.path(.element(id: _, action: .restoreInfo(.gotItTapped))))):
var leavesScreenOpen = false
for element in state.onboardingState.restoreWalletCoordFlowState.path {
if case .restoreInfo(let restoreInfoState) = element {
leavesScreenOpen = restoreInfoState.isAcknowledged
}
}
userDefaults.setValue(leavesScreenOpen, Constants.udLeavesScreenOpen)
state.isRestoringWallet = true
userDefaults.setValue(true, Constants.udIsRestoringWallet)
state.$walletStatus.withLock { $0 = .restoring }
return .concatenate(
.send(.initialization(.initializeSDK(.restoreWallet))),
.send(.initialization(.checkBackupPhraseValidation)),
.send(.batteryStateChanged(nil))
)
// MARK: - Scan Coord Flow
case .scanCoordFlow(.scan(.cancelTapped)):
state.path = nil
return .none
case .scanCoordFlow(.path(.element(id: _, action: .sendForm(.dismissRequired)))):
state.path = nil
return .none
case .scanCoordFlow(.path(.element(id: _, action: .transactionDetails(.closeDetailTapped)))):
state.path = nil
return .none
case .scanCoordFlow(.path(.element(id: _, action: .sendResultSuccess(.closeTapped)))),
.scanCoordFlow(.path(.element(id: _, action: .sendResultResubmission(.closeTapped)))),
.scanCoordFlow(.path(.element(id: _, action: .sendResultPartial(.dismiss)))):
state.path = nil
return .none
// MARK: - Self
case .sendAgainRequested(let transactionState):
state.sendCoordFlowState = .initial
state.path = .sendCoordFlow
state.sendCoordFlowState.sendFormState.memoState.text = state.transactionMemos[transactionState.id]?.first ?? ""
return .merge(
.send(.sendCoordFlow(.sendForm(.zecAmountUpdated(transactionState.amountWithoutFee.decimalString().redacted)))),
.send(.sendCoordFlow(.sendForm(.addressUpdated(transactionState.address.redacted))))
)
case .deeplinkWarning(.rescanInZashi):
state = .initial
state.splashAppeared = true
return .merge(
.send(.destination(.updateDestination(.home))),
.send(.home(.scanTapped))
)
// MARK: - Send Coord Flow
case .sendCoordFlow(.path(.element(id: _, action: .sendResultSuccess(.closeTapped)))),
.sendCoordFlow(.path(.element(id: _, action: .sendResultResubmission(.closeTapped)))),
.sendCoordFlow(.path(.element(id: _, action: .sendResultPartial(.dismiss)))):
state.path = nil
return .none
case .sendCoordFlow(.path(.element(id: _, action: .transactionDetails(.closeDetailTapped)))):
state.path = nil
return .none
case .sendCoordFlow(.dismissRequired):
state.path = nil
return .none
// MARK: - Sign with Keystone Coord Flow
case .signWithKeystoneCoordFlow(.path(.element(id: _, action: .sendResultSuccess(.closeTapped)))),
.signWithKeystoneCoordFlow(.path(.element(id: _, action: .sendResultResubmission(.closeTapped)))):
state.signWithKeystoneCoordFlowBinding = false
return .none
case .signWithKeystoneCoordFlow(.path(.element(id: _, action: .transactionDetails(.closeDetailTapped)))):
state.signWithKeystoneCoordFlowBinding = false
return .none
// MARK: - Transactions Coord Flow
case .transactionsCoordFlow(.transactionDetails(.closeDetailTapped)):
state.path = nil
return .none
case .transactionsCoordFlow(.transactionsManager(.dismissRequired)):
state.path = nil
return .none
case .transactionsCoordFlow(.transactionDetails(.sendAgainTapped)):
state.path = nil
let transactionState = state.transactionsCoordFlowState.transactionDetailsState.transaction
return .run { send in
try? await mainQueue.sleep(for: .seconds(0.8))
await send(.sendAgainRequested(transactionState))
}
case .transactionsCoordFlow(.path(.element(id: _, action: .transactionDetails(.sendAgainTapped)))):
for element in state.transactionsCoordFlowState.path {
if case .transactionDetails(let transactionDetailsState) = element {
state.path = nil
return .run { send in
try? await mainQueue.sleep(for: .seconds(0.8))
await send(.sendAgainRequested(transactionDetailsState.transaction))
}
}
}
return .none
// MARK: - Wallet Backup Coord Flow
case .walletBackupCoordFlow(.path(.element(id: _, action: .phrase(.remindMeLaterTapped)))):
state.path = nil
return .send(.home(.smartBanner(.remindMeLaterTapped(.priority6))))
case .walletBackupCoordFlow(.path(.element(id: _, action: .phrase(.seedSavedTapped)))):
state.path = nil
do {
try walletStorage.markUserPassedPhraseBackupTest(true)
} catch {
state.alert = AlertState.cantStoreThatUserPassedPhraseBackupTest(error.toZcashError())
}
return .merge(
.send(.home(.smartBanner(.closeAndCleanupBanner))),
.send(.home(.smartBanner(.closeSheetTapped)))
)
default: return .none
}
}
}
}