From c8e54550fc78f1af340e3fbb216c8e2d58a114dd Mon Sep 17 00:00:00 2001 From: Lukas Korba <62649321+lukaskorba-indiedev@users.noreply.github.com> Date: Wed, 23 Feb 2022 17:49:17 +0100 Subject: [PATCH 1/5] Requested changes done The failed color (red) updated. The haptic feedback (.error) now occurs when the chips are not at the right indices. --- secant.xcodeproj/project.pbxproj | 4 +++ .../BackupFlow/FeedbackGenerator.swift | 26 +++++++++++++++++++ .../RecoveryPhraseDisplayStore.swift | 7 +++-- .../BackupFlow/RecoveryPhraseValidation.swift | 16 +++++++++--- .../BackupFlow/View+WhenDraggable.swift | 2 +- .../RecoveryPhraseBackupValidationView.swift | 10 ++++--- secant/UIComponents/Chips/PhraseChip.swift | 6 ++--- 7 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 secant/Features/BackupFlow/FeedbackGenerator.swift diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index ae19652..fe9a880 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ 66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; }; 66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; }; 66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; }; + 9E4DC71027C6981300E657F4 /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.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 */; }; @@ -209,6 +210,7 @@ 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = ""; }; 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = ""; }; 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = ""; }; + 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = ""; }; F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = ""; }; F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = ""; }; @@ -325,6 +327,7 @@ 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */, 0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */, 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */, + 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */, ); path = BackupFlow; sourceTree = ""; @@ -1062,6 +1065,7 @@ 0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */, F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */, 0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */, + 9E4DC71027C6981300E657F4 /* FeedbackGenerator.swift in Sources */, F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */, 0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */, 6654C7412715A47300901167 /* Onboarding.swift in Sources */, diff --git a/secant/Features/BackupFlow/FeedbackGenerator.swift b/secant/Features/BackupFlow/FeedbackGenerator.swift new file mode 100644 index 0000000..fd88ce1 --- /dev/null +++ b/secant/Features/BackupFlow/FeedbackGenerator.swift @@ -0,0 +1,26 @@ +// +// FeedbackGenerator.swift +// secant-testnet +// +// Created by Lukáš Korba on 02/23/2022. +// + +import UIKit + +protocol FeedbackGenerator { + func generateFeedback() +} + +/// use in case of testing or when real haptic feedback is not appropriate +class SilentFeedbackGenerator: FeedbackGenerator { + func generateFeedback() { } +} + +/// haptic feedback for the failures (when we want to amplify importance of the failure) +class ImpactFeedbackGenerator: FeedbackGenerator { + let generator = UINotificationFeedbackGenerator() + + func generateFeedback() { + generator.notificationOccurred(.error) + } +} diff --git a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift index bdba941..f480d03 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift @@ -40,6 +40,7 @@ struct BackupPhraseEnvironment { let mainQueue: AnySchedulerOf let newPhrase: () -> Effect let pasteboard: Pasteboard + let feedbackGenerator: FeedbackGenerator } extension BackupPhraseEnvironment { @@ -51,13 +52,15 @@ extension BackupPhraseEnvironment { static let demo = Self( mainQueue: DispatchQueue.main.eraseToAnyScheduler(), newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, - pasteboard: .test + pasteboard: .test, + feedbackGenerator: SilentFeedbackGenerator() ) static let live = Self( mainQueue: DispatchQueue.main.eraseToAnyScheduler(), newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, - pasteboard: .live + pasteboard: .live, + feedbackGenerator: ImpactFeedbackGenerator() ) } diff --git a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift index c779565..68eae62 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift @@ -133,6 +133,7 @@ enum RecoveryPhraseValidationAction: Equatable { case move(wordChip: PhraseChip.Kind, intoGroup: Int) case succeed case fail + case failureFeedback case proceedToHome case displayBackedUpPhrase } @@ -147,7 +148,7 @@ extension RecoveryPhraseValidationReducer { case let .move(wordChip, group): guard - case let PhraseChip.Kind.unassigned(word) = wordChip, + case let PhraseChip.Kind.unassigned(word, color) = wordChip, let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip) else { return .none } @@ -156,9 +157,13 @@ extension RecoveryPhraseValidationReducer { if state.isComplete { let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail - return Effect(value: value) - .delay(for: 1, scheduler: environment.mainQueue) - .eraseToEffect() + + return .concatenate( + .init(value: .failureFeedback), + Effect(value: value) + .delay(for: 1, scheduler: environment.mainQueue) + .eraseToEffect() + ) } return .none @@ -168,6 +173,9 @@ extension RecoveryPhraseValidationReducer { case .fail: state.route = .failure + case .failureFeedback: + environment.feedbackGenerator.generateFeedback() + case .updateRoute(let route): state.route = route diff --git a/secant/Features/BackupFlow/View+WhenDraggable.swift b/secant/Features/BackupFlow/View+WhenDraggable.swift index fa81c19..9fb8350 100644 --- a/secant/Features/BackupFlow/View+WhenDraggable.swift +++ b/secant/Features/BackupFlow/View+WhenDraggable.swift @@ -15,7 +15,7 @@ extension PhraseChip { /// Makes a PhraseChip draggable when it is of kind .unassigned @ViewBuilder func makeDraggable() -> some View { switch self.kind { - case .unassigned(let word): + case let .unassigned(word, _): self.onDrag { NSItemProvider(object: word as NSString) } diff --git a/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift b/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift index ea07ce1..dc34175 100644 --- a/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift +++ b/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift @@ -116,7 +116,8 @@ extension RecoveryPhraseValidationState { func wordsChips( for groupIndex: Int, groupSize: Int, - from wordGroup: RecoveryPhrase.Group + from wordGroup: RecoveryPhrase.Group, + state: RecoveryPhraseValidationState ) -> [PhraseChip.Kind] { let validationWord = validationWords.first(where: { $0.groupIndex == groupIndex }) @@ -126,7 +127,7 @@ extension RecoveryPhraseValidationState { } if let completedWord = validationWord?.word { - return .unassigned(word: completedWord) + return .unassigned(word: completedWord, color: state.coloredChipColor) } return .empty @@ -247,8 +248,9 @@ private extension WordChipGrid { ) { let chips = state.wordsChips( for: groupIndex, - groupSize: RecoveryPhraseValidationState.wordGroupSize, - from: wordGroup + groupSize: RecoveryPhraseValidationState.wordGroupSize, + from: wordGroup, + state: state ) self.init(chips: chips, coloredChipColor: state.coloredChipColor) diff --git a/secant/UIComponents/Chips/PhraseChip.swift b/secant/UIComponents/Chips/PhraseChip.swift index 491ba9a..ff4899e 100644 --- a/secant/UIComponents/Chips/PhraseChip.swift +++ b/secant/UIComponents/Chips/PhraseChip.swift @@ -10,7 +10,7 @@ import SwiftUI struct PhraseChip: View { enum Kind: Hashable { case empty - case unassigned(word: String) + case unassigned(word: String, color: Color = Asset.Colors.Buttons.activeButton.color) case ordered(position: Int, word: String) } @@ -22,8 +22,8 @@ struct PhraseChip: View { EmptyChip() case let .ordered(position, word): EnumeratedChip(index: position, text: word) - case .unassigned(let word): - ColoredChip(word: word) + case let .unassigned(word, color): + ColoredChip(word: word, color: color) } } } From b7fccb3c289e8f9492525bd1cbff3c32fa25a3f6 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Wed, 23 Feb 2022 18:39:39 +0100 Subject: [PATCH 2/5] TCA way The syntax convention for the feedback generator was re-implemented to follow TCA and secant wallet principles. --- .../BackupFlow/FeedbackGenerator.swift | 22 ++++++++----------- .../RecoveryPhraseDisplayStore.swift | 4 ++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/secant/Features/BackupFlow/FeedbackGenerator.swift b/secant/Features/BackupFlow/FeedbackGenerator.swift index fd88ce1..83edd91 100644 --- a/secant/Features/BackupFlow/FeedbackGenerator.swift +++ b/secant/Features/BackupFlow/FeedbackGenerator.swift @@ -7,20 +7,16 @@ import UIKit -protocol FeedbackGenerator { - func generateFeedback() +struct FeedbackGenerator { + let generateFeedback: () -> Void } -/// use in case of testing or when real haptic feedback is not appropriate -class SilentFeedbackGenerator: FeedbackGenerator { - func generateFeedback() { } -} - -/// haptic feedback for the failures (when we want to amplify importance of the failure) -class ImpactFeedbackGenerator: FeedbackGenerator { - let generator = UINotificationFeedbackGenerator() +extension FeedbackGenerator { + static let haptic = FeedbackGenerator( + generateFeedback: { UINotificationFeedbackGenerator().notificationOccurred(.error) } + ) - func generateFeedback() { - generator.notificationOccurred(.error) - } + static let silent = FeedbackGenerator( + generateFeedback: { } + ) } diff --git a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift index f480d03..424a13c 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift @@ -53,14 +53,14 @@ extension BackupPhraseEnvironment { mainQueue: DispatchQueue.main.eraseToAnyScheduler(), newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, pasteboard: .test, - feedbackGenerator: SilentFeedbackGenerator() + feedbackGenerator: .silent ) static let live = Self( mainQueue: DispatchQueue.main.eraseToAnyScheduler(), newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, pasteboard: .live, - feedbackGenerator: ImpactFeedbackGenerator() + feedbackGenerator: .haptic ) } From e618cc48e5791340244732c7888727ef26747416 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Wed, 23 Feb 2022 18:51:18 +0100 Subject: [PATCH 3/5] Generator moved I decided to also follow the Pastboard dependency and get rid of the specific file just for the generator. --- secant.xcodeproj/project.pbxproj | 4 ---- .../BackupFlow/FeedbackGenerator.swift | 22 ------------------- .../RecoveryPhraseDisplayStore.swift | 14 ++++++++++++ 3 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 secant/Features/BackupFlow/FeedbackGenerator.swift diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index fe9a880..ae19652 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ 66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; }; 66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; }; 66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; }; - 9E4DC71027C6981300E657F4 /* FeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.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 */; }; @@ -210,7 +209,6 @@ 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = ""; }; 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = ""; }; 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = ""; }; - 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.swift; sourceTree = ""; }; F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = ""; }; F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = ""; }; F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = ""; }; @@ -327,7 +325,6 @@ 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */, 0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */, 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */, - 9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */, ); path = BackupFlow; sourceTree = ""; @@ -1065,7 +1062,6 @@ 0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */, F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */, 0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */, - 9E4DC71027C6981300E657F4 /* FeedbackGenerator.swift in Sources */, F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */, 0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */, 6654C7412715A47300901167 /* Onboarding.swift in Sources */, diff --git a/secant/Features/BackupFlow/FeedbackGenerator.swift b/secant/Features/BackupFlow/FeedbackGenerator.swift deleted file mode 100644 index 83edd91..0000000 --- a/secant/Features/BackupFlow/FeedbackGenerator.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// FeedbackGenerator.swift -// secant-testnet -// -// Created by Lukáš Korba on 02/23/2022. -// - -import UIKit - -struct FeedbackGenerator { - let generateFeedback: () -> Void -} - -extension FeedbackGenerator { - static let haptic = FeedbackGenerator( - generateFeedback: { UINotificationFeedbackGenerator().notificationOccurred(.error) } - ) - - static let silent = FeedbackGenerator( - generateFeedback: { } - ) -} diff --git a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift index 424a13c..c465c91 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseDisplayStore.swift @@ -14,6 +14,20 @@ enum RecoveryPhraseError: Error { case unableToGeneratePhrase } +struct FeedbackGenerator { + let generateFeedback: () -> Void +} + +extension FeedbackGenerator { + static let haptic = FeedbackGenerator( + generateFeedback: { UINotificationFeedbackGenerator().notificationOccurred(.error) } + ) + + static let silent = FeedbackGenerator( + generateFeedback: { } + ) +} + struct Pasteboard { let setString: (String) -> Void let getString: () -> String? From 598eac0eed86751dec876a1e1078edfd4298d26b Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 24 Feb 2022 14:14:50 +0100 Subject: [PATCH 4/5] Requested changes All PR comments have been fixed in this commit --- .../Features/BackupFlow/RecoveryPhraseValidation.swift | 2 +- .../Views/RecoveryPhraseBackupValidationView.swift | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift index 68eae62..c39a188 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift @@ -159,7 +159,7 @@ extension RecoveryPhraseValidationReducer { let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail return .concatenate( - .init(value: .failureFeedback), + Effect(value: .failureFeedback), Effect(value: value) .delay(for: 1, scheduler: environment.mainQueue) .eraseToEffect() diff --git a/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift b/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift index dc34175..00391f5 100644 --- a/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift +++ b/secant/Features/BackupFlow/Views/RecoveryPhraseBackupValidationView.swift @@ -116,8 +116,7 @@ extension RecoveryPhraseValidationState { func wordsChips( for groupIndex: Int, groupSize: Int, - from wordGroup: RecoveryPhrase.Group, - state: RecoveryPhraseValidationState + from wordGroup: RecoveryPhrase.Group ) -> [PhraseChip.Kind] { let validationWord = validationWords.first(where: { $0.groupIndex == groupIndex }) @@ -127,7 +126,7 @@ extension RecoveryPhraseValidationState { } if let completedWord = validationWord?.word { - return .unassigned(word: completedWord, color: state.coloredChipColor) + return .unassigned(word: completedWord, color: self.coloredChipColor) } return .empty @@ -248,9 +247,8 @@ private extension WordChipGrid { ) { let chips = state.wordsChips( for: groupIndex, - groupSize: RecoveryPhraseValidationState.wordGroupSize, - from: wordGroup, - state: state + groupSize: RecoveryPhraseValidationState.wordGroupSize, + from: wordGroup ) self.init(chips: chips, coloredChipColor: state.coloredChipColor) From 465d4fededfa67d74ca7812815e4152f8ddeb1a4 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 24 Feb 2022 17:26:06 +0100 Subject: [PATCH 5/5] Test fixes --- .../BackupFlow/RecoveryPhraseValidation.swift | 17 +++++++++++------ .../RecoveryPhraseValidationTests.swift | 7 ++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift index c39a188..3368e4b 100644 --- a/secant/Features/BackupFlow/RecoveryPhraseValidation.swift +++ b/secant/Features/BackupFlow/RecoveryPhraseValidation.swift @@ -157,13 +157,18 @@ extension RecoveryPhraseValidationReducer { if state.isComplete { let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail + let effect = Effect(value: value) + .delay(for: 1, scheduler: environment.mainQueue) + .eraseToEffect() - return .concatenate( - Effect(value: .failureFeedback), - Effect(value: value) - .delay(for: 1, scheduler: environment.mainQueue) - .eraseToEffect() - ) + if value == .succeed { + return effect + } else { + return .concatenate( + Effect(value: .failureFeedback), + effect + ) + } } return .none diff --git a/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift b/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift index 0873b8a..d9e3da0 100644 --- a/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift +++ b/secantTests/RecoveryPhraseValidationTests/RecoveryPhraseValidationTests.swift @@ -15,7 +15,8 @@ class RecoveryPhraseValidationTests: XCTestCase { let testEnvironment = BackupPhraseEnvironment( mainQueue: testScheduler.eraseToAnyScheduler(), newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, - pasteboard: .test + pasteboard: .test, + feedbackGenerator: .silent ) func testPickWordsFromMissingIndices() throws { @@ -374,6 +375,10 @@ class RecoveryPhraseValidationTests: XCTestCase { Self.testScheduler.advance(by: 2) + store.receive(.failureFeedback) { + XCTAssertTrue($0.isComplete) + } + store.receive(.fail) { $0.route = .failure XCTAssertFalse($0.isValid)