From 71deb3c757962867f0e10d4bfc540483ecb40c2f Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Mon, 4 Apr 2022 13:23:57 +0200 Subject: [PATCH] gestures added debug menus are accessible starting on welcome screen following 2 steps: 1. long press (0.75s) 2. swipe up/down to access either home/startup cleanup Debug menus connected Also fixed the App flow --- secant.xcodeproj/project.pbxproj | 4 +++ secant/Features/App/App.swift | 26 +++++++++++++- secant/Features/App/Views/AppView.swift | 9 +++-- secant/Features/Welcome/Welcome.swift | 38 ++++++++++++++++++++ secant/Features/Welcome/WelcomeView.swift | 43 +++++++++++++++++++++-- 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 secant/Features/Welcome/Welcome.swift diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index f03dbeb..c04273e 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ 9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; }; 9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; }; 9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; }; + 9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; }; 9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; }; 9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; }; 9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; }; @@ -225,6 +226,7 @@ 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = ""; }; 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = ""; }; + 9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = ""; }; 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = ""; }; 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = ""; }; 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = ""; }; @@ -292,6 +294,7 @@ 0D0781C2278750C00083ACD7 /* Welcome */ = { isa = PBXGroup; children = ( + 9E69A24C27FB002800A55317 /* Welcome.swift */, 0D0781C3278750E30083ACD7 /* WelcomeView.swift */, ); path = Welcome; @@ -1147,6 +1150,7 @@ F9971A6027680DF600A2DB75 /* Scan.swift in Sources */, 9EF8139127F191BF0075AF48 /* WalletStorageInteractor.swift in Sources */, 0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */, + 9E69A24D27FB002800A55317 /* Welcome.swift in Sources */, F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */, 0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */, F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */, diff --git a/secant/Features/App/App.swift b/secant/Features/App/App.swift index 64f7f0a..36455cf 100644 --- a/secant/Features/App/App.swift +++ b/secant/Features/App/App.swift @@ -14,6 +14,7 @@ struct AppState: Equatable { var onboardingState: OnboardingState var phraseValidationState: RecoveryPhraseValidationState var phraseDisplayState: RecoveryPhraseDisplayState + var welcomeState: WelcomeState var route: Route = .welcome var storedWallet: StoredWallet? var appInitializationState: InitializationState = .uninitialized @@ -29,6 +30,7 @@ enum AppAction: Equatable { case phraseDisplay(RecoveryPhraseDisplayAction) case phraseValidation(RecoveryPhraseValidationAction) case updateRoute(AppState.Route) + case welcome(WelcomeAction) } struct AppEnvironment { @@ -53,6 +55,8 @@ extension AppEnvironment { // MARK: - AppReducer +private struct ListenerId: Hashable {} + typealias AppReducer = Reducer extension AppReducer { @@ -123,6 +127,7 @@ extension AppReducer { return Effect(value: .updateRoute(.onboarding)) .delay(for: 3, scheduler: environment.scheduler) .eraseToEffect() + .cancellable(id: ListenerId(), cancelInFlight: true) } return .none @@ -143,6 +148,18 @@ extension AppReducer { .delay(for: 3, scheduler: environment.scheduler) .eraseToEffect() + case .welcome(.debugMenuHome): + return .concatenate( + Effect.cancel(id: ListenerId()), + Effect(value: .updateRoute(.home)) + ) + + case .welcome(.debugMenuStartup): + return .concatenate( + Effect.cancel(id: ListenerId()), + Effect(value: .updateRoute(.startup)) + ) + /// 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 @@ -201,6 +218,12 @@ extension AppReducer { action: /AppAction.phraseDisplay, environment: { _ in BackupPhraseEnvironment.demo } ) + + private static let welcomeReducer: AppReducer = WelcomeReducer.default.pullback( + state: \AppState.welcomeState, + action: /AppAction.welcome, + environment: { _ in } + ) } // MARK: - AppStore @@ -236,7 +259,8 @@ extension AppState { phraseValidationState: RecoveryPhraseValidationState.placeholder, phraseDisplayState: RecoveryPhraseDisplayState( phrase: .placeholder - ) + ), + welcomeState: .placeholder ) } } diff --git a/secant/Features/App/Views/AppView.swift b/secant/Features/App/Views/AppView.swift index ac2dc1d..b3f910d 100644 --- a/secant/Features/App/Views/AppView.swift +++ b/secant/Features/App/Views/AppView.swift @@ -57,8 +57,13 @@ struct AppView: View { ) } case .welcome: - WelcomeView() - .onAppear(perform: { viewStore.send(.checkWalletInitialization) }) + WelcomeView( + store: store.scope( + state: \.welcomeState, + action: AppAction.welcome + ) + ) + .onAppear(perform: { viewStore.send(.checkWalletInitialization) }) } } } diff --git a/secant/Features/Welcome/Welcome.swift b/secant/Features/Welcome/Welcome.swift new file mode 100644 index 0000000..4d1a477 --- /dev/null +++ b/secant/Features/Welcome/Welcome.swift @@ -0,0 +1,38 @@ +// +// Welcome.swift +// secant-testnet +// +// Created by Lukáš Korba on 04.04.2022. +// + +import Foundation +import ComposableArchitecture + +struct WelcomeState: Equatable {} + +extension WelcomeState { + static let placeholder = WelcomeState() +} + +enum WelcomeAction: Equatable { + case debugMenuStartup + case debugMenuHome +} + +typealias WelcomeReducer = Reducer + +extension WelcomeReducer { + static let `default` = WelcomeReducer { _, _, _ in + return .none + } +} + +typealias WelcomeStore = Store + +extension WelcomeStore { + static var demo = WelcomeStore( + initialState: .placeholder, + reducer: .default, + environment: () + ) +} diff --git a/secant/Features/Welcome/WelcomeView.swift b/secant/Features/Welcome/WelcomeView.swift index 26c1182..3cd2568 100644 --- a/secant/Features/Welcome/WelcomeView.swift +++ b/secant/Features/Welcome/WelcomeView.swift @@ -6,13 +6,49 @@ // import SwiftUI +import ComposableArchitecture struct WelcomeView: View { + var store: WelcomeStore + + enum DragState { + case inactive + case pressing + case dragging(translation: CGSize) + } + let topPaddingRatio: Double = 0.18 let horizontalPaddingRatio: Double = 0.07 + @GestureState var dragState = DragState.inactive + var body: some View { - GeometryReader { proxy in + let longPressDrag = LongPressGesture(minimumDuration: 0.75) + .sequenced(before: DragGesture()) + .updating($dragState) { value, state, _ in + switch value { + // Long press begins. + case .first(true): + state = .pressing + // Long press confirmed, dragging may begin. + case .second(true, let drag): + state = .dragging(translation: drag?.translation ?? .zero) + // Dragging ended or the long press cancelled. + default: + state = .inactive + } + } + .onEnded { value in + guard case .second(true, let drag?) = value else { return } + + if drag.translation.height < 0 { + ViewStore(store).send(.debugMenuHome) + } else { + ViewStore(store).send(.debugMenuStartup) + } + } + + return GeometryReader { proxy in ZStack(alignment: .top) { VStack(alignment: .center, spacing: 80) { let diameter = proxy.size.width - 40 @@ -21,6 +57,7 @@ struct WelcomeView: View { width: diameter, height: diameter ) + .gesture(longPressDrag) VStack { Text("welcomeScreen.title") @@ -118,10 +155,10 @@ struct WelcomeView_Previews: PreviewProvider { .preferredColorScheme(.light) Group { - WelcomeView() + WelcomeView(store: .demo) .preferredColorScheme(.dark) - WelcomeView() + WelcomeView(store: .demo) .previewDevice("iPhone SE (2nd generation)") } }