[#224] [Scaffold] Balance Breakdown (#412)

- full screen cover for the balance breakdown
- clearing out the background so we can do semi transparency
- bindings for the full screen cover

[224] [Scaffold] Balance Breakdown

- draft of UI

[224] [Scaffold] Balance Breakdown

- latest block business logic
- mocked auto shielding threshold
- unit tests
- snapshot tests

[224] [Scaffold] Balance Breakdown (412)

- comments resolved
- bigger refactor of the synchronizer, taking advantage of SynchronizerState being reported every time .synced pass
- unit tests fixed and refactored

[224] [Scaffold] Balance Breakdown (412)

- ClearBackgroundView documented, reference to the proposed solution added
This commit is contained in:
Lukas Korba 2022-08-17 15:22:35 +02:00 committed by GitHub
parent 558675aced
commit 368f95e7a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 654 additions and 170 deletions

View File

@ -128,6 +128,9 @@
9E66129B28884BFB00C75B70 /* LocalAuthenticationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E66129A28884BFB00C75B70 /* LocalAuthenticationHandler.swift */; };
9E66129E288938A300C75B70 /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E66129D288938A300C75B70 /* SettingsTests.swift */; };
9E6713F12897F81B00A6796F /* MultiLineTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */; };
9E6713F7289BC58C00A6796F /* BalanceBreakdownStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F6289BC58C00A6796F /* BalanceBreakdownStore.swift */; };
9E6713F8289BC58C00A6796F /* BalanceBreakdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */; };
9E6713FA289BE0E100A6796F /* ClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */; };
9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; };
9E7225F12889539300DF7F17 /* SettingsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */; };
9E7225F3288AB6DD00DF7F17 /* MultipleLineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */; };
@ -155,6 +158,8 @@
9E7FE0F92832824C00C374E8 /* QRCodeScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */; };
9E87ADF128363DE400122FCC /* WrappedAudioServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E87ADF028363DE400122FCC /* WrappedAudioServices.swift */; };
9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E92AF0728530EBF007367AD /* View+UIImage.swift */; };
9E94C62028AA7DEE008256E9 /* BalanceBreakdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */; };
9E94C62328AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */; };
9E9ECC9728589E150099D5A2 /* HomeSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC8C28589E150099D5A2 /* HomeSnapshotTests.swift */; };
9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC8E28589E150099D5A2 /* WelcomeSnapshotTests.swift */; };
9E9ECC9928589E150099D5A2 /* RecoveryPhraseDisplaySnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC9028589E150099D5A2 /* RecoveryPhraseDisplaySnapshotTests.swift */; };
@ -184,7 +189,6 @@
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
9EF8139127F191BF0075AF48 /* WrappedWalletStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139027F191BF0075AF48 /* WrappedWalletStorage.swift */; };
9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */; };
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139B27F47AED0075AF48 /* InitializationState.swift */; };
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
@ -355,6 +359,9 @@
9E66129A28884BFB00C75B70 /* LocalAuthenticationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationHandler.swift; sourceTree = "<group>"; };
9E66129D288938A300C75B70 /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldTests.swift; sourceTree = "<group>"; };
9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownView.swift; sourceTree = "<group>"; };
9E6713F6289BC58C00A6796F /* BalanceBreakdownStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownStore.swift; sourceTree = "<group>"; };
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearBackgroundView.swift; sourceTree = "<group>"; };
9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = "<group>"; };
9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSnapshotTests.swift; sourceTree = "<group>"; };
9E7225F2288AB6DD00DF7F17 /* MultipleLineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleLineTextField.swift; sourceTree = "<group>"; };
@ -380,6 +387,8 @@
9E7FE0F82832824C00C374E8 /* QRCodeScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScanView.swift; sourceTree = "<group>"; };
9E87ADF028363DE400122FCC /* WrappedAudioServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedAudioServices.swift; sourceTree = "<group>"; };
9E92AF0728530EBF007367AD /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = "<group>"; };
9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownTests.swift; sourceTree = "<group>"; };
9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BalanceBreakdownSnapshotTests.swift; sourceTree = "<group>"; };
9E9ECC8C28589E150099D5A2 /* HomeSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeSnapshotTests.swift; sourceTree = "<group>"; };
9E9ECC8E28589E150099D5A2 /* WelcomeSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeSnapshotTests.swift; sourceTree = "<group>"; };
9E9ECC9028589E150099D5A2 /* RecoveryPhraseDisplaySnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplaySnapshotTests.swift; sourceTree = "<group>"; };
@ -440,7 +449,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */,
9E6612312878337F00C75B70 /* Lottie in Frameworks */,
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */,
0DB4E0B42881FD9100947B78 /* ZcashLightClientKit in Frameworks */,
@ -555,6 +563,7 @@
isa = PBXGroup;
children = (
9E391162284E3ECF0073DD9A /* SnapshotTests */,
9E94C61E28AA7DD5008256E9 /* BalanceBreakdownTests */,
9E6713EF2897F80A00A6796F /* MultiLineTextFieldTests */,
9E7CB6222874245400A02233 /* ProfileTests */,
9EAB4674285B5C68002904A0 /* DeeplinkTests */,
@ -718,6 +727,7 @@
0D0781C2278750C00083ACD7 /* Welcome */,
F9971A4927680DC400A2DB75 /* App */,
F93874EC273C4DE200F0E875 /* Home */,
9E6713F2289BC51200A6796F /* BalanceBreakdown */,
F9971A4F27680DD000A2DB75 /* Profile */,
9E7CB61B2874140900A02233 /* AddressDetails */,
F9971A5527680DDE00A2DB75 /* Request */,
@ -856,6 +866,7 @@
9E391162284E3ECF0073DD9A /* SnapshotTests */ = {
isa = PBXGroup;
children = (
9E94C62128AA7ECD008256E9 /* BalanceBreakdownSnapshotTests */,
9E7225EF2889537E00DF7F17 /* SettingsSnapshotTests */,
9E7CB6252874267B00A02233 /* ProfileSnapshotTests */,
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */,
@ -931,6 +942,15 @@
path = MultiLineTextFieldTests;
sourceTree = "<group>";
};
9E6713F2289BC51200A6796F /* BalanceBreakdown */ = {
isa = PBXGroup;
children = (
9E6713F6289BC58C00A6796F /* BalanceBreakdownStore.swift */,
9E6713F5289BC58C00A6796F /* BalanceBreakdownView.swift */,
);
path = BalanceBreakdown;
sourceTree = "<group>";
};
9E7225EF2889537E00DF7F17 /* SettingsSnapshotTests */ = {
isa = PBXGroup;
children = (
@ -1029,6 +1049,7 @@
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */,
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */,
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -1132,6 +1153,22 @@
path = UIKitBridge;
sourceTree = "<group>";
};
9E94C61E28AA7DD5008256E9 /* BalanceBreakdownTests */ = {
isa = PBXGroup;
children = (
9E94C61F28AA7DEE008256E9 /* BalanceBreakdownTests.swift */,
);
path = BalanceBreakdownTests;
sourceTree = "<group>";
};
9E94C62128AA7ECD008256E9 /* BalanceBreakdownSnapshotTests */ = {
isa = PBXGroup;
children = (
9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */,
);
path = BalanceBreakdownSnapshotTests;
sourceTree = "<group>";
};
9E9ECC8B28589E150099D5A2 /* HomeSnapshotTests */ = {
isa = PBXGroup;
children = (
@ -1345,7 +1382,6 @@
packageProductDependencies = (
6654C7392715A38000901167 /* ComposableArchitecture */,
9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */,
9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */,
9EAB466C285A0468002904A0 /* Parsing */,
9EAB466E285A0468002904A0 /* _URLRouting */,
9E7CB6172872D3DF00A02233 /* URLRouting */,
@ -1428,7 +1464,6 @@
packageReferences = (
6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */,
9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */,
9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */,
9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */,
9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */,
9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */,
@ -1574,6 +1609,7 @@
9E7FE0D9282D289B00C374E8 /* WrappedFeedbackGenerator.swift in Sources */,
2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */,
9EBEF87A27CE369800B4F343 /* RecoveryPhraseValidationFlowView.swift in Sources */,
9E6713F7289BC58C00A6796F /* BalanceBreakdownStore.swift in Sources */,
9E66122C2877188700C75B70 /* SyncStatusSnapshot.swift in Sources */,
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */,
0DDB6A5127737D4A0012A410 /* RecoveryPhraseBackupFailedView.swift in Sources */,
@ -1657,6 +1693,7 @@
F9C165B4274031F600592F76 /* Bindings.swift in Sources */,
2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */,
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */,
9E6713F8289BC58C00A6796F /* BalanceBreakdownView.swift in Sources */,
F9971A6627680DFE00A2DB75 /* SettingsView.swift in Sources */,
F96B41EB273B50520021B49A /* Strings.swift in Sources */,
2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */,
@ -1675,6 +1712,7 @@
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
2E8719CB27FB09990082C926 /* TransactionAmountTextField.swift in Sources */,
9E7CB6212874143800A02233 /* AddressDetailsView.swift in Sources */,
9E6713FA289BE0E100A6796F /* ClearBackgroundView.swift in Sources */,
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
);
@ -1694,6 +1732,7 @@
9E7CB6272874269F00A02233 /* ProfileSnapshotTests.swift in Sources */,
9E92AF0828530EBF007367AD /* View+UIImage.swift in Sources */,
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
9E94C62328AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift in Sources */,
9E39112E283F91600073DD9A /* ZatoshiTests.swift in Sources */,
9E9ECC9B28589E150099D5A2 /* ImportWalletSnapshotTests.swift in Sources */,
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */,
@ -1720,6 +1759,7 @@
0DB4E0B12881F2DB00947B78 /* WalletBalance+testing.swift in Sources */,
9E02B56C27FED475005B809B /* DatabaseFilesTests.swift in Sources */,
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */,
9E94C62028AA7DEE008256E9 /* BalanceBreakdownTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2041,8 +2081,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/zcash/ZcashLightClientKit/";
requirement = {
branch = master;
kind = branch;
kind = exactVersion;
version = "0.16.6-beta";
};
};
6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = {
@ -2085,14 +2125,6 @@
minimumVersion = 0.9.2;
};
};
9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/zcash/ZcashLightClientKit";
requirement = {
branch = master;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -2131,11 +2163,6 @@
package = 9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */;
productName = _URLRouting;
};
9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */ = {
isa = XCSwiftPackageProductDependency;
package = 9EF8139627F1FAEC0075AF48 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */;
productName = ZcashLightClientKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 0D4E79FD26B364170058B01E /* Project object */;

View File

@ -203,8 +203,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash/ZcashLightClientKit",
"state" : {
"branch" : "master",
"revision" : "fba4cecbe61cce424ada9fe1f98b05b88d5c8920"
"revision" : "a37c140441d78fa6ad9c89e2fbb539f1ddb7e5a1",
"version" : "0.16.6-beta"
}
}
],

View File

@ -16,6 +16,7 @@ fileprivate enum ZcashSDKConstants {
static let endpointPort = 9067
static let mnemonicWordsMaxCount = 24
static let requiredTransactionConfirmations = 10
static let streamingCallTimeoutInMillis = Int64(10 * 60 * 60 * 1000) // ten hours
}
struct ZCashSDKEnvironment {
@ -33,29 +34,49 @@ struct ZCashSDKEnvironment {
extension ZCashSDKEnvironment {
static let mainnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort),
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointMainnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
),
isMainnet: { true },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort)
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointMainnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
)
),
memoCharLimit: 512,
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .mainnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.14.0-beta"
sdkVersion: "0.16.5-beta"
)
static let testnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort),
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointTestnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
),
isMainnet: { false },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort)
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointTestnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
)
),
memoCharLimit: 512,
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.14.0-beta"
sdkVersion: "0.16.5-beta"
)
}

