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:
parent
8c12556236
commit
c8e54550fc
|
@ -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 */,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
.delay(for: 1, scheduler: environment.mainQueue)
|
return .concatenate(
|
||||||
.eraseToEffect()
|
.init(value: .failureFeedback),
|
||||||
|
Effect(value: value)
|
||||||
|
.delay(for: 1, scheduler: environment.mainQueue)
|
||||||
|
.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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -247,8 +248,9 @@ 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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue