Requested changes done

The failed color (red) updated. The haptic feedback (.error) now occurs when the chips are not at the right indices.
This commit is contained in:
Lukas Korba 2022-02-23 17:49:17 +01:00
parent 8c12556236
commit c8e54550fc
7 changed files with 57 additions and 14 deletions

View File

@ -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 = "<group>"; };
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGenerator.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>"; };
@ -325,6 +327,7 @@
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */,
0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */,
0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */,
9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */,
);
path = BackupFlow;
sourceTree = "<group>";
@ -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 */,

View File

@ -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)
}
}

View File

@ -40,6 +40,7 @@ struct BackupPhraseEnvironment {
let mainQueue: AnySchedulerOf<DispatchQueue>
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
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()
)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
}
}