[#180] Project Structure and Source code consistency (#317)

phrase 1:
- project restructured
- swifgen rewired
- files renamed

[180] Code inconsistency

phase 2:
- stores and views refactored

phase 3:
- models, dependencies, utils and UI components

[180] Code inconsistency

- tests fixed
This commit is contained in:
Lukas Korba 2022-05-13 18:29:57 +02:00 committed by GitHub
parent 42424fd413
commit af054b098c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
333 changed files with 1828 additions and 1970 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
//
// AppError.swift
// secant-testnet
//
// Created by Francisco Gindre on 9/3/21.
//
import Foundation
enum AppError: Error, Equatable {
case failedToInitialize
}

View File

@ -116,72 +116,3 @@ struct DatabaseFiles {
}
}
}
struct DatabaseFilesInteractor {
let documentsDirectory: () throws -> URL
let cacheDbURLFor: (ZcashNetwork) throws -> URL
let dataDbURLFor: (ZcashNetwork) throws -> URL
let outputParamsURLFor: (ZcashNetwork) throws -> URL
let pendingDbURLFor: (ZcashNetwork) throws -> URL
let spendParamsURLFor: (ZcashNetwork) throws -> URL
let areDbFilesPresentFor: (ZcashNetwork) throws -> Bool
let nukeDbFilesFor: (ZcashNetwork) throws -> Void
}
extension DatabaseFilesInteractor {
static func live(databaseFiles: DatabaseFiles = DatabaseFiles(fileManager: .live)) -> Self {
Self(
documentsDirectory: {
try databaseFiles.documentsDirectory()
},
cacheDbURLFor: { network in
try databaseFiles.cacheDbURL(for: network)
},
dataDbURLFor: { network in
try databaseFiles.dataDbURL(for: network)
},
outputParamsURLFor: { network in
try databaseFiles.outputParamsURL(for: network)
},
pendingDbURLFor: { network in
try databaseFiles.pendingDbURL(for: network)
},
spendParamsURLFor: { network in
try databaseFiles.spendParamsURL(for: network)
},
areDbFilesPresentFor: { network in
try databaseFiles.areDbFilesPresent(for: network)
},
nukeDbFilesFor: { network in
try databaseFiles.nukeDbFiles(for: network)
}
)
}
static var throwing = DatabaseFilesInteractor(
documentsDirectory: {
throw DatabaseFiles.DatabaseFilesError.getDocumentsURL
},
cacheDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getCacheURL
},
dataDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getDataURL
},
outputParamsURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getOutputParamsURL
},
pendingDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getPendingURL
},
spendParamsURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getSpendParamsURL
},
areDbFilesPresentFor: { _ in
throw DatabaseFiles.DatabaseFilesError.filesPresentCheck
},
nukeDbFilesFor: { _ in
throw DatabaseFiles.DatabaseFilesError.nukeFiles
}
)
}

View File

