[#1290] Inform a user to plug in a device when restoring

- Changelog updated
- Screen with the syncing tips has been implemented
- The sync starts immediately after we know seed + BD (optional) and the view is just presented to inform users
This commit is contained in:
Lukas Korba 2024-06-04 15:50:29 +02:00
parent 891461e6f9
commit d72307e080
12 changed files with 220 additions and 5 deletions

View File

@ -6,7 +6,10 @@ directly impact users rather than highlighting other crucial architectural updat
## [Unreleased]
## Fixed
### Added
- Screen summarizing successful restoration with some syncing tips.
### Fixed
- The application startup pipeline has been optimized, significantly improving performance. Consequently, Zashi now features faster cold and warm starts, and the transaction history is populated almost instantly.
- Transaction messages are now checked for duplicity and removed if duplicates are found.

View File

@ -42,6 +42,7 @@ let package = Package(
.library(name: "PrivateDataConsent", targets: ["PrivateDataConsent"]),
.library(name: "QRImageDetector", targets: ["QRImageDetector"]),
.library(name: "RecoveryPhraseDisplay", targets: ["RecoveryPhraseDisplay"]),
.library(name: "RestoreInfo", targets: ["RestoreInfo"]),
.library(name: "ReviewRequest", targets: ["ReviewRequest"]),
.library(name: "Root", targets: ["Root"]),
.library(name: "Sandbox", targets: ["Sandbox"]),
@ -286,6 +287,7 @@ let package = Package(
dependencies: [
"Generated",
"MnemonicClient",
"RestoreInfo",
"UIComponents",
"Utils",
"WalletStorage",
@ -424,6 +426,15 @@ let package = Package(
],
path: "Sources/Dependencies/RestoreWalletStorage"
),
.target(
name: "RestoreInfo",
dependencies: [
"Generated",
"UIComponents",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
path: "Sources/Features/RestoreInfo"
),
.target(
name: "ReviewRequest",
dependencies: [

View File

@ -13,6 +13,7 @@ import Generated
import WalletStorage
import MnemonicClient
import ZcashSDKEnvironment
import RestoreInfo
public typealias ImportWalletStore = Store<ImportWalletReducer.State, ImportWalletReducer.Action>
public typealias ImportWalletViewStore = ViewStore<ImportWalletReducer.State, ImportWalletReducer.Action>
@ -31,6 +32,8 @@ public struct ImportWalletReducer: Reducer {
public var isValidMnemonic = false
public var isValidNumberOfWords = false
public var maxWordsCount = 0
public var restoreInfoState: RestoreInfo.State
public var restoreInfoViewBinding: Bool = false
public var wordsCount = 0
public var mnemonicStatus: String {
@ -55,9 +58,10 @@ public struct ImportWalletReducer: Reducer {
isValidMnemonic: Bool = false,
isValidNumberOfWords: Bool = false,
maxWordsCount: Int = 0,
restoreInfoState: RestoreInfo.State,
restoreInfoViewBinding: Bool = false,
wordsCount: Int = 0
) {
self.alert = alert
self.birthdayHeight = birthdayHeight
self.birthdayHeightValue = birthdayHeightValue
self.destination = destination
@ -65,6 +69,8 @@ public struct ImportWalletReducer: Reducer {
self.isValidMnemonic = isValidMnemonic
self.isValidNumberOfWords = isValidNumberOfWords
self.maxWordsCount = maxWordsCount
self.restoreInfoState = restoreInfoState
self.restoreInfoViewBinding = restoreInfoViewBinding
self.wordsCount = wordsCount
}
}
@ -76,6 +82,8 @@ public struct ImportWalletReducer: Reducer {
case initializeSDK
case nextPressed
case onAppear
case restoreInfo(RestoreInfo.Action)
case restoreInfoRequested(Bool)
case restoreWallet
case seedPhraseInputChanged(RedactableString)
case successfullyRecovered
@ -89,6 +97,10 @@ public struct ImportWalletReducer: Reducer {
public init() { }
public var body: some Reducer<State, Action> {
Scope(state: \.restoreInfoState, action: /Action.restoreInfo) {
RestoreInfo()
}
Reduce { state, action in
switch action {
case .onAppear:
@ -134,6 +146,13 @@ public struct ImportWalletReducer: Reducer {
case .nextPressed:
return .none
case .restoreInfo:
return .none
case .restoreInfoRequested(let newValue):
state.restoreInfoViewBinding = newValue
return .none
case .restoreWallet:
do {
// validate the seed
@ -170,6 +189,7 @@ public struct ImportWalletReducer: Reducer {
return .none
case .successfullyRecovered:
state.restoreInfoViewBinding = true
return .none
case .initializeSDK:
@ -225,7 +245,7 @@ extension AlertState where Action == ImportWalletReducer.Action {
// MARK: - Placeholders
extension ImportWalletReducer.State {
public static let initial = ImportWalletReducer.State()
public static let initial = ImportWalletReducer.State(restoreInfoState: .initial)
}
extension ImportWalletStore {

View File

@ -10,6 +10,7 @@ import ComposableArchitecture
import Generated
import UIComponents
import Utils
import RestoreInfo
public struct ImportWalletView: View {
private enum InputID: Hashable {
@ -111,6 +112,20 @@ public struct ImportWalletView: View {
isActive: viewStore.bindingForDestination(.birthday),
destination: { ImportBirthdayView(store: store) }
)
.navigationLinkEmpty(
isActive: Binding(
get: { viewStore.state.restoreInfoViewBinding },
set: { viewStore.send(.restoreInfoRequested($0)) }
),
destination: {
RestoreInfoView(
store: store.scope(
state: \.restoreInfoState,
action: ImportWalletReducer.Action.restoreInfo
)
)
}
)
.alert(store: store.scope(
state: \.$alert,
action: { .alert($0) }

View File

@ -0,0 +1,31 @@
//
// RestoreInfoStore.swift
// Zashi
//
// Created by Lukáš Korba on 06-03-2024
//
import ComposableArchitecture
import Generated
@Reducer
public struct RestoreInfo {
@ObservableState
public struct State: Equatable { }
public enum Action: Equatable {
case gotItTapped
}
public init() { }
public var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .gotItTapped:
return .none
}
}
}
}

View File

@ -0,0 +1,98 @@
//
// RestoreInfoView.swift
// Zashi
//
// Created by Lukáš Korba on 06-03-2024
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct RestoreInfoView: View {
@Perception.Bindable var store: StoreOf<RestoreInfo>
public init(store: StoreOf<RestoreInfo>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
ScrollView {
Text(L10n.RestoreInfo.title)
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
.padding(.vertical, 30)
Asset.Assets.restoreInfo.image
.renderingMode(.template)
.resizable()
.frame(width: 106, height: 168)
.foregroundColor(Asset.Colors.primary.color)
.padding(.bottom, 30)
VStack(alignment: .leading) {
Text(L10n.RestoreInfo.tips)
.font(.custom(FontFamily.Inter.bold.name, size: 14))
bulletpoint(L10n.RestoreInfo.tip1)
bulletpoint(L10n.RestoreInfo.tip2)
bulletpoint(L10n.RestoreInfo.tip3)
Text(L10n.RestoreInfo.note)
.font(.custom(FontFamily.Inter.medium.name, size: 11))
.padding(.top, 20)
}
.padding(.horizontal, 30)
Button(L10n.RestoreInfo.gotIt.uppercased()) {
store.send(.gotItTapped)
}
.zcashStyle()
.padding(.vertical, 50)
.padding(.horizontal, 40)
}
.padding(.horizontal, 50)
.padding(.vertical, 1)
.zashiBack(hidden: true)
}
.navigationBarTitleDisplayMode(.inline)
.applyScreenBackground(withPattern: true)
}
@ViewBuilder
private func bulletpoint(_ text: String) -> some View {
HStack(alignment: .top) {
Circle()
.frame(width: 4, height: 4)
.padding(.top, 7)
.padding(.leading, 8)
Text(text)
.font(.custom(FontFamily.Inter.regular.name, size: 14))
}
.padding(.bottom, 5)
}
}
// MARK: - Previews
#Preview {
RestoreInfoView(store: RestoreInfo.initial)
}
// MARK: - Store
extension RestoreInfo {
public static var initial = StoreOf<RestoreInfo>(
initialState: .initial
) {
RestoreInfo()
}
}
// MARK: - Placeholders
extension RestoreInfo.State {
public static let initial = RestoreInfo.State()
}

View File

@ -444,8 +444,7 @@ extension RootReducer {
return .none
}
case .onboarding(.importWallet(.successfullyRecovered)):
state.alert = AlertState.successfullyRecovered()
case .onboarding(.importWallet(.restoreInfo(.gotItTapped))):
return Effect.send(.destination(.updateDestination(.tabs)))
case .onboarding(.importWallet(.initializeSDK)):

View File

@ -303,6 +303,22 @@ public enum L10n {
public static let wroteItDown = L10n.tr("Localizable", "recoveryPhraseDisplay.button.wroteItDown", fallback: "I've saved it")
}
}
public enum RestoreInfo {
/// Got it!
public static let gotIt = L10n.tr("Localizable", "restoreInfo.gotIt", fallback: "Got it!")
/// *Note: During the initial sync your funds cannot be sent or spent. Depending on the age of your wallet, it may take a few hours to fully sync.
public static let note = L10n.tr("Localizable", "restoreInfo.note", fallback: "*Note: During the initial sync your funds cannot be sent or spent. Depending on the age of your wallet, it may take a few hours to fully sync.")
/// Zashi needs to stay open in order to continue syncing.
public static let tip1 = L10n.tr("Localizable", "restoreInfo.tip1", fallback: "Zashi needs to stay open in order to continue syncing.")
/// To prevent interruption, plug your open phone into a power source.
public static let tip2 = L10n.tr("Localizable", "restoreInfo.tip2", fallback: "To prevent interruption, plug your open phone into a power source.")
/// Keep your open phone in a secure place.
public static let tip3 = L10n.tr("Localizable", "restoreInfo.tip3", fallback: "Keep your open phone in a secure place.")
/// Syncing Tips:
public static let tips = L10n.tr("Localizable", "restoreInfo.tips", fallback: "Syncing Tips:")
/// Your wallet has been successfully restored!*
public static let title = L10n.tr("Localizable", "restoreInfo.title", fallback: "Your wallet has been successfully restored!*")
}
public enum Root {
public enum Debug {
/// Startup

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "restoreInfo.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -45,6 +45,15 @@
"importWallet.optionalBirthday" = "(optional)";
"importWallet.enterPlaceholder" = "privacy dignity freedom ...";
// MARK: - Restore Info
"restoreInfo.title" = "Your wallet has been successfully restored!*";
"restoreInfo.tips" = "Syncing Tips:";
"restoreInfo.tip1" = "Zashi needs to stay open in order to continue syncing.";
"restoreInfo.tip2" = "To prevent interruption, plug your open phone into a power source.";
"restoreInfo.tip3" = "Keep your open phone in a secure place.";
"restoreInfo.note" = "*Note: During the initial sync your funds cannot be sent or spent. Depending on the age of your wallet, it may take a few hours to fully sync.";
"restoreInfo.gotIt" = "Got it!";
// MARK: - Tabs
"tabs.account" = "Account";
"tabs.send" = "Send";

View File

@ -34,6 +34,7 @@ public enum Asset {
public static let eyeOn = ImageAsset(name: "eyeOn")
public static let flyReceivedFilled = ImageAsset(name: "flyReceivedFilled")
public static let gridTile = ImageAsset(name: "gridTile")
public static let restoreInfo = ImageAsset(name: "restoreInfo")
public static let share = ImageAsset(name: "share")
public static let shield = ImageAsset(name: "shield")
public static let surroundedShield = ImageAsset(name: "surroundedShield")