Root subflows Batch 4

- scan with sendings for all possible scenarios finished
- currency conversion path from home finished
- transaction details, list and seng again flows finished
This commit is contained in:
Lukas Korba 2025-03-25 14:55:21 +01:00
parent dff59a4905
commit 013fa12b88
10 changed files with 319 additions and 33 deletions

View File

@ -69,8 +69,15 @@ extension ScanCoordFlow {
return .none
}
}
// handling the path in senf form
// handling the path in send confirmation
for element in state.path {
if element.is(\.sendConfirmation) {
return .none
}
}
// handling the path in send form
for element in state.path {
if element.is(\.scan) {
let _ = state.path.popLast()
@ -82,7 +89,11 @@ extension ScanCoordFlow {
// MARK: - Request ZEC Confirmation
case .path(.element(id: _, action: .requestZecConfirmation(.goBackTappedFromRequestZec))):
let _ = state.path.removeLast()
for (id, element) in zip(state.path.ids, state.path) {
if element.is(\.sendForm) {
state.path.pop(to: id)
}
}
return .none
case .path(.element(id: _, action: .requestZecConfirmation(.sendTapped))):
@ -94,19 +105,82 @@ extension ScanCoordFlow {
}
return .none
// MARK: - Self
case .path(.element(id: _, action: .requestZecConfirmation(.saveAddressTapped(let address)))):
var addressBookState = AddressBook.State.initial
addressBookState.isNameFocused = true
addressBookState.address = address.data
addressBookState.isValidZcashAddress = true
state.path.append(.addressBookContact(addressBookState))
return .none
case .scan(.foundAddress(let address)):
// Handling of scan on the root
if state.path.ids.isEmpty {
audioServices.systemSoundVibrate()
state.path.append(.sendForm(SendForm.State.initial))
if let first = state.path.ids.first {
return .send(.path(.element(id: first, action: .sendForm(.addressUpdated(address)))))
case .path(.element(id: _, action: .requestZecConfirmation(.updateResult(let result)))):
for element in state.path {
if case .requestZecConfirmation(let sendConfirmationState) = element {
switch result {
case .failure:
state.path.append(.sendResultFailure(sendConfirmationState))
break
case .partial:
var partialProposalErrorState = PartialProposalError.State.initial
partialProposalErrorState.statuses = sendConfirmationState.partialFailureStatuses
partialProposalErrorState.txIds = sendConfirmationState.partialFailureTxIds
state.path.append(.sendResultPartial(partialProposalErrorState))
break
case .resubmission:
state.path.append(.sendResultResubmission(sendConfirmationState))
break
case .success:
state.path.append(.sendResultSuccess(sendConfirmationState))
default: break
}
}
}
return .none
// MARK: - Scan
case .path(.element(id: _, action: .scan(.foundAddress(let address)))):
for (id, element) in zip(state.path.ids, state.path) {
if element.is(\.sendForm) {
let _ = state.path.removeLast()
audioServices.systemSoundVibrate()
return .send(.path(.element(id: id, action: .sendForm(.addressUpdated(address)))))
}
}
return .none
case .path(.element(id: _, action: .scan(.cancelTapped))):
let _ = state.path.popLast()
return .none
case .path(.element(id: _, action: .scan(.foundRequestZec(let requestPayment)))):
if case .legacy(let address) = requestPayment {
for (id, element) in zip(state.path.ids, state.path) {
if element.is(\.sendForm) {
let _ = state.path.removeLast()
audioServices.systemSoundVibrate()
state.path[id: id, case: \.sendForm]?.memoState.text = ""
return .merge(
.send(.path(.element(id: id, action: .sendForm(.zecAmountUpdated("".redacted))))),
.send(.path(.element(id: id, action: .sendForm(.addressUpdated(address.value.redacted)))))
)
}
}
} else if case .request(let paymentRequest) = requestPayment {
return .send(.getProposal(paymentRequest))
}
return .none
// MARK: - Self
case .scan(.foundAddress(let address)):
audioServices.systemSoundVibrate()
state.path.append(.sendForm(SendForm.State.initial))
if let first = state.path.ids.first {
return .send(.path(.element(id: first, action: .sendForm(.addressUpdated(address)))))
}
return .none
case .scan(.foundRequestZec(let requestPayment)):
if case .legacy(let address) = requestPayment {
return .send(.scan(.foundAddress(address.value.redacted)))
@ -153,6 +227,39 @@ extension ScanCoordFlow {
}
case .proposalResolved(let proposal):
if state.path.ids.isEmpty {
return .send(.proposalResolvedNoSendForm(proposal))
}
return .send(.proposalResolvedExistingSendForm(proposal))
case .proposalResolvedExistingSendForm(let proposal):
state.proposal = proposal
guard let address = state.recipient?.stringEncoded else {
return .send(.requestZecFailed)
}
var sendConfirmationState = SendConfirmation.State.initial
sendConfirmationState.amount = state.amount
sendConfirmationState.address = address
sendConfirmationState.proposal = proposal
sendConfirmationState.feeRequired = proposal.totalFeeRequired()
sendConfirmationState.message = state.memo?.toString() ?? ""
sendConfirmationState.currencyAmount = state.currencyConversion?.convert(state.amount).redacted ?? .empty
state.path.append(.requestZecConfirmation(sendConfirmationState))
audioServices.systemSoundVibrate()
if let first = state.path.ids.first {
state.path[id: first, case: \.sendForm]?.memoState.text = sendConfirmationState.message
return .merge(
.send(.path(.element(id: first, action: .sendForm(.zecAmountUpdated(state.amount.decimalString().redacted))))),
.send(.path(.element(id: first, action: .sendForm(.addressUpdated(address.redacted)))))
)
}
return .none
case .proposalResolvedNoSendForm(let proposal):
state.proposal = proposal
guard let address = state.recipient?.stringEncoded else {
@ -183,6 +290,29 @@ extension ScanCoordFlow {
return .none
case .requestZecFailed:
if state.path.ids.isEmpty {
return .send(.requestZecFailedNoSendForm)
}
return .send(.requestZecFailedExistingSendForm)
case .requestZecFailedExistingSendForm:
let _ = state.path.removeLast()
for (id, element) in zip(state.path.ids, state.path) {
if element.is(\.sendForm) {
audioServices.systemSoundVibrate()
let address = state.recipient?.stringEncoded ?? ""
let memo = state.memo?.toString() ?? ""
state.path[id: id, case: \.sendForm]?.memoState.text = memo
return .merge(
.send(.path(.element(id: id, action: .sendForm(.zecAmountUpdated(state.amount.decimalString().redacted))))),
.send(.path(.element(id: id, action: .sendForm(.addressUpdated(address.redacted)))))
)
}
}
return .none
case .requestZecFailedNoSendForm:
let address = state.recipient?.stringEncoded ?? ""
var sendFormState = SendForm.State.initial
sendFormState.memoState.text = state.memo?.toString() ?? ""
@ -206,6 +336,14 @@ extension ScanCoordFlow {
state.path.append(.addressBook(addressBookState))
return .none
case .path(.element(id: _, action: .sendForm(.addNewContactTapped(let address)))):
var addressBookState = AddressBook.State.initial
addressBookState.isNameFocused = true
addressBookState.address = address.data
addressBookState.isValidZcashAddress = true
state.path.append(.addressBookContact(addressBookState))
return .none
case .path(.element(id: _, action: .sendForm(.scanTapped))):
var scanState = Scan.State.initial
scanState.checkers = [.zcashAddressScanChecker, .requestZecScanChecker]
@ -230,10 +368,7 @@ extension ScanCoordFlow {
}
}
return .none
case let .path(.element(id: _, action: .sendForm(.sendFailed(_, confirmationType)))):
return .none
// MARK: - Send Confirmation
case .path(.element(id: _, action: .sendConfirmation(.cancelTapped))):
@ -299,7 +434,7 @@ extension ScanCoordFlow {
}
}
return .none
// MARK: - Transaction Details
case .path(.element(id: _, action: .transactionDetails(.saveAddressTapped))):
@ -322,7 +457,7 @@ extension ScanCoordFlow {
}
return .none
// MARK: -
@ -347,7 +482,7 @@ extension ScanCoordFlow {
// return .send(.sendForm(.requestZec(requestPayment)))
/*
// MARK: - Address Book
// MARK: Address Book
case .path(.element(id: _, action: .addressBook(.editId(let address)))):
state.path.removeAll()
@ -366,7 +501,7 @@ extension ScanCoordFlow {
state.path.append(.scan(scanState))
return .none
// MARK: - Address Book Contact
// MARK: Address Book Contact
case .path(.element(id: _, action: .addressBookContact(.dismissAddContactRequired))):
let _ = state.path.popLast()
@ -378,7 +513,7 @@ extension ScanCoordFlow {
}
return .none
// MARK: - Request ZEC Confirmation
// MARK: Request ZEC Confirmation
case .path(.element(id: _, action: .requestZecConfirmation(.goBackTappedFromRequestZec))):
state.path.removeAll()
@ -399,7 +534,7 @@ extension ScanCoordFlow {
// MARK: - Send
// MARK: Send
case .sendForm(.addressBookTapped):
var addressBookState = AddressBook.State.initial
@ -436,7 +571,7 @@ extension ScanCoordFlow {
}
return .none
// MARK: - Send Confirmation
// MARK: Send Confirmation
case .path(.element(id: _, action: .sendConfirmation(.cancelTapped))):
let _ = state.path.popLast()
@ -502,12 +637,12 @@ extension ScanCoordFlow {
}
return .none
// MARK: - Self
// MARK: Self
case .dismissRequired:
return .none
// MARK: - Transaction Details
// MARK: Transaction Details
case .path(.element(id: _, action: .transactionDetails(.saveAddressTapped))):
var addressBookState = AddressBook.State.initial

View File

@ -65,7 +65,11 @@ public struct ScanCoordFlow {
case onAppear
case path(StackActionOf<Path>)
case proposalResolved(Proposal)
case proposalResolvedExistingSendForm(Proposal)
case proposalResolvedNoSendForm(Proposal)
case requestZecFailed
case requestZecFailedExistingSendForm
case requestZecFailedNoSendForm
case scan(Scan.Action)
}

View File

@ -80,6 +80,38 @@ extension SendCoordFlow {
state.path.removeAll()
return .none
case .path(.element(id: _, action: .requestZecConfirmation(.saveAddressTapped(let address)))):
var addressBookState = AddressBook.State.initial
addressBookState.isNameFocused = true
addressBookState.address = address.data
addressBookState.isValidZcashAddress = true
state.path.append(.addressBookContact(addressBookState))
return .none
case .path(.element(id: _, action: .requestZecConfirmation(.updateResult(let result)))):
for element in state.path {
if case .requestZecConfirmation(let sendConfirmationState) = element {
switch result {
case .failure:
state.path.append(.sendResultFailure(sendConfirmationState))
break
case .partial:
var partialProposalErrorState = PartialProposalError.State.initial
partialProposalErrorState.statuses = sendConfirmationState.partialFailureStatuses
partialProposalErrorState.txIds = sendConfirmationState.partialFailureTxIds
state.path.append(.sendResultPartial(partialProposalErrorState))
break
case .resubmission:
state.path.append(.sendResultResubmission(sendConfirmationState))
break
case .success:
state.path.append(.sendResultSuccess(sendConfirmationState))
default: break
}
}
}
return .none
// MARK: - Scan
case .path(.element(id: _, action: .scan(.foundAddress(let address)))):
@ -115,6 +147,14 @@ extension SendCoordFlow {
state.path.append(.addressBook(addressBookState))
return .none
case .sendForm(.addNewContactTapped(let address)):
var addressBookState = AddressBook.State.initial
addressBookState.isNameFocused = true
addressBookState.address = address.data
addressBookState.isValidZcashAddress = true
state.path.append(.addressBookContact(addressBookState))
return .none
case .sendForm(.scanTapped):
var scanState = Scan.State.initial
scanState.checkers = [.zcashAddressScanChecker, .requestZecScanChecker]
@ -159,8 +199,7 @@ extension SendCoordFlow {
}
return .none
case .path(.element(id: _, action: .sendConfirmation(.updateResult(let result)))),
.path(.element(id: _, action: .requestZecConfirmation(.updateResult(let result)))):
case .path(.element(id: _, action: .sendConfirmation(.updateResult(let result)))):
for element in state.path {
if case .sendConfirmation(let sendConfirmationState) = element {
switch result {

View File

@ -9,6 +9,7 @@ import ComposableArchitecture
import Generated
// Path
import AddressBook
import TransactionDetails
import TransactionsManager
@ -16,11 +17,51 @@ extension TransactionsCoordFlow {
public func coordinatorReduce() -> Reduce<TransactionsCoordFlow.State, TransactionsCoordFlow.Action> {
Reduce { state, action in
switch action {
// MARK: - Address Book Contact
case .path(.element(id: _, action: .addressBookContact(.dismissAddContactRequired))):
let _ = state.path.popLast()
return .none
// MARK: - Self: Transaction Details
case .transactionDetails(.saveAddressTapped):
var addressBookState = AddressBook.State.initial
addressBookState.address = state.transactionDetailsState.transaction.address
addressBookState.isValidZcashAddress = true
addressBookState.isNameFocused = true
state.path.append(.addressBookContact(addressBookState))
return .none
// MARK: - Self: Transaction Manager
case .transactionsManager(.transactionTapped(let txId)):
if let index = state.transactions.index(id: txId) {
var transactionDetailsState = TransactionDetails.State.initial
transactionDetailsState.transaction = state.transactions[index]
state.path.append(.transactionDetails(transactionDetailsState))
}
return .none
// MARK: - Transaction Details
case .path(.element(id: _, action: .transactionDetails(.saveAddressTapped))):
for element in state.path {
if case .transactionDetails(let transactionDetailsState) = element {
var addressBookState = AddressBook.State.initial
addressBookState.address = transactionDetailsState.transaction.address
addressBookState.isValidZcashAddress = true
addressBookState.isNameFocused = true
state.path.append(.addressBookContact(addressBookState))
}
}
return .none
case .path(.element(id: _, action: .transactionDetails(.closeDetailTapped))):
let _ = state.path.removeLast()
return .none
default: return .none
}
}

View File

@ -9,7 +9,10 @@ import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
import Models
// Path
import AddressBook
import TransactionDetails
import TransactionsManager
@ -17,6 +20,7 @@ import TransactionsManager
public struct TransactionsCoordFlow {
@Reducer
public enum Path {
case addressBookContact(AddressBook)
case transactionDetails(TransactionDetails)
}
@ -24,6 +28,7 @@ public struct TransactionsCoordFlow {
public struct State {
public var path = StackState<Path.State>()
public var transactionDetailsState = TransactionDetails.State.initial
@Shared(.inMemory(.transactions)) public var transactions: IdentifiedArrayOf<TransactionState> = []
public var transactionsManagerState = TransactionsManager.State.initial
public var transactionToOpen: String?

View File

@ -12,6 +12,7 @@ import UIComponents
import Generated
// Path
import AddressBook
import TransactionDetails
import TransactionsManager
@ -52,6 +53,8 @@ public struct TransactionsCoordFlowView: View {
}
} destination: { store in
switch store.case {
case let .addressBookContact(store):
AddressBookContactView(store: store)
case let .transactionDetails(store):
TransactionDetailsView(store: store, tokenName: tokenName)
}

View File

@ -69,6 +69,13 @@ extension Root {
}
.cancellable(id: CancelFlexaId, cancelInFlight: true)
// MARK: - Currency Conversion Setup
case .currencyConversionSetup(.skipTapped), .currencyConversionSetup(.enableTapped):
state.path = nil
state.homeState.isRateEducationEnabled = false
return .none
// MARK: - Home
case .home(.settingsTapped):
@ -108,6 +115,9 @@ extension Root {
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
@ -115,6 +125,11 @@ extension Root {
state.transactionsCoordFlowState = .initial
state.path = .transactionsCoordFlow
return .none
case .home(.currencyConversionSetupTapped):
state.currencyConversionSetupState = .initial
state.path = .currencyConversionSetup
return .none
// MARK: - Integrations
@ -152,6 +167,17 @@ extension Root {
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))))
)
// MARK: - Send Coord Flow
case .sendCoordFlow(.path(.element(id: _, action: .sendResultSuccess(.closeTapped)))),
@ -178,6 +204,26 @@ extension Root {
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
default: return .none
}
}

View File

@ -36,7 +36,7 @@ import AudioServices
//import AddKeystoneHWWallet
//import AddressBook
//import AddressDetails
//import CurrencyConversionSetup
import CurrencyConversionSetup
//import DeleteWallet
//import ExportTransactionHistory
import Home
@ -113,6 +113,7 @@ public struct Root {
public struct State {
public enum Path {
case addKeystoneHWWalletCoordFlow
case currencyConversionSetup
case receive
case requestZecCoordFlow
case scanCoordFlow
@ -166,6 +167,7 @@ public struct Root {
// Path
public var addKeystoneHWWalletCoordFlowState = AddKeystoneHWWalletCoordFlow.State.initial
public var currencyConversionSetupState = CurrencyConversionSetup.State.initial
public var receiveState = Receive.State.initial
public var requestZecCoordFlowState = RequestZecCoordFlow.State.initial
public var scanCoordFlowState = ScanCoordFlow.State.initial
@ -257,9 +259,11 @@ public struct Root {
// Path
case addKeystoneHWWalletCoordFlow(AddKeystoneHWWalletCoordFlow.Action)
case currencyConversionSetup(CurrencyConversionSetup.Action)
case receive(Receive.Action)
case requestZecCoordFlow(RequestZecCoordFlow.Action)
case scanCoordFlow(ScanCoordFlow.Action)
case sendAgainRequested(TransactionState)
case sendCoordFlow(SendCoordFlow.Action)
case settings(Settings.Action)
case transactionsCoordFlow(TransactionsCoordFlow.Action)
@ -379,6 +383,10 @@ public struct Root {
TransactionsCoordFlow()
}
Scope(state: \.currencyConversionSetupState, action: \.currencyConversionSetup) {
CurrencyConversionSetup()
}
initializationReduce()
destinationReduce()

View File

@ -16,7 +16,7 @@ import OSStatusError
//import AddKeystoneHWWallet
//import AddressBook
//import AddressDetails
//import CurrencyConversionSetup
import CurrencyConversionSetup
//import DeleteWallet
//import ExportTransactionHistory
import Home
@ -203,6 +203,14 @@ private extension RootView {
tokenName: tokenName
)
}
.navigationLinkEmpty(isActive: store.bindingFor(.currencyConversionSetup)) {
CurrencyConversionSetupView(
store:
store.scope(
state: \.currencyConversionSetupState,
action: \.currencyConversionSetup)
)
}
}
.navigationViewStyle(.stack)
.overlayedWithSplash(store.splashAppeared) {

View File

@ -344,11 +344,8 @@ public struct SendForm {
}
case let .sendFailed(error, confirmationType):
if confirmationType == .requestPayment {
// return .send(.updateDestination(nil))
} else {
if confirmationType == .send {
state.alert = AlertState.sendFailure(error)
return .none
}
return .none