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 */; }; 66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; };
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; }; 66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; };
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.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 */; }; F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; }; F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = "<group>"; };
@ -325,6 +327,7 @@
0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */, 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */,
0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */, 0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */,
0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */, 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */,
9E4DC70F27C6981300E657F4 /* FeedbackGenerator.swift */,
); );
path = BackupFlow; path = BackupFlow;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1062,6 +1065,7 @@
0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */, 0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */,
F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */, F9C165CB2741AB5D00592F76 /* SendView.swift in Sources */,
0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */, 0D0781C4278750E30083ACD7 /* WelcomeView.swift in Sources */,
9E4DC71027C6981300E657F4 /* FeedbackGenerator.swift in Sources */,
F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */, F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */,
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */, 0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
6654C7412715A47300901167 /* Onboarding.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 mainQueue: AnySchedulerOf<DispatchQueue>
let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError> let newPhrase: () -> Effect<RecoveryPhrase, RecoveryPhraseError>
let pasteboard: Pasteboard let pasteboard: Pasteboard
let feedbackGenerator: FeedbackGenerator
} }
extension BackupPhraseEnvironment { extension BackupPhraseEnvironment {
@ -51,13 +52,15 @@ extension BackupPhraseEnvironment {
static let demo = Self( static let demo = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(), mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) },
pasteboard: .test pasteboard: .test,
feedbackGenerator: SilentFeedbackGenerator()
) )
static let live = Self( static let live = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler(), mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
newPhrase: { Effect(value: .init(words: RecoveryPhrase.placeholder.words)) }, 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 move(wordChip: PhraseChip.Kind, intoGroup: Int)
case succeed case succeed
case fail case fail
case failureFeedback
case proceedToHome case proceedToHome
case displayBackedUpPhrase case displayBackedUpPhrase
} }
@ -147,7 +148,7 @@ extension RecoveryPhraseValidationReducer {
case let .move(wordChip, group): case let .move(wordChip, group):
guard guard
case let PhraseChip.Kind.unassigned(word) = wordChip, case let PhraseChip.Kind.unassigned(word, color) = wordChip,
let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip) let missingChipIndex = state.missingWordChips.firstIndex(of: wordChip)
else { return .none } else { return .none }
@ -156,9 +157,13 @@ extension RecoveryPhraseValidationReducer {
if state.isComplete { if state.isComplete {
let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail let value: RecoveryPhraseValidationAction = state.isValid ? .succeed : .fail
return Effect(value: value)
return .concatenate(
.init(value: .failureFeedback),
Effect(value: value)
.delay(for: 1, scheduler: environment.mainQueue) .delay(for: 1, scheduler: environment.mainQueue)
.eraseToEffect() .eraseToEffect()
)
} }
return .none return .none
@ -168,6 +173,9 @@ extension RecoveryPhraseValidationReducer {
case .fail: case .fail:
state.route = .failure state.route = .failure
case .failureFeedback:
environment.feedbackGenerator.generateFeedback()
case .updateRoute(let route): case .updateRoute(let route):
state.route = route state.route = route

View File

@ -15,7 +15,7 @@ extension PhraseChip {
/// Makes a PhraseChip draggable when it is of kind .unassigned /// Makes a PhraseChip draggable when it is of kind .unassigned
@ViewBuilder func makeDraggable() -> some View { @ViewBuilder func makeDraggable() -> some View {
switch self.kind { switch self.kind {
case .unassigned(let word): case let .unassigned(word, _):
self.onDrag { self.onDrag {
NSItemProvider(object: word as NSString) NSItemProvider(object: word as NSString)
} }

View File

@ -116,7 +116,8 @@ extension RecoveryPhraseValidationState {
func wordsChips( func wordsChips(
for groupIndex: Int, for groupIndex: Int,
groupSize: Int, groupSize: Int,
from wordGroup: RecoveryPhrase.Group from wordGroup: RecoveryPhrase.Group,
state: RecoveryPhraseValidationState
) -> [PhraseChip.Kind] { ) -> [PhraseChip.Kind] {
let validationWord = validationWords.first(where: { $0.groupIndex == groupIndex }) let validationWord = validationWords.first(where: { $0.groupIndex == groupIndex })
@ -126,7 +127,7 @@ extension RecoveryPhraseValidationState {
} }
if let completedWord = validationWord?.word { if let completedWord = validationWord?.word {
return .unassigned(word: completedWord) return .unassigned(word: completedWord, color: state.coloredChipColor)
} }
return .empty return .empty
@ -248,7 +249,8 @@ private extension WordChipGrid {
let chips = state.wordsChips( let chips = state.wordsChips(
for: groupIndex, for: groupIndex,
groupSize: RecoveryPhraseValidationState.wordGroupSize, groupSize: RecoveryPhraseValidationState.wordGroupSize,
from: wordGroup from: wordGroup,
state: state
) )
self.init(chips: chips, coloredChipColor: state.coloredChipColor) self.init(chips: chips, coloredChipColor: state.coloredChipColor)

View File

@ -10,7 +10,7 @@ import SwiftUI
struct PhraseChip: View { struct PhraseChip: View {
enum Kind: Hashable { enum Kind: Hashable {
case empty case empty
case unassigned(word: String) case unassigned(word: String, color: Color = Asset.Colors.Buttons.activeButton.color)
case ordered(position: Int, word: String) case ordered(position: Int, word: String)
} }
@ -22,8 +22,8 @@ struct PhraseChip: View {
EmptyChip() EmptyChip()
case let .ordered(position, word): case let .ordered(position, word):
EnumeratedChip(index: position, text: word) EnumeratedChip(index: position, text: word)
case .unassigned(let word): case let .unassigned(word, color):
ColoredChip(word: word) ColoredChip(word: word, color: color)
} }
} }
} }