[#1029] Not enough free space UI
- The UI has been updated to the latest design - The values of disc space are provided - The business logic has changed to react to check every app start or app foreground - Test has been fixed [#1029] Not enough free space UI - Final copy [#1029] Not enough free space UI (#1170) - changelog updated
This commit is contained in:
parent
9decc16ffa
commit
88f91380eb
|
@ -11,6 +11,9 @@ directly impact users rather than highlighting other crucial architectural updat
|
|||
### Added
|
||||
- Tap to Copy memo.
|
||||
|
||||
### Changed
|
||||
- Zashi requires 1 GB of free space to operate. We have updated the user experience to display a message when this requirement is not met, indicating the actual amount of free space available. From this screen, you can access the settings to obtain the recovery phrase if needed.
|
||||
|
||||
### Fixed
|
||||
- Tap to Copy transaction ID button animation.
|
||||
- Transparent balance added up to the total balance.
|
||||
|
|
|
@ -32,6 +32,7 @@ let package = Package(
|
|||
.library(name: "LogsHandler", targets: ["LogsHandler"]),
|
||||
.library(name: "MnemonicClient", targets: ["MnemonicClient"]),
|
||||
.library(name: "Models", targets: ["Models"]),
|
||||
.library(name: "NotEnoughFreeSpace", targets: ["NotEnoughFreeSpace"]),
|
||||
.library(name: "NumberFormatter", targets: ["NumberFormatter"]),
|
||||
.library(name: "OnboardingFlow", targets: ["OnboardingFlow"]),
|
||||
.library(name: "PartialProposalError", targets: ["PartialProposalError"]),
|
||||
|
@ -233,8 +234,6 @@ let package = Package(
|
|||
.target(
|
||||
name: "Home",
|
||||
dependencies: [
|
||||
"AudioServices",
|
||||
"DiskSpaceChecker",
|
||||
"Generated",
|
||||
"Models",
|
||||
"RestoreWalletStorage",
|
||||
|
@ -299,6 +298,18 @@ let package = Package(
|
|||
],
|
||||
path: "Sources/Models"
|
||||
),
|
||||
.target(
|
||||
name: "NotEnoughFreeSpace",
|
||||
dependencies: [
|
||||
"DiskSpaceChecker",
|
||||
"Generated",
|
||||
"Settings",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
|
||||
],
|
||||
path: "Sources/Features/NotEnoughFreeSpace"
|
||||
),
|
||||
.target(
|
||||
name: "NumberFormatter",
|
||||
dependencies: [
|
||||
|
@ -393,10 +404,12 @@ let package = Package(
|
|||
"DatabaseFiles",
|
||||
"Deeplink",
|
||||
"DerivationTool",
|
||||
"DiskSpaceChecker",
|
||||
"ExportLogs",
|
||||
"Generated",
|
||||
"MnemonicClient",
|
||||
"Models",
|
||||
"NotEnoughFreeSpace",
|
||||
"NumberFormatter",
|
||||
"OnboardingFlow",
|
||||
"Pasteboard",
|
||||
|
|
|
@ -10,11 +10,11 @@ import Foundation
|
|||
public struct DiskSpaceChecker {
|
||||
/// Free space on disk in bytes required to do sync
|
||||
public func freeSpaceRequiredForSync() -> Int64 {
|
||||
return 1 * 1024 * 1024 * 1024 // 1GB converted to bytes
|
||||
1 * 1024 * 1024 * 1024 // 1GB converted to bytes
|
||||
}
|
||||
|
||||
public func hasEnoughFreeSpaceForSync() -> Bool {
|
||||
return freeSpace() > freeSpaceRequiredForSync()
|
||||
freeSpace() > freeSpaceRequiredForSync()
|
||||
}
|
||||
|
||||
public func freeSpace() -> Int64 {
|
||||
|
|
|
@ -3,8 +3,6 @@ import SwiftUI
|
|||
import AVFoundation
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import AudioServices
|
||||
import DiskSpaceChecker
|
||||
import Utils
|
||||
import Models
|
||||
import Generated
|
||||
|
@ -22,12 +20,7 @@ public struct HomeReducer: Reducer {
|
|||
private let CancelEventId = UUID()
|
||||
|
||||
public struct State: Equatable {
|
||||
public enum Destination: Equatable {
|
||||
case notEnoughFreeDiskSpace
|
||||
}
|
||||
|
||||
@PresentationState public var alert: AlertState<Action>?
|
||||
public var destination: Destination?
|
||||
public var canRequestReview = false
|
||||
public var isRestoringWallet = false
|
||||
public var requiredTransactionConfirmations = 0
|
||||
|
@ -61,7 +54,6 @@ public struct HomeReducer: Reducer {
|
|||
}
|
||||
|
||||
public init(
|
||||
destination: Destination? = nil,
|
||||
canRequestReview: Bool = false,
|
||||
isRestoringWallet: Bool = false,
|
||||
requiredTransactionConfirmations: Int = 0,
|
||||
|
@ -76,7 +68,6 @@ public struct HomeReducer: Reducer {
|
|||
walletConfig: WalletConfig,
|
||||
zecPrice: Decimal = Decimal(140.0)
|
||||
) {
|
||||
self.destination = destination
|
||||
self.canRequestReview = canRequestReview
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.requiredTransactionConfirmations = requiredTransactionConfirmations
|
||||
|
@ -109,13 +100,10 @@ public struct HomeReducer: Reducer {
|
|||
case synchronizerStateChanged(RedactableSynchronizerState)
|
||||
case syncFailed(ZcashError)
|
||||
case syncProgress(SyncProgressReducer.Action)
|
||||
case updateDestination(HomeReducer.State.Destination?)
|
||||
case updateTransactionList([TransactionState])
|
||||
case transactionList(TransactionListReducer.Action)
|
||||
}
|
||||
|
||||
@Dependency(\.audioServices) var audioServices
|
||||
@Dependency(\.diskSpaceChecker) var diskSpaceChecker
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.restoreWalletStorage) var restoreWalletStorage
|
||||
@Dependency(\.reviewRequest) var reviewRequest
|
||||
|
@ -137,32 +125,26 @@ public struct HomeReducer: Reducer {
|
|||
switch action {
|
||||
case .onAppear:
|
||||
state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations
|
||||
|
||||
if diskSpaceChecker.hasEnoughFreeSpaceForSync() {
|
||||
return .merge(
|
||||
Effect.send(.updateDestination(nil)),
|
||||
.publisher {
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map { $0.redacted }
|
||||
.map(HomeReducer.Action.synchronizerStateChanged)
|
||||
}
|
||||
.cancellable(id: CancelStateId, cancelInFlight: true),
|
||||
.publisher {
|
||||
sdkSynchronizer.eventStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.compactMap {
|
||||
if case SynchronizerEvent.foundTransactions = $0 {
|
||||
return HomeReducer.Action.foundTransactions
|
||||
}
|
||||
return nil
|
||||
return .merge(
|
||||
.publisher {
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map { $0.redacted }
|
||||
.map(HomeReducer.Action.synchronizerStateChanged)
|
||||
}
|
||||
.cancellable(id: CancelStateId, cancelInFlight: true),
|
||||
.publisher {
|
||||
sdkSynchronizer.eventStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.compactMap {
|
||||
if case SynchronizerEvent.foundTransactions = $0 {
|
||||
return HomeReducer.Action.foundTransactions
|
||||
}
|
||||
}
|
||||
.cancellable(id: CancelEventId, cancelInFlight: true)
|
||||
)
|
||||
} else {
|
||||
return Effect.send(.updateDestination(.notEnoughFreeDiskSpace))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
.cancellable(id: CancelEventId, cancelInFlight: true)
|
||||
)
|
||||
|
||||
case .onDisappear:
|
||||
return .concatenate(
|
||||
|
@ -233,10 +215,6 @@ public struct HomeReducer: Reducer {
|
|||
reviewRequest.foundTransactions()
|
||||
}
|
||||
|
||||
case .updateDestination(let destination):
|
||||
state.destination = destination
|
||||
return .none
|
||||
|
||||
case .transactionList:
|
||||
return .none
|
||||
|
||||
|
@ -286,19 +264,6 @@ extension HomeStore {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewStore
|
||||
|
||||
extension HomeViewStore {
|
||||
func bindingForDestination(_ destination: HomeReducer.State.Destination) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: { $0.destination == destination },
|
||||
send: { isActive in
|
||||
return .updateDestination(isActive ? destination : nil)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
||||
extension HomeReducer.State {
|
||||
|
|
|
@ -55,10 +55,6 @@ public struct HomeView: View {
|
|||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
))
|
||||
.navigationLinkEmpty(
|
||||
isActive: viewStore.bindingForDestination(.notEnoughFreeDiskSpace),
|
||||
destination: { NotEnoughFreeSpaceView(viewStore: viewStore) }
|
||||
)
|
||||
}
|
||||
.task { await store.send(.restoreWalletTask).finish() }
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// NotEnoughFreeSpace.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Michal Fousek on 28.09.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import UIComponents
|
||||
import Generated
|
||||
|
||||
public struct NotEnoughFreeSpaceView: View {
|
||||
let viewStore: HomeViewStore
|
||||
|
||||
public init(viewStore: HomeViewStore) {
|
||||
self.viewStore = viewStore
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Text(L10n.Nefs.message)
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct NotEnoughFreeSpaceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotEnoughFreeSpaceView(viewStore: ViewStore(HomeStore.placeholder, observe: { $0 }))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// NotEnoughFreeSpaceStore.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 02.04.2024.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
|
||||
import DiskSpaceChecker
|
||||
import Settings
|
||||
|
||||
@Reducer
|
||||
public struct NotEnoughFreeSpace {
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
public var freeSpaceRequiredForSync = ""
|
||||
public var freeSpace = ""
|
||||
public var isSettingsOpen = false
|
||||
public var settingsState: SettingsReducer.State
|
||||
|
||||
public init(
|
||||
isSettingsOpen: Bool = false,
|
||||
settingsState: SettingsReducer.State
|
||||
) {
|
||||
self.isSettingsOpen = isSettingsOpen
|
||||
self.settingsState = settingsState
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: BindableAction, Equatable {
|
||||
case binding(BindingAction<NotEnoughFreeSpace.State>)
|
||||
case onAppear
|
||||
case settings(SettingsReducer.Action)
|
||||
}
|
||||
|
||||
@Dependency(\.diskSpaceChecker) var diskSpaceChecker
|
||||
|
||||
public init() {}
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
|
||||
Scope(state: \.settingsState, action: /Action.settings) {
|
||||
SettingsReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
let fsrts = Double(diskSpaceChecker.freeSpaceRequiredForSync())
|
||||
let fSpace = Double(diskSpaceChecker.freeSpace())
|
||||
// We show the value in GB so any required value is divided by 1_073_741_824 bytes
|
||||
state.freeSpaceRequiredForSync = String(format: "%0.0f", fsrts / Double(1_073_741_824))
|
||||
// We show the value in MB so any required value is divided by 1_048_576 bytes
|
||||
state.freeSpace = String(format: "%0.0f", fSpace / Double(1_048_576))
|
||||
return .none
|
||||
|
||||
case .binding:
|
||||
return .none
|
||||
|
||||
case .settings:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// NotEnoughFreeSpaceView.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Michal Fousek on 28.09.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
import Generated
|
||||
import Settings
|
||||
import UIComponents
|
||||
|
||||
public struct NotEnoughFreeSpaceView: View {
|
||||
@Perception.Bindable var store: StoreOf<NotEnoughFreeSpace>
|
||||
|
||||
public init(store: StoreOf<NotEnoughFreeSpace>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithPerceptionTracking {
|
||||
VStack {
|
||||
ZashiErrorIcon()
|
||||
.padding(.vertical, 20)
|
||||
|
||||
Text(L10n.NotEnoughFreeSpace.message(store.freeSpaceRequiredForSync, store.freeSpace))
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.vertical, 1)
|
||||
.onAppear { store.send(.onAppear) }
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing: settingsButton())
|
||||
.applyScreenBackground()
|
||||
.zashiTitle {
|
||||
Text(L10n.NotEnoughFreeSpace.title.uppercased())
|
||||
.font(.custom(FontFamily.Archivo.bold.name, size: 14))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func settingsButton() -> some View {
|
||||
return Image(systemName: "line.3.horizontal")
|
||||
.resizable()
|
||||
.frame(width: 21, height: 15)
|
||||
.padding(15)
|
||||
.navigationLink(
|
||||
isActive: $store.isSettingsOpen,
|
||||
destination: {
|
||||
SettingsView(
|
||||
store:
|
||||
store.scope(
|
||||
state: \.settingsState,
|
||||
action: \.settings
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
.tint(Asset.Colors.primary.color)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
NotEnoughFreeSpaceView(
|
||||
store:
|
||||
StoreOf<NotEnoughFreeSpace>(
|
||||
initialState: NotEnoughFreeSpace.State(
|
||||
settingsState: .initial
|
||||
)
|
||||
) {
|
||||
NotEnoughFreeSpace()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
||||
extension NotEnoughFreeSpace.State {
|
||||
public static let initial = NotEnoughFreeSpace.State(
|
||||
settingsState: .initial
|
||||
)
|
||||
}
|
||||
|
||||
extension NotEnoughFreeSpace {
|
||||
public static let placeholder = StoreOf<NotEnoughFreeSpace>(
|
||||
initialState: .initial
|
||||
) {
|
||||
NotEnoughFreeSpace()
|
||||
}
|
||||
}
|
|
@ -22,16 +22,7 @@ public struct PartialProposalErrorView: View {
|
|||
ScrollView {
|
||||
WithPerceptionTracking {
|
||||
VStack(alignment: .center) {
|
||||
ZashiIcon()
|
||||
.padding(.top, 20)
|
||||
.scaleEffect(2)
|
||||
.padding(.vertical, 30)
|
||||
.overlay {
|
||||
Asset.Assets.alertIcon.image
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.offset(x: 25, y: 15)
|
||||
}
|
||||
ZashiErrorIcon()
|
||||
|
||||
Group {
|
||||
Text(L10n.ProposalPartial.message1)
|
||||
|
|
|
@ -16,6 +16,7 @@ import DerivationTool
|
|||
extension RootReducer {
|
||||
public struct DestinationState: Equatable {
|
||||
public enum Destination: Equatable {
|
||||
case notEnoughFreeSpace
|
||||
case onboarding
|
||||
case phraseDisplay
|
||||
case sandbox
|
||||
|
@ -25,6 +26,7 @@ extension RootReducer {
|
|||
}
|
||||
|
||||
public var internalDestination: Destination = .welcome
|
||||
public var preNotEnoughFreeSpaceDestination: Destination?
|
||||
public var previousDestination: Destination?
|
||||
|
||||
public var destination: Destination {
|
||||
|
@ -106,7 +108,8 @@ extension RootReducer {
|
|||
return .none
|
||||
|
||||
case .tabs, .initialization, .onboarding, .sandbox, .updateStateAfterConfigUpdate, .alert, .phraseDisplay, .synchronizerStateChanged,
|
||||
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .exportLogs, .confirmationDialog:
|
||||
.welcome, .binding, .nukeWalletFailed, .nukeWalletSucceeded, .debug, .walletConfigLoaded, .exportLogs, .confirmationDialog,
|
||||
.notEnoughFreeSpace:
|
||||
return .none
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import ComposableArchitecture
|
|||
import Foundation
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import NotEnoughFreeSpace
|
||||
import Utils
|
||||
|
||||
/// In this file is a collection of helpers that control all state and action related operations
|
||||
|
@ -132,6 +133,13 @@ extension RootReducer {
|
|||
return .none
|
||||
|
||||
case .initialization(.retryStart):
|
||||
if !diskSpaceChecker.hasEnoughFreeSpaceForSync() {
|
||||
state.destinationState.preNotEnoughFreeSpaceDestination = state.destinationState.internalDestination
|
||||
return .send(.destination(.updateDestination(.notEnoughFreeSpace)))
|
||||
} else if let preNotEnoughFreeSpaceDestination = state.destinationState.preNotEnoughFreeSpaceDestination {
|
||||
state.destinationState.internalDestination = preNotEnoughFreeSpaceDestination
|
||||
state.destinationState.preNotEnoughFreeSpaceDestination = nil
|
||||
}
|
||||
// Try the start only if the synchronizer has been already prepared
|
||||
guard sdkSynchronizer.latestState().syncStatus.isPrepared else {
|
||||
return .none
|
||||
|
@ -182,6 +190,13 @@ extension RootReducer {
|
|||
)
|
||||
|
||||
case .initialization(.initialSetups):
|
||||
if !diskSpaceChecker.hasEnoughFreeSpaceForSync() {
|
||||
state.destinationState.preNotEnoughFreeSpaceDestination = state.destinationState.internalDestination
|
||||
return .send(.destination(.updateDestination(.notEnoughFreeSpace)))
|
||||
} else if let preNotEnoughFreeSpaceDestination = state.destinationState.preNotEnoughFreeSpaceDestination {
|
||||
state.destinationState.internalDestination = preNotEnoughFreeSpaceDestination
|
||||
state.destinationState.preNotEnoughFreeSpaceDestination = nil
|
||||
}
|
||||
// TODO: [#524] finish all the wallet events according to definition, https://github.com/Electric-Coin-Company/zashi-ios/issues/524
|
||||
LoggerProxy.event(".appDelegate(.didFinishLaunching)")
|
||||
/// We need to fetch data from keychain, in order to be 100% sure the keychain can be read we delay the check a bit
|
||||
|
@ -453,7 +468,7 @@ extension RootReducer {
|
|||
case .onboarding(.securityWarning(.recoveryPhraseDisplay(.finishedPressed))):
|
||||
return Effect.send(.destination(.updateDestination(.tabs)))
|
||||
|
||||
case .tabs, .destination, .onboarding, .sandbox, .phraseDisplay,
|
||||
case .tabs, .destination, .onboarding, .sandbox, .phraseDisplay, .notEnoughFreeSpace,
|
||||
.welcome, .binding, .debug, .exportLogs, .alert, .splashFinished, .splashRemovalRequested, .confirmationDialog:
|
||||
return .none
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ import ComposableArchitecture
|
|||
import ZcashLightClientKit
|
||||
import DatabaseFiles
|
||||
import Deeplink
|
||||
import DiskSpaceChecker
|
||||
import ZcashSDKEnvironment
|
||||
import WalletStorage
|
||||
import WalletConfigProvider
|
||||
import UserPreferencesStorage
|
||||
import Models
|
||||
import NotEnoughFreeSpace
|
||||
import Welcome
|
||||
import Generated
|
||||
import Foundation
|
||||
|
@ -42,6 +44,7 @@ public struct RootReducer: Reducer {
|
|||
public var exportLogsState: ExportLogsReducer.State
|
||||
public var isLockedInKeychainUnavailableState = false
|
||||
public var isRestoringWallet = false
|
||||
public var notEnoughFreeSpaceState: NotEnoughFreeSpace.State
|
||||
public var onboardingState: OnboardingFlowReducer.State
|
||||
public var phraseDisplayState: RecoveryPhraseDisplay.State
|
||||
public var sandboxState: SandboxReducer.State
|
||||
|
@ -58,6 +61,7 @@ public struct RootReducer: Reducer {
|
|||
exportLogsState: ExportLogsReducer.State,
|
||||
isLockedInKeychainUnavailableState: Bool = false,
|
||||
isRestoringWallet: Bool = false,
|
||||
notEnoughFreeSpaceState: NotEnoughFreeSpace.State = .initial,
|
||||
onboardingState: OnboardingFlowReducer.State,
|
||||
phraseDisplayState: RecoveryPhraseDisplay.State,
|
||||
sandboxState: SandboxReducer.State,
|
||||
|
@ -73,6 +77,7 @@ public struct RootReducer: Reducer {
|
|||
self.isLockedInKeychainUnavailableState = isLockedInKeychainUnavailableState
|
||||
self.isRestoringWallet = isRestoringWallet
|
||||
self.onboardingState = onboardingState
|
||||
self.notEnoughFreeSpaceState = notEnoughFreeSpaceState
|
||||
self.phraseDisplayState = phraseDisplayState
|
||||
self.sandboxState = sandboxState
|
||||
self.tabsState = tabsState
|
||||
|
@ -95,6 +100,7 @@ public struct RootReducer: Reducer {
|
|||
case exportLogs(ExportLogsReducer.Action)
|
||||
case tabs(TabsReducer.Action)
|
||||
case initialization(InitializationAction)
|
||||
case notEnoughFreeSpace(NotEnoughFreeSpace.Action)
|
||||
case nukeWalletFailed
|
||||
case nukeWalletSucceeded
|
||||
case onboarding(OnboardingFlowReducer.Action)
|
||||
|
@ -112,6 +118,7 @@ public struct RootReducer: Reducer {
|
|||
@Dependency(\.databaseFiles) var databaseFiles
|
||||
@Dependency(\.deeplink) var deeplink
|
||||
@Dependency(\.derivationTool) var derivationTool
|
||||
@Dependency(\.diskSpaceChecker) var diskSpaceChecker
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.mnemonic) var mnemonic
|
||||
@Dependency(\.numberFormatter) var numberFormatter
|
||||
|
@ -136,6 +143,10 @@ public struct RootReducer: Reducer {
|
|||
ExportLogsReducer()
|
||||
}
|
||||
|
||||
Scope(state: \.notEnoughFreeSpaceState, action: /Action.notEnoughFreeSpace) {
|
||||
NotEnoughFreeSpace()
|
||||
}
|
||||
|
||||
Scope(state: \.onboardingState, action: /Action.onboarding) {
|
||||
OnboardingFlowReducer()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import StoreKit
|
|||
import ComposableArchitecture
|
||||
import Generated
|
||||
import Models
|
||||
import NotEnoughFreeSpace
|
||||
import RecoveryPhraseDisplay
|
||||
import Welcome
|
||||
import ExportLogs
|
||||
|
@ -47,6 +48,20 @@ private extension RootView {
|
|||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
Group {
|
||||
switch viewStore.destinationState.destination {
|
||||
case .notEnoughFreeSpace:
|
||||
NavigationView {
|
||||
NotEnoughFreeSpaceView(
|
||||
store: store.scope(
|
||||
state: \.notEnoughFreeSpaceState,
|
||||
action: RootReducer.Action.notEnoughFreeSpace
|
||||
)
|
||||
)
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.overlayedWithSplash(viewStore.splashAppeared) {
|
||||
viewStore.send(.splashRemovalRequested)
|
||||
}
|
||||
|
||||
case .tabs:
|
||||
NavigationView {
|
||||
TabsView(
|
||||
|
|
|
@ -235,6 +235,14 @@ public enum L10n {
|
|||
/// Not enough space on disk to do synchronisation!
|
||||
public static let message = L10n.tr("Localizable", "nefs.message", fallback: "Not enough space on disk to do synchronisation!")
|
||||
}
|
||||
public enum NotEnoughFreeSpace {
|
||||
/// Zashi requires at least %@ GB of space to operate but there is only %@ MB available. Go to your device settings and make more space available if you wish to use the Zashi app.
|
||||
public static func message(_ p1: Any, _ p2: Any) -> String {
|
||||
return L10n.tr("Localizable", "notEnoughFreeSpace.message", String(describing: p1), String(describing: p2), fallback: "Zashi requires at least %@ GB of space to operate but there is only %@ MB available. Go to your device settings and make more space available if you wish to use the Zashi app.")
|
||||
}
|
||||
/// Not enough free space
|
||||
public static let title = L10n.tr("Localizable", "notEnoughFreeSpace.title", fallback: "Not enough free space")
|
||||
}
|
||||
public enum Onboarding {
|
||||
public enum Button {
|
||||
/// Import an Existing Wallet
|
||||
|
|
|
@ -176,6 +176,10 @@
|
|||
"deleteWallet.iUnderstand" = "I understand";
|
||||
"deleteWallet.actionButtonTitle" = "Delete Zashi";
|
||||
|
||||
// MARK: - Not Enogh Free Space
|
||||
"notEnoughFreeSpace.title" = "Not enough free space";
|
||||
"notEnoughFreeSpace.message" = "Zashi requires at least %@ GB of space to operate but there is only %@ MB available. Go to your device settings and make more space available if you wish to use the Zashi app.";
|
||||
|
||||
// MARK: - Settings
|
||||
"settings.feedback" = "Send us feedback";
|
||||
"settings.advanced" = "Advanced";
|
||||
|
|
|
@ -21,6 +21,27 @@ public struct ZashiIcon: View {
|
|||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ZashiIcon()
|
||||
public struct ZashiErrorIcon: View {
|
||||
public init() { }
|
||||
|
||||
public var body: some View {
|
||||
ZashiIcon()
|
||||
.padding(.top, 20)
|
||||
.scaleEffect(2)
|
||||
.padding(.vertical, 30)
|
||||
.overlay {
|
||||
Asset.Assets.alertIcon.image
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.offset(x: 25, y: 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(spacing: 40) {
|
||||
ZashiIcon()
|
||||
|
||||
ZashiErrorIcon()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ class HomeTests: XCTestCase {
|
|||
let snapshot = SyncStatusSnapshot.snapshotFor(state: syncState.syncStatus)
|
||||
|
||||
// expected side effects as a result of .onAppear registration
|
||||
await store.receive(.updateDestination(nil))
|
||||
await store.receive(.synchronizerStateChanged(SynchronizerState.zero.redacted)) { state in
|
||||
state.synchronizerStatusSnapshot = snapshot
|
||||
}
|
||||
|
@ -72,32 +71,6 @@ class HomeTests: XCTestCase {
|
|||
await store.finish()
|
||||
}
|
||||
|
||||
@MainActor func testOnAppear_notEnoughSpaceOnDisk() async throws {
|
||||
let store = TestStore(
|
||||
initialState: .initial
|
||||
) {
|
||||
HomeReducer()
|
||||
}
|
||||
|
||||
store.dependencies.diskSpaceChecker = .mockFullDisk
|
||||
store.dependencies.reviewRequest = .noOp
|
||||
|
||||
await store.send(.onAppear) { state in
|
||||
state.requiredTransactionConfirmations = 10
|
||||
}
|
||||
|
||||
// expected side effects as a result of .onAppear registration
|
||||
await store.receive(.updateDestination(.notEnoughFreeDiskSpace)) { state in
|
||||
state.destination = .notEnoughFreeDiskSpace
|
||||
}
|
||||
|
||||
// long-living (cancelable) effects need to be properly canceled.
|
||||
// the .onDisappear action cancels the observer of the synchronizer status change.
|
||||
await store.send(.onDisappear)
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
@MainActor func testSynchronizerErrorBringsUpAlert() async {
|
||||
let testError = ZcashError.synchronizerNotPrepared
|
||||
let errorSnapshot = SyncStatusSnapshot.snapshotFor(
|
||||
|
|
|
@ -46,6 +46,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.databaseFiles = .noOp
|
||||
store.dependencies.databaseFiles.areDbFilesPresentFor = { _ in true }
|
||||
store.dependencies.derivationTool = .liveValue
|
||||
store.dependencies.diskSpaceChecker = .mockEmptyDisk
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.mnemonic = .mock
|
||||
store.dependencies.walletStorage.exportWallet = { storedWallet }
|
||||
|
@ -120,6 +121,7 @@ class AppInitializationTests: XCTestCase {
|
|||
store.dependencies.databaseFiles = .noOp
|
||||
store.dependencies.databaseFiles.areDbFilesPresentFor = { _ in true }
|
||||
store.dependencies.derivationTool = .liveValue
|
||||
store.dependencies.diskSpaceChecker = .mockEmptyDisk
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.mnemonic = .mock
|
||||
store.dependencies.walletStorage.exportWallet = { storedWallet }
|
||||
|
@ -184,6 +186,7 @@ class AppInitializationTests: XCTestCase {
|
|||
|
||||
store.dependencies.databaseFiles = .noOp
|
||||
store.dependencies.databaseFiles.areDbFilesPresentFor = { _ in true }
|
||||
store.dependencies.diskSpaceChecker = .mockEmptyDisk
|
||||
store.dependencies.walletStorage = .noOp
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.walletConfigProvider = .noOp
|
||||
|
@ -222,6 +225,7 @@ class AppInitializationTests: XCTestCase {
|
|||
}
|
||||
|
||||
store.dependencies.databaseFiles = .noOp
|
||||
store.dependencies.diskSpaceChecker = .mockEmptyDisk
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.walletStorage = .noOp
|
||||
store.dependencies.walletConfigProvider = .noOp
|
||||
|
|
|
@ -5,16 +5,20 @@
|
|||
// Created by Michal Fousek on 28.09.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Home
|
||||
import NotEnoughFreeSpace
|
||||
@testable import secant_testnet
|
||||
|
||||
class NotEnoughFeeSpaceSnapshots: XCTestCase {
|
||||
func testNotEnoughFreeSpaceSnapshot() throws {
|
||||
addAttachments(NotEnoughFreeSpaceView(viewStore: ViewStore(HomeStore.placeholder, observe: { $0 })))
|
||||
let store = StoreOf<NotEnoughFreeSpace>(
|
||||
initialState: .initial
|
||||
) {
|
||||
NotEnoughFreeSpace()
|
||||
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
|
||||
}
|
||||
|
||||
addAttachments(NotEnoughFreeSpaceView(store: store))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue