App initialization and checks (#223)

* AppDelegate connected
* Initialisation of the app and checks in place
* cleanup
* switch-default purpose commented
* checkWalletInitialisation simplified
* initialisation -> initialization
This commit is contained in:
Lukas Korba 2022-04-04 14:04:01 +02:00 committed by GitHub
parent 18cff600d0
commit fd7109d1f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 50 deletions

View File

@ -20,7 +20,6 @@
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */; };
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */; };
0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0826B364170058B01E /* SecantApp.swift */; };
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0A26B364170058B01E /* ContentView.swift */; };
0D4E7A0D26B364180058B01E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0C26B364180058B01E /* Assets.xcassets */; };
0D4E7A1026B364180058B01E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0F26B364180058B01E /* Preview Assets.xcassets */; };
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A1A26B364180058B01E /* secantTests.swift */; };
@ -92,6 +91,8 @@
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
9EF8135C27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */; };
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139B27F47AED0075AF48 /* InitializationState.swift */; };
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; };
@ -153,7 +154,6 @@
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayStore.swift; sourceTree = "<group>"; };
0D4E7A0526B364170058B01E /* secant-testnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "secant-testnet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
0D4E7A0826B364170058B01E /* SecantApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantApp.swift; sourceTree = "<group>"; };
0D4E7A0A26B364170058B01E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
0D4E7A0C26B364180058B01E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0D4E7A0F26B364180058B01E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
0D4E7A1126B364180058B01E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -229,6 +229,8 @@
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorageTests.swift; sourceTree = "<group>"; };
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9EF8139B27F47AED0075AF48 /* InitializationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationState.swift; sourceTree = "<group>"; };
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = "<group>"; };
F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = "<group>"; };
F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = "<group>"; };
@ -396,7 +398,6 @@
0D1922EB26BDD9A500052649 /* Screens */,
0D170A7426BC9B7500EB6A46 /* MockedDependencies */,
0D4E7A0826B364170058B01E /* SecantApp.swift */,
0D4E7A0A26B364170058B01E /* ContentView.swift */,
0D4E7A0C26B364180058B01E /* Assets.xcassets */,
660558E8270C7A54009D6954 /* Colors.xcassets */,
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */,
@ -511,6 +512,7 @@
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */,
9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */,
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */,
9EF8139B27F47AED0075AF48 /* InitializationState.swift */,
);
path = Util;
sourceTree = "<group>";
@ -746,6 +748,7 @@
isa = PBXGroup;
children = (
F9971A4A27680DC400A2DB75 /* App.swift */,
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */,
F9971A4B27680DC400A2DB75 /* Views */,
);
path = App;
@ -1069,6 +1072,7 @@
0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */,
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */,
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
@ -1109,7 +1113,6 @@
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */,
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */,
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */,
0D354A0926D5A9D000315F45 /* Services.swift in Sources */,
660558F7270C862F009D6954 /* Fonts+Generated.swift in Sources */,
F96B41E7273B501F0021B49A /* TransactionHistoryStore.swift in Sources */,
@ -1143,6 +1146,7 @@
F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */,
0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */,
F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */,
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */,
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
6654C7412715A47300901167 /* Onboarding.swift in Sources */,
F9C165C42740403600592F76 /* SentView.swift in Sources */,

View File

@ -1,22 +0,0 @@
//
// ContentView.swift
// secant
//
// Created by Francisco Gindre on 7/29/21.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, Zcash!")
.font(FontFamily.Zboto.regular.textStyle(.body))
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@ -15,29 +15,37 @@ struct AppState: Equatable {
var phraseValidationState: RecoveryPhraseValidationState
var phraseDisplayState: RecoveryPhraseDisplayState
var route: Route = .welcome
var storedWallet: StoredWallet?
var appInitializationState: InitializationState = .uninitialized
}
enum AppAction: Equatable {
case appDelegate(AppDelegateAction)
case checkWalletInitialization
case createNewWallet
case updateRoute(AppState.Route)
case home(HomeAction)
case initializeApp
case onboarding(OnboardingAction)
case phraseDisplay(RecoveryPhraseDisplayAction)
case phraseValidation(RecoveryPhraseValidationAction)
case updateRoute(AppState.Route)
}
struct AppEnvironment {
let scheduler: AnySchedulerOf<DispatchQueue>
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: RecoveryPhraseStorage
}
extension AppEnvironment {
static let live = AppEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .live,
walletStorage: RecoveryPhraseStorage()
)
static let mock = AppEnvironment(
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock,
walletStorage: RecoveryPhraseStorage()
)
@ -82,11 +90,61 @@ extension AppReducer {
return Effect(value: .phraseValidation(.displayBackedUpPhrase))
/// 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.
default:
return .none
}
}
private static let routeReducer = AppReducer { state, action, _ in
switch action {
case let .updateRoute(route):
@ -108,6 +166,7 @@ extension AppReducer {
case .phraseDisplay(.finishedPressed):
state.route = .phraseValidation
/// 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.
default:
break
}
@ -145,6 +204,13 @@ extension AppReducer {
typealias AppStore = Store<AppState, AppAction>
extension AppStore {
static var placeholder: AppStore {
AppStore(
initialState: .placeholder,
reducer: .default,
environment: .mock
)
}
}
// MARK: - AppViewStore

View File

@ -0,0 +1,12 @@
//
// AppDelegate.swift
// secant-testnet
//
// Created by Lukáš Korba on 27.03.2022.
//
import Foundation
public enum AppDelegateAction: Equatable {
case didFinishLaunching
}

View File

@ -58,14 +58,7 @@ struct AppView: View {
}
case .welcome:
WelcomeView()
.transition(.opacity)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation(.easeInOut(duration: 1)) {
viewStore.send(.updateRoute(.startup))
}
}
}
.onAppear(perform: { viewStore.send(.checkWalletInitialization) })
}
}
}

View File

@ -31,7 +31,7 @@ protocol KeyStoring {
/**
Check if the wallet representation `StoredWallet` is present in the persistent storage.
*/
func areKeysPresent() throws -> Bool
func areKeysPresent() -> Bool
/**
Update the birthday in the securely stored wallet.

View File

@ -6,24 +6,31 @@
//
import SwiftUI
import ComposableArchitecture
final class AppDelegate: NSObject, UIApplicationDelegate {
var appStore: AppStore = .placeholder
lazy var appViewStore = ViewStore(
appStore.stateless,
removeDuplicates: ==
)
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
appViewStore.send(.appDelegate(.didFinishLaunching))
return true
}
}
@main
struct SecantApp: App {
var appStore: AppStore = .placeholder
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
var body: some Scene {
WindowGroup {
AppView(store: appStore)
AppView(store: appDelegate.appStore)
}
}
}
extension AppStore {
static var placeholder: AppStore {
AppStore(
initialState: .placeholder,
reducer: .default,
environment: .mock
)
}
}

View File

@ -0,0 +1,16 @@
//
// InitializationState.swift
// secant-testnet
//
// Created by Lukáš Korba on 30.03.2022.
//
import Foundation
enum InitializationState: Equatable {
case failed
case initialized
case keysMissing
case filesMissing
case uninitialized
}

View File

@ -220,7 +220,7 @@ extension RecoveryPhraseStorage: KeyStoring {
}
}
func areKeysPresent() throws -> Bool {
func areKeysPresent() -> Bool {
do {
_ = try exportWallet()
} catch KeyStoringError.uninitializedWallet {