Add `Send` "Placeholder" Feature

This adds screens to represent a basic "Send" Feature. The main
purpose of this is two-fold.

1. To act as a placeholder for the actual journet
2. To demonstrate a slightly different navigation paradigm, i.e,
   a `NavigationView` with >1 depth.
This commit is contained in:
Daniel Haight 2021-11-21 14:28:10 +00:00
parent 24c21d4965
commit 85395bb9f0
8 changed files with 310 additions and 0 deletions

View File

@ -98,6 +98,10 @@
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */; };
F96B41EB273B50520021B49A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41EA273B50520021B49A /* Strings.swift */; };
F9C165B4274031F600592F76 /* Bindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B3274031F600592F76 /* Bindings.swift */; };
F9C165BF2740403600592F76 /* SendStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B72740403600592F76 /* SendStore.swift */; };
F9C165C02740403600592F76 /* ApproveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B92740403600592F76 /* ApproveView.swift */; };
F9C165C22740403600592F76 /* CreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BB2740403600592F76 /* CreateView.swift */; };
F9C165C42740403600592F76 /* SentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165BD2740403600592F76 /* SentView.swift */; };
F9EEB8162742C2210032EEB8 /* WithStateBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */; };
/* End PBXBuildFile section */
@ -215,6 +219,10 @@
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>"; };
F9C165B3274031F600592F76 /* Bindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bindings.swift; sourceTree = "<group>"; };
F9C165B72740403600592F76 /* SendStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendStore.swift; sourceTree = "<group>"; };
F9C165B92740403600592F76 /* ApproveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApproveView.swift; sourceTree = "<group>"; };
F9C165BB2740403600592F76 /* CreateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateView.swift; sourceTree = "<group>"; };
F9C165BD2740403600592F76 /* SentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentView.swift; sourceTree = "<group>"; };
F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithStateBinding.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -607,6 +615,7 @@
isa = PBXGroup;
children = (
F93874EC273C4DE200F0E875 /* Home */,
F9C165B62740403600592F76 /* Send */,
F96B41E2273B501F0021B49A /* TransactionHistory */,
6654C73C2715A3FA00901167 /* Onboarding */,
);
@ -699,6 +708,25 @@
path = Views;
sourceTree = "<group>";
};
F9C165B62740403600592F76 /* Send */ = {
isa = PBXGroup;
children = (
F9C165B72740403600592F76 /* SendStore.swift */,
F9C165B82740403600592F76 /* Views */,
);
path = Send;
sourceTree = "<group>";
};
F9C165B82740403600592F76 /* Views */ = {
isa = PBXGroup;
children = (
F9C165BB2740403600592F76 /* CreateView.swift */,
F9C165B92740403600592F76 /* ApproveView.swift */,
F9C165BD2740403600592F76 /* SentView.swift */,
);
path = Views;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -939,12 +967,14 @@
0DA13C9726C186FF00E3B610 /* RestoreWalletScreen.swift in Sources */,
0DACFA8127208D940039EEA5 /* UInt+SuperscriptText.swift in Sources */,
0DF2DC51272344E400FA31E2 /* EmptyChip.swift in Sources */,
F9C165BF2740403600592F76 /* SendStore.swift in Sources */,
0D1922EA26BDD96A00052649 /* ViewModel.swift in Sources */,
0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */,
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */,
0D864A0926E154FD00A61879 /* InitFailedScreen.swift in Sources */,
663FABA0271D876200E495F8 /* PrimaryButton.swift in Sources */,
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
0D32281A26C5864B00262533 /* ProfileScreenViewModel.swift in Sources */,
0D185819272723FF0046B928 /* BlueChip.swift in Sources */,
@ -954,6 +984,7 @@
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
663FAB9E271D875700E495F8 /* CreateButton.swift in Sources */,
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
F9C165C22740403600592F76 /* CreateView.swift in Sources */,
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */,
F9C165B4274031F600592F76 /* Bindings.swift in Sources */,
0D32282826C586E000262533 /* RequestZcashScreen.swift in Sources */,
@ -965,6 +996,7 @@
0D32281926C5864B00262533 /* ProfileScreen.swift in Sources */,
6654C7412715A47300901167 /* Onboarding.swift in Sources */,
0D32282426C586A800262533 /* HistoryScreenViewModel.swift in Sources */,
F9C165C42740403600592F76 /* SentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -89,4 +89,13 @@ extension HomeViewStore {
}
)
}
var showSendBinding: Binding<Bool> {
self.binding(
get: { $0.route == .send },
send: { isActive in
return .updateRoute(isActive ? .send : nil)
}
)
}
}

View File

@ -21,6 +21,13 @@ struct HomeView: View {
.primaryButtonStyle
.frame(height: 50)
Button(
action: { viewStore.send(.updateRoute(.send)) },
label: { Text("Go to Send") }
)
.primaryButtonStyle
.frame(height: 50)
Spacer()
HStack {
@ -36,6 +43,21 @@ struct HomeView: View {
}
.padding(.horizontal, 30)
.navigationBarTitle("Home", displayMode: .inline)
.navigationLinkEmpty(
isActive: viewStore.showSendBinding,
destination: {
Create(
store: .init(
initialState: .init(
transaction: .demo,
route: nil
),
reducer: .default.debug(),
environment: ()
)
)
}
)
.fullScreenCover(
isPresented: viewStore.showHistoryBinding,
content: {

View File

@ -0,0 +1,57 @@
import SwiftUI
import ComposableArchitecture
struct SendState: Equatable {
var transaction: Transaction
var route: Create.Route?
}
enum SendAction: Equatable {
case updateTransaction(Transaction)
case updateRoute(Create.Route?)
}
// Mark: - SendReducer
typealias SendReducer = Reducer<SendState, SendAction, Void>
extension SendReducer {
private struct SyncStatusUpdatesID: Hashable {}
static let `default` = Reducer<SendState, SendAction, Void> { state, action, _ in
switch action {
case let .updateTransaction(transaction):
state.transaction = transaction
return .none
case let .updateRoute(route):
state.route = route
return .none
}
}
}
// Mark: - SendStore
typealias SendStore = Store<SendState, SendAction>
// Mark: - SendViewStore
typealias SendViewStore = ViewStore<SendState, SendAction>
extension SendViewStore {
var bindingForTransaction: Binding<Transaction> {
self.binding(
get: \.transaction,
send: SendAction.updateTransaction
)
}
var routeBinding: Binding<Create.Route?> {
self.binding(
get: \.route,
send: SendAction.updateRoute
)
}
}

View File

@ -0,0 +1,63 @@
import SwiftUI
import ComposableArchitecture
struct Approve: View {
enum Route: Equatable {
case showSent(route: Sent.Route?)
}
let transaction: Transaction
@Binding var route: Route?
var body: some View {
VStack {
Button(
action: { route = .showSent(route: nil) },
label: { Text("Go to sent") }
)
.primaryButtonStyle
.frame(height: 50)
.padding()
Text("\(String(dumping: transaction))")
Text("\(String(dumping: route))")
Spacer()
}
.navigationTitle(Text("2. Approve"))
.navigationLinkEmpty(
isActive: $route.map(
extract: {
if case .showSent = $0 {
return true
} else {
return false
}
},
embed: { $0 ? .showSent(route: (/Route.showSent).extract(from: route)) : nil }
),
destination: {
Sent(
transaction: transaction,
route: $route.map(
extract: /Route.showSent,
embed: Route.showSent
)
)
}
)
}
}
struct Approve_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
StateContainer(initialState: (Transaction.demo, Approve.Route?.none)) {
Approve(
transaction: $0.0.wrappedValue,
route: $0.1
)
}
}
}
}

View File

@ -0,0 +1,89 @@
import SwiftUI
import ComposableArchitecture
struct Create: View {
enum Route: Equatable {
case showApprove(route: Approve.Route?)
}
let store: Store<SendState, SendAction>
var body: some View {
WithViewStore(store) { viewStore in
VStack {
Button(
action: { viewStore.send(.updateRoute(.showApprove(route: nil))) },
label: { Text("Go To Approve") }
)
.primaryButtonStyle
.frame(height: 50)
.padding()
TextField(
"Amount",
text: viewStore
.bindingForTransaction
.amount
.compactMap(
extract: String.init,
embed: UInt.init
)
)
.padding()
TextField(
"Address",
text: viewStore.bindingForTransaction.toAddress
)
Text("\(String(dumping: viewStore.transaction))")
Text("\(String(dumping: viewStore.route))")
Spacer()
}
.padding()
.navigationTitle(Text("1. Create"))
.navigationLinkEmpty(
isActive: viewStore.routeBinding.map(
extract: { $0.map(/Route.showApprove) != nil },
embed: { $0 ? .showApprove(route: (/Route.showApprove).extract(from: viewStore.route)) : nil }
),
destination: {
Approve(
transaction: viewStore.transaction,
route: viewStore.routeBinding.map(
extract: /Route.showApprove,
embed: Route.showApprove
)
)
}
)
}
}
}
// MARK: - Previews
struct Create_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
Create(store: .demo)
.navigationBarTitleDisplayMode(.inline)
}
}
}
#if DEBUG
extension SendStore {
static var demo: SendStore {
return SendStore(
initialState: .init(
transaction: .demo,
route: nil
),
reducer: .default,
environment: ()
)
}
}
#endif

View File

@ -0,0 +1,37 @@
import SwiftUI
import ComposableArchitecture
struct Sent: View {
enum Route {
case done
}
@State var transaction: Transaction
@Binding var route: Route?
var body: some View {
VStack {
Button(
action: {
route = .done
},
label: { Text("Done") }
)
.primaryButtonStyle
.frame(height: 50)
.padding()
Text("\(String(dumping: transaction))")
Text("\(String(dumping: route))")
Spacer()
}
.navigationTitle(Text("3. Sent"))
}
}
struct Done_Previews: PreviewProvider {
static var previews: some View {
Sent(transaction: .demo, route: .constant(nil))
}
}

View File

@ -15,6 +15,7 @@ struct SecantApp: App {
NavigationView {
HomeView(store: homeStore)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
}