Shielding refactored to be single source of truth

This commit is contained in:
Lukas Korba 2025-04-18 10:18:04 +02:00
parent 067f1cf22e
commit 1f94c1480e
32 changed files with 608 additions and 326 deletions

View File

@ -207,6 +207,7 @@ let package = Package(
"NumberFormatter",
"PartialProposalError",
"SDKSynchronizer",
"ShieldingProcessor",
"SyncProgress",
"UIComponents",
"Utils",
@ -687,6 +688,8 @@ let package = Package(
"SendForm",
"ServerSetup",
"Settings",
"ShieldingProcessor",
"SupportDataGenerator",
"TransactionDetails",
"TransactionsManager",
"UIComponents",
@ -875,6 +878,7 @@ let package = Package(
"Models",
"NetworkMonitor",
"SDKSynchronizer",
"ShieldingProcessor",
"SupportDataGenerator",
"UIComponents",
"UserPreferencesStorage",

View File

@ -0,0 +1,35 @@
//
// ShieldingProcessorInterface.swift
// Zashi
//
// Created by Lukáš Korba on 2025-04-17.
//
import Foundation
import ComposableArchitecture
import ZcashLightClientKit
import Combine
import Models
extension DependencyValues {
public var shieldingProcessor: ShieldingProcessorClient {
get { self[ShieldingProcessorClient.self] }
set { self[ShieldingProcessorClient.self] = newValue }
}
}
@DependencyClient
public struct ShieldingProcessorClient {
public enum State: Equatable {
case failed(ZcashError)
case grpc
case proposal(Proposal)
case requested
case succeeded
case unknown
}
public let observe: () -> AnyPublisher<ShieldingProcessorClient.State, Never>
public let shieldFunds: () -> Void
}

View File

@ -0,0 +1,94 @@
//
// TaxExporterLiveKey.swift
// Zashi
//
// Created by Lukáš Korba on 2025-02-13.
//
import Foundation
import ComposableArchitecture
import UIKit
import Combine
import Generated
import Models
import SDKSynchronizer
import WalletStorage
import MnemonicClient
import DerivationTool
import ZcashSDKEnvironment
extension ShieldingProcessorClient: DependencyKey {
public static let liveValue: ShieldingProcessorClient = Self.live()
public static func live() -> Self {
@Dependency(\.derivationTool) var derivationTool
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
@Shared(.inMemory(.selectedWalletAccount)) var selectedWalletAccount: WalletAccount? = nil
let subject = CurrentValueSubject<ShieldingProcessorClient.State, Never>(.unknown)
return ShieldingProcessorClient(
observe: { subject.eraseToAnyPublisher() },
shieldFunds: {
subject.send(.requested)
guard let account = selectedWalletAccount, let zip32AccountIndex = account.zip32AccountIndex else {
subject.send(.failed("shieldFunds failed, no account available".toZcashError()))
return
}
if account.vendor == .keystone {
Task {
do {
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
guard let proposal else { throw "shieldFunds with Keystone: nil proposal" }
subject.send(.proposal(proposal))
} catch {
subject.send(.failed(error.toZcashError()))
}
}
} else {
Task {
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, zip32AccountIndex, zcashSDKEnvironment.network.networkType)
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
// try? await Task.sleep(for: .seconds(4))
//subject.send(.succeeded)
// subject.send(.failed("parada".toZcashError()))
// subject.send(.grpc)
// return
guard let proposal else { throw "shieldFunds nil proposal" }
let result = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)
switch result {
case .grpcFailure:
subject.send(.grpc)
case let .failure(_, code, description):
subject.send(.failed("shieldFunds failed \(code) \(description)".toZcashError()))
case .partial:
break
case .success:
walletStorage.resetShieldingReminder(WalletAccount.Vendor.zcash.name())
subject.send(.succeeded)
}
} catch {
subject.send(.failed(error.toZcashError()))
}
}
}
}
)
}
}

View File

@ -1,129 +0,0 @@
//
// ShieldingProcessorStore.swift
// modules
//
// Created by Lukáš Korba on 08.04.2025.
//
import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
import DerivationTool
import MnemonicClient
import Utils
import Generated
import WalletStorage
import SDKSynchronizer
import Models
import ZcashSDKEnvironment
@Reducer
public struct ShieldingProcessor {
@ObservableState
public struct State: Equatable {
public var isShieldingFunds = false
@Shared(.inMemory(.selectedWalletAccount)) public var selectedWalletAccount: WalletAccount? = nil
public init() { }
}
@CasePathable
public enum Action: Equatable {
case proposalReadyForShieldingWithKeystone(Proposal)
case shieldFunds
case shieldFundsFailure(ZcashError)
case shieldFundsSuccess
case shieldFundsWithKeystone
}
@Dependency(\.derivationTool) var derivationTool
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
public init() { }
public var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .shieldFunds:
guard let account = state.selectedWalletAccount, let zip32AccountIndex = account.zip32AccountIndex else {
return .none
}
if account.vendor == .keystone {
return .send(.shieldFundsWithKeystone)
}
// Regular path only for Zashi account
state.isShieldingFunds = true
return .run { send in
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, zip32AccountIndex, zcashSDKEnvironment.network.networkType)
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
let result = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)
switch result {
case .grpcFailure:
await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions".toZcashError()))
case .failure:
await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions".toZcashError()))
case .partial:
break
case .success:
await send(.shieldFundsSuccess)
}
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
}
case .shieldFundsWithKeystone:
guard let account = state.selectedWalletAccount else {
return .none
}
return .run { send in
do {
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
await send(.proposalReadyForShieldingWithKeystone(proposal))
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
}
case .proposalReadyForShieldingWithKeystone:
return .none
case .shieldFundsFailure:
state.isShieldingFunds = false
return .none
case .shieldFundsSuccess:
state.isShieldingFunds = false
if let account = state.selectedWalletAccount {
walletStorage.resetShieldingReminder(account.account)
}
return .none
}
}
}
}
// MARK: Alerts
extension AlertState where Action == ShieldingProcessor.Action {
public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
AlertState {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.title)
} message: {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.message(error.detailedMessage))
}
}
}

View File

@ -97,8 +97,6 @@ extension TaxExporterClient: DependencyKey {
} catch {
throw error
}
return .emptyURL
}
)
}

View File

