Add `History` TCA feature
This adds a "TCA" feature of a (very) basic transaction history and detail. It demonstrates a purely state driven navigation stack. Specifically, a `route: Route?` value is tracked in the state. This value is driven by the selection of a transaction in the list, setting it to to `.selectedTransaction(transaction)`, which then pushes the detail view for that `transaction` onto the `NavigationView`. Popping the detail view sets the property to `nil`. Take a look at the previews in `TransactionHistoryView` try them "live" as well. **N.B** The models are _not_ correct in any way, though are meant to be somewhat representative and give something to display.
This commit is contained in:
parent
94a0684380
commit
0ce7d14c81
|
@ -89,6 +89,10 @@
|
|||
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; };
|
||||
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; };
|
||||
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
|
||||
F96B41E7273B501F0021B49A /* TransactionHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E3273B501F0021B49A /* TransactionHistoryStore.swift */; };
|
||||
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E5273B501F0021B49A /* TransactionDetailView.swift */; };
|
||||
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */; };
|
||||
F96B41EB273B50520021B49A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41EA273B50520021B49A /* Strings.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -196,6 +200,10 @@
|
|||
66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
|
||||
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
|
||||
F96B41E3273B501F0021B49A /* TransactionHistoryStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHistoryStore.swift; sourceTree = "<group>"; };
|
||||
F96B41E5273B501F0021B49A /* TransactionDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
|
||||
F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHistoryView.swift; sourceTree = "<group>"; };
|
||||
F96B41EA273B50520021B49A /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -525,6 +533,7 @@
|
|||
children = (
|
||||
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
|
||||
0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */,
|
||||
F96B41EA273B50520021B49A /* Strings.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -581,6 +590,7 @@
|
|||
6654C73B2715A3F000901167 /* Features */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F96B41E2273B501F0021B49A /* TransactionHistory */,
|
||||
6654C73C2715A3FA00901167 /* Onboarding */,
|
||||
);
|
||||
path = Features;
|
||||
|
@ -637,6 +647,24 @@
|
|||
path = CircularFrame;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F96B41E2273B501F0021B49A /* TransactionHistory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F96B41E3273B501F0021B49A /* TransactionHistoryStore.swift */,
|
||||
F96B41E4273B501F0021B49A /* Views */,
|
||||
);
|
||||
path = TransactionHistory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F96B41E4273B501F0021B49A /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F96B41E5273B501F0021B49A /* TransactionDetailView.swift */,
|
||||
F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -835,9 +863,11 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
660558F8270C862F009D6954 /* XCAssets+Generated.swift in Sources */,
|
||||
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */,
|
||||
0D32281F26C5867D00262533 /* ScanQrScreenViewModel.swift in Sources */,
|
||||
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
|
||||
0D32282E26C5870B00262533 /* SendScreenViewModel.swift in Sources */,
|
||||
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
|
||||
0D32282D26C5870B00262533 /* SendScreen.swift in Sources */,
|
||||
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
|
||||
0D1922ED26BDE0C600052649 /* AppRouter.swift in Sources */,
|
||||
|
@ -866,6 +896,7 @@
|
|||
0D170A7226BC802800EB6A46 /* Router.swift in Sources */,
|
||||
0D354A0926D5A9D000315F45 /* Services.swift in Sources */,
|
||||
660558F7270C862F009D6954 /* Fonts+Generated.swift in Sources */,
|
||||
F96B41E7273B501F0021B49A /* TransactionHistoryStore.swift in Sources */,
|
||||
0DA13C9726C186FF00E3B610 /* RestoreWalletScreen.swift in Sources */,
|
||||
0DACFA8127208D940039EEA5 /* UInt+SuperscriptText.swift in Sources */,
|
||||
0DF2DC51272344E400FA31E2 /* EmptyChip.swift in Sources */,
|
||||
|
@ -886,6 +917,7 @@
|
|||
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
|
||||
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */,
|
||||
0D32282826C586E000262533 /* RequestZcashScreen.swift in Sources */,
|
||||
F96B41EB273B50520021B49A /* Strings.swift in Sources */,
|
||||
0D32283226C5877A00262533 /* BalanceScreen.swift in Sources */,
|
||||
0D354A0A26D5A9D000315F45 /* KeyStoring.swift in Sources */,
|
||||
0DA13CA226C1955600E3B610 /* HomeScreenViewModel.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
|
||||
struct Transaction: Identifiable, Equatable, Hashable {
|
||||
var id: Int
|
||||
var amount: UInt
|
||||
var memo: String
|
||||
var toAddress: String
|
||||
var fromAddress: String
|
||||
}
|
||||
|
||||
struct TransactionHistoryState: Equatable {
|
||||
enum Route: Equatable {
|
||||
case showTransaction(Transaction)
|
||||
}
|
||||
|
||||
var transactions: IdentifiedArrayOf<Transaction>
|
||||
var route: Route?
|
||||
}
|
||||
|
||||
enum TransactionHistoryAction: Equatable {
|
||||
case setRoute(TransactionHistoryState.Route?)
|
||||
}
|
||||
|
||||
// MARK: - TransactionHistoryReducer
|
||||
|
||||
typealias TransactionHistoryReducer = Reducer<TransactionHistoryState, TransactionHistoryAction, Void>
|
||||
|
||||
extension TransactionHistoryReducer {
|
||||
static let `default` = TransactionHistoryReducer { state, action, _ in
|
||||
switch action {
|
||||
case let .setRoute(route):
|
||||
state.route = route
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TransactionHistoryStore
|
||||
|
||||
typealias TransactionHistoryStore = Store<TransactionHistoryState, TransactionHistoryAction>
|
||||
|
||||
// MARK: - TransactionHistoryViewStore
|
||||
|
||||
typealias TransactionHistoryViewStore = ViewStore<TransactionHistoryState, TransactionHistoryAction>
|
||||
|
||||
extension TransactionHistoryViewStore {
|
||||
private typealias Route = TransactionHistoryState.Route
|
||||
|
||||
func bindingForSelectingTransaction(_ transaction: Transaction) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: { $0.route.map(/TransactionHistoryState.Route.showTransaction) == transaction },
|
||||
send: { isActive in
|
||||
TransactionHistoryAction.setRoute( isActive ? TransactionHistoryState.Route.showTransaction(transaction) : nil)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TransactionDetailView: View {
|
||||
var transaction: Transaction
|
||||
var body: some View {
|
||||
Text(String(dumping: transaction))
|
||||
.padding()
|
||||
.navigationTitle("Transaction: \(transaction.id)")
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionDetail_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
TransactionDetailView(transaction: .demo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension Transaction {
|
||||
static var demo: Self {
|
||||
.init(
|
||||
id: 2,
|
||||
amount: 123,
|
||||
memo: "defaultMemo",
|
||||
toAddress: "ToAddress",
|
||||
fromAddress: "FromAddress"
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,78 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct TransactionHistoryView: View {
|
||||
let store: Store<TransactionHistoryState, TransactionHistoryAction>
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
List {
|
||||
ForEach(viewStore.transactions) { transaction in
|
||||
NavigationLink(
|
||||
isActive: viewStore.bindingForSelectingTransaction(transaction),
|
||||
destination: { TransactionDetailView(transaction: transaction) },
|
||||
label: { Text("Show Transaction \(transaction.id)") }
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle(Text("Transactions"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
TransactionHistoryView(store: .demo)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
NavigationView {
|
||||
TransactionHistoryView(store: .demoWithSelectedTransaction)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension TransactionHistoryStore {
|
||||
static var demo: Store<TransactionHistoryState, TransactionHistoryAction> {
|
||||
return Store(
|
||||
initialState: TransactionHistoryState(
|
||||
transactions: .demo,
|
||||
route: nil
|
||||
),
|
||||
reducer: .default,
|
||||
environment: ()
|
||||
)
|
||||
}
|
||||
|
||||
static var demoWithSelectedTransaction: Store<TransactionHistoryState, TransactionHistoryAction> {
|
||||
let transactions = IdentifiedArrayOf<Transaction>.demo
|
||||
return Store(
|
||||
initialState: TransactionHistoryState(
|
||||
transactions: transactions,
|
||||
route: .showTransaction(transactions[3])
|
||||
),
|
||||
reducer: .default.debug(),
|
||||
environment: ()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension IdentifiedArrayOf where Element == Transaction {
|
||||
static var demo: IdentifiedArrayOf<Transaction> {
|
||||
return .init(
|
||||
uniqueElements: (0..<10).map {
|
||||
Transaction(
|
||||
id: $0,
|
||||
amount: 25,
|
||||
memo: "defaultMemo",
|
||||
toAddress: "ToAddress",
|
||||
fromAddress: "FromAddress"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
#if DEBUG
|
||||
extension String {
|
||||
init<T>(dumping value: T) {
|
||||
var output = String()
|
||||
dump(value, to: &output)
|
||||
self.init(stringLiteral: output)
|
||||
}
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue