* synchronizer status * amount input field enhancements Closes #302
This commit is contained in:
parent
f6e6f6991f
commit
6a12e09ee9
|
@ -106,6 +106,7 @@
|
|||
9E5BF641281FD7B600BA3F17 /* TransactionFailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF640281FD7B600BA3F17 /* TransactionFailedView.swift */; };
|
||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF643281FEC9900BA3F17 /* SendTests.swift */; };
|
||||
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */; };
|
||||
9E5BF648282277BE00BA3F17 /* WrappedNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */; };
|
||||
9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; };
|
||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
|
||||
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; };
|
||||
|
@ -274,6 +275,7 @@
|
|||
9E5BF640281FD7B600BA3F17 /* TransactionFailedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionFailedView.swift; sourceTree = "<group>"; };
|
||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTests.swift; sourceTree = "<group>"; };
|
||||
9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedUserDefaults.swift; sourceTree = "<group>"; };
|
||||
9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedNotificationCenter.swift; sourceTree = "<group>"; };
|
||||
9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = "<group>"; };
|
||||
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
|
||||
9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -762,6 +764,7 @@
|
|||
9E02B5C2280458D2005B809B /* WrappedDerivationTool.swift */,
|
||||
9EAFEB872806E5AE00199FC9 /* WrappedSDKSynchronizer.swift */,
|
||||
9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */,
|
||||
9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */,
|
||||
);
|
||||
path = Wrappers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1232,6 +1235,7 @@
|
|||
0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */,
|
||||
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */,
|
||||
9EAFEB8A2806F48100199FC9 /* ZCashSDKEnvironment.swift in Sources */,
|
||||
9E5BF648282277BE00BA3F17 /* WrappedNotificationCenter.swift in Sources */,
|
||||
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */,
|
||||
9E5BF641281FD7B600BA3F17 /* TransactionFailedView.swift in Sources */,
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
|
||||
struct HomeState: Equatable {
|
||||
enum Route: Equatable {
|
||||
|
@ -16,6 +17,7 @@ struct HomeState: Equatable {
|
|||
var requestState: RequestState
|
||||
var sendState: SendState
|
||||
var scanState: ScanState
|
||||
var synchronizerStatus: String
|
||||
var totalBalance: Double
|
||||
var transactionHistoryState: TransactionHistoryState
|
||||
var verifiedBalance: Double
|
||||
|
@ -34,6 +36,7 @@ enum HomeAction: Equatable {
|
|||
case updateBalance(Balance)
|
||||
case updateDrawer(DrawerOverlay)
|
||||
case updateRoute(HomeState.Route?)
|
||||
case updateSynchronizerStatus
|
||||
case updateTransactions([TransactionState])
|
||||
}
|
||||
|
||||
|
@ -83,20 +86,19 @@ extension HomeReducer {
|
|||
.receive(on: environment.scheduler)
|
||||
.map({ Balance(verified: $0.verified, total: $0.total) })
|
||||
.map(HomeAction.updateBalance)
|
||||
.eraseToEffect()
|
||||
.eraseToEffect(),
|
||||
|
||||
Effect(value: .updateSynchronizerStatus)
|
||||
)
|
||||
|
||||
case .synchronizerStateChanged(let synchronizerState):
|
||||
return .none
|
||||
return Effect(value: .updateSynchronizerStatus)
|
||||
|
||||
case .updateBalance(let balance):
|
||||
state.totalBalance = balance.total.asHumanReadableZecBalance()
|
||||
state.verifiedBalance = balance.verified.asHumanReadableZecBalance()
|
||||
return .none
|
||||
|
||||
case .debugMenuStartup:
|
||||
return .none
|
||||
|
||||
case .updateDrawer(let drawerOverlay):
|
||||
state.drawerOverlay = drawerOverlay
|
||||
state.transactionHistoryState.isScrollable = drawerOverlay == .full ? true : false
|
||||
|
@ -105,6 +107,10 @@ extension HomeReducer {
|
|||
case .updateTransactions(let transactions):
|
||||
return .none
|
||||
|
||||
case .updateSynchronizerStatus:
|
||||
state.synchronizerStatus = environment.wrappedSDKSynchronizer.status()
|
||||
return .none
|
||||
|
||||
case .updateRoute(let route):
|
||||
state.route = route
|
||||
return .none
|
||||
|
@ -132,6 +138,9 @@ extension HomeReducer {
|
|||
|
||||
case .send(let action):
|
||||
return .none
|
||||
|
||||
case .debugMenuStartup:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,9 +243,46 @@ extension HomeState {
|
|||
requestState: .placeholder,
|
||||
sendState: .placeholder,
|
||||
scanState: .placeholder,
|
||||
synchronizerStatus: "",
|
||||
totalBalance: 0.0,
|
||||
transactionHistoryState: .emptyPlaceHolder,
|
||||
verifiedBalance: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDKSynchronizer {
|
||||
static func textFor(state: SyncStatus) -> String {
|
||||
switch state {
|
||||
case .downloading(let progress):
|
||||
return "Downloading \(progress.progressHeight)/\(progress.targetHeight)"
|
||||
|
||||
case .enhancing(let enhanceProgress):
|
||||
return "Enhancing tx \(enhanceProgress.enhancedTransactions) of \(enhanceProgress.totalTransactions)"
|
||||
|
||||
case .fetching:
|
||||
return "fetching UTXOs"
|
||||
|
||||
case .scanning(let scanProgress):
|
||||
return "Scanning: \(scanProgress.progressHeight)/\(scanProgress.targetHeight)"
|
||||
|
||||
case .disconnected:
|
||||
return "disconnected 💔"
|
||||
|
||||
case .stopped:
|
||||
return "Stopped 🚫"
|
||||
|
||||
case .synced:
|
||||
return "Synced 😎"
|
||||
|
||||
case .unprepared:
|
||||
return "Unprepared 😅"
|
||||
|
||||
case .validating:
|
||||
return "Validating"
|
||||
|
||||
case .error(let err):
|
||||
return "Error: \(err.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,14 @@ struct HomeView: View {
|
|||
sendButton(viewStore)
|
||||
|
||||
VStack {
|
||||
Text("\(viewStore.synchronizerStatus)")
|
||||
.padding(.top, 60)
|
||||
|
||||
Text("balance: \(viewStore.totalBalance)")
|
||||
.accessDebugMenuWithHiddenGesture {
|
||||
viewStore.send(.debugMenuStartup)
|
||||
}
|
||||
.padding(.top, 180)
|
||||
.padding(.top, 120)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ struct SandboxView: View {
|
|||
case .history:
|
||||
TransactionHistoryView(store: store.historyStore())
|
||||
case .send:
|
||||
EmptyView()
|
||||
SendView(
|
||||
store: .init(
|
||||
initialState: .placeholder,
|
||||
|
|
|
@ -35,12 +35,19 @@ struct SendState: Equatable {
|
|||
var route: Route?
|
||||
|
||||
var isSendingTransaction = false
|
||||
var totalBalance = 0.0
|
||||
var transaction: Transaction
|
||||
var transactionInputState: TransactionInputState
|
||||
}
|
||||
|
||||
enum SendAction: Equatable {
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case sendConfirmationPressed
|
||||
case sendTransactionResult(Result<TransactionState, NSError>)
|
||||
case synchronizerStateChanged(WrappedSDKSynchronizerState)
|
||||
case transactionInput(TransactionInputAction)
|
||||
case updateBalance(Double)
|
||||
case updateTransaction(Transaction)
|
||||
case updateRoute(SendState.Route?)
|
||||
}
|
||||
|
@ -55,12 +62,23 @@ struct SendEnvironment {
|
|||
|
||||
// MARK: - SendReducer
|
||||
|
||||
private struct ListenerId: Hashable {}
|
||||
|
||||
typealias SendReducer = Reducer<SendState, SendAction, SendEnvironment>
|
||||
|
||||
extension SendReducer {
|
||||
private struct SyncStatusUpdatesID: Hashable {}
|
||||
|
||||
static let `default` = Reducer<SendState, SendAction, SendEnvironment> { state, action, environment in
|
||||
static let `default` = SendReducer.combine(
|
||||
[
|
||||
balanceReducer,
|
||||
sendReducer,
|
||||
transactionInputReducer
|
||||
]
|
||||
)
|
||||
.debug()
|
||||
|
||||
private static let sendReducer = SendReducer { state, action, environment in
|
||||
switch action {
|
||||
case let .updateTransaction(transaction):
|
||||
state.transaction = transaction
|
||||
|
@ -111,9 +129,51 @@ extension SendReducer {
|
|||
} catch {
|
||||
return Effect(value: .updateRoute(.failure))
|
||||
}
|
||||
case .transactionInput(let transactionInput):
|
||||
return .none
|
||||
|
||||
default:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
private static let balanceReducer = SendReducer { state, action, environment in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
return environment.wrappedSDKSynchronizer.stateChanged
|
||||
.map(SendAction.synchronizerStateChanged)
|
||||
.eraseToEffect()
|
||||
.cancellable(id: ListenerId(), cancelInFlight: true)
|
||||
|
||||
case .onDisappear:
|
||||
return Effect.cancel(id: ListenerId())
|
||||
|
||||
case .synchronizerStateChanged(.synced):
|
||||
return environment.wrappedSDKSynchronizer.getShieldedBalance()
|
||||
.receive(on: environment.scheduler)
|
||||
.map({ Double($0.total) / Double(100_000_000) })
|
||||
.map(SendAction.updateBalance)
|
||||
.eraseToEffect()
|
||||
|
||||
case .synchronizerStateChanged(let synchronizerState):
|
||||
return .none
|
||||
|
||||
case .updateBalance(let balance):
|
||||
state.totalBalance = balance
|
||||
state.transactionInputState.maxValue = Int64(balance * 100_000_000)
|
||||
return .none
|
||||
|
||||
default:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
private static let transactionInputReducer: SendReducer = TransactionInputReducer.default.pullback(
|
||||
state: \SendState.transactionInputState,
|
||||
action: /SendAction.transactionInput,
|
||||
environment: { _ in TransactionInputEnvironment() }
|
||||
)
|
||||
|
||||
static func `default`(whenDone: @escaping () -> Void) -> SendReducer {
|
||||
SendReducer { state, action, environment in
|
||||
switch action {
|
||||
|
@ -176,13 +236,24 @@ extension SendViewStore {
|
|||
embed: { $0 ? SendState.Route.done : SendState.Route.showConfirmation }
|
||||
)
|
||||
}
|
||||
|
||||
var bindingForBalance: Binding<Double> {
|
||||
self.binding(
|
||||
get: \.totalBalance,
|
||||
send: SendAction.updateBalance
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: PlaceHolders
|
||||
|
||||
extension SendState {
|
||||
static var placeholder: Self {
|
||||
.init(route: nil, transaction: .placeholder)
|
||||
.init(
|
||||
route: nil,
|
||||
transaction: .placeholder,
|
||||
transactionInputState: .placeholer
|
||||
)
|
||||
}
|
||||
|
||||
static var emptyPlaceholder: Self {
|
||||
|
@ -192,7 +263,8 @@ extension SendState {
|
|||
amount: 0,
|
||||
memo: "",
|
||||
toAddress: ""
|
||||
)
|
||||
),
|
||||
transactionInputState: .placeholer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,61 +2,76 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
|
||||
struct CreateTransaction: View {
|
||||
let store: TransactionInputStore
|
||||
|
||||
@Binding var transaction: Transaction
|
||||
@Binding var isComplete: Bool
|
||||
@Binding var totalBalance: Double
|
||||
|
||||
var body: some View {
|
||||
UITextView.appearance().backgroundColor = .clear
|
||||
|
||||
return VStack {
|
||||
return WithViewStore(store) { viewStore in
|
||||
VStack {
|
||||
Text("ZEC Amount")
|
||||
VStack {
|
||||
Text("Balance \(totalBalance)")
|
||||
|
||||
TextField(
|
||||
"ZEC Amount",
|
||||
text: $transaction.amountString
|
||||
)
|
||||
SingleLineTextField(
|
||||
placeholderText: "0",
|
||||
title: "How much ZEC would you like to send?",
|
||||
store: store.scope(
|
||||
state: \.textFieldState,
|
||||
action: TransactionInputAction.textField
|
||||
),
|
||||
titleAccessoryView: {
|
||||
Button(
|
||||
action: { viewStore.send(.setMax(viewStore.maxValue)) },
|
||||
label: { Text("Max") }
|
||||
)
|
||||
.textFieldTitleAccessoryButtonStyle
|
||||
},
|
||||
inputAccessoryView: {
|
||||
}
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
}
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
Text("To Address")
|
||||
VStack {
|
||||
Text("To Address")
|
||||
|
||||
TextField(
|
||||
"Address",
|
||||
text: $transaction.toAddress
|
||||
)
|
||||
.font(.system(size: 14))
|
||||
TextField(
|
||||
"Address",
|
||||
text: $transaction.toAddress
|
||||
)
|
||||
.font(.system(size: 14))
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
}
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||
|
||||
VStack {
|
||||
Text("Memo")
|
||||
|
||||
TextEditor(text: $transaction.memo)
|
||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
||||
.importSeedEditorModifier()
|
||||
}
|
||||
.padding()
|
||||
|
||||
Button(
|
||||
action: { isComplete = true },
|
||||
label: { Text("Send") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
|
||||
VStack {
|
||||
Text("Memo")
|
||||
|
||||
TextEditor(text: $transaction.memo)
|
||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
||||
.importSeedEditorModifier()
|
||||
}
|
||||
.padding()
|
||||
|
||||
Button(
|
||||
action: { isComplete = true },
|
||||
label: { Text("Send") }
|
||||
)
|
||||
.activeButtonStyle
|
||||
.frame(height: 50)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
.applyScreenBackground()
|
||||
}
|
||||
.padding()
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,12 +83,15 @@ struct Create_Previews: PreviewProvider {
|
|||
StateContainer(
|
||||
initialState: (
|
||||
Transaction.placeholder,
|
||||
false
|
||||
false,
|
||||
0.0
|
||||
)
|
||||
) {
|
||||
CreateTransaction(
|
||||
store: .placeholder,
|
||||
transaction: $0.0,
|
||||
isComplete: $0.1
|
||||
isComplete: $0.1,
|
||||
totalBalance: $0.2
|
||||
)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
@ -88,7 +106,8 @@ extension SendStore {
|
|||
return SendStore(
|
||||
initialState: .init(
|
||||
route: nil,
|
||||
transaction: .placeholder
|
||||
transaction: .placeholder,
|
||||
transactionInputState: .placeholer
|
||||
),
|
||||
reducer: .default,
|
||||
environment: SendEnvironment(
|
||||
|
|
|
@ -7,9 +7,16 @@ struct SendView: View {
|
|||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
CreateTransaction(
|
||||
store: store.scope(
|
||||
state: \.transactionInputState,
|
||||
action: SendAction.transactionInput
|
||||
),
|
||||
transaction: viewStore.bindingForTransaction,
|
||||
isComplete: viewStore.bindingForConfirmation
|
||||
isComplete: viewStore.bindingForConfirmation,
|
||||
totalBalance: viewStore.bindingForBalance
|
||||
)
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear { viewStore.send(.onDisappear) }
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForConfirmation,
|
||||
destination: {
|
||||
|
@ -35,7 +42,8 @@ struct SendView_Previews: PreviewProvider {
|
|||
store: .init(
|
||||
initialState: .init(
|
||||
route: nil,
|
||||
transaction: .placeholder
|
||||
transaction: .placeholder,
|
||||
transactionInputState: .placeholer
|
||||
),
|
||||
reducer: .default,
|
||||
environment: SendEnvironment(
|
||||
|
|
|
@ -6,7 +6,7 @@ struct TransactionConfirmation: View {
|
|||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Send \(String(format: "%.7f", Int64(viewStore.transaction.amount).asHumanReadableZecBalance())) ZEC")
|
||||
Text("Send \(String(format: "%.7f", Int64(viewStore.transactionInputState.amount).asHumanReadableZecBalance())) ZEC")
|
||||
.padding()
|
||||
|
||||
Text("To \(viewStore.transaction.toAddress)")
|
||||
|
|
|
@ -21,8 +21,8 @@ struct TextFieldState: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
enum TextFieldAction {
|
||||
case apply((String) -> String)
|
||||
enum TextFieldAction: Equatable {
|
||||
// case apply((String) -> String)
|
||||
case set(String)
|
||||
}
|
||||
|
||||
|
@ -31,10 +31,9 @@ struct TextFieldEnvironment: Equatable { }
|
|||
extension TextFieldReducer {
|
||||
static let `default` = TextFieldReducer { state, action, _ in
|
||||
switch action {
|
||||
case .apply(let action):
|
||||
state.text = action(state.text)
|
||||
state.valid = state.text.isValid(for: state.validationType)
|
||||
|
||||
// case .apply(let action):
|
||||
// state.text = action(state.text)
|
||||
// state.valid = state.text.isValid(for: state.validationType)
|
||||
case .set(let text):
|
||||
state.text = text
|
||||
state.valid = state.text.isValid(for: state.validationType)
|
||||
|
@ -60,3 +59,10 @@ extension TextFieldStore {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension TextFieldState {
|
||||
static let placeholder = TextFieldState(
|
||||
validationType: nil,
|
||||
text: ""
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,30 +18,21 @@ typealias TransactionInputStore = Store<TransactionInputState, TransactionInputA
|
|||
struct TransactionInputState: Equatable {
|
||||
var textFieldState: TextFieldState
|
||||
var currencySelectionState: CurrencySelectionState
|
||||
var maxValue: Int64 = 0
|
||||
|
||||
var amount: Int64 {
|
||||
Int64((Double(textFieldState.text) ?? 0.0) * 100_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
enum TransactionInputAction {
|
||||
case setMax(Double)
|
||||
enum TransactionInputAction: Equatable {
|
||||
case setMax(Int64)
|
||||
case textField(TextFieldAction)
|
||||
case currencySelection(CurrencySelectionAction)
|
||||
}
|
||||
|
||||
struct TransactionInputEnvironment: Equatable {}
|
||||
|
||||
func maxOverride(_ reducer: @escaping (TransactionReducerData)) -> TransactionReducerData {
|
||||
return { state, action in
|
||||
switch action {
|
||||
case .setMax(let value):
|
||||
state.textFieldState.text = "\(value)"
|
||||
state.currencySelectionState.currencyType = .usd
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
reducer(&state, action)
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionInputReducer {
|
||||
static let `default` = TransactionInputReducer.combine(
|
||||
[
|
||||
|
@ -56,7 +47,7 @@ extension TransactionInputReducer {
|
|||
switch action {
|
||||
case .setMax(let value):
|
||||
state.currencySelectionState.currencyType = .zec
|
||||
state.textFieldState.text = "\(value)"
|
||||
state.textFieldState.text = "\(value.asHumanReadableZecBalance())"
|
||||
|
||||
default: break
|
||||
}
|
||||
|
@ -99,3 +90,18 @@ extension TransactionInputReducer {
|
|||
environment: { _ in return .init() }
|
||||
)
|
||||
}
|
||||
|
||||
extension TransactionInputState {
|
||||
static let placeholer = TransactionInputState(
|
||||
textFieldState: .placeholder,
|
||||
currencySelectionState: CurrencySelectionState()
|
||||
)
|
||||
}
|
||||
|
||||
extension TransactionInputStore {
|
||||
static let placeholder = TransactionInputStore(
|
||||
initialState: .placeholer,
|
||||
reducer: .default,
|
||||
environment: TransactionInputEnvironment()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ struct TransactionTextField: View {
|
|||
// Constant example used here, this could be injected by a dependency
|
||||
// Access to this value could also be injected into the store as a dependency
|
||||
// with a function to prouce this value.
|
||||
let maxTransactionValue = 500.0
|
||||
let maxTransactionValue: Int64 = 500
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
|
@ -68,5 +68,41 @@ struct TransactionTextField_Previews: PreviewProvider {
|
|||
.padding(.horizontal, 50)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 500, height: 200))
|
||||
|
||||
SingleLineTextField(
|
||||
placeholderText: "$0",
|
||||
title: "How much?",
|
||||
store: .transaction,
|
||||
titleAccessoryView: {
|
||||
Button(
|
||||
action: { },
|
||||
label: { Text("Max") }
|
||||
)
|
||||
.textFieldTitleAccessoryButtonStyle
|
||||
},
|
||||
inputAccessoryView: {
|
||||
}
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
.padding(.horizontal, 50)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 500, height: 200))
|
||||
|
||||
SingleLineTextField(
|
||||
placeholderText: "",
|
||||
title: "Address",
|
||||
store: .address,
|
||||
titleAccessoryView: {
|
||||
},
|
||||
inputAccessoryView: {
|
||||
Image(Asset.Assets.Icons.qrCode.name)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
)
|
||||
.preferredColorScheme(.dark)
|
||||
.padding(.horizontal, 50)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 500, height: 200))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// WrappedNotificationCenter.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 04.05.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct WrappedNotificationCenter {
|
||||
let publisherFor: (Notification.Name) -> NotificationCenter.Publisher?
|
||||
}
|
||||
|
||||
extension WrappedNotificationCenter {
|
||||
static let live = WrappedNotificationCenter(
|
||||
publisherFor: { NotificationCenter.default.publisher(for: $0) }
|
||||
)
|
||||
|
||||
static let mock = WrappedNotificationCenter(
|
||||
publisherFor: { _ in nil }
|
||||
)
|
||||
}
|
|
@ -40,11 +40,12 @@ struct Balance: WalletBalance, Equatable {
|
|||
protocol WrappedSDKSynchronizer {
|
||||
var synchronizer: SDKSynchronizer? { get }
|
||||
var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never> { get }
|
||||
var notificationCenter: WrappedNotificationCenter { get }
|
||||
|
||||
func prepareWith(initializer: Initializer) throws
|
||||
func start(retry: Bool) throws
|
||||
func stop()
|
||||
func synchronizerSynced()
|
||||
func status() -> String
|
||||
|
||||
func getShieldedBalance() -> Effect<Balance, Never>
|
||||
func getAllClearedTransactions() -> Effect<[TransactionState], Never>
|
||||
|
@ -81,8 +82,10 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
private var cancellables: [AnyCancellable] = []
|
||||
private(set) var synchronizer: SDKSynchronizer?
|
||||
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
|
||||
private(set) var notificationCenter: WrappedNotificationCenter
|
||||
|
||||
init() {
|
||||
init(notificationCenter: WrappedNotificationCenter = .live) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.stateChanged = CurrentValueSubject<WrappedSDKSynchronizerState, Never>(.unknown)
|
||||
}
|
||||
|
||||
|
@ -93,11 +96,24 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
func prepareWith(initializer: Initializer) throws {
|
||||
synchronizer = try SDKSynchronizer(initializer: initializer)
|
||||
|
||||
NotificationCenter.default.publisher(for: .synchronizerSynced)
|
||||
notificationCenter.publisherFor(.synchronizerStarted)?
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] _ in
|
||||
self?.synchronizerSynced()
|
||||
})
|
||||
.sink { [weak self] _ in self?.synchronizerStarted() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
notificationCenter.publisherFor(.synchronizerSynced)?
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in self?.synchronizerSynced() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
notificationCenter.publisherFor(.synchronizerProgressUpdated)?
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in self?.synchronizerProgressUpdated() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
notificationCenter.publisherFor(.synchronizerStopped)?
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in self?.synchronizerStopped() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
try synchronizer?.prepare()
|
||||
|
@ -111,10 +127,30 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
synchronizer?.stop()
|
||||
}
|
||||
|
||||
func synchronizerStarted() {
|
||||
stateChanged.send(.started)
|
||||
}
|
||||
|
||||
func synchronizerSynced() {
|
||||
stateChanged.send(.synced)
|
||||
}
|
||||
|
||||
func synchronizerProgressUpdated() {
|
||||
stateChanged.send(.progressUpdated)
|
||||
}
|
||||
|
||||
func synchronizerStopped() {
|
||||
stateChanged.send(.stopped)
|
||||
}
|
||||
|
||||
func status() -> String {
|
||||
guard let synchronizer = synchronizer else {
|
||||
return ""
|
||||
}
|
||||
|
||||
return SDKSynchronizer.textFor(state: synchronizer.status)
|
||||
}
|
||||
|
||||
func getShieldedBalance() -> Effect<Balance, Never> {
|
||||
if let shieldedVerifiedBalance = synchronizer?.getShieldedVerifiedBalance(),
|
||||
let shieldedTotalBalance = synchronizer?.getShieldedBalance(accountIndex: 0) {
|
||||
|
@ -215,8 +251,10 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
private var cancellables: [AnyCancellable] = []
|
||||
private(set) var synchronizer: SDKSynchronizer?
|
||||
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
|
||||
private(set) var notificationCenter: WrappedNotificationCenter
|
||||
|
||||
init() {
|
||||
init(notificationCenter: WrappedNotificationCenter = .mock) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.stateChanged = CurrentValueSubject<WrappedSDKSynchronizerState, Never>(.unknown)
|
||||
}
|
||||
|
||||
|
@ -249,6 +287,14 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
stateChanged.send(.synced)
|
||||
}
|
||||
|
||||
func status() -> String {
|
||||
guard let synchronizer = synchronizer else {
|
||||
return ""
|
||||
}
|
||||
|
||||
return SDKSynchronizer.textFor(state: synchronizer.status)
|
||||
}
|
||||
|
||||
func getShieldedBalance() -> Effect<Balance, Never> {
|
||||
return Effect(value: Balance(verified: 12345000, total: 12345000))
|
||||
}
|
||||
|
@ -339,8 +385,10 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||
private(set) var synchronizer: SDKSynchronizer?
|
||||
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
|
||||
private(set) var notificationCenter: WrappedNotificationCenter
|
||||
|
||||
init() {
|
||||
init(notificationCenter: WrappedNotificationCenter = .mock) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.stateChanged = CurrentValueSubject<WrappedSDKSynchronizerState, Never>(.unknown)
|
||||
}
|
||||
|
||||
|
@ -352,6 +400,8 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
|||
|
||||
func synchronizerSynced() { }
|
||||
|
||||
func status() -> String { "" }
|
||||
|
||||
func getShieldedBalance() -> Effect<Balance, Never> {
|
||||
return .none
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class UserPreferencesStorageTests: XCTestCase {
|
|||
super.setUp()
|
||||
|
||||
guard let userDefaults = UserDefaults.init(suiteName: "test") else {
|
||||
XCTFail("UserPreferencesStorageTests: UserDefaults.init(suiteName: "test") failed to initialize")
|
||||
XCTFail("UserPreferencesStorageTests: UserDefaults.init(suiteName: \"test\") failed to initialize")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,7 @@ class UserPreferencesStorageTests: XCTestCase {
|
|||
|
||||
func testRemoveAll() throws {
|
||||
guard let userDefaults = UserDefaults.init(suiteName: "test") else {
|
||||
XCTFail("User Preferences: UserDefaults.init(suiteName: "test") failed to initialize")
|
||||
XCTFail("User Preferences: UserDefaults.init(suiteName: \"test\") failed to initialize")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue