[#467] Migrate TransactionAmountTextField to ReducerProtocol (#483)

- TransactionAmountTextField migrated to ReducerProtocol
- unit tests fixed
This commit is contained in:
Lukas Korba 2022-11-08 09:36:03 +01:00 committed by GitHub
parent 1bba7d0d16
commit 046681efff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 189 deletions

View File

@ -9,6 +9,7 @@ import ComposableArchitecture
private enum NumberFormatterKey: DependencyKey {
static let liveValue = WrappedNumberFormatter.live()
static let testValue = WrappedNumberFormatter.live()
}
extension DependencyValues {

View File

@ -18,6 +18,11 @@ typealias AnyTransactionAddressTextFieldReducer = AnyReducer<
TransactionAddressTextFieldReducer.Action,
SendFlowEnvironment
>
typealias AnyTransactionAmountTextFieldReducer = AnyReducer<
TransactionAmountTextFieldReducer.State,
TransactionAmountTextFieldReducer.Action,
SendFlowEnvironment
>
typealias AnyMultiLineTextFieldReducer = AnyReducer<MultiLineTextFieldReducer.State, MultiLineTextFieldReducer.Action, SendFlowEnvironment>
typealias AnyCheckCircleReducer = AnyReducer<Bool, CheckCircleReducer.Action, SendFlowEnvironment>
@ -38,7 +43,7 @@ struct SendFlowState: Equatable {
var route: Route?
var shieldedBalance = WalletBalance.zero
var transactionAddressInputState: TransactionAddressTextFieldReducer.State
var transactionAmountInputState: TransactionAmountTextFieldState
var transactionAmountInputState: TransactionAmountTextFieldReducer.State
var address: String {
get { transactionAddressInputState.textFieldState.text }
@ -92,7 +97,7 @@ enum SendFlowAction: Equatable {
case sendTransactionResult(Result<TransactionState, NSError>)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case transactionAddressInput(TransactionAddressTextFieldReducer.Action)
case transactionAmountInput(TransactionAmountTextFieldAction)
case transactionAmountInput(TransactionAmountTextFieldReducer.Action)
case updateRoute(SendFlowState.Route?)
}
@ -242,16 +247,15 @@ extension SendFlowReducer {
environment: { $0 }
)
private static let transactionAmountInputReducer: SendFlowReducer = TransactionAmountTextFieldReducer.default.pullback(
private static let transactionAmountInputReducer: SendFlowReducer = AnyTransactionAmountTextFieldReducer { _ in
TransactionAmountTextFieldReducer()
}
.pullback(
state: \SendFlowState.transactionAmountInputState,
action: /SendFlowAction.transactionAmountInput,
environment: { environment in
TransactionAmountTextFieldEnvironment(
numberFormatter: environment.numberFormatter
)
}
environment: { $0 }
)
private static let memoReducer: SendFlowReducer = AnyMultiLineTextFieldReducer { _ in
MultiLineTextFieldReducer()
}

View File

@ -19,7 +19,7 @@ struct TransactionAmountTextField: View {
title: "How much ZEC would you like to send?",
store: store.scope(
state: \.textFieldState,
action: TransactionAmountTextFieldAction.textField
action: TransactionAmountTextFieldReducer.Action.textField
),
titleAccessoryView: {
Button(
@ -44,7 +44,7 @@ struct TransactionAmountTextField: View {
CurrencySelectionView(
store: store.scope(
state: \.currencySelectionState,
action: TransactionAmountTextFieldAction.currencySelection
action: TransactionAmountTextFieldReducer.Action.currencySelection
)
)
}
@ -65,10 +65,7 @@ struct TransactionAmountTextField_Previews: PreviewProvider {
text: ""
)
),
reducer: .default,
environment: .init(
numberFormatter: .live()
)
reducer: TransactionAmountTextFieldReducer()
)
)
.preferredColorScheme(.dark)

View File

@ -9,130 +9,105 @@ import ComposableArchitecture
import ZcashLightClientKit
import Foundation
typealias TransactionAmountTextFieldReducer = Reducer<
TransactionAmountTextFieldState,
TransactionAmountTextFieldAction,
TransactionAmountTextFieldEnvironment
>
typealias TransactionAmountTextFieldStore = Store<TransactionAmountTextFieldReducer.State, TransactionAmountTextFieldReducer.Action>
typealias TransactionAmountTextFieldStore = Store<TransactionAmountTextFieldState, TransactionAmountTextFieldAction>
struct TransactionAmountTextFieldReducer: ReducerProtocol {
struct State: Equatable {
var amount: Int64 = 0
var currencySelectionState: CurrencySelectionReducer.State
var maxValue: Int64 = 0
var textFieldState: TCATextFieldReducer.State
// TODO [#311]: - Get the ZEC price from the SDK, https://github.com/zcash/secant-ios-wallet/issues/311
var zecPrice = Decimal(140.0)
typealias AnyTCATextFieldReducerAmount = AnyReducer<TCATextFieldReducer.State, TCATextFieldReducer.Action, TransactionAmountTextFieldEnvironment>
typealias AnyCurrencySelectionReducer = AnyReducer<CurrencySelectionReducer.State, CurrencySelectionReducer.Action, TransactionAmountTextFieldEnvironment>
struct TransactionAmountTextFieldState: Equatable {
var amount: Int64 = 0
var currencySelectionState: CurrencySelectionReducer.State
var maxValue: Int64 = 0
var textFieldState: TCATextFieldReducer.State
// TODO [#311]: - Get the ZEC price from the SDK, https://github.com/zcash/secant-ios-wallet/issues/311
var zecPrice = Decimal(140.0)
var isMax: Bool {
return amount == maxValue
}
}
enum TransactionAmountTextFieldAction: Equatable {
case clearValue
case currencySelection(CurrencySelectionReducer.Action)
case setMax
case textField(TCATextFieldReducer.Action)
case updateAmount
}
struct TransactionAmountTextFieldEnvironment {
let numberFormatter: WrappedNumberFormatter
}
extension TransactionAmountTextFieldReducer {
static let `default` = TransactionAmountTextFieldReducer.combine(
[
textFieldReducer,
currencySelectionReducer,
amountTextFieldReducer
]
)
static let amountTextFieldReducer = TransactionAmountTextFieldReducer { state, action, environment in
switch action {
case .setMax:
let maxValueAsZec = Decimal(state.maxValue) / Decimal(Zatoshi.Constants.oneZecInZatoshi)
let currencyType = state.currencySelectionState.currencyType
let maxCurrencyConvertedValue: NSDecimalNumber = currencyType == .zec ?
NSDecimalNumber(decimal: maxValueAsZec).roundedZec :
NSDecimalNumber(decimal: maxValueAsZec * state.zecPrice).roundedZec
let decimalString = environment.numberFormatter.string(maxCurrencyConvertedValue) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
case .clearValue:
state.textFieldState.text = ""
return .none
case .textField(.set(let amount)):
return Effect(value: .updateAmount)
case .updateAmount:
guard var number = environment.numberFormatter.number(state.textFieldState.text) else {
state.amount = 0
return .none
}
switch state.currencySelectionState.currencyType {
case .zec:
state.amount = NSDecimalNumber(decimal: number.decimalValue * Decimal(Zatoshi.Constants.oneZecInZatoshi)).roundedZec.int64Value
case .usd:
let decimal = (number.decimalValue / state.zecPrice) * Decimal(Zatoshi.Constants.oneZecInZatoshi)
state.amount = NSDecimalNumber(decimal: decimal).roundedZec.int64Value
}
return .none
case .currencySelection:
guard let number = environment.numberFormatter.number(state.textFieldState.text) else {
state.amount = 0
return .none
}
let currencyType = state.currencySelectionState.currencyType
let newValue = currencyType == .zec ?
number.decimalValue / state.zecPrice :
number.decimalValue * state.zecPrice
let decimalString = environment.numberFormatter.string(NSDecimalNumber(decimal: newValue)) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
var isMax: Bool {
return amount == maxValue
}
}
private static let textFieldReducer: TransactionAmountTextFieldReducer = AnyTCATextFieldReducerAmount { _ in
TCATextFieldReducer()
enum Action: Equatable {
case clearValue
case currencySelection(CurrencySelectionReducer.Action)
case setMax
case textField(TCATextFieldReducer.Action)
case updateAmount
}
.pullback(
state: \TransactionAmountTextFieldState.textFieldState,
action: /TransactionAmountTextFieldAction.textField,
environment: { $0 }
)
private static let currencySelectionReducer: TransactionAmountTextFieldReducer = AnyCurrencySelectionReducer { _ in
CurrencySelectionReducer()
@Dependency(\.numberFormatter) var numberFormatter
var body: some ReducerProtocol<State, Action> {
Scope(state: \.currencySelectionState, action: /Action.currencySelection) {
CurrencySelectionReducer()
}
Scope(state: \.textFieldState, action: /Action.textField) {
TCATextFieldReducer()
}
Reduce { state, action in
switch action {
case .setMax:
let maxValueAsZec = Decimal(state.maxValue) / Decimal(Zatoshi.Constants.oneZecInZatoshi)
let currencyType = state.currencySelectionState.currencyType
let maxCurrencyConvertedValue: NSDecimalNumber = currencyType == .zec ?
NSDecimalNumber(decimal: maxValueAsZec).roundedZec :
NSDecimalNumber(decimal: maxValueAsZec * state.zecPrice).roundedZec
let decimalString = numberFormatter.string(maxCurrencyConvertedValue) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
case .clearValue:
state.textFieldState.text = ""
return .none
case .textField(.set):
return Effect(value: .updateAmount)
case .updateAmount:
guard let number = numberFormatter.number(state.textFieldState.text) else {
state.amount = 0
return .none
}
switch state.currencySelectionState.currencyType {
case .zec:
state.amount = NSDecimalNumber(decimal: number.decimalValue * Decimal(Zatoshi.Constants.oneZecInZatoshi)).roundedZec.int64Value
case .usd:
let decimal = (number.decimalValue / state.zecPrice) * Decimal(Zatoshi.Constants.oneZecInZatoshi)
state.amount = NSDecimalNumber(decimal: decimal).roundedZec.int64Value
}
return .none
case .currencySelection:
guard let number = numberFormatter.number(state.textFieldState.text) else {
state.amount = 0
return .none
}
let currencyType = state.currencySelectionState.currencyType
let newValue = currencyType == .zec ?
number.decimalValue / state.zecPrice :
number.decimalValue * state.zecPrice
let decimalString = numberFormatter.string(NSDecimalNumber(decimal: newValue)) ?? ""
state.textFieldState.text = "\(decimalString)"
return Effect(value: .updateAmount)
}
}
}
.pullback(
state: \TransactionAmountTextFieldState.currencySelectionState,
action: /TransactionAmountTextFieldAction.currencySelection,
environment: { $0 }
)
}
extension TransactionAmountTextFieldState {
static let placeholder = TransactionAmountTextFieldState(
// MARK: - Placeholders
extension TransactionAmountTextFieldReducer.State {
static let placeholder = TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState: .placeholder
)
static let amount = TransactionAmountTextFieldState(
static let amount = TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState: .amount
)
@ -141,10 +116,6 @@ extension TransactionAmountTextFieldState {
extension TransactionAmountTextFieldStore {
static let placeholder = TransactionAmountTextFieldStore(
initialState: .placeholder,
reducer: .default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live()
)
reducer: TransactionAmountTextFieldReducer()
)
}

View File

@ -315,7 +315,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_300,
textFieldState:
@ -395,7 +395,7 @@ class SendTests: XCTestCase {
route: nil,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState:
TCATextFieldReducer.State(
@ -428,7 +428,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
amount: 501_301,
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_302,
@ -477,7 +477,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_300,
textFieldState:
@ -525,7 +525,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_302,
textFieldState:
@ -573,7 +573,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_302,
textFieldState:
@ -630,7 +630,7 @@ class SendTests: XCTestCase {
)
),
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
amount: 100,
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_302,
@ -675,7 +675,7 @@ class SendTests: XCTestCase {
memoState: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_302,
textFieldState:

View File

@ -24,7 +24,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testMaxValue() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_301,
textFieldState:
@ -33,11 +33,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
text: "0.002"
)
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.setMax) { state in
@ -52,7 +49,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testClearValue() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
maxValue: 501_301,
textFieldState:
@ -61,11 +58,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
text: "0.002"
)
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live()
)
reducer: TransactionAmountTextFieldReducer()
)
store.send(.clearValue) { state in
@ -77,7 +70,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testZec_to_UsdConvertedAmount() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .zec
@ -89,11 +82,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.currencySelection(.swapCurrencyType)) { state in
@ -109,7 +99,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testBigZecAmount_to_UsdConvertedAmount() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .zec
@ -121,11 +111,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.currencySelection(.swapCurrencyType)) { state in
@ -141,7 +128,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testUsd_to_ZecConvertedAmount() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .usd
@ -153,11 +140,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.currencySelection(.swapCurrencyType)) { state in
@ -173,7 +157,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testIfAmountIsMax() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .usd
@ -186,11 +170,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.textField(.set("1 000"))) { state in
@ -215,7 +196,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testMaxZecValue() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .zec
@ -228,11 +209,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.setMax) { state in
@ -247,7 +225,7 @@ class TransactionAmountTextFieldTests: XCTestCase {
func testMaxUsdValue() throws {
let store = TestStore(
initialState:
TransactionAmountTextFieldState(
TransactionAmountTextFieldReducer.State(
currencySelectionState:
CurrencySelectionReducer.State(
currencyType: .usd
@ -260,11 +238,8 @@ class TransactionAmountTextFieldTests: XCTestCase {
),
zecPrice: 1000.0
),
reducer: TransactionAmountTextFieldReducer.default,
environment:
TransactionAmountTextFieldEnvironment(
numberFormatter: .live(numberFormatter: usNumberFormatter)
)
reducer: TransactionAmountTextFieldReducer()
.dependency(\.numberFormatter, .live(numberFormatter: usNumberFormatter))
)
store.send(.setMax) { state in

View File

@ -31,7 +31,7 @@ class TransactionConfirmationSnapshotTests: XCTestCase {
text: "ztestmockeddestinationaddress"
)
)
state.transactionAmountInputState = TransactionAmountTextFieldState(
state.transactionAmountInputState = TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState: TCATextFieldReducer.State(
validationType: nil,
@ -68,7 +68,7 @@ class TransactionConfirmationSnapshotTests: XCTestCase {
text: "ztestmockeddestinationaddress"
)
)
state.transactionAmountInputState = TransactionAmountTextFieldState(
state.transactionAmountInputState = TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState: TCATextFieldReducer.State(
validationType: nil,

View File

@ -31,7 +31,7 @@ class TransactionSendingTests: XCTestCase {
text: "ztestmockeddestinationaddress"
)
)
state.transactionAmountInputState = TransactionAmountTextFieldState(
state.transactionAmountInputState = TransactionAmountTextFieldReducer.State(
currencySelectionState: CurrencySelectionReducer.State(),
textFieldState: TCATextFieldReducer.State(
validationType: nil,