2021-12-13 16:51:31 -08:00
|
|
|
import ComposableArchitecture
|
|
|
|
|
|
|
|
struct AppState: Equatable {
|
2021-12-13 12:50:04 -08:00
|
|
|
enum Route: Equatable {
|
2022-02-18 06:06:17 -08:00
|
|
|
case welcome
|
2021-12-13 16:51:31 -08:00
|
|
|
case startup
|
|
|
|
case onboarding
|
|
|
|
case home
|
2021-12-13 12:50:04 -08:00
|
|
|
case phraseValidation
|
|
|
|
case phraseDisplay
|
2021-12-13 16:51:31 -08:00
|
|
|
}
|
2021-12-13 12:50:04 -08:00
|
|
|
|
2021-12-13 16:51:31 -08:00
|
|
|
var homeState: HomeState
|
|
|
|
var onboardingState: OnboardingState
|
2021-12-13 12:50:04 -08:00
|
|
|
var phraseValidationState: RecoveryPhraseValidationState
|
|
|
|
var phraseDisplayState: RecoveryPhraseDisplayState
|
2022-02-18 06:06:17 -08:00
|
|
|
var route: Route = .welcome
|
2022-04-04 05:04:01 -07:00
|
|
|
var storedWallet: StoredWallet?
|
|
|
|
var appInitializationState: InitializationState = .uninitialized
|
2021-12-13 16:51:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
enum AppAction: Equatable {
|
2022-04-04 05:04:01 -07:00
|
|
|
case appDelegate(AppDelegateAction)
|
|
|
|
case checkWalletInitialization
|
2022-04-01 04:27:37 -07:00
|
|
|
case createNewWallet
|
2021-12-13 16:51:31 -08:00
|
|
|
case home(HomeAction)
|
2022-04-04 05:04:01 -07:00
|
|
|
case initializeApp
|
2021-12-13 16:51:31 -08:00
|
|
|
case onboarding(OnboardingAction)
|
2021-12-13 12:50:04 -08:00
|
|
|
case phraseDisplay(RecoveryPhraseDisplayAction)
|
|
|
|
case phraseValidation(RecoveryPhraseValidationAction)
|
2022-04-04 05:04:01 -07:00
|
|
|
case updateRoute(AppState.Route)
|
2021-12-13 16:51:31 -08:00
|
|
|
}
|
|
|
|
|
2022-04-01 04:27:37 -07:00
|
|
|
struct AppEnvironment {
|
2022-04-04 05:04:01 -07:00
|
|
|
let scheduler: AnySchedulerOf<DispatchQueue>
|
2022-04-01 04:27:37 -07:00
|
|
|
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
|
|
|
|
let walletStorage: RecoveryPhraseStorage
|
|
|
|
}
|
|
|
|
|
|
|
|
extension AppEnvironment {
|
|
|
|
static let live = AppEnvironment(
|
2022-04-04 05:04:01 -07:00
|
|
|
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
2022-04-01 04:27:37 -07:00
|
|
|
mnemonicSeedPhraseProvider: .live,
|
|
|
|
walletStorage: RecoveryPhraseStorage()
|
|
|
|
)
|
|
|
|
|
|
|
|
static let mock = AppEnvironment(
|
2022-04-04 05:04:01 -07:00
|
|
|
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
|
2022-04-01 04:27:37 -07:00
|
|
|
mnemonicSeedPhraseProvider: .mock,
|
|
|
|
walletStorage: RecoveryPhraseStorage()
|
|
|
|
)
|
|
|
|
}
|
2021-12-13 16:51:31 -08:00
|
|
|
|
|
|
|
// MARK: - AppReducer
|
|
|
|
|
|
|
|
typealias AppReducer = Reducer<AppState, AppAction, AppEnvironment>
|
|
|
|
|
|
|
|
extension AppReducer {
|
|
|
|
static let `default` = AppReducer.combine(
|
|
|
|
[
|
2022-04-01 04:27:37 -07:00
|
|
|
appReducer,
|
2021-12-13 16:51:31 -08:00
|
|
|
routeReducer,
|
|
|
|
homeReducer,
|
2021-12-13 12:50:04 -08:00
|
|
|
onboardingReducer,
|
2022-03-02 08:33:58 -08:00
|
|
|
phraseValidationReducer,
|
|
|
|
phraseDisplayReducer
|
2021-12-13 16:51:31 -08:00
|
|
|
]
|
|
|
|
)
|
2022-03-02 08:33:58 -08:00
|
|
|
.debug()
|
2021-12-13 16:51:31 -08:00
|
|
|
|
2022-04-01 04:27:37 -07:00
|
|
|
private static let appReducer = AppReducer { state, action, environment in
|
|
|
|
switch action {
|
|
|
|
case .createNewWallet:
|
|
|
|
let randomPhraseWords: [String]
|
|
|
|
do {
|
|
|
|
let randomPhrase = try environment.mnemonicSeedPhraseProvider.randomMnemonic()
|
|
|
|
randomPhraseWords = try environment.mnemonicSeedPhraseProvider.asWords(randomPhrase)
|
|
|
|
// TODO: - Get birthday from the integrated SDK, issue 228 (https://github.com/zcash/secant-ios-wallet/issues/228)
|
|
|
|
let birthday = BlockHeight(12345678)
|
|
|
|
|
|
|
|
try environment.walletStorage.importRecoveryPhrase(bip39: randomPhrase, birthday: birthday)
|
|
|
|
} catch {
|
|
|
|
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
|
|
|
let recoveryPhrase = RecoveryPhrase(words: randomPhraseWords)
|
|
|
|
state.phraseDisplayState.phrase = recoveryPhrase
|
|
|
|
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
|
|
|
|
|
|
|
|
return Effect(value: .phraseValidation(.displayBackedUpPhrase))
|
|
|
|
|
2022-04-04 05:04:01 -07:00
|
|
|
/// Checking presense of stored wallet in the keychain and presense of database files in documents directory.
|
|
|
|
case .checkWalletInitialization:
|
|
|
|
// TODO: Create a dependency to handle database files for the SDK, issue #220 (https://github.com/zcash/secant-ios-wallet/issues/220)
|
|
|
|
let fileManager = FileManager()
|
|
|
|
// TODO: use updated dependency from PR #217 (https://github.com/zcash/secant-ios-wallet/pull/217)
|
|
|
|
let keysPresent = environment.walletStorage.areKeysPresent()
|
|
|
|
|
|
|
|
do {
|
|
|
|
// TODO: use database URL from the same issue #220
|
|
|
|
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
|
|
|
let dataDatabaseURL = documentsURL.appendingPathComponent("ZcashSDK.defaultDataDbName", isDirectory: false)
|
|
|
|
let attributes = try fileManager.attributesOfItem(atPath: dataDatabaseURL.path)
|
|
|
|
let databaseFilesPresent = attributes.isEmpty
|
|
|
|
|
|
|
|
switch (keysPresent, databaseFilesPresent) {
|
|
|
|
case (false, false):
|
|
|
|
return Effect(value: .updateRoute(.onboarding))
|
|
|
|
.delay(for: 3, scheduler: environment.scheduler)
|
|
|
|
.eraseToEffect()
|
|
|
|
case (false, true):
|
|
|
|
state.appInitializationState = .keysMissing
|
|
|
|
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
|
|
|
|
case (true, false), (true, true):
|
|
|
|
return Effect(value: .initializeApp)
|
|
|
|
}
|
|
|
|
} catch CocoaError.fileNoSuchFile, CocoaError.fileReadNoSuchFile {
|
|
|
|
state.appInitializationState = keysPresent ? .filesMissing : .uninitialized
|
|
|
|
} catch {
|
|
|
|
state.appInitializationState = .failed
|
|
|
|
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
|
|
|
|
}
|
|
|
|
return .none
|
|
|
|
|
|
|
|
/// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments.
|
|
|
|
/// When initialization succeeds user is taken to the home screen.
|
|
|
|
case .initializeApp:
|
|
|
|
do {
|
|
|
|
state.storedWallet = try environment.walletStorage.exportWallet()
|
|
|
|
} catch {
|
|
|
|
state.appInitializationState = .failed
|
|
|
|
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
|
|
|
state.appInitializationState = .initialized
|
|
|
|
return Effect(value: .updateRoute(.startup))
|
|
|
|
.delay(for: 3, scheduler: environment.scheduler)
|
|
|
|
.eraseToEffect()
|
|
|
|
|
|
|
|
/// Default is meaningful here because there's `routeReducer` handling routes and this reducer is handling only actions. We don't here plenty of unused cases.
|
2022-04-01 04:27:37 -07:00
|
|
|
default:
|
|
|
|
return .none
|
|
|
|
}
|
|
|
|
}
|
2022-04-04 05:04:01 -07:00
|
|
|
|
2021-12-13 18:16:03 -08:00
|
|
|
private static let routeReducer = AppReducer { state, action, _ in
|
2021-12-13 16:51:31 -08:00
|
|
|
switch action {
|
|
|
|
case let .updateRoute(route):
|
|
|
|
state.route = route
|
2021-12-13 12:50:04 -08:00
|
|
|
|
2021-12-13 16:20:56 -08:00
|
|
|
case .home(.reset):
|
|
|
|
state.route = .startup
|
2021-12-13 12:50:04 -08:00
|
|
|
|
2022-04-01 04:27:37 -07:00
|
|
|
case .onboarding(.createNewWallet):
|
|
|
|
return Effect(value: .createNewWallet)
|
|
|
|
|
|
|
|
case .phraseValidation(.proceedToHome):
|
2021-12-13 16:51:31 -08:00
|
|
|
state.route = .home
|
2021-12-13 12:50:04 -08:00
|
|
|
|
|
|
|
case .phraseValidation(.displayBackedUpPhrase),
|
|
|
|
.phraseDisplay(.createPhrase):
|
|
|
|
state.route = .phraseDisplay
|
|
|
|
|
|
|
|
case .phraseDisplay(.finishedPressed):
|
|
|
|
state.route = .phraseValidation
|
|
|
|
|
2022-04-04 05:04:01 -07:00
|
|
|
/// Default is meaningful here because there's `appReducer` handling actions and this reducer is handling only routes. We don't here plenty of unused cases.
|
2021-12-13 16:51:31 -08:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2021-12-13 12:50:04 -08:00
|
|
|
|
2021-12-13 16:51:31 -08:00
|
|
|
return .none
|
|
|
|
}
|
|
|
|
|
|
|
|
private static let homeReducer: AppReducer = HomeReducer.default.pullback(
|
|
|
|
state: \AppState.homeState,
|
|
|
|
action: /AppAction.home,
|
|
|
|
environment: { _ in }
|
|
|
|
)
|
|
|
|
|
|
|
|
private static let onboardingReducer: AppReducer = OnboardingReducer.default.pullback(
|
|
|
|
state: \AppState.onboardingState,
|
|
|
|
action: /AppAction.onboarding,
|
2021-12-13 18:16:03 -08:00
|
|
|
environment: { _ in }
|
2021-12-13 16:51:31 -08:00
|
|
|
)
|
2021-12-13 12:50:04 -08:00
|
|
|
|
|
|
|
private static let phraseValidationReducer: AppReducer = RecoveryPhraseValidationReducer.default.pullback(
|
|
|
|
state: \AppState.phraseValidationState,
|
|
|
|
action: /AppAction.phraseValidation,
|
|
|
|
environment: { _ in BackupPhraseEnvironment.demo }
|
|
|
|
)
|
|
|
|
|
|
|
|
private static let phraseDisplayReducer: AppReducer = RecoveryPhraseDisplayReducer.default.pullback(
|
|
|
|
state: \AppState.phraseDisplayState,
|
|
|
|
action: /AppAction.phraseDisplay,
|
|
|
|
environment: { _ in BackupPhraseEnvironment.demo }
|
|
|
|
)
|
2021-12-13 16:51:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - AppStore
|
|
|
|
|
|
|
|
typealias AppStore = Store<AppState, AppAction>
|
|
|
|
|
|
|
|
extension AppStore {
|
2022-04-04 05:04:01 -07:00
|
|
|
static var placeholder: AppStore {
|
|
|
|
AppStore(
|
|
|
|
initialState: .placeholder,
|
|
|
|
reducer: .default,
|
|
|
|
environment: .mock
|
|
|
|
)
|
|
|
|
}
|
2021-12-13 16:51:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - AppViewStore
|
|
|
|
|
|
|
|
typealias AppViewStore = ViewStore<AppState, AppAction>
|
|
|
|
|
|
|
|
extension AppViewStore {
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: PlaceHolders
|
|
|
|
|
|
|
|
extension AppState {
|
|
|
|
static var placeholder: Self {
|
|
|
|
.init(
|
|
|
|
homeState: .placeholder,
|
2022-03-01 00:12:50 -08:00
|
|
|
onboardingState: .init(
|
|
|
|
importWalletState: .placeholder
|
|
|
|
),
|
2021-12-13 12:50:04 -08:00
|
|
|
phraseValidationState: RecoveryPhraseValidationState.placeholder,
|
|
|
|
phraseDisplayState: RecoveryPhraseDisplayState(
|
|
|
|
phrase: .placeholder
|
|
|
|
)
|
2021-12-13 16:51:31 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|