@ -1,6 +1,12 @@
import ComposableArchitecture
import ZcashLightClientKit
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
typealias AppStore = Store<AppState, AppAction>
typealias AppViewStore = ViewStore<AppState, AppAction>
// MARK: - State
struct AppState: Equatable {
enum Route: Equatable {
case welcome
@ -14,8 +20,8 @@ struct AppState: Equatable {
var appInitializationState: InitializationState = .uninitialized
var homeState: HomeState
var onboardingState: OnboardingState
var phraseValidationState: RecoveryPhraseValidationState
var onboardingState: OnboardingFlowState
var phraseValidationState: RecoveryPhraseValidationFlowState
var phraseDisplayState: RecoveryPhraseDisplayState
var route: Route = .welcome
var sandboxState: SandboxState
@ -23,6 +29,8 @@ struct AppState: Equatable {
var welcomeState: WelcomeState
}
// MARK: - Action
enum AppAction: Equatable {
case appDelegate(AppDelegateAction)
case checkBackupPhraseValidation
@ -31,54 +39,54 @@ enum AppAction: Equatable {
case home(HomeAction)
case initializeSDK
case nukeWallet
case onboarding(OnboardingAction)
case onboarding(OnboardingFlowAction)
case phraseDisplay(RecoveryPhraseDisplayAction)
case phraseValidation(RecoveryPhraseValidationAction)
case phraseValidation(RecoveryPhraseValidationFlowAction)
case respondToWalletInitializationState(InitializationState)
case sandbox(SandboxAction)
case updateRoute(AppState.Route)
case welcome(WelcomeAction)
}
// MARK: - Environment
struct AppEnvironment {
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
let databaseFiles: DatabaseFilesInteractor
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let SDKSynchronizer: WrappedSDKSynchronizer
let databaseFiles: WrappedDatabaseFiles
let mnemonicSeedPhraseProvider: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WalletStorageInteractor
let wrappedDerivationTool: WrappedDerivationTool
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension AppEnvironment {
static let live = AppEnvironment(
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
databaseFiles: .live(),
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(),
derivationTool: .live(),
zcashSDKEnvironment: .mainnet
)
static let mock = AppEnvironment(
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
databaseFiles: .live(),
mnemonicSeedPhraseProvider: .mock,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(derivationTool: DerivationTool(networkType: .mainnet)),
derivationTool: .live(derivationTool: DerivationTool(networkType: .mainnet)),
zcashSDKEnvironment: .mainnet
)
}
// MARK: - AppReducer
private struct ListenerId: Hashable {}
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
// MARK: - Reducer
extension AppReducer {
private struct ListenerId: Hashable {}
static let `default` = AppReducer.combine(
[
appReducer,
@ -153,8 +161,8 @@ extension AppReducer {
birthday: birthday,
with: environment
)
try environment.wrappedSDKSynchronizer.prepareWith(initializer: initializer)
try environment.wrappedSDKSynchronizer.start()
try environment.SDKSynchronizer.prepareWith(initializer: initializer)
try environment.SDKSynchronizer.start()
} catch {
state.appInitializationState = .failed
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
@ -176,7 +184,7 @@ extension AppReducer {
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
state.phraseValidationState = RecoveryPhraseValidationFlowState.random(phrase: recoveryPhrase)
landingRoute = .phraseDisplay
} catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
@ -203,7 +211,7 @@ extension AppReducer {
let randomPhraseWords = try environment.mnemonicSeedPhraseProvider.asWords(randomPhrase)
let recoveryPhrase = RecoveryPhrase(words: randomPhraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
state.phraseValidationState = RecoveryPhraseValidationFlowState.random(phrase: recoveryPhrase)
return .concatenate(
Effect(value: .initializeSDK),
@ -294,17 +302,17 @@ extension AppReducer {
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage,
wrappedDerivationTool: environment.wrappedDerivationTool,
wrappedSDKSynchronizer: environment.wrappedSDKSynchronizer
derivationTool: environment.derivationTool,
SDKSynchronizer: environment.SDKSynchronizer
)
}
)
private static let onboardingReducer: AppReducer = OnboardingReducer.default.pullback(
private static let onboardingReducer: AppReducer = OnboardingFlowReducer.default.pullback(
state: \AppState.onboardingState,
action: /AppAction.onboarding,
environment: { environment in
OnboardingEnvironment(
OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
@ -312,33 +320,31 @@ extension AppReducer {
}
)
private static let phraseValidationReducer: AppReducer = RecoveryPhraseValidationReducer.default.pullback(
private static let phraseValidationReducer: AppReducer = RecoveryPhraseValidationFlowReducer.default.pullback(
state: \AppState.phraseValidationState,
action: /AppAction.phraseValidation,
environment: { _ in BackupPhraseEnvironment.demo }
environment: { _ in RecoveryPhraseValidationFlowEnvironment.demo }
)
private static let phraseDisplayReducer: AppReducer = RecoveryPhraseDisplayReducer.default.pullback(
state: \AppState.phraseDisplayState,
action: /AppAction.phraseDisplay,
environment: { _ in BackupPhraseEnvironment.demo }
environment: { _ in RecoveryPhraseDisplayEnvironment.demo }
)
private static let sandboxReducer: AppReducer = SandboxReducer.default.pullback(
state: \AppState.sandboxState,
action: /AppAction.sandbox,
environment: { _ in }
environment: { _ in SandboxEnvironment() }
)
private static let welcomeReducer: AppReducer = WelcomeReducer.default.pullback(
state: \AppState.welcomeState,
action: /AppAction.welcome,
environment: { _ in }
environment: { _ in WelcomeEnvironment() }
)
}
// MARK: - AppReducer Helper Functions
extension AppReducer {
static func walletInitializationState(_ environment: AppEnvironment) -> InitializationState {
var keysPresent = false
@ -386,7 +392,7 @@ extension AppReducer {
) throws -> Initializer {
do {
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(seedPhrase)
let viewingKeys = try environment.wrappedDerivationTool.deriveUnifiedViewingKeysFromSeed(seedBytes, 1)
let viewingKeys = try environment.derivationTool.deriveUnifiedViewingKeysFromSeed(seedBytes, 1)
let network = environment.zcashSDKEnvironment.network
@ -409,9 +415,24 @@ extension AppReducer {
}
}
// MARK: - AppStore
// MARK: Placeholders
typealias AppStore = Store<AppState, AppAction>
extension AppState {
static var placeholder: Self {
.init(
homeState: .placeholder,
onboardingState: .init(
importWalletState: .placeholder
),
phraseValidationState: RecoveryPhraseValidationFlowState.placeholder,
phraseDisplayState: RecoveryPhraseDisplayState(
phrase: .placeholder
),
sandboxState: .placeholder,
welcomeState: .placeholder
)
}
}
extension AppStore {
static var placeholder: AppStore {
@ -422,29 +443,3 @@ extension AppStore {
)
}
}
// MARK: - AppViewStore
typealias AppViewStore = ViewStore<AppState, AppAction>
extension AppViewStore {
}
// MARK: PlaceHolders
extension AppState {
static var placeholder: Self {
.init(
homeState: .placeholder,
onboardingState: .init(
importWalletState: .placeholder
),
phraseValidationState: RecoveryPhraseValidationState.placeholder,
phraseDisplayState: RecoveryPhraseDisplayState(
phrase: .placeholder
),
sandboxState: .placeholder,
welcomeState: .placeholder
)
}
}

View File

@ -43,13 +43,13 @@ struct AppView: View {
case .startup:
ZStack(alignment: .topTrailing) {
StartupView(sendAction: viewStore.send)
DebugView(sendAction: viewStore.send)
.transition(.opacity)
}
case .phraseValidation:
NavigationView {
RecoveryPhraseTestPreambleView(
RecoveryPhraseValidationFlowView(
store: store.scope(
state: \.phraseValidationState,
action: AppAction.phraseValidation
@ -80,37 +80,41 @@ struct AppView: View {
}
}
private struct StartupView: View {
var sendAction: (AppAction) -> Void
var body: some View {
List {
Section(header: Text("Navigation Stack Routes")) {
Button("Go To Sandbox (navigation proof)") {
sendAction(.updateRoute(.sandbox))
}
Button("Go To Onboarding") {
sendAction(.updateRoute(.onboarding))
}
Button("Go To Phrase Validation Demo") {
sendAction(.updateRoute(.phraseValidation))
}
Button("Restart the app") {
sendAction(.updateRoute(.welcome))
}
Button("[Be careful] Nuke Wallet") {
sendAction(.nukeWallet)
private extension AppView {
struct DebugView: View {
var sendAction: (AppAction) -> Void
var body: some View {
List {
Section(header: Text("Navigation Stack Routes")) {
Button("Go To Sandbox (navigation proof)") {
sendAction(.updateRoute(.sandbox))
}
Button("Go To Onboarding") {
sendAction(.updateRoute(.onboarding))
}
Button("Go To Phrase Validation Demo") {
sendAction(.updateRoute(.phraseValidation))
}
Button("Restart the app") {
sendAction(.updateRoute(.welcome))
}
Button("[Be careful] Nuke Wallet") {
sendAction(.nukeWallet)
}
}
}
.navigationBarTitle("Startup")
}
.navigationBarTitle("Startup")
}
}
// MARK: - Previews
struct AppView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {

View File

@ -2,6 +2,12 @@ import ComposableArchitecture
import SwiftUI
import ZcashLightClientKit
typealias HomeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
typealias HomeStore = Store<HomeState, HomeAction>
typealias HomeViewStore = ViewStore<HomeState, HomeAction>
// MARK: State
struct HomeState: Equatable {
enum Route: Equatable {
case profile
@ -15,24 +21,26 @@ struct HomeState: Equatable {
var drawerOverlay: DrawerOverlay
var profileState: ProfileState
var requestState: RequestState
var sendState: SendState
var sendState: SendFlowState
var scanState: ScanState
var synchronizerStatus: String
var totalBalance: Int64
var transactionHistoryState: TransactionHistoryState
var transactionHistoryState: TransactionHistoryFlowState
var verifiedBalance: Int64
}
// MARK: Action
enum HomeAction: Equatable {
case debugMenuStartup
case onAppear
case onDisappear
case profile(ProfileAction)
case request(RequestAction)
case send(SendAction)
case send(SendFlowAction)
case scan(ScanAction)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case transactionHistory(TransactionHistoryAction)
case transactionHistory(TransactionHistoryFlowAction)
case updateBalance(Balance)
case updateDrawer(DrawerOverlay)
case updateRoute(HomeState.Route?)
@ -40,21 +48,21 @@ enum HomeAction: Equatable {
case updateTransactions([TransactionState])
}
// MARK: Environment
struct HomeEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let mnemonicSeedPhraseProvider: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WalletStorageInteractor
let wrappedDerivationTool: WrappedDerivationTool
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
let SDKSynchronizer: WrappedSDKSynchronizer
}
// MARK: - HomeReducer
private struct ListenerId: Hashable {}
typealias HomeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
// MARK: - Reducer
extension HomeReducer {
private struct ListenerId: Hashable {}
static let `default` = HomeReducer.combine(
[
homeReducer,
@ -67,7 +75,7 @@ extension HomeReducer {
private static let homeReducer = HomeReducer { state, action, environment in
switch action {
case .onAppear:
return environment.wrappedSDKSynchronizer.stateChanged
return environment.SDKSynchronizer.stateChanged
.map(HomeAction.synchronizerStateChanged)
.eraseToEffect()
.cancellable(id: ListenerId(), cancelInFlight: true)
@ -77,12 +85,12 @@ extension HomeReducer {
case .synchronizerStateChanged(.synced):
return .merge(
environment.wrappedSDKSynchronizer.getAllClearedTransactions()
environment.SDKSynchronizer.getAllClearedTransactions()
.receive(on: environment.scheduler)
.map(HomeAction.updateTransactions)
.eraseToEffect(),
environment.wrappedSDKSynchronizer.getShieldedBalance()
environment.SDKSynchronizer.getShieldedBalance()
.receive(on: environment.scheduler)
.map({ Balance(verified: $0.verified, total: $0.total) })
.map(HomeAction.updateBalance)
@ -108,7 +116,7 @@ extension HomeReducer {
return .none
case .updateSynchronizerStatus:
state.synchronizerStatus = environment.wrappedSDKSynchronizer.status()
state.synchronizerStatus = environment.SDKSynchronizer.status()
return .none
case .updateRoute(let route):
@ -144,60 +152,36 @@ extension HomeReducer {
}
}
private static let historyReducer: HomeReducer = TransactionHistoryReducer.default.pullback(
private static let historyReducer: HomeReducer = TransactionHistoryFlowReducer.default.pullback(
state: \HomeState.transactionHistoryState,
action: /HomeAction.transactionHistory,
environment: { environment in
TransactionHistoryEnvironment(
TransactionHistoryFlowEnvironment(
scheduler: environment.scheduler,
wrappedSDKSynchronizer: environment.wrappedSDKSynchronizer
SDKSynchronizer: environment.SDKSynchronizer
)
}
)
private static let sendReducer: HomeReducer = SendReducer.default.pullback(
private static let sendReducer: HomeReducer = SendFlowReducer.default.pullback(
state: \HomeState.sendState,
action: /HomeAction.send,
environment: { environment in
SendEnvironment(
SendFlowEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
scheduler: environment.scheduler,
walletStorage: environment.walletStorage,
wrappedDerivationTool: environment.wrappedDerivationTool,
wrappedSDKSynchronizer: environment.wrappedSDKSynchronizer
derivationTool: environment.derivationTool,
SDKSynchronizer: environment.SDKSynchronizer
)
}
)
}
// MARK: - HomeViewStore
typealias HomeViewStore = ViewStore<HomeState, HomeAction>
extension HomeViewStore {
func bindingForRoute(_ route: HomeState.Route) -> Binding<Bool> {
self.binding(
get: { $0.route == route },
send: { isActive in
return .updateRoute(isActive ? route : nil)
}
)
}
func bindingForDrawer() -> Binding<DrawerOverlay> {
self.binding(
get: { $0.drawerOverlay },
send: { .updateDrawer($0) }
)
}
}
// MARK: - HomeStore
typealias HomeStore = Store<HomeState, HomeAction>
// MARK: - Store
extension HomeStore {
func historyStore() -> TransactionHistoryStore {
func historyStore() -> TransactionHistoryFlowStore {
self.scope(
state: \.transactionHistoryState,
action: HomeAction.transactionHistory
@ -218,7 +202,7 @@ extension HomeStore {
)
}
func sendStore() -> SendStore {
func sendStore() -> SendFlowStore {
self.scope(
state: \.sendState,
action: HomeAction.send
@ -233,7 +217,27 @@ extension HomeStore {
}
}
// MARK: PlaceHolders
// MARK: - ViewStore
extension HomeViewStore {
func bindingForRoute(_ route: HomeState.Route) -> Binding<Bool> {
self.binding(
get: { $0.route == route },
send: { isActive in
return .updateRoute(isActive ? route : nil)
}
)
}
func bindingForDrawer() -> Binding<DrawerOverlay> {
self.binding(
get: { $0.drawerOverlay },
send: { .updateDrawer($0) }
)
}
}
// MARK: Placeholders
extension HomeState {
static var placeholder: Self {
@ -251,38 +255,18 @@ extension HomeState {
}
}
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)"
}
extension HomeStore {
static var placeholder: HomeStore {
HomeStore(
initialState: .placeholder,
reducer: .default.debug(),
environment: HomeEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}
}

View File

@ -30,7 +30,7 @@ struct HomeView: View {
if proxy.size.height > 0 {
Drawer(overlay: viewStore.bindingForDrawer(), maxHeight: proxy.size.height) {
VStack {
TransactionHistoryView(store: store.historyStore())
TransactionHistoryFlowView(store: store.historyStore())
.padding(.top, 10)
Spacer()
@ -93,7 +93,7 @@ extension HomeView {
.navigationLink(
isActive: viewStore.bindingForRoute(.send),
destination: {
SendView(store: store.sendStore())
SendFlowView(store: store.sendStore())
}
)
@ -126,22 +126,6 @@ extension HomeView {
// MARK: - Previews
extension HomeStore {
static var placeholder: HomeStore {
HomeStore(
initialState: .placeholder,
reducer: .default.debug(),
environment: HomeEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {

View File

@ -8,13 +8,19 @@
import ComposableArchitecture
import ZcashLightClientKit
typealias ImportWalletReducer = Reducer<ImportWalletState, ImportWalletAction, ImportWalletEnvironment>
typealias ImportWalletStore = Store<ImportWalletState, ImportWalletAction>
typealias ImportWalletViewStore = ViewStore<ImportWalletState, ImportWalletAction>
// MARK: - State
struct ImportWalletState: Equatable {
@BindableState var alert: AlertState<ImportWalletAction>?
@BindableState var importedSeedPhrase: String = ""
}
// MARK: - Action
enum ImportWalletAction: Equatable, BindableAction {
case binding(BindingAction<ImportWalletState>)
case dismissAlert
@ -24,9 +30,11 @@ enum ImportWalletAction: Equatable, BindableAction {
case successfullyRecovered
}
// MARK: - Environment
struct ImportWalletEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: WalletStorageInteractor
let mnemonicSeedPhraseProvider: WrappedMnemonic
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
@ -44,7 +52,7 @@ extension ImportWalletEnvironment {
)
}
typealias ImportWalletReducer = Reducer<ImportWalletState, ImportWalletAction, ImportWalletEnvironment>
// MARK: - Reducer
extension ImportWalletReducer {
static let `default` = ImportWalletReducer { state, action, environment in
@ -106,3 +114,19 @@ extension ImportWalletReducer {
}
.binding()
}
// MARK: - Placeholders
extension ImportWalletState {
static let placeholder = ImportWalletState(importedSeedPhrase: "")
static let live = ImportWalletState(importedSeedPhrase: "")
}
extension ImportWalletStore {
static let demo = Store(
initialState: .placeholder,
reducer: .default,
environment: .demo
)
}

View File

@ -81,19 +81,7 @@ extension View {
}
}
extension ImportWalletStore {
static let demo = Store(
initialState: .placeholder,
reducer: .default,
environment: .demo
)
}
extension ImportWalletState {
static let placeholder = ImportWalletState(importedSeedPhrase: "")
static let live = ImportWalletState(importedSeedPhrase: "")
}
// MARK: - Previews
struct ImportWalletView_Previews: PreviewProvider {
static var previews: some View {

View File

@ -1,105 +0,0 @@
//
// Onboarding.swift
// secant-testnet
//
// Created by Adam Stener on 10/12/21.
//
import SwiftUI
import ComposableArchitecture
struct OnboardingView: View {
let store: Store<OnboardingState, OnboardingAction>
var body: some View {
WithViewStore(self.store) { viewStore in
VStack(spacing: 50) {
HStack(spacing: 50) {
Button("Back") { viewStore.send(.back) }
.disabled(viewStore.isInitialStep)
Spacer()
Button("Next") { viewStore.send(.next) }
Button("Skip") { viewStore.send(.skip) }
.disabled(viewStore.isFinalStep)
}
.frame(height: 100)
.padding(.horizontal, 50)
Spacer()
Text(viewStore.currentStep.title)
.frame(maxWidth: .infinity)
.offset(y: viewStore.offset)
.animation(.easeOut(duration: 0.4))
Spacer()
VStack {
Text(viewStore.currentStep.description)
ProgressView(
"Progress \(viewStore.progress)%",
value: Double(viewStore.index + 1),
total: Double(viewStore.steps.count)
)
.padding(.horizontal, 25)
.padding(.vertical, 50)
}
.animation(.easeOut(duration: 0.2))
}
}
}
}
extension OnboardingState {
static let onboardingSteps = IdentifiedArray(
uniqueElements: [
Step(
id: UUID(),
title: "onboarding.step1.title",
description: "onboarding.step1.description",
background: Asset.Assets.Backgrounds.callout1.image,
badge: .shield
),
Step(
id: UUID(),
title: "onboarding.step2.title",
description: "onboarding.step2.description",
background: Asset.Assets.Backgrounds.callout2.image,
badge: .person
),
Step(
id: UUID(),
title: "onboarding.step3.title",
description: "onboarding.step3.description",
background: Asset.Assets.Backgrounds.callout3.image,
badge: .list
),
Step(
id: UUID(),
title: "onboarding.step4.title",
description: "onboarding.step4.description",
background: Asset.Assets.Backgrounds.callout4.image,
badge: .shield
)
]
)
}
struct Onboarding_Previews: PreviewProvider {
static var previews: some View {
Group {
OnboardingView(
store: Store(
initialState: OnboardingState(
importWalletState: .placeholder
),
reducer: .default,
environment: (.demo)
)
)
}
}
}

View File

@ -9,9 +9,13 @@ import Foundation
import SwiftUI
import ComposableArchitecture
typealias OnboardingViewStore = ViewStore<OnboardingState, OnboardingAction>
typealias OnboardingFlowReducer = Reducer<OnboardingFlowState, OnboardingFlowAction, OnboardingFlowEnvironment>
typealias OnboardingFlowStore = Store<OnboardingFlowState, OnboardingFlowAction>
typealias OnboardingFlowViewStore = ViewStore<OnboardingFlowState, OnboardingFlowAction>
struct OnboardingState: Equatable {
// MARK: - State
struct OnboardingFlowState: Equatable {
enum Route: Equatable, CaseIterable {
case createNewWallet
case importExistingWallet
@ -45,58 +49,86 @@ struct OnboardingState: Equatable {
var importWalletState: ImportWalletState
}
extension OnboardingViewStore {
func bindingForRoute(_ route: OnboardingState.Route) -> Binding<Bool> {
self.binding(
get: { $0.route == route },
send: { isActive in
return .updateRoute(isActive ? route : nil)
}
)
}
extension OnboardingFlowState {
static let onboardingSteps = IdentifiedArray(
uniqueElements: [
Step(
id: UUID(),
title: "onboarding.step1.title",
description: "onboarding.step1.description",
background: Asset.Assets.Backgrounds.callout1.image,
badge: .shield
),
Step(
id: UUID(),
title: "onboarding.step2.title",
description: "onboarding.step2.description",
background: Asset.Assets.Backgrounds.callout2.image,
badge: .person
),
Step(
id: UUID(),
title: "onboarding.step3.title",
description: "onboarding.step3.description",
background: Asset.Assets.Backgrounds.callout3.image,
badge: .list
),
Step(
id: UUID(),
title: "onboarding.step4.title",
description: "onboarding.step4.description",
background: Asset.Assets.Backgrounds.callout4.image,
badge: .shield
)
]
)
}
enum OnboardingAction: Equatable {
// MARK: - Action
enum OnboardingFlowAction: Equatable {
case next
case back
case skip
case updateRoute(OnboardingState.Route?)
case updateRoute(OnboardingFlowState.Route?)
case createNewWallet
case importExistingWallet
case importWallet(ImportWalletAction)
}
struct OnboardingEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: WalletStorageInteractor
// MARK: - Environment
struct OnboardingFlowEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension OnboardingEnvironment {
static let live = OnboardingEnvironment(
extension OnboardingFlowEnvironment {
static let live = OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
walletStorage: .live(),
zcashSDKEnvironment: .mainnet
)
static let demo = OnboardingEnvironment(
static let demo = OnboardingFlowEnvironment(
mnemonicSeedPhraseProvider: .mock,
walletStorage: .live(),
zcashSDKEnvironment: .testnet
)
}
typealias OnboardingReducer = Reducer<OnboardingState, OnboardingAction, OnboardingEnvironment>
// MARK: - Reducer
extension OnboardingReducer {
static let `default` = OnboardingReducer.combine(
extension OnboardingFlowReducer {
static let `default` = OnboardingFlowReducer.combine(
[
onboardingReducer,
importWalletReducer
]
)
private static let onboardingReducer = OnboardingReducer { state, action, _ in
private static let onboardingReducer = OnboardingFlowReducer { state, action, _ in
switch action {
case .back:
guard state.index > 0 else { return .none }
@ -136,9 +168,9 @@ extension OnboardingReducer {
}
}
private static let importWalletReducer: OnboardingReducer = ImportWalletReducer.default.pullback(
state: \OnboardingState.importWalletState,
action: /OnboardingAction.importWallet,
private static let importWalletReducer: OnboardingFlowReducer = ImportWalletReducer.default.pullback(
state: \OnboardingFlowState.importWalletState,
action: /OnboardingFlowAction.importWallet,
environment: { environment in
ImportWalletEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
@ -148,3 +180,16 @@ extension OnboardingReducer {
}
)
}
// MARK: - ViewStore
extension OnboardingFlowViewStore {
func bindingForRoute(_ route: OnboardingFlowState.Route) -> Binding<Bool> {
self.binding(
get: { $0.route == route },
send: { isActive in
return .updateRoute(isActive ? route : nil)
}
)
}
}

View File

@ -9,7 +9,7 @@ import SwiftUI
import ComposableArchitecture
struct OnboardingScreen: View {
let store: Store<OnboardingState, OnboardingAction>
let store: Store<OnboardingFlowState, OnboardingFlowAction>
var body: some View {
GeometryReader { proxy in
@ -46,14 +46,16 @@ struct OnboardingScreen: View {
}
}
// MARK: - Previews
struct OnboardingScreen_Previews: PreviewProvider {
static var previews: some View {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)
@ -62,10 +64,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)
@ -74,10 +76,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)
@ -86,10 +88,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)
@ -98,10 +100,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)
@ -110,10 +112,10 @@ struct OnboardingScreen_Previews: PreviewProvider {
OnboardingScreen(
store: Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
)

View File

@ -9,7 +9,7 @@ import SwiftUI
import ComposableArchitecture
struct OnboardingContentView: View {
let store: Store<OnboardingState, OnboardingAction>
let store: Store<OnboardingFlowState, OnboardingFlowAction>
let width: Double
let height: Double
@ -127,11 +127,11 @@ extension OnboardingContentView {
struct OnboardingContentView_Previews: PreviewProvider {
static var previews: some View {
let store = Store(
initialState: OnboardingState(
initialState: OnboardingFlowState(
index: 0,
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)
@ -146,8 +146,10 @@ struct OnboardingContentView_Previews: PreviewProvider {
}
}
// MARK: - Previews
extension OnboardingContentView_Previews {
static func example(_ store: Store<OnboardingState, OnboardingAction>) -> some View {
static func example(_ store: Store<OnboardingFlowState, OnboardingFlowAction>) -> some View {
GeometryReader { proxy in
ZStack {
OnboardingHeaderView(

View File

@ -9,7 +9,7 @@ import SwiftUI
import ComposableArchitecture
struct OnboardingFooterView: View {
let store: Store<OnboardingState, OnboardingAction>
let store: Store<OnboardingFlowState, OnboardingFlowAction>
let animationDuration: CGFloat = 0.8
var body: some View {
@ -58,7 +58,7 @@ struct OnboardingFooterView: View {
ImportWalletView(
store: store.scope(
state: \.importWalletState,
action: OnboardingAction.importWallet
action: OnboardingFlowAction.importWallet
)
)
}
@ -83,14 +83,16 @@ extension View {
}
}
// MARK: - Previews
struct OnboardingFooterView_Previews: PreviewProvider {
static var previews: some View {
let store = Store<OnboardingState, OnboardingAction>(
initialState: OnboardingState(
let store = Store<OnboardingFlowState, OnboardingFlowAction>(
initialState: OnboardingFlowState(
index: 3,
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)

View File

@ -60,14 +60,16 @@ struct OnboardingHeaderView: View {
}
}
// MARK: - Previews
struct OnboardingHeaderView_Previews: PreviewProvider {
static var previews: some View {
let store = Store<OnboardingState, OnboardingAction>(
initialState: OnboardingState(
let store = Store<OnboardingFlowState, OnboardingFlowAction>(
initialState: OnboardingFlowState(
index: 0,
importWalletState: .placeholder
),
reducer: OnboardingReducer.default,
reducer: OnboardingFlowReducer.default,
environment: (.demo)
)

View File

@ -1,6 +1,12 @@
import ComposableArchitecture
import SwiftUI
typealias ProfileReducer = Reducer<ProfileState, ProfileAction, ProfileEnvironment>
typealias ProfileStore = Store<ProfileState, ProfileAction>
typealias ProfileViewStore = ViewStore<ProfileState, ProfileAction>
// MARK: - State
struct ProfileState: Equatable {
enum Route {
case settings
@ -12,16 +18,17 @@ struct ProfileState: Equatable {
var route: Route?
}
// MARK: - Action
enum ProfileAction: Equatable {
case updateRoute(ProfileState.Route?)
}
struct ProfileEnvironment {
}
// MARK: - Environment
// MARK: - ProfileReducer
struct ProfileEnvironment { }
typealias ProfileReducer = Reducer<ProfileState, ProfileAction, ProfileEnvironment>
// MARK: - Reducer
extension ProfileReducer {
static let `default` = ProfileReducer { state, action, _ in
@ -33,16 +40,7 @@ extension ProfileReducer {
}
}
// MARK: - ProfileStore
typealias ProfileStore = Store<ProfileState, ProfileAction>
extension ProfileStore {
}
// MARK: - ProfileViewStore
typealias ProfileViewStore = ViewStore<ProfileState, ProfileAction>
// MARK: - ViewStore
extension ProfileViewStore {
var routeBinding: Binding<ProfileState.Route?> {
@ -67,7 +65,7 @@ extension ProfileViewStore {
}
}
// MARK: PlaceHolders
// MARK: Placeholders
extension ProfileState {
static var placeholder: Self {

View File

@ -28,6 +28,8 @@ struct ProfileView: View {
}
}
// MARK: - Previews
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {

View File

@ -7,57 +7,37 @@
import Foundation
import ComposableArchitecture
import UIKit
enum RecoveryPhraseError: Error {
/// This error is thrown then the Recovery Phrase can't be generated
case unableToGeneratePhrase
typealias RecoveryPhraseDisplayReducer = Reducer<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction, RecoveryPhraseDisplayEnvironment>
typealias RecoveryPhraseDisplayStore = Store<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction>
typealias RecoveryPhraseDisplayViewStore = ViewStore<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction>
// MARK: - State
struct RecoveryPhraseDisplayState: Equatable {
var phrase: RecoveryPhrase?
var showCopyToBufferAlert = false
}
struct FeedbackGenerator {
let generateFeedback: () -> Void
// MARK: - Action
enum RecoveryPhraseDisplayAction: Equatable {
case createPhrase
case copyToBufferPressed
case finishedPressed
case phraseResponse(Result<RecoveryPhrase, RecoveryPhraseError>)
}
extension FeedbackGenerator {
static let haptic = FeedbackGenerator(
generateFeedback: { UINotificationFeedbackGenerator().notificationOccurred(.error) }
)
static let silent = FeedbackGenerator(
generateFeedback: { }
)
}
// MARK: - Environment
struct Pasteboard {
let setString: (String) -> Void
let getString: () -> String?
}
extension Pasteboard {
private struct TestPasteboard {
static var general = TestPasteboard()
var string: String?
}
static let live = Pasteboard(
setString: { UIPasteboard.general.string = $0 },
getString: { UIPasteboard.general.string }
)
static let test = Pasteboard(
setString: { TestPasteboard.general.string = $0 },
getString: { TestPasteboard.general.string }
)
}
struct BackupPhraseEnvironment {
struct RecoveryPhraseDisplayEnvironment {
let mainQueue: AnySchedulerOf<DispatchQueue>
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
let pasteboard: Pasteboard
let feedbackGenerator: FeedbackGenerator
let pasteboard: WrappedPasteboard
let feedbackGenerator: WrappedFeedbackGenerator
}
extension BackupPhraseEnvironment {
extension RecoveryPhraseDisplayEnvironment {
private struct DemoPasteboard {
static var general = Self()
var string: String?
@ -78,51 +58,7 @@ extension BackupPhraseEnvironment {
)
}
typealias RecoveryPhraseDisplayStore = Store<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction>
struct RecoveryPhrase: Equatable {
struct Group: Hashable {
var startIndex: Int
var words: [String]
}
let words: [String]
private let groupSize = 6
func toGroups() -> [Group] {
let chunks = words.count / groupSize
return zip(0 ..< chunks, words.chunked(into: groupSize)).map {
Group(startIndex: $0 * groupSize + 1, words: $1)
}
}
func toString() -> String {
words.joined(separator: " ")
}
func words(fromMissingIndices indices: [Int]) -> [PhraseChip.Kind] {
assert((indices.count - 1) * groupSize <= self.words.count)
return indices.enumerated().map { index, position in
.unassigned(word: self.words[(index * groupSize) + position])
}
}
}
struct RecoveryPhraseDisplayState: Equatable {
var phrase: RecoveryPhrase?
var showCopyToBufferAlert = false
}
enum RecoveryPhraseDisplayAction: Equatable {
case createPhrase
case copyToBufferPressed
case finishedPressed
case phraseResponse(Result<RecoveryPhrase, RecoveryPhraseError>)
}
typealias RecoveryPhraseDisplayReducer = Reducer<RecoveryPhraseDisplayState, RecoveryPhraseDisplayAction, BackupPhraseEnvironment>
// MARK: - Reducer
extension RecoveryPhraseDisplayReducer {
static let `default` = RecoveryPhraseDisplayReducer { state, action, environment in
@ -148,11 +84,3 @@ extension RecoveryPhraseDisplayReducer {
}
}
}
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}

View File

@ -9,19 +9,17 @@ import Foundation
import ComposableArchitecture
import SwiftUI
typealias RecoveryPhraseValidationStore = Store<RecoveryPhraseValidationState, RecoveryPhraseValidationAction>
typealias RecoveryPhraseValidationViewStore = ViewStore<RecoveryPhraseValidationState, RecoveryPhraseValidationAction>
/// Represents the data of a word that has been placed into an empty position, that will be used
/// to validate the completed phrase when all ValidationWords have been placed.
struct ValidationWord: Equatable {
var groupIndex: Int
var word: String
}
typealias RecoveryPhraseValidationFlowReducer = Reducer<
RecoveryPhraseValidationFlowState,
RecoveryPhraseValidationFlowAction,
RecoveryPhraseValidationFlowEnvironment
>
typealias RecoveryPhraseValidationFlowStore = Store<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
typealias RecoveryPhraseValidationFlowViewStore = ViewStore<RecoveryPhraseValidationFlowState, RecoveryPhraseValidationFlowAction>
// MARK: - State
struct RecoveryPhraseValidationState: Equatable {
struct RecoveryPhraseValidationFlowState: Equatable {
enum Route: Equatable, CaseIterable {
case validation
case success
@ -47,14 +45,14 @@ struct RecoveryPhraseValidationState: Equatable {
}
}
extension RecoveryPhraseValidationState {
extension RecoveryPhraseValidationFlowState {
/// creates an initial `RecoveryPhraseValidationState` with no completions and random missing indices.
/// - Note: Use this function to create a random validation puzzle for a given phrase.
static func random(phrase: RecoveryPhrase) -> Self {
let missingIndices = Self.randomIndices()
let missingWordChipKind = phrase.words(fromMissingIndices: missingIndices).shuffled()
return RecoveryPhraseValidationState(
return RecoveryPhraseValidationFlowState(
phrase: phrase,
missingIndices: missingIndices,
missingWordChips: missingWordChipKind,
@ -63,7 +61,7 @@ extension RecoveryPhraseValidationState {
}
}
extension RecoveryPhraseValidationState {
extension RecoveryPhraseValidationFlowState {
/// Given an array of RecoveryPhraseStepCompletion, missing indices, original phrase and the number of groups it was split into,
/// assembly the resulting phrase. This comes up with the "proposed solution" for the recovery phrase validation challenge.
/// - returns:an array of String containing the recovery phrase words ordered by the original phrase order, or `nil`
@ -121,8 +119,8 @@ extension RecoveryPhrase.Group {
// MARK: - Action
enum RecoveryPhraseValidationAction: Equatable {
case updateRoute(RecoveryPhraseValidationState.Route?)
enum RecoveryPhraseValidationFlowAction: Equatable {
case updateRoute(RecoveryPhraseValidationFlowState.Route?)
case reset
case move(wordChip: PhraseChip.Kind, intoGroup: Int)
case succeed
@ -132,15 +130,43 @@ enum RecoveryPhraseValidationAction: Equatable {
case displayBackedUpPhrase
}
// MARK: - Environment
struct RecoveryPhraseValidationFlowEnvironment {
let mainQueue: AnySchedulerOf<DispatchQueue>
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
let pasteboard: WrappedPasteboard
let feedbackGenerator: WrappedFeedbackGenerator
}
extension RecoveryPhraseValidationFlowEnvironment {
private struct DemoPasteboard {
static var general = Self()
var string: String?
}
static let demo = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) },
pasteboard: .test,
feedbackGenerator: .silent
)
static let live = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) },
pasteboard: .live,
feedbackGenerator: .haptic
)
}
// MARK: - Reducer
typealias RecoveryPhraseValidationReducer = Reducer<RecoveryPhraseValidationState, RecoveryPhraseValidationAction, BackupPhraseEnvironment>
extension RecoveryPhraseValidationReducer {
static let `default` = RecoveryPhraseValidationReducer { state, action, environment in
extension RecoveryPhraseValidationFlowReducer {
static let `default` = RecoveryPhraseValidationFlowReducer { state, action, environment in
switch action {
case .reset:
state = RecoveryPhraseValidationState.random(phrase: state.phrase)
state = RecoveryPhraseValidationFlowState.random(phrase: state.phrase)
state.route = .validation
// FIXME: Resetting causes route to be nil = preamble screen, hence setting the .validation. The transition back is not animated though (issue 186)
@ -154,8 +180,8 @@ extension RecoveryPhraseValidationReducer {
state.validationWords.append(ValidationWord(groupIndex: group, word: word))
if state.isComplete {
let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail
let effect = Effect<RecoveryPhraseValidationAction, Never>(value: value)
let value: RecoveryPhraseValidationFlowAction = state.isValid ? .succeed : .fail
let effect = Effect<RecoveryPhraseValidationFlowAction, Never>(value: value)
.delay(for: 1, scheduler: environment.mainQueue)
.eraseToEffect()
@ -181,7 +207,7 @@ extension RecoveryPhraseValidationReducer {
case .updateRoute(let route):
guard let route = route else {
state = RecoveryPhraseValidationState.random(phrase: state.phrase)
state = RecoveryPhraseValidationFlowState.random(phrase: state.phrase)
return .none
}
state.route = route
@ -198,8 +224,8 @@ extension RecoveryPhraseValidationReducer {
// MARK: - ViewStore
extension RecoveryPhraseValidationViewStore {
func bindingForRoute(_ route: RecoveryPhraseValidationState.Route) -> Binding<Bool> {
extension RecoveryPhraseValidationFlowViewStore {
func bindingForRoute(_ route: RecoveryPhraseValidationFlowState.Route) -> Binding<Bool> {
self.binding(
get: { $0.route == route },
send: { isActive in
@ -209,7 +235,7 @@ extension RecoveryPhraseValidationViewStore {
}
}
extension RecoveryPhraseValidationViewStore {
extension RecoveryPhraseValidationFlowViewStore {
var bindingForValidation: Binding<Bool> {
self.binding(
get: { $0.route != nil },
@ -237,3 +263,109 @@ extension RecoveryPhraseValidationViewStore {
)
}
}
// MARK: - Placeholders
extension RecoveryPhraseValidationFlowState {
static let placeholder = RecoveryPhraseValidationFlowState.random(phrase: .placeholder)
static let placeholderStep1 = RecoveryPhraseValidationFlowState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.unassigned(word: "thank"),
.empty,
.unassigned(word: "boil"),
.unassigned(word: "garlic")
],
validationWords: [
.init(groupIndex: 2, word: "morning")
],
route: nil
)
static let placeholderStep2 = RecoveryPhraseValidationFlowState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil"),
.unassigned(word: "garlic")
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank")
],
route: nil
)
static let placeholderStep3 = RecoveryPhraseValidationFlowState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil"),
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank"),
.init(groupIndex: 3, word: "garlic")
],
route: nil
)
static let placeholderStep4 = RecoveryPhraseValidationFlowState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.empty,
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank"),
.init(groupIndex: 3, word: "garlic"),
.init(groupIndex: 1, word: "boil")
],
route: nil
)
}
extension RecoveryPhraseValidationFlowStore {
private static let scheduler = DispatchQueue.main
static let demo = Store(
initialState: .placeholder,
reducer: .default,
environment: .demo
)
static let demoStep1 = Store(
initialState: .placeholderStep1,
reducer: .default,
environment: .demo
)
static let demoStep2 = Store(
initialState: .placeholderStep1,
reducer: .default,
environment: .demo
)
static let demoStep3 = Store(
initialState: .placeholderStep3,
reducer: .default,
environment: .demo
)
static let demoStep4 = Store(
initialState: .placeholderStep4,
reducer: .default,
environment: .demo
)
}

View File

@ -1,5 +1,5 @@
//
// RecoveryPhraseTestPreambleView.swift
// RecoveryPhraseValidationFlowView.swift
// secant-testnet
//
// Created by Lukáš Korba on 03/01/22.
@ -8,8 +8,8 @@
import SwiftUI
import ComposableArchitecture
struct RecoveryPhraseTestPreambleView: View {
var store: RecoveryPhraseValidationStore
struct RecoveryPhraseValidationFlowView: View {
var store: RecoveryPhraseValidationFlowStore
var body: some View {
WithViewStore(store) { viewStore in
@ -77,7 +77,7 @@ struct RecoveryPhraseTestPreambleView: View {
.navigationLinkEmpty(
isActive: viewStore.bindingForValidation,
destination: {
RecoveryPhraseBackupValidationView(store: store)
RecoveryPhraseBackupView(store: store)
}
)
}
@ -91,7 +91,7 @@ struct RecoveryPhraseTestPreambleView: View {
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (apects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension RecoveryPhraseTestPreambleView {
extension RecoveryPhraseValidationFlowView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
@ -108,16 +108,16 @@ struct RecoveryPhraseTestPreambleView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
RecoveryPhraseTestPreambleView(store: .demo)
RecoveryPhraseValidationFlowView(store: .demo)
}
RecoveryPhraseTestPreambleView(store: .demo)
RecoveryPhraseValidationFlowView(store: .demo)
.preferredColorScheme(.dark)
RecoveryPhraseTestPreambleView(store: .demo)
RecoveryPhraseValidationFlowView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
RecoveryPhraseTestPreambleView(store: .demo)
RecoveryPhraseValidationFlowView(store: .demo)
.environment(\.sizeCategory, .accessibilityLarge)
}
}

View File

@ -8,10 +8,10 @@
import SwiftUI
import ComposableArchitecture
struct ValidationFailedView: View {
struct RecoveryPhraseBackupFailedView: View {
@Environment(\.presentationMode) var presentationMode
var store: RecoveryPhraseValidationStore
var store: RecoveryPhraseValidationFlowStore
var body: some View {
WithViewStore(store) { viewStore in
@ -82,7 +82,7 @@ struct ValidationFailedView: View {
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (apects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension ValidationFailedView {
extension RecoveryPhraseBackupFailedView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
@ -95,23 +95,25 @@ extension ValidationFailedView {
}
}
struct ValidationFailed_Previews: PreviewProvider {
// MARK: - Previews
struct RecoveryPhraseBackupValidationFailedView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
ValidationFailedView(store: .demo)
RecoveryPhraseBackupFailedView(store: .demo)
}
ValidationFailedView(store: .demo)
RecoveryPhraseBackupFailedView(store: .demo)
.preferredColorScheme(.dark)
ValidationFailedView(store: .demo)
RecoveryPhraseBackupFailedView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
ValidationFailedView(store: .demo)
RecoveryPhraseBackupFailedView(store: .demo)
.environment(\.sizeCategory, .accessibilityLarge)
ValidationFailedView(store: .demo)
RecoveryPhraseBackupFailedView(store: .demo)
.environment(\.sizeCategory, .accessibilityLarge)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}

View File

@ -8,8 +8,8 @@
import SwiftUI
import ComposableArchitecture
struct ValidationSucceededView: View {
var store: RecoveryPhraseValidationStore
struct RecoveryPhraseBackupSucceededView: View {
var store: RecoveryPhraseValidationFlowStore
var body: some View {
WithViewStore(store) { viewStore in
@ -53,7 +53,7 @@ struct ValidationSucceededView: View {
}
)
.activeButtonStyle
.validationSucceededViewLayout()
.recoveryPhraseBackupValidationSucceededViewLayout()
Button(
action: {
@ -68,7 +68,7 @@ struct ValidationSucceededView: View {
}
)
.secondaryButtonStyle
.validationSucceededViewLayout()
.recoveryPhraseBackupValidationSucceededViewLayout()
}
}
.padding(.horizontal)
@ -83,7 +83,7 @@ struct ValidationSucceededView: View {
/// Following computations are necessary to handle properly sizing and positioning of elements
/// on different devices (apects). iPhone SE and iPhone 8 are similar aspect family devices
/// while iPhone X, 11, etc are different family devices, capable to use more of the space.
extension ValidationSucceededView {
extension RecoveryPhraseBackupSucceededView {
func circularFrameUniformSize(width: CGFloat, height: CGFloat) -> CGFloat {
var deviceMultiplier = 1.0
@ -96,8 +96,8 @@ extension ValidationSucceededView {
}
}
// swiftlint:disable:next private_over_fileprivate strict_fileprivate
fileprivate struct ValidationSucceededViewLayout: ViewModifier {
// swiftlint:disable:next private_over_fileprivate strict_fileprivate type_name
fileprivate struct RecoveryPhraseBackupValidationSucceededViewLayout: ViewModifier {
func body(content: Content) -> some View {
content
.frame(
@ -114,28 +114,31 @@ fileprivate struct ValidationSucceededViewLayout: ViewModifier {
}
extension View {
func validationSucceededViewLayout() -> some View {
modifier(ValidationSucceededViewLayout())
func recoveryPhraseBackupValidationSucceededViewLayout() -> some View {
modifier(RecoveryPhraseBackupValidationSucceededViewLayout())
}
}
struct ValidationSuccededView_Previews: PreviewProvider {
// MARK: - Previews
// swiftlint:disable:next type_name
struct RecoveryPhraseBackupValidationSucceededView_Previews: PreviewProvider {
static var previews: some View {
Group {
NavigationView {
ValidationSucceededView(store: .demo)
RecoveryPhraseBackupSucceededView(store: .demo)
}
ValidationSucceededView(store: .demo)
RecoveryPhraseBackupSucceededView(store: .demo)
.preferredColorScheme(.dark)
ValidationSucceededView(store: .demo)
RecoveryPhraseBackupSucceededView(store: .demo)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
ValidationSucceededView(store: .demo)
RecoveryPhraseBackupSucceededView(store: .demo)
.environment(\.sizeCategory, .accessibilityLarge)
ValidationSucceededView(store: .demo)
RecoveryPhraseBackupSucceededView(store: .demo)
.environment(\.sizeCategory, .accessibilityLarge)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}

View File

@ -8,10 +8,10 @@
import SwiftUI
import ComposableArchitecture
struct RecoveryPhraseBackupValidationView: View {
let store: RecoveryPhraseValidationStore
struct RecoveryPhraseBackupView: View {
let store: RecoveryPhraseValidationFlowStore
var viewStore: RecoveryPhraseValidationViewStore {
var viewStore: RecoveryPhraseValidationFlowViewStore {
ViewStore(store)
}
@ -52,11 +52,11 @@ struct RecoveryPhraseBackupValidationView: View {
.padding(.top, 0)
.navigationLinkEmpty(
isActive: viewStore.bindingForSuccess,
destination: { ValidationSucceededView(store: store) }
destination: { RecoveryPhraseBackupSucceededView(store: store) }
)
.navigationLinkEmpty(
isActive: viewStore.bindingForFailure,
destination: { ValidationFailedView(store: store) }
destination: { RecoveryPhraseBackupFailedView(store: store) }
)
}
.frame(alignment: .top)
@ -66,8 +66,10 @@ struct RecoveryPhraseBackupValidationView: View {
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(Text("recoveryPhraseBackupValidation.title"))
}
}
@ViewBuilder func header(for viewStore: RecoveryPhraseValidationViewStore) -> some View {
private extension RecoveryPhraseBackupView {
@ViewBuilder func header(for viewStore: RecoveryPhraseValidationFlowViewStore) -> some View {
VStack {
if viewStore.isComplete {
completeHeader(for: viewStore.state)
@ -81,7 +83,7 @@ struct RecoveryPhraseBackupValidationView: View {
.padding(.horizontal, 30)
}
@ViewBuilder func completeHeader(for state: RecoveryPhraseValidationState) -> some View {
@ViewBuilder func completeHeader(for state: RecoveryPhraseValidationFlowState) -> some View {
if state.isValid {
Text("recoveryPhraseBackupValidation.successResult")
.bodyText()
@ -92,7 +94,7 @@ struct RecoveryPhraseBackupValidationView: View {
}
}
private extension RecoveryPhraseValidationState {
private extension RecoveryPhraseValidationFlowState {
@ViewBuilder func missingWordGrid() -> some View {
let columns = Array(
repeating: GridItem(.flexible(minimum: 100, maximum: 120), spacing: 20),
@ -114,7 +116,7 @@ private extension RecoveryPhraseValidationState {
}
}
extension RecoveryPhraseValidationState {
extension RecoveryPhraseValidationFlowState {
func wordsChips(
for groupIndex: Int,
groupSize: Int,
@ -136,120 +138,16 @@ extension RecoveryPhraseValidationState {
}
}
extension RecoveryPhraseValidationState {
static let placeholder = RecoveryPhraseValidationState.random(phrase: .placeholder)
static let placeholderStep1 = RecoveryPhraseValidationState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.unassigned(word: "thank"),
.empty,
.unassigned(word: "boil"),
.unassigned(word: "garlic")
],
validationWords: [
.init(groupIndex: 2, word: "morning")
],
route: nil
)
static let placeholderStep2 = RecoveryPhraseValidationState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil"),
.unassigned(word: "garlic")
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank")
],
route: nil
)
static let placeholderStep3 = RecoveryPhraseValidationState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.unassigned(word: "boil"),
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank"),
.init(groupIndex: 3, word: "garlic")
],
route: nil
)
static let placeholderStep4 = RecoveryPhraseValidationState(
phrase: .placeholder,
missingIndices: [2, 0, 3, 5],
missingWordChips: [
.empty,
.empty,
.empty,
.empty
],
validationWords: [
.init(groupIndex: 2, word: "morning"),
.init(groupIndex: 0, word: "thank"),
.init(groupIndex: 3, word: "garlic"),
.init(groupIndex: 1, word: "boil")
],
route: nil
)
}
extension RecoveryPhraseValidationStore {
private static let scheduler = DispatchQueue.main
static let demo = Store(
initialState: .placeholder,
reducer: .default,
environment: .demo
)
static let demoStep1 = Store(
initialState: .placeholderStep1,
reducer: .default,
environment: .demo
)
static let demoStep2 = Store(
initialState: .placeholderStep1,
reducer: .default,
environment: .demo
)
static let demoStep3 = Store(
initialState: .placeholderStep3,
reducer: .default,
environment: .demo
)
static let demoStep4 = Store(
initialState: .placeholderStep4,
reducer: .default,
environment: .demo
)
}
private extension WordChipGrid {
init(
state: RecoveryPhraseValidationState,
state: RecoveryPhraseValidationFlowState,
groupIndex: Int,
wordGroup: RecoveryPhrase.Group,
misingIndex: Int
) {
let chips = state.wordsChips(
for: groupIndex,
groupSize: RecoveryPhraseValidationState.wordGroupSize,
groupSize: RecoveryPhraseValidationFlowState.wordGroupSize,
from: wordGroup
)
@ -257,7 +155,7 @@ private extension WordChipGrid {
}
}
private extension RecoveryPhraseValidationState {
private extension RecoveryPhraseValidationFlowState {
var coloredChipColor: Color {
if self.isComplete {
return isValid ? Asset.Colors.Buttons.activeButton.color : Asset.Colors.BackgroundColors.red.color
@ -267,18 +165,20 @@ private extension RecoveryPhraseValidationState {
}
}
// MARK: - Previews
struct RecoveryPhraseBackupView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
RecoveryPhraseBackupValidationView(store: .demoStep4)
RecoveryPhraseValidationFlowView(store: .demoStep4)
}
NavigationView {
RecoveryPhraseBackupValidationView(store: .demoStep1)
RecoveryPhraseValidationFlowView(store: .demoStep1)
}
NavigationView {
RecoveryPhraseBackupValidationView(store: .demoStep1)
RecoveryPhraseValidationFlowView(store: .demoStep1)
}
.preferredColorScheme(.dark)
}

View File

@ -1,18 +1,25 @@
import ComposableArchitecture
typealias RequestReducer = Reducer<RequestState, RequestAction, RequestEnvironment>
typealias RequestStore = Store<RequestState, RequestAction>
typealias RequestViewStore = ViewStore<RequestState, RequestAction>
// MARK: - State
struct RequestState: Equatable {
}
// MARK: - Action
enum RequestAction: Equatable {
case noOp
}
struct RequestEnvironment {
}
// MARK: - Environment
// MARK: - RequestReducer
struct RequestEnvironment { }
typealias RequestReducer = Reducer<RequestState, RequestAction, RequestEnvironment>
// MARK: - Reducer
extension RequestReducer {
static let `default` = RequestReducer { _, action, _ in
@ -23,9 +30,13 @@ extension RequestReducer {
}
}
// MARK: - RequestStore
// MARK: Placeholders
typealias RequestStore = Store<RequestState, RequestAction>
extension RequestState {
static var placeholder: Self {
.init()
}
}
extension RequestStore {
static let placeholder = RequestStore(
@ -34,18 +45,3 @@ extension RequestStore {
environment: RequestEnvironment()
)
}
// MARK: - RequestViewStore
typealias RequestViewStore = ViewStore<RequestState, RequestAction>
extension RequestViewStore {
}
// MARK: PlaceHolders
extension RequestState {
static var placeholder: Self {
.init()
}
}

View File

@ -11,6 +11,8 @@ struct RequestView: View {
}
}
// MARK: - Previews
struct RequestView_Previews: PreviewProvider {
static var previews: some View {
RequestView(store: .placeholder)

View File

@ -1,6 +1,12 @@
import ComposableArchitecture
import SwiftUI
typealias SandboxReducer = Reducer<SandboxState, SandboxAction, SandboxEnvironment>
typealias SandboxStore = Store<SandboxState, SandboxAction>
typealias SandboxViewStore = ViewStore<SandboxState, SandboxAction>
// MARK: - State
struct SandboxState: Equatable {
enum Route: Equatable, CaseIterable {
case history
@ -10,21 +16,25 @@ struct SandboxState: Equatable {
case scan
case request
}
var transactionHistoryState: TransactionHistoryState
var transactionHistoryState: TransactionHistoryFlowState
var profileState: ProfileState
var route: Route?
}
// MARK: - Action
enum SandboxAction: Equatable {
case updateRoute(SandboxState.Route?)
case transactionHistory(TransactionHistoryAction)
case transactionHistory(TransactionHistoryFlowAction)
case profile(ProfileAction)
case reset
}
// MARK: - SandboxReducer
// MARK: - Environment
typealias SandboxReducer = Reducer<SandboxState, SandboxAction, Void>
struct SandboxEnvironment { }
// MARK: - Reducer
extension SandboxReducer {
static let `default` = SandboxReducer { state, action, environment in
@ -33,14 +43,14 @@ extension SandboxReducer {
state.route = route
return .none
case let .transactionHistory(transactionHistoryAction):
return TransactionHistoryReducer
return TransactionHistoryFlowReducer
.default
.run(
&state.transactionHistoryState,
transactionHistoryAction,
TransactionHistoryEnvironment(
TransactionHistoryFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
.map(SandboxAction.transactionHistory)
@ -61,12 +71,10 @@ extension SandboxReducer {
}
}
// MARK: - SandboxStore
typealias SandboxStore = Store<SandboxState, SandboxAction>
// MARK: - Store
extension SandboxStore {
func historyStore() -> TransactionHistoryStore {
func historyStore() -> TransactionHistoryFlowStore {
self.scope(
state: \.transactionHistoryState,
action: SandboxAction.transactionHistory
@ -81,22 +89,20 @@ extension SandboxStore {
}
}
// MARK: - SandboxViewStore
typealias SandboxViewStore = ViewStore<SandboxState, SandboxAction>
// MARK: - ViewStore
extension SandboxViewStore {
func toggleSelectedTransaction() {
let isAlreadySelected = (self.selectedTranactionID != nil)
let transcation = self.transactionHistoryState.transactions[5]
let newRoute = isAlreadySelected ? nil : TransactionHistoryState.Route.showTransaction(transcation)
let newRoute = isAlreadySelected ? nil : TransactionHistoryFlowState.Route.showTransaction(transcation)
send(.transactionHistory(.updateRoute(newRoute)))
}
var selectedTranactionID: String? {
self.transactionHistoryState
.route
.flatMap(/TransactionHistoryState.Route.showTransaction)
.flatMap(/TransactionHistoryFlowState.Route.showTransaction)
.map(\.id)
}
@ -110,7 +116,8 @@ extension SandboxViewStore {
}
}
// MARK: PlaceHolders
// MARK: - PlaceHolders
extension SandboxState {
static var placeholder: Self {
.init(
@ -120,3 +127,17 @@ extension SandboxState {
)
}
}
extension SandboxStore {
static var placeholder: SandboxStore {
SandboxStore(
initialState: SandboxState(
transactionHistoryState: .placeHolder,
profileState: .placeholder,
route: nil
),
reducer: .default.debug(),
environment: SandboxEnvironment()
)
}
}

View File

@ -2,7 +2,12 @@ import SwiftUI
import ComposableArchitecture
struct SandboxView: View {
let store: Store<SandboxState, SandboxAction>
struct SandboxRouteValue: Identifiable {
let id: Int
let route: SandboxState.Route
}
let store: SandboxStore
var navigationRouteValues: [SandboxRouteValue] = SandboxState.Route.allCases
.enumerated()
@ -17,21 +22,21 @@ struct SandboxView: View {
@ViewBuilder func view(for route: SandboxState.Route) -> some View {
switch route {
case .history:
TransactionHistoryView(store: store.historyStore())
TransactionHistoryFlowView(store: store.historyStore())
case .send:
SendView(
SendFlowView(
store: .init(
initialState: .placeholder,
reducer: SendReducer.default(
reducer: SendFlowReducer.default(
whenDone: { SandboxViewStore(store).send(.updateRoute(nil)) }
)
.debug(),
environment: SendEnvironment(
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
)
@ -88,7 +93,7 @@ struct SandboxView: View {
isPresented: viewStore.bindingForRoute(.history),
content: {
NavigationView {
TransactionHistoryView(store: store.historyStore())
TransactionHistoryFlowView(store: store.historyStore())
.toolbar {
ToolbarItem {
Button("Done") { viewStore.send(.updateRoute(nil)) }
@ -102,27 +107,8 @@ struct SandboxView: View {
}
}
struct SandboxRouteValue: Identifiable {
let id: Int
let route: SandboxState.Route
}
// MARK: - Previews
extension SandboxStore {
static var placeholder: SandboxStore {
SandboxStore(
initialState: SandboxState(
transactionHistoryState: .placeHolder,
profileState: .placeholder,
route: nil
),
reducer: .default.debug(),
environment: ()
)
}
}
struct SandboxView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {

View File

@ -1,18 +1,25 @@
import ComposableArchitecture
typealias ScanReducer = Reducer<ScanState, ScanAction, ScanEnvironment>
typealias ScanStore = Store<ScanState, ScanAction>
typealias ScanViewStore = ViewStore<ScanState, ScanAction>
// MARK: - State
struct ScanState: Equatable {
}
// MARK: - Action
enum ScanAction: Equatable {
case noOp
}
struct ScanEnvironment {
}
// MARK: - Environment
// MARK: - ScanReducer
struct ScanEnvironment { }
typealias ScanReducer = Reducer<ScanState, ScanAction, ScanEnvironment>
// MARK: - Reducer
extension ScanReducer {
static let `default` = ScanReducer { _, action, _ in
@ -23,9 +30,13 @@ extension ScanReducer {
}
}
// MARK: - ScanStore
// MARK: Placeholders
typealias ScanStore = Store<ScanState, ScanAction>
extension ScanState {
static var placeholder: Self {
.init()
}
}
extension ScanStore {
static let placeholder = ScanStore(
@ -34,18 +45,3 @@ extension ScanStore {
environment: ScanEnvironment()
)
}
// MARK: - ScanViewStore
typealias ScanViewStore = ViewStore<ScanState, ScanAction>
extension ScanViewStore {
}
// MARK: PlaceHolders
extension ScanState {
static var placeholder: Self {
.init()
}
}

View File

@ -11,6 +11,8 @@ struct ScanView: View {
}
}
// MARK: - Previews
struct ScanView_Previews: PreviewProvider {
static var previews: some View {
ScanView(store: .placeholder)

View File

@ -1,24 +1,21 @@
//
// SendFlowStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 04/25/2022.
//
import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
struct Transaction: Equatable {
var amount: Int64
var memo: String
var toAddress: String
}
typealias SendFlowReducer = Reducer<SendFlowState, SendFlowAction, SendFlowEnvironment>
typealias SendFlowStore = Store<SendFlowState, SendFlowAction>
typealias SendFlowViewStore = ViewStore<SendFlowState, SendFlowAction>
extension Transaction {
static var placeholder: Self {
.init(
amount: 0,
memo: "",
toAddress: ""
)
}
}
// MARK: - State
struct SendState: Equatable {
struct SendFlowState: Equatable {
enum Route: Equatable {
case confirmation
case success
@ -31,9 +28,9 @@ struct SendState: Equatable {
var isSendingTransaction = false
var memo = ""
var totalBalance: Int64 = 0
var transaction: Transaction
var transactionAddressInputState: TransactionAddressInputState
var transactionAmountInputState: TransactionAmountInputState
var transaction: SendFlowTransaction
var transactionAddressInputState: TransactionAddressTextFieldState
var transactionAmountInputState: TransactionAmountTextFieldState
var isInvalidAddressFormat: Bool {
!transactionAddressInputState.isValidAddress
@ -60,38 +57,38 @@ struct SendState: Equatable {
}
}
enum SendAction: Equatable {
// MARK: - Action
enum SendFlowAction: Equatable {
case onAppear
case onDisappear
case sendConfirmationPressed
case sendTransactionResult(Result<TransactionState, NSError>)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case transactionAddressInput(TransactionAddressInputAction)
case transactionAmountInput(TransactionAmountInputAction)
case transactionAddressInput(TransactionAddressTextFieldAction)
case transactionAmountInput(TransactionAmountTextFieldAction)
case updateBalance(Int64)
case updateMemo(String)
case updateTransaction(Transaction)
case updateRoute(SendState.Route?)
case updateTransaction(SendFlowTransaction)
case updateRoute(SendFlowState.Route?)
}
struct SendEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
// MARK: - Environment
struct SendFlowEnvironment {
let mnemonicSeedPhraseProvider: WrappedMnemonic
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WalletStorageInteractor
let wrappedDerivationTool: WrappedDerivationTool
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
let walletStorage: WrappedWalletStorage
let derivationTool: WrappedDerivationTool
let SDKSynchronizer: WrappedSDKSynchronizer
}
// MARK: - SendReducer
// MARK: - Reducer
private struct ListenerId: Hashable {}
typealias SendReducer = Reducer<SendState, SendAction, SendEnvironment>
extension SendReducer {
extension SendFlowReducer {
private struct SyncStatusUpdatesID: Hashable {}
static let `default` = SendReducer.combine(
static let `default` = SendFlowReducer.combine(
[
sendReducer,
transactionAddressInputReducer,
@ -100,7 +97,7 @@ extension SendReducer {
)
.debug()
private static let sendReducer = SendReducer { state, action, environment in
private static let sendReducer = SendFlowReducer { state, action, environment in
switch action {
case let .updateTransaction(transaction):
state.transaction = transaction
@ -128,13 +125,13 @@ extension SendReducer {
do {
let storedWallet = try environment.walletStorage.exportWallet()
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(storedWallet.seedPhrase)
guard let spendingKey = try environment.wrappedDerivationTool.deriveSpendingKeys(seedBytes, 1).first else {
guard let spendingKey = try environment.derivationTool.deriveSpendingKeys(seedBytes, 1).first else {
return Effect(value: .updateRoute(.failure))
}
state.isSendingTransaction = true
return environment.wrappedSDKSynchronizer.sendTransaction(
return environment.SDKSynchronizer.sendTransaction(
with: spendingKey,
zatoshi: Int64(state.transaction.amount),
to: state.transaction.toAddress,
@ -142,7 +139,7 @@ extension SendReducer {
from: 0
)
.receive(on: environment.scheduler)
.map(SendAction.sendTransactionResult)
.map(SendFlowAction.sendTransactionResult)
.eraseToEffect()
} catch {
return Effect(value: .updateRoute(.failure))
@ -164,19 +161,19 @@ extension SendReducer {
return .none
case .onAppear:
return environment.wrappedSDKSynchronizer.stateChanged
.map(SendAction.synchronizerStateChanged)
return environment.SDKSynchronizer.stateChanged
.map(SendFlowAction.synchronizerStateChanged)
.eraseToEffect()
.cancellable(id: ListenerId(), cancelInFlight: true)
.cancellable(id: SyncStatusUpdatesID(), cancelInFlight: true)
case .onDisappear:
return Effect.cancel(id: ListenerId())
return Effect.cancel(id: SyncStatusUpdatesID())
case .synchronizerStateChanged(.synced):
return environment.wrappedSDKSynchronizer.getShieldedBalance()
return environment.SDKSynchronizer.getShieldedBalance()
.receive(on: environment.scheduler)
.map({ $0.total })
.map(SendAction.updateBalance)
.map(SendFlowAction.updateBalance)
.eraseToEffect()
case .synchronizerStateChanged(let synchronizerState):
@ -193,24 +190,24 @@ extension SendReducer {
}
}
private static let transactionAddressInputReducer: SendReducer = TransactionAddressInputReducer.default.pullback(
state: \SendState.transactionAddressInputState,
action: /SendAction.transactionAddressInput,
private static let transactionAddressInputReducer: SendFlowReducer = TransactionAddressTextFieldReducer.default.pullback(
state: \SendFlowState.transactionAddressInputState,
action: /SendFlowAction.transactionAddressInput,
environment: { environment in
TransactionAddressInputEnvironment(
wrappedDerivationTool: environment.wrappedDerivationTool
TransactionAddressTextFieldEnvironment(
derivationTool: environment.derivationTool
)
}
)
private static let transactionAmountInputReducer: SendReducer = TransactionAmountInputReducer.default.pullback(
state: \SendState.transactionAmountInputState,
action: /SendAction.transactionAmountInput,
environment: { _ in TransactionAmountInputEnvironment() }
private static let transactionAmountInputReducer: SendFlowReducer = TransactionAmountTextFieldReducer.default.pullback(
state: \SendFlowState.transactionAmountInputState,
action: /SendFlowAction.transactionAmountInput,
environment: { _ in TransactionAmountTextFieldEnvironment() }
)
static func `default`(whenDone: @escaping () -> Void) -> SendReducer {
SendReducer { state, action, environment in
static func `default`(whenDone: @escaping () -> Void) -> SendFlowReducer {
SendFlowReducer { state, action, environment in
switch action {
case let .updateRoute(route) where route == .done:
return Effect.fireAndForget(whenDone)
@ -221,68 +218,62 @@ extension SendReducer {
}
}
// MARK: - SendStore
// MARK: - ViewStore
typealias SendStore = Store<SendState, SendAction>
// MARK: - SendViewStore
typealias SendViewStore = ViewStore<SendState, SendAction>
extension SendViewStore {
var bindingForTransaction: Binding<Transaction> {
extension SendFlowViewStore {
var bindingForTransaction: Binding<SendFlowTransaction> {
self.binding(
get: \.transaction,
send: SendAction.updateTransaction
send: SendFlowAction.updateTransaction
)
}
var routeBinding: Binding<SendState.Route?> {
var routeBinding: Binding<SendFlowState.Route?> {
self.binding(
get: \.route,
send: SendAction.updateRoute
send: SendFlowAction.updateRoute
)
}
var bindingForConfirmation: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .confirmation || self.bindingForSuccess.wrappedValue || self.bindingForFailure.wrappedValue },
embed: { $0 ? SendState.Route.confirmation : nil }
embed: { $0 ? SendFlowState.Route.confirmation : nil }
)
}
var bindingForSuccess: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .success || self.bindingForDone.wrappedValue },
embed: { $0 ? SendState.Route.success : SendState.Route.confirmation }
embed: { $0 ? SendFlowState.Route.success : SendFlowState.Route.confirmation }
)
}
var bindingForFailure: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .failure || self.bindingForDone.wrappedValue },
embed: { $0 ? SendState.Route.failure : SendState.Route.confirmation }
embed: { $0 ? SendFlowState.Route.failure : SendFlowState.Route.confirmation }
)
}
var bindingForDone: Binding<Bool> {
self.routeBinding.map(
extract: { $0 == .done },
embed: { $0 ? SendState.Route.done : SendState.Route.confirmation }
embed: { $0 ? SendFlowState.Route.done : SendFlowState.Route.confirmation }
)
}
var bindingForMemo: Binding<String> {
self.binding(
get: \.memo,
send: SendAction.updateMemo
send: SendFlowAction.updateMemo
)
}
}
// MARK: PlaceHolders
// MARK: Placeholders
extension SendState {
extension SendFlowState {
static var placeholder: Self {
.init(
route: nil,
@ -305,3 +296,26 @@ extension SendState {
)
}
}
// #if DEBUG // FIX: Issue #306 - Release build is broken
extension SendFlowStore {
static var placeholder: SendFlowStore {
return SendFlowStore(
initialState: .init(
route: nil,
transaction: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState: .placeholder
),
reducer: .default,
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}
}
// #endif

View File

@ -1,8 +1,15 @@
//
// SendFlowView.swift
// secant-testnet
//
// Created by Lukáš Korba on 04/25/2022.
//
import SwiftUI
import ComposableArchitecture
struct SendView: View {
let store: SendStore
struct SendFlowView: View {
let store: SendFlowStore
var body: some View {
WithViewStore(store) { viewStore in
@ -19,10 +26,12 @@ struct SendView: View {
}
}
struct SendView_Previews: PreviewProvider {
// MARK: - Previews
struct SendFLowView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
SendView(
SendFlowView(
store: .init(
initialState: .init(
route: nil,
@ -31,12 +40,12 @@ struct SendView_Previews: PreviewProvider {
transactionAmountInputState: .placeholder
),
reducer: .default,
environment: SendEnvironment(
environment: SendFlowEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
derivationTool: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
)

View File

@ -2,7 +2,7 @@ import SwiftUI
import ComposableArchitecture
struct CreateTransaction: View {
let store: SendStore
let store: SendFlowStore
var body: some View {
UITextView.appearance().backgroundColor = .clear
@ -21,7 +21,7 @@ struct CreateTransaction: View {
TransactionAmountTextField(
store: store.scope(
state: \.transactionAmountInputState,
action: SendAction.transactionAmountInput
action: SendFlowAction.transactionAmountInput
)
)
@ -49,7 +49,7 @@ struct CreateTransaction: View {
TransactionAddressTextField(
store: store.scope(
state: \.transactionAddressInputState,
action: SendAction.transactionAddressInput
action: SendFlowAction.transactionAddressInput
)
)
@ -106,26 +106,3 @@ struct Create_Previews: PreviewProvider {
}
}
}
// #if DEBUG // FIX: Issue #306 - Release build is broken
extension SendStore {
static var placeholder: SendStore {
return SendStore(
initialState: .init(
route: nil,
transaction: .placeholder,
transactionAddressInputState: .placeholder,
transactionAmountInputState: .placeholder
),
reducer: .default,
environment: SendEnvironment(
mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(),
wrappedDerivationTool: .live(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}
}
// #endif

View File

@ -2,7 +2,7 @@ import SwiftUI
import ComposableArchitecture
struct TransactionConfirmation: View {
let viewStore: SendViewStore
let viewStore: SendFlowViewStore
var body: some View {
VStack {
@ -36,12 +36,14 @@ struct TransactionConfirmation: View {
}
}
// MARK: - Previews
struct Confirmation_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
StateContainer(
initialState: (
Transaction.placeholder,
SendFlowTransaction.placeholder,
false
)
) { _ in

View File

@ -2,7 +2,7 @@ import SwiftUI
import ComposableArchitecture
struct TransactionFailed: View {
let viewStore: SendViewStore
let viewStore: SendFlowViewStore
var body: some View {
VStack {
@ -25,6 +25,8 @@ struct TransactionFailed: View {
}
}
// MARK: - Previews
struct TransactionFailed_Previews: PreviewProvider {
static var previews: some View {
TransactionFailed(viewStore: ViewStore(.placeholder))

View File

@ -2,7 +2,7 @@ import SwiftUI
import ComposableArchitecture
struct TransactionSent: View {
let viewStore: SendViewStore
let viewStore: SendFlowViewStore
var body: some View {
VStack {
@ -27,6 +27,8 @@ struct TransactionSent: View {
}
}
// MARK: - Previews
struct TransactionSent_Previews: PreviewProvider {
static var previews: some View {
TransactionSent(viewStore: ViewStore(.placeholder))

View File

@ -1,18 +1,25 @@
import ComposableArchitecture
typealias SettingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment>
typealias SettingsStore = Store<SettingsState, SettingsAction>
typealias SettingsViewStore = ViewStore<SettingsState, SettingsAction>
// MARK: - State
struct SettingsState: Equatable {
}
// MARK: - Action
enum SettingsAction: Equatable {
case noOp
}
struct SettingsEnvironment: Equatable {
}
// MARK: - Environment
// MARK: - SettingsStateReducer
struct SettingsEnvironment: Equatable { }
typealias SettingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment>
// MARK: - Reducer
extension SettingsReducer {
static let `default` = SettingsReducer { _, action, _ in
@ -22,17 +29,3 @@ extension SettingsReducer {
}
}
}
// MARK: - SettingsStore
typealias SettingsStore = Store<SettingsState, SettingsAction>
extension SettingsStore {
}
// MARK: - SettingsViewStore
typealias SettingsViewStore = ViewStore<SettingsState, SettingsAction>
extension SettingsViewStore {
}

View File

@ -6,6 +6,8 @@ struct SettingsView: View {
}
}
// MARK: - Previews
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()

View File

@ -1,18 +1,13 @@
import ComposableArchitecture
import SwiftUI
extension Date {
func asHumanReadable() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .short
return dateFormatter.string(from: self)
}
}
typealias TransactionHistoryFlowReducer = Reducer<TransactionHistoryFlowState, TransactionHistoryFlowAction, TransactionHistoryFlowEnvironment>
typealias TransactionHistoryFlowStore = Store<TransactionHistoryFlowState, TransactionHistoryFlowAction>
typealias TransactionHistoryFlowViewStore = ViewStore<TransactionHistoryFlowState, TransactionHistoryFlowAction>
struct TransactionHistoryState: Equatable {
// MARK: - State
struct TransactionHistoryFlowState: Equatable {
enum Route: Equatable {
case latest
case all
@ -25,31 +20,33 @@ struct TransactionHistoryState: Equatable {
var transactions: IdentifiedArrayOf<TransactionState>
}
enum TransactionHistoryAction: Equatable {
// MARK: - Action
enum TransactionHistoryFlowAction: Equatable {
case onAppear
case onDisappear
case updateRoute(TransactionHistoryState.Route?)
case updateRoute(TransactionHistoryFlowState.Route?)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case updateTransactions([TransactionState])
}
struct TransactionHistoryEnvironment {
// MARK: - Environment
struct TransactionHistoryFlowEnvironment {
let scheduler: AnySchedulerOf<DispatchQueue>
let wrappedSDKSynchronizer: WrappedSDKSynchronizer
let SDKSynchronizer: WrappedSDKSynchronizer
}
// MARK: - TransactionHistoryReducer
// MARK: - Reducer
private struct ListenerId: Hashable {}
typealias TransactionHistoryReducer = Reducer<TransactionHistoryState, TransactionHistoryAction, TransactionHistoryEnvironment>
extension TransactionHistoryReducer {
static let `default` = TransactionHistoryReducer { state, action, environment in
extension TransactionHistoryFlowReducer {
private struct ListenerId: Hashable {}
static let `default` = TransactionHistoryFlowReducer { state, action, environment in
switch action {
case .onAppear:
return environment.wrappedSDKSynchronizer.stateChanged
.map(TransactionHistoryAction.synchronizerStateChanged)
return environment.SDKSynchronizer.stateChanged
.map(TransactionHistoryFlowAction.synchronizerStateChanged)
.eraseToEffect()
.cancellable(id: ListenerId(), cancelInFlight: true)
@ -57,9 +54,9 @@ extension TransactionHistoryReducer {
return Effect.cancel(id: ListenerId())
case .synchronizerStateChanged(.synced):
return environment.wrappedSDKSynchronizer.getAllTransactions()
return environment.SDKSynchronizer.getAllTransactions()
.receive(on: environment.scheduler)
.map(TransactionHistoryAction.updateTransactions)
.map(TransactionHistoryFlowAction.updateTransactions)
.eraseToEffect()
case .synchronizerStateChanged(let synchronizerState):
@ -80,28 +77,22 @@ extension TransactionHistoryReducer {
}
}
// MARK: - TransactionHistoryStore
// MARK: - ViewStore
typealias TransactionHistoryStore = Store<TransactionHistoryState, TransactionHistoryAction>
// MARK: - TransactionHistoryViewStore
typealias TransactionHistoryViewStore = ViewStore<TransactionHistoryState, TransactionHistoryAction>
extension TransactionHistoryViewStore {
private typealias Route = TransactionHistoryState.Route
extension TransactionHistoryFlowViewStore {
private typealias Route = TransactionHistoryFlowState.Route
func bindingForSelectingTransaction(_ transaction: TransactionState) -> Binding<Bool> {
self.binding(
get: { $0.route.map(/TransactionHistoryState.Route.showTransaction) == transaction },
get: { $0.route.map(/TransactionHistoryFlowState.Route.showTransaction) == transaction },
send: { isActive in
TransactionHistoryAction.updateRoute( isActive ? TransactionHistoryState.Route.showTransaction(transaction) : nil)
TransactionHistoryFlowAction.updateRoute( isActive ? TransactionHistoryFlowState.Route.showTransaction(transaction) : nil)
}
)
}
}
// MARK: PlaceHolders
// MARK: Placeholders
extension TransactionState {
static var placeholder: Self {
@ -115,7 +106,7 @@ extension TransactionState {
}
}
extension TransactionHistoryState {
extension TransactionHistoryFlowState {
static var placeHolder: Self {
.init(transactions: .placeholder)
}
@ -125,29 +116,29 @@ extension TransactionHistoryState {
}
}
extension TransactionHistoryStore {
static var placeholder: Store<TransactionHistoryState, TransactionHistoryAction> {
extension TransactionHistoryFlowStore {
static var placeholder: Store<TransactionHistoryFlowState, TransactionHistoryFlowAction> {
return Store(
initialState: .placeHolder,
reducer: .default,
environment: TransactionHistoryEnvironment(
environment: TransactionHistoryFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}
static var demoWithSelectedTransaction: Store<TransactionHistoryState, TransactionHistoryAction> {
static var demoWithSelectedTransaction: Store<TransactionHistoryFlowState, TransactionHistoryFlowAction> {
let transactions = IdentifiedArrayOf<TransactionState>.placeholder
return Store(
initialState: TransactionHistoryState(
initialState: TransactionHistoryFlowState(
route: .showTransaction(transactions[3]),
transactions: transactions
),
reducer: .default.debug(),
environment: TransactionHistoryEnvironment(
environment: TransactionHistoryFlowEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
wrappedSDKSynchronizer: LiveWrappedSDKSynchronizer()
SDKSynchronizer: LiveWrappedSDKSynchronizer()
)
)
}

View File

@ -1,8 +1,8 @@
import SwiftUI
import ComposableArchitecture
struct TransactionHistoryView: View {
let store: Store<TransactionHistoryState, TransactionHistoryAction>
struct TransactionHistoryFlowView: View {
let store: TransactionHistoryFlowStore
var body: some View {
UITableView.appearance().backgroundColor = .clear
@ -28,8 +28,8 @@ struct TransactionHistoryView: View {
}
}
extension TransactionHistoryView {
func transactionsList(with viewStore: TransactionHistoryViewStore) -> some View {
extension TransactionHistoryFlowView {
func transactionsList(with viewStore: TransactionHistoryFlowViewStore) -> some View {
ForEach(viewStore.transactions) { transaction in
WithStateBinding(binding: viewStore.bindingForSelectingTransaction(transaction)) { active in
VStack {
@ -65,7 +65,7 @@ extension TransactionHistoryView {
}
}
func header(with viewStore: TransactionHistoryViewStore) -> some View {
func header(with viewStore: TransactionHistoryFlowViewStore) -> some View {
HStack(spacing: 0) {
VStack {
Button("Latest") {
@ -90,10 +90,12 @@ extension TransactionHistoryView {
}
}
// MARK: - Previews
struct TransactionView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TransactionHistoryView(store: .placeholder)
TransactionHistoryFlowView(store: .placeholder)
.preferredColorScheme(.dark)
}
}

View File

@ -9,6 +9,8 @@ struct TransactionDetailView: View {
}
}
// MARK: - Previews
struct TransactionDetail_Previews: PreviewProvider {
static var previews: some View {
NavigationView {

View File

@ -1,18 +1,26 @@
import ComposableArchitecture
typealias WalletInfoReducer = Reducer<WalletInfoState, WalletInfoAction, WalletInfoEnvironment>
typealias WalletInfoStore = Store<WalletInfoState, WalletInfoAction>
typealias WalletInfoViewStore = ViewStore<WalletInfoState, WalletInfoAction>
// MARK: - State
struct WalletInfoState: Equatable {
}
// MARK: - Action
enum WalletInfoAction: Equatable {
case noOp
}
// MARK: - Environment
struct WalletInfoEnvironment: Equatable {
}
// MARK: - WalletInfoReducer
typealias WalletInfoReducer = Reducer<WalletInfoState, WalletInfoAction, WalletInfoEnvironment>
// MARK: - Reducer
extension WalletInfoReducer {
static let `default` = WalletInfoReducer { _, action, _ in
@ -22,17 +30,3 @@ extension WalletInfoReducer {
}
}
}
// MARK: - WalletInfoStore
typealias WalletInfoStore = Store<WalletInfoState, WalletInfoAction>
extension WalletInfoStore {
}
// MARK: - WalletInfoViewStore
typealias WalletInfoViewStore = ViewStore<WalletInfoState, WalletInfoAction>
extension WalletInfoViewStore {
}

View File

@ -6,6 +6,8 @@ struct WalletInfoView: View {
}
}
// MARK: - Previews
struct WalletInfoView_Previews: PreviewProvider {
static var previews: some View {
WalletInfoView()

View File

@ -8,17 +8,25 @@
import Foundation
import ComposableArchitecture
typealias WelcomeReducer = Reducer<WelcomeState, WelcomeAction, WelcomeEnvironment>
typealias WelcomeStore = Store<WelcomeState, WelcomeAction>
typealias WelcomeViewStore = ViewStore<WelcomeState, WelcomeAction>
// MARK: - State
struct WelcomeState: Equatable {}
extension WelcomeState {
static let placeholder = WelcomeState()
}
// MARK: - Action
enum WelcomeAction: Equatable {
case debugMenuStartup
}
typealias WelcomeReducer = Reducer<WelcomeState, WelcomeAction, Void>
// MARK: - Environment
struct WelcomeEnvironment { }
// MARK: - Reducer
extension WelcomeReducer {
static let `default` = WelcomeReducer { _, _, _ in
@ -26,12 +34,18 @@ extension WelcomeReducer {
}
}
typealias WelcomeStore = Store<WelcomeState, WelcomeAction>
// MARK: - Store
extension WelcomeStore {
static var demo = WelcomeStore(
initialState: .placeholder,
reducer: .default,
environment: ()
environment: WelcomeEnvironment()
)
}
// MARK: - Placeholders
extension WelcomeState {
static let placeholder = WelcomeState()
}

View File

@ -41,57 +41,7 @@ struct WelcomeView: View {
}
}
struct ZcashBadge: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
ZStack {
GeometryReader { proxy in
let outterPadding = proxy.size.height * 0.015
let firstPadding = proxy.size.height * 0.075 + outterPadding
let secondRingPadding = firstPadding * 1.5
let outerShadowDrop = proxy.size.height * 0.14
let outerShadowOffset = proxy.size.height * 0.055
Circle()
.fill(
LinearGradient(
colors: [
Asset.Colors.ZcashBadge.outerRingGradientStart.color,
Asset.Colors.ZcashBadge.outerRingGradientEnd.color
],
startPoint: UnitPoint(x: 0.5, y: 0),
endPoint: UnitPoint(x: 0.5, y: 1)
)
)
.if(colorScheme == .light) { view in
view.shadow(
color: Asset.Colors.ZcashBadge.shadowColor.color,
radius: outerShadowDrop,
x: outerShadowOffset,
y: outerShadowOffset
)
}
Circle()
.foregroundColor(Asset.Colors.ZcashBadge.thickRing.color)
.padding(outterPadding)
Circle()
.foregroundColor(Asset.Colors.ZcashBadge.thinRing.color)
.padding(firstPadding)
Circle()
.foregroundColor(Asset.Colors.ZcashBadge.innerCircle.color)
.padding(secondRingPadding)
ZcashSymbol()
.fill(Asset.Colors.ZcashBadge.zcashLogoFill.color)
.padding(firstPadding + secondRingPadding)
}
}
}
}
// MARK: - Previews
struct WelcomeView_Previews: PreviewProvider {
static let squarePreviewSize: CGFloat = 360

View File

@ -1,19 +0,0 @@
//
// Services.swift
// secant
//
// Created by Francisco Gindre on 8/6/21.
//
import Foundation
import ZcashLightClientKit
protocol Services {
var networkProvider: ZcashNetworkProvider { get }
var seedHandler: MnemonicSeedPhraseProvider { get }
var walletStorage: WalletStorageInteractor { get }
}
protocol ZcashNetworkProvider {
func currentNetwork() -> ZcashNetwork
}

View File

@ -1,108 +0,0 @@
//
// Balance.swift
// secant
//
// Created by Francisco Gindre on 8/9/21.
//
import Foundation
/**
Funds Expressed in Zatoshis
*/
protocol Funds {
/**
Confirmed, spendable funds
*/
var confirmed: Int64 { get }
/**
Unconfirmed, not yet spendable funds.
*/
var unconfirmed: Int64 { get }
/**
Represents a null value of Funds with Zero confirmed and Zero unconfirmed funds.
*/
static var noFunds: Funds { get }
}
/**
Wallet Balance that condenses transparent, Sapling and Orchard funds
*/
protocol WalletBalance {
/**
Transparent funds. This is the sum of the UTXOs of the user found at a given time
*/
var transparent: Funds { get }
/**
Funds on the Sapling shielded pool for a given user.
*/
var sapling: Funds { get }
/**
Funds on the Orchard shielded pool for a given user.
*/
var orchard: Funds { get }
/**
The sum of all confirmed funds, transparent, sapling and orchard funds (calculated)
*/
var totalAvailableBalance: Int64 { get }
/**
The sum of all unconfirmed funds: transparent, sapling, and orchard funds (calculated
*/
var totalUnconfirmedBalance: Int64 { get }
/**
The sum of all funds confirmed and unconfirmed of all pools (transparent, sapling and orchard).
*/
var totalBalance: Int64 { get }
/**
Represents a the value of Zero funds.
*/
static var nullBalance: WalletBalance { get }
}
extension WalletBalance {
static var nullBalance: WalletBalance {
Balance(
transparent: ZcashFunds.noFunds,
sapling: ZcashFunds.noFunds,
orchard: ZcashFunds.noFunds
)
}
var totalAvailableBalance: Int64 {
transparent.confirmed + sapling.confirmed + orchard.confirmed
}
var totalUnconfirmedBalance: Int64 {
transparent.unconfirmed + sapling.unconfirmed + orchard.unconfirmed
}
var totalBalance: Int64 {
totalAvailableBalance + totalUnconfirmedBalance
}
}
/**
Concrete Wallet Balance.
*/
struct Balance: WalletBalance {
var transparent: Funds
var sapling: Funds
var orchard: Funds
}
struct ZcashFunds: Funds {
static var noFunds: Funds {
ZcashFunds(confirmed: 0, unconfirmed: 0)
}
var confirmed: Int64
var unconfirmed: Int64
}

View File

@ -36,7 +36,7 @@ struct WordChipDropDelegate: DropDelegate {
}
}
extension RecoveryPhraseValidationState {
extension RecoveryPhraseValidationFlowState {
func groupCompleted(index: Int) -> Bool {
validationWords.first(where: { $0.groupIndex == index }) != nil
}

View File

@ -0,0 +1,43 @@
//
// RecoveryPhrase.swift
// secant-testnet
//
// Created by Lukáš Korba on 12.05.2022.
//
import Foundation
enum RecoveryPhraseError: Error {
/// This error is thrown then the Recovery Phrase can't be generated
case unableToGeneratePhrase
}
struct RecoveryPhrase: Equatable {
struct Group: Hashable {
var startIndex: Int
var words: [String]
}
let words: [String]
private let groupSize = 6
func toGroups() -> [Group] {
let chunks = words.count / groupSize
return zip(0 ..< chunks, words.chunked(into: groupSize)).map {
Group(startIndex: $0 * groupSize + 1, words: $1)
}
}
func toString() -> String {
words.joined(separator: " ")
}
func words(fromMissingIndices indices: [Int]) -> [PhraseChip.Kind] {
assert((indices.count - 1) * groupSize <= self.words.count)
return indices.enumerated().map { index, position in
.unassigned(word: self.words[(index * groupSize) + position])
}
}
}

View File

@ -0,0 +1,25 @@
//
// Transaction.swift
// secant-testnet
//
// Created by Lukáš Korba on 12.05.2022.
//
import Foundation
/// Simple model that holds data throughout the `SendFlow` feature
struct SendFlowTransaction: Equatable {
var amount: Int64
var memo: String
var toAddress: String
}
extension SendFlowTransaction {
static var placeholder: Self {
.init(
amount: 0,
memo: "",
toAddress: ""
)
}
}

View File

@ -0,0 +1,20 @@
//
// StoredWallet.swift
// secant-testnet
//
// Created by Lukáš Korba on 13.05.2022.
//
import Foundation
import ZcashLightClientKit
import MnemonicSwift
/// Representation of the wallet stored in the persistent storage (typically keychain, handled by `WalletStorage`).
struct StoredWallet: Codable, Equatable {
let language: MnemonicLanguageType
let seedPhrase: String
let version: Int
var birthday: BlockHeight?
var hasUserPassedPhraseBackupTest: Bool
}

View File

@ -8,6 +8,7 @@
import Foundation
import ZcashLightClientKit
/// Representation of the transaction on the SDK side, used as a bridge to the TCA wallet side.
struct TransactionState: Equatable, Identifiable {
enum Status: Equatable {
case paid(success: Bool)

View File

@ -0,0 +1,15 @@
//
// ValidationWord.swift
// secant-testnet
//
// Created by Lukáš Korba on 12.05.2022.
//
import Foundation
/// Represents the data of a word that has been placed into an empty position, that will be used
/// to validate the completed phrase when all ValidationWords have been placed.
struct ValidationWord: Equatable {
var groupIndex: Int
var word: String
}

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 288 KiB

After

Width:  |  Height:  |  Size: 288 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 998 B

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Some files were not shown because too many files have changed in this diff Show More