View File

@ -0,0 +1,121 @@
//
// BalanceBreakdownStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 04.08.2022.
//
import Foundation
import ComposableArchitecture
import ZcashLightClientKit
typealias BalanceBreakdownReducer = Reducer<BalanceBreakdownState, BalanceBreakdownAction, BalanceBreakdownEnvironment>
typealias BalanceBreakdownStore = Store<BalanceBreakdownState, BalanceBreakdownAction>
typealias BalanceBreakdownViewStore = ViewStore<BalanceBreakdownState, BalanceBreakdownAction>
// MARK: - State
struct BalanceBreakdownState: Equatable {
var autoShieldingTreshold: Zatoshi
var latestBlock: String
var shieldedBalance: WalletBalance
var transparentBalance: WalletBalance
var totalBalance: Zatoshi {
shieldedBalance.total + transparentBalance.total
}
}
// MARK: - Action
enum BalanceBreakdownAction: Equatable {
case onAppear
case onDisappear
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case updateLatestBlock
case updateSynchronizerStatus
}
// MARK: - Environment
struct BalanceBreakdownEnvironment {
let numberFormatter: WrappedNumberFormatter
let SDKSynchronizer: WrappedSDKSynchronizer
let scheduler: AnySchedulerOf<DispatchQueue>
}
extension BalanceBreakdownEnvironment {
static let live = BalanceBreakdownEnvironment(
numberFormatter: .live(),
SDKSynchronizer: LiveWrappedSDKSynchronizer(),
scheduler: DispatchQueue.main.eraseToAnyScheduler()
)
static let mock = BalanceBreakdownEnvironment(
numberFormatter: .live(),
SDKSynchronizer: MockWrappedSDKSynchronizer(),
scheduler: DispatchQueue.main.eraseToAnyScheduler()
)
}
// MARK: - Reducer
extension BalanceBreakdownReducer {
private enum CancelId {}
static let `default` = BalanceBreakdownReducer { state, action, environment in
switch action {
case .onAppear:
return environment.SDKSynchronizer.stateChanged
.map(BalanceBreakdownAction.synchronizerStateChanged)
.eraseToEffect()
.cancellable(id: CancelId.self, cancelInFlight: true)
case .onDisappear:
return Effect.cancel(id: CancelId.self)
case .synchronizerStateChanged(.synced):
return Effect(value: .updateSynchronizerStatus)
case .synchronizerStateChanged(let synchronizerState):
return Effect(value: .updateSynchronizerStatus)
case .updateSynchronizerStatus:
if let shieldedBalance = environment.SDKSynchronizer.latestScannedSynchronizerState?.shieldedBalance {
state.shieldedBalance = shieldedBalance
}
if let transparentBalance = environment.SDKSynchronizer.latestScannedSynchronizerState?.transparentBalance {
state.transparentBalance = transparentBalance
}
return Effect(value: .updateLatestBlock)
case .updateLatestBlock:
guard let latestBlockNumber = environment.SDKSynchronizer.latestScannedSynchronizerState?.latestScannedHeight,
let latestBlock = environment.numberFormatter.string(NSDecimalNumber(value: latestBlockNumber)) else {
state.latestBlock = "unknown"
return .none
}
state.latestBlock = "\(latestBlock)"
return .none
}
}
}
// MARK: - Placeholders
extension BalanceBreakdownState {
static let placeholder = BalanceBreakdownState(
autoShieldingTreshold: Zatoshi(1_000_000),
latestBlock: "unknown",
shieldedBalance: WalletBalance.zero,
transparentBalance: WalletBalance.zero
)
}
extension BalanceBreakdownStore {
static let placeholder = BalanceBreakdownStore(
initialState: .placeholder,
reducer: .default,
environment: .live
)
}

