[#1477] Restore wallet screen logic

[#1477] Update Restore Wallet flow

- Everything has been redesigned
- Estimation of Birthday height logic implemented
This commit is contained in:
Lukas Korba 2025-03-31 08:30:02 +02:00
parent ab3226aced
commit aba41ab0ee
42 changed files with 1219 additions and 160 deletions

View File

@ -6,6 +6,9 @@ directly impact users rather than highlighting other crucial architectural updat
## [Unreleased]
### Changed
- Restore wallet flow has been fully redesigned. New option to estimate Birthday of the wallet based on a date has been implemented.
## 1.5.3 build 1 (2025-04-14)
### Fixed

View File

@ -84,6 +84,7 @@ let package = Package(
.library(name: "Utils", targets: ["Utils"]),
.library(name: "Vendors", targets: ["Vendors"]),
.library(name: "WalletBalances", targets: ["WalletBalances"]),
.library(name: "WalletBirthday", targets: ["WalletBirthday"]),
.library(name: "WalletConfigProvider", targets: ["WalletConfigProvider"]),
.library(name: "WalletStorage", targets: ["WalletStorage"]),
.library(name: "Welcome", targets: ["Welcome"]),
@ -97,8 +98,9 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.5.6"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.6.2"),
.package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.5"),
.package(url: "https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk", from: "2.2.11"),
.package(url: "https://github.com/flexa/flexa-ios.git", from: "1.0.9"),
// .package(url: "https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk", from: "2.2.10"),
.package(url: "https://github.com/LukasKorba/ZcashLightClientKit", branch: "1537-Birthday-estimate-based-on-a-date"),
.package(url: "https://github.com/flexa/flexa-ios.git", exact: "1.0.9"),
.package(url: "https://github.com/pacu/zcash-swift-payment-uri", from: "0.1.0-beta.10"),
.package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.5.1"),
.package(url: "https://github.com/KeystoneHQ/keystone-sdk-ios/", from: "0.0.1")
@ -126,7 +128,7 @@ let package = Package(
"UIComponents",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "KeystoneSDK", package: "keystone-sdk-ios")
],
path: "Sources/Features/AddKeystoneHWWallet"
@ -142,7 +144,7 @@ let package = Package(
"Scan",
"UIComponents",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/AddressBook"
),
@ -155,7 +157,7 @@ let package = Package(
"Utils",
"WalletStorage",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/AddressBookClient"
),
@ -168,7 +170,7 @@ let package = Package(
"UIComponents",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/AddressDetails"
),
@ -211,7 +213,7 @@ let package = Package(
"WalletStorage",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/BalanceBreakdown"
),
@ -221,7 +223,7 @@ let package = Package(
"Generated",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/BalanceFormatter"
),
@ -238,10 +240,13 @@ let package = Package(
"AddressBook",
"AudioServices",
"Generated",
"MnemonicSwift",
"Models",
"NumberFormatter",
"PartialProposalError",
"Pasteboard",
"RequestZec",
"RestoreInfo",
"Scan",
"SDKSynchronizer",
"SendConfirmation",
@ -250,10 +255,12 @@ let package = Package(
"TransactionsManager",
"UIComponents",
"Utils",
"WalletBirthday",
"WalletStorage",
"ZcashSDKEnvironment",
"ZecKeyboard",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/CoordFlows"
),
@ -276,7 +283,7 @@ let package = Package(
"FileManager",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/DatabaseFiles"
),
@ -293,7 +300,7 @@ let package = Package(
"DerivationTool",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "URLRouting", package: "swift-url-routing"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/Deeplink"
),
@ -314,7 +321,7 @@ let package = Package(
"UIComponents",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/DeleteWallet"
),
@ -323,7 +330,7 @@ let package = Package(
dependencies: [
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/DerivationTool"
),
@ -341,7 +348,7 @@ let package = Package(
"UserPreferencesStorage",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/ExchangeRate"
),
@ -352,7 +359,7 @@ let package = Package(
"LogsHandler",
"Utils",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/ExportLogs"
),
@ -389,7 +396,7 @@ let package = Package(
"PartnerKeys",
"UserDefaults",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "Flexa", package: "flexa-ios")
],
path: "Sources/Dependencies/FlexaHandler"
@ -417,7 +424,7 @@ let package = Package(
"WalletBalances",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/Home"
),
@ -432,7 +439,7 @@ let package = Package(
"WalletStorage",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/ImportWallet"
),
@ -508,7 +515,7 @@ let package = Package(
"SecurityWarning",
"UIComponents",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/OnboardingFlow"
),
@ -580,7 +587,7 @@ let package = Package(
"Utils",
"ZecKeyboard",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/Receive"
),
@ -595,7 +602,7 @@ let package = Package(
"Utils",
"WalletStorage",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/RecoveryPhraseDisplay"
),
@ -616,7 +623,7 @@ let package = Package(
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "ZcashPaymentURI", package: "zcash-swift-payment-uri")
],
path: "Sources/Features/RequestZec"
@ -699,7 +706,7 @@ let package = Package(
"ZcashSDKEnvironment",
"ZecKeyboard",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/Root"
),
@ -716,7 +723,7 @@ let package = Package(
"ZcashSDKEnvironment",
.product(name: "ZcashPaymentURI", package: "zcash-swift-payment-uri"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "KeystoneSDK", package: "keystone-sdk-ios")
],
path: "Sources/Features/Scan"
@ -728,7 +735,7 @@ let package = Package(
"Models",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "KeystoneSDK", package: "keystone-sdk-ios")
],
path: "Sources/Dependencies/SDKSynchronizer"
@ -784,7 +791,7 @@ let package = Package(
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "Lottie", package: "lottie-spm"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "KeystoneSDK", package: "keystone-sdk-ios")
],
path: "Sources/Features/SendConfirmation"
@ -817,7 +824,7 @@ let package = Package(
"WalletBalances",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/SendForm"
),
@ -830,7 +837,7 @@ let package = Package(
"UserPreferencesStorage",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/ServerSetup"
),
@ -859,7 +866,7 @@ let package = Package(
"UIComponents",
"WhatsNew",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "Flexa", package: "flexa-ios")
],
path: "Sources/Features/Settings"
@ -917,7 +924,7 @@ let package = Package(
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/TransactionDetails"
),
@ -937,7 +944,7 @@ let package = Package(
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/TransactionList"
),
@ -958,7 +965,7 @@ let package = Package(
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/TransactionsManager"
),
@ -985,7 +992,7 @@ let package = Package(
"Models",
.product(name: "ZcashPaymentURI", package: "zcash-swift-payment-uri"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/URIParser"
),
@ -1005,7 +1012,7 @@ let package = Package(
"Utils",
"WalletStorage",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/UserMetadataProvider"
),
@ -1014,14 +1021,14 @@ let package = Package(
dependencies: [
"UserDefaults",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/UserPreferencesStorage"
),
.target(
name: "Utils",
dependencies: [
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "CasePaths", package: "swift-case-paths"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
@ -1046,10 +1053,24 @@ let package = Package(
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/WalletBalances"
),
.target(
name: "WalletBirthday",
dependencies: [
"Generated",
"Models",
"SDKSynchronizer",
"UIComponents",
"Utils",
"ZcashSDKEnvironment",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Features/WalletBirthday"
),
.target(
name: "WalletConfigProvider",
dependencies: [
@ -1067,7 +1088,7 @@ let package = Package(
"MnemonicClient",
"Models",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk")
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
],
path: "Sources/Dependencies/WalletStorage"
),
@ -1105,7 +1126,7 @@ let package = Package(
"Generated",
"UserDefaults",
"UserPreferencesStorage",
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
path: "Sources/Dependencies/ZcashSDKEnvironment"
@ -1117,7 +1138,7 @@ let package = Package(
"Models",
"UIComponents",
"Utils",
.product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"),
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
],
path: "Sources/Features/ZecKeyboard"

View File

@ -69,6 +69,8 @@ public struct SDKSynchronizerClient {
public var walletAccounts: () async throws -> [WalletAccount] = { [] }
public var estimateBirthdayHeight: (Date) -> BlockHeight = { _ in BlockHeight(0) }
// PCZT
public var createPCZTFromProposal: (AccountUUID, Proposal) async throws -> Pczt
public var addProofsToPCZT: (Pczt) async throws -> Pczt

View File

@ -206,6 +206,9 @@ extension SDKSynchronizerClient: DependencyKey {
return sortedWalletAccounts
},
estimateBirthdayHeight: { date in
synchronizer.estimateBirthdayHeight(for: date)
},
createPCZTFromProposal: { accountUUID, proposal in
try await synchronizer.createPCZTFromProposal(accountUUID: accountUUID, proposal: proposal)
},

View File

@ -42,6 +42,7 @@ extension SDKSynchronizerClient: TestDependencyKey {
refreshExchangeRateUSD: unimplemented("\(Self.self).refreshExchangeRateUSD", placeholder: {}()),
evaluateBestOf: { _, _, _, _, _, _ in fatalError("evaluateBestOf not implemented") },
walletAccounts: unimplemented("\(Self.self).walletAccounts", placeholder: []),
estimateBirthdayHeight: unimplemented("\(Self.self).estimateBirthdayHeight", placeholder: BlockHeight(0)),
createPCZTFromProposal: unimplemented("\(Self.self).createPCZTFromProposal", placeholder: Pczt()),
addProofsToPCZT: unimplemented("\(Self.self).addProofsToPCZT", placeholder: Pczt()),
createTransactionFromPCZT: unimplemented("\(Self.self).createTransactionFromPCZT", placeholder: .success(txIds: [])),
@ -80,6 +81,7 @@ extension SDKSynchronizerClient {
refreshExchangeRateUSD: { },
evaluateBestOf: { _, _, _, _, _, _ in [] },
walletAccounts: { [] },
estimateBirthdayHeight: { _ in BlockHeight(0) },
createPCZTFromProposal: { _, _ in Pczt() },
addProofsToPCZT: { _ in Pczt() },
createTransactionFromPCZT: { _, _ in .success(txIds: []) },
@ -189,6 +191,7 @@ extension SDKSynchronizerClient {
refreshExchangeRateUSD: @escaping () -> Void = { },
evaluateBestOf: @escaping ([LightWalletEndpoint], Double, Double, UInt64, Int, NetworkType) async -> [LightWalletEndpoint] = { _, _, _, _, _, _ in [] },
walletAccounts: @escaping () async throws -> [WalletAccount] = { [] },
estimateBirthdayHeight: @escaping (Date) -> BlockHeight = { _ in BlockHeight(0) },
createPCZTFromProposal: @escaping (AccountUUID, Proposal) async throws -> Pczt = { _, _ in Pczt() },
addProofsToPCZT: @escaping (Data) async throws -> Pczt = { _ in Pczt() },
createTransactionFromPCZT: @escaping (Pczt, Pczt) async throws -> CreateProposedTransactionsResult = { _, _ in .success(txIds: []) },
@ -224,6 +227,7 @@ extension SDKSynchronizerClient {
refreshExchangeRateUSD: refreshExchangeRateUSD,
evaluateBestOf: evaluateBestOf,
walletAccounts: walletAccounts,
estimateBirthdayHeight: estimateBirthdayHeight,
createPCZTFromProposal: createPCZTFromProposal,
addProofsToPCZT: addProofsToPCZT,
createTransactionFromPCZT: createTransactionFromPCZT,

View File

@ -61,7 +61,7 @@ public struct AboutView: View {
.onAppear { store.send(.onAppear) }
.zashiBack()
.screenTitle(L10n.Settings.about)
.walletStatusPanel(background: .transparent)
//..walletstatusPanel(background: .transparent)
}
//.navigationBarTitleDisplayMode(.inline)
.screenHorizontalPadding()

View File

@ -9,6 +9,8 @@ import ComposableArchitecture
import Generated
// Path
import RestoreInfo
import WalletBirthday
extension RestoreWalletCoordFlow {
public func coordinatorReduce() -> Reduce<RestoreWalletCoordFlow.State, RestoreWalletCoordFlow.Action> {
@ -16,9 +18,67 @@ extension RestoreWalletCoordFlow {
switch action {
// MARK: - Self
// case .path(.element(id: _, action: .requestZec(.requestTapped))):
// state.path.append(.requestZecSummary(state.requestZecState))
// return .none
case .nextTapped:
state.path.append(.walletBirthday(WalletBirthday.State.initial))
return .none
case .resolveRestoreWithBirthday(let birthday):
do {
let seedPhrase = state.words.joined(separator: " ")
// validate the seed
try mnemonic.isValid(seedPhrase)
try walletStorage.importWallet(seedPhrase, birthday, .english, false)
// update the backup phrase validation flag
try walletStorage.markUserPassedPhraseBackupTest(true)
state.path.append(.restoreInfo(RestoreInfo.State.initial))
// notify user
return .send(.successfullyRecovered)
} catch {
return .send(.failedToRecover(error.toZcashError()))
}
// MARK: - Wallet Birthday
case .path(.element(id: _, action: .walletBirthday(.helpSheetRequested))):
state.isHelpSheetPreseted.toggle()
return .none
case .path(.element(id: _, action: .walletBirthday(.estimateHeightTapped))):
state.path.append(.estimateBirthdaysDate(WalletBirthday.State.initial))
return .none
case .path(.element(id: _, action: .walletBirthday(.restoreTapped))):
for element in state.path {
if case .walletBirthday(let walletBirthdayState) = element {
return .send(.resolveRestoreWithBirthday(walletBirthdayState.estimatedHeight))
}
}
return .none
case .path(.element(id: _, action: .estimateBirthdaysDate(.helpSheetRequested))):
state.isHelpSheetPreseted.toggle()
return .none
case .path(.element(id: _, action: .estimateBirthdaysDate(.estimateHeightReady))):
for element in state.path {
if case .estimateBirthdaysDate(let estimateBirthdaysDateState) = element {
state.path.append(.estimatedBirthday(estimateBirthdaysDateState))
}
}
return .none
case .path(.element(id: _, action: .estimatedBirthday(.restoreTapped))):
for element in state.path {
if case .estimatedBirthday(let estimatedBirthdayState) = element {
return .send(.resolveRestoreWithBirthday(estimatedBirthdayState.estimatedHeight))
}
}
return .none
default: return .none
}

View File

@ -9,38 +9,138 @@ import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
import MnemonicSwift
import Pasteboard
import WalletStorage
// Path
import RestoreInfo
import WalletBirthday
@Reducer
public struct RestoreWalletCoordFlow {
@Reducer
public enum Path {
case estimateBirthdaysDate(WalletBirthday)
case estimatedBirthday(WalletBirthday)
case restoreInfo(RestoreInfo)
case walletBirthday(WalletBirthday)
}
@ObservableState
public struct State {
public var isHelpSheetPreseted = false
public var isValidSeed = false
public var nextIndex: Int?
public var path = StackState<Path.State>()
// public var zecKeyboardState = ZecKeyboard.State.initial
public var prevWords: [String] = Array(repeating: "", count: 24)
public var selectedIndex: Int?
public var suggestedWords: [String] = []
public var words: [String] = Array(repeating: "", count: 24)
public var wordsValidity: [Bool] = Array(repeating: true, count: 24)
public init() { }
}
public enum Action {
public enum Action: BindableAction {
case binding(BindingAction<RestoreWalletCoordFlow.State>)
case evaluateSeedValidity
case failedToRecover(ZcashError)
case helpSheetRequested
case nextTapped
case path(StackActionOf<Path>)
// case zecKeyboard(ZecKeyboard.Action)
case resolveRestoreWithBirthday(BlockHeight)
case selectedIndex(Int?)
case successfullyRecovered
case suggestedWordTapped(String)
case suggestionsRequested(Int)
#if DEBUG
case debugPasteSeed
#endif
}
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.walletStorage) var walletStorage
public init() { }
public var body: some Reducer<State, Action> {
coordinatorReduce()
// Scope(state: \.zecKeyboardState, action: \.zecKeyboard) {
// ZecKeyboard()
// }
BindingReducer()
Reduce { state, action in
switch action {
case .binding(\.words):
let changedIndices = state.words.indices.filter { state.words[$0] != state.prevWords[$0] }
state.prevWords = state.words
if let index = changedIndices.first {
let word = state.words[index]
if word.hasSuffix(" ") {
state.words[index] = word.trimmingCharacters(in: .whitespaces)
state.prevWords = state.words
return .send(.suggestedWordTapped(state.words[index]))
}
return .send(.suggestionsRequested(index))
}
return .none
case .selectedIndex(let index):
state.selectedIndex = index
if let index {
return .send(.suggestionsRequested(index))
}
return .none
case .suggestionsRequested(let index):
let prefix = state.words[index]
if prefix.isEmpty {
state.suggestedWords = []
} else {
state.suggestedWords = mnemonic.suggestWords(prefix)
state.wordsValidity[index] = !state.suggestedWords.isEmpty
}
return .send(.evaluateSeedValidity)
case .suggestedWordTapped(let word):
if let index = state.selectedIndex {
state.words[index] = word
state.prevWords = state.words
state.nextIndex = index + 1 < 24 ? index + 1 : 0
return .send(.evaluateSeedValidity)
}
return .none
case .helpSheetRequested:
state.isHelpSheetPreseted.toggle()
return .none
case .evaluateSeedValidity:
do {
try mnemonic.isValid(state.words.joined(separator: " "))
state.isValidSeed = true
} catch {
state.isValidSeed = false
}
return .none
#if DEBUG
case .debugPasteSeed:
do {
let seedToPaste = pasteboard.getString()?.data ?? ""
try mnemonic.isValid(seedToPaste)
state.words = seedToPaste.components(separatedBy: " ")
state.isValidSeed = true
} catch {
state.isValidSeed = false
}
return .none
#endif
default: return .none
}
}

View File

@ -12,12 +12,21 @@ import UIComponents
import Generated
// Path
import RestoreInfo
import WalletBirthday
public struct RestoreWalletCoordFlowView: View {
enum FocusTextField: Hashable {
case field(Int)
}
@Environment(\.colorScheme) var colorScheme
@Perception.Bindable var store: StoreOf<RestoreWalletCoordFlow>
@FocusState private var focusedField: FocusTextField?
@State private var keyboardVisible: Bool = false
public init(store: StoreOf<RestoreWalletCoordFlow>) {
self.store = store
}
@ -25,28 +34,247 @@ public struct RestoreWalletCoordFlowView: View {
public var body: some View {
WithPerceptionTracking {
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
Text("RestoreWalletCoordFlowView")
// ZecKeyboardView(
// store:
// store.scope(
// state: \.zecKeyboardState,
// action: \.zecKeyboard
// ),
// tokenName: tokenName
// )
ZStack {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Text(L10n.RestoreWallet.title)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.top, 20)
.onLongPressGesture {
#if DEBUG
store.send(.debugPasteSeed)
#endif
}
Text(L10n.RestoreWallet.info)
.zFont(size: 14, style: Design.Text.primary)
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 8)
.padding(.bottom, 20)
ForEach(0..<8, id: \.self) { j in
HStack(spacing: 4) {
ForEach(0..<3, id: \.self) { i in
HStack(spacing: 0) {
Text("\(j * 3 + i + 1)")
.zFont(.medium, size: 14, style: Design.Text.primary)
.padding(.trailing, 4)
TextField("", text: $store.words[j * 3 + i])
.zFont(size: 16, style: Design.Text.primary)
.disableAutocorrection(true)
.textInputAutocapitalization(.never)
.focused($focusedField, equals: .field((j * 3 + i)))
.keyboardType(.alphabet)
.submitLabel(.next)
.onSubmit {
focusedField = ((j * 3 + i) < 23)
? .field((j * 3 + i) + 1)
: .field(0)
}
}
.padding(6)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(
focusedField == .field(j * 3 + i)
? Design.Surfaces.bgPrimary.color(colorScheme)
: Design.Surfaces.bgSecondary.color(colorScheme)
)
.background {
RoundedRectangle(cornerRadius: 12)
.stroke(
!store.wordsValidity[j * 3 + i]
? Design.Inputs.ErrorFilled.stroke.color(colorScheme)
: focusedField == .field(j * 3 + i)
? Design.Text.primary.color(colorScheme)
: Design.Surfaces.bgSecondary.color(colorScheme),
lineWidth: 2
)
}
}
.padding(2)
.padding(.bottom, 4)
}
}
}
if keyboardVisible {
Color.clear
.frame(height: 44)
}
}
.screenHorizontalPadding()
}
.padding(.vertical, 1)
VStack {
Spacer()
ZashiButton(L10n.General.next) {
store.send(.nextTapped)
}
.disabled(!store.isValidSeed)
.padding(.bottom, 24)
.screenHorizontalPadding()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
.frame(maxWidth: .infinity)
.onAppear { observeKeyboardNotifications() }
.onChange(of: focusedField) { handle in
if case .field(let index) = handle {
store.send(.selectedIndex(index))
}
if handle == nil {
store.send(.selectedIndex(nil))
}
}
.onChange(of: store.nextIndex) { value in
if let nextIndex = value {
focusedField = .field(nextIndex)
}
}
.applyScreenBackground()
.overlay(
VStack(spacing: 0) {
Spacer()
Asset.Colors.primary.color
.frame(height: 1)
.opacity(0.1)
HStack(alignment: .center) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
ForEach(store.suggestedWords, id: \.self) { suggestedWord in
Button {
store.send(.suggestedWordTapped(suggestedWord))
} label: {
Text(suggestedWord)
.zFont(size: 16, style: Design.Text.primary)
.fixedSize()
.padding(8)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(Design.Surfaces.bgSecondary.color(colorScheme))
}
}
}
}
.padding(.leading, 4)
}
.mask(
LinearGradient(
gradient: Gradient(stops: [
.init(color: Design.Surfaces.bgSecondary.color(colorScheme).opacity(0.7), location: 0.9),
.init(color: Design.Surfaces.bgSecondary.color(colorScheme).opacity(0), location: 0.98)
]),
startPoint: .leading,
endPoint: .trailing
)
)
.frame(height: 38)
//.clipped()
Spacer()
Button {
focusedField = nil
} label: {
Text(L10n.General.done.uppercased())
.zFont(.regular, size: 14, style: Design.Text.primary)
}
// .padding(.bottom, 4)
.padding(.trailing, 24)
.padding(.leading, 4)
}
.applyScreenBackground()
.frame(height: keyboardVisible ? 44 : 0)
.frame(maxWidth: .infinity)
.opacity(keyboardVisible ? 1 : 0)
}
)
// .navigationBarHidden(true)
} destination: { store in
// switch store.case {
// case let .requestZec(store):
// RequestZecView(store: store, tokenName: tokenName)
// }
switch store.case {
case let .estimateBirthdaysDate(store):
WalletBirthdayEstimateDateView(store: store)
case let .estimatedBirthday(store):
WalletBirthdayEstimatedHeightView(store: store)
case let .restoreInfo(store):
RestoreInfoView(store: store)
case let .walletBirthday(store):
WalletBirthdayView(store: store)
}
}
.navigationBarHidden(!store.path.isEmpty)
.navigationBarItems(
trailing:
Button {
store.send(.helpSheetRequested)
} label: {
Asset.Assets.Icons.help.image
.zImage(size: 24, style: Design.Text.primary)
.padding(8)
}
)
.zashiSheet(isPresented: $store.isHelpSheetPreseted) {
helpSheetContent()
.screenHorizontalPadding()
}
}
.padding(.horizontal, 4)
.applyScreenBackground()
.zashiBack()
.screenTitle(L10n.General.request)
.screenTitle(L10n.ImportWallet.Button.restoreWallet)
}
private func observeKeyboardNotifications() {
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in
withAnimation {
keyboardVisible = true
}
}
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
withAnimation {
keyboardVisible = false
}
}
}
@ViewBuilder private func helpSheetContent() -> some View {
Text(L10n.RestoreWallet.Help.title)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.top, 24)
.padding(.bottom, 12)
infoContent(text: L10n.RestoreWallet.Help.phrase)
.padding(.bottom, 12)
infoContent(text: L10n.RestoreWallet.Help.birthday)
.padding(.bottom, 32)
ZashiButton(L10n.RestoreInfo.gotIt) {
store.send(.helpSheetRequested)
}
.padding(.bottom, 24)
}
@ViewBuilder private func infoContent(text: String) -> some View {
HStack(alignment: .top, spacing: 8) {
Asset.Assets.infoCircle.image
.zImage(size: 20, style: Design.Text.primary)
if let attrText = try? AttributedString(
markdown: text,
including: \.zashiApp
) {
ZashiText(withAttributedString: attrText, colorScheme: colorScheme)
.zFont(size: 14, style: Design.Text.tertiary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
}

View File

@ -50,7 +50,7 @@ public struct ExportTransactionHistoryView: View {
}
}
.zashiBack()
.walletStatusPanel()
//..walletstatusPanel()
shareLogsView()
}

View File

@ -73,14 +73,14 @@ public struct HomeView: View {
}
button(
"Scan",
L10n.HomeScreen.scan,
icon: Asset.Assets.Icons.scan.image
) {
store.send(.scanTapped)
}
button(
"More",
L10n.HomeScreen.more,
icon: Asset.Assets.Icons.dotsMenu.image
) {
store.send(.moreTapped)
@ -292,7 +292,7 @@ public struct HomeView: View {
}
}
}
.walletStatusPanel()
//..walletstatusPanel()
.applyScreenBackground()
.onAppear {
store.send(.onAppear)

View File

@ -84,6 +84,7 @@ public struct OnboardingFlow {
return .none
case .importExistingWallet:
state.restoreWalletCoordFlowState = .initial
state.destination = .importExistingWallet
return .none

View File

@ -97,7 +97,7 @@ public struct PrivateDataConsentView: View {
.padding(.vertical, 1)
.zashiBack()
.onAppear { store.send(.onAppear)}
.walletStatusPanel()
//..walletstatusPanel()
shareLogsView()
}

View File

@ -12,17 +12,25 @@ import Generated
@Reducer
public struct RestoreInfo {
@ObservableState
public struct State: Equatable { }
public struct State: Equatable {
public var isAcknowledged = true
}
public enum Action: Equatable {
public enum Action: BindableAction, Equatable {
case binding(BindingAction<RestoreInfo.State>)
case gotItTapped
}
public init() { }
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .none
case .gotItTapped:
return .none
}

View File

@ -11,6 +11,8 @@ import Generated
import UIComponents
public struct RestoreInfoView: View {
@Environment(\.colorScheme) var colorScheme
@Perception.Bindable var store: StoreOf<RestoreInfo>
public init(store: StoreOf<RestoreInfo>) {
@ -19,47 +21,64 @@ public struct RestoreInfoView: View {
public var body: some View {
WithPerceptionTracking {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Asset.Assets.Illustrations.connect.image
.resizable()
.frame(width: 132, height: 90)
.padding(.top, 40)
.padding(.bottom, 24)
Text(L10n.RestoreInfo.title)
.font(.custom(FontFamily.Inter.semiBold.name, size: 25))
.padding(.vertical, 30)
Asset.Assets.restoreInfo.image
.zImage(width: 90, height: 172, color: Asset.Colors.primary.color)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.bottom, 8)
Text(L10n.RestoreInfo.subTitle)
.font(.custom(FontFamily.Inter.semiBold.name, size: 16))
.multilineTextAlignment(.center)
.padding(.vertical, 30)
.padding(.horizontal, 50)
.zFont(.medium, size: 16, style: Design.Text.primary)
.padding(.bottom, 16)
VStack(alignment: .leading) {
Text(L10n.RestoreInfo.tips)
.font(.custom(FontFamily.Inter.bold.name, size: 12))
Text(L10n.RestoreInfo.tips)
.zFont(size: 14, style: Design.Text.primary)
.padding(.bottom, 4)
bulletpoint(L10n.RestoreInfo.tip1)
bulletpoint(L10n.RestoreInfo.tip2)
.padding(.bottom, 20)
Spacer()
Text("\(Text(L10n.RestoreInfo.note).bold())\(L10n.RestoreInfo.noteInfo)")
.zFont(size: 12, style: Design.Text.primary)
.padding(.bottom, 24)
HStack {
ZashiToggle(
isOn: $store.isAcknowledged,
label: L10n.RestoreInfo.checkbox,
textSize: 16
)
bulletpoint(L10n.RestoreInfo.tip1)
bulletpoint(L10n.RestoreInfo.tip2)
bulletpoint(L10n.RestoreInfo.tip3)
.padding(.bottom, 20)
Text(L10n.RestoreInfo.note)
.font(.custom(FontFamily.Inter.bold.name, size: 11))
+ Text(L10n.RestoreInfo.noteInfo)
.font(.custom(FontFamily.Inter.regular.name, size: 11))
Spacer()
}
.padding(.horizontal, 30)
.padding(.leading, 1)
// Group {
// Text(L10n.RestoreInfo.note)
// .font(.custom(FontFamily.Inter.bold.name, size: 12))
// + Text(L10n.RestoreInfo.noteInfo)
// .font(.custom(FontFamily.Inter.regular.name, size: 12))
// }
// .foregroundColor(Design.Text.primary.color(colorScheme))
ZashiButton(L10n.RestoreInfo.gotIt) {
store.send(.gotItTapped)
}
.padding(.vertical, 50)
.padding(.vertical, 24)
}
.padding(.vertical, 1)
.zashiBack(hidden: true)
}
.navigationBarTitleDisplayMode(.inline)
.screenHorizontalPadding()
.applyScreenBackground()
.applyErredScreenBackground()
}
@ViewBuilder
@ -71,7 +90,7 @@ public struct RestoreInfoView: View {
.padding(.leading, 8)
Text(text)
.font(.custom(FontFamily.Inter.regular.name, size: 12))
.zFont(size: 14, style: Design.Text.primary)
}
.padding(.bottom, 5)
}

View File

@ -168,6 +168,25 @@ extension Root {
case .settings(.path(.element(id: _, action: .resetZashi(.deleteTapped)))):
return .send(.initialization(.resetZashiRequest))
// MARK: - Restore Wallet Coord Flow from Onboarding
case .onboarding(.restoreWalletCoordFlow(.path(.element(id: _, action: .restoreInfo(.gotItTapped))))):
var leavesScreenOpen = false
for element in state.onboardingState.restoreWalletCoordFlowState.path {
if case .restoreInfo(let restoreInfoState) = element {
leavesScreenOpen = restoreInfoState.isAcknowledged
}
}
userDefaults.setValue(leavesScreenOpen, Constants.udLeavesScreenOpen)
state.isRestoringWallet = true
userDefaults.setValue(true, Constants.udIsRestoringWallet)
state.$walletStatus.withLock { $0 = .restoring }
return .concatenate(
.send(.initialization(.initializeSDK(.restoreWallet))),
.send(.initialization(.checkBackupPhraseValidation)),
.send(.batteryStateChanged(nil))
)
// MARK: - Scan Coord Flow
case .scanCoordFlow(.scan(.cancelTapped)):

View File

@ -19,6 +19,7 @@ import WalletStorage
extension Root {
public enum Constants {
static let udIsRestoringWallet = "udIsRestoringWallet"
static let udLeavesScreenOpen = "udLeaves_screen_open"
static let noAuthenticationWithinXMinutes = 15
}
@ -494,6 +495,7 @@ extension Root {
state.splashAppeared = true
state.isRestoringWallet = false
userDefaults.remove(Constants.udIsRestoringWallet)
userDefaults.remove(Constants.udLeavesScreenOpen)
flexaHandler.signOut()
userStoredPreferences.removeAll()
try? readTransactionsStorage.resetZashi()

View File

@ -492,7 +492,8 @@ public struct Root {
return .none
case .batteryStateChanged:
autolockHandler.value(state.walletStatus == .restoring)
var leavesScreenOpen = userDefaults.objectForKey(Constants.udLeavesScreenOpen) as? Bool ?? false
autolockHandler.value(state.walletStatus == .restoring && leavesScreenOpen)
return .none
case .cancelAllRunningEffects:
@ -646,15 +647,7 @@ extension AlertState where Action == Root.Action {
TextState(L10n.Root.Initialization.Alert.Wipe.message)
}
}
public static func successfullyRecovered() -> AlertState {
AlertState {
TextState(L10n.General.success)
} message: {
TextState(L10n.ImportWallet.Alert.Success.message)
}
}
public static func differentSeed() -> AlertState {
AlertState {
TextState(L10n.General.Alert.warning)

View File

@ -70,7 +70,7 @@ public struct AdvancedSettingsView: View {
}
.padding(.top, 24)
.padding(.horizontal, 4)
.walletStatusPanel()
//..walletstatusPanel()
Spacer()

View File

@ -87,7 +87,7 @@ public struct IntegrationsView: View {
}
.padding(.top, 24)
.padding(.horizontal, 4)
.walletStatusPanel()
//..walletstatusPanel()
.sheet(isPresented: $store.isInAppBrowserOn) {
if let urlStr = store.inAppBrowserURL, let url = URL(string: urlStr) {
InAppBrowserView(url: url)

View File

@ -167,7 +167,7 @@ public struct SettingsView: View {
.zashiBack()
.navigationBarHidden(!store.path.isEmpty)
.screenTitle(L10n.Settings.title)
.walletStatusPanel()
//..walletstatusPanel()
}
}
}

View File

@ -218,7 +218,7 @@ public struct TabsView: View {
)
}
)
.walletStatusPanel()
//..walletstatusPanel()
.sheet(isPresented: $store.isInAppBrowserOn) {
if let urlStr = store.inAppBrowserURL, let url = URL(string: urlStr) {
InAppBrowserView(url: url)
@ -539,7 +539,7 @@ public struct TabsView: View {
// }
// .animation(nil, value: store.selectedTab)
// )
// .walletStatusPanel()
// //..walletstatusPanel()
// .sheet(isPresented: $store.isInAppBrowserOn) {
// if let url = URL(string: store.inAppBrowserURL) {
// InAppBrowserView(url: url)

View File

@ -153,7 +153,7 @@ public struct TransactionDetailsView: View {
}
}
.navigationBarTitleDisplayMode(.inline)
.walletStatusPanel(background: .transparent)
//..walletstatusPanel(background: .transparent)
.applyDefaultGradientScreenBackground()
}
}

View File

@ -144,7 +144,7 @@ public struct TransactionsManagerView: View {
}
}
.disabled(store.transactions.isEmpty)
.walletStatusPanel()
//..walletstatusPanel()
.applyScreenBackground()
.listStyle(.plain)
.onAppear { store.send(.onAppear) }

View File

@ -0,0 +1,81 @@
//
// WalletBirthdayEstimateDateView.swift
// Zashi
//
// Created by Lukáš Korba on 03-31-2025.
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct WalletBirthdayEstimateDateView: View {
@Perception.Bindable var store: StoreOf<WalletBirthday>
public init(store: StoreOf<WalletBirthday>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
VStack(alignment: .leading, spacing: 0) {
Text(L10n.RestoreWallet.Birthday.EstimateDate.title)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.top, 40)
.padding(.bottom, 8)
Text(L10n.RestoreWallet.Birthday.EstimateDate.info)
.zFont(size: 14, style: Design.Text.primary)
.padding(.bottom, 32)
HStack {
Picker("", selection: $store.selectedMonth) {
ForEach(store.months, id: \.self) { month in
Text(month)
.zFont(size: 23, style: Design.Text.primary)
}
}
.pickerStyle(.wheel)
Picker("", selection: $store.selectedYear) {
ForEach(store.years, id: \.self) { year in
Text("\(String(year))")
.zFont(size: 23, style: Design.Text.primary)
}
}
.pickerStyle(.wheel)
}
Spacer()
ZashiButton(L10n.General.next) {
store.send(.estimateHeightRequested)
}
.padding(.bottom, 24)
}
.onAppear { store.send(.onAppear) }
.zashiBack()
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing:
Button {
store.send(.helpSheetRequested)
} label: {
Asset.Assets.Icons.help.image
.zImage(size: 24, style: Design.Text.primary)
.padding(8)
}
)
.screenHorizontalPadding()
.applyScreenBackground()
.screenTitle(L10n.ImportWallet.Button.restoreWallet)
}
}
// MARK: - Previews
#Preview {
WalletBirthdayEstimateDateView(store: WalletBirthday.initial)
}

View File

@ -0,0 +1,80 @@
//
// WalletBirthdayEstimatedHeightView.swift
// Zashi
//
// Created by Lukáš Korba on 03-31-2025.
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct WalletBirthdayEstimatedHeightView: View {
@Perception.Bindable var store: StoreOf<WalletBirthday>
public init(store: StoreOf<WalletBirthday>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
VStack(alignment: .leading, spacing: 0) {
Text(L10n.RestoreWallet.Birthday.Estimated.title)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.top, 40)
.padding(.bottom, 8)
Text(L10n.RestoreWallet.Birthday.Estimated.info)
.zFont(size: 14, style: Design.Text.primary)
.padding(.bottom, 56)
VStack {
Text(store.estimatedHeightString)
.zFont(.semiBold, size: 48, style: Design.Text.primary)
.padding(.bottom, 12)
ZashiButton(
L10n.Receive.copy,
type: .tertiary,
infinityWidth: false,
prefixView:
Asset.Assets.copy.image
.zImage(size: 20, style: Design.Btns.Tertiary.fg)
) {
store.send(.copyBirthdayTapped)
}
}
.frame(maxWidth: .infinity)
Spacer()
ZashiButton(L10n.ImportWallet.Button.restoreWallet) {
store.send(.restoreTapped)
}
.padding(.bottom, 24)
}
.zashiBack()
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing:
Button {
store.send(.helpSheetRequested)
} label: {
Asset.Assets.Icons.help.image
.zImage(size: 24, style: Design.Text.primary)
.padding(8)
}
)
.screenHorizontalPadding()
.applyScreenBackground()
.screenTitle(L10n.ImportWallet.Button.restoreWallet)
}
}
// MARK: - Previews
#Preview {
WalletBirthdayEstimateDateView(store: WalletBirthday.initial)
}

View File

@ -0,0 +1,148 @@
//
// WalletBirthdayStore.swift
// Zashi
//
// Created by Lukáš Korba on 03-31-2025.
//
import Foundation
import ComposableArchitecture
import Generated
import SDKSynchronizer
import Utils
import ZcashLightClientKit
import Pasteboard
import UIComponents
import ZcashSDKEnvironment
@Reducer
public struct WalletBirthday {
enum Constants {
static let startYear: Int = 2018
static let startMonth: Int = 10
}
@ObservableState
public struct State: Equatable {
public var birthday = ""
public var estimatedHeight = BlockHeight(0)
public var isValidBirthday = false
public var months: [String] = []
public var selectedMonth = ""
public var selectedYear = Constants.startYear
@Shared(.inMemory(.toast)) public var toast: Toast.Edge? = nil
public var years: [Int] = []
public var estimatedHeightString: String {
Zatoshi(Int64(estimatedHeight * 100_000_000)).decimalString()
}
public init() { }
}
public enum Action: BindableAction, Equatable {
case binding(BindingAction<WalletBirthday.State>)
case copyBirthdayTapped
case estimateHeightReady
case estimateHeightRequested
case estimateHeightTapped
case helpSheetRequested
case onAppear
case restoreTapped
case updateMonths
}
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
public init() { }
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .onAppear:
let currentYear = Calendar.current.component(.year, from: Date())
state.years = Array(Constants.startYear...currentYear)
state.estimatedHeight = zcashSDKEnvironment.network.constants.saplingActivationHeight
return .send(.updateMonths)
case .binding(\.birthday):
let saplingActivation = zcashSDKEnvironment.network.constants.saplingActivationHeight
if let birthdayHeight = BlockHeight(state.birthday), birthdayHeight >= saplingActivation {
state.estimatedHeight = birthdayHeight
state.isValidBirthday = true
} else {
state.isValidBirthday = false
}
return .none
case .binding(\.selectedYear):
return .send(.updateMonths)
case .binding:
return .none
case .restoreTapped:
return .none
case .copyBirthdayTapped:
pasteboard.setString(state.birthday.redacted)
state.$toast.withLock { $0 = .top(L10n.General.copiedToTheClipboard) }
return .none
case .updateMonths:
let currentYear = Calendar.current.component(.year, from: Date())
let currentMonth = Calendar.current.component(.month, from: Date())
let formatter = DateFormatter()
formatter.locale = Locale.current
if state.selectedYear > Constants.startYear && state.selectedYear < currentYear {
state.months = formatter.monthSymbols
} else if state.selectedYear == Constants.startYear {
state.months = formatter.monthSymbols.suffix(13 - Constants.startMonth)
if !state.months.contains(state.selectedMonth) {
if let first = state.months.first {
state.selectedMonth = first
}
}
} else if state.selectedYear == currentYear {
state.months = Array(formatter.monthSymbols.prefix(currentMonth))
if !state.months.contains(state.selectedMonth) {
if let last = state.months.last {
state.selectedMonth = last
}
}
}
return .none
case .helpSheetRequested:
return .none
case .estimateHeightRequested:
// compute date based on the picker state
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM yyyy"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
if let date = dateFormatter.date(from: "\(state.selectedMonth) \(state.selectedYear)") {
state.estimatedHeight = sdkSynchronizer.estimateBirthdayHeight(date)
state.isValidBirthday = true
return .send(.estimateHeightReady)
} else {
state.estimatedHeight = BlockHeight(0)
state.isValidBirthday = false
}
return .none
case .estimateHeightReady:
return .none
case .estimateHeightTapped:
return .none
}
}
}
}

View File

@ -0,0 +1,100 @@
//
// WalletBirthdayView.swift
// Zashi
//
// Created by Lukáš Korba on 03-31-2025.
//
import SwiftUI
import ComposableArchitecture
import Generated
import UIComponents
public struct WalletBirthdayView: View {
@Perception.Bindable var store: StoreOf<WalletBirthday>
public init(store: StoreOf<WalletBirthday>) {
self.store = store
}
public var body: some View {
WithPerceptionTracking {
VStack(alignment: .leading, spacing: 0) {
Text(L10n.ImportWallet.Birthday.title)
.zFont(.semiBold, size: 24, style: Design.Text.primary)
.padding(.top, 40)
.padding(.bottom, 8)
Text(L10n.RestoreWallet.Birthday.info)
.zFont(size: 14, style: Design.Text.primary)
.padding(.bottom, 32)
ZashiTextField(
text: $store.birthday,
placeholder: L10n.RestoreWallet.Birthday.placeholder,
title: L10n.RestoreWallet.Birthday.title
)
.padding(.bottom, 6)
.keyboardType(.numberPad)
.autocapitalization(.none)
.disableAutocorrection(true)
Text(L10n.RestoreWallet.Birthday.fieldInfo)
.zFont(size: 12, style: Design.Text.tertiary)
Spacer()
ZashiButton(
L10n.RestoreWallet.Birthday.estimate,
type: .ghost
) {
store.send(.estimateHeightTapped)
}
.padding(.bottom, 12)
ZashiButton(L10n.ImportWallet.Button.restoreWallet) {
store.send(.restoreTapped)
}
.disabled(!store.isValidBirthday)
.padding(.bottom, 24)
}
.zashiBack()
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing:
Button {
store.send(.helpSheetRequested)
} label: {
Asset.Assets.Icons.help.image
.zImage(size: 24, style: Design.Text.primary)
.padding(8)
}
)
.screenHorizontalPadding()
.applyScreenBackground()
.screenTitle(L10n.ImportWallet.Button.restoreWallet)
}
}
// MARK: - Previews
#Preview {
WalletBirthdayView(store: WalletBirthday.initial)
}
// MARK: - Store
extension WalletBirthday {
public static var initial = StoreOf<WalletBirthday>(
initialState: .initial
) {
WalletBirthday()
}
}
// MARK: - Placeholders
extension WalletBirthday.State {
public static let initial = WalletBirthday.State()
}

View File

@ -12,6 +12,8 @@ import UIComponents
import WhatsNewProvider
public struct WhatsNewView: View {
@Environment(\.colorScheme) var colorScheme
@Perception.Bindable var store: StoreOf<WhatsNew>
public init(store: StoreOf<WhatsNew>) {
@ -60,7 +62,7 @@ public struct WhatsNewView: View {
Spacer()
}
ZashiText(withAttributedString: previewText)
ZashiText(withAttributedString: previewText, colorScheme: colorScheme)
.zFont(size: 14, style: Design.Text.primary)
.frame(maxWidth: .infinity, alignment: .leading)
.accentColor(Asset.Colors.primary.color)

View File

@ -368,6 +368,12 @@ public enum L10n {
/// Upgrading databases
public static let migratingDatabases = L10n.tr("Localizable", "home.migratingDatabases", fallback: "Upgrading databases…")
}
public enum HomeScreen {
/// More
public static let more = L10n.tr("Localizable", "homeScreen.more", fallback: "More")
/// Scan
public static let scan = L10n.tr("Localizable", "homeScreen.scan", fallback: "Scan")
}
public enum ImportWallet {
/// Enter secret
/// recovery phrase
@ -669,25 +675,65 @@ public enum L10n {
}
}
public enum RestoreInfo {
/// Keep screen on while restoring
public static let checkbox = L10n.tr("Localizable", "restoreInfo.checkbox", fallback: "Keep screen on while restoring")
/// Got it!
public static let gotIt = L10n.tr("Localizable", "restoreInfo.gotIt", fallback: "Got it!")
/// Note:
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.
public static let noteInfo = L10n.tr("Localizable", "restoreInfo.noteInfo", fallback: "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.")
/// Your wallet has been successfully restored and is now syncing
public static let subTitle = L10n.tr("Localizable", "restoreInfo.subTitle", fallback: "Your wallet has been successfully restored and is now syncing")
/// 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 funds cannot be spent with Zashi until your wallet is fully restored.
public static let noteInfo = L10n.tr("Localizable", "restoreInfo.noteInfo", fallback: "Your funds cannot be spent with Zashi until your wallet is fully restored.")
/// Your wallet is being restored.
public static let subTitle = L10n.tr("Localizable", "restoreInfo.subTitle", fallback: "Your wallet is being restored.")
/// Keep the Zashi app open on an active phone screen.
public static let tip1 = L10n.tr("Localizable", "restoreInfo.tip1", fallback: "Keep the Zashi app open on an active phone screen.")
/// To prevent your phone screen from going dark, turn off power-saving mode and keep your phone plugged in.
public static let tip2 = L10n.tr("Localizable", "restoreInfo.tip2", fallback: "To prevent your phone screen from going dark, turn off power-saving mode and keep your phone plugged in.")
/// Zashi is scanning the blockchain to retrieve your transactions. Older wallets can take hours to restore. Follow these steps to prevent interruption:
public static let tips = L10n.tr("Localizable", "restoreInfo.tips", fallback: "Zashi is scanning the blockchain to retrieve your transactions. Older wallets can take hours to restore. Follow these steps to prevent interruption:")
/// Keep Zashi open!
public static let title = L10n.tr("Localizable", "restoreInfo.title", fallback: "Keep Zashi open!")
}
public enum RestoreWallet {
/// Please type in your 24-word secret recovery phrase in the correct order.
public static let info = L10n.tr("Localizable", "restoreWallet.info", fallback: "Please type in your 24-word secret recovery phrase in the correct order.")
/// Secret Recovery Phrase
public static let title = L10n.tr("Localizable", "restoreWallet.title", fallback: "Secret Recovery Phrase")
public enum Birthday {
/// Estimate my block height
public static let estimate = L10n.tr("Localizable", "restoreWallet.birthday.estimate", fallback: "Estimate my block height")
/// Wallet Birthday Height is the point in time when your wallet was created.
public static let fieldInfo = L10n.tr("Localizable", "restoreWallet.birthday.fieldInfo", fallback: "Wallet Birthday Height is the point in time when your wallet was created.")
/// Entering your Wallet Birthday Height helps speed up the restore process.
public static let info = L10n.tr("Localizable", "restoreWallet.birthday.info", fallback: "Entering your Wallet Birthday Height helps speed up the restore process.")
/// Enter number
public static let placeholder = L10n.tr("Localizable", "restoreWallet.birthday.placeholder", fallback: "Enter number")
/// Block Height
public static let title = L10n.tr("Localizable", "restoreWallet.birthday.title", fallback: "Block Height")
public enum EstimateDate {
/// Entering the block height at which your wallet was created reduces the number of blocks that need to be scanned to recover your wallet.
public static let info = L10n.tr("Localizable", "restoreWallet.birthday.estimateDate.info", fallback: "Entering the block height at which your wallet was created reduces the number of blocks that need to be scanned to recover your wallet.")
/// First Wallet Transaction
public static let title = L10n.tr("Localizable", "restoreWallet.birthday.estimateDate.title", fallback: "First Wallet Transaction")
/// If youre not sure, choose an earlier date.
public static let warning = L10n.tr("Localizable", "restoreWallet.birthday.estimateDate.warning", fallback: "If youre not sure, choose an earlier date.")
}
public enum Estimated {
/// Zashi will scan and recover all transactions made after the following block number.
public static let info = L10n.tr("Localizable", "restoreWallet.birthday.estimated.info", fallback: "Zashi will scan and recover all transactions made after the following block number.")
/// Estimated Block Height
public static let title = L10n.tr("Localizable", "restoreWallet.birthday.estimated.title", fallback: "Estimated Block Height")
}
}
public enum Help {
/// ^[The Wallet Birthday Height](style: 'boldPrimary') is the block height (block # in the blockchain) at which your wallet was created. If you ever lose access to your Zashi app and need to recover your funds, providing the block height along with your recovery phrase can significantly speed up the process.
public static let birthday = L10n.tr("Localizable", "restoreWallet.help.birthday", fallback: "^[The Wallet Birthday Height](style: 'boldPrimary') is the block height (block # in the blockchain) at which your wallet was created. If you ever lose access to your Zashi app and need to recover your funds, providing the block height along with your recovery phrase can significantly speed up the process.")
/// ^[The Secret Recovery Phrase](style: 'boldPrimary') is a unique set of 24 words, appearing in a precise order. It can be used to gain full control of your funds from any device via any Zcash wallet app. Think of it as the master key to your wallet. It is stored in Zashis Advanced Settings.
public static let phrase = L10n.tr("Localizable", "restoreWallet.help.phrase", fallback: "^[The Secret Recovery Phrase](style: 'boldPrimary') is a unique set of 24 words, appearing in a precise order. It can be used to gain full control of your funds from any device via any Zcash wallet app. Think of it as the master key to your wallet. It is stored in Zashis Advanced Settings.")
/// Need to know more?
public static let title = L10n.tr("Localizable", "restoreWallet.help.title", fallback: "Need to know more?")
}
}
public enum Root {
public enum Debug {
/// Startup

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "connect.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "connectDark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -61,13 +61,13 @@
// MARK: - Restore Info
"restoreInfo.title" = "Keep Zashi open!";
"restoreInfo.subTitle" = "Your wallet has been successfully restored and is now syncing";
"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.subTitle" = "Your wallet is being restored.";
"restoreInfo.tips" = "Zashi is scanning the blockchain to retrieve your transactions. Older wallets can take hours to restore. Follow these steps to prevent interruption:";
"restoreInfo.tip1" = "Keep the Zashi app open on an active phone screen.";
"restoreInfo.tip2" = "To prevent your phone screen from going dark, turn off power-saving mode and keep your phone plugged in.";
//"restoreInfo.tip3" = "Keep your open phone in a secure place.";
"restoreInfo.note" = "Note: ";
"restoreInfo.noteInfo" = "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.noteInfo" = "Your funds cannot be spent with Zashi until your wallet is fully restored.";
"restoreInfo.gotIt" = "Got it!";
// MARK: - Tabs
@ -566,3 +566,25 @@
// More actions
"more.options" = "More Options";
// Home screen
"homeScreen.scan" = "Scan";
"homeScreen.more" = "More";
// Onboarding: Restore Wallet
"restoreWallet.title" = "Secret Recovery Phrase";
"restoreWallet.info" = "Please type in your 24-word secret recovery phrase in the correct order.";
"restoreWallet.help.title" = "Need to know more?";
"restoreWallet.help.phrase" = "^[The Secret Recovery Phrase](style: 'boldPrimary') is a unique set of 24 words, appearing in a precise order. It can be used to gain full control of your funds from any device via any Zcash wallet app. Think of it as the master key to your wallet. It is stored in Zashis Advanced Settings.";
"restoreWallet.help.birthday" = "^[The Wallet Birthday Height](style: 'boldPrimary') is the block height (block # in the blockchain) at which your wallet was created. If you ever lose access to your Zashi app and need to recover your funds, providing the block height along with your recovery phrase can significantly speed up the process.";
"restoreWallet.birthday.placeholder" = "Enter number";
"restoreWallet.birthday.title" = "Block Height";
"restoreWallet.birthday.info" = "Entering your Wallet Birthday Height helps speed up the restore process.";
"restoreWallet.birthday.fieldInfo" = "Wallet Birthday Height is the point in time when your wallet was created.";
"restoreWallet.birthday.estimate" = "Estimate my block height";
"restoreWallet.birthday.estimateDate.title" = "First Wallet Transaction";
"restoreWallet.birthday.estimateDate.info" = "Entering the block height at which your wallet was created reduces the number of blocks that need to be scanned to recover your wallet.";
"restoreWallet.birthday.estimateDate.warning" = "If youre not sure, choose an earlier date.";
"restoreWallet.birthday.estimated.title" = "Estimated Block Height";
"restoreWallet.birthday.estimated.info" = "Zashi will scan and recover all transactions made after the following block number.";
"restoreInfo.checkbox" = "Keep screen on while restoring";

View File

@ -32,6 +32,7 @@ public enum Asset {
public static let fly = ImageAsset(name: "Fly")
public static let flyReceived = ImageAsset(name: "FlyReceived")
public enum Illustrations {
public static let connect = ImageAsset(name: "connect")
public static let emptyState = ImageAsset(name: "emptyState")
public static let failure1 = ImageAsset(name: "failure1")
public static let failure2 = ImageAsset(name: "failure2")
@ -89,6 +90,7 @@ public enum Asset {
public static let filter = ImageAsset(name: "filter")
public static let flashOff = ImageAsset(name: "flashOff")
public static let flashOn = ImageAsset(name: "flashOn")
public static let help = ImageAsset(name: "help")
public static let imageLibrary = ImageAsset(name: "imageLibrary")
public static let integrations = ImageAsset(name: "integrations")
public static let key = ImageAsset(name: "key")

View File

@ -0,0 +1,53 @@
//
// ZashiSheet.swift
// modules
//
// Created by Lukáš Korba on 31.03.2025.
//
import SwiftUI
public struct ZashiSheetModifier<SheetContent: View>: ViewModifier {
@Binding public var isPresented: Bool
@State var sheetHeight: CGFloat = .zero
var sheetContent: SheetContent
public func body(content: Content) -> some View {
content
.sheet(isPresented: $isPresented) {
if #available(iOS 16.0, *) {
mainBody()
.presentationDetents([.height(sheetHeight)])
.presentationDragIndicator(.visible)
} else {
mainBody(stickToBottom: true)
}
}
}
@ViewBuilder func mainBody(stickToBottom: Bool = false) -> some View {
VStack(alignment: .leading, spacing: 0) {
if stickToBottom {
Spacer()
}
sheetContent
}
.background {
GeometryReader { proxy in
Color.clear
.task {
sheetHeight = proxy.size.height
}
}
}
}
}
extension View {
public func zashiSheet(isPresented: Binding<Bool>, content: @escaping () -> some View) -> some View {
modifier(
ZashiSheetModifier(isPresented: isPresented, sheetContent: content())
)
}
}

View File

@ -81,7 +81,7 @@ private struct WalletStatusPanel: View {
Text("Hello, World")
}
.padding(.vertical, 1)
.walletStatusPanel()
//..walletstatusPanel()
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
trailing: Text("M")

View File

@ -15,22 +15,29 @@ public struct ZashiText: View {
Text(attributedString)
}
public init(withAttributedString attributedString: AttributedString) {
self.attributedString = ZashiText.annotateRainbowColors(from: attributedString)
public init(withAttributedString attributedString: AttributedString, colorScheme: ColorScheme) {
self.attributedString = AttributedString("")
self.attributedString = ZashiText.annotateStyle(from: attributedString, colorScheme: colorScheme)
}
public init(_ localizedKey: String.LocalizationValue) {
attributedString = ZashiText.annotateRainbowColors(
from: AttributedString(localized: localizedKey, including: \.zashiApp))
public init(_ localizedKey: String.LocalizationValue, colorScheme: ColorScheme) {
self.attributedString = AttributedString("")
self.attributedString = ZashiText.annotateStyle(
from: AttributedString(localized: localizedKey, including: \.zashiApp), colorScheme: colorScheme)
}
private static func annotateRainbowColors(from source: AttributedString) -> AttributedString {
private static func annotateStyle(from source: AttributedString, colorScheme: ColorScheme) -> AttributedString {
var attrString = source
for run in attrString.runs {
if let zStyle = run.zStyle {
switch zStyle {
case .bold:
attrString[run.range].font = .system(size: 14, weight: .bold)
case .boldPrimary:
attrString[run.range].font = .system(size: 14, weight: .bold)
attrString[run.range].foregroundColor = Design.Text.primary.color(colorScheme)
case .italic:
attrString[run.range].font = .system(size: 14).italic()
case .boldItalic:
@ -47,6 +54,7 @@ public struct ZashiText: View {
enum ZashiTextAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey {
enum Value: String, Codable, Hashable {
case bold
case boldPrimary
case italic
case boldItalic
case link
@ -69,13 +77,8 @@ extension AttributeDynamicLookup {
}
}
struct RainbowText_Previews: PreviewProvider {
static var previews: some View {
guard let previewText = try? AttributedString(
markdown: "Some ^[bold](style: 'bold') ^[italic](style: 'italic') ^[boldItalic](style: 'boldItalic') [link example](https://electriccoin.co) text.",
including: \.zashiApp) else {
return ZashiText(withAttributedString: "Couldn't load the preview text.")
}
return ZashiText(withAttributedString: previewText)
}
}
// Example:
//let previewText = try? AttributedString(
// markdown: "Some ^[bold](style: 'bold') ^[italic](style: 'italic') ^[boldItalic](style: 'boldItalic') [link example](https://electriccoin.co) text.",
// including: \.zashiApp)
//ZashiText(withAttributedString: previewText)

View File

@ -12,15 +12,18 @@ public struct ZashiToggle: View {
@Binding var isOn: Bool
let label: String
let textColor: Color
let textSize: CGFloat
public init(
isOn: Binding<Bool>,
label: String = "",
textColor: Color = Asset.Colors.primary.color
textColor: Color = Asset.Colors.primary.color,
textSize: CGFloat = 14
) {
self._isOn = isOn
self.label = label
self.textColor = textColor
self.textSize = textSize
}
public var body: some View {
@ -33,7 +36,7 @@ public struct ZashiToggle: View {
.padding(.trailing, 8)
Text(label)
.zFont(.medium, size: 14, style: Design.Text.primary)
.zFont(.medium, size: textSize, style: Design.Text.primary)
.multilineTextAlignment(.leading)
}
}

View File

@ -61,13 +61,13 @@
// MARK: - Restore Info
"restoreInfo.title" = "Keep Zashi open!";
"restoreInfo.subTitle" = "Your wallet has been successfully restored and is now syncing";
"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.subTitle" = "Your wallet is being restored.";
"restoreInfo.tips" = "Zashi is scanning the blockchain to retrieve your transactions. Older wallets can take hours to restore. Follow these steps to prevent interruption:";
"restoreInfo.tip1" = "Keep the Zashi app open on an active phone screen.";
"restoreInfo.tip2" = "To prevent your phone screen from going dark, turn off power-saving mode and keep your phone plugged in.";
//"restoreInfo.tip3" = "Keep your open phone in a secure place.";
"restoreInfo.note" = "Note: ";
"restoreInfo.noteInfo" = "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.noteInfo" = "Your funds cannot be spent with Zashi until your wallet is fully restored.";
"restoreInfo.gotIt" = "Got it!";
// MARK: - Tabs
@ -566,3 +566,25 @@
// More actions
"more.options" = "More Options";
// Home screen
"homeScreen.scan" = "Scan";
"homeScreen.more" = "More";
// Onboarding: Restore Wallet
"restoreWallet.title" = "Secret Recovery Phrase";
"restoreWallet.info" = "Please type in your 24-word secret recovery phrase in the correct order.";
"restoreWallet.help.title" = "Need to know more?";
"restoreWallet.help.phrase" = "^[The Secret Recovery Phrase](style: 'boldPrimary') is a unique set of 24 words, appearing in a precise order. It can be used to gain full control of your funds from any device via any Zcash wallet app. Think of it as the master key to your wallet. It is stored in Zashis Advanced Settings.";
"restoreWallet.help.birthday" = "^[The Wallet Birthday Height](style: 'boldPrimary') is the block height (block # in the blockchain) at which your wallet was created. If you ever lose access to your Zashi app and need to recover your funds, providing the block height along with your recovery phrase can significantly speed up the process.";
"restoreWallet.birthday.placeholder" = "Enter number";
"restoreWallet.birthday.title" = "Block Height";
"restoreWallet.birthday.info" = "Entering your Wallet Birthday Height helps speed up the restore process.";
"restoreWallet.birthday.fieldInfo" = "Wallet Birthday Height is the point in time when your wallet was created.";
"restoreWallet.birthday.estimate" = "Estimate my block height";
"restoreWallet.birthday.estimateDate.title" = "First Wallet Transaction";
"restoreWallet.birthday.estimateDate.info" = "Entering the block height at which your wallet was created reduces the number of blocks that need to be scanned to recover your wallet.";
"restoreWallet.birthday.estimateDate.warning" = "If youre not sure, choose an earlier date.";
"restoreWallet.birthday.estimated.title" = "Estimated Block Height";
"restoreWallet.birthday.estimated.info" = "Zashi will scan and recover all transactions made after the following block number.";
"restoreInfo.checkbox" = "Keep screen on while restoring";