The broadcasting technology done, the views can subscribe to the restoring wallet state dependency The restoring wallet badge implemented + handling of different backgrounds underneath it Progress of the sync implemented on the Account screen SyncProgress feature implemented, this new component is used 2 times already in Zashi so it's been separated into its own module (used in balances screen and at home screen when restoring the wallet) Unit tests fixed + implemented new ones for the restore wallet flag
This commit is contained in:
parent
1d60dd3275
commit
b801ac72d7
|
@ -9,6 +9,7 @@ directly impact users rather than highlighting other crucial architectural updat
|
|||
### Added
|
||||
- The exported logs also show the shielded balances (total & verified) for every finished sync metric.
|
||||
- Synchronization in the background. When the iPhone is connected to the power and wifi, the background task will try to synchronize randomly between 3-4am.
|
||||
- Restore of the wallet is now indiated in the UI throughout the application.
|
||||
|
||||
### Fixed
|
||||
- The export buttons are disabled when exporting of the private data is in progress.
|
||||
|
|
|
@ -46,7 +46,9 @@ let package = Package(
|
|||
.library(name: "SendFlow", targets: ["SendFlow"]),
|
||||
.library(name: "Settings", targets: ["Settings"]),
|
||||
.library(name: "SupportDataGenerator", targets: ["SupportDataGenerator"]),
|
||||
.library(name: "SyncProgress", targets: ["SyncProgress"]),
|
||||
.library(name: "ReadTransactionsStorage", targets: ["ReadTransactionsStorage"]),
|
||||
.library(name: "RestoreWalletStorage", targets: ["RestoreWalletStorage"]),
|
||||
.library(name: "Tabs", targets: ["Tabs"]),
|
||||
.library(name: "TransactionList", targets: ["TransactionList"]),
|
||||
.library(name: "UIComponents", targets: ["UIComponents"]),
|
||||
|
@ -103,7 +105,9 @@ let package = Package(
|
|||
"MnemonicClient",
|
||||
"Models",
|
||||
"NumberFormatter",
|
||||
"RestoreWalletStorage",
|
||||
"SDKSynchronizer",
|
||||
"SyncProgress",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
"WalletStorage",
|
||||
|
@ -215,10 +219,12 @@ let package = Package(
|
|||
"DiskSpaceChecker",
|
||||
"Generated",
|
||||
"Models",
|
||||
"RestoreWalletStorage",
|
||||
"ReviewRequest",
|
||||
"Scan",
|
||||
"Settings",
|
||||
"SDKSynchronizer",
|
||||
"SyncProgress",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
"TransactionList",
|
||||
|
@ -311,6 +317,7 @@ let package = Package(
|
|||
"DatabaseFiles",
|
||||
"Generated",
|
||||
"Models",
|
||||
"RestoreWalletStorage",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
|
||||
|
@ -333,6 +340,13 @@ let package = Package(
|
|||
],
|
||||
path: "Sources/Features/RecoveryPhraseDisplay"
|
||||
),
|
||||
.target(
|
||||
name: "RestoreWalletStorage",
|
||||
dependencies: [
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
|
||||
],
|
||||
path: "Sources/Dependencies/RestoreWalletStorage"
|
||||
),
|
||||
.target(
|
||||
name: "ReviewRequest",
|
||||
dependencies: [
|
||||
|
@ -358,6 +372,7 @@ let package = Package(
|
|||
"OnboardingFlow",
|
||||
"ReadTransactionsStorage",
|
||||
"RecoveryPhraseDisplay",
|
||||
"RestoreWalletStorage",
|
||||
"Sandbox",
|
||||
"SDKSynchronizer",
|
||||
"Tabs",
|
||||
|
@ -463,6 +478,7 @@ let package = Package(
|
|||
"Models",
|
||||
"PrivateDataConsent",
|
||||
"RecoveryPhraseDisplay",
|
||||
"RestoreWalletStorage",
|
||||
"SDKSynchronizer",
|
||||
"SupportDataGenerator",
|
||||
"UIComponents",
|
||||
|
@ -481,6 +497,18 @@ let package = Package(
|
|||
],
|
||||
path: "Sources/Dependencies/SupportDataGenerator"
|
||||
),
|
||||
.target(
|
||||
name: "SyncProgress",
|
||||
dependencies: [
|
||||
"Generated",
|
||||
"Models",
|
||||
"SDKSynchronizer",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
|
||||
],
|
||||
path: "Sources/Features/SyncProgress"
|
||||
),
|
||||
.target(
|
||||
name: "ReadTransactionsStorage",
|
||||
dependencies: [
|
||||
|
@ -496,8 +524,10 @@ let package = Package(
|
|||
"BalanceBreakdown",
|
||||
"Generated",
|
||||
"Home",
|
||||
"RestoreWalletStorage",
|
||||
"SendFlow",
|
||||
"Settings",
|
||||
"UIComponents",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
|
||||
],
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// RestoreWalletStorageInterface.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 19.12.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
import Combine
|
||||
|
||||
extension DependencyValues {
|
||||
public var restoreWalletStorage: RestoreWalletStorageClient {
|
||||
get { self[RestoreWalletStorageClient.self] }
|
||||
set { self[RestoreWalletStorageClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
public struct RestoreWalletStorageClient {
|
||||
public var value: @Sendable () async -> AsyncStream<Bool>
|
||||
public var updateValue: @Sendable (Bool) async -> Void
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// RestoreWalletStorageLiveKey.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 19.12.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
import Combine
|
||||
|
||||
extension RestoreWalletStorageClient: DependencyKey {
|
||||
public static var liveValue: Self {
|
||||
let storage = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
return .init(
|
||||
value: {
|
||||
AsyncStream { continuation in
|
||||
let cancellable = storage.sink {
|
||||
continuation.yield($0)
|
||||
}
|
||||
|
||||
continuation.onTermination = { _ in
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
},
|
||||
updateValue: { storage.value = $0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension AsyncStream<Bool> {
|
||||
static let placeholder = AsyncStream { continuation in continuation.finish() }
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// RestoreWalletStorageTestKey.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 19.12.2023.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
import XCTestDynamicOverlay
|
||||
import Combine
|
||||
|
||||
extension RestoreWalletStorageClient: TestDependencyKey {
|
||||
public static let testValue = Self(
|
||||
value: XCTUnimplemented("\(Self.self).value", placeholder: .placeholder),
|
||||
updateValue: XCTUnimplemented("\(Self.self).updateValue")
|
||||
)
|
||||
}
|
||||
|
||||
extension RestoreWalletStorageClient {
|
||||
public static let noOp = Self(
|
||||
value: { .placeholder },
|
||||
updateValue: { _ in }
|
||||
)
|
||||
}
|
|
@ -16,6 +16,8 @@ import Generated
|
|||
import WalletStorage
|
||||
import SDKSynchronizer
|
||||
import Models
|
||||
import SyncProgress
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias BalanceBreakdownStore = Store<BalanceBreakdownReducer.State, BalanceBreakdownReducer.Action>
|
||||
public typealias BalanceBreakdownViewStore = ViewStore<BalanceBreakdownReducer.State, BalanceBreakdownReducer.Action>
|
||||
|
@ -28,12 +30,11 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
@PresentationState public var alert: AlertState<Action>?
|
||||
public var autoShieldingThreshold: Zatoshi
|
||||
public var changePending: Zatoshi
|
||||
public var isRestoringWallet = false
|
||||
public var isShieldingFunds: Bool
|
||||
public var lastKnownSyncPercentage: Float = 0
|
||||
public var pendingTransactions: Zatoshi
|
||||
public var shieldedBalance: Balance
|
||||
public var synchronizerStatusSnapshot: SyncStatusSnapshot
|
||||
public var syncStatusMessage = ""
|
||||
public var syncProgressState: SyncProgressReducer.State
|
||||
public var transparentBalance: Balance
|
||||
|
||||
public var totalBalance: Zatoshi {
|
||||
|
@ -48,37 +49,23 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
isShieldingFunds || !isShieldableBalanceAvailable
|
||||
}
|
||||
|
||||
public var isSyncing: Bool {
|
||||
synchronizerStatusSnapshot.syncStatus.isSyncing
|
||||
}
|
||||
|
||||
public var syncingPercentage: Float {
|
||||
if case .syncing(let progress) = synchronizerStatusSnapshot.syncStatus {
|
||||
return progress * 0.999
|
||||
}
|
||||
|
||||
return lastKnownSyncPercentage
|
||||
}
|
||||
|
||||
public init(
|
||||
autoShieldingThreshold: Zatoshi,
|
||||
changePending: Zatoshi,
|
||||
isRestoringWallet: Bool = false,
|
||||
isShieldingFunds: Bool,
|
||||
lastKnownSyncPercentage: Float = 0,
|
||||
pendingTransactions: Zatoshi,
|
||||
shieldedBalance: Balance,
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot,
|
||||
syncStatusMessage: String = "",
|
||||
syncProgressState: SyncProgressReducer.State,
|
||||
transparentBalance: Balance
|
||||
) {
|
||||
self.autoShieldingThreshold = autoShieldingThreshold
|
||||
self.changePending = changePending
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.isShieldingFunds = isShieldingFunds
|
||||
self.lastKnownSyncPercentage = lastKnownSyncPercentage
|
||||
self.pendingTransactions = pendingTransactions
|
||||
self.shieldedBalance = shieldedBalance
|
||||
self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
|
||||
self.syncStatusMessage = syncStatusMessage
|
||||
self.syncProgressState = syncProgressState
|
||||
self.transparentBalance = transparentBalance
|
||||
}
|
||||
}
|
||||
|
@ -87,16 +74,20 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
case alert(PresentationAction<Action>)
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case restoreWalletTask
|
||||
case restoreWalletValue(Bool)
|
||||
case shieldFunds
|
||||
case shieldFundsSuccess(TransactionState)
|
||||
case shieldFundsFailure(ZcashError)
|
||||
case synchronizerStateChanged(SynchronizerState)
|
||||
case syncProgress(SyncProgressReducer.Action)
|
||||
}
|
||||
|
||||
@Dependency(\.derivationTool) var derivationTool
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.mnemonic) var mnemonic
|
||||
@Dependency(\.numberFormatter) var numberFormatter
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.walletStorage) var walletStorage
|
||||
|
||||
|
@ -105,6 +96,10 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
}
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
Scope(state: \.syncProgressState, action: /Action.syncProgress) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .alert(.presented(let action)):
|
||||
|
@ -121,13 +116,24 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
return .publisher {
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map(BalanceBreakdownReducer.Action.synchronizerStateChanged)
|
||||
.map(Action.synchronizerStateChanged)
|
||||
}
|
||||
.cancellable(id: CancelId.timer, cancelInFlight: true)
|
||||
|
||||
case .onDisappear:
|
||||
return .cancel(id: CancelId.timer)
|
||||
|
||||
case .restoreWalletTask:
|
||||
return .run { send in
|
||||
for await value in await restoreWalletStorage.value() {
|
||||
await send(.restoreWalletValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
case .restoreWalletValue(let value):
|
||||
state.isRestoringWallet = value
|
||||
return .none
|
||||
|
||||
case .shieldFunds:
|
||||
state.isShieldingFunds = true
|
||||
return .run { [state] send in
|
||||
|
@ -157,29 +163,9 @@ public struct BalanceBreakdownReducer: Reducer {
|
|||
case .synchronizerStateChanged(let latestState):
|
||||
state.shieldedBalance = latestState.shieldedBalance.redacted
|
||||
state.transparentBalance = latestState.transparentBalance.redacted
|
||||
return .none
|
||||
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.syncStatus)
|
||||
if snapshot.syncStatus != state.synchronizerStatusSnapshot.syncStatus {
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
|
||||
if case .syncing(let progress) = snapshot.syncStatus {
|
||||
state.lastKnownSyncPercentage = progress
|
||||
}
|
||||
|
||||
// TODO: [#931] The statuses of the sync process
|
||||
// https://github.com/Electric-Coin-Company/zashi-ios/issues/931
|
||||
// until then, this is temporary quick solution
|
||||
switch snapshot.syncStatus {
|
||||
case .syncing:
|
||||
state.syncStatusMessage = L10n.Balances.syncing
|
||||
case .upToDate:
|
||||
state.lastKnownSyncPercentage = 1
|
||||
state.syncStatusMessage = L10n.Balances.synced
|
||||
case .error, .stopped, .unprepared:
|
||||
state.syncStatusMessage = snapshot.message
|
||||
}
|
||||
}
|
||||
|
||||
case .syncProgress:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +193,7 @@ extension BalanceBreakdownReducer.State {
|
|||
isShieldingFunds: false,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .placeholder,
|
||||
syncProgressState: .initial,
|
||||
transparentBalance: Balance.zero
|
||||
)
|
||||
|
||||
|
@ -217,7 +203,7 @@ extension BalanceBreakdownReducer.State {
|
|||
isShieldingFunds: false,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
syncProgressState: .initial,
|
||||
transparentBalance: Balance.zero
|
||||
)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import UIComponents
|
|||
import Utils
|
||||
import Models
|
||||
import BalanceFormatter
|
||||
import SyncProgress
|
||||
|
||||
public struct BalanceBreakdownView: View {
|
||||
let store: BalanceBreakdownStore
|
||||
|
@ -24,30 +25,43 @@ public struct BalanceBreakdownView: View {
|
|||
}
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
ScrollView {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
ScrollView {
|
||||
BalanceWithIconView(balance: viewStore.shieldedBalance.data.total)
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
AvailableBalanceView(
|
||||
balance: viewStore.shieldedBalance.data.verified,
|
||||
tokenName: tokenName
|
||||
)
|
||||
|
||||
Asset.Colors.primary.color
|
||||
.frame(height: 1)
|
||||
.padding(EdgeInsets(top: 30, leading: 30, bottom: 10, trailing: 30))
|
||||
|
||||
balancesBlock(viewStore)
|
||||
|
||||
transparentBlock(viewStore)
|
||||
|
||||
progressBlock(viewStore)
|
||||
BalanceWithIconView(balance: viewStore.shieldedBalance.data.total)
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 5)
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear { viewStore.send(.onDisappear) }
|
||||
|
||||
AvailableBalanceView(
|
||||
balance: viewStore.shieldedBalance.data.verified,
|
||||
tokenName: tokenName
|
||||
)
|
||||
|
||||
Asset.Colors.primary.color
|
||||
.frame(height: 1)
|
||||
.padding(EdgeInsets(top: 30, leading: 30, bottom: 10, trailing: 30))
|
||||
|
||||
balancesBlock(viewStore)
|
||||
|
||||
transparentBlock(viewStore)
|
||||
|
||||
if viewStore.isRestoringWallet {
|
||||
Text(L10n.Balances.restoringWalletWarning)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 10))
|
||||
.foregroundColor(Asset.Colors.error.color)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear { viewStore.send(.onDisappear) }
|
||||
|
||||
SyncProgressView(
|
||||
store: store.scope(
|
||||
state: \.syncProgressState,
|
||||
action: BalanceBreakdownReducer.Action.syncProgress
|
||||
)
|
||||
)
|
||||
.padding(.top, viewStore.isRestoringWallet ? 0 : 40)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
|
@ -56,6 +70,7 @@ public struct BalanceBreakdownView: View {
|
|||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
))
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,29 +227,6 @@ extension BalanceBreakdownView {
|
|||
// }
|
||||
}
|
||||
|
||||
@ViewBuilder func progressBlock(_ viewStore: BalanceBreakdownViewStore) -> some View {
|
||||
VStack(spacing: 5) {
|
||||
HStack {
|
||||
Text(viewStore.syncStatusMessage)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 10))
|
||||
|
||||
if viewStore.isSyncing {
|
||||
progressViewLooping()
|
||||
}
|
||||
}
|
||||
.frame(height: 16)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
Text(String(format: "%0.1f%%", viewStore.syncingPercentage * 100))
|
||||
.font(.custom(FontFamily.Inter.black.name, size: 10))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
|
||||
ProgressView(value: viewStore.syncingPercentage, total: 1.0)
|
||||
.progressViewStyle(ZashiSyncingProgressStyle())
|
||||
}
|
||||
.padding(.top, 40)
|
||||
}
|
||||
|
||||
@ViewBuilder func progressViewLooping() -> some View {
|
||||
ProgressView()
|
||||
.scaleEffect(0.7)
|
||||
|
@ -254,8 +246,11 @@ extension BalanceBreakdownView {
|
|||
isShieldingFunds: true,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance(WalletBalance(verified: Zatoshi(25_234_778), total: Zatoshi(35_814_169))),
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
|
||||
syncStatusMessage: "Syncing",
|
||||
syncProgressState: .init(
|
||||
lastKnownSyncPercentage: 0.43,
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
|
||||
syncStatusMessage: "Syncing"
|
||||
),
|
||||
transparentBalance: Balance(WalletBalance(verified: Zatoshi(25_234_778), total: Zatoshi(35_814_169)))
|
||||
)
|
||||
) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import Generated
|
|||
import ReviewRequest
|
||||
import TransactionList
|
||||
import Scan
|
||||
import SyncProgress
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias HomeStore = Store<HomeReducer.State, HomeReducer.Action>
|
||||
public typealias HomeViewStore = ViewStore<HomeReducer.State, HomeReducer.Action>
|
||||
|
@ -28,10 +30,12 @@ public struct HomeReducer: Reducer {
|
|||
@PresentationState public var alert: AlertState<Action>?
|
||||
public var destination: Destination?
|
||||
public var canRequestReview = false
|
||||
public var isRestoringWallet = false
|
||||
public var requiredTransactionConfirmations = 0
|
||||
public var scanState: ScanReducer.State
|
||||
public var shieldedBalance: Balance
|
||||
public var synchronizerStatusSnapshot: SyncStatusSnapshot
|
||||
public var syncProgressState: SyncProgressReducer.State
|
||||
public var walletConfig: WalletConfig
|
||||
public var transactionListState: TransactionListReducer.State
|
||||
public var migratingDatabase = true
|
||||
|
@ -49,22 +53,26 @@ public struct HomeReducer: Reducer {
|
|||
public init(
|
||||
destination: Destination? = nil,
|
||||
canRequestReview: Bool = false,
|
||||
isRestoringWallet: Bool = false,
|
||||
requiredTransactionConfirmations: Int = 0,
|
||||
scanState: ScanReducer.State,
|
||||
shieldedBalance: Balance,
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot,
|
||||
walletConfig: WalletConfig,
|
||||
syncProgressState: SyncProgressReducer.State,
|
||||
transactionListState: TransactionListReducer.State,
|
||||
walletConfig: WalletConfig,
|
||||
zecPrice: Decimal = Decimal(140.0)
|
||||
) {
|
||||
self.destination = destination
|
||||
self.canRequestReview = canRequestReview
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.requiredTransactionConfirmations = requiredTransactionConfirmations
|
||||
self.scanState = scanState
|
||||
self.shieldedBalance = shieldedBalance
|
||||
self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
|
||||
self.walletConfig = walletConfig
|
||||
self.syncProgressState = syncProgressState
|
||||
self.transactionListState = transactionListState
|
||||
self.walletConfig = walletConfig
|
||||
self.zecPrice = zecPrice
|
||||
}
|
||||
}
|
||||
|
@ -77,11 +85,14 @@ public struct HomeReducer: Reducer {
|
|||
case onAppear
|
||||
case onDisappear
|
||||
case resolveReviewRequest
|
||||
case restoreWalletTask
|
||||
case restoreWalletValue(Bool)
|
||||
case retrySync
|
||||
case reviewRequestFinished
|
||||
case showSynchronizerErrorAlert(ZcashError)
|
||||
case synchronizerStateChanged(SynchronizerState)
|
||||
case syncFailed(ZcashError)
|
||||
case syncProgress(SyncProgressReducer.Action)
|
||||
case updateDestination(HomeReducer.State.Destination?)
|
||||
case updateTransactionList([TransactionState])
|
||||
case transactionList(TransactionListReducer.Action)
|
||||
|
@ -90,6 +101,7 @@ public struct HomeReducer: Reducer {
|
|||
@Dependency(\.audioServices) var audioServices
|
||||
@Dependency(\.diskSpaceChecker) var diskSpaceChecker
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
@Dependency(\.reviewRequest) var reviewRequest
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
||||
|
@ -103,6 +115,10 @@ public struct HomeReducer: Reducer {
|
|||
TransactionListReducer()
|
||||
}
|
||||
|
||||
Scope(state: \.syncProgressState, action: /Action.syncProgress) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
|
@ -148,6 +164,17 @@ public struct HomeReducer: Reducer {
|
|||
}
|
||||
return .none
|
||||
|
||||
case .restoreWalletTask:
|
||||
return .run { send in
|
||||
for await value in await restoreWalletStorage.value() {
|
||||
await send(.restoreWalletValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
case .restoreWalletValue(let value):
|
||||
state.isRestoringWallet = value
|
||||
return .none
|
||||
|
||||
case .reviewRequestFinished:
|
||||
state.canRequestReview = false
|
||||
return .none
|
||||
|
@ -182,6 +209,9 @@ public struct HomeReducer: Reducer {
|
|||
return .none
|
||||
}
|
||||
|
||||
case .syncProgress:
|
||||
return .none
|
||||
|
||||
case .foundTransactions:
|
||||
return .run { _ in
|
||||
reviewRequest.foundTransactions()
|
||||
|
@ -261,8 +291,9 @@ extension HomeReducer.State {
|
|||
scanState: .initial,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
walletConfig: .initial,
|
||||
transactionListState: .initial
|
||||
syncProgressState: .initial,
|
||||
transactionListState: .initial,
|
||||
walletConfig: .initial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -284,8 +315,9 @@ extension HomeStore {
|
|||
synchronizerStatusSnapshot: .snapshotFor(
|
||||
state: .error(ZcashError.synchronizerNotPrepared)
|
||||
),
|
||||
walletConfig: .initial,
|
||||
transactionListState: .initial
|
||||
syncProgressState: .initial,
|
||||
transactionListState: .initial,
|
||||
walletConfig: .initial
|
||||
)
|
||||
) {
|
||||
HomeReducer(networkType: .testnet)
|
||||
|
|
|
@ -5,6 +5,9 @@ import Generated
|
|||
import TransactionList
|
||||
import Settings
|
||||
import UIComponents
|
||||
import SyncProgress
|
||||
import Utils
|
||||
import Models
|
||||
|
||||
public struct HomeView: View {
|
||||
let store: HomeStore
|
||||
|
@ -20,6 +23,18 @@ public struct HomeView: View {
|
|||
VStack {
|
||||
balance(viewStore)
|
||||
|
||||
if viewStore.isRestoringWallet {
|
||||
SyncProgressView(
|
||||
store: store.scope(
|
||||
state: \.syncProgressState,
|
||||
action: HomeReducer.Action.syncProgress
|
||||
)
|
||||
)
|
||||
.frame(height: 94)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Asset.Colors.shade92.color)
|
||||
}
|
||||
|
||||
TransactionListView(store: store.historyStore(), tokenName: tokenName)
|
||||
}
|
||||
.applyScreenBackground()
|
||||
|
@ -44,6 +59,7 @@ public struct HomeView: View {
|
|||
destination: { NotEnoughFreeSpaceView(viewStore: viewStore) }
|
||||
)
|
||||
}
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +101,34 @@ extension HomeView {
|
|||
struct HomeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
HomeView(store: .placeholder, tokenName: "ZEC")
|
||||
HomeView(
|
||||
store:
|
||||
HomeStore(
|
||||
initialState:
|
||||
.init(
|
||||
scanState: .initial,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
syncProgressState: .init(
|
||||
lastKnownSyncPercentage: Float(0.43),
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
|
||||
syncStatusMessage: "Syncing"
|
||||
),
|
||||
transactionListState: .initial,
|
||||
walletConfig: .initial
|
||||
)
|
||||
) {
|
||||
HomeReducer(networkType: .testnet)
|
||||
},
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(
|
||||
trailing: Text("M")
|
||||
)
|
||||
.zashiTitle {
|
||||
Text("Title")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import SwiftUI
|
|||
import ExportLogs
|
||||
import DatabaseFiles
|
||||
import ExportLogs
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias PrivateDataConsentStore = Store<PrivateDataConsentReducer.State, PrivateDataConsentReducer.Action>
|
||||
public typealias PrivateDataConsentViewStore = ViewStore<PrivateDataConsentReducer.State, PrivateDataConsentReducer.Action>
|
||||
|
@ -23,11 +24,12 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
let networkType: NetworkType
|
||||
|
||||
public struct State: Equatable {
|
||||
@BindingState public var isAcknowledged: Bool = false
|
||||
public var exportBinding: Bool
|
||||
public var exportOnlyLogs = true
|
||||
@BindingState public var isAcknowledged: Bool = false
|
||||
public var isExportingData: Bool
|
||||
public var isExportingLogs: Bool
|
||||
public var isRestoringWallet = false
|
||||
public var dataDbURL: [URL] = []
|
||||
public var exportLogsState: ExportLogsReducer.State
|
||||
|
||||
|
@ -42,21 +44,23 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
}
|
||||
|
||||
public init(
|
||||
isAcknowledged: Bool = false,
|
||||
dataDbURL: [URL],
|
||||
exportBinding: Bool,
|
||||
exportLogsState: ExportLogsReducer.State,
|
||||
exportOnlyLogs: Bool = true,
|
||||
isAcknowledged: Bool = false,
|
||||
isExportingData: Bool = false,
|
||||
isExportingLogs: Bool = false
|
||||
isExportingLogs: Bool = false,
|
||||
isRestoringWallet: Bool = false
|
||||
) {
|
||||
self.isAcknowledged = isAcknowledged
|
||||
self.dataDbURL = dataDbURL
|
||||
self.exportBinding = exportBinding
|
||||
self.exportLogsState = exportLogsState
|
||||
self.exportOnlyLogs = exportOnlyLogs
|
||||
self.isAcknowledged = isAcknowledged
|
||||
self.isExportingData = isExportingData
|
||||
self.isExportingLogs = isExportingLogs
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +70,8 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
case exportLogsRequested
|
||||
case exportRequested
|
||||
case onAppear
|
||||
case restoreWalletTask
|
||||
case restoreWalletValue(Bool)
|
||||
case shareFinished
|
||||
}
|
||||
|
||||
|
@ -74,6 +80,7 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
}
|
||||
|
||||
@Dependency(\.databaseFiles) var databaseFiles
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
|
@ -86,7 +93,6 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
switch action {
|
||||
case .onAppear:
|
||||
state.dataDbURL = [databaseFiles.dataDbURLFor(ZcashNetworkBuilder.network(for: networkType))]
|
||||
state.isAcknowledged = false
|
||||
return .none
|
||||
|
||||
case .exportLogs(.finished):
|
||||
|
@ -106,6 +112,17 @@ public struct PrivateDataConsentReducer: Reducer {
|
|||
state.exportOnlyLogs = false
|
||||
return .send(.exportLogs(.start))
|
||||
|
||||
case .restoreWalletTask:
|
||||
return .run { send in
|
||||
for await value in await restoreWalletStorage.value() {
|
||||
await send(.restoreWalletValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
case .restoreWalletValue(let value):
|
||||
state.isRestoringWallet = value
|
||||
return .none
|
||||
|
||||
case .shareFinished:
|
||||
state.isExportingData = false
|
||||
state.isExportingLogs = false
|
||||
|
|
|
@ -23,6 +23,7 @@ public struct PrivateDataConsentView: View {
|
|||
ScrollView {
|
||||
Group {
|
||||
ZashiIcon()
|
||||
.padding(.top, viewStore.isRestoringWallet ? 30 : 0)
|
||||
|
||||
Text(L10n.PrivateDataConsent.title)
|
||||
.font(.custom(FontFamily.Archivo.semiBold.name, size: 25))
|
||||
|
@ -87,11 +88,13 @@ public struct PrivateDataConsentView: View {
|
|||
.onAppear {
|
||||
viewStore.send(.onAppear)
|
||||
}
|
||||
|
||||
.restoringWalletBadge(isOn: viewStore.isRestoringWallet, background: .pattern)
|
||||
|
||||
shareLogsView(viewStore)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.applyScreenBackground(withPattern: true)
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ extension RootReducer {
|
|||
public enum InitializationAction: Equatable {
|
||||
case appDelegate(AppDelegateAction)
|
||||
case checkBackupPhraseValidation
|
||||
case checkRestoreWalletFlag(SyncStatus)
|
||||
case checkWalletInitialization
|
||||
case configureCrashReporter
|
||||
case checkWalletConfig
|
||||
|
@ -56,12 +57,12 @@ extension RootReducer {
|
|||
}
|
||||
|
||||
case .synchronizerStateChanged(let latestState):
|
||||
guard state.bgTask != nil else {
|
||||
return .none
|
||||
}
|
||||
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.syncStatus)
|
||||
|
||||
guard state.bgTask != nil else {
|
||||
return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus)))
|
||||
}
|
||||
|
||||
var finishBGTask = false
|
||||
var successOfBGTask = false
|
||||
|
||||
|
@ -84,8 +85,18 @@ extension RootReducer {
|
|||
return .cancel(id: CancelStateId.timer)
|
||||
}
|
||||
|
||||
return .none
|
||||
|
||||
return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus)))
|
||||
|
||||
case .initialization(.checkRestoreWalletFlag(let syncStatus)):
|
||||
if state.isRestoringWallet && syncStatus == .upToDate {
|
||||
state.isRestoringWallet = false
|
||||
return .run { _ in
|
||||
await restoreWalletStorage.updateValue(false)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
|
||||
case .initialization(.synchronizerStartFailed):
|
||||
return .none
|
||||
|
||||
|
@ -324,7 +335,13 @@ extension RootReducer {
|
|||
return Effect.send(.destination(.updateDestination(.tabs)))
|
||||
|
||||
case .onboarding(.importWallet(.initializeSDK)):
|
||||
return Effect.send(.initialization(.initializeSDK(.restoreWallet)))
|
||||
state.isRestoringWallet = true
|
||||
return .merge(
|
||||
Effect.send(.initialization(.initializeSDK(.restoreWallet))),
|
||||
.run { _ in
|
||||
await restoreWalletStorage.updateValue(true)
|
||||
}
|
||||
)
|
||||
|
||||
case .initialization(.configureCrashReporter):
|
||||
crashReporter.configure(
|
||||
|
|
|
@ -18,6 +18,7 @@ import CrashReporter
|
|||
import ReadTransactionsStorage
|
||||
import RecoveryPhraseDisplay
|
||||
import BackgroundTasks
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias RootStore = Store<RootReducer.State, RootReducer.Action>
|
||||
public typealias RootViewStore = ViewStore<RootReducer.State, RootReducer.Action>
|
||||
|
@ -38,6 +39,7 @@ public struct RootReducer: Reducer {
|
|||
public var debugState: DebugState
|
||||
public var destinationState: DestinationState
|
||||
public var exportLogsState: ExportLogsReducer.State
|
||||
public var isRestoringWallet = false
|
||||
public var onboardingState: OnboardingFlowReducer.State
|
||||
public var phraseDisplayState: RecoveryPhraseDisplayReducer.State
|
||||
public var sandboxState: SandboxReducer.State
|
||||
|
@ -52,6 +54,7 @@ public struct RootReducer: Reducer {
|
|||
debugState: DebugState,
|
||||
destinationState: DestinationState,
|
||||
exportLogsState: ExportLogsReducer.State,
|
||||
isRestoringWallet: Bool = false,
|
||||
onboardingState: OnboardingFlowReducer.State,
|
||||
phraseDisplayState: RecoveryPhraseDisplayReducer.State,
|
||||
sandboxState: SandboxReducer.State,
|
||||
|
@ -64,6 +67,7 @@ public struct RootReducer: Reducer {
|
|||
self.debugState = debugState
|
||||
self.destinationState = destinationState
|
||||
self.exportLogsState = exportLogsState
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.onboardingState = onboardingState
|
||||
self.phraseDisplayState = phraseDisplayState
|
||||
self.sandboxState = sandboxState
|
||||
|
@ -114,6 +118,7 @@ public struct RootReducer: Reducer {
|
|||
@Dependency(\.walletStorage) var walletStorage
|
||||
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
||||
@Dependency(\.readTransactionsStorage) var readTransactionsStorage
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
|
||||
public init(tokenName: String, zcashNetwork: ZcashNetwork) {
|
||||
self.tokenName = tokenName
|
||||
|
|
|
@ -12,6 +12,7 @@ import Generated
|
|||
import WalletStorage
|
||||
import SDKSynchronizer
|
||||
import PrivateDataConsent
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias SettingsStore = Store<SettingsReducer.State, SettingsReducer.Action>
|
||||
public typealias SettingsViewStore = ViewStore<SettingsReducer.State, SettingsReducer.Action>
|
||||
|
@ -30,6 +31,7 @@ public struct SettingsReducer: Reducer {
|
|||
public var appVersion = ""
|
||||
public var appBuild = ""
|
||||
public var destination: Destination?
|
||||
public var isRestoringWallet = false
|
||||
public var phraseDisplayState: RecoveryPhraseDisplayReducer.State
|
||||
public var privateDataConsentState: PrivateDataConsentReducer.State
|
||||
public var supportData: SupportData?
|
||||
|
@ -38,6 +40,7 @@ public struct SettingsReducer: Reducer {
|
|||
appVersion: String = "",
|
||||
appBuild: String = "",
|
||||
destination: Destination? = nil,
|
||||
isRestoringWallet: Bool = false,
|
||||
phraseDisplayState: RecoveryPhraseDisplayReducer.State,
|
||||
privateDataConsentState: PrivateDataConsentReducer.State,
|
||||
supportData: SupportData? = nil
|
||||
|
@ -45,6 +48,7 @@ public struct SettingsReducer: Reducer {
|
|||
self.appVersion = appVersion
|
||||
self.appBuild = appBuild
|
||||
self.destination = destination
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.phraseDisplayState = phraseDisplayState
|
||||
self.privateDataConsentState = privateDataConsentState
|
||||
self.supportData = supportData
|
||||
|
@ -57,6 +61,8 @@ public struct SettingsReducer: Reducer {
|
|||
case onAppear
|
||||
case phraseDisplay(RecoveryPhraseDisplayReducer.Action)
|
||||
case privateDataConsent(PrivateDataConsentReducer.Action)
|
||||
case restoreWalletTask
|
||||
case restoreWalletValue(Bool)
|
||||
case sendSupportMail
|
||||
case sendSupportMailFinished
|
||||
case updateDestination(SettingsReducer.State.Destination?)
|
||||
|
@ -67,6 +73,7 @@ public struct SettingsReducer: Reducer {
|
|||
@Dependency(\.mnemonic) var mnemonic
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.walletStorage) var walletStorage
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
|
||||
public init(networkType: NetworkType) {
|
||||
self.networkType = networkType
|
||||
|
@ -79,6 +86,7 @@ public struct SettingsReducer: Reducer {
|
|||
state.appVersion = appVersion.appVersion()
|
||||
state.appBuild = appVersion.appBuild()
|
||||
return .none
|
||||
|
||||
case .backupWalletAccessRequest:
|
||||
return .run { send in
|
||||
if await localAuthentication.authenticate() {
|
||||
|
@ -92,11 +100,27 @@ public struct SettingsReducer: Reducer {
|
|||
|
||||
case .phraseDisplay:
|
||||
return .none
|
||||
|
||||
|
||||
case .updateDestination(.privateDataConsent):
|
||||
state.destination = .privateDataConsent
|
||||
state.privateDataConsentState.isAcknowledged = false
|
||||
return .none
|
||||
|
||||
case .updateDestination(let destination):
|
||||
state.destination = destination
|
||||
return .none
|
||||
|
||||
case .restoreWalletTask:
|
||||
return .run { send in
|
||||
for await value in await restoreWalletStorage.value() {
|
||||
await send(.restoreWalletValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
case .restoreWalletValue(let value):
|
||||
state.isRestoringWallet = value
|
||||
return .none
|
||||
|
||||
case .sendSupportMail:
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
state.supportData = SupportDataGenerator.generate()
|
||||
|
|
|
@ -7,6 +7,7 @@ import PrivateDataConsent
|
|||
|
||||
public struct SettingsView: View {
|
||||
@Environment(\.openURL) var openURL
|
||||
@State private var isRestoringWalletBadgeOn = false
|
||||
|
||||
let store: SettingsStore
|
||||
|
||||
|
@ -15,20 +16,55 @@ public struct SettingsView: View {
|
|||
}
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
VStack {
|
||||
ScrollView {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
Button(L10n.Settings.recoveryPhrase.uppercased()) {
|
||||
viewStore.send(.backupWalletAccessRequest)
|
||||
}
|
||||
.zcashStyle()
|
||||
.padding(.vertical, 25)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForBackupPhrase,
|
||||
destination: {
|
||||
RecoveryPhraseDisplayView(store: store.backupPhraseStore())
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForAbout,
|
||||
destination: {
|
||||
About(store: store)
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForPrivateDataConsent,
|
||||
destination: {
|
||||
PrivateDataConsentView(store: store.privateDataConsentStore())
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
viewStore.send(.onAppear)
|
||||
isRestoringWalletBadgeOn = viewStore.isRestoringWallet
|
||||
}
|
||||
.onChange(of: viewStore.isRestoringWallet) { isRestoringWalletBadgeOn = $0 }
|
||||
|
||||
if let supportData = viewStore.supportData {
|
||||
UIMailDialogView(
|
||||
supportData: supportData,
|
||||
completion: {
|
||||
viewStore.send(.sendSupportMailFinished)
|
||||
}
|
||||
)
|
||||
// UIMailDialogView only wraps MFMailComposeViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
}
|
||||
|
||||
Button(L10n.Settings.feedback.uppercased()) {
|
||||
viewStore.send(.sendSupportMail)
|
||||
}
|
||||
.zcashStyle()
|
||||
.padding(.bottom, 25)
|
||||
|
||||
|
||||
Button(L10n.Settings.privacyPolicy.uppercased()) {
|
||||
if let url = URL(string: "https://z.cash/privacy-policy/") {
|
||||
openURL(url)
|
||||
|
@ -36,61 +72,30 @@ public struct SettingsView: View {
|
|||
}
|
||||
.zcashStyle()
|
||||
.padding(.bottom, 25)
|
||||
|
||||
|
||||
Button(L10n.Settings.documentation.uppercased()) {
|
||||
// TODO: - [#866] finish the documentation button action
|
||||
// https://github.com/Electric-Coin-Company/zashi-ios/issues/866
|
||||
}
|
||||
.zcashStyle()
|
||||
.padding(.bottom, 25)
|
||||
|
||||
|
||||
Button(L10n.Settings.exportPrivateData.uppercased()) {
|
||||
viewStore.send(.updateDestination(.privateDataConsent))
|
||||
}
|
||||
.zcashStyle()
|
||||
.padding(.bottom, 80)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(L10n.Settings.about.uppercased()) {
|
||||
viewStore.send(.updateDestination(.about))
|
||||
}
|
||||
.zcashStyle()
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForBackupPhrase,
|
||||
destination: {
|
||||
RecoveryPhraseDisplayView(store: store.backupPhraseStore())
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForAbout,
|
||||
destination: {
|
||||
About(store: store)
|
||||
}
|
||||
)
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForPrivateDataConsent,
|
||||
destination: {
|
||||
PrivateDataConsentView(store: store.privateDataConsentStore())
|
||||
}
|
||||
)
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
|
||||
if let supportData = viewStore.supportData {
|
||||
UIMailDialogView(
|
||||
supportData: supportData,
|
||||
completion: {
|
||||
viewStore.send(.sendSupportMailFinished)
|
||||
}
|
||||
)
|
||||
// UIMailDialogView only wraps MFMailComposeViewController presentation
|
||||
// so frame is set to 0 to not break SwiftUIs layout
|
||||
.frame(width: 0, height: 0)
|
||||
}
|
||||
.padding(.horizontal, 70)
|
||||
}
|
||||
.padding(.horizontal, 70)
|
||||
.padding(.vertical, 1)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.applyScreenBackground()
|
||||
.alert(store: store.scope(
|
||||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
|
@ -101,6 +106,8 @@ public struct SettingsView: View {
|
|||
.resizable()
|
||||
.frame(width: 62, height: 17)
|
||||
}
|
||||
.restoringWalletBadge(isOn: isRestoringWalletBadgeOn)
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,9 +54,11 @@ public struct About: View {
|
|||
.font(.custom(FontFamily.Archivo.bold.name, size: 14))
|
||||
}
|
||||
.padding(.horizontal, 70)
|
||||
.restoringWalletBadge(isOn: viewStore.isRestoringWallet, background: .transparent)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.applyScreenBackground(withPattern: true)
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// SyncProgressStore.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 21.12.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
|
||||
import Generated
|
||||
import Models
|
||||
import SDKSynchronizer
|
||||
|
||||
public typealias SyncProgressStore = Store<SyncProgressReducer.State, SyncProgressReducer.Action>
|
||||
|
||||
public struct SyncProgressReducer: Reducer {
|
||||
private enum CancelId { case timer }
|
||||
|
||||
public struct State: Equatable {
|
||||
public var lastKnownSyncPercentage: Float = 0
|
||||
public var synchronizerStatusSnapshot: SyncStatusSnapshot
|
||||
public var syncStatusMessage = ""
|
||||
|
||||
public var isSyncing: Bool {
|
||||
synchronizerStatusSnapshot.syncStatus.isSyncing
|
||||
}
|
||||
|
||||
public var syncingPercentage: Float {
|
||||
if case .syncing(let progress) = synchronizerStatusSnapshot.syncStatus {
|
||||
return progress * 0.999
|
||||
}
|
||||
|
||||
return lastKnownSyncPercentage
|
||||
}
|
||||
|
||||
public init(
|
||||
lastKnownSyncPercentage: Float,
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot,
|
||||
syncStatusMessage: String = ""
|
||||
) {
|
||||
self.lastKnownSyncPercentage = lastKnownSyncPercentage
|
||||
self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
|
||||
self.syncStatusMessage = syncStatusMessage
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: Equatable {
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case synchronizerStateChanged(SynchronizerState)
|
||||
}
|
||||
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
|
||||
public init() {}
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
return .publisher {
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map(Action.synchronizerStateChanged)
|
||||
}
|
||||
.cancellable(id: CancelId.timer, cancelInFlight: true)
|
||||
|
||||
case .onDisappear:
|
||||
return .cancel(id: CancelId.timer)
|
||||
|
||||
case .synchronizerStateChanged(let latestState):
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.syncStatus)
|
||||
if snapshot.syncStatus != state.synchronizerStatusSnapshot.syncStatus {
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
|
||||
if case .syncing(let progress) = snapshot.syncStatus {
|
||||
state.lastKnownSyncPercentage = progress
|
||||
}
|
||||
|
||||
// TODO: [#931] The statuses of the sync process
|
||||
// https://github.com/Electric-Coin-Company/zashi-ios/issues/931
|
||||
// until then, this is temporary quick solution
|
||||
switch snapshot.syncStatus {
|
||||
case .syncing:
|
||||
state.syncStatusMessage = L10n.Balances.syncing
|
||||
case .upToDate:
|
||||
state.lastKnownSyncPercentage = 1
|
||||
state.syncStatusMessage = L10n.Balances.synced
|
||||
case .error, .stopped, .unprepared:
|
||||
state.syncStatusMessage = snapshot.message
|
||||
}
|
||||
}
|
||||
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Store
|
||||
|
||||
extension SyncProgressStore {
|
||||
public static var initial = SyncProgressStore(
|
||||
initialState: .initial
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
extension SyncProgressReducer.State {
|
||||
public static let initial = SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0,
|
||||
synchronizerStatusSnapshot: .initial
|
||||
)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// SyncProgressView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 21.12.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
import Generated
|
||||
import UIComponents
|
||||
import Models
|
||||
|
||||
public struct SyncProgressView: View {
|
||||
var store: SyncProgressStore
|
||||
|
||||
public init(store: SyncProgressStore) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
VStack(spacing: 5) {
|
||||
HStack {
|
||||
Text(viewStore.syncStatusMessage)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 10))
|
||||
|
||||
if viewStore.isSyncing {
|
||||
ProgressView()
|
||||
.scaleEffect(0.7)
|
||||
.frame(width: 11, height: 14)
|
||||
}
|
||||
}
|
||||
.frame(height: 16)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
Text(String(format: "%0.1f%%", viewStore.syncingPercentage * 100))
|
||||
.font(.custom(FontFamily.Inter.black.name, size: 10))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
|
||||
ProgressView(value: viewStore.syncingPercentage, total: 1.0)
|
||||
.progressViewStyle(ZashiSyncingProgressStyle())
|
||||
}
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear { viewStore.send(.onDisappear) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SyncProgressView(
|
||||
store:
|
||||
SyncProgressStore(
|
||||
initialState: .init(
|
||||
lastKnownSyncPercentage: Float(0.43),
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot(.syncing(0.41)),
|
||||
syncStatusMessage: "Syncing"
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
)
|
||||
.background(.red)
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
|
||||
import Generated
|
||||
import AddressDetails
|
||||
import BalanceBreakdown
|
||||
|
@ -14,7 +16,7 @@ import Home
|
|||
import SendFlow
|
||||
import Settings
|
||||
import ZcashLightClientKit
|
||||
import SwiftUI
|
||||
import RestoreWalletStorage
|
||||
|
||||
public typealias TabsStore = Store<TabsReducer.State, TabsReducer.Action>
|
||||
public typealias TabsViewStore = ViewStore<TabsReducer.State, TabsReducer.Action>
|
||||
|
@ -52,6 +54,7 @@ public struct TabsReducer: Reducer {
|
|||
public var balanceBreakdownState: BalanceBreakdownReducer.State
|
||||
public var destination: Destination?
|
||||
public var homeState: HomeReducer.State
|
||||
public var isRestoringWallet = false
|
||||
public var selectedTab: Tab = .account
|
||||
public var sendState: SendFlowReducer.State
|
||||
public var settingsState: SettingsReducer.State
|
||||
|
@ -61,6 +64,7 @@ public struct TabsReducer: Reducer {
|
|||
balanceBreakdownState: BalanceBreakdownReducer.State,
|
||||
destination: Destination? = nil,
|
||||
homeState: HomeReducer.State,
|
||||
isRestoringWallet: Bool = false,
|
||||
selectedTab: Tab = .account,
|
||||
sendState: SendFlowReducer.State,
|
||||
settingsState: SettingsReducer.State
|
||||
|
@ -69,6 +73,7 @@ public struct TabsReducer: Reducer {
|
|||
self.balanceBreakdownState = balanceBreakdownState
|
||||
self.destination = destination
|
||||
self.homeState = homeState
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.selectedTab = selectedTab
|
||||
self.sendState = sendState
|
||||
self.settingsState = settingsState
|
||||
|
@ -79,17 +84,21 @@ public struct TabsReducer: Reducer {
|
|||
case addressDetails(AddressDetailsReducer.Action)
|
||||
case balanceBreakdown(BalanceBreakdownReducer.Action)
|
||||
case home(HomeReducer.Action)
|
||||
case restoreWalletTask
|
||||
case restoreWalletValue(Bool)
|
||||
case selectedTabChanged(State.Tab)
|
||||
case send(SendFlowReducer.Action)
|
||||
case settings(SettingsReducer.Action)
|
||||
case updateDestination(TabsReducer.State.Destination?)
|
||||
}
|
||||
|
||||
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
|
||||
public init(tokenName: String, networkType: NetworkType) {
|
||||
self.tokenName = tokenName
|
||||
self.networkType = networkType
|
||||
}
|
||||
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
Scope(state: \.sendState, action: /Action.send) {
|
||||
SendFlowReducer(networkType: networkType)
|
||||
|
@ -129,7 +138,18 @@ public struct TabsReducer: Reducer {
|
|||
|
||||
case .home:
|
||||
return .none
|
||||
|
||||
|
||||
case .restoreWalletTask:
|
||||
return .run { send in
|
||||
for await value in await restoreWalletStorage.value() {
|
||||
await send(.restoreWalletValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
case .restoreWalletValue(let value):
|
||||
state.isRestoringWallet = value
|
||||
return .none
|
||||
|
||||
case .send(.sendDone(let transaction)):
|
||||
state.homeState.transactionListState.transactionList.insert(transaction, at: 0)
|
||||
state.selectedTab = .account
|
||||
|
@ -181,9 +201,7 @@ extension TabsViewStore {
|
|||
func bindingForDestination(_ destination: TabsReducer.State.Destination) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: { $0.destination == destination },
|
||||
send: { isActive in
|
||||
return .updateDestination(isActive ? destination : nil)
|
||||
}
|
||||
send: { isActive in .updateDestination(isActive ? destination : nil) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import BalanceBreakdown
|
|||
import Home
|
||||
import SendFlow
|
||||
import Settings
|
||||
import UIComponents
|
||||
|
||||
public struct TabsView: View {
|
||||
let networkType: NetworkType
|
||||
|
@ -30,7 +31,7 @@ public struct TabsView: View {
|
|||
|
||||
public var body: some View {
|
||||
WithViewStore(self.store, observe: \.selectedTab) { tab in
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
WithViewStore(self.store, observe: \.isRestoringWallet) { isRestoringWallet in
|
||||
ZStack {
|
||||
TabView(selection: tab.binding(send: TabsReducer.Action.selectedTabChanged)) {
|
||||
HomeView(
|
||||
|
@ -71,17 +72,17 @@ public struct TabsView: View {
|
|||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.padding(.bottom, 50)
|
||||
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
ForEach((TabsReducer.State.Tab.allCases), id: \.self) { item in
|
||||
Button {
|
||||
viewStore.send(.selectedTabChanged(item), animation: .easeInOut)
|
||||
store.send(.selectedTabChanged(item), animation: .easeInOut)
|
||||
} label: {
|
||||
VStack {
|
||||
if viewStore.selectedTab == item {
|
||||
if tab.state == item {//viewStore.selectedTab == item {
|
||||
Text("\(item.title)")
|
||||
.font(.custom(FontFamily.Archivo.black.name, size: 12))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
|
@ -112,8 +113,10 @@ public struct TabsView: View {
|
|||
.ignoresSafeArea(.keyboard)
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing: settingsButton(viewStore))
|
||||
.navigationBarItems(trailing: settingsButton(store))
|
||||
.zashiTitle { navBarView(tab.state) }
|
||||
.restoringWalletBadge(isOn: isRestoringWallet.state)
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,18 +138,20 @@ public struct TabsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
func settingsButton(_ viewStore: TabsViewStore) -> some View {
|
||||
Image(systemName: "line.3.horizontal")
|
||||
.resizable()
|
||||
.frame(width: 21, height: 15)
|
||||
.padding(15)
|
||||
.navigationLink(
|
||||
isActive: viewStore.bindingForDestination(.settings),
|
||||
destination: {
|
||||
SettingsView(store: store.settingsStore())
|
||||
}
|
||||
)
|
||||
.tint(Asset.Colors.primary.color)
|
||||
func settingsButton(_ store: TabsStore) -> some View {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
Image(systemName: "line.3.horizontal")
|
||||
.resizable()
|
||||
.frame(width: 21, height: 15)
|
||||
.padding(15)
|
||||
.navigationLink(
|
||||
isActive: viewStore.bindingForDestination(.settings),
|
||||
destination: {
|
||||
SettingsView(store: store.settingsStore())
|
||||
}
|
||||
)
|
||||
.tint(Asset.Colors.primary.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ public enum L10n {
|
|||
}
|
||||
/// Pending transactions
|
||||
public static let pendingTransactions = L10n.tr("Localizable", "balances.pendingTransactions", fallback: "Pending transactions")
|
||||
/// The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.
|
||||
public static let restoringWalletWarning = L10n.tr("Localizable", "balances.restoringWalletWarning", fallback: "The restore process can take several hours on lower-powered devices, and even on powerful devices is likely to take more than an hour.")
|
||||
/// Shield and consolidate funds
|
||||
public static let shieldButtonTitle = L10n.tr("Localizable", "balances.shieldButtonTitle", fallback: "Shield and consolidate funds")
|
||||
/// Shielding funds
|
||||
|
@ -133,6 +135,8 @@ public enum L10n {
|
|||
public static let no = L10n.tr("Localizable", "general.no", fallback: "No")
|
||||
/// Ok
|
||||
public static let ok = L10n.tr("Localizable", "general.ok", fallback: "Ok")
|
||||
/// [RESTORING YOUR WALLET…]
|
||||
public static let restoringWallet = L10n.tr("Localizable", "general.restoringWallet", fallback: "[RESTORING YOUR WALLET…]")
|
||||
/// Send
|
||||
public static let send = L10n.tr("Localizable", "general.send", fallback: "Send")
|
||||
/// Skip
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 86 B After Width: | Height: | Size: 90 B |
|
@ -116,6 +116,7 @@
|
|||
"balances.fee" = "(Fee %@)";
|
||||
"balances.synced" = "Synced";
|
||||
"balances.syncing" = "Syncing";
|
||||
"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: %@ (code: %@)";
|
||||
|
||||
|
@ -202,6 +203,7 @@ Sharing this private data is irrevocable — once you have shared this private d
|
|||
"general.success" = "Success";
|
||||
"general.unknown" = "Unknown";
|
||||
"general.done" = "Done";
|
||||
"general.restoringWallet" = "[RESTORING YOUR WALLET…]";
|
||||
"balance.available" = "Available balance %@ %@";
|
||||
"balance.availableTitle" = "Available Balance";
|
||||
"qrCodeFor" = "QR Code for %@";
|
||||
|
|
|
@ -21,7 +21,6 @@ public struct ScreenBackgroundModifier: ViewModifier {
|
|||
Asset.Assets.gridTile.image
|
||||
.resizable(resizingMode: .tile)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.opacity(0.18)
|
||||
}
|
||||
|
||||
content
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// RestoringWalletBadge.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 18.12.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
|
||||
public struct RestoringWalletBadgeModifier: ViewModifier {
|
||||
public enum Background {
|
||||
case pattern
|
||||
case solid
|
||||
case transparent
|
||||
}
|
||||
|
||||
let isOn: Bool
|
||||
let background: Background
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
if isOn {
|
||||
ZStack(alignment: .top) {
|
||||
content
|
||||
.zIndex(0)
|
||||
|
||||
if background == .pattern {
|
||||
RestoringWalletBadge()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, 6)
|
||||
.background(
|
||||
Asset.Assets.gridTile.image
|
||||
.resizable(resizingMode: .tile)
|
||||
)
|
||||
.zIndex(1)
|
||||
} else {
|
||||
RestoringWalletBadge()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, 6)
|
||||
.background(
|
||||
background == .transparent
|
||||
? .clear
|
||||
: Asset.Colors.secondary.color
|
||||
)
|
||||
.zIndex(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
public func restoringWalletBadge(
|
||||
isOn: Bool,
|
||||
background: RestoringWalletBadgeModifier.Background = .solid
|
||||
) -> some View {
|
||||
modifier(
|
||||
RestoringWalletBadgeModifier(isOn: isOn, background: background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct RestoringWalletBadge: View {
|
||||
var body: some View {
|
||||
Text(L10n.General.restoringWallet)
|
||||
.font(.custom(FontFamily.Archivo.semiBold.name, size: 12))
|
||||
.foregroundStyle(Asset.Colors.shade55.color)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
ScrollView{
|
||||
Text("Hello, World")
|
||||
}
|
||||
.padding(.vertical, 1)
|
||||
.restoringWalletBadge(isOn: true)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(
|
||||
trailing: Text("M")
|
||||
)
|
||||
.zashiTitle {
|
||||
Text("Title")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,6 +72,7 @@
|
|||
9E4938D82ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4938D72ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift */; };
|
||||
9E5AAEC02A67CEC4003F283D /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9E5AAEBF2A67CEC4003F283D /* Colors.xcassets */; };
|
||||
9E5AAEC12A67CEC4003F283D /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9E5AAEBF2A67CEC4003F283D /* Colors.xcassets */; };
|
||||
9E5B8E742B46E04E00CA3616 /* RestoreWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5B8E732B46E04E00CA3616 /* RestoreWalletTests.swift */; };
|
||||
9E6612362878345000C75B70 /* endlessCircleProgress.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E6612352878345000C75B70 /* endlessCircleProgress.json */; };
|
||||
9E683E472B0377F0002E7B5D /* WalletNukeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E683E462B0377F0002E7B5D /* WalletNukeTests.swift */; };
|
||||
9E74CCD029DC0628003D6E32 /* ReviewRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E74CCCF29DC0628003D6E32 /* ReviewRequestTests.swift */; };
|
||||
|
@ -83,6 +84,7 @@
|
|||
9EB35D632A31F1DD00A2149B /* Root in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D622A31F1DD00A2149B /* Root */; };
|
||||
9EB35D6A2A3A2D7B00A2149B /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D692A3A2D7B00A2149B /* Utils */; };
|
||||
9EB35D6C2A3A2D9200A2149B /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 9EB35D6B2A3A2D9200A2149B /* Utils */; };
|
||||
9EEB06C62B344F1E00EEE50F /* SyncProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEB06C52B344F1E00EEE50F /* SyncProgressTests.swift */; };
|
||||
9EEB06C82B405A0400EEE50F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */; };
|
||||
9EEB06C92B405A0400EEE50F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEB06C72B405A0400EEE50F /* AppDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
@ -144,6 +146,7 @@
|
|||
9E4938D72ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWarningSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E4A01762B0C9ABD005AFC7E /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
|
||||
9E5AAEBF2A67CEC4003F283D /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
|
||||
9E5B8E732B46E04E00CA3616 /* RestoreWalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreWalletTests.swift; sourceTree = "<group>"; };
|
||||
9E5BF63E2819542C00BA3F17 /* TransactionListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionListTests.swift; sourceTree = "<group>"; };
|
||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTests.swift; sourceTree = "<group>"; };
|
||||
9E612C7829913F3600D09B09 /* SensitiveDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensitiveDataTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -170,6 +173,7 @@
|
|||
9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencySelectionTests.swift; sourceTree = "<group>"; };
|
||||
9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAmountInputTests.swift; sourceTree = "<group>"; };
|
||||
9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressInputTests.swift; sourceTree = "<group>"; };
|
||||
9EEB06C52B344F1E00EEE50F /* SyncProgressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncProgressTests.swift; sourceTree = "<group>"; };
|
||||
9EEB06C72B405A0400EEE50F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = "<group>"; };
|
||||
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -266,14 +270,13 @@
|
|||
0D4E7A1926B364180058B01E /* secantTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E139AAD2B07B39700D104B8 /* ZatoshiStringRepresentation */,
|
||||
0D4E7A1C26B364180058B01E /* Info.plist */,
|
||||
9E207C372966EF6E003E2C9B /* AddressDetailsTests */,
|
||||
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */,
|
||||
9E94C61E28AA7DD5008256E9 /* BalanceBreakdownTests */,
|
||||
9EAB4674285B5C68002904A0 /* DeeplinkTests */,
|
||||
9E3911372848AD3A0073DD9A /* HomeTests */,
|
||||
9E391122283E4C970073DD9A /* ImportWalletTests */,
|
||||
0D4E7A1C26B364180058B01E /* Info.plist */,
|
||||
9E6713EF2897F80A00A6796F /* MultiLineTextFieldTests */,
|
||||
9E1FAFB82AF2C7DA0084CA3D /* PrivateDataConsentTests */,
|
||||
9E74CCCE29DC060B003D6E32 /* ReviewRequestTests */,
|
||||
|
@ -283,10 +286,12 @@
|
|||
9E612C7729913F2300D09B09 /* SensitiveDataTests */,
|
||||
9E66129C2889388C00C75B70 /* SettingsTests */,
|
||||
9E391162284E3ECF0073DD9A /* SnapshotTests */,
|
||||
9EEB06C42B344F0E00EEE50F /* SyncProgressTests */,
|
||||
9E4691982AD573420082D7DF /* TabsTests */,
|
||||
9E5BF63D281953F900BA3F17 /* TransactionListTests */,
|
||||
9EF8135927ECC25E0075AF48 /* UtilTests */,
|
||||
34F039B129ABCE8500CF0053 /* WalletConfigProviderTests */,
|
||||
9E5BF63D281953F900BA3F17 /* TransactionListTests */,
|
||||
9E139AAD2B07B39700D104B8 /* ZatoshiStringRepresentation */,
|
||||
);
|
||||
path = secantTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -585,10 +590,19 @@
|
|||
9E391131284644580073DD9A /* AppInitializationTests.swift */,
|
||||
9E852D6429B0A86300CF4AC1 /* DebugTests.swift */,
|
||||
9E683E462B0377F0002E7B5D /* WalletNukeTests.swift */,
|
||||
9E5B8E732B46E04E00CA3616 /* RestoreWalletTests.swift */,
|
||||
);
|
||||
path = RootTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EEB06C42B344F0E00EEE50F /* SyncProgressTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9EEB06C52B344F1E00EEE50F /* SyncProgressTests.swift */,
|
||||
);
|
||||
path = SyncProgressTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EF8135927ECC25E0075AF48 /* UtilTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -950,6 +964,7 @@
|
|||
9E3451C429C857DF00177D16 /* View+UIImage.swift in Sources */,
|
||||
9E34519729C4A51100177D16 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||
9E0C0D702AFB842B00D69A16 /* TransactionStateTests.swift in Sources */,
|
||||
9E5B8E742B46E04E00CA3616 /* RestoreWalletTests.swift in Sources */,
|
||||
9E683E472B0377F0002E7B5D /* WalletNukeTests.swift in Sources */,
|
||||
9E3451BC29C857C800177D16 /* NotEnoughFeeSpaceSnapshots.swift in Sources */,
|
||||
9E3451B229C8565500177D16 /* SecItemClientTests.swift in Sources */,
|
||||
|
@ -957,6 +972,7 @@
|
|||
9E3451B329C8565500177D16 /* WalletBalance+testing.swift in Sources */,
|
||||
9E3451AA29C84ED500177D16 /* CurrencySelectionTests.swift in Sources */,
|
||||
9E3451C529C857E400177D16 /* TransactionListSnapshotTests.swift in Sources */,
|
||||
9EEB06C62B344F1E00EEE50F /* SyncProgressTests.swift in Sources */,
|
||||
9E34519829C4A51100177D16 /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9E34519C29C4A91A00177D16 /* HomeTests.swift in Sources */,
|
||||
9E1FAFB72AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift in Sources */,
|
||||
|
@ -1192,7 +1208,7 @@
|
|||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
DEVELOPMENT_TEAM = W5KABFU8SV;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "secant/secant-testnet-Info.plist";
|
||||
|
@ -1220,7 +1236,7 @@
|
|||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = RLPRR8CPQG;
|
||||
DEVELOPMENT_TEAM = W5KABFU8SV;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "secant/secant-testnet-Info.plist";
|
||||
|
|
|
@ -88,7 +88,7 @@ class BalanceBreakdownTests: XCTestCase {
|
|||
isShieldingFunds: false,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
syncProgressState: .initial,
|
||||
transparentBalance: Balance(
|
||||
WalletBalance(
|
||||
verified: Zatoshi(1_000_000),
|
||||
|
@ -113,7 +113,7 @@ class BalanceBreakdownTests: XCTestCase {
|
|||
isShieldingFunds: true,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
syncProgressState: .initial,
|
||||
transparentBalance: Balance(
|
||||
WalletBalance(
|
||||
verified: Zatoshi(1_000_000),
|
||||
|
@ -130,130 +130,30 @@ class BalanceBreakdownTests: XCTestCase {
|
|||
XCTAssertTrue(store.state.isShieldingButtonDisabled)
|
||||
}
|
||||
|
||||
func testSyncingData() async throws {
|
||||
let store = TestStore(
|
||||
initialState: BalanceBreakdownReducer.State(
|
||||
autoShieldingThreshold: Zatoshi(1_000_000),
|
||||
changePending: .zero,
|
||||
isShieldingFunds: true,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513)),
|
||||
transparentBalance: Balance(
|
||||
WalletBalance(
|
||||
verified: Zatoshi(1_000_000),
|
||||
total: Zatoshi(1_000_000)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
BalanceBreakdownReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
XCTAssertTrue(store.state.isSyncing)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0.513 * 0.999)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_Zero() async throws {
|
||||
let store = TestStore(
|
||||
initialState: BalanceBreakdownReducer.State(
|
||||
autoShieldingThreshold: Zatoshi(1_000_000),
|
||||
changePending: .zero,
|
||||
isShieldingFunds: true,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .placeholder,
|
||||
transparentBalance: Balance(
|
||||
WalletBalance(
|
||||
verified: Zatoshi(1_000_000),
|
||||
total: Zatoshi(1_000_000)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
BalanceBreakdownReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
XCTAssertEqual(store.state.lastKnownSyncPercentage, 0)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_MoreThanZero() async throws {
|
||||
let store = TestStore(
|
||||
initialState: BalanceBreakdownReducer.State(
|
||||
autoShieldingThreshold: Zatoshi(1_000_000),
|
||||
changePending: .zero,
|
||||
isShieldingFunds: true,
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .placeholder,
|
||||
transparentBalance: Balance(
|
||||
WalletBalance(
|
||||
verified: Zatoshi(1_000_000),
|
||||
total: Zatoshi(1_000_000)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
BalanceBreakdownReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
XCTAssertEqual(store.state.lastKnownSyncPercentage, 0.15)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0.15)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_FromSyncedState() async throws {
|
||||
let store = TestStore(
|
||||
initialState: BalanceBreakdownReducer.State(
|
||||
autoShieldingThreshold: Zatoshi(1_000_000),
|
||||
changePending: .zero,
|
||||
isShieldingFunds: true,
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513)),
|
||||
transparentBalance: .zero
|
||||
)
|
||||
) {
|
||||
BalanceBreakdownReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
var syncState: SynchronizerState = .zero
|
||||
syncState.syncStatus = .upToDate
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
|
||||
func testRestoreWalletSubscription() async throws {
|
||||
var initialState = BalanceBreakdownReducer.State.initial
|
||||
initialState.isRestoringWallet = false
|
||||
|
||||
await store.send(.synchronizerStateChanged(syncState)) { state in
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
state.syncStatusMessage = "Synced"
|
||||
state.lastKnownSyncPercentage = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_FromSyncingState() async throws {
|
||||
let store = TestStore(
|
||||
initialState: BalanceBreakdownReducer.State(
|
||||
autoShieldingThreshold: Zatoshi(1_000_000),
|
||||
changePending: .zero,
|
||||
isShieldingFunds: true,
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513)),
|
||||
transparentBalance: .zero
|
||||
)
|
||||
initialState: initialState
|
||||
) {
|
||||
BalanceBreakdownReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
var syncState: SynchronizerState = .zero
|
||||
syncState.syncStatus = .syncing(0.545)
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
|
||||
|
||||
await store.send(.synchronizerStateChanged(syncState)) { state in
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
state.syncStatusMessage = "Syncing"
|
||||
state.lastKnownSyncPercentage = 0.545
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.value = {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield(true)
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
|
||||
await store.send(.restoreWalletTask)
|
||||
|
||||
await store.receive(.restoreWalletValue(true)) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,9 @@ class HomeTests: XCTestCase {
|
|||
scanState: .initial,
|
||||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: mockSnapshot,
|
||||
walletConfig: .initial,
|
||||
transactionListState: .initial
|
||||
syncProgressState: .initial,
|
||||
transactionListState: .initial,
|
||||
walletConfig: .initial
|
||||
)
|
||||
) {
|
||||
HomeReducer(networkType: .testnet)
|
||||
|
@ -115,4 +116,31 @@ class HomeTests: XCTestCase {
|
|||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
@MainActor func testRestoreWalletSubscription() async throws {
|
||||
var initialState = HomeReducer.State.initial
|
||||
initialState.isRestoringWallet = false
|
||||
|
||||
let store = TestStore(
|
||||
initialState: initialState
|
||||
) {
|
||||
HomeReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.value = {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield(true)
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
|
||||
await store.send(.restoreWalletTask)
|
||||
|
||||
await store.receive(.restoreWalletValue(true)) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,30 +30,6 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
await store.finish()
|
||||
}
|
||||
|
||||
func testClearOutAcknowledgeConfirmation() async throws {
|
||||
let store = TestStore(
|
||||
initialState: PrivateDataConsentReducer.State(
|
||||
isAcknowledged: true,
|
||||
dataDbURL: [],
|
||||
exportBinding: false,
|
||||
exportLogsState: .initial
|
||||
)
|
||||
) {
|
||||
PrivateDataConsentReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
let URL = URL(string: "https://electriccoin.co")!
|
||||
|
||||
store.dependencies.databaseFiles.dataDbURLFor = { _ in URL }
|
||||
|
||||
await store.send(.onAppear) { state in
|
||||
state.dataDbURL = [URL]
|
||||
state.isAcknowledged = false
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testExportRequestSet() async throws {
|
||||
let store = TestStore(
|
||||
initialState: PrivateDataConsentReducer.State(
|
||||
|
@ -139,6 +115,33 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
await store.finish()
|
||||
}
|
||||
|
||||
func testRestoreWalletSubscription() async throws {
|
||||
var initialState = PrivateDataConsentReducer.State.initial
|
||||
initialState.isRestoringWallet = false
|
||||
|
||||
let store = TestStore(
|
||||
initialState: initialState
|
||||
) {
|
||||
PrivateDataConsentReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.value = {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield(true)
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
|
||||
await store.send(.restoreWalletTask)
|
||||
|
||||
await store.receive(.restoreWalletValue(true)) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testExportURLs_logsOnly() async throws {
|
||||
let URLdb = URL(string: "http://db.url")!
|
||||
let URLlogs = URL(string: "http://logs.url")!
|
||||
|
@ -169,11 +172,11 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
|
||||
func testIsExportPossible_NoBecauseNotAcknowledged() async throws {
|
||||
let state = PrivateDataConsentReducer.State(
|
||||
isAcknowledged: false,
|
||||
dataDbURL: [],
|
||||
exportBinding: true,
|
||||
exportLogsState: .initial,
|
||||
exportOnlyLogs: true
|
||||
exportOnlyLogs: true,
|
||||
isAcknowledged: false
|
||||
)
|
||||
|
||||
XCTAssertFalse(state.isExportPossible)
|
||||
|
@ -181,11 +184,11 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
|
||||
func testIsExportPossible_NoBecauseExportingLogs() async throws {
|
||||
let state = PrivateDataConsentReducer.State(
|
||||
isAcknowledged: true,
|
||||
dataDbURL: [],
|
||||
exportBinding: true,
|
||||
exportLogsState: .initial,
|
||||
exportOnlyLogs: true,
|
||||
isAcknowledged: true,
|
||||
isExportingLogs: true
|
||||
)
|
||||
|
||||
|
@ -194,11 +197,11 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
|
||||
func testIsExportPossible_NoBecauseExportingData() async throws {
|
||||
let state = PrivateDataConsentReducer.State(
|
||||
isAcknowledged: true,
|
||||
dataDbURL: [],
|
||||
exportBinding: true,
|
||||
exportLogsState: .initial,
|
||||
exportOnlyLogs: true,
|
||||
isAcknowledged: true,
|
||||
isExportingData: true
|
||||
)
|
||||
|
||||
|
@ -207,11 +210,11 @@ final class PrivateDataConsentTests: XCTestCase {
|
|||
|
||||
func testIsExportPossible() async throws {
|
||||
let state = PrivateDataConsentReducer.State(
|
||||
isAcknowledged: true,
|
||||
dataDbURL: [],
|
||||
exportBinding: true,
|
||||
exportLogsState: .initial,
|
||||
exportOnlyLogs: true
|
||||
exportOnlyLogs: true,
|
||||
isAcknowledged: true
|
||||
)
|
||||
|
||||
XCTAssertTrue(state.isExportPossible)
|
||||
|
|
|
@ -54,6 +54,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.sdkSynchronizer = .noOp
|
||||
store.dependencies.crashReporter = .noOp
|
||||
store.dependencies.numberFormatter = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
|
||||
// Root of the test, the app finished the launch process and triggers the checks and initializations.
|
||||
await store.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
@ -127,6 +128,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.sdkSynchronizer = .noOp
|
||||
store.dependencies.crashReporter = .noOp
|
||||
store.dependencies.numberFormatter = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
|
||||
// Root of the test, the app finished the launch process and triggers the checks and initializations.
|
||||
await store.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
@ -184,6 +186,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.walletConfigProvider = .noOp
|
||||
store.dependencies.crashReporter = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
|
||||
// Root of the test, the app finished the launch process and triggers the checks and initializations.
|
||||
await store.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
@ -215,6 +218,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.walletStorage = .noOp
|
||||
store.dependencies.walletConfigProvider = .noOp
|
||||
store.dependencies.crashReporter = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
|
||||
// Root of the test, the app finished the launch process and triggers the checks and initializations.
|
||||
await store.send(.initialization(.appDelegate(.didFinishLaunching)))
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// RestoreWalletTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 04.01.2024.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Combine
|
||||
import ComposableArchitecture
|
||||
import Root
|
||||
import Utils
|
||||
import ZcashLightClientKit
|
||||
@testable import secant_testnet
|
||||
|
||||
@MainActor
|
||||
final class RestoreWalletTests: XCTestCase {
|
||||
func testIsRestoringWallet() async throws {
|
||||
let store = TestStore(
|
||||
initialState: .initial
|
||||
) {
|
||||
RootReducer(tokenName: "ZEC", zcashNetwork: ZcashNetworkBuilder.network(for: .testnet))
|
||||
}
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.mnemonic = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.updateValue = { value in
|
||||
XCTAssertTrue(value)
|
||||
}
|
||||
store.dependencies.sdkSynchronizer = .noOp
|
||||
store.dependencies.walletStorage = .noOp
|
||||
|
||||
await store.send(.onboarding(.importWallet(.initializeSDK))) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.receive(.initialization(.initializeSDK(.restoreWallet))) { state in
|
||||
state.storedWallet = .placeholder
|
||||
}
|
||||
|
||||
await store.receive(.initialization(.initializationSuccessfullyDone(nil)))
|
||||
|
||||
await store.receive(.initialization(.registerForSynchronizersUpdate))
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testIsRestoringWalletFinished() async throws {
|
||||
var state = RootReducer.State.initial
|
||||
state.isRestoringWallet = true
|
||||
|
||||
let store = TestStore(
|
||||
initialState: state
|
||||
) {
|
||||
RootReducer(
|
||||
tokenName: "ZEC",
|
||||
zcashNetwork: ZcashNetworkBuilder.network(for: .testnet)
|
||||
)
|
||||
}
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.mnemonic = .noOp
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.updateValue = { value in
|
||||
XCTAssertFalse(value)
|
||||
}
|
||||
store.dependencies.sdkSynchronizer = .noOp
|
||||
store.dependencies.walletStorage = .noOp
|
||||
|
||||
var syncState: SynchronizerState = .zero
|
||||
syncState.syncStatus = .upToDate
|
||||
|
||||
await store.send(.synchronizerStateChanged(syncState))
|
||||
|
||||
await store.receive(.initialization(.checkRestoreWalletFlag(syncState.syncStatus))) { state in
|
||||
state.isRestoringWallet = false
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
}
|
|
@ -85,4 +85,31 @@ class SettingsTests: XCTestCase {
|
|||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testRestoreWalletSubscription() async throws {
|
||||
var initialState = SettingsReducer.State.initial
|
||||
initialState.isRestoringWallet = false
|
||||
|
||||
let store = TestStore(
|
||||
initialState: initialState
|
||||
) {
|
||||
SettingsReducer(networkType: .testnet)
|
||||
}
|
||||
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.value = {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield(true)
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
|
||||
await store.send(.restoreWalletTask)
|
||||
|
||||
await store.receive(.restoreWalletValue(true)) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class BalanceBreakdownSnapshotTests: XCTestCase {
|
|||
isShieldingFunds: false,
|
||||
pendingTransactions: .zero,
|
||||
shieldedBalance: WalletBalance(verified: Zatoshi(123_000_000_000), total: Zatoshi(123_000_000_000)).redacted,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
syncProgressState: .initial,
|
||||
transparentBalance: WalletBalance(verified: Zatoshi(850_000_000), total: Zatoshi(850_000_000)).redacted
|
||||
)
|
||||
) {
|
||||
|
|
|
@ -42,8 +42,9 @@ class HomeSnapshotTests: XCTestCase {
|
|||
scanState: .initial,
|
||||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .initial,
|
||||
walletConfig: .initial,
|
||||
transactionListState: .init(transactionList: IdentifiedArrayOf(uniqueElements: transactionList))
|
||||
syncProgressState: .initial,
|
||||
transactionListState: .init(transactionList: IdentifiedArrayOf(uniqueElements: transactionList)),
|
||||
walletConfig: .initial
|
||||
)
|
||||
) {
|
||||
HomeReducer(networkType: .testnet)
|
||||
|
|
|
@ -67,7 +67,9 @@ class SendSnapshotTests: XCTestCase {
|
|||
textFieldState:
|
||||
TCATextFieldReducer.State(
|
||||
validationType: nil,
|
||||
text: "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h".redacted
|
||||
text:
|
||||
// swiftlint:disable line_length
|
||||
"utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h".redacted
|
||||
)
|
||||
),
|
||||
transactionAmountInputState: TransactionAmountTextFieldReducer.State(
|
||||
|
@ -109,7 +111,9 @@ class SendSnapshotTests: XCTestCase {
|
|||
textFieldState:
|
||||
TCATextFieldReducer.State(
|
||||
validationType: nil,
|
||||
text: "utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h".redacted
|
||||
text:
|
||||
// swiftlint:disable line_length
|
||||
"utest1zkkkjfxkamagznjr6ayemffj2d2gacdwpzcyw669pvg06xevzqslpmm27zjsctlkstl2vsw62xrjktmzqcu4yu9zdhdxqz3kafa4j2q85y6mv74rzjcgjg8c0ytrg7dwyzwtgnuc76h".redacted
|
||||
)
|
||||
),
|
||||
transactionAmountInputState: TransactionAmountTextFieldReducer.State(
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// SyncProgressTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 21.12.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import SyncProgress
|
||||
import Models
|
||||
@testable import secant_testnet
|
||||
|
||||
@MainActor
|
||||
final class SyncProgressTests: XCTestCase {
|
||||
func testSyncingData() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0.0,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513))
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
XCTAssertTrue(store.state.isSyncing)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0.513 * 0.999)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_Zero() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0.0,
|
||||
synchronizerStatusSnapshot: .placeholder
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
XCTAssertEqual(store.state.lastKnownSyncPercentage, 0)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_MoreThanZero() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
synchronizerStatusSnapshot: .placeholder
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
XCTAssertEqual(store.state.lastKnownSyncPercentage, 0.15)
|
||||
XCTAssertEqual(store.state.syncingPercentage, 0.15)
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_FromSyncedState() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513))
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
var syncState: SynchronizerState = .zero
|
||||
syncState.syncStatus = .upToDate
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
|
||||
|
||||
await store.send(.synchronizerStateChanged(syncState)) { state in
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
state.syncStatusMessage = "Synced"
|
||||
state.lastKnownSyncPercentage = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
func testlastKnownSyncingPercentage_FromSyncingState() async throws {
|
||||
let store = TestStore(
|
||||
initialState: SyncProgressReducer.State(
|
||||
lastKnownSyncPercentage: 0.15,
|
||||
synchronizerStatusSnapshot: .snapshotFor(state: .syncing(0.513))
|
||||
)
|
||||
) {
|
||||
SyncProgressReducer()
|
||||
}
|
||||
|
||||
var syncState: SynchronizerState = .zero
|
||||
syncState.syncStatus = .syncing(0.545)
|
||||
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
|
||||
|
||||
await store.send(.synchronizerStateChanged(syncState)) { state in
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
state.syncStatusMessage = "Syncing"
|
||||
state.lastKnownSyncPercentage = 0.545
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,6 +68,33 @@ class TabsTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testRestoreWalletSubscription() async throws {
|
||||
var initialState = TabsReducer.State.initial
|
||||
initialState.isRestoringWallet = false
|
||||
|
||||
let store = TestStore(
|
||||
initialState: initialState
|
||||
) {
|
||||
TabsReducer(tokenName: "TAZ", networkType: .testnet)
|
||||
}
|
||||
|
||||
store.dependencies.restoreWalletStorage = .noOp
|
||||
store.dependencies.restoreWalletStorage.value = {
|
||||
AsyncStream { continuation in
|
||||
continuation.yield(true)
|
||||
continuation.finish()
|
||||
}
|
||||
}
|
||||
|
||||
await store.send(.restoreWalletTask)
|
||||
|
||||
await store.receive(.restoreWalletValue(true)) { state in
|
||||
state.isRestoringWallet = true
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testAccountTabTitle() {
|
||||
var tabsState = TabsReducer.State.initial
|
||||
tabsState.selectedTab = .account
|
||||
|
|
Loading…
Reference in New Issue