View File

@ -0,0 +1,68 @@
//
// BalanceBreakdownView.swift
// secant-testnet
//
// Created by Lukáš Korba on 04.08.2022.
//
import SwiftUI
import ComposableArchitecture
import ZcashLightClientKit
struct BalanceBreakdown: View {
let store: BalanceBreakdownStore
var body: some View {
WithViewStore(store) { viewStore in
VStack {
HStack {
Spacer()
Text("Block: \(viewStore.latestBlock)")
}
.padding(.horizontal, 50)
VStack(alignment: .leading, spacing: 10) {
balanceView(title: "SHIELDED ZEC (SPENDABLE)", viewStore.shieldedBalance.total, titleColor: Asset.Colors.Text.balanceText.color)
balanceView(title: "TRANSPARENT BALANCE", viewStore.transparentBalance.total)
balanceView(title: "TOTAL BALANCE", viewStore.totalBalance)
}
.padding(30)
.background(Asset.Colors.ScreenBackground.modalDialog.color)
.cornerRadius(8)
.onAppear { viewStore.send(.onAppear) }
HStack {
Spacer()
Text("Auto Shielding Treshold: \(viewStore.autoShieldingTreshold.decimalString()) ZEC")
}
.padding(.horizontal, 50)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.applySemiTransparentScreenBackground()
.edgesIgnoringSafeArea(.all)
.onTapGesture {
viewStore.send(.onDisappear)
}
}
.background(ClearBackgroundView())
}
}
extension BalanceBreakdown {
func balanceView(title: String, _ balance: Zatoshi, titleColor: Color = .white) -> some View {
VStack(alignment: .leading) {
Text("\(title)")
.foregroundColor(titleColor)
Text("$\(balance.decimalString(formatter: NumberFormatter.zcashNumberFormatter8FractionDigits))")
.font(.custom(FontFamily.Zboto.regular.name, size: 40))
.foregroundColor(Color.white)
}
}
}
struct BalanceBreakdown_Previews: PreviewProvider {
static var previews: some View {
BalanceBreakdown(store: .placeholder)
.preferredColorScheme(.dark)
}
}

View File

@ -17,25 +17,26 @@ struct HomeState: Equatable {
case request
case send
case scan
case balanceBreakdown
}
var route: Route?
var balanceBreakdown: BalanceBreakdownState
var drawerOverlay: DrawerOverlay
var profileState: ProfileState
var requestState: RequestState
var requiredTransactionConfirmations = 0
var sendState: SendFlowState
var scanState: ScanState
var sendState: SendFlowState
var shieldedBalance: WalletBalance
var synchronizerStatusSnapshot: SyncStatusSnapshot
var totalBalance: Zatoshi
var walletEventsState: WalletEventsFlowState
var verifiedBalance: Zatoshi
// TODO: - Get the ZEC price from the SDK, issue 311, https://github.com/zcash/secant-ios-wallet/issues/311
var zecPrice = Decimal(140.0)
var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: totalBalance.decimalValue.decimalValue * zecPrice)
Zatoshi.from(decimal: shieldedBalance.total.decimalValue.decimalValue * zecPrice)
}
var isDownloading: Bool {
@ -56,6 +57,7 @@ struct HomeState: Equatable {
// MARK: Action
enum HomeAction: Equatable {
case balanceBreakdown(BalanceBreakdownAction)
case debugMenuStartup
case onAppear
case onDisappear
@ -65,7 +67,6 @@ enum HomeAction: Equatable {
case scan(ScanAction)
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case walletEvents(WalletEventsFlowAction)
case updateBalance(WalletBalance)
case updateDrawer(DrawerOverlay)
case updateRoute(HomeState.Route?)
case updateSynchronizerStatus
@ -109,7 +110,8 @@ extension HomeReducer {
historyReducer,
sendReducer,
scanReducer,
profileReducer
profileReducer,
balanceBreakdownReducer
]
)
@ -137,11 +139,6 @@ extension HomeReducer {
case .synchronizerStateChanged(let synchronizerState):
return Effect(value: .updateSynchronizerStatus)
case .updateBalance(let balance):
state.totalBalance = balance.total
state.verifiedBalance = balance.verified
return .none
case .updateDrawer(let drawerOverlay):
state.drawerOverlay = drawerOverlay
state.walletEventsState.isScrollable = drawerOverlay == .full ? true : false
@ -152,11 +149,10 @@ extension HomeReducer {
case .updateSynchronizerStatus:
state.synchronizerStatusSnapshot = environment.SDKSynchronizer.statusSnapshot()
return environment.SDKSynchronizer.getShieldedBalance()
.receive(on: environment.scheduler)
.map({ WalletBalance(verified: $0.verified, total: $0.total) })
.map(HomeAction.updateBalance)
.eraseToEffect()
if let shieldedBalance = environment.SDKSynchronizer.latestScannedSynchronizerState?.shieldedBalance {
state.shieldedBalance = shieldedBalance
}
return .none
case .updateRoute(let route):
state.route = route
@ -212,6 +208,13 @@ extension HomeReducer {
case .scan(let action):
return .none
case .balanceBreakdown(.onDisappear):
state.route = nil
return .none
case .balanceBreakdown:
return .none
case .debugMenuStartup:
return .none
}
@ -272,6 +275,18 @@ extension HomeReducer {
)
}
)
private static let balanceBreakdownReducer: HomeReducer = BalanceBreakdownReducer.default.pullback(
state: \HomeState.balanceBreakdown,
action: /HomeAction.balanceBreakdown,
environment: { environment in
BalanceBreakdownEnvironment(
numberFormatter: .live(),
SDKSynchronizer: environment.SDKSynchronizer,
scheduler: environment.scheduler
)
}
)
}
// MARK: - Store
@ -311,6 +326,13 @@ extension HomeStore {
action: HomeAction.scan
)
}
func balanceBreakdownStore() -> BalanceBreakdownStore {
self.scope(
state: \.balanceBreakdown,
action: HomeAction.balanceBreakdown
)
}
}
// MARK: - ViewStore
@ -338,15 +360,15 @@ extension HomeViewStore {
extension HomeState {
static var placeholder: Self {
.init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: WalletBalance.zero,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi.zero,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: Zatoshi.zero
walletEventsState: .emptyPlaceHolder
)
}
}

View File

@ -27,6 +27,9 @@ struct HomeView: View {
.navigationBarHidden(true)
.onAppear(perform: { viewStore.send(.onAppear) })
.onDisappear(perform: { viewStore.send(.onDisappear) })
.fullScreenCover(isPresented: viewStore.bindingForRoute(.balanceBreakdown)) {
BalanceBreakdown(store: store.balanceBreakdownStore())
}
}
}
}
@ -120,20 +123,24 @@ extension HomeView {
.padding(.top, 50)
VStack {
Text("$\(viewStore.totalBalance.decimalString())")
.font(.custom(FontFamily.Zboto.regular.name, size: 40))
.foregroundColor(Asset.Colors.Text.balanceText.color)
.accessDebugMenuWithHiddenGesture {
viewStore.send(.debugMenuStartup)
}
.padding(.top, 80)
Button {
viewStore.send(.updateRoute(.balanceBreakdown))
} label: {
Text("$\(viewStore.shieldedBalance.total.decimalString())")
.font(.custom(FontFamily.Zboto.regular.name, size: 40))
.foregroundColor(Asset.Colors.Text.balanceText.color)
.padding(.top, 80)
}
Text("$\(viewStore.totalCurrencyBalance.decimalString())")
.font(.custom(FontFamily.Rubik.regular.name, size: 13))
.opacity(0.6)
.padding(.bottom, 50)
Text("\(viewStore.synchronizerStatusSnapshot.message)")
.accessDebugMenuWithHiddenGesture {
viewStore.send(.debugMenuStartup)
}
}
}

