Merge pull request #1176 from LukasKorba/1087--Total-and-available-balances-component

[#1087] Total and available balances component
This commit is contained in:
Lukas Korba 2024-04-05 12:18:20 +02:00 committed by GitHub
commit d8f70a64ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 408 additions and 357 deletions

View File

@ -60,6 +60,7 @@ let package = Package(
.library(name: "UserDefaults", targets: ["UserDefaults"]),
.library(name: "UserPreferencesStorage", targets: ["UserPreferencesStorage"]),
.library(name: "Utils", targets: ["Utils"]),
.library(name: "WalletBalances", targets: ["WalletBalances"]),
.library(name: "WalletConfigProvider", targets: ["WalletConfigProvider"]),
.library(name: "WalletStorage", targets: ["WalletStorage"]),
.library(name: "Welcome", targets: ["Welcome"]),
@ -245,6 +246,7 @@ let package = Package(
"UIComponents",
"Utils",
"TransactionList",
"WalletBalances",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
@ -506,6 +508,7 @@ let package = Package(
"SDKSynchronizer",
"UIComponents",
"Utils",
"WalletBalances",
"WalletStorage",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
@ -653,6 +656,19 @@ let package = Package(
],
path: "Sources/Utils"
),
.target(
name: "WalletBalances",
dependencies: [
"Generated",
"Models",
"SDKSynchronizer",
"UIComponents",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
],
path: "Sources/Features/WalletBalances"
),
.target(
name: "WalletConfigProvider",
dependencies: [

View File

@ -19,6 +19,7 @@ import SDKSynchronizer
import Models
import SyncProgress
import RestoreWalletStorage
import WalletBalances
import ZcashSDKEnvironment
public typealias BalanceBreakdownStore = Store<BalanceBreakdownReducer.State, BalanceBreakdownReducer.Action>
@ -42,10 +43,9 @@ public struct BalanceBreakdownReducer: Reducer {
public var partialProposalErrorState: PartialProposalError.State
public var pendingTransactions: Zatoshi
public var shieldedBalance: Zatoshi
public var shieldedWithPendingBalance: Zatoshi
public var syncProgressState: SyncProgressReducer.State
public var totalBalance: Zatoshi
public var transparentBalance: Zatoshi
public var walletBalancesState: WalletBalances.State
public var isShieldableBalanceAvailable: Bool {
transparentBalance.amount >= autoShieldingThreshold.amount
@ -54,15 +54,7 @@ public struct BalanceBreakdownReducer: Reducer {
public var isShieldingButtonDisabled: Bool {
isShieldingFunds || !isShieldableBalanceAvailable
}
public var isProcessingZeroAvailableBalance: Bool {
if shieldedBalance.amount == 0 && transparentBalance.amount > 0 {
return false
}
return totalBalance.amount != shieldedBalance.amount && shieldedBalance.amount == 0
}
public init(
autoShieldingThreshold: Zatoshi,
changePending: Zatoshi,
@ -72,11 +64,10 @@ public struct BalanceBreakdownReducer: Reducer {
isHintBoxVisible: Bool = false,
partialProposalErrorState: PartialProposalError.State,
pendingTransactions: Zatoshi,
shieldedBalance: Zatoshi,
shieldedWithPendingBalance: Zatoshi = .zero,
shieldedBalance: Zatoshi = .zero,
syncProgressState: SyncProgressReducer.State,
totalBalance: Zatoshi,
transparentBalance: Zatoshi
transparentBalance: Zatoshi = .zero,
walletBalancesState: WalletBalances.State
) {
self.autoShieldingThreshold = autoShieldingThreshold
self.changePending = changePending
@ -87,10 +78,9 @@ public struct BalanceBreakdownReducer: Reducer {
self.partialProposalErrorState = partialProposalErrorState
self.pendingTransactions = pendingTransactions
self.shieldedBalance = shieldedBalance
self.shieldedWithPendingBalance = shieldedWithPendingBalance
self.totalBalance = totalBalance
self.syncProgressState = syncProgressState
self.transparentBalance = transparentBalance
self.walletBalancesState = walletBalancesState
}
}
@ -105,10 +95,10 @@ public struct BalanceBreakdownReducer: Reducer {
case shieldFundsFailure(ZcashError)
case shieldFundsPartial([String], [String])
case shieldFundsSuccess
case synchronizerStateChanged(RedactableSynchronizerState)
case syncProgress(SyncProgressReducer.Action)
case updateDestination(BalanceBreakdownReducer.State.Destination?)
case updateHintBoxVisibility(Bool)
case walletBalances(WalletBalances.Action)
}
@Dependency(\.derivationTool) var derivationTool
@ -131,6 +121,10 @@ public struct BalanceBreakdownReducer: Reducer {
PartialProposalError()
}
Scope(state: \.walletBalancesState, action: /Action.walletBalances) {
WalletBalances()
}
Reduce { state, action in
switch action {
case .alert(.presented(let action)):
@ -145,13 +139,7 @@ public struct BalanceBreakdownReducer: Reducer {
case .onAppear:
state.autoShieldingThreshold = zcashSDKEnvironment.shieldingThreshold
return .publisher {
sdkSynchronizer.stateStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
.map { $0.redacted }
.map(Action.synchronizerStateChanged)
}
.cancellable(id: CancelId, cancelInFlight: true)
return .none
case .onDisappear:
return .cancel(id: CancelId)
@ -207,24 +195,13 @@ public struct BalanceBreakdownReducer: Reducer {
case .shieldFundsSuccess:
state.isShieldingFunds = false
state.transparentBalance = .zero
state.walletBalancesState.transparentBalance = .zero
return .none
case let .shieldFundsPartial(txIds, statuses):
state.partialProposalErrorState.txIds = txIds
state.partialProposalErrorState.statuses = statuses
return .send(.updateDestination(.partialProposalError))
case .synchronizerStateChanged(let latestState):
let accountBalance = latestState.data.accountBalance?.data
state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero)
state.shieldedWithPendingBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = accountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
state.changePending = (accountBalance?.saplingBalance.changePendingConfirmation ?? .zero) + (accountBalance?.orchardBalance.changePendingConfirmation ?? .zero)
state.pendingTransactions = (accountBalance?.saplingBalance.valuePendingSpendability ?? .zero) + (accountBalance?.orchardBalance.valuePendingSpendability ?? .zero)
return .none
case .syncProgress:
return .none
@ -236,6 +213,14 @@ public struct BalanceBreakdownReducer: Reducer {
case .updateHintBoxVisibility(let visibility):
state.isHintBoxVisible = visibility
return .none
case .walletBalances(.balancesUpdated):
state.shieldedBalance = state.walletBalancesState.shieldedBalance
state.transparentBalance = state.walletBalancesState.transparentBalance
return .none
case .walletBalances:
return .none
}
}
}
@ -291,10 +276,8 @@ extension BalanceBreakdownReducer.State {
isShieldingFunds: false,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: .zero
walletBalancesState: .initial
)
public static let initial = BalanceBreakdownReducer.State(
@ -303,10 +286,8 @@ extension BalanceBreakdownReducer.State {
isShieldingFunds: false,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: .zero
walletBalancesState: .initial
)
}

View File

@ -15,6 +15,7 @@ import Utils
import Models
import BalanceFormatter
import SyncProgress
import WalletBalances
public struct BalanceBreakdownView: View {
let store: BalanceBreakdownStore
@ -28,21 +29,18 @@ public struct BalanceBreakdownView: View {
public var body: some View {
ScrollView {
WithViewStore(store, observe: { $0 }) { viewStore in
BalanceWithIconView(balance: viewStore.totalBalance)
.padding(.top, 40)
.padding(.bottom, 5)
.onAppear { viewStore.send(.onAppear) }
.onDisappear { viewStore.send(.onDisappear) }
AvailableBalanceView(
balance: viewStore.shieldedBalance,
WalletBalancesView(
store: store.scope(
state: \.walletBalancesState,
action: BalanceBreakdownReducer.Action.walletBalances
),
tokenName: tokenName,
showIndicator: viewStore.isProcessingZeroAvailableBalance
underlinedAvailableBalance: false
)
Asset.Colors.primary.color
.frame(height: 1)
.padding(EdgeInsets(top: 30, leading: 30, bottom: 10, trailing: 30))
.padding(EdgeInsets(top: 0, leading: 30, bottom: 10, trailing: 30))
balancesBlock(viewStore)
@ -81,10 +79,12 @@ public struct BalanceBreakdownView: View {
}
.padding(.vertical, 1)
.applyScreenBackground()
.alert(store: store.scope(
state: \.$alert,
action: { .alert($0) }
))
.alert(
store: store.scope(
state: \.$alert,
action: { .alert($0) }
)
)
.task { await store.send(.restoreWalletTask).finish() }
}
}
@ -272,14 +272,12 @@ extension BalanceBreakdownView {
isHintBoxVisible: true,
partialProposalErrorState: .initial,
pendingTransactions: Zatoshi(25_234_000),
shieldedBalance: Zatoshi(25_234_778),
syncProgressState: .init(
lastKnownSyncPercentage: 0.43,
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
syncStatusMessage: "Syncing"
),
totalBalance: Zatoshi(25_234_778),
transparentBalance: Zatoshi(25_234_778)
walletBalancesState: .initial
)
) {
BalanceBreakdownReducer()

View File

@ -11,6 +11,7 @@ import TransactionList
import Scan
import SyncProgress
import RestoreWalletStorage
import WalletBalances
public typealias HomeStore = Store<HomeReducer.State, HomeReducer.Action>
public typealias HomeViewStore = ViewStore<HomeReducer.State, HomeReducer.Action>
@ -23,71 +24,36 @@ public struct HomeReducer: Reducer {
@PresentationState public var alert: AlertState<Action>?
public var canRequestReview = false
public var isRestoringWallet = false
public var requiredTransactionConfirmations = 0
public var migratingDatabase = true
public var scanState: Scan.State
public var shieldedBalance: Zatoshi
public var shieldedWithPendingBalance: Zatoshi
public var synchronizerStatusSnapshot: SyncStatusSnapshot
public var syncProgressState: SyncProgressReducer.State
public var walletConfig: WalletConfig
public var totalBalance: Zatoshi
public var transactionListState: TransactionListReducer.State
public var transparentBalance: Zatoshi
public var migratingDatabase = true
// TODO: [#311] - Get the ZEC price from the SDK, https://github.com/Electric-Coin-Company/zashi-ios/issues/311
public var zecPrice = Decimal(140.0)
public var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: shieldedBalance.decimalValue.decimalValue * zecPrice)
}
public var isSendButtonDisabled: Bool {
shieldedBalance.amount == 0
}
public var isProcessingZeroAvailableBalance: Bool {
if shieldedBalance.amount == 0 && transparentBalance.amount > 0 {
return false
}
return totalBalance.amount != shieldedBalance.amount && shieldedBalance.amount == 0
}
public var walletBalancesState: WalletBalances.State
public init(
canRequestReview: Bool = false,
isRestoringWallet: Bool = false,
requiredTransactionConfirmations: Int = 0,
migratingDatabase: Bool = true,
scanState: Scan.State,
shieldedBalance: Zatoshi,
shieldedWithPendingBalance: Zatoshi = .zero,
synchronizerStatusSnapshot: SyncStatusSnapshot,
syncProgressState: SyncProgressReducer.State,
totalBalance: Zatoshi = .zero,
transactionListState: TransactionListReducer.State,
transparentBalance: Zatoshi = .zero,
walletConfig: WalletConfig,
zecPrice: Decimal = Decimal(140.0)
walletBalancesState: WalletBalances.State,
walletConfig: WalletConfig
) {
self.canRequestReview = canRequestReview
self.isRestoringWallet = isRestoringWallet
self.requiredTransactionConfirmations = requiredTransactionConfirmations
self.migratingDatabase = migratingDatabase
self.scanState = scanState
self.shieldedBalance = shieldedBalance
self.shieldedWithPendingBalance = shieldedWithPendingBalance
self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
self.syncProgressState = syncProgressState
self.totalBalance = totalBalance
self.transactionListState = transactionListState
self.transparentBalance = transparentBalance
self.walletConfig = walletConfig
self.zecPrice = zecPrice
self.walletBalancesState = walletBalancesState
}
}
public enum Action: Equatable {
case alert(PresentationAction<Action>)
case balanceBreakdown
case debugMenuStartup
case foundTransactions
case onAppear
case onDisappear
@ -102,6 +68,7 @@ public struct HomeReducer: Reducer {
case syncProgress(SyncProgressReducer.Action)
case updateTransactionList([TransactionState])
case transactionList(TransactionListReducer.Action)
case walletBalances(WalletBalances.Action)
}
@Dependency(\.mainQueue) var mainQueue
@ -121,19 +88,16 @@ public struct HomeReducer: Reducer {
SyncProgressReducer()
}
Scope(state: \.walletBalancesState, action: /Action.walletBalances) {
WalletBalances()
}
Reduce { state, action in
switch action {
case .onAppear:
state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations
return .merge(
.publisher {
sdkSynchronizer.stateStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
.map { $0.redacted }
.map(HomeReducer.Action.synchronizerStateChanged)
}
.cancellable(id: CancelStateId, cancelInFlight: true),
.publisher {
state.walletBalancesState.migratingDatabase = state.migratingDatabase
state.migratingDatabase = false
return .publisher {
sdkSynchronizer.eventStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
.compactMap {
@ -144,7 +108,6 @@ public struct HomeReducer: Reducer {
}
}
.cancellable(id: CancelEventId, cancelInFlight: true)
)
case .onDisappear:
return .concatenate(
@ -181,19 +144,6 @@ public struct HomeReducer: Reducer {
case .synchronizerStateChanged(let latestState):
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.data.syncStatus)
if snapshot.syncStatus != .unprepared {
state.migratingDatabase = false
}
state.synchronizerStatusSnapshot = snapshot
let accountBalance = latestState.data.accountBalance?.data
state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero)
state.shieldedWithPendingBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = accountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
switch snapshot.syncStatus {
case .error(let error):
return Effect.send(.showSynchronizerErrorAlert(error.toZcashError()))
@ -230,13 +180,10 @@ public struct HomeReducer: Reducer {
case .showSynchronizerErrorAlert:
return .none
case .debugMenuStartup:
return .none
case .syncFailed:
return .none
case .balanceBreakdown:
case .walletBalances:
return .none
case .alert(.presented(let action)):
@ -270,10 +217,9 @@ extension HomeReducer.State {
public static var initial: Self {
.init(
scanState: .initial,
shieldedBalance: .zero,
synchronizerStatusSnapshot: .initial,
syncProgressState: .initial,
transactionListState: .initial,
walletBalancesState: .initial,
walletConfig: .initial
)
}
@ -292,12 +238,9 @@ extension HomeStore {
HomeStore(
initialState: .init(
scanState: .initial,
shieldedBalance: .zero,
synchronizerStatusSnapshot: .snapshotFor(
state: .error(ZcashError.synchronizerNotPrepared)
),
syncProgressState: .initial,
transactionListState: .initial,
walletBalancesState: .initial,
walletConfig: .initial
)
) {

View File

@ -8,6 +8,7 @@ import UIComponents
import SyncProgress
import Utils
import Models
import WalletBalances
public struct HomeView: View {
let store: HomeStore
@ -21,7 +22,14 @@ public struct HomeView: View {
public var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack(spacing: 0) {
balance(viewStore)
WalletBalancesView(
store: store.scope(
state: \.walletBalancesState,
action: HomeReducer.Action.walletBalances
),
tokenName: tokenName
)
.padding(.top, 1)
if viewStore.isRestoringWallet {
SyncProgressView(
@ -60,42 +68,6 @@ public struct HomeView: View {
}
}
// MARK: - Buttons
extension HomeView {
func balance(_ viewStore: HomeViewStore) -> some View {
VStack(spacing: 0) {
Button {
viewStore.send(.balanceBreakdown)
} label: {
BalanceWithIconView(balance: viewStore.totalBalance)
}
.padding(.top, 40)
if viewStore.migratingDatabase {
Text(L10n.Home.migratingDatabases)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
.padding(.top, 10)
.padding(.bottom, 30)
} else {
AvailableBalanceView(
balance: viewStore.shieldedBalance,
tokenName: tokenName,
showIndicator: viewStore.isProcessingZeroAvailableBalance
)
#if !SECANT_DISTRIB
.accessDebugMenuWithHiddenGesture {
viewStore.send(.debugMenuStartup)
}
#endif
.padding(.top, 10)
.padding(.bottom, 30)
}
}
.foregroundColor(Asset.Colors.primary.color)
}
}
// MARK: - Previews
struct HomeView_Previews: PreviewProvider {
@ -108,15 +80,13 @@ struct HomeView_Previews: PreviewProvider {
.init(
isRestoringWallet: true,
scanState: .initial,
shieldedBalance: .zero,
synchronizerStatusSnapshot: .initial,
syncProgressState: .init(
lastKnownSyncPercentage: Float(0.43),
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
syncStatusMessage: "Syncing"
),
totalBalance: .zero,
transactionListState: .placeholder,
walletBalancesState: .initial,
walletConfig: .initial
)
) {

View File

@ -386,7 +386,7 @@ extension RootReducer {
}
return .none
case .welcome(.debugMenuStartup), .tabs(.home(.debugMenuStartup)):
case .welcome(.debugMenuStartup), .tabs(.home(.walletBalances(.debugMenuStartup))):
return .concatenate(
Effect.cancel(id: CancelId),
Effect.send(.destination(.updateDestination(.startup)))

View File

@ -153,7 +153,6 @@ public struct SendFlowConfirmationView: View {
),
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(4412323012_345),
transactionAddressInputState:
TransactionAddressTextFieldReducer.State(
textFieldState:
@ -162,7 +161,8 @@ public struct SendFlowConfirmationView: View {
text: "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h".redacted
)
),
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
) {
SendFlowReducer()

View File

@ -20,13 +20,12 @@ import UIComponents
import Models
import Generated
import BalanceFormatter
import WalletBalances
public typealias SendFlowStore = Store<SendFlowReducer.State, SendFlowReducer.Action>
public typealias SendFlowViewStore = ViewStore<SendFlowReducer.State, SendFlowReducer.Action>
public struct SendFlowReducer: Reducer {
private let SyncStatusUpdatesID = UUID()
public struct State: Equatable {
public enum Destination: Equatable {
case partialProposalError
@ -42,12 +41,10 @@ public struct SendFlowReducer: Reducer {
public var partialProposalErrorState: PartialProposalError.State
public var proposal: Proposal?
public var scanState: Scan.State
public var spendableBalance = Zatoshi.zero
public var shieldedWithPendingBalance = Zatoshi.zero
public var totalBalance = Zatoshi.zero
public var shieldedBalance: Zatoshi
public var transactionAddressInputState: TransactionAddressTextFieldReducer.State
public var transactionAmountInputState: TransactionAmountTextFieldReducer.State
public var transparentBalance: Zatoshi
public var walletBalancesState: WalletBalances.State
public var address: String {
get { transactionAddressInputState.textFieldState.text.data }
@ -96,7 +93,7 @@ public struct SendFlowReducer: Reducer {
public var isInsufficientFunds: Bool {
guard transactionAmountInputState.isValidInput else { return false }
return transactionAmountInputState.amount.data > spendableBalance.amount
return transactionAmountInputState.amount.data > shieldedBalance.amount
}
public var isMemoInputEnabled: Bool {
@ -104,19 +101,11 @@ public struct SendFlowReducer: Reducer {
}
public var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: spendableBalance.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
Zatoshi.from(decimal: shieldedBalance.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
}
public var spendableBalanceString: String {
spendableBalance.decimalString(formatter: NumberFormatter.zashiBalanceFormatter)
}
public var isProcessingZeroAvailableBalance: Bool {
if spendableBalance.amount == 0 && transparentBalance.amount > 0 {
return false
}
return totalBalance.amount != spendableBalance.amount && spendableBalance.amount == 0
shieldedBalance.decimalString(formatter: NumberFormatter.zashiBalanceFormatter)
}
public init(
@ -126,12 +115,10 @@ public struct SendFlowReducer: Reducer {
memoState: MessageEditorReducer.State,
partialProposalErrorState: PartialProposalError.State,
scanState: Scan.State,
spendableBalance: Zatoshi = .zero,
shieldedWithPendingBalance: Zatoshi = .zero,
totalBalance: Zatoshi = .zero,
shieldedBalance: Zatoshi = .zero,
transactionAddressInputState: TransactionAddressTextFieldReducer.State,
transactionAmountInputState: TransactionAmountTextFieldReducer.State,
transparentBalance: Zatoshi = .zero
walletBalancesState: WalletBalances.State
) {
self.addMemoState = addMemoState
self.destination = destination
@ -139,11 +126,10 @@ public struct SendFlowReducer: Reducer {
self.memoState = memoState
self.partialProposalErrorState = partialProposalErrorState
self.scanState = scanState
self.spendableBalance = spendableBalance
self.totalBalance = totalBalance
self.shieldedBalance = shieldedBalance
self.transactionAddressInputState = transactionAddressInputState
self.transactionAmountInputState = transactionAmountInputState
self.transparentBalance = transparentBalance
self.walletBalancesState = walletBalancesState
}
}
@ -152,7 +138,6 @@ public struct SendFlowReducer: Reducer {
case goBackPressed
case memo(MessageEditorReducer.Action)
case onAppear
case onDisappear
case partialProposalError(PartialProposalError.Action)
case proposal(Proposal)
case reviewPressed
@ -161,10 +146,10 @@ public struct SendFlowReducer: Reducer {
case sendDone
case sendFailed(ZcashError)
case sendPartial([String], [String])
case synchronizerStateChanged(RedactableSynchronizerState)
case transactionAddressInput(TransactionAddressTextFieldReducer.Action)
case transactionAmountInput(TransactionAmountTextFieldReducer.Action)
case updateDestination(SendFlowReducer.State.Destination?)
case walletBalances(WalletBalances.Action)
}
@Dependency(\.audioServices) var audioServices
@ -198,6 +183,10 @@ public struct SendFlowReducer: Reducer {
PartialProposalError()
}
Scope(state: \.walletBalancesState, action: /Action.walletBalances) {
WalletBalances()
}
Reduce { state, action in
switch action {
case .alert(.presented(let action)):
@ -212,16 +201,7 @@ public struct SendFlowReducer: Reducer {
case .onAppear:
state.memoState.charLimit = zcashSDKEnvironment.memoCharLimit
return Effect.publisher {
sdkSynchronizer.stateStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
.map{ $0.redacted }
.map(SendFlowReducer.Action.synchronizerStateChanged)
}
.cancellable(id: SyncStatusUpdatesID, cancelInFlight: true)
case .onDisappear:
return .cancel(id: SyncStatusUpdatesID)
return .none
case .goBackPressed:
state.destination = nil
@ -328,16 +308,6 @@ public struct SendFlowReducer: Reducer {
case .transactionAddressInput:
return .none
case .synchronizerStateChanged(let latestState):
let latestAccountBalance = latestState.data.accountBalance?.data
state.spendableBalance = (latestAccountBalance?.saplingBalance.spendableValue ?? .zero) + (latestAccountBalance?.orchardBalance.spendableValue ?? .zero)
state.shieldedWithPendingBalance = (latestAccountBalance?.saplingBalance.total() ?? .zero) + (latestAccountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = latestAccountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
state.transactionAmountInputState.maxValue = state.spendableBalance.amount.redacted
return .none
case .memo:
return .none
@ -359,6 +329,13 @@ public struct SendFlowReducer: Reducer {
case .scan:
return .none
case .walletBalances(.balancesUpdated):
state.shieldedBalance = state.walletBalancesState.shieldedBalance
return .none
case .walletBalances:
return .none
}
}
}
@ -448,7 +425,8 @@ extension SendFlowReducer.State {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
}
}

View File

@ -12,6 +12,7 @@ import Scan
import UIComponents
import BalanceFormatter
import PartialProposalError
import WalletBalances
public struct SendFlowView: View {
private enum InputID: Hashable {
@ -36,14 +37,12 @@ public struct SendFlowView: View {
ScrollView {
ScrollViewReader { value in
VStack(alignment: .center) {
BalanceWithIconView(balance: viewStore.totalBalance)
.padding(.top, 40)
.padding(.bottom, 5)
AvailableBalanceView(
balance: viewStore.spendableBalance,
tokenName: tokenName,
showIndicator: viewStore.isProcessingZeroAvailableBalance
WalletBalancesView(
store: store.scope(
state: \.walletBalancesState,
action: SendFlowReducer.Action.walletBalances
),
tokenName: tokenName
)
VStack(alignment: .leading) {
@ -98,7 +97,6 @@ public struct SendFlowView: View {
}
.padding(.bottom, 20)
}
.padding(.top, 40)
MessageEditor(store: store.memoStore())
.frame(height: 175)
@ -143,7 +141,6 @@ public struct SendFlowView: View {
}
}
.onAppear { viewStore.send(.onAppear) }
.onDisappear { viewStore.send(.onDisappear) }
.applyScreenBackground()
.navigationLinkEmpty(
isActive: viewStore.bindingForScanQR,
@ -188,7 +185,8 @@ public struct SendFlowView: View {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
) {
SendFlowReducer()

View File

@ -125,7 +125,8 @@ public struct TabsReducer: Reducer {
case .balanceBreakdown:
return .none
case .home(.balanceBreakdown):
case .home(.walletBalances(.availableBalanceTapped)),
.send(.walletBalances(.availableBalanceTapped)):
state.selectedTab = .balances
return .none

View File

@ -0,0 +1,119 @@
//
// WalletBalancesStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 04-02-2024
//
import Foundation
import ComposableArchitecture
import Models
import SDKSynchronizer
import Utils
import ZcashLightClientKit
@Reducer
public struct WalletBalances {
private let CancelStateId = UUID()
@ObservableState
public struct State: Equatable {
public var isAvailableBalanceTappable = true
public var migratingDatabase = false
public var shieldedBalance: Zatoshi
public var shieldedWithPendingBalance: Zatoshi
public var totalBalance: Zatoshi
public var transparentBalance: Zatoshi
public var isProcessingZeroAvailableBalance: Bool {
if shieldedBalance.amount == 0 && transparentBalance.amount > 0 {
return false
}
return totalBalance.amount != shieldedBalance.amount && shieldedBalance.amount == 0
}
public init(
isAvailableBalanceTappable: Bool = true,
migratingDatabase: Bool = false,
shieldedBalance: Zatoshi = .zero,
shieldedWithPendingBalance: Zatoshi = .zero,
totalBalance: Zatoshi = .zero,
transparentBalance: Zatoshi = .zero
) {
self.isAvailableBalanceTappable = isAvailableBalanceTappable
self.migratingDatabase = migratingDatabase
self.shieldedBalance = shieldedBalance
self.shieldedWithPendingBalance = shieldedWithPendingBalance
self.totalBalance = totalBalance
self.transparentBalance = transparentBalance
}
}
public enum Action: Equatable {
case availableBalanceTapped
case balancesUpdated
case debugMenuStartup
case onAppear
case onDisappear
case synchronizerStateChanged(RedactableSynchronizerState)
}
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
public init() { }
public var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .onAppear:
let accountBalance = sdkSynchronizer.latestState().accountBalance
state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero)
state.shieldedWithPendingBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = accountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
return .merge(
.send(.balancesUpdated),
.publisher {
sdkSynchronizer.stateStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
.map { $0.redacted }
.map(WalletBalances.Action.synchronizerStateChanged)
}
.cancellable(id: CancelStateId, cancelInFlight: true)
)
case .onDisappear:
return .cancel(id: CancelStateId)
case .availableBalanceTapped:
return .none
case .balancesUpdated:
return .none
case .debugMenuStartup:
return .none
case .synchronizerStateChanged(let latestState):
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.data.syncStatus)
if snapshot.syncStatus != .unprepared {
state.migratingDatabase = false
}
let accountBalance = latestState.data.accountBalance?.data
state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero)
state.shieldedWithPendingBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = accountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
return .send(.balancesUpdated)
}
}
}
}

View File

@ -0,0 +1,99 @@
//
// WalletBalancesView.swift
// secant-testnet
//
// Created by Lukáš Korba on 04-02-2024
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct WalletBalancesView: View {
@Perception.Bindable var store: StoreOf<WalletBalances>
let tokenName: String
let underlinedAvailableBalance: Bool
public init(
store: StoreOf<WalletBalances>,
tokenName: String,
underlinedAvailableBalance: Bool = true
) {
self.store = store
self.tokenName = tokenName
self.underlinedAvailableBalance = underlinedAvailableBalance
}
public var body: some View {
WithPerceptionTracking {
VStack(spacing: 0) {
BalanceWithIconView(balance: store.totalBalance)
.padding(.top, 40)
#if !SECANT_DISTRIB
.accessDebugMenuWithHiddenGesture {
store.send(.debugMenuStartup)
}
#endif
if store.migratingDatabase {
Text(L10n.Home.migratingDatabases)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
.padding(.top, 10)
.padding(.bottom, 30)
} else {
if underlinedAvailableBalance {
Button {
store.send(.availableBalanceTapped)
} label: {
AvailableBalanceView(
balance: store.shieldedBalance,
tokenName: tokenName,
showIndicator: store.isProcessingZeroAvailableBalance,
underlined: underlinedAvailableBalance
)
.padding(.top, 10)
.padding(.bottom, 30)
}
} else {
AvailableBalanceView(
balance: store.shieldedBalance,
tokenName: tokenName,
showIndicator: store.isProcessingZeroAvailableBalance,
underlined: underlinedAvailableBalance
)
.padding(.top, 10)
.padding(.bottom, 30)
}
}
}
.foregroundColor(Asset.Colors.primary.color)
.onAppear { store.send(.onAppear) }
.onDisappear { store.send(.onDisappear) }
}
}
}
// MARK: - Previews
#Preview {
WalletBalancesView(store: WalletBalances.initial, tokenName: "ZEC")
}
// MARK: - Store
extension WalletBalances {
public static var initial = StoreOf<WalletBalances>(
initialState: .initial
) {
WalletBalances()
}
}
// MARK: - Placeholders
extension WalletBalances.State {
public static let initial = WalletBalances.State(
shieldedBalance: .zero
)
}

View File

@ -37,12 +37,8 @@ public enum L10n {
}
}
public enum Balance {
/// Available balance %@ %@
public static func available(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "balance.available", String(describing: p1), String(describing: p2), fallback: "Available balance %@ %@")
}
/// Available Balance
public static let availableTitle = L10n.tr("Localizable", "balance.availableTitle", fallback: "Available Balance")
/// Available Balance:
public static let availableTitle = L10n.tr("Localizable", "balance.availableTitle", fallback: "Available Balance:")
}
public enum Balances {
/// Change pending

View File

@ -241,8 +241,7 @@ Sharing this private data is irrevocable — once you have shared this private d
"general.done" = "Done";
"general.save" = "Save";
"general.restoringWallet" = "[RESTORING YOUR WALLET…]";
"balance.available" = "Available balance %@ %@";
"balance.availableTitle" = "Available Balance";
"balance.availableTitle" = "Available Balance:";
"qrCodeFor" = "QR Code for %@";
"general.dateNotAvailable" = "date not available";
"general.tapToCopy" = "Tap to copy";

View File

@ -13,22 +13,30 @@ public struct AvailableBalanceView: View {
let balance: Zatoshi
let tokenName: String
let showIndicator: Bool
let underlined: Bool
public init(
balance: Zatoshi,
tokenName: String,
showIndicator: Bool = false
showIndicator: Bool = false,
underlined: Bool = true
) {
self.balance = balance
self.tokenName = tokenName
self.showIndicator = showIndicator
self.underlined = underlined
}
public var body: some View {
HStack(spacing: 3) {
Text(L10n.Balance.availableTitle)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
.underline()
if underlined {
Text(L10n.Balance.availableTitle)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
.underline()
} else {
Text(L10n.Balance.availableTitle)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
}
if showIndicator {
ProgressView()

View File

@ -14,35 +14,11 @@ import Generated
import BalanceBreakdown
import Models
import ZcashSDKEnvironment
import WalletBalances
@testable import secant_testnet
@MainActor
class BalanceBreakdownTests: XCTestCase {
func testOnAppear() async throws {
let store = TestStore(
initialState: .placeholder
) {
BalanceBreakdownReducer()
}
store.dependencies.sdkSynchronizer = .mocked()
store.dependencies.mainQueue = .immediate
store.dependencies.numberFormatter = .noOp
await store.send(.onAppear) { state in
state.autoShieldingThreshold = ZcashSDKEnvironment.liveValue.shieldingThreshold
}
// expected side effects as a result of .onAppear registration
await store.receive(.synchronizerStateChanged(SynchronizerState.zero.redacted))
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancels the observer of the synchronizer status change.
await store.send(.onDisappear)
await store.finish()
}
func testShieldFundsFails() async throws {
let store = TestStore(
initialState: .placeholder
@ -101,10 +77,9 @@ class BalanceBreakdownTests: XCTestCase {
isShieldingFunds: false,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: Zatoshi(1_000_000)
transparentBalance: Zatoshi(1_000_000),
walletBalancesState: .initial
)
) {
BalanceBreakdownReducer()
@ -123,10 +98,9 @@ class BalanceBreakdownTests: XCTestCase {
isShieldingFunds: true,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: .zero,
syncProgressState: .initial,
totalBalance: .zero,
transparentBalance: Zatoshi(1_000_000)
transparentBalance: Zatoshi(1_000_000),
walletBalancesState: .initial
)
) {
BalanceBreakdownReducer()

View File

@ -16,27 +16,6 @@ import Home
@testable import ZcashLightClientKit
class HomeTests: XCTestCase {
func testSendButtonIsDisabledWhenSyncing() {
let mockSnapshot = SyncStatusSnapshot.init(
.syncing(0.7)
)
let store = TestStore(
initialState: .init(
scanState: .initial,
shieldedBalance: .zero,
synchronizerStatusSnapshot: mockSnapshot,
syncProgressState: .initial,
transactionListState: .initial,
walletConfig: .initial
)
) {
HomeReducer()
}
XCTAssertTrue(store.state.isSendButtonDisabled)
}
/// The .onAppear action is important to register for the synchronizer state updates.
/// The integration tests make sure registrations and side effects are properly implemented.
@MainActor func testOnAppear() async throws {
@ -52,17 +31,12 @@ class HomeTests: XCTestCase {
store.dependencies.reviewRequest = .noOp
await store.send(.onAppear) { state in
state.requiredTransactionConfirmations = 10
state.migratingDatabase = false
state.walletBalancesState.migratingDatabase = true
}
var syncState: SynchronizerState = .zero
syncState.syncStatus = .unprepared
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
// expected side effects as a result of .onAppear registration
await store.receive(.synchronizerStateChanged(SynchronizerState.zero.redacted)) { state in
state.synchronizerStatusSnapshot = snapshot
}
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancels the observer of the synchronizer status change.
@ -73,9 +47,6 @@ class HomeTests: XCTestCase {
@MainActor func testSynchronizerErrorBringsUpAlert() async {
let testError = ZcashError.synchronizerNotPrepared
let errorSnapshot = SyncStatusSnapshot.snapshotFor(
state: .error(testError)
)
var state = SynchronizerState.zero
state.syncStatus = .error(testError)
@ -86,10 +57,7 @@ class HomeTests: XCTestCase {
HomeReducer()
}
await store.send(.synchronizerStateChanged(state.redacted)) { state in
state.synchronizerStatusSnapshot = errorSnapshot
state.migratingDatabase = false
}
await store.send(.synchronizerStateChanged(state.redacted))
await store.receive(.showSynchronizerErrorAlert(testError))

View File

@ -44,12 +44,8 @@ final class ReviewRequestTests: XCTestCase {
var syncState: SynchronizerState = .zero
syncState.syncStatus = .upToDate
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
await store.send(.synchronizerStateChanged(syncState.redacted)) { state in
state.synchronizerStatusSnapshot = snapshot
state.migratingDatabase = false
}
await store.send(.synchronizerStateChanged(syncState.redacted))
let storedDate = userDefaultsClient.objectForKey(ReviewRequestClient.Constants.latestSyncKey) as? TimeInterval
XCTAssertEqual(now.timeIntervalSince1970, storedDate, "Review Request: stored date doesn't match the input.")

View File

@ -14,6 +14,7 @@ import Models
import WalletStorage
import SendFlow
import UIComponents
import WalletBalances
@testable import secant_testnet
// swiftlint:disable type_body_length
@ -260,9 +261,10 @@ class SendTests: XCTestCase {
memoState: .initial,
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(100_000),
shieldedBalance: Zatoshi(100_000),
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(
@ -295,9 +297,9 @@ class SendTests: XCTestCase {
memoState: .initial,
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(90_000),
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(
@ -352,9 +354,10 @@ class SendTests: XCTestCase {
memoState: .initial,
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(100_000),
shieldedBalance: Zatoshi(100_000),
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(
@ -414,7 +417,8 @@ class SendTests: XCTestCase {
validationType: .customFloatingPoint(usNumberFormatter),
text: "0.00501301".redacted
)
)
),
walletBalancesState: .initial
)
let store = TestStore(
@ -459,7 +463,8 @@ class SendTests: XCTestCase {
validationType: .floatingPoint,
text: "0.00501301".redacted
)
)
),
walletBalancesState: .initial
)
let store = TestStore(
@ -504,7 +509,8 @@ class SendTests: XCTestCase {
validationType: .floatingPoint,
text: "0.00501301".redacted
)
)
),
walletBalancesState: .initial
)
let store = TestStore(
@ -537,7 +543,6 @@ class SendTests: XCTestCase {
memoState: MessageEditorReducer.State(charLimit: 3),
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(1),
transactionAddressInputState:
TransactionAddressTextFieldReducer.State(
isValidAddress: true,
@ -557,7 +562,8 @@ class SendTests: XCTestCase {
validationType: .floatingPoint,
text: "0.0.0501301".redacted
)
)
),
walletBalancesState: .initial
)
let store = TestStore(
@ -595,7 +601,8 @@ class SendTests: XCTestCase {
validationType: .floatingPoint,
text: "0.0.0501301".redacted
)
)
),
walletBalancesState: .initial
)
let store = TestStore(
@ -610,10 +617,6 @@ class SendTests: XCTestCase {
await store.send(.onAppear) { state in
state.memoState.charLimit = 512
}
// .onAppear action starts long living cancelable action .synchronizerStateChanged
// .onDisappear cancels it, must have for the test to pass
await store.send(.onDisappear)
}
func testScannedAddress() async throws {
@ -623,7 +626,8 @@ class SendTests: XCTestCase {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(
@ -654,7 +658,8 @@ class SendTests: XCTestCase {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
sendState.address = "tmViyFacKkncJ6zhEqs8rjqNPkGqXsMV4uq"
@ -690,7 +695,8 @@ class SendTests: XCTestCase {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(
@ -714,7 +720,8 @@ class SendTests: XCTestCase {
partialProposalErrorState: .initial,
scanState: .initial,
transactionAddressInputState: .initial,
transactionAmountInputState: .initial
transactionAmountInputState: .initial,
walletBalancesState: .initial
)
let store = TestStore(

View File

@ -22,16 +22,15 @@ class BalanceBreakdownSnapshotTests: XCTestCase {
isShieldingFunds: false,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: Zatoshi(123_000_000_000),
syncProgressState: .initial,
totalBalance: Zatoshi(123_000_000_000),
transparentBalance: Zatoshi(850_000_000)
walletBalancesState: .initial
)
) {
BalanceBreakdownReducer()
.dependency(\.sdkSynchronizer, .noOp)
.dependency(\.mainQueue, .immediate)
.dependency(\.restoreWalletStorage, .noOp)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
}
addAttachments(BalanceBreakdownView(store: store, tokenName: "ZEC"))
@ -46,16 +45,15 @@ class BalanceBreakdownSnapshotTests: XCTestCase {
isHintBoxVisible: true,
partialProposalErrorState: .initial,
pendingTransactions: .zero,
shieldedBalance: Zatoshi(123_000_000_000),
syncProgressState: .initial,
totalBalance: Zatoshi(123_000_000_000),
transparentBalance: Zatoshi(850_000_000)
walletBalancesState: .initial
)
) {
BalanceBreakdownReducer()
.dependency(\.sdkSynchronizer, .noOp)
.dependency(\.mainQueue, .immediate)
.dependency(\.restoreWalletStorage, .noOp)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
}
addAttachments(BalanceBreakdownView(store: store, tokenName: "ZEC"))

View File

@ -38,10 +38,9 @@ class HomeSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
scanState: .initial,
shieldedBalance: Zatoshi(12_345_000),
synchronizerStatusSnapshot: .initial,
syncProgressState: .initial,
transactionListState: .init(transactionList: IdentifiedArrayOf(uniqueElements: transactionList)),
walletBalancesState: .initial,
walletConfig: .initial
)
) {

View File

@ -41,6 +41,7 @@ class SendSnapshotTests: XCTestCase {
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())
.dependency(\.sdkSynchronizer, .mock)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
}
addAttachments(SendFlowView(store: store, tokenName: "ZEC"))
@ -57,7 +58,6 @@ class SendSnapshotTests: XCTestCase {
),
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(4412323012_345),
transactionAddressInputState:
TransactionAddressTextFieldReducer.State(
textFieldState:
@ -75,7 +75,8 @@ class SendSnapshotTests: XCTestCase {
validationType: nil,
text: "0.9153234".redacted
)
)
),
walletBalancesState: .initial
)
) {
SendFlowReducer()
@ -84,6 +85,7 @@ class SendSnapshotTests: XCTestCase {
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())
.dependency(\.sdkSynchronizer, .mock)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
}
addAttachments(SendFlowConfirmationView(store: store, tokenName: "ZEC"))
@ -97,7 +99,6 @@ class SendSnapshotTests: XCTestCase {
memoState: MessageEditorReducer.State(),
partialProposalErrorState: .initial,
scanState: .initial,
spendableBalance: Zatoshi(4412323012_345),
transactionAddressInputState:
TransactionAddressTextFieldReducer.State(
textFieldState:
@ -115,7 +116,8 @@ class SendSnapshotTests: XCTestCase {
validationType: nil,
text: "0.9153234".redacted
)
)
),
walletBalancesState: .initial
)
) {
SendFlowReducer()
@ -124,6 +126,7 @@ class SendSnapshotTests: XCTestCase {
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())
.dependency(\.sdkSynchronizer, .mock)
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
}
addAttachments(SendFlowConfirmationView(store: store, tokenName: "ZEC"))

View File

@ -24,7 +24,7 @@ class TabsTests: XCTestCase {
TabsReducer()
}
await store.send(.home(.balanceBreakdown)) { state in
await store.send(.home(.walletBalances(.availableBalanceTapped))) { state in
state.selectedTab = .balances
}
}
@ -170,7 +170,7 @@ class TabsTests: XCTestCase {
func testShieldFundsSucceed() async throws {
var placeholderState = TabsReducer.State.initial
placeholderState.selectedTab = .send
placeholderState.balanceBreakdownState.transparentBalance = Zatoshi(10_000)
placeholderState.balanceBreakdownState.walletBalancesState.transparentBalance = Zatoshi(10_000)
let store = TestStore(
initialState: placeholderState
@ -193,7 +193,7 @@ class TabsTests: XCTestCase {
}
await store.receive(.balanceBreakdown(.shieldFundsSuccess)) { state in
state.balanceBreakdownState.transparentBalance = .zero
state.balanceBreakdownState.walletBalancesState.transparentBalance = .zero
state.balanceBreakdownState.isShieldingFunds = false
}