@ -23,8 +23,8 @@ public struct WalletStorage {
public static let zcashStoredWalletBackupReminder = "zcashStoredWalletBackupReminder"
public static let zcashStoredShieldingReminder = "zcashStoredShieldingReminder"
public static func zcashStoredShieldingReminder(account: Account) -> String {
"\(Constants.zcashStoredShieldingReminder)_\(account.name?.lowercased() ?? "")"
public static func zcashStoredShieldingReminder(accountName: String) -> String {
"\(Constants.zcashStoredShieldingReminder)_\(accountName)"
}
/// Versioning of the stored data
@ -273,25 +273,25 @@ public struct WalletStorage {
return try? decode(json: reqData, as: ReminedMeTimestamp.self)
}
public func importShieldingReminder(_ reminder: ReminedMeTimestamp, account: Account) throws {
public func importShieldingReminder(_ reminder: ReminedMeTimestamp, accountName: String) throws {
guard let data = try? encode(object: reminder) else {
throw KeychainError.encoding
}
do {
try setData(data, forKey: Constants.zcashStoredShieldingReminder(account: account))
try setData(data, forKey: Constants.zcashStoredShieldingReminder(accountName: accountName))
} catch KeychainError.duplicate {
try updateData(data, forKey: Constants.zcashStoredShieldingReminder(account: account))
try updateData(data, forKey: Constants.zcashStoredShieldingReminder(accountName: accountName))
} catch {
throw WalletStorageError.storageError(error)
}
}
public func exportShieldingReminder(account: Account) -> ReminedMeTimestamp? {
public func exportShieldingReminder(accountName: String) -> ReminedMeTimestamp? {
let reqData: Data?
do {
reqData = try data(forKey: Constants.zcashStoredShieldingReminder(account: account))
reqData = try data(forKey: Constants.zcashStoredShieldingReminder(accountName: accountName))
} catch {
return nil
}
@ -303,8 +303,8 @@ public struct WalletStorage {
return try? decode(json: reqData, as: ReminedMeTimestamp.self)
}
public func resetShieldingReminder(account: Account) {
try? deleteData(forKey: Constants.zcashStoredShieldingReminder(account: account))
public func resetShieldingReminder(accountName: String) {
try? deleteData(forKey: Constants.zcashStoredShieldingReminder(accountName: accountName))
}

View File

@ -85,7 +85,7 @@ public struct WalletStorageClient {
public var importWalletBackupReminder: (ReminedMeTimestamp) throws -> Void
public var exportWalletBackupReminder: () -> ReminedMeTimestamp?
public var importShieldingReminder: (ReminedMeTimestamp, Account) throws -> Void
public var exportShieldingReminder: (Account) -> ReminedMeTimestamp?
public var resetShieldingReminder: (Account) -> Void
public var importShieldingReminder: (ReminedMeTimestamp, String) throws -> Void
public var exportShieldingReminder: (String) -> ReminedMeTimestamp?
public var resetShieldingReminder: (String) -> Void
}

View File

@ -57,14 +57,14 @@ extension WalletStorageClient: DependencyKey {
exportWalletBackupReminder: {
walletStorage.exportWalletBackupReminder()
},
importShieldingReminder: { reminedMeTimestamp, account in
try walletStorage.importShieldingReminder(reminedMeTimestamp, account: account)
importShieldingReminder: { reminedMeTimestamp, accountName in
try walletStorage.importShieldingReminder(reminedMeTimestamp, accountName: accountName)
},
exportShieldingReminder: { account in
walletStorage.exportShieldingReminder(account: account)
exportShieldingReminder: { accountName in
walletStorage.exportShieldingReminder(accountName: accountName)
},
resetShieldingReminder: { account in
walletStorage.resetShieldingReminder(account: account)
resetShieldingReminder: { accountName in
walletStorage.resetShieldingReminder(accountName: accountName)
}
)
}

View File

@ -17,17 +17,19 @@ import WalletStorage
import SDKSynchronizer
import Models
import ZcashSDKEnvironment
import ShieldingProcessor
@Reducer
public struct Balances {
private let CancelId = UUID()
@ObservableState
public struct State: Equatable {
public var stateStreamCancelId = UUID()
public var shieldingProcessorCancelId = UUID()
@Presents public var alert: AlertState<Action>?
public var autoShieldingThreshold: Zatoshi
public var changePending: Zatoshi
public var isShieldingFunds: Bool
public var isShielding: Bool
public var pendingTransactions: Zatoshi
@Shared(.inMemory(.selectedWalletAccount)) public var selectedWalletAccount: WalletAccount? = nil
public var shieldedBalance: Zatoshi
@ -45,7 +47,7 @@ public struct Balances {
}
public var isShieldingButtonDisabled: Bool {
isShieldingFunds || !isShieldableBalanceAvailable
isShielding || !isShieldableBalanceAvailable
}
public var isProcessingZeroAvailableBalance: Bool {
@ -59,14 +61,14 @@ public struct Balances {
public init(
autoShieldingThreshold: Zatoshi,
changePending: Zatoshi,
isShieldingFunds: Bool,
isShielding: Bool,
pendingTransactions: Zatoshi,
shieldedBalance: Zatoshi = .zero,
transparentBalance: Zatoshi = .zero
) {
self.autoShieldingThreshold = autoShieldingThreshold
self.changePending = changePending
self.isShieldingFunds = isShieldingFunds
self.isShielding = isShielding
self.pendingTransactions = pendingTransactions
self.shieldedBalance = shieldedBalance
self.transparentBalance = transparentBalance
@ -79,12 +81,14 @@ public struct Balances {
case dismissTapped
case onAppear
case onDisappear
case proposalReadyForShieldingWithKeystone(Proposal)
// case proposalReadyForShieldingWithKeystone(Proposal)
case sheetHeightUpdated(CGFloat)
case shieldFunds
case shieldFundsFailure(ZcashError)
case shieldFundsSuccess
case shieldFundsWithKeystone
case shieldFundsTapped
case shieldingProcessorStateChanged(ShieldingProcessorClient.State)
// case shieldFunds
// case shieldFundsFailure(ZcashError)
// case shieldFundsSuccess
// case shieldFundsWithKeystone
case synchronizerStateChanged(RedactableSynchronizerState)
case updateBalance(AccountBalance?)
case updateBalances([AccountUUID: AccountBalance])
@ -96,6 +100,7 @@ public struct Balances {
@Dependency(\.mnemonic) var mnemonic
@Dependency(\.numberFormatter) var numberFormatter
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.shieldingProcessor) var shieldingProcessor
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
@ -123,12 +128,27 @@ public struct Balances {
.map { $0.redacted }
.map(Action.synchronizerStateChanged)
}
.cancellable(id: CancelId, cancelInFlight: true),
.cancellable(id: state.stateStreamCancelId, cancelInFlight: true),
.publisher {
shieldingProcessor.observe()
.map(Action.shieldingProcessorStateChanged)
}
.cancellable(id: state.shieldingProcessorCancelId, cancelInFlight: true),
.send(.updateBalancesOnAppear)
)
case .onDisappear:
return .cancel(id: CancelId)
return .merge(
.cancel(id: state.stateStreamCancelId),
.cancel(id: state.shieldingProcessorCancelId)
)
case .shieldingProcessorStateChanged(let shieldingProcessorState):
state.isShielding = shieldingProcessorState == .requested
if shieldingProcessorState == .succeeded {
return .send(.updateBalancesOnAppear)
}
return .none
case .updateBalancesOnAppear:
guard let account = state.selectedWalletAccount else {
@ -148,74 +168,78 @@ public struct Balances {
case .dismissTapped:
return .none
case .shieldFunds:
guard let account = state.selectedWalletAccount, let zip32AccountIndex = account.zip32AccountIndex else {
return .none
}
if account.vendor == .keystone {
return .send(.shieldFundsWithKeystone)
}
// Regular path only for Zashi account
state.isShieldingFunds = true
return .run { send in
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, zip32AccountIndex, zcashSDKEnvironment.network.networkType)
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
let result = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)
//await send(.walletBalances(.updateBalances))
switch result {
case .grpcFailure:
await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions-grpcFailure".toZcashError()))
case let.failure(_, code, description):
await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions-failure \(code) \(description)".toZcashError()))
case .partial:
return
case .success:
await send(.shieldFundsSuccess)
}
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
}
case .shieldFundsTapped:
shieldingProcessor.shieldFunds()
return .none
case .shieldFundsWithKeystone:
guard let account = state.selectedWalletAccount else {
return .none
}
return .run { send in
do {
let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
await send(.proposalReadyForShieldingWithKeystone(proposal))
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
}
case .proposalReadyForShieldingWithKeystone:
return .none
case .shieldFundsFailure:
state.isShieldingFunds = false
//state.alert = AlertState.shieldFundsFailure(error)
return .none
case .shieldFundsSuccess:
state.isShieldingFunds = false
state.transparentBalance = .zero
if let account = state.selectedWalletAccount {
walletStorage.resetShieldingReminder(account.account)
}
return .none
// case .shieldFunds:
// guard let account = state.selectedWalletAccount, let zip32AccountIndex = account.zip32AccountIndex else {
// return .none
// }
// if account.vendor == .keystone {
// return .send(.shieldFundsWithKeystone)
// }
// // Regular path only for Zashi account
// state.isShieldingFunds = true
// return .run { send in
// do {
// let storedWallet = try walletStorage.exportWallet()
// let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
// let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, zip32AccountIndex, zcashSDKEnvironment.network.networkType)
//
// let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
//
// guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
//
// let result = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)
//
// //await send(.walletBalances(.updateBalances))
//
// switch result {
// case .grpcFailure:
// await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions-grpcFailure".toZcashError()))
// case let.failure(_, code, description):
// await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions-failure \(code) \(description)".toZcashError()))
// case .partial:
// return
// case .success:
// await send(.shieldFundsSuccess)
// }
// } catch {
// await send(.shieldFundsFailure(error.toZcashError()))
// }
// }
//
// case .shieldFundsWithKeystone:
// guard let account = state.selectedWalletAccount else {
// return .none
// }
// return .run { send in
// do {
// let proposal = try await sdkSynchronizer.proposeShielding(account.id, zcashSDKEnvironment.shieldingThreshold, .empty, nil)
//
// guard let proposal else { throw "sdkSynchronizer.proposeShielding" }
// await send(.proposalReadyForShieldingWithKeystone(proposal))
// } catch {
// await send(.shieldFundsFailure(error.toZcashError()))
// }
// }
//
// case .proposalReadyForShieldingWithKeystone:
// return .none
//
// case .shieldFundsFailure:
// state.isShieldingFunds = false
// //state.alert = AlertState.shieldFundsFailure(error)
// return .none
//
// case .shieldFundsSuccess:
// state.isShieldingFunds = false
// state.transparentBalance = .zero
// if let account = state.selectedWalletAccount {
// walletStorage.resetShieldingReminder(account.account)
// }
// return .none
case .synchronizerStateChanged(let latestState):
return .send(.updateBalances(latestState.data.accountsBalances))

View File

@ -39,7 +39,9 @@ public struct BalancesView: View {
.padding(.top, 40)
Text(
store.isPendingInProcess
store.spendability == .everything
? L10n.Balances.everythingDone
: store.isPendingInProcess
? L10n.Balances.infoPending1
: L10n.Balances.shieldInfo1
)
@ -47,6 +49,11 @@ public struct BalancesView: View {
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 8)
.multilineTextAlignment(
store.spendability == .everything || !store.isShieldableBalanceAvailable
? .center
: .leading
)
if store.isShieldableBalanceAvailable {
Text(
@ -150,8 +157,9 @@ extension BalancesView {
L10n.SmartBanner.Content.Shield.button,
infinityWidth: false
) {
store.send(.shieldFunds)
store.send(.shieldFundsTapped)
}
.disabled(store.isShielding)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
@ -182,7 +190,7 @@ extension BalancesView {
initialState: Balances.State(
autoShieldingThreshold: Zatoshi(1_000_000),
changePending: Zatoshi(25_234_000),
isShieldingFunds: true,
isShielding: true,
pendingTransactions: Zatoshi(25_234_000)
)
) {
@ -200,14 +208,14 @@ extension Balances.State {
public static let placeholder = Balances.State(
autoShieldingThreshold: .zero,
changePending: .zero,
isShieldingFunds: false,
isShielding: false,
pendingTransactions: .zero
)
public static let initial = Balances.State(
autoShieldingThreshold: .zero,
changePending: .zero,
isShieldingFunds: false,
isShielding: false,
pendingTransactions: .zero
)
}

View File

@ -8,6 +8,7 @@
import ComposableArchitecture
import Generated
import AudioServices
import Models
// Path
import SendConfirmation
@ -48,6 +49,9 @@ extension SignWithKeystoneCoordFlow {
state.path.append(.sendResultResubmission(state.sendConfirmationState))
break
case .success:
if state.sendConfirmationState.isShielding {
walletStorage.resetShieldingReminder(WalletAccount.Vendor.keystone.name())
}
state.path.append(.sendResultSuccess(state.sendConfirmationState))
default: break
}

View File

@ -11,6 +11,7 @@ import ZcashLightClientKit
import AudioServices
import Models
import WalletStorage
// Path
import SendConfirmation
@ -45,7 +46,8 @@ public struct SignWithKeystoneCoordFlow {
}
@Dependency(\.audioServices) var audioServices
@Dependency(\.walletStorage) var walletStorage
public init() { }
public var body: some Reducer<State, Action> {

View File

@ -15,7 +15,7 @@ import PartnerKeys
import UserPreferencesStorage
import Utils
import SmartBanner
import ShieldingProcessor
//import ShieldingProcessor
@Reducer
public struct Home {
@ -35,7 +35,7 @@ public struct Home {
public var isRateTooltipEnabled = false
public var migratingDatabase = true
public var moreRequest = false
public var shieldingProcessorState = ShieldingProcessor.State()
// public var shieldingProcessorState = ShieldingProcessor.State()
public var smartBannerState = SmartBanner.State.initial
public var syncProgressState: SyncProgress.State
public var walletConfig: WalletConfig
@ -116,7 +116,7 @@ public struct Home {
case seeAllTransactionsTapped
case sendTapped
case settingsTapped
case shieldingProcessor(ShieldingProcessor.Action)
// case shieldingProcessor(ShieldingProcessor.Action)
case showSynchronizerErrorAlert(ZcashError)
case smartBanner(SmartBanner.Action)
case synchronizerStateChanged(RedactableSynchronizerState)
@ -147,9 +147,9 @@ public struct Home {
TransactionList()
}
Scope(state: \.shieldingProcessorState, action: \.shieldingProcessor) {
ShieldingProcessor()
}
// Scope(state: \.shieldingProcessorState, action: \.shieldingProcessor) {
// ShieldingProcessor()
// }
// Scope(state: \.scanState, action: \.scan) {
// Scan()
@ -358,14 +358,15 @@ public struct Home {
case .smartBanner(.currencyConversionScreenRequested):
return .send(.currencyConversionSetupTapped)
case .smartBanner(.shieldTapped):
return .send(.shieldingProcessor(.shieldFunds))
case .smartBanner(.shieldFundsTapped):
//return .send(.shieldingProcessor(.shieldFunds))
return .none
// Shielding processor
case .shieldingProcessor(.shieldFundsFailure(let error)):
state.alert = AlertState.shieldFundsFailure(error)
return .none
// case .shieldingProcessor(.shieldFundsFailure(let error)):
// state.alert = AlertState.shieldFundsFailure(error)
// return .none
// More actions
case .coinbaseTapped:
@ -382,8 +383,8 @@ public struct Home {
case .smartBanner:
return .none
case .shieldingProcessor:
return .none
// case .shieldingProcessor:
// return .none
}
}
}
@ -391,12 +392,12 @@ public struct Home {
// MARK: Alerts
extension AlertState where Action == Home.Action {
public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
AlertState {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.title)
} message: {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.message(error.detailedMessage))
}
}
}
//extension AlertState where Action == Home.Action {
// public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
// AlertState {
// TextState(L10n.Balances.Alert.ShieldFunds.Failure.title)
// } message: {
// TextState(L10n.Balances.Alert.ShieldFunds.Failure.message(error.detailedMessage))
// }
// }
//}

View File

@ -166,7 +166,6 @@ public struct HomeView: View {
}
//.padding(.top, 12)
}
// .popover(
// isPresented:
// Binding(
@ -174,15 +173,16 @@ public struct HomeView: View {
// set: { store.send(.balancesBindingUpdated($0)) }
// )
// ) {
//
// //NavigationView {
// BalancesView(
// store:
// store.scope(
// state: \.balancesState,
// action: \.balances
// ),
// tokenName: tokenName
// )
//// BalancesView(
//// store:
//// store.scope(
//// state: \.balancesState,
//// action: \.balances
//// ),
//// tokenName: tokenName
//// )
// //}
// }
.sheet(isPresented: $store.isInAppBrowserCoinbaseOn) {

View File

@ -161,7 +161,8 @@ extension Root {
// }
case .signWithKeystoneRequested:
state.path = .signWithKeystoneCoordFlow
//state.path = .signWithKeystoneCoordFlow
state.signWithKeystoneCoordFlowBinding = true
return .send(.signWithKeystoneCoordFlow(.sendConfirmation(.resolvePCZT)))
// MARK: - Request Zec
@ -253,11 +254,11 @@ extension Root {
case .signWithKeystoneCoordFlow(.path(.element(id: _, action: .sendResultSuccess(.closeTapped)))),
.signWithKeystoneCoordFlow(.path(.element(id: _, action: .sendResultResubmission(.closeTapped)))):
state.path = nil
state.signWithKeystoneCoordFlowBinding = false
return .none
case .signWithKeystoneCoordFlow(.path(.element(id: _, action: .transactionDetails(.closeDetailTapped)))):
state.path = nil
state.signWithKeystoneCoordFlowBinding = false
return .none
// MARK: - Transactions Coord Flow

View File

@ -378,7 +378,8 @@ extension Root {
}
.cancellable(id: CancelBatteryStateId, cancelInFlight: true),
.send(.batteryStateChanged(nil)),
.send(.observeTransactions)
.send(.observeTransactions),
.send(.observeShieldingProcessor)
)
case .initialization(.loadedWalletAccounts(let walletAccounts)):

View File

@ -0,0 +1,79 @@
//
// RootTransactions.swift
// modules
//
// Created by Lukáš Korba on 29.01.2025.
//
import Combine
import ComposableArchitecture
import Foundation
import MessageUI
import ZcashLightClientKit
import Generated
import Models
import SupportDataGenerator
extension Root {
public func shieldingProcessorReduce() -> Reduce<Root.State, Root.Action> {
Reduce { state, action in
switch action {
case .observeShieldingProcessor:
return .publisher {
shieldingProcessor.observe()
.map(Action.shieldingProcessorStateChanged)
}
.cancellable(id: state.shieldingProcessorCancelId, cancelInFlight: true)
case .shieldingProcessorStateChanged(let shieldingProcessorState):
switch shieldingProcessorState {
case .failed(let error):
state.messageToBeShared = error.detailedMessage
state.alert = AlertState.shieldFundsFailure(error)
case .grpc:
state.alert = AlertState.shieldFundsGrpc()
case .proposal(let proposal):
state.signWithKeystoneCoordFlowState = .initial
state.signWithKeystoneCoordFlowState.sendConfirmationState.proposal = proposal
state.signWithKeystoneCoordFlowState.sendConfirmationState.isShielding = true
//state.homeState.balancesBinding = false
return .run { send in
try? await mainQueue.sleep(for: .seconds(0.8))
await send(.signWithKeystoneRequested)
}
case .requested:
break
case .succeeded:
break
case .unknown:
break
}
return .none
case .reportShieldingFailure:
var supportData = SupportDataGenerator.generate()
supportData.message =
"""
code: -3000
\(state.messageToBeShared)
\(supportData.message)
"""
// if MFMailComposeViewController.canSendMail() {
// state.supportData = supportData
// } else {
state.messageShareBinding = supportData.message
// }
return .none
case .shareFinished:
state.messageShareBinding = nil
state.messageToBeShared = ""
return .none
default: return .none
}
}
}
}

View File

@ -30,6 +30,8 @@ import OSStatusError
import AddressBookClient
import UserMetadataProvider
import AudioServices
import ShieldingProcessor
import SupportDataGenerator
// Screens
//import About
@ -119,12 +121,13 @@ public struct Root {
case scanCoordFlow
case sendCoordFlow
case settings
case signWithKeystoneCoordFlow
//case signWithKeystoneCoordFlow
case transactionsCoordFlow
}
public var CancelEventId = UUID()
public var CancelStateId = UUID()
public var shieldingProcessorCancelId = UUID()
// public var addressBookBinding: Bool = false
// public var addressBookContactBinding: Bool = false
@ -146,6 +149,8 @@ public struct Root {
@Shared(.appStorage(.lastAuthenticationTimestamp)) public var lastAuthenticationTimestamp: Int = 0
public var maxResetZashiAppAttempts = ResetZashiConstants.maxResetZashiAppAttempts
public var maxResetZashiSDKAttempts = ResetZashiConstants.maxResetZashiSDKAttempts
public var messageToBeShared = ""
public var messageShareBinding: String?
public var notEnoughFreeSpaceState: NotEnoughFreeSpace.State
public var onboardingState: OnboardingFlow.State
public var osStatusErrorState: OSStatusError.State
@ -155,7 +160,9 @@ public struct Root {
@Shared(.inMemory(.selectedWalletAccount)) public var selectedWalletAccount: WalletAccount? = nil
public var serverSetupState: ServerSetup.State
public var serverSetupViewBinding = false
public var signWithKeystoneCoordFlowBinding = false
public var splashAppeared = false
public var supportData: SupportData?
@Shared(.inMemory(.transactions)) public var transactions: IdentifiedArrayOf<TransactionState> = []
@Shared(.inMemory(.transactionMemos)) public var transactionMemos: [String: [String]] = [:]
@Shared(.inMemory(.walletAccounts)) public var walletAccounts: [WalletAccount] = []
@ -287,6 +294,12 @@ public struct Root {
// UserMetadata
case loadUserMetadata
case resolveMetadataEncryptionKeys
// Shielding
case observeShieldingProcessor
case reportShieldingFailure
case shareFinished
case shieldingProcessorStateChanged(ShieldingProcessorClient.State)
}
@Dependency(\.addressBook) var addressBook
@ -304,6 +317,7 @@ public struct Root {
@Dependency(\.numberFormatter) var numberFormatter
@Dependency(\.pasteboard) var pasteboard
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.shieldingProcessor) var shieldingProcessor
@Dependency(\.uriParser) var uriParser
@Dependency(\.userDefaults) var userDefaults
@Dependency(\.userMetadataProvider) var userMetadataProvider
@ -408,6 +422,8 @@ public struct Root {
userMetadataReduce()
coordinatorReduce()
shieldingProcessorReduce()
}
public var body: some Reducer<State, Action> {
@ -695,6 +711,29 @@ extension AlertState where Action == Root.Action {
TextState(L10n.Root.ServiceUnavailable.message)
}
}
public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
AlertState {
TextState(L10n.ShieldFunds.Error.title)
} actions: {
ButtonState(action: .alert(.dismiss)) {
TextState(L10n.General.ok)
}
ButtonState(action: .reportShieldingFailure) {
TextState(L10n.Send.report)
}
} message: {
TextState(L10n.ShieldFunds.Error.Failure.message(error.detailedMessage))
}
}
public static func shieldFundsGrpc() -> AlertState {
AlertState {
TextState(L10n.ShieldFunds.Error.title)
} message: {
TextState(L10n.ShieldFunds.Error.Gprc.message)
}
}
}
extension ConfirmationDialogState where Action == Root.Action.ConfirmationDialog {

View File

@ -211,7 +211,7 @@ private extension RootView {
action: \.currencyConversionSetup)
)
}
.navigationLinkEmpty(isActive: store.bindingFor(.signWithKeystoneCoordFlow)) {
.popover(isPresented: $store.signWithKeystoneCoordFlowBinding) {
SignWithKeystoneCoordFlowView(
store:
store.scope(
@ -220,12 +220,22 @@ private extension RootView {
tokenName: tokenName
)
}
// .navigationLinkEmpty(isActive: store.bindingFor(.signWithKeystoneCoordFlow)) {
// SignWithKeystoneCoordFlowView(
// store:
// store.scope(
// state: \.signWithKeystoneCoordFlowState,
// action: \.signWithKeystoneCoordFlow),
// tokenName: tokenName
// )
// }
}
.navigationViewStyle(.stack)
.overlayedWithSplash(store.splashAppeared) {
store.send(.splashRemovalRequested)
}
// } destination: { store in
// switch store.case {
// case let .about(store):
@ -339,6 +349,18 @@ private extension RootView {
)
)
}
// if let supportData = store.supportData {
// UIMailDialogView(
// supportData: supportData,
// completion: {
// store.send(.sendSupportMailFinished)
// }
// )
// // UIMailDialogView only wraps MFMailComposeViewController presentation
// // so frame is set to 0 to not break SwiftUIs layout
// .frame(width: 0, height: 0)
// }
}
.onOpenURL(perform: { store.goToDeeplink($0) })
.alert(
@ -373,6 +395,7 @@ private extension RootView {
}
shareLogsView(store)
shareView()
}
.toast()
}
@ -393,6 +416,25 @@ private extension RootView {
EmptyView()
}
}
@ViewBuilder func shareView() -> some View {
if let message = store.messageShareBinding {
UIShareDialogView(activityItems: [
ShareableMessage(
title: L10n.SendFeedback.Share.title,
message: message,
desc: L10n.SendFeedback.Share.desc
),
]) {
store.send(.shareFinished)
}
// UIShareDialogView only wraps UIActivityViewController presentation
// so frame is set to 0 to not break SwiftUIs layout
.frame(width: 0, height: 0)
} else {
EmptyView()
}
}
@ViewBuilder func debugView(_ store: StoreOf<Root>) -> some View {
VStack(alignment: .leading) {

View File

@ -469,13 +469,13 @@ public struct SendForm {
state.balancesBinding = false
return .none
case .balances(.shieldFunds):
case .balances(.shieldFundsTapped):
state.balancesBinding = false
return .none
case .balances(.shieldFundsFailure(let error)):
state.alert = AlertState.shieldFundsFailure(error)
return .none
// case .balances(.shieldFundsFailure(let error)):
// state.alert = AlertState.shieldFundsFailure(error)
// return .none
case .balances:
return .none
@ -555,11 +555,11 @@ extension AlertState where Action == SendForm.Action {
}
}
public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
AlertState {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.title)
} message: {
TextState(L10n.Balances.Alert.ShieldFunds.Failure.message(error.detailedMessage))
}
}
// public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
// AlertState {
// TextState(L10n.Balances.Alert.ShieldFunds.Failure.title)
// } message: {
// TextState(L10n.Balances.Alert.ShieldFunds.Failure.message(error.detailedMessage))
// }
// }
}

View File

@ -159,8 +159,13 @@ extension SmartBannerView {
.padding(.trailing, 12)
VStack(alignment: .leading, spacing: 2) {
Text(L10n.SmartBanner.Content.Shield.title)
.zFont(.medium, size: 14, color: titleStyle())
ViewThatFits {
Text(L10n.SmartBanner.Content.Shield.title)
.zFont(.medium, size: 14, color: titleStyle())
Text(L10n.SmartBanner.Content.Shield.titleShorter)
.zFont(.medium, size: 14, color: titleStyle())
}
ZatoshiText(store.transparentBalance, .expanded, store.tokenName)
.zFont(.medium, size: 12, color: infoStyle())
@ -173,8 +178,9 @@ extension SmartBannerView {
type: .ghost,
infinityWidth: false
) {
store.send(.shieldTapped)
store.send(.shieldFundsTapped)
}
.disabled(store.isShielding)
}
}

View File

@ -278,8 +278,9 @@ extension SmartBannerView {
.padding(.bottom, 12)
ZashiButton(L10n.SmartBanner.Content.Shield.button) {
store.send(.shieldTapped)
store.send(.shieldFundsTapped)
}
.disabled(store.isShielding)
.padding(.bottom, 32)
}
}

View File

@ -20,6 +20,7 @@ import NetworkMonitor
import ZcashSDKEnvironment
import SupportDataGenerator
import MessageUI
import ShieldingProcessor
@Reducer
public struct SmartBanner {
@ -50,6 +51,7 @@ public struct SmartBanner {
public var CancelNetworkMonitorId = UUID()
public var CancelStateStreamId = UUID()
public var CancelShieldingProcessorId = UUID()
public var delay = 1.5
public var isOpen = false
@ -118,6 +120,7 @@ public struct SmartBanner {
case reportPrepared
case reportTapped
case shareFinished
case shieldingProcessorStateChanged(ShieldingProcessorClient.State)
case smartBannerContentTapped
case synchronizerStateChanged(RedactableSynchronizerState)
case transparentBalanceUpdated(Zatoshi)
@ -128,13 +131,14 @@ public struct SmartBanner {
case autoShieldingTapped
case currencyConversionScreenRequested
case currencyConversionTapped
case shieldTapped
case shieldFundsTapped
case walletBackupTapped
}
@Dependency(\.mainQueue) var mainQueue
@Dependency(\.networkMonitor) var networkMonitor
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
@Dependency(\.shieldingProcessor) var shieldingProcessor
@Dependency(\.userStoredPreferences) var userStoredPreferences
@Dependency(\.walletStorage) var walletStorage
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
@ -172,18 +176,42 @@ public struct SmartBanner {
.map { $0.redacted }
.map(Action.synchronizerStateChanged)
}
.cancellable(id: state.CancelStateStreamId, cancelInFlight: true)
.cancellable(id: state.CancelStateStreamId, cancelInFlight: true),
.publisher {
shieldingProcessor.observe()
.map(Action.shieldingProcessorStateChanged)
}
.cancellable(id: state.CancelShieldingProcessorId, cancelInFlight: true)
)
case .onDisappear:
return .merge(
.cancel(id: state.CancelNetworkMonitorId),
.cancel(id: state.CancelStateStreamId)
.cancel(id: state.CancelStateStreamId),
.cancel(id: state.CancelShieldingProcessorId)
)
case .binding:
return .none
case .shieldingProcessorStateChanged(let shieldingProcessorState):
state.isShielding = shieldingProcessorState == .requested
if state.isOpen || state.isSmartBannerSheetPresented {
var hideEverything = false
if case .proposal = shieldingProcessorState {
hideEverything = true
} else if shieldingProcessorState == .succeeded {
hideEverything = true
}
if hideEverything {
return .merge(
.send(.closeAndCleanupBanner),
.send(.closeSheetTapped)
)
}
}
return .none
case .walletAccountChanged:
state.remindMeShieldedPhaseCounter = 0
return .run { send in
@ -259,13 +287,13 @@ public struct SmartBanner {
} else if priority == .priority7 {
// shielding = priority7
if let account = state.selectedWalletAccount {
if var shieldingReminder = walletStorage.exportShieldingReminder(account.account) {
if var shieldingReminder = walletStorage.exportShieldingReminder(account.vendor.name()) {
shieldingReminder.occurence += 1
shieldingReminder.timestamp = now
try? walletStorage.importShieldingReminder(shieldingReminder, account.account)
try? walletStorage.importShieldingReminder(shieldingReminder, account.vendor.name())
} else {
let shieldingReminder = ReminedMeTimestamp(timestamp: now, occurence: 1)
try? walletStorage.importShieldingReminder(shieldingReminder, account.account)
try? walletStorage.importShieldingReminder(shieldingReminder, account.vendor.name())
}
}
}
@ -301,6 +329,19 @@ public struct SmartBanner {
}
default: break
}
if state.priorityContent == .priority7 {
if let account = state.selectedWalletAccount, let accountBalance = latestState.data.accountsBalances[account.id] {
if accountBalance.unshielded.amount > 0 {
return .send(.transparentBalanceUpdated(accountBalance.unshielded))
} else {
return .merge(
.send(.closeAndCleanupBanner),
.send(.closeSheetTapped)
)
}
}
}
}
return .none
@ -361,7 +402,7 @@ public struct SmartBanner {
guard let account = state.selectedWalletAccount else {
return .none
}
if let shieldedReminder = walletStorage.exportShieldingReminder(account.account) {
if let shieldedReminder = walletStorage.exportShieldingReminder(account.vendor.name()) {
state.remindMeShieldedPhaseCounter = shieldedReminder.occurence
}
return .run { [remindMeShieldedPhaseCounter = state.remindMeShieldedPhaseCounter] send in
@ -369,7 +410,7 @@ public struct SmartBanner {
accountBalance.unshielded >= zcashSDKEnvironment.shieldingThreshold {
await send(.transparentBalanceUpdated(accountBalance.unshielded))
if let shieldedReminder = walletStorage.exportShieldingReminder(account.account) {
if let shieldedReminder = walletStorage.exportShieldingReminder(account.vendor.name()) {
let now = Date().timeIntervalSince1970
if (remindMeShieldedPhaseCounter == 1 && shieldedReminder.timestamp + Constants.remindMe2days < now)
@ -463,8 +504,9 @@ public struct SmartBanner {
case .currencyConversionTapped:
return .send(.smartBannerContentTapped)
case .shieldTapped:
case .shieldFundsTapped:
state.isSmartBannerSheetPresented = false
shieldingProcessor.shieldFunds()
return .send(.closeAndCleanupBanner)
case .walletBackupTapped:

View File

@ -99,7 +99,6 @@ public struct SyncProgress {
state.synchronizerStatusSnapshot = snapshot
if case let .syncing(syncProgress, recoveryProgress) = snapshot.syncStatus {
print("__LD \(syncProgress) \(recoveryProgress)")
state.lastKnownSyncPercentage = syncProgress
}

View File

@ -365,7 +365,9 @@ extension TransactionDetailsView {
}
detailView(
title: L10n.TransactionHistory.completed,
title: store.transaction.listDateYearString == nil
? L10n.TransactionHistory.status
: L10n.TransactionHistory.completed,
value: store.transaction.listDateYearString ?? L10n.TransactionHistory.pending,
rowAppereance: store.annotation.isEmpty ? .bottom : .middle
)
@ -471,7 +473,9 @@ extension TransactionDetailsView {
}
detailView(
title: L10n.TransactionHistory.completed,
title: store.transaction.listDateYearString == nil
? L10n.TransactionHistory.status
: L10n.TransactionHistory.completed,
value: store.transaction.listDateYearString ?? L10n.TransactionHistory.pending,
rowAppereance: store.annotation.isEmpty ? .bottom : .middle
)

View File

@ -184,11 +184,14 @@ public struct WalletBalances {
state.shieldedWithPendingBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)
state.transparentBalance = accountBalance?.unshielded ?? .zero
state.totalBalance = state.shieldedWithPendingBalance + state.transparentBalance
let everythingCondition = state.shieldedBalance == state.totalBalance
|| (state.transparentBalance < zcashSDKEnvironment.shieldingThreshold && state.shieldedBalance == state.totalBalance - state.transparentBalance)
// spendability
if state.isProcessingZeroAvailableBalance {
state.spendability = .nothing
} else if state.shieldedBalance == state.totalBalance {
} else if everythingCondition {
state.spendability = .everything
} else {
state.spendability = .something

View File

@ -332,7 +332,7 @@ public enum Design {
public extension Design {
func color(_ colorScheme: ColorScheme) -> Color {
switch self {
case .screenBackground: return Design.col(Asset.Colors.ZDesign.Base.bone.color, Asset.Colors.ZDesign.Base.obsidian.color, colorScheme)
case .screenBackground: return Design.col(Asset.Colors.ZDesign.Base.bone.color, Asset.Colors.ZDesign.Base.midnight.color, colorScheme)
}
}
}

View File

@ -131,8 +131,10 @@ public enum L10n {
public enum Balances {
/// Dismiss
public static let dismiss = L10n.tr("Localizable", "balances.dismiss", fallback: "Dismiss")
/// Your last transaction is getting mined and confirmed.
public static let infoPending1 = L10n.tr("Localizable", "balances.infoPending1", fallback: "Your last transaction is getting mined and confirmed.")
/// All your funds are shielded and spendable.
public static let everythingDone = L10n.tr("Localizable", "balances.everythingDone", fallback: "All your funds are shielded and spendable.")
/// Pending transactions are getting mined and confirmed.
public static let infoPending1 = L10n.tr("Localizable", "balances.infoPending1", fallback: "Pending transactions are getting mined and confirmed.")
/// Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)
public static let infoPending2 = L10n.tr("Localizable", "balances.infoPending2", fallback: "Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)")
/// Pending
@ -157,18 +159,6 @@ public enum L10n {
public static let syncingError = L10n.tr("Localizable", "balances.syncingError", fallback: "Zashi encountered an error while syncing, attempting to resolve...")
/// Transparent balance
public static let transparentBalance = L10n.tr("Localizable", "balances.transparentBalance", fallback: "Transparent balance")
public enum Alert {
public enum ShieldFunds {
public enum Failure {
/// Error: %@
public static func message(_ p1: Any) -> String {
return L10n.tr("Localizable", "balances.alert.shieldFunds.failure.message", String(describing: p1), fallback: "Error: %@")
}
/// Failed to shield funds
public static let title = L10n.tr("Localizable", "balances.alert.shieldFunds.failure.title", fallback: "Failed to shield funds")
}
}
}
public enum HintBox {
/// I got it!
public static let dismiss = L10n.tr("Localizable", "balances.hintBox.dismiss", fallback: "I got it!")
@ -1107,6 +1097,24 @@ public enum L10n {
}
}
}
public enum ShieldFunds {
public enum Error {
/// Shielding Error
public static let title = L10n.tr("Localizable", "shieldFunds.error.title", fallback: "Shielding Error")
public enum Failure {
/// An error happened during the last shielding transaction. You can try again later or report this issue if the problem persists.
///
/// %@
public static func message(_ p1: Any) -> String {
return L10n.tr("Localizable", "shieldFunds.error.failure.message", String(describing: p1), fallback: "An error happened during the last shielding transaction. You can try again later or report this issue if the problem persists. \n\n %@")
}
}
public enum Gprc {
/// An error happened during the last shielding transaction. We will try to resubmit the transaction later. If it fails, you may need to repeat the shielding attempt at a later time.
public static let message = L10n.tr("Localizable", "shieldFunds.error.gprc.message", fallback: "An error happened during the last shielding transaction. We will try to resubmit the transaction later. If it fails, you may need to repeat the shielding attempt at a later time.")
}
}
}
public enum SmartBanner {
public enum Content {
public enum AutoShielding {
@ -1152,6 +1160,8 @@ public enum L10n {
public static let button = L10n.tr("Localizable", "smartBanner.content.shield.button", fallback: "Shield")
/// Transparent Balance Detected
public static let title = L10n.tr("Localizable", "smartBanner.content.shield.title", fallback: "Transparent Balance Detected")
/// Transparent Balance
public static let titleShorter = L10n.tr("Localizable", "smartBanner.content.shield.titleShorter", fallback: "Transparent Balance")
}
public enum Sync {
/// Your wallet is getting updated
@ -1402,6 +1412,8 @@ public enum L10n {
public static let sendAgain = L10n.tr("Localizable", "transactionHistory.sendAgain", fallback: "Send again")
/// Sent to
public static let sentTo = L10n.tr("Localizable", "transactionHistory.sentTo", fallback: "Sent to")
/// Status
public static let status = L10n.tr("Localizable", "transactionHistory.status", fallback: "Status")
/// %@...
public static func threeDots(_ p1: Any) -> String {
return L10n.tr("Localizable", "transactionHistory.threeDots", String(describing: p1), fallback: "%@...")

View File

@ -123,8 +123,6 @@
"balances.hintBox.message" = "Zashi uses the latest network upgrade and does not support sending transparent (unshielded) ZEC. Use the Shield and Consolidate button to shield your funds, which will add to your available balance and make your ZEC spendable.";
"balances.hintBox.dismiss" = "I got it!";
"balances.restoringWalletWarning" = "The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.";
"balances.alert.shieldFunds.failure.title" = "Failed to shield funds";
"balances.alert.shieldFunds.failure.message" = "Error: %@";
// MARK: - Scan
"scan.invalidQR" = "This QR code doesn't hold a valid Zcash address.";
@ -627,6 +625,7 @@
"smartBanner.content.backup.button" = "Start";
"smartBanner.content.shield.title" = "Transparent Balance Detected";
"smartBanner.content.shield.titleShorter" = "Transparent Balance";
"smartBanner.content.shield.button" = "Shield";
"smartBanner.content.currencyConversion.title" = "Currency Conversion";
@ -644,7 +643,14 @@
"balances.dismiss" = "Dismiss";
"balances.spendableBalance.title" = "Spendable Balance";
"balances.spendableBalance" = "Shielded (Spendable)";
"balances.infoPending1" = "Your last transaction is getting mined and confirmed.";
"balances.infoPending1" = "Pending transactions are getting mined and confirmed.";
"balances.infoPending2" = "Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)";
"balances.shieldInfo1" = "Shield your transparent balance to make your funds spendable and private.";
"balances.shieldInfo2" = "This will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)";
"balances.everythingDone" = "All your funds are shielded and spendable.";
"shieldFunds.error.title" = "Shielding Error";
"shieldFunds.error.failure.message" = "An error happened during the last shielding transaction. You can try again later or report this issue if the problem persists. \n\n %@";
"shieldFunds.error.gprc.message" = "An error happened during the last shielding transaction. We will try to resubmit the transaction later. If it fails, you may need to repeat the shielding attempt at a later time.";
"transactionHistory.status" = "Status";

View File

@ -33,7 +33,7 @@ public struct SyncStatusSnapshot: Equatable {
case .stopped:
return SyncStatusSnapshot(state, L10n.Sync.Message.stopped)
case let .syncing(syncProgress, recoveryProgress):
case let .syncing(syncProgress, _):
return SyncStatusSnapshot(state, L10n.Sync.Message.sync(String(format: "%0.1f", syncProgress * 100)))
}
}

View File

@ -2367,7 +2367,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
@ -2398,7 +2398,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_BITCODE = NO;
@ -2428,7 +2428,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_BITCODE = NO;

View File

@ -123,8 +123,6 @@
"balances.hintBox.message" = "Zashi uses the latest network upgrade and does not support sending transparent (unshielded) ZEC. Use the Shield and Consolidate button to shield your funds, which will add to your available balance and make your ZEC spendable.";
"balances.hintBox.dismiss" = "I got it!";
"balances.restoringWalletWarning" = "The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.";
"balances.alert.shieldFunds.failure.title" = "Failed to shield funds";
"balances.alert.shieldFunds.failure.message" = "Error: %@";
// MARK: - Scan
"scan.invalidQR" = "This QR code doesn't hold a valid Zcash address.";
@ -627,6 +625,7 @@
"smartBanner.content.backup.button" = "Start";
"smartBanner.content.shield.title" = "Transparent Balance Detected";
"smartBanner.content.shield.titleShorter" = "Transparent Balance";
"smartBanner.content.shield.button" = "Shield";
"smartBanner.content.currencyConversion.title" = "Currency Conversion";
@ -644,7 +643,14 @@
"balances.dismiss" = "Dismiss";
"balances.spendableBalance.title" = "Spendable Balance";
"balances.spendableBalance" = "Shielded (Spendable)";
"balances.infoPending1" = "Your last transaction is getting mined and confirmed.";
"balances.infoPending1" = "Pending transactions are getting mined and confirmed.";
"balances.infoPending2" = "Shield your transparent ZEC to make it spendable and private. Shielding transparent funds will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)";
"balances.shieldInfo1" = "Shield your transparent balance to make your funds spendable and private.";
"balances.shieldInfo2" = "This will create a shielding in-wallet transaction, consolidating your transparent and shielded funds. (Typical fee: .001 ZEC)";
"balances.everythingDone" = "All your funds are shielded and spendable.";
"shieldFunds.error.title" = "Shielding Error";
"shieldFunds.error.failure.message" = "An error happened during the last shielding transaction. You can try again later or report this issue if the problem persists. \n\n %@";
"shieldFunds.error.gprc.message" = "An error happened during the last shielding transaction. We will try to resubmit the transaction later. If it fails, you may need to repeat the shielding attempt at a later time.";
"transactionHistory.status" = "Status";