View File

@ -26,7 +26,7 @@ struct SendFlowState: Equatable {
var isSendingTransaction = false
var memoState: MultiLineTextFieldState
var route: Route?
var totalBalance = Zatoshi.zero
var shieldedBalance = WalletBalance.zero
var transactionAddressInputState: TransactionAddressTextFieldState
var transactionAmountInputState: TransactionAmountTextFieldState
@ -67,7 +67,7 @@ struct SendFlowState: Equatable {
}
var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: totalBalance.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
Zatoshi.from(decimal: shieldedBalance.total.decimalValue.decimalValue * transactionAmountInputState.zecPrice)
}
}
@ -82,7 +82,6 @@ enum SendFlowAction: Equatable {
case synchronizerStateChanged(WrappedSDKSynchronizerState)
case transactionAddressInput(TransactionAddressTextFieldAction)
case transactionAmountInput(TransactionAmountTextFieldAction)
case updateBalance(Zatoshi)
case updateRoute(SendFlowState.Route?)
}
@ -183,18 +182,13 @@ extension SendFlowReducer {
return Effect.cancel(id: SyncStatusUpdatesID())
case .synchronizerStateChanged(.synced):
return environment.SDKSynchronizer.getShieldedBalance()
.receive(on: environment.scheduler)
.map({ $0.total })
.map(SendFlowAction.updateBalance)
.eraseToEffect()
case .synchronizerStateChanged(let synchronizerState):
if let shieldedBalance = environment.SDKSynchronizer.latestScannedSynchronizerState?.shieldedBalance {
state.shieldedBalance = shieldedBalance
state.transactionAmountInputState.maxValue = shieldedBalance.total.amount
}
return .none
case .updateBalance(let balance):
state.totalBalance = balance
state.transactionAmountInputState.maxValue = balance.amount
case .synchronizerStateChanged(let synchronizerState):
return .none
case .memo:

View File

@ -10,7 +10,7 @@ struct CreateTransaction: View {
return WithViewStore(store) { viewStore in
VStack {
VStack(spacing: 0) {
Text("WalletBalance \(viewStore.totalBalance.decimalString()) ZEC")
Text("WalletBalance \(viewStore.shieldedBalance.total.decimalString()) ZEC")
Text("($\(viewStore.totalCurrencyBalance.decimalString()))")
.font(.system(size: 13))
.opacity(0.6)

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "243",
"green" : "228",
"red" : "210"
"blue" : "0xF3",
"green" : "0xE4",
"red" : "0xD2"
}
},
"idiom" : "universal"

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "249",
"green" : "239",
"red" : "227"
"blue" : "0xF9",
"green" : "0xEF",
"red" : "0xE3"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.953",
"green" : "0.894",
"red" : "0.824"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.323",
"green" : "0.099",
"red" : "0.083"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.900",
"blue" : "0xF3",
"green" : "0xE4",
"red" : "0xD2"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.900",
"blue" : "0x55",
"green" : "0x31",
"red" : "0x24"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.900",
"blue" : "0xF9",
"green" : "0xEF",
"red" : "0xE3"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.900",
"blue" : "0x5A",
"green" : "0x36",
"red" : "0x29"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -95,8 +95,11 @@ internal enum Asset {
internal static let gradientStart = ColorAsset(name: "gradientStart")
internal static let greenGradientEnd = ColorAsset(name: "greenGradientEnd")
internal static let greenGradientStart = ColorAsset(name: "greenGradientStart")
internal static let modalDialog = ColorAsset(name: "modalDialog")
internal static let redGradientEnd = ColorAsset(name: "redGradientEnd")
internal static let redGradientStart = ColorAsset(name: "redGradientStart")
internal static let semiTransparentGradientEnd = ColorAsset(name: "semiTransparentGradientEnd")
internal static let semiTransparentGradientStart = ColorAsset(name: "semiTransparentGradientStart")
}
internal enum Shadow {
internal static let drawerShadow = ColorAsset(name: "drawerShadow")

View File

@ -86,6 +86,17 @@ extension View {
)
)
}
func applySemiTransparentScreenBackground() -> some View {
self.modifier(
ScreenBackgroundModifier(
colors: [
Asset.Colors.ScreenBackground.semiTransparentGradientStart.color,
Asset.Colors.ScreenBackground.semiTransparentGradientEnd.color
]
)
)
}
}
struct ScreenBackground_Previews: PreviewProvider {

View File

@ -0,0 +1,25 @@
//
// ClearBackgroundView.swift
// secant-testnet
//
// Created by Lukáš Korba on 04.08.2022.
//
import SwiftUI
/// Purpose of this utility is to solve background transparency of the view used for example in `.fullScreenCover`.
/// Usually used for the modal full screen views with semi-transparent backgrounds.
struct ClearBackgroundView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView()
/// Wrapped in the dispatch queue to achieve the background clearance,
/// it doesn't work otherwise (opaque background instead of fully transparent).
/// Comes from https://stackoverflow.com/a/66925883
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}

View File

@ -16,6 +16,15 @@ extension NumberFormatter {
formatter.usesGroupingSeparator = true
return formatter
}()
static let zcashNumberFormatter8FractionDigits: NumberFormatter = {
var formatter = NumberFormatter()
formatter.minimumFractionDigits = 8
formatter.maximumIntegerDigits = 8
formatter.numberStyle = .decimal
formatter.usesGroupingSeparator = true
return formatter
}()
}
struct WrappedNumberFormatter {

View File

@ -37,15 +37,18 @@ protocol WrappedSDKSynchronizer {
var synchronizer: SDKSynchronizer? { get }
var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never> { get }
var walletBirthday: BlockHeight? { get }
var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState? { get }
func prepareWith(initializer: Initializer) throws
func start(retry: Bool) throws
func stop()
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?)
func statusSnapshot() -> SyncStatusSnapshot
func rewind(_ policy: RewindPolicy) throws
func getShieldedBalance() -> Effect<WalletBalance, Never>
func getShieldedBalance() -> WalletBalance?
func getTransparentBalance() -> WalletBalance?
func getAllClearedTransactions() -> Effect<[WalletEvent], Never>
func getAllPendingTransactions() -> Effect<[WalletEvent], Never>
func getAllTransactions() -> Effect<[WalletEvent], Never>
@ -83,6 +86,7 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
private(set) var notificationCenter: WrappedNotificationCenter
private(set) var walletBirthday: BlockHeight?
private(set) var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState?
init(notificationCenter: WrappedNotificationCenter = .live) {
self.notificationCenter = notificationCenter
@ -103,7 +107,10 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
notificationCenter.publisherFor(.synchronizerSynced)?
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in self?.synchronizerSynced() }
.sink { [weak self] output in
let synchronizerState = output.userInfo?[SDKSynchronizer.NotificationKeys.synchronizerState] as? SDKSynchronizer.SynchronizerState
self?.synchronizerSynced(synchronizerState)
}
.store(in: &cancellables)
notificationCenter.publisherFor(.synchronizerProgressUpdated)?
@ -133,8 +140,9 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
stateChanged.send(.started)
}
func synchronizerSynced() {
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) {
stateChanged.send(.synced)
latestScannedSynchronizerState = synchronizerState
}
func synchronizerProgressUpdated() {
@ -176,15 +184,14 @@ class LiveWrappedSDKSynchronizer: WrappedSDKSynchronizer {
}
}
func getShieldedBalance() -> Effect<WalletBalance, Never> {
if let shieldedVerifiedBalance: Zatoshi = synchronizer?.getShieldedVerifiedBalance(),
let shieldedTotalBalance: Zatoshi = synchronizer?.getShieldedBalance(accountIndex: 0) {
return Effect(value: WalletBalance(verified: shieldedVerifiedBalance, total: shieldedTotalBalance))
}
return .none
func getShieldedBalance() -> WalletBalance? {
latestScannedSynchronizerState?.shieldedBalance
}
func getTransparentBalance() -> WalletBalance? {
latestScannedSynchronizerState?.transparentBalance
}
func getAllClearedTransactions() -> Effect<[WalletEvent], Never> {
if let clearedTransactions = try? synchronizer?.allClearedTransactions() {
return Effect(value: clearedTransactions.map {
@ -282,6 +289,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
private(set) var synchronizer: SDKSynchronizer?
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
private(set) var walletBirthday: BlockHeight?
private(set) var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState?
init(notificationCenter: WrappedNotificationCenter = .mock) {
self.notificationCenter = notificationCenter
@ -304,7 +312,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
synchronizer?.stop()
}
func synchronizerSynced() {
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) {
stateChanged.send(.synced)
}
@ -318,10 +326,14 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func rewind(_ policy: RewindPolicy) throws { }
func getShieldedBalance() -> Effect<WalletBalance, Never> {
return Effect(value: WalletBalance(verified: Zatoshi(12345000), total: Zatoshi(12345000)))
func getShieldedBalance() -> WalletBalance? {
WalletBalance(verified: Zatoshi(12345000), total: Zatoshi(12345000))
}
func getTransparentBalance() -> WalletBalance? {
WalletBalance(verified: Zatoshi(12345000), total: Zatoshi(12345000))
}
func getAllClearedTransactions() -> Effect<[WalletEvent], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: false), uuid: "1"),
@ -414,6 +426,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
private(set) var synchronizer: SDKSynchronizer?
private(set) var stateChanged: CurrentValueSubject<WrappedSDKSynchronizerState, Never>
private(set) var walletBirthday: BlockHeight?
private(set) var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState?
init(notificationCenter: WrappedNotificationCenter = .mock) {
self.notificationCenter = notificationCenter
@ -426,16 +439,16 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
func stop() { }
func synchronizerSynced() { }
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?) { }
func statusSnapshot() -> SyncStatusSnapshot { .default }
func rewind(_ policy: RewindPolicy) throws { }
func getShieldedBalance() -> Effect<WalletBalance, Never> {
return .none
}
func getShieldedBalance() -> WalletBalance? { nil }
func getTransparentBalance() -> WalletBalance? { nil }
func getAllClearedTransactions() -> Effect<[WalletEvent], Never> {
let mocked: [TransactionStateMockHelper] = [
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: false), uuid: "aa11"),

View File

@ -0,0 +1,42 @@
//
// BalanceBreakdownTests.swift
// secantTests
//
// Created by Lukáš Korba on 15.08.2022.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
import ZcashLightClientKit
class BalanceBreakdownTests: XCTestCase {
func testOnAppear() throws {
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
let store = TestStore(
initialState: .placeholder,
reducer: BalanceBreakdownReducer.default,
environment:
BalanceBreakdownEnvironment(
numberFormatter: .live(),
SDKSynchronizer: MockWrappedSDKSynchronizer(),
scheduler: testScheduler.eraseToAnyScheduler()
)
)
store.send(.onAppear)
testScheduler.advance(by: 0.1)
// expected side effects as a result of .onAppear registration
store.receive(.synchronizerStateChanged(.unknown))
store.receive(.updateSynchronizerStatus)
store.receive(.updateLatestBlock)
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancles the observer of the synchronizer status change.
store.send(.onDisappear)
}
}

View File

@ -10,7 +10,6 @@ import XCTest
import ComposableArchitecture
import ZcashLightClientKit
// swiftlint:disable type_body_length
class HomeTests: XCTestCase {
func testSynchronizerStateChanged_AnyButSynced() throws {
// setup the store and environment to be fully mocked
@ -38,18 +37,11 @@ class HomeTests: XCTestCase {
testScheduler.advance(by: 0.01)
store.receive(.updateSynchronizerStatus)
let balance = WalletBalance(verified: Zatoshi(12_345_000), total: Zatoshi(12_345_000))
store.receive(.updateBalance(balance)) { state in
state.totalBalance = Zatoshi(12_345_000)
state.verifiedBalance = Zatoshi(12_345_000)
}
}
/// When the synchronizer status change to .synced, several things happen
/// 1. the .updateSynchronizerStatus is called
/// 2. the side effect to update the transactions history is called
/// 3. the side effect to update the balance is called
func testSynchronizerStateChanged_Synced() throws {
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
@ -99,14 +91,6 @@ class HomeTests: XCTestCase {
}
store.receive(.updateWalletEvents(walletEvents))
// ad 3.
let balance = WalletBalance(verified: Zatoshi(12_345_000), total: Zatoshi(12_345_000))
store.receive(.updateBalance(balance)) { state in
state.verifiedBalance = Zatoshi(12_345_000)
state.totalBalance = Zatoshi(12_345_000)
}
}
func testWalletEventsPartial_to_FullDrawer() throws {
@ -125,15 +109,15 @@ class HomeTests: XCTestCase {
)
let homeState = HomeState(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: WalletBalance.zero,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi.zero,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: Zatoshi.zero
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
@ -168,15 +152,15 @@ class HomeTests: XCTestCase {
)
let homeState = HomeState(
balanceBreakdown: .placeholder,
drawerOverlay: .full,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: WalletBalance.zero,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi.zero,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: Zatoshi.zero
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
@ -227,12 +211,6 @@ class HomeTests: XCTestCase {
// expected side effects as a result of .onAppear registration
store.receive(.synchronizerStateChanged(.unknown))
store.receive(.updateSynchronizerStatus)
let balance = WalletBalance(verified: Zatoshi(12_345_000), total: Zatoshi(12_345_000))
store.receive(.updateBalance(balance)) { state in
state.totalBalance = Zatoshi(12_345_000)
state.verifiedBalance = Zatoshi(12_345_000)
}
// long-living (cancelable) effects need to be properly canceled.
// the .onDisappear action cancles the observer of the synchronizer status change.
@ -256,15 +234,15 @@ class HomeTests: XCTestCase {
let homeState = HomeState(
route: .profile,
balanceBreakdown: .placeholder,
drawerOverlay: .full,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: WalletBalance.zero,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi.zero,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: Zatoshi.zero
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
@ -295,15 +273,15 @@ class HomeTests: XCTestCase {
let homeState = HomeState(
route: .profile,
balanceBreakdown: .placeholder,
drawerOverlay: .full,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: WalletBalance.zero,
synchronizerStatusSnapshot: .default,
totalBalance: Zatoshi.zero,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: Zatoshi.zero
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(

View File

@ -32,7 +32,7 @@ class ProfileTests: XCTestCase {
state.address = "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8"
state.appVersion = "0.0.1"
state.appBuild = "31"
state.sdkVersion = "0.14.0-beta"
state.sdkVersion = "0.16.5-beta"
}
}
}

View File

@ -536,7 +536,7 @@ class SendTests: XCTestCase {
func testInvalidForm_ExceededMemoCharLimit() throws {
let sendState = SendFlowState(
memoState: MultiLineTextFieldState(charLimit: 3),
totalBalance: Zatoshi(1),
shieldedBalance: WalletBalance(verified: Zatoshi(1), total: Zatoshi(1)),
transactionAddressInputState:
TransactionAddressTextFieldState(
isValidAddress: true,

View File

@ -0,0 +1,29 @@
//
// BalanceBreakdownSnapshotTests.swift
// secantTests
//
// Created by Lukáš Korba on 15.08.2022.
//
import XCTest
@testable import secant_testnet
import ComposableArchitecture
import ZcashLightClientKit
import SwiftUI
class BalanceBreakdownSnapshotTests: XCTestCase {
func testBalanceBreakdownSnapshot() throws {
let store = Store(
initialState: BalanceBreakdownState(
autoShieldingTreshold: Zatoshi(1_000_000),
latestBlock: "unknown",
shieldedBalance: WalletBalance(verified: Zatoshi(123_000_000_000), total: Zatoshi(123_000_000_000)),
transparentBalance: WalletBalance(verified: Zatoshi(850_000_000), total: Zatoshi(850_000_000))
),
reducer: BalanceBreakdownReducer.default,
environment: .mock
)
addAttachments(BalanceBreakdown(store: store))
}
}

View File

@ -42,15 +42,15 @@ class HomeCircularProgressSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: balance.verified
walletEventsState: .emptyPlaceHolder
),
reducer: .default,
environment: testEnvironment
@ -90,15 +90,15 @@ class HomeCircularProgressSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: balance.verified
walletEventsState: .emptyPlaceHolder
),
reducer: .default,
environment: testEnvironment
@ -131,15 +131,15 @@ class HomeCircularProgressSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .emptyPlaceHolder,
verifiedBalance: balance.verified
walletEventsState: .emptyPlaceHolder
),
reducer: .default,
environment: testEnvironment

View File

@ -37,15 +37,15 @@ class HomeSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents))
),
reducer: .default,
environment: .demo

View File

@ -38,15 +38,15 @@ class WalletEventsSnapshotTests: XCTestCase {
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents))
),
reducer: .default,
environment: .demo
@ -85,15 +85,15 @@ class WalletEventsSnapshotTests: XCTestCase {
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
),
reducer: .default,
environment: .demo
@ -142,15 +142,15 @@ class WalletEventsSnapshotTests: XCTestCase {
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
),
reducer: .default,
environment: .demo
@ -199,15 +199,15 @@ class WalletEventsSnapshotTests: XCTestCase {
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
),
reducer: .default,
environment: .demo
@ -262,15 +262,15 @@ class WalletEventsSnapshotTests: XCTestCase {
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
let store = HomeStore(
initialState: .init(
balanceBreakdown: .placeholder,
drawerOverlay: .partial,
profileState: .placeholder,
requestState: .placeholder,
sendState: .placeholder,
scanState: .placeholder,
sendState: .placeholder,
shieldedBalance: balance,
synchronizerStatusSnapshot: .default,
totalBalance: balance.total,
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])),
verifiedBalance: balance.verified
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
),
reducer: .default,
environment: .demo