Merge pull request #170 from LukasKorba/163_validation_feedback
Requested changes done
This commit is contained in:
commit
3b2dd7c43a
|
@ -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?
|
||||
|
@ -40,6 +54,7 @@ struct BackupPhraseEnvironment {
|
|||
let mainQueue: AnySchedulerOf<DispatchQueue>
|
||||
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
|
||||
let pasteboard: Pasteboard
|
||||
let feedbackGenerator: FeedbackGenerator
|
||||
}
|
||||
|
||||
extension BackupPhraseEnvironment {
|
||||
|
@ -51,13 +66,15 @@ extension BackupPhraseEnvironment {
|
|||
static let demo = Self(
|
||||
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) },
|
||||
pasteboard: .test
|
||||
pasteboard: .test,
|
||||
feedbackGenerator: .silent
|
||||
)
|
||||
|
||||
static let live = Self(
|
||||
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
|
||||
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) },
|
||||
pasteboard: .live
|
||||
pasteboard: .live,
|
||||
feedbackGenerator: .haptic
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,18 @@ extension RecoveryPhraseValidationReducer {
|
|||
|
||||
if state.isComplete {
|
||||
let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail
|
||||
return Effect(value: value)
|
||||
let effect = Effect<RecoveryPhraseValidationAction, Never>(value: value)
|
||||
.delay(for: 1, scheduler: environment.mainQueue)
|
||||
.eraseToEffect()
|
||||
|
||||
if value == .succeed {
|
||||
return effect
|
||||
} else {
|
||||
return .concatenate(
|
||||
Effect(value: .failureFeedback),
|
||||
effect
|
||||
)
|
||||
}
|
||||
}
|
||||
return .none
|
||||
|
||||
|
@ -168,6 +178,9 @@ extension RecoveryPhraseValidationReducer {
|
|||
case .fail:
|
||||
state.route = .failure
|
||||
|
||||
case .failureFeedback:
|
||||
environment.feedbackGenerator.generateFeedback()
|
||||
|
||||
case .updateRoute(let route):
|
||||
state.route = route
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ extension RecoveryPhraseValidationState {
|
|||
}
|
||||
|
||||
if let completedWord = validationWord?.word {
|
||||
return .unassigned(word: completedWord)
|
||||
return .unassigned(word: completedWord, color: self.coloredChipColor)
|
||||
}
|
||||
|
||||
return .empty
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue