Shielding refactored to be single source of truth
This commit is contained in:
parent
067f1cf22e
commit
1f94c1480e
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -97,8 +97,6 @@ extension TaxExporterClient: DependencyKey {
|
|||
} catch {
|
||||
throw error
|
||||
}
|
||||
|
||||
return .emptyURL
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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))
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: "%@...")
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue