- Concept of WalletEvents removed and replaced with TransactionState and TransactionList - UI updated to enable list of transactions and expansion with the details - Color naming cleanups and refactors - Conditional font modifier implemented - Conditional strikethrough modifier implemented - Title + tests of the title for the transaction implemented - Color + tests for the title of the transaction implemented - All texts localized - tests fot the TCA TransactionList implemented
This commit is contained in:
parent
3fa10e5147
commit
34f0077604
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "TransactionList"
|
||||
BuildableName = "TransactionList"
|
||||
BlueprintName = "TransactionList"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "TransactionList"
|
||||
BuildableName = "TransactionList"
|
||||
BlueprintName = "TransactionList"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -52,7 +52,7 @@ let package = Package(
|
|||
.library(name: "UserPreferencesStorage", targets: ["UserPreferencesStorage"]),
|
||||
.library(name: "Utils", targets: ["Utils"]),
|
||||
.library(name: "WalletConfigProvider", targets: ["WalletConfigProvider"]),
|
||||
.library(name: "WalletEventsFlow", targets: ["WalletEventsFlow"]),
|
||||
.library(name: "TransactionList", targets: ["TransactionList"]),
|
||||
.library(name: "WalletStorage", targets: ["WalletStorage"]),
|
||||
.library(name: "Welcome", targets: ["Welcome"]),
|
||||
.library(name: "ZcashSDKEnvironment", targets: ["ZcashSDKEnvironment"])
|
||||
|
@ -209,7 +209,7 @@ let package = Package(
|
|||
"SDKSynchronizer",
|
||||
"UIComponents",
|
||||
"Utils",
|
||||
"WalletEventsFlow",
|
||||
"TransactionList",
|
||||
"ZcashSDKEnvironment",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
|
||||
|
@ -364,7 +364,7 @@ let package = Package(
|
|||
"RecoveryPhraseDisplay",
|
||||
"Scan",
|
||||
"SendFlow",
|
||||
"WalletEventsFlow",
|
||||
"TransactionList",
|
||||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
|
||||
],
|
||||
|
@ -531,7 +531,7 @@ let package = Package(
|
|||
path: "Sources/Dependencies/WalletConfigProvider"
|
||||
),
|
||||
.target(
|
||||
name: "WalletEventsFlow",
|
||||
name: "TransactionList",
|
||||
dependencies: [
|
||||
"Generated",
|
||||
"Models",
|
||||
|
@ -543,7 +543,7 @@ let package = Package(
|
|||
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
|
||||
.product(name: "ZcashLightClientKit", package: "ZcashLightClientKit")
|
||||
],
|
||||
path: "Sources/Features/WalletEventsFlow"
|
||||
path: "Sources/Features/TransactionList"
|
||||
),
|
||||
.target(
|
||||
name: "WalletStorage",
|
||||
|
|
|
@ -33,7 +33,7 @@ public struct SDKSynchronizerClient {
|
|||
|
||||
public let getShieldedBalance: () -> WalletBalance?
|
||||
public let getTransparentBalance: () -> WalletBalance?
|
||||
public var getAllTransactions: () async throws -> [WalletEvent]
|
||||
public var getAllTransactions: () async throws -> [TransactionState]
|
||||
|
||||
public let getUnifiedAddress: (_ account: Int) async throws -> UnifiedAddress?
|
||||
public let getTransparentAddress: (_ account: Int) async throws -> TransparentAddress?
|
||||
|
|
|
@ -52,7 +52,7 @@ extension SDKSynchronizerClient {
|
|||
getAllTransactions: {
|
||||
let clearedTransactions = try await synchronizer.allTransactions()
|
||||
|
||||
var clearedTxs: [WalletEvent] = []
|
||||
var clearedTxs: [TransactionState] = []
|
||||
|
||||
for clearedTransaction in clearedTransactions {
|
||||
var transaction = TransactionState.init(
|
||||
|
@ -72,13 +72,7 @@ extension SDKSynchronizerClient {
|
|||
|
||||
transaction.zAddress = addresses.first?.stringEncoded
|
||||
|
||||
clearedTxs.append(
|
||||
WalletEvent(
|
||||
id: transaction.id,
|
||||
state: .transaction(transaction),
|
||||
timestamp: transaction.timestamp
|
||||
)
|
||||
)
|
||||
clearedTxs.append(transaction)
|
||||
}
|
||||
|
||||
return clearedTxs
|
||||
|
|
|
@ -74,11 +74,11 @@ extension SDKSynchronizerClient {
|
|||
rewind: @escaping (RewindPolicy) -> AnyPublisher<Void, Error> = { _ in return Empty<Void, Error>().eraseToAnyPublisher() },
|
||||
getShieldedBalance: @escaping () -> WalletBalance? = { WalletBalance(verified: Zatoshi(12345000), total: Zatoshi(12345000)) },
|
||||
getTransparentBalance: @escaping () -> WalletBalance? = { WalletBalance(verified: Zatoshi(12345000), total: Zatoshi(12345000)) },
|
||||
getAllTransactions: @escaping () -> [WalletEvent] = {
|
||||
getAllTransactions: @escaping () -> [TransactionState] = {
|
||||
let mockedCleared: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: false), uuid: "aa11"),
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid, uuid: "aa11"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), uuid: "bb22"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .paid(success: true), uuid: "cc33"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .paid, uuid: "cc33"),
|
||||
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(4), uuid: "dd44"),
|
||||
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(5), uuid: "ee55")
|
||||
]
|
||||
|
@ -93,18 +93,18 @@ extension SDKSynchronizerClient {
|
|||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
return WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp ?? 0)
|
||||
return transaction
|
||||
}
|
||||
|
||||
let mockedPending: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(
|
||||
date: 1651039606,
|
||||
amount: Zatoshi(6),
|
||||
status: .paid(success: false),
|
||||
status: .paid,
|
||||
uuid: "ff66"
|
||||
),
|
||||
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(7), uuid: "gg77"),
|
||||
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(8), status: .paid(success: true), uuid: "hh88"),
|
||||
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(8), status: .paid, uuid: "hh88"),
|
||||
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(9), uuid: "ii99")
|
||||
]
|
||||
|
||||
|
@ -118,7 +118,7 @@ extension SDKSynchronizerClient {
|
|||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
return WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
return transaction
|
||||
}
|
||||
|
||||
clearedTransactions.append(contentsOf: pendingTransactions)
|
||||
|
@ -156,7 +156,7 @@ extension SDKSynchronizerClient {
|
|||
zAddress: "tteafadlamnelkqe",
|
||||
fee: Zatoshi(10),
|
||||
id: "id",
|
||||
status: .paid(success: true),
|
||||
status: .paid,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(10)
|
||||
)
|
||||
|
@ -170,7 +170,7 @@ extension SDKSynchronizerClient {
|
|||
zAddress: "tteafadlamnelkqe",
|
||||
fee: Zatoshi(10),
|
||||
id: "id",
|
||||
status: .paid(success: true),
|
||||
status: .paid,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(10)
|
||||
)
|
||||
|
|
|
@ -56,7 +56,7 @@ public struct AddressDetailsView: View {
|
|||
|
||||
Text(address)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 16))
|
||||
.foregroundColor(Asset.Colors.suppressed47.color)
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
.frame(width: 270)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import Utils
|
|||
import Models
|
||||
import Generated
|
||||
import ReviewRequest
|
||||
import WalletEventsFlow
|
||||
import TransactionList
|
||||
import Scan
|
||||
|
||||
public typealias HomeStore = Store<HomeReducer.State, HomeReducer.Action>
|
||||
|
@ -32,7 +32,7 @@ public struct HomeReducer: ReducerProtocol {
|
|||
public var shieldedBalance: Balance
|
||||
public var synchronizerStatusSnapshot: SyncStatusSnapshot
|
||||
public var walletConfig: WalletConfig
|
||||
public var walletEventsState: WalletEventsFlowReducer.State
|
||||
public var transactionListState: TransactionListReducer.State
|
||||
public var migratingDatabase = true
|
||||
// TODO: [#311] - Get the ZEC price from the SDK, https://github.com/zcash/secant-ios-wallet/issues/311
|
||||
public var zecPrice = Decimal(140.0)
|
||||
|
@ -53,7 +53,7 @@ public struct HomeReducer: ReducerProtocol {
|
|||
shieldedBalance: Balance,
|
||||
synchronizerStatusSnapshot: SyncStatusSnapshot,
|
||||
walletConfig: WalletConfig,
|
||||
walletEventsState: WalletEventsFlowReducer.State,
|
||||
transactionListState: TransactionListReducer.State,
|
||||
zecPrice: Decimal = Decimal(140.0)
|
||||
) {
|
||||
self.destination = destination
|
||||
|
@ -63,7 +63,7 @@ public struct HomeReducer: ReducerProtocol {
|
|||
self.shieldedBalance = shieldedBalance
|
||||
self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
|
||||
self.walletConfig = walletConfig
|
||||
self.walletEventsState = walletEventsState
|
||||
self.transactionListState = transactionListState
|
||||
self.zecPrice = zecPrice
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,8 @@ public struct HomeReducer: ReducerProtocol {
|
|||
case synchronizerStateChanged(SynchronizerState)
|
||||
case syncFailed(ZcashError)
|
||||
case updateDestination(HomeReducer.State.Destination?)
|
||||
case updateWalletEvents([WalletEvent])
|
||||
case walletEvents(WalletEventsFlowReducer.Action)
|
||||
case updateTransactionList([TransactionState])
|
||||
case transactionList(TransactionListReducer.Action)
|
||||
}
|
||||
|
||||
@Dependency(\.audioServices) var audioServices
|
||||
|
@ -98,8 +98,8 @@ public struct HomeReducer: ReducerProtocol {
|
|||
}
|
||||
|
||||
public var body: some ReducerProtocol<State, Action> {
|
||||
Scope(state: \.walletEventsState, action: /Action.walletEvents) {
|
||||
WalletEventsFlowReducer()
|
||||
Scope(state: \.transactionListState, action: /Action.transactionList) {
|
||||
TransactionListReducer()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
|
@ -136,7 +136,7 @@ public struct HomeReducer: ReducerProtocol {
|
|||
state.canRequestReview = false
|
||||
return .none
|
||||
|
||||
case .updateWalletEvents:
|
||||
case .updateTransactionList:
|
||||
return .none
|
||||
|
||||
case .synchronizerStateChanged(let latestState):
|
||||
|
@ -171,7 +171,7 @@ public struct HomeReducer: ReducerProtocol {
|
|||
state.destination = destination
|
||||
return .none
|
||||
|
||||
case .walletEvents:
|
||||
case .transactionList:
|
||||
return .none
|
||||
|
||||
case .retrySync:
|
||||
|
@ -212,10 +212,10 @@ public struct HomeReducer: ReducerProtocol {
|
|||
// MARK: - Store
|
||||
|
||||
extension HomeStore {
|
||||
func historyStore() -> WalletEventsFlowStore {
|
||||
func historyStore() -> TransactionListStore {
|
||||
self.scope(
|
||||
state: \.walletEventsState,
|
||||
action: HomeReducer.Action.walletEvents
|
||||
state: \.transactionListState,
|
||||
action: HomeReducer.Action.transactionList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ extension HomeReducer.State {
|
|||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .emptyPlaceHolder
|
||||
transactionListState: .emptyPlaceHolder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ extension HomeStore {
|
|||
state: .error(ZcashError.synchronizerNotPrepared)
|
||||
),
|
||||
walletConfig: .default,
|
||||
walletEventsState: .emptyPlaceHolder
|
||||
transactionListState: .emptyPlaceHolder
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||
import ComposableArchitecture
|
||||
import StoreKit
|
||||
import Generated
|
||||
import WalletEventsFlow
|
||||
import TransactionList
|
||||
import Settings
|
||||
import UIComponents
|
||||
|
||||
|
@ -20,9 +20,8 @@ public struct HomeView: View {
|
|||
VStack {
|
||||
balance(viewStore)
|
||||
|
||||
WalletEventsFlowView(store: store.historyStore(), tokenName: tokenName)
|
||||
TransactionListView(store: store.historyStore(), tokenName: tokenName)
|
||||
}
|
||||
.padding()
|
||||
.applyScreenBackground()
|
||||
.onAppear {
|
||||
viewStore.send(.onAppear)
|
||||
|
@ -52,26 +51,26 @@ public struct HomeView: View {
|
|||
|
||||
extension HomeView {
|
||||
func balance(_ viewStore: HomeViewStore) -> some View {
|
||||
Group {
|
||||
VStack(spacing: 0) {
|
||||
Button {
|
||||
viewStore.send(.balanceBreakdown)
|
||||
} label: {
|
||||
BalanceTitle(balance: viewStore.shieldedBalance.data.total)
|
||||
}
|
||||
.padding(.top, 40)
|
||||
|
||||
if viewStore.walletConfig.isEnabled(.showFiatConversion) {
|
||||
Text("$\(viewStore.totalCurrencyBalance.decimalZashiFormatted())")
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 20))
|
||||
}
|
||||
|
||||
if viewStore.migratingDatabase {
|
||||
Text(L10n.Home.migratingDatabases)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 30)
|
||||
} else {
|
||||
Text(L10n.Balance.available(viewStore.shieldedBalance.data.verified.decimalZashiFormatted(), tokenName))
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 12))
|
||||
.accessDebugMenuWithHiddenGesture {
|
||||
viewStore.send(.debugMenuStartup)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 30)
|
||||
}
|
||||
}
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
|
|
|
@ -70,7 +70,7 @@ public struct ImportWalletView: View {
|
|||
VStack {
|
||||
Text(L10n.ImportWallet.enterPlaceholder)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.suppressed72.color)
|
||||
.foregroundColor(Asset.Colors.shade72.color)
|
||||
.onTapGesture {
|
||||
isFocused = true
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
import WalletEventsFlow
|
||||
import TransactionList
|
||||
|
||||
public typealias SandboxStore = Store<SandboxReducer.State, SandboxReducer.Action>
|
||||
public typealias SandboxViewStore = ViewStore<SandboxReducer.State, SandboxReducer.Action>
|
||||
|
@ -13,13 +13,13 @@ public struct SandboxReducer: ReducerProtocol {
|
|||
case recoveryPhraseDisplay
|
||||
case scan
|
||||
}
|
||||
public var walletEventsState: WalletEventsFlowReducer.State
|
||||
public var transactionListState: TransactionListReducer.State
|
||||
public var destination: Destination?
|
||||
}
|
||||
|
||||
public enum Action: Equatable {
|
||||
case updateDestination(SandboxReducer.State.Destination?)
|
||||
case walletEvents(WalletEventsFlowReducer.Action)
|
||||
case transactionList(TransactionListReducer.Action)
|
||||
case reset
|
||||
}
|
||||
|
||||
|
@ -31,10 +31,10 @@ public struct SandboxReducer: ReducerProtocol {
|
|||
state.destination = destination
|
||||
return .none
|
||||
|
||||
case let .walletEvents(walletEventsAction):
|
||||
return WalletEventsFlowReducer()
|
||||
.reduce(into: &state.walletEventsState, action: walletEventsAction)
|
||||
.map(SandboxReducer.Action.walletEvents)
|
||||
case let .transactionList(transactionListAction):
|
||||
return TransactionListReducer()
|
||||
.reduce(into: &state.transactionListState, action: transactionListAction)
|
||||
.map(SandboxReducer.Action.transactionList)
|
||||
|
||||
case .reset:
|
||||
return .none
|
||||
|
@ -45,10 +45,10 @@ public struct SandboxReducer: ReducerProtocol {
|
|||
// MARK: - Store
|
||||
|
||||
extension SandboxStore {
|
||||
func historyStore() -> WalletEventsFlowStore {
|
||||
func historyStore() -> TransactionListStore {
|
||||
self.scope(
|
||||
state: \.walletEventsState,
|
||||
action: SandboxReducer.Action.walletEvents
|
||||
state: \.transactionListState,
|
||||
action: SandboxReducer.Action.transactionList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -56,20 +56,6 @@ extension SandboxStore {
|
|||
// MARK: - ViewStore
|
||||
|
||||
extension SandboxViewStore {
|
||||
func toggleSelectedTransaction() {
|
||||
let isAlreadySelected = (self.selectedTranactionID != nil)
|
||||
let walletEvent = self.walletEventsState.walletEvents[5]
|
||||
let newDestination = isAlreadySelected ? nil : WalletEventsFlowReducer.State.Destination.showWalletEvent(walletEvent)
|
||||
send(.walletEvents(.updateDestination(newDestination)))
|
||||
}
|
||||
|
||||
var selectedTranactionID: String? {
|
||||
self.walletEventsState
|
||||
.destination
|
||||
.flatMap(/WalletEventsFlowReducer.State.Destination.showWalletEvent)
|
||||
.map(\.id)
|
||||
}
|
||||
|
||||
func bindingForDestination(_ destination: SandboxReducer.State.Destination) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: { $0.destination == destination },
|
||||
|
@ -85,7 +71,7 @@ extension SandboxViewStore {
|
|||
extension SandboxReducer.State {
|
||||
public static var placeholder: Self {
|
||||
.init(
|
||||
walletEventsState: .placeHolder,
|
||||
transactionListState: .placeHolder,
|
||||
destination: nil
|
||||
)
|
||||
}
|
||||
|
@ -95,7 +81,7 @@ extension SandboxStore {
|
|||
public static var placeholder: SandboxStore {
|
||||
SandboxStore(
|
||||
initialState: SandboxReducer.State(
|
||||
walletEventsState: .placeHolder,
|
||||
transactionListState: .placeHolder,
|
||||
destination: nil
|
||||
),
|
||||
reducer: SandboxReducer()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import RecoveryPhraseDisplay
|
||||
import WalletEventsFlow
|
||||
import TransactionList
|
||||
import Scan
|
||||
import SendFlow
|
||||
import ZcashLightClientKit
|
||||
|
@ -29,7 +29,7 @@ public struct SandboxView: View {
|
|||
@ViewBuilder func view(for destination: SandboxReducer.State.Destination) -> some View {
|
||||
switch destination {
|
||||
case .history:
|
||||
WalletEventsFlowView(store: store.historyStore(), tokenName: tokenName)
|
||||
TransactionListView(store: store.historyStore(), tokenName: tokenName)
|
||||
case .send:
|
||||
SendFlowView(
|
||||
store: .init(
|
||||
|
@ -77,11 +77,6 @@ public struct SandboxView: View {
|
|||
}
|
||||
|
||||
Section(header: Text("Other Actions")) {
|
||||
Button(
|
||||
action: { viewStore.toggleSelectedTransaction() },
|
||||
label: { Text("Toggle Selected Transaction") }
|
||||
)
|
||||
|
||||
Button(
|
||||
action: { viewStore.send(.reset) },
|
||||
label: { Text("Reset (to startup)") }
|
||||
|
@ -93,7 +88,7 @@ public struct SandboxView: View {
|
|||
isPresented: viewStore.bindingForDestination(.history),
|
||||
content: {
|
||||
NavigationView {
|
||||
WalletEventsFlowView(store: store.historyStore(), tokenName: tokenName)
|
||||
TransactionListView(store: store.historyStore(), tokenName: tokenName)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button("Done") { viewStore.send(.updateDestination(nil)) }
|
||||
|
|
|
@ -117,7 +117,6 @@ public struct SettingsReducer: ReducerProtocol {
|
|||
return .none
|
||||
|
||||
case .privateDataConsent(.shareFinished):
|
||||
state.destination = nil
|
||||
return .none
|
||||
|
||||
case .privateDataConsent:
|
||||
|
|
|
@ -44,7 +44,7 @@ public struct About: View {
|
|||
|
||||
Text(L10n.Settings.About.info)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.foregroundColor(Asset.Colors.suppressed30.color)
|
||||
.foregroundColor(Asset.Colors.shade30.color)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ public struct TabsView: View {
|
|||
.foregroundColor(Asset.Colors.primary.color)
|
||||
Rectangle()
|
||||
.frame(height: 2)
|
||||
.foregroundColor(Asset.Colors.tabsUnderline.color)
|
||||
.foregroundColor(Asset.Colors.primaryTint.color)
|
||||
.matchedGeometryEffect(id: "Tabs", in: tabsID, properties: .frame)
|
||||
} else {
|
||||
Text("\(item.title)")
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
import Utils
|
||||
import Models
|
||||
import Generated
|
||||
import Pasteboard
|
||||
import SDKSynchronizer
|
||||
import ZcashSDKEnvironment
|
||||
|
||||
public typealias TransactionListStore = Store<TransactionListReducer.State, TransactionListReducer.Action>
|
||||
public typealias TransactionListViewStore = ViewStore<TransactionListReducer.State, TransactionListReducer.Action>
|
||||
|
||||
public struct TransactionListReducer: ReducerProtocol {
|
||||
private enum CancelId { case timer }
|
||||
|
||||
public struct State: Equatable {
|
||||
public var latestMinedHeight: BlockHeight?
|
||||
public var isScrollable = true
|
||||
public var requiredTransactionConfirmations = 0
|
||||
public var transactionList: IdentifiedArrayOf<TransactionState>
|
||||
public var latestTranassctionId = ""
|
||||
|
||||
public init(
|
||||
latestMinedHeight: BlockHeight? = nil,
|
||||
isScrollable: Bool = true,
|
||||
requiredTransactionConfirmations: Int = 0,
|
||||
transactionList: IdentifiedArrayOf<TransactionState>
|
||||
) {
|
||||
self.latestMinedHeight = latestMinedHeight
|
||||
self.isScrollable = isScrollable
|
||||
self.requiredTransactionConfirmations = requiredTransactionConfirmations
|
||||
self.transactionList = transactionList
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: Equatable {
|
||||
case copyToPastboard(RedactableString)
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case synchronizerStateChanged(SyncStatus)
|
||||
case transactionCollapseRequested(String)
|
||||
case transactionAddressExpandRequested(String)
|
||||
case transactionExpandRequested(String)
|
||||
case transactionIdExpandRequested(String)
|
||||
case updateTransactionList([TransactionState])
|
||||
}
|
||||
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.pasteboard) var pasteboard
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
||||
|
||||
public init() {}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
public func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask<Action> {
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations
|
||||
return .merge(
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map { TransactionListReducer.Action.synchronizerStateChanged($0.syncStatus) }
|
||||
.eraseToEffect()
|
||||
.cancellable(id: CancelId.timer, cancelInFlight: true),
|
||||
.run { send in
|
||||
await send(.updateTransactionList(try await sdkSynchronizer.getAllTransactions()))
|
||||
}
|
||||
)
|
||||
|
||||
case .onDisappear:
|
||||
return .cancel(id: CancelId.timer)
|
||||
|
||||
case .synchronizerStateChanged(.upToDate):
|
||||
state.latestMinedHeight = sdkSynchronizer.latestState().latestBlockHeight
|
||||
return .task {
|
||||
return .updateTransactionList(try await sdkSynchronizer.getAllTransactions())
|
||||
}
|
||||
|
||||
case .synchronizerStateChanged:
|
||||
return .none
|
||||
|
||||
case .updateTransactionList(let transactionList):
|
||||
let sortedTransactionList = transactionList
|
||||
.sorted(by: { lhs, rhs in
|
||||
guard let lhsTimestamp = lhs.timestamp, let rhsTimestamp = rhs.timestamp else {
|
||||
return false
|
||||
}
|
||||
return lhsTimestamp > rhsTimestamp
|
||||
}).map { transaction in
|
||||
if let index = state.transactionList.index(id: transaction.id) {
|
||||
var copiedTransaction = transaction
|
||||
|
||||
copiedTransaction.isAddressExpanded = state.transactionList[index].isAddressExpanded
|
||||
copiedTransaction.isExpanded = state.transactionList[index].isExpanded
|
||||
copiedTransaction.isIdExpanded = state.transactionList[index].isIdExpanded
|
||||
|
||||
return copiedTransaction
|
||||
}
|
||||
|
||||
return transaction
|
||||
}
|
||||
state.transactionList = IdentifiedArrayOf(uniqueElements: sortedTransactionList)
|
||||
state.latestTranassctionId = state.transactionList.first?.id ?? ""
|
||||
return .none
|
||||
|
||||
case .copyToPastboard(let value):
|
||||
pasteboard.setString(value)
|
||||
return .none
|
||||
|
||||
case .transactionCollapseRequested(let id):
|
||||
if let index = state.transactionList.index(id: id) {
|
||||
state.transactionList[index].isAddressExpanded = false
|
||||
state.transactionList[index].isExpanded = false
|
||||
state.transactionList[index].isIdExpanded = false
|
||||
}
|
||||
return .none
|
||||
|
||||
case .transactionAddressExpandRequested(let id):
|
||||
if let index = state.transactionList.index(id: id) {
|
||||
if state.transactionList[index].isExpanded {
|
||||
state.transactionList[index].isAddressExpanded = true
|
||||
} else {
|
||||
state.transactionList[index].isExpanded = true
|
||||
}
|
||||
}
|
||||
return .none
|
||||
|
||||
case .transactionExpandRequested(let id):
|
||||
if let index = state.transactionList.index(id: id) {
|
||||
state.transactionList[index].isExpanded = true
|
||||
}
|
||||
return .none
|
||||
|
||||
case .transactionIdExpandRequested(let id):
|
||||
if let index = state.transactionList.index(id: id) {
|
||||
if state.transactionList[index].isExpanded {
|
||||
state.transactionList[index].isIdExpanded = true
|
||||
} else {
|
||||
state.transactionList[index].isExpanded = true
|
||||
}
|
||||
}
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ViewStore
|
||||
|
||||
extension TransactionListViewStore {
|
||||
func isLatestTransaction(id: String) -> Bool {
|
||||
state.latestTranassctionId == id
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
||||
extension TransactionListReducer.State {
|
||||
public static var placeHolder: Self {
|
||||
.init(transactionList: .mocked)
|
||||
}
|
||||
|
||||
public static var emptyPlaceHolder: Self {
|
||||
.init(transactionList: [])
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionListStore {
|
||||
public static var placeholder: Store<TransactionListReducer.State, TransactionListReducer.Action> {
|
||||
return Store(
|
||||
initialState: .placeHolder,
|
||||
reducer: TransactionListReducer()
|
||||
.dependency(\.zcashSDKEnvironment, .testnet)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension IdentifiedArrayOf where Element == TransactionState {
|
||||
public static var placeholder: IdentifiedArrayOf<TransactionState> {
|
||||
return .init(
|
||||
uniqueElements: (0..<30).map {
|
||||
TransactionState(
|
||||
fee: Zatoshi(10),
|
||||
id: String($0),
|
||||
status: .paid,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public static var mocked: IdentifiedArrayOf<TransactionState> {
|
||||
return .init(
|
||||
uniqueElements: [
|
||||
TransactionState.mockedSent,
|
||||
TransactionState.mockedReceived
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
import UIComponents
|
||||
|
||||
public struct TransactionListView: View {
|
||||
let store: TransactionListStore
|
||||
let tokenName: String
|
||||
|
||||
public init(store: TransactionListStore, tokenName: String) {
|
||||
self.store = store
|
||||
self.tokenName = tokenName
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store, observe: { $0 }) { viewStore in
|
||||
List {
|
||||
ForEach(viewStore.transactionList) { transaction in
|
||||
TransactionRowView(
|
||||
viewStore: viewStore,
|
||||
transaction: transaction,
|
||||
tokenName: tokenName,
|
||||
isLatestTransaction: viewStore.isLatestTransaction(id: transaction.id)
|
||||
)
|
||||
.listRowInsets(EdgeInsets())
|
||||
}
|
||||
.listRowBackground(Asset.Colors.shade97.color)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
.refreshable {
|
||||
viewStore.send(.synchronizerStateChanged(.upToDate))
|
||||
}
|
||||
.background(Asset.Colors.shade97.color)
|
||||
.listStyle(.plain)
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear(perform: { viewStore.send(.onDisappear) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview {
|
||||
NavigationView {
|
||||
TransactionListView(store: .placeholder, tokenName: "ZEC")
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// CollapseTransactionView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 04.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
|
||||
public struct CollapseTransactionView: View {
|
||||
public var body: some View {
|
||||
HStack {
|
||||
Asset.Assets.upArrow.image
|
||||
.resizable()
|
||||
.frame(width: 10, height: 7)
|
||||
.scaleEffect(0.6)
|
||||
.font(.custom(FontFamily.Inter.black.name, size: 10))
|
||||
.foregroundColor(Asset.Colors.primaryTint.color)
|
||||
.overlay {
|
||||
Rectangle()
|
||||
.stroke()
|
||||
.frame(width: 10, height: 10)
|
||||
.foregroundColor(Asset.Colors.shade72.color)
|
||||
}
|
||||
|
||||
Text(L10n.TransactionList.collapse)
|
||||
.font(.custom(FontFamily.Inter.italic.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
.underline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CollapseTransactionView()
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// MessageView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 05.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
|
||||
struct MessageView: View {
|
||||
let message: String?
|
||||
let isSpending: Bool
|
||||
let isFailed: Bool
|
||||
|
||||
public init(
|
||||
message: String?,
|
||||
isSpending: Bool,
|
||||
isFailed: Bool = false
|
||||
) {
|
||||
self.message = message
|
||||
self.isSpending = isSpending
|
||||
self.isFailed = isFailed
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let memoText = message {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(L10n.TransactionList.messageTitle)
|
||||
.font(.custom(FontFamily.Inter.medium.name, size: 13))
|
||||
.padding(.bottom, 8)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Color.clear.frame(height: 0)
|
||||
|
||||
if isFailed {
|
||||
Text(memoText)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.error.color)
|
||||
.strikethrough()
|
||||
.padding()
|
||||
} else {
|
||||
Text(memoText)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.messageShape(
|
||||
filled: !isSpending
|
||||
? Asset.Colors.messageBcgReceived.color
|
||||
: nil,
|
||||
orientation: !isSpending
|
||||
? .right
|
||||
: .left
|
||||
)
|
||||
}
|
||||
.padding(.bottom, 7)
|
||||
.padding(.vertical, 10)
|
||||
} else {
|
||||
Text(L10n.TransactionList.noMessageIncluded)
|
||||
.font(.custom(FontFamily.Inter.italic.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(alignment: .leading) {
|
||||
MessageView(message: "Test", isSpending: true)
|
||||
.padding(.bottom, 50)
|
||||
|
||||
MessageView(message: "Test", isSpending: true, isFailed: true)
|
||||
.padding(.bottom, 50)
|
||||
|
||||
MessageView(message: "Test", isSpending: false)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// TransactionFeeView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 04.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
import ZcashLightClientKit
|
||||
|
||||
public struct TransactionFeeView: View {
|
||||
let fee: Zatoshi
|
||||
|
||||
public init(fee: Zatoshi) {
|
||||
self.fee = fee
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(L10n.TransactionList.transactionFee)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
|
||||
Text(fee.decimalString(formatter: NumberFormatter.zashiBalanceFormatter))
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
}
|
||||
|
||||
Color.clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TransactionFeeView(fee: Zatoshi(10_000))
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
//
|
||||
// TransactionHeaderView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 05.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
import Models
|
||||
import UIComponents
|
||||
|
||||
struct TransactionHeaderView: View {
|
||||
let viewStore: TransactionListViewStore
|
||||
let transaction: TransactionState
|
||||
let isLatestTransaction: Bool
|
||||
|
||||
init(
|
||||
viewStore: TransactionListViewStore,
|
||||
transaction: TransactionState,
|
||||
isLatestTransaction: Bool = false
|
||||
) {
|
||||
self.viewStore = viewStore
|
||||
self.transaction = transaction
|
||||
self.isLatestTransaction = isLatestTransaction
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.bottom, 30)
|
||||
.opacity(isLatestTransaction ? 0.0 : 1.0)
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
HStack(spacing: 0) {
|
||||
iconImage()
|
||||
|
||||
titleText()
|
||||
|
||||
addressArea()
|
||||
|
||||
Spacer(minLength: 60)
|
||||
|
||||
balanceView()
|
||||
}
|
||||
.padding(.trailing, 30)
|
||||
|
||||
if transaction.zAddress != nil && transaction.isAddressExpanded {
|
||||
HStack {
|
||||
Text(transaction.address)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
|
||||
Spacer(minLength: 100)
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
|
||||
Text("\(transaction.dateString ?? "")")
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
.padding(.horizontal, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 30)
|
||||
}
|
||||
|
||||
@ViewBuilder private func iconImage() -> some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
icon
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
.frame(width: 60)
|
||||
}
|
||||
|
||||
@ViewBuilder private func titleText() -> some View {
|
||||
Text(transaction.title)
|
||||
.conditionalStrikethrough(transaction.status == .failed)
|
||||
.conditionalFont(
|
||||
condition: transaction.isPending,
|
||||
true: .custom(FontFamily.Inter.boldItalic.name, size: 13),
|
||||
else: .custom(FontFamily.Inter.bold.name, size: 13)
|
||||
)
|
||||
.foregroundColor(transaction.titleColor)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
|
||||
@ViewBuilder private func addressArea() -> some View {
|
||||
if transaction.zAddress == nil {
|
||||
Asset.Assets.shield.image
|
||||
.resizable()
|
||||
.frame(width: 17, height: 13)
|
||||
} else if !transaction.isAddressExpanded {
|
||||
Button {
|
||||
viewStore.send(.transactionAddressExpandRequested(transaction.id))
|
||||
} label: {
|
||||
Text(transaction.address)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
.disabled(!transaction.isExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func balanceView() -> some View {
|
||||
if transaction.isExpanded {
|
||||
HStack(spacing: 0) {
|
||||
FullBalanceTitle(
|
||||
primary: transaction.expandedAmountString.primary,
|
||||
secondary: transaction.expandedAmountString.secondary,
|
||||
fontName: FontFamily.Inter.regular.name,
|
||||
primaryFontSize: 12,
|
||||
secondaryFontSize: 8
|
||||
)
|
||||
}
|
||||
.foregroundColor(transaction.balanceColor)
|
||||
} else {
|
||||
Text(transaction.roundedAmountString)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 12))
|
||||
.conditionalStrikethrough(transaction.status == .failed)
|
||||
.foregroundColor(transaction.balanceColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionHeaderView {
|
||||
var icon: some View {
|
||||
HStack {
|
||||
switch transaction.status {
|
||||
case .paid, .failed:
|
||||
Asset.Assets.fly.image
|
||||
.resizable()
|
||||
.frame(width: 20, height: 16)
|
||||
|
||||
case .received, .sending, .receiving:
|
||||
Asset.Assets.flyReceived.image
|
||||
.resizable()
|
||||
.frame(width: 17, height: 11)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(spacing: 0) {
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedFailed
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedFailedReceive
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedSent
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedReceived
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedSending
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
TransactionHeaderView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedReceiving
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// TransactionIdView.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 05.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
import Models
|
||||
|
||||
struct TransactionIdView: View {
|
||||
let viewStore: TransactionListViewStore
|
||||
let transaction: TransactionState
|
||||
|
||||
public init(viewStore: TransactionListViewStore, transaction: TransactionState) {
|
||||
self.viewStore = viewStore
|
||||
self.transaction = transaction
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if !transaction.isIdExpanded {
|
||||
HStack {
|
||||
Text(L10n.TransactionList.transactionId)
|
||||
|
||||
Button {
|
||||
viewStore.send(.transactionIdExpandRequested(transaction.id))
|
||||
} label: {
|
||||
Text(transaction.id)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
|
||||
Spacer(minLength: 50)
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
|
||||
if transaction.isIdExpanded {
|
||||
Text(L10n.TransactionList.transactionId)
|
||||
.padding(.top, 20)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
HStack {
|
||||
Text(transaction.id)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
|
||||
Spacer(minLength: 100)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.shade47.color)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TransactionIdView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .placeholder()
|
||||
)
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// TransactionRowView.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 21.06.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import Generated
|
||||
import UIComponents
|
||||
|
||||
public struct TransactionRowView: View {
|
||||
let viewStore: TransactionListViewStore
|
||||
let transaction: TransactionState
|
||||
let tokenName: String
|
||||
let isLatestTransaction: Bool
|
||||
|
||||
public init(
|
||||
viewStore: TransactionListViewStore,
|
||||
transaction: TransactionState,
|
||||
tokenName: String,
|
||||
isLatestTransaction: Bool = false
|
||||
) {
|
||||
self.viewStore = viewStore
|
||||
self.transaction = transaction
|
||||
self.tokenName = tokenName
|
||||
self.isLatestTransaction = isLatestTransaction
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Button {
|
||||
viewStore.send(.transactionExpandRequested(transaction.id), animation: .default)
|
||||
} label: {
|
||||
if transaction.isExpanded {
|
||||
TransactionHeaderView(
|
||||
viewStore: viewStore,
|
||||
transaction: transaction,
|
||||
isLatestTransaction: isLatestTransaction
|
||||
)
|
||||
} else {
|
||||
TransactionHeaderView(
|
||||
viewStore: viewStore,
|
||||
transaction: transaction,
|
||||
isLatestTransaction: isLatestTransaction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if transaction.isExpanded {
|
||||
Group {
|
||||
MessageView(
|
||||
message: transaction.textMemo?.toString(),
|
||||
isSpending: transaction.isSpending,
|
||||
isFailed: transaction.status == .failed
|
||||
)
|
||||
|
||||
TransactionIdView(
|
||||
viewStore: viewStore,
|
||||
transaction: transaction
|
||||
)
|
||||
|
||||
if transaction.isSpending {
|
||||
TransactionFeeView(fee: transaction.fee)
|
||||
.padding(.vertical, 10)
|
||||
}
|
||||
|
||||
Button {
|
||||
viewStore.send(.transactionCollapseRequested(transaction.id), animation: .default)
|
||||
} label: {
|
||||
CollapseTransactionView()
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
List {
|
||||
TransactionRowView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedFailed,
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets())
|
||||
|
||||
TransactionRowView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedReceived,
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets())
|
||||
|
||||
TransactionRowView(
|
||||
viewStore: ViewStore(.placeholder, observe: { $0 }),
|
||||
transaction: .mockedSent,
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets())
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Utils
|
||||
import Models
|
||||
import Generated
|
||||
import UIComponents
|
||||
|
||||
public struct TransactionDetailView: View {
|
||||
public enum RowMark {
|
||||
case neutral
|
||||
case success
|
||||
case fail
|
||||
case inactive
|
||||
case highlight
|
||||
}
|
||||
|
||||
let store: WalletEventsFlowStore
|
||||
let transaction: TransactionState
|
||||
let tokenName: String
|
||||
|
||||
public init(store: WalletEventsFlowStore, transaction: TransactionState, tokenName: String) {
|
||||
self.store = store
|
||||
self.transaction = transaction
|
||||
self.tokenName = tokenName
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
VStack(alignment: .leading) {
|
||||
header
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
switch transaction.status {
|
||||
case .paid:
|
||||
Text(L10n.Transaction.youSent(transaction.zecAmount.decimalZashiFormatted(), tokenName))
|
||||
.padding()
|
||||
address(mark: .inactive, viewStore: viewStore)
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .sending:
|
||||
Text(L10n.Transaction.youAreSending(transaction.zecAmount.decimalZashiFormatted(), tokenName))
|
||||
.padding()
|
||||
address(mark: .inactive, viewStore: viewStore)
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .receiving:
|
||||
Text(L10n.Transaction.youAreReceiving(transaction.zecAmount.decimalZashiFormatted(), tokenName))
|
||||
.padding()
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .received:
|
||||
Text(L10n.Transaction.youReceived(transaction.zecAmount.decimalZashiFormatted(), tokenName))
|
||||
.padding()
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
case .failed:
|
||||
Text(L10n.Transaction.youDidNotSent(transaction.zecAmount.decimalZashiFormatted(), tokenName))
|
||||
.padding()
|
||||
|
||||
address(mark: .inactive, viewStore: viewStore)
|
||||
memo(transaction, viewStore, mark: .highlight)
|
||||
|
||||
Text(L10n.TransactionDetail.error(transaction.errorMessage ?? L10n.General.unknown))
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.navigationTitle(L10n.TransactionDetail.title)
|
||||
}
|
||||
.zashiBack()
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionDetailView {
|
||||
var header: some View {
|
||||
HStack {
|
||||
switch transaction.status {
|
||||
case .sending:
|
||||
Text(L10n.Transaction.sending)
|
||||
Spacer()
|
||||
case .receiving:
|
||||
Text(L10n.Transaction.receiving)
|
||||
Spacer()
|
||||
case .failed:
|
||||
Text("\(transaction.dateString ?? L10n.General.dateNotAvailable)")
|
||||
default:
|
||||
Text("\(transaction.dateString ?? L10n.General.dateNotAvailable)")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
func address(mark: RowMark = .neutral, viewStore: WalletEventsFlowViewStore) -> some View {
|
||||
Text("\(addressPrefixText) \(transaction.address)")
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.padding()
|
||||
}
|
||||
|
||||
func memo(
|
||||
_ transaction: TransactionState,
|
||||
_ viewStore: WalletEventsFlowViewStore,
|
||||
mark: RowMark = .neutral
|
||||
) -> some View {
|
||||
Group {
|
||||
if let memoText = transaction.textMemo?.toString() {
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.Transaction.withMemo)
|
||||
.padding(.leading)
|
||||
Text("\"\(memoText)\"")
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(.leading)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func confirmed(mark: RowMark = .neutral, viewStore: WalletEventsFlowViewStore) -> some View {
|
||||
HStack {
|
||||
Text(L10n.Transaction.confirmed)
|
||||
Spacer()
|
||||
Text(L10n.Transaction.confirmedTimes(transaction.confirmationsWith(viewStore.latestMinedHeight)))
|
||||
}
|
||||
.transactionDetailRow(mark: mark)
|
||||
}
|
||||
|
||||
func confirming(mark: RowMark = .neutral, viewStore: WalletEventsFlowViewStore) -> some View {
|
||||
HStack {
|
||||
Text(L10n.Transaction.confirming(viewStore.requiredTransactionConfirmations))
|
||||
Spacer()
|
||||
Text("\(transaction.confirmationsWith(viewStore.latestMinedHeight))/\(viewStore.requiredTransactionConfirmations)")
|
||||
}
|
||||
.transactionDetailRow(mark: mark)
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionDetailView {
|
||||
var addressPrefixText: String {
|
||||
(transaction.status == .received || transaction.status == .receiving)
|
||||
? "" : L10n.Transaction.to
|
||||
}
|
||||
|
||||
var heightText: String {
|
||||
guard let minedHeight = transaction.minedHeight else { return L10n.Transaction.unconfirmed }
|
||||
return minedHeight > 0 ? String(minedHeight) : L10n.Transaction.unconfirmed
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Row modifier
|
||||
|
||||
struct TransactionDetailRow: ViewModifier {
|
||||
let mark: TransactionDetailView.RowMark
|
||||
let textColor: Color
|
||||
let backgroundColor: Color
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.foregroundColor(textColor)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(backgroundColor)
|
||||
.padding(.leading, 20)
|
||||
.background(markColor(mark))
|
||||
}
|
||||
|
||||
private func markColor(_ mark: TransactionDetailView.RowMark) -> Color {
|
||||
let markColor: Color
|
||||
|
||||
switch mark {
|
||||
case .neutral: markColor = Asset.Colors.primary.color
|
||||
case .success: markColor = Asset.Colors.primary.color
|
||||
case .fail: markColor = Asset.Colors.primary.color
|
||||
case .inactive: markColor = Asset.Colors.primary.color
|
||||
case .highlight: markColor = Asset.Colors.primary.color
|
||||
}
|
||||
|
||||
return markColor
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func transactionDetailRow(
|
||||
mark: TransactionDetailView.RowMark = .neutral
|
||||
) -> some View {
|
||||
modifier(
|
||||
TransactionDetailRow(
|
||||
mark: mark,
|
||||
textColor: mark == .inactive ?
|
||||
Asset.Colors.primary.color :
|
||||
Asset.Colors.primary.color,
|
||||
backgroundColor: Asset.Colors.primary.color
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TransactionDetail_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
TransactionDetailView(
|
||||
store: WalletEventsFlowStore.placeholder,
|
||||
transaction:
|
||||
TransactionState(
|
||||
errorMessage: L10n.Error.rollBack,
|
||||
memos: [Memo.placeholder],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .paid(success: true),
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
||||
NavigationView {
|
||||
TransactionDetailView(
|
||||
store: WalletEventsFlowStore.placeholder,
|
||||
transaction:
|
||||
TransactionState(
|
||||
errorMessage: L10n.Error.rollBack,
|
||||
memos: [Memo.placeholder],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
||||
NavigationView {
|
||||
TransactionDetailView(
|
||||
store: WalletEventsFlowStore.placeholder,
|
||||
transaction:
|
||||
TransactionState(
|
||||
errorMessage: L10n.Error.rollBack,
|
||||
memos: [Memo.placeholder],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .failed,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
||||
NavigationView {
|
||||
TransactionDetailView(
|
||||
store: WalletEventsFlowStore.placeholder,
|
||||
transaction:
|
||||
TransactionState(
|
||||
errorMessage: L10n.Error.rollBack,
|
||||
memos: [Memo.placeholder],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .received,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Memo {
|
||||
// swiftlint:disable:next force_try
|
||||
static let placeholder = try! Memo(string:
|
||||
"""
|
||||
Testing some long memo so I can see many lines of text \
|
||||
instead of just one. This can take some time and I'm \
|
||||
bored to write all this stuff.
|
||||
""")
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
//
|
||||
// TransactionRowView.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 21.06.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import Generated
|
||||
|
||||
public struct TransactionRowView: View {
|
||||
let transaction: TransactionState
|
||||
let tokenName: String
|
||||
|
||||
public init(transaction: TransactionState, tokenName: String) {
|
||||
self.transaction = transaction
|
||||
self.tokenName = tokenName
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
icon
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(operationTitle)
|
||||
.font(.custom(FontFamily.Inter.bold.name, size: 12))
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
|
||||
Text("\(transaction.dateString ?? L10n.General.dateNotAvailable)")
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 12))
|
||||
.foregroundColor(Asset.Colors.suppressed72.color)
|
||||
.opacity(0.5)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Group {
|
||||
Text(transaction.unarySymbol)
|
||||
+ Text(transaction.zecAmount.decimalZashiFormatted())
|
||||
}
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 12))
|
||||
.foregroundColor(
|
||||
transaction.unarySymbol == "-"
|
||||
? Asset.Colors.error.color
|
||||
: Asset.Colors.primary.color
|
||||
)
|
||||
.padding(.trailing, 30)
|
||||
}
|
||||
.padding(.leading, 80)
|
||||
}
|
||||
.frame(height: 60)
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionRowView {
|
||||
var operationTitle: String {
|
||||
switch transaction.status {
|
||||
case .paid:
|
||||
return L10n.Transaction.sent
|
||||
case .received:
|
||||
return L10n.Transaction.received
|
||||
case .failed:
|
||||
// TODO: [#392] final text to be provided (https://github.com/zcash/secant-ios-wallet/issues/392)
|
||||
return L10n.Transaction.failed
|
||||
case .sending:
|
||||
return L10n.Transaction.sending
|
||||
case .receiving:
|
||||
return L10n.Transaction.receiving
|
||||
}
|
||||
}
|
||||
|
||||
var icon: some View {
|
||||
let inTransaction = transaction.status == .received || transaction.status == .receiving
|
||||
return HStack {
|
||||
switch transaction.status {
|
||||
case .paid, .received, .sending, .receiving:
|
||||
Image(systemName: "envelope.fill")
|
||||
.resizable()
|
||||
.frame(width: 16, height: 12)
|
||||
.foregroundColor(Asset.Colors.primary.color)
|
||||
.padding(10)
|
||||
.padding(.leading, 14)
|
||||
case .failed:
|
||||
// TODO: [#392] final icon to be provided (https://github.com/zcash/secant-ios-wallet/issues/392)
|
||||
Circle()
|
||||
.frame(width: 30, height: 30)
|
||||
.foregroundColor(Color.red)
|
||||
.padding(15)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading, 15)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TransactionRowView(
|
||||
transaction:
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .paid(success: true),
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 428, height: 60))
|
||||
|
||||
TransactionRowView(
|
||||
transaction:
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .failed,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 428, height: 60))
|
||||
|
||||
TransactionRowView(
|
||||
transaction:
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 428, height: 60))
|
||||
|
||||
TransactionRowView(
|
||||
transaction:
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .received,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
),
|
||||
tokenName: "ZEC"
|
||||
)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 428, height: 60))
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// WalletEvent+View.swift
|
||||
// secant
|
||||
//
|
||||
// Created by Lukáš Korba on 30.05.2023.
|
||||
//
|
||||
|
||||
import ComposableArchitecture
|
||||
import Models
|
||||
import Generated
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
|
||||
// MARK: - Rows
|
||||
|
||||
extension WalletEvent {
|
||||
@ViewBuilder public func rowView(_ viewStore: WalletEventsFlowViewStore, tokenName: String) -> some View {
|
||||
switch state {
|
||||
case .transaction(let transaction):
|
||||
TransactionRowView(transaction: transaction, tokenName: tokenName)
|
||||
case .shielded(let zatoshi):
|
||||
// TODO: [#390] implement design once shielding is supported
|
||||
// https://github.com/zcash/secant-ios-wallet/issues/390
|
||||
Text(L10n.WalletEvent.Row.shielded(zatoshi.decimalZashiFormatted()))
|
||||
.padding(.leading, 30)
|
||||
case .walletImport:
|
||||
// TODO: [#391] implement design once shielding is supported
|
||||
// https://github.com/zcash/secant-ios-wallet/issues/391
|
||||
Text(L10n.WalletEvent.Row.import)
|
||||
.padding(.leading, 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Details
|
||||
|
||||
extension WalletEvent {
|
||||
@ViewBuilder public func detailView(_ store: WalletEventsFlowStore, tokenName: String) -> some View {
|
||||
switch state {
|
||||
case .transaction(let transaction):
|
||||
TransactionDetailView(store: store, transaction: transaction, tokenName: tokenName)
|
||||
case .shielded(let zatoshi):
|
||||
// TODO: [#390] implement design once shielding is supported
|
||||
// https://github.com/zcash/secant-ios-wallet/issues/390
|
||||
Text(L10n.WalletEvent.Detail.shielded(zatoshi.decimalZashiFormatted()))
|
||||
case .walletImport:
|
||||
// TODO: [#391] implement design once shielding is supported
|
||||
// https://github.com/zcash/secant-ios-wallet/issues/391
|
||||
Text(L10n.WalletEvent.Detail.import)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Placeholders
|
||||
|
||||
private extension WalletEvent {
|
||||
static func randomWalletEventState() -> WalletEvent.WalletEventState {
|
||||
switch Int.random(in: 0..<3) {
|
||||
case 1: return .shielded(Zatoshi(234_000_000))
|
||||
case 2: return .walletImport(BlockHeight(1_629_724))
|
||||
default: return .transaction(.placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
static func mockedWalletEventState(atIndex: Int) -> WalletEvent.WalletEventState {
|
||||
switch atIndex % 5 {
|
||||
case 0: return .transaction(.statePlaceholder(.received))
|
||||
case 1: return .transaction(.statePlaceholder(.failed))
|
||||
case 2: return .transaction(.statePlaceholder(.sending))
|
||||
case 3: return .transaction(.statePlaceholder(.receiving))
|
||||
case 4: return .transaction(.placeholder)
|
||||
default: return .transaction(.placeholder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension IdentifiedArrayOf where Element == WalletEvent {
|
||||
public static var placeholder: IdentifiedArrayOf<WalletEvent> {
|
||||
.init(
|
||||
uniqueElements: (0..<30).map {
|
||||
WalletEvent(
|
||||
id: String($0),
|
||||
state: WalletEvent.mockedWalletEventState(atIndex: $0),
|
||||
timestamp: 1234567
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
import Utils
|
||||
import Models
|
||||
import Generated
|
||||
import Pasteboard
|
||||
import SDKSynchronizer
|
||||
import ZcashSDKEnvironment
|
||||
|
||||
public typealias WalletEventsFlowStore = Store<WalletEventsFlowReducer.State, WalletEventsFlowReducer.Action>
|
||||
public typealias WalletEventsFlowViewStore = ViewStore<WalletEventsFlowReducer.State, WalletEventsFlowReducer.Action>
|
||||
|
||||
public struct WalletEventsFlowReducer: ReducerProtocol {
|
||||
private enum CancelId { case timer }
|
||||
|
||||
public struct State: Equatable {
|
||||
public enum Destination: Equatable {
|
||||
case latest
|
||||
case all
|
||||
case showWalletEvent(WalletEvent)
|
||||
}
|
||||
|
||||
@PresentationState public var alert: AlertState<Action>?
|
||||
public var destination: Destination?
|
||||
public var latestMinedHeight: BlockHeight?
|
||||
public var isScrollable = true
|
||||
public var requiredTransactionConfirmations = 0
|
||||
public var walletEvents = IdentifiedArrayOf<WalletEvent>.placeholder
|
||||
public var selectedWalletEvent: WalletEvent?
|
||||
|
||||
public init(
|
||||
destination: Destination? = nil,
|
||||
latestMinedHeight: BlockHeight? = nil,
|
||||
isScrollable: Bool = true,
|
||||
requiredTransactionConfirmations: Int = 0,
|
||||
walletEvents: IdentifiedArrayOf<WalletEvent> = .placeholder,
|
||||
selectedWalletEvent: WalletEvent? = nil
|
||||
) {
|
||||
self.destination = destination
|
||||
self.latestMinedHeight = latestMinedHeight
|
||||
self.isScrollable = isScrollable
|
||||
self.requiredTransactionConfirmations = requiredTransactionConfirmations
|
||||
self.walletEvents = walletEvents
|
||||
self.selectedWalletEvent = selectedWalletEvent
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: Equatable {
|
||||
case alert(PresentationAction<Action>)
|
||||
case copyToPastboard(RedactableString)
|
||||
case onAppear
|
||||
case onDisappear
|
||||
case openBlockExplorer(URL?)
|
||||
case updateDestination(WalletEventsFlowReducer.State.Destination?)
|
||||
case synchronizerStateChanged(SyncStatus)
|
||||
case updateWalletEvents([WalletEvent])
|
||||
case warnBeforeLeavingApp(URL?)
|
||||
}
|
||||
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
@Dependency(\.pasteboard) var pasteboard
|
||||
@Dependency(\.sdkSynchronizer) var sdkSynchronizer
|
||||
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
||||
|
||||
public init() {}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
public func reduce(into state: inout State, action: Action) -> ComposableArchitecture.EffectTask<Action> {
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations
|
||||
return .merge(
|
||||
sdkSynchronizer.stateStream()
|
||||
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
||||
.map { WalletEventsFlowReducer.Action.synchronizerStateChanged($0.syncStatus) }
|
||||
.eraseToEffect()
|
||||
.cancellable(id: CancelId.timer, cancelInFlight: true),
|
||||
.run { send in
|
||||
await send(.updateWalletEvents(try await sdkSynchronizer.getAllTransactions()))
|
||||
}
|
||||
)
|
||||
|
||||
case .onDisappear:
|
||||
return .cancel(id: CancelId.timer)
|
||||
|
||||
case .synchronizerStateChanged(.upToDate):
|
||||
state.latestMinedHeight = sdkSynchronizer.latestState().latestBlockHeight
|
||||
return .task {
|
||||
return .updateWalletEvents(try await sdkSynchronizer.getAllTransactions())
|
||||
}
|
||||
|
||||
case .synchronizerStateChanged:
|
||||
return .none
|
||||
|
||||
case .updateWalletEvents(let walletEvents):
|
||||
let sortedWalletEvents = walletEvents
|
||||
.sorted(by: { lhs, rhs in
|
||||
guard let lhsTimestamp = lhs.timestamp, let rhsTimestamp = rhs.timestamp else {
|
||||
return false
|
||||
}
|
||||
return lhsTimestamp > rhsTimestamp
|
||||
})
|
||||
state.walletEvents = IdentifiedArrayOf(uniqueElements: sortedWalletEvents)
|
||||
return .none
|
||||
|
||||
case .updateDestination(.showWalletEvent(let walletEvent)):
|
||||
state.selectedWalletEvent = walletEvent
|
||||
state.destination = .showWalletEvent(walletEvent)
|
||||
return .none
|
||||
|
||||
case .updateDestination(let destination):
|
||||
state.destination = destination
|
||||
if destination == nil {
|
||||
state.selectedWalletEvent = nil
|
||||
}
|
||||
return .none
|
||||
|
||||
case .copyToPastboard(let value):
|
||||
pasteboard.setString(value)
|
||||
return .none
|
||||
|
||||
case .alert(.presented(let action)):
|
||||
return Effect.send(action)
|
||||
|
||||
case .alert(.dismiss):
|
||||
state.alert = nil
|
||||
return .none
|
||||
|
||||
case .alert:
|
||||
return .none
|
||||
|
||||
case .warnBeforeLeavingApp(let blockExplorerURL):
|
||||
state.alert = AlertState.warnBeforeLeavingApp(blockExplorerURL)
|
||||
return .none
|
||||
|
||||
case .openBlockExplorer(let blockExplorerURL):
|
||||
if let url = blockExplorerURL {
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewStore
|
||||
|
||||
extension WalletEventsFlowViewStore {
|
||||
private typealias Destination = WalletEventsFlowReducer.State.Destination
|
||||
|
||||
func bindingForSelectedWalletEvent(_ walletEvent: WalletEvent?) -> Binding<Bool> {
|
||||
self.binding(
|
||||
get: {
|
||||
guard let walletEvent else {
|
||||
return false
|
||||
}
|
||||
|
||||
return $0.destination.map(/WalletEventsFlowReducer.State.Destination.showWalletEvent) == walletEvent
|
||||
},
|
||||
send: { isActive in
|
||||
guard let walletEvent else {
|
||||
return WalletEventsFlowReducer.Action.updateDestination(nil)
|
||||
}
|
||||
|
||||
return WalletEventsFlowReducer.Action.updateDestination(
|
||||
isActive ? WalletEventsFlowReducer.State.Destination.showWalletEvent(walletEvent) : nil
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Alerts
|
||||
|
||||
extension AlertState where Action == WalletEventsFlowReducer.Action {
|
||||
public static func warnBeforeLeavingApp(_ blockExplorerURL: URL?) -> AlertState {
|
||||
AlertState {
|
||||
TextState(L10n.WalletEvent.Alert.LeavingApp.title)
|
||||
} actions: {
|
||||
ButtonState(action: .openBlockExplorer(blockExplorerURL)) {
|
||||
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.seeOnline)
|
||||
}
|
||||
ButtonState(role: .cancel, action: .alert(.dismiss)) {
|
||||
TextState(L10n.WalletEvent.Alert.LeavingApp.Button.nevermind)
|
||||
}
|
||||
} message: {
|
||||
TextState(L10n.WalletEvent.Alert.LeavingApp.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Placeholders
|
||||
|
||||
extension TransactionState {
|
||||
public static var placeholder: Self {
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: .paid(success: true),
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
)
|
||||
}
|
||||
|
||||
public static func statePlaceholder(_ status: Status) -> Self {
|
||||
.init(
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(10),
|
||||
id: "2",
|
||||
status: status,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(123_000_000)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension WalletEventsFlowReducer.State {
|
||||
public static var placeHolder: Self {
|
||||
.init(walletEvents: .placeholder)
|
||||
}
|
||||
|
||||
public static var emptyPlaceHolder: Self {
|
||||
.init(walletEvents: [])
|
||||
}
|
||||
}
|
||||
|
||||
extension WalletEventsFlowStore {
|
||||
public static var placeholder: Store<WalletEventsFlowReducer.State, WalletEventsFlowReducer.Action> {
|
||||
return Store(
|
||||
initialState: .placeHolder,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
.dependency(\.zcashSDKEnvironment, .testnet)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension IdentifiedArrayOf where Element == TransactionState {
|
||||
public static var placeholder: IdentifiedArrayOf<TransactionState> {
|
||||
return .init(
|
||||
uniqueElements: (0..<30).map {
|
||||
TransactionState(
|
||||
fee: Zatoshi(10),
|
||||
id: String($0),
|
||||
status: .paid(success: true),
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
import Generated
|
||||
import UIComponents
|
||||
|
||||
public struct WalletEventsFlowView: View {
|
||||
let store: WalletEventsFlowStore
|
||||
let tokenName: String
|
||||
|
||||
public init(store: WalletEventsFlowStore, tokenName: String) {
|
||||
self.store = store
|
||||
self.tokenName = tokenName
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
WithViewStore(store) { viewStore in
|
||||
List {
|
||||
walletEventsList(with: viewStore)
|
||||
}
|
||||
.navigationTitle(L10n.Transactions.title)
|
||||
.listStyle(.plain)
|
||||
.onAppear { viewStore.send(.onAppear) }
|
||||
.onDisappear(perform: { viewStore.send(.onDisappear) })
|
||||
.navigationLinkEmpty(isActive: viewStore.bindingForSelectedWalletEvent(viewStore.selectedWalletEvent)) {
|
||||
viewStore.selectedWalletEvent?.detailView(store, tokenName: tokenName)
|
||||
}
|
||||
}
|
||||
.alert(store: store.scope(
|
||||
state: \.$alert,
|
||||
action: { .alert($0) }
|
||||
))
|
||||
.zashiBack()
|
||||
}
|
||||
}
|
||||
|
||||
extension WalletEventsFlowView {
|
||||
func walletEventsList(with viewStore: WalletEventsFlowViewStore) -> some View {
|
||||
ForEach(viewStore.walletEvents) { walletEvent in
|
||||
walletEvent.rowView(viewStore, tokenName: tokenName)
|
||||
.onTapGesture {
|
||||
viewStore.send(.updateDestination(.showWalletEvent(walletEvent)))
|
||||
}
|
||||
.listRowInsets(EdgeInsets())
|
||||
.frame(height: 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TransactionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
WalletEventsFlowView(store: .placeholder, tokenName: "ZEC")
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -664,14 +664,16 @@ public enum L10n {
|
|||
public static func confirming(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "transaction.confirming", String(describing: p1), fallback: "Confirming ~%@mins")
|
||||
}
|
||||
/// Failed
|
||||
public static let failed = L10n.tr("Localizable", "transaction.failed", fallback: "Failed")
|
||||
/// Receive failed
|
||||
public static let failedReceive = L10n.tr("Localizable", "transaction.failedReceive", fallback: "Receive failed")
|
||||
/// Send failed
|
||||
public static let failedSend = L10n.tr("Localizable", "transaction.failedSend", fallback: "Send failed")
|
||||
/// Received
|
||||
public static let received = L10n.tr("Localizable", "transaction.received", fallback: "Received")
|
||||
/// Receiving
|
||||
public static let receiving = L10n.tr("Localizable", "transaction.receiving", fallback: "Receiving")
|
||||
/// Sending
|
||||
public static let sending = L10n.tr("Localizable", "transaction.sending", fallback: "Sending")
|
||||
/// Receiving...
|
||||
public static let receiving = L10n.tr("Localizable", "transaction.receiving", fallback: "Receiving...")
|
||||
/// Sending...
|
||||
public static let sending = L10n.tr("Localizable", "transaction.sending", fallback: "Sending...")
|
||||
/// Sent
|
||||
public static let sent = L10n.tr("Localizable", "transaction.sent", fallback: "Sent")
|
||||
/// to
|
||||
|
@ -709,6 +711,18 @@ public enum L10n {
|
|||
/// Transaction detail
|
||||
public static let title = L10n.tr("Localizable", "transactionDetail.title", fallback: "Transaction detail")
|
||||
}
|
||||
public enum TransactionList {
|
||||
/// Collapse transaction
|
||||
public static let collapse = L10n.tr("Localizable", "transactionList.collapse", fallback: "Collapse transaction")
|
||||
/// Message
|
||||
public static let messageTitle = L10n.tr("Localizable", "transactionList.messageTitle", fallback: "Message")
|
||||
/// No message included in transaction
|
||||
public static let noMessageIncluded = L10n.tr("Localizable", "transactionList.noMessageIncluded", fallback: "No message included in transaction")
|
||||
/// Transaction Fee
|
||||
public static let transactionFee = L10n.tr("Localizable", "transactionList.transactionFee", fallback: "Transaction Fee")
|
||||
/// Transaction ID
|
||||
public static let transactionId = L10n.tr("Localizable", "transactionList.transactionId", fallback: "Transaction ID")
|
||||
}
|
||||
public enum Transactions {
|
||||
/// Transactions
|
||||
public static let title = L10n.tr("Localizable", "transactions.title", fallback: "Transactions")
|
||||
|
@ -737,38 +751,6 @@ public enum L10n {
|
|||
public static let phraseAgain = L10n.tr("Localizable", "validationSuccess.button.phraseAgain", fallback: "Show me my phrase again")
|
||||
}
|
||||
}
|
||||
public enum WalletEvent {
|
||||
public enum Alert {
|
||||
public enum LeavingApp {
|
||||
/// While usually an acceptable risk, you will possibly exposing your behavior and interest in this transaction by going online. OH NOES! What will you do?
|
||||
public static let message = L10n.tr("Localizable", "walletEvent.alert.leavingApp.message", fallback: "While usually an acceptable risk, you will possibly exposing your behavior and interest in this transaction by going online. OH NOES! What will you do?")
|
||||
/// You are exiting your wallet
|
||||
public static let title = L10n.tr("Localizable", "walletEvent.alert.leavingApp.title", fallback: "You are exiting your wallet")
|
||||
public enum Button {
|
||||
/// NEVERMIND
|
||||
public static let nevermind = L10n.tr("Localizable", "walletEvent.alert.leavingApp.button.nevermind", fallback: "NEVERMIND")
|
||||
/// SEE TX ONLINE
|
||||
public static let seeOnline = L10n.tr("Localizable", "walletEvent.alert.leavingApp.button.seeOnline", fallback: "SEE TX ONLINE")
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum Detail {
|
||||
/// wallet import wallet event
|
||||
public static let `import` = L10n.tr("Localizable", "walletEvent.detail.import", fallback: "wallet import wallet event")
|
||||
/// shielded %@ detail
|
||||
public static func shielded(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "walletEvent.detail.shielded", String(describing: p1), fallback: "shielded %@ detail")
|
||||
}
|
||||
}
|
||||
public enum Row {
|
||||
/// wallet import wallet event
|
||||
public static let `import` = L10n.tr("Localizable", "walletEvent.row.import", fallback: "wallet import wallet event")
|
||||
/// shielded wallet event %@
|
||||
public static func shielded(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "walletEvent.row.shielded", String(describing: p1), fallback: "shielded wallet event %@")
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum WelcomeScreen {
|
||||
/// Just Loading, one sec
|
||||
public static let subtitle = L10n.tr("Localizable", "welcomeScreen.subtitle", fallback: "Just Loading, one sec")
|
||||
|
|
12
modules/Sources/Generated/Resources/Assets.xcassets/FlyReceived.imageset/Contents.json
vendored
Normal file
12
modules/Sources/Generated/Resources/Assets.xcassets/FlyReceived.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "flyReceived.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/FlyReceived.imageset/flyReceived.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/FlyReceived.imageset/flyReceived.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
21
modules/Sources/Generated/Resources/Assets.xcassets/flyReceivedFilled.imageset/Contents.json
vendored
Normal file
21
modules/Sources/Generated/Resources/Assets.xcassets/flyReceivedFilled.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "flyReceivedFilled.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/flyReceivedFilled.imageset/flyReceivedFilled.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/flyReceivedFilled.imageset/flyReceivedFilled.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
12
modules/Sources/Generated/Resources/Assets.xcassets/shield.imageset/Contents.json
vendored
Normal file
12
modules/Sources/Generated/Resources/Assets.xcassets/shield.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shield.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/shield.imageset/shield.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/shield.imageset/shield.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
12
modules/Sources/Generated/Resources/Assets.xcassets/upArrow.imageset/Contents.json
vendored
Normal file
12
modules/Sources/Generated/Resources/Assets.xcassets/upArrow.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "upArrow.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
modules/Sources/Generated/Resources/Assets.xcassets/upArrow.imageset/upArrow.png
vendored
Normal file
BIN
modules/Sources/Generated/Resources/Assets.xcassets/upArrow.imageset/upArrow.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 822 B |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xCD",
|
||||
"green" : "0xE9",
|
||||
"red" : "0xF6"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.970",
|
||||
"green" : "0.970",
|
||||
"red" : "0.970"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -81,7 +81,6 @@
|
|||
"importWallet.optionalBirthday" = "(optional)";
|
||||
"importWallet.enterPlaceholder" = "Enter private seed here…";
|
||||
|
||||
|
||||
// MARK: - Tabs
|
||||
"tabs.account" = "Account";
|
||||
"tabs.send" = "Send";
|
||||
|
@ -174,27 +173,6 @@ Sharing this private data is irrevocable — once you have shared this private d
|
|||
"sync.message.stopped" = "Stopped";
|
||||
"sync.message.sync" = "%@%% Synced";
|
||||
|
||||
// MARK: - Transactions
|
||||
"transactions.title" = "Transactions";
|
||||
"transaction.sent" = "Sent";
|
||||
"transaction.sending" = "Sending";
|
||||
"transaction.receiving" = "Receiving";
|
||||
"transaction.received" = "Received";
|
||||
"transaction.failed" = "Failed";
|
||||
"transaction.youSent" = "You sent %@ %@";
|
||||
"transaction.youAreSending" = "You are sending %@ %@";
|
||||
"transaction.youAreReceiving" = "You are receiving %@ %@";
|
||||
"transaction.youReceived" = "You received %@ %@";
|
||||
"transaction.youDidNotSent" = "You DID NOT send %@ %@";
|
||||
"transaction.confirmed" = "Confirmed";
|
||||
"transaction.confirmedTimes" = "%@ times";
|
||||
"transaction.confirming" = "Confirming ~%@mins";
|
||||
"transaction.withMemo" = "With memo:";
|
||||
"transaction.to" = "to";
|
||||
"transaction.unconfirmed" = "unconfirmed";
|
||||
"transactionDetail.title" = "Transaction detail";
|
||||
"transactionDetail.error" = "Error: %@";
|
||||
|
||||
// MARK: - Not Enough Free Space
|
||||
"nefs.message" = "Not enough space on disk to do synchronisation!";
|
||||
|
||||
|
@ -221,15 +199,34 @@ Sharing this private data is irrevocable — once you have shared this private d
|
|||
"qrCodeFor" = "QR Code for %@";
|
||||
"general.dateNotAvailable" = "date not available";
|
||||
|
||||
// MARK: - Wallet event
|
||||
"walletEvent.row.shielded" = "shielded wallet event %@";
|
||||
"walletEvent.row.import" = "wallet import wallet event";
|
||||
"walletEvent.detail.shielded" = "shielded %@ detail";
|
||||
"walletEvent.detail.import" = "wallet import wallet event";
|
||||
"walletEvent.alert.leavingApp.title" = "You are exiting your wallet";
|
||||
"walletEvent.alert.leavingApp.message" = "While usually an acceptable risk, you will possibly exposing your behavior and interest in this transaction by going online. OH NOES! What will you do?";
|
||||
"walletEvent.alert.leavingApp.button.nevermind" = "NEVERMIND";
|
||||
"walletEvent.alert.leavingApp.button.seeOnline" = "SEE TX ONLINE";
|
||||
// MARK: - Transaction List
|
||||
"transactionList.collapse" = "Collapse transaction";
|
||||
"transactionList.messageTitle" = "Message";
|
||||
"transactionList.noMessageIncluded" = "No message included in transaction";
|
||||
"transactionList.transactionFee" = "Transaction Fee";
|
||||
"transactionList.transactionId" = "Transaction ID";
|
||||
|
||||
// MARK: - Transactions
|
||||
"transactions.title" = "Transactions";
|
||||
"transaction.sent" = "Sent";
|
||||
"transaction.sending" = "Sending...";
|
||||
"transaction.receiving" = "Receiving...";
|
||||
"transaction.received" = "Received";
|
||||
"transaction.failedSend" = "Send failed";
|
||||
"transaction.failedReceive" = "Receive failed";
|
||||
"transaction.youSent" = "You sent %@ %@";
|
||||
"transaction.youAreSending" = "You are sending %@ %@";
|
||||
"transaction.youAreReceiving" = "You are receiving %@ %@";
|
||||
"transaction.youReceived" = "You received %@ %@";
|
||||
"transaction.youDidNotSent" = "You DID NOT send %@ %@";
|
||||
"transaction.confirmed" = "Confirmed";
|
||||
"transaction.confirmedTimes" = "%@ times";
|
||||
"transaction.confirming" = "Confirming ~%@mins";
|
||||
"transaction.withMemo" = "With memo:";
|
||||
"transaction.to" = "to";
|
||||
"transaction.unconfirmed" = "unconfirmed";
|
||||
"transactionDetail.title" = "Transaction detail";
|
||||
"transactionDetail.error" = "Error: %@";
|
||||
|
||||
// MARK: - Local authentication
|
||||
"localAuthentication.reason" = "The Following content requires authentication.";
|
||||
|
|
|
@ -23,21 +23,27 @@ public typealias AssetImageTypeAlias = ImageAsset.UniversalImage
|
|||
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
|
||||
public enum Asset {
|
||||
public enum Assets {
|
||||
public static let fly = ImageAsset(name: "Fly")
|
||||
public static let splashHi = ImageAsset(name: "SplashHi")
|
||||
public static let welcomeScreenLogo = ImageAsset(name: "WelcomeScreenLogo")
|
||||
public static let zashiLogo = ImageAsset(name: "ZashiLogo")
|
||||
public static let fly = ImageAsset(name: "fly")
|
||||
public static let flyReceived = ImageAsset(name: "flyReceived")
|
||||
public static let flyReceivedFilled = ImageAsset(name: "flyReceivedFilled")
|
||||
public static let shield = ImageAsset(name: "shield")
|
||||
public static let splashHi = ImageAsset(name: "splashHi")
|
||||
public static let upArrow = ImageAsset(name: "upArrow")
|
||||
public static let welcomeScreenLogo = ImageAsset(name: "welcomeScreenLogo")
|
||||
public static let zashiLogo = ImageAsset(name: "zashiLogo")
|
||||
public static let zashiTitle = ImageAsset(name: "zashiTitle")
|
||||
}
|
||||
public enum Colors {
|
||||
public static let error = ColorAsset(name: "error")
|
||||
public static let messageBcgReceived = ColorAsset(name: "messageBcgReceived")
|
||||
public static let primary = ColorAsset(name: "primary")
|
||||
public static let primaryTint = ColorAsset(name: "primaryTint")
|
||||
public static let secondary = ColorAsset(name: "secondary")
|
||||
public static let shade30 = ColorAsset(name: "shade30")
|
||||
public static let shade47 = ColorAsset(name: "shade47")
|
||||
public static let shade72 = ColorAsset(name: "shade72")
|
||||
public static let shade97 = ColorAsset(name: "shade97")
|
||||
public static let splash = ColorAsset(name: "splash")
|
||||
public static let suppressed30 = ColorAsset(name: "suppressed30")
|
||||
public static let suppressed47 = ColorAsset(name: "suppressed47")
|
||||
public static let suppressed72 = ColorAsset(name: "suppressed72")
|
||||
public static let tabsUnderline = ColorAsset(name: "tabsUnderline")
|
||||
}
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||
|
|
|
@ -6,17 +6,18 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
import Generated
|
||||
|
||||
/// Representation of the transaction on the SDK side, used as a bridge to the TCA wallet side.
|
||||
public struct TransactionState: Equatable, Identifiable {
|
||||
public enum Status: Equatable {
|
||||
case paid(success: Bool)
|
||||
case received
|
||||
case failed
|
||||
case sending
|
||||
case paid
|
||||
case received
|
||||
case receiving
|
||||
case sending
|
||||
}
|
||||
|
||||
public var errorMessage: String?
|
||||
|
@ -25,6 +26,7 @@ public struct TransactionState: Equatable, Identifiable {
|
|||
public var minedHeight: BlockHeight?
|
||||
public var shielded = true
|
||||
public var zAddress: String?
|
||||
public var isSentTransaction: Bool
|
||||
|
||||
public var fee: Zatoshi
|
||||
public var id: String
|
||||
|
@ -32,41 +34,129 @@ public struct TransactionState: Equatable, Identifiable {
|
|||
public var timestamp: TimeInterval?
|
||||
public var zecAmount: Zatoshi
|
||||
|
||||
public var isAddressExpanded: Bool
|
||||
public var isExpanded: Bool
|
||||
public var isIdExpanded: Bool
|
||||
|
||||
// UI Colors
|
||||
public var balanceColor: Color {
|
||||
status == .failed
|
||||
? Asset.Colors.error.color
|
||||
: isSpending
|
||||
? Asset.Colors.error.color
|
||||
: Asset.Colors.primary.color
|
||||
}
|
||||
|
||||
public var titleColor: Color {
|
||||
status == .failed
|
||||
? Asset.Colors.error.color
|
||||
: isPending
|
||||
? Asset.Colors.shade47.color
|
||||
: Asset.Colors.primary.color
|
||||
}
|
||||
|
||||
// UI Texts
|
||||
public var address: String {
|
||||
zAddress ?? ""
|
||||
}
|
||||
|
||||
public var unarySymbol: String {
|
||||
public var title: String {
|
||||
switch status {
|
||||
case .failed:
|
||||
return isSentTransaction
|
||||
? L10n.Transaction.failedSend
|
||||
: L10n.Transaction.failedReceive
|
||||
case .paid:
|
||||
return L10n.Transaction.sent
|
||||
case .received:
|
||||
return L10n.Transaction.received
|
||||
case .receiving:
|
||||
return L10n.Transaction.receiving
|
||||
case .sending:
|
||||
return L10n.Transaction.sending
|
||||
}
|
||||
}
|
||||
|
||||
public var roundedAmountString: String {
|
||||
let formatted = zecAmount.decimalZashiFormatted()
|
||||
|
||||
switch status {
|
||||
case .paid, .sending:
|
||||
return "-"
|
||||
return "-\(formatted)"
|
||||
case .received, .receiving:
|
||||
return "+"
|
||||
return "+\(formatted)"
|
||||
case .failed:
|
||||
return ""
|
||||
return isSentTransaction ? "-\(formatted)" : "+\(formatted)"
|
||||
}
|
||||
}
|
||||
|
||||
public var expandedAmountString: (primary: String, secondary: String) {
|
||||
let formatted = zecAmount.decimalZashiFullFormatted()
|
||||
|
||||
let smallPart = String(formatted.suffix(5))
|
||||
let normal = formatted.dropLast(5)
|
||||
|
||||
switch status {
|
||||
case .paid, .sending:
|
||||
return (primary: "-\(normal)", secondary: smallPart)
|
||||
case .received, .receiving:
|
||||
return (primary: "+\(normal)", secondary: smallPart)
|
||||
case .failed:
|
||||
return isSentTransaction
|
||||
? (primary: "-\(normal)", secondary: smallPart)
|
||||
: (primary: "+\(normal)", secondary: smallPart)
|
||||
}
|
||||
}
|
||||
|
||||
public var dateString: String? {
|
||||
guard let minedHeight else { return "" }
|
||||
guard minedHeight != nil else { return "" }
|
||||
guard let timestamp else { return nil }
|
||||
|
||||
return Date(timeIntervalSince1970: timestamp).asHumanReadable()
|
||||
}
|
||||
|
||||
// Helper flags
|
||||
public var isPending: Bool {
|
||||
switch status {
|
||||
case .failed:
|
||||
return false
|
||||
case .paid:
|
||||
return false
|
||||
case .received:
|
||||
return false
|
||||
case .receiving:
|
||||
return true
|
||||
case .sending:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// The purpose of this flag is to help understand if the transaction affected the wallet and a user paid a fee
|
||||
public var isSpending: Bool {
|
||||
switch status {
|
||||
case .paid, .sending:
|
||||
return true
|
||||
case .received, .receiving:
|
||||
return false
|
||||
case .failed:
|
||||
return isSentTransaction
|
||||
}
|
||||
}
|
||||
|
||||
// Values
|
||||
public var totalAmount: Zatoshi {
|
||||
Zatoshi(zecAmount.amount + fee.amount)
|
||||
}
|
||||
|
||||
public var viewOnlineURL: URL? {
|
||||
URL(string: "https://zcashblockexplorer.com/transactions/\(id)")
|
||||
}
|
||||
|
||||
public var textMemo: Memo? {
|
||||
guard let memos else { return nil }
|
||||
|
||||
for memo in memos {
|
||||
if case .text = memo {
|
||||
guard let memoText = memo.toString(), !memoText.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return memo
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +175,11 @@ public struct TransactionState: Equatable, Identifiable {
|
|||
id: String,
|
||||
status: Status,
|
||||
timestamp: TimeInterval? = nil,
|
||||
zecAmount: Zatoshi
|
||||
zecAmount: Zatoshi,
|
||||
isSentTransaction: Bool = false,
|
||||
isAddressExpanded: Bool = false,
|
||||
isExpanded: Bool = false,
|
||||
isIdExpanded: Bool = false
|
||||
) {
|
||||
self.errorMessage = errorMessage
|
||||
self.expiryHeight = expiryHeight
|
||||
|
@ -98,6 +192,10 @@ public struct TransactionState: Equatable, Identifiable {
|
|||
self.status = status
|
||||
self.timestamp = timestamp
|
||||
self.zecAmount = zecAmount
|
||||
self.isSentTransaction = isSentTransaction
|
||||
self.isAddressExpanded = isAddressExpanded
|
||||
self.isExpanded = isExpanded
|
||||
self.isIdExpanded = isIdExpanded
|
||||
}
|
||||
|
||||
public func confirmationsWith(_ latestMinedHeight: BlockHeight?) -> BlockHeight {
|
||||
|
@ -117,23 +215,30 @@ extension TransactionState {
|
|||
id = transaction.rawID.toHexStringTxId()
|
||||
timestamp = transaction.blockTime
|
||||
zecAmount = transaction.isSentTransaction ? Zatoshi(-transaction.value.amount) : transaction.value
|
||||
isSentTransaction = transaction.isSentTransaction
|
||||
isAddressExpanded = false
|
||||
isExpanded = false
|
||||
isIdExpanded = false
|
||||
self.memos = memos
|
||||
|
||||
let isSent = transaction.isSentTransaction
|
||||
|
||||
// TODO: [#1313] SDK improvements so a client doesn't need to determing if the transaction isPending
|
||||
// https://github.com/zcash/ZcashLightClientKit/issues/1313
|
||||
// The only reason why `latestBlockHeight` is provided here is to determine pending
|
||||
// state of the transaction. SDK knows the latestBlockHeight so ideally ZcashTransaction.Overview
|
||||
// already knows and provides isPending as a bool value.
|
||||
// Once SDK's #1313 is done, adopt the SDK and remove latestBlockHeight here.
|
||||
let isPending = transaction.isPending(currentHeight: latestBlockHeight)
|
||||
|
||||
switch (isSent, isPending) {
|
||||
case (true, true): status = .sending
|
||||
case (true, false): status = .paid(success: minedHeight ?? 0 > 0)
|
||||
case (false, true): status = .receiving
|
||||
case (false, false): status = .received
|
||||
// failed check
|
||||
if let expiryHeight = transaction.expiryHeight, expiryHeight <= latestBlockHeight && minedHeight == nil {
|
||||
status = .failed
|
||||
} else {
|
||||
// TODO: [#1313] SDK improvements so a client doesn't need to determing if the transaction isPending
|
||||
// https://github.com/zcash/ZcashLightClientKit/issues/1313
|
||||
// The only reason why `latestBlockHeight` is provided here is to determine pending
|
||||
// state of the transaction. SDK knows the latestBlockHeight so ideally ZcashTransaction.Overview
|
||||
// already knows and provides isPending as a bool value.
|
||||
// Once SDK's #1313 is done, adopt the SDK and remove latestBlockHeight here.
|
||||
let isPending = transaction.isPending(currentHeight: latestBlockHeight)
|
||||
|
||||
switch (isSentTransaction, isPending) {
|
||||
case (true, true): status = .sending
|
||||
case (true, false): status = .paid
|
||||
case (false, true): status = .receiving
|
||||
case (false, false): status = .received
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +267,91 @@ extension TransactionState {
|
|||
zecAmount: status == .received ? amount : Zatoshi(-amount.amount)
|
||||
)
|
||||
}
|
||||
|
||||
public static let mockedSent = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "utest1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzjanqtl8uqp5vln3zyy246ejtx86vqftp73j7jg9099jxafyjhfm6u956j3",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
public static let mockedReceived = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699292621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
public static let mockedFailed = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: nil,
|
||||
zAddress: "utest1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzjanqtl8uqp5vln3zyy246ejtx86vqftp73j7jg9099jxafyjhfm6u956j3",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: true,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
public static let mockedFailedReceive = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: nil,
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
public static let mockedSending = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: nil,
|
||||
zAddress: "utest1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzjanqtl8uqp5vln3zyy246ejtx86vqftp73j7jg9099jxafyjhfm6u956j3",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .sending,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: true,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
public static let mockedReceiving = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: nil,
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .receiving,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
}
|
||||
|
||||
public struct TransactionStateMockHelper {
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// WalletEvent.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 20.06.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
import SwiftUI
|
||||
import ZcashLightClientKit
|
||||
import Utils
|
||||
|
||||
// MARK: - Model
|
||||
|
||||
public struct WalletEvent: Equatable, Identifiable, Redactable {
|
||||
public enum WalletEventState: Equatable {
|
||||
case transaction(TransactionState)
|
||||
case shielded(Zatoshi)
|
||||
case walletImport(BlockHeight)
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let state: WalletEventState
|
||||
public var timestamp: TimeInterval?
|
||||
|
||||
public init(id: String, state: WalletEventState, timestamp: TimeInterval? = nil) {
|
||||
self.id = id
|
||||
self.state = state
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ public struct BalanceTitle: View {
|
|||
|
||||
Circle()
|
||||
.frame(width: 25, height: 25)
|
||||
.foregroundColor(Asset.Colors.tabsUnderline.color)
|
||||
.foregroundColor(Asset.Colors.primaryTint.color)
|
||||
.overlay {
|
||||
ZcashSymbol()
|
||||
.frame(width: 15, height: 15)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// FullBalanceTitle.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 03.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Generated
|
||||
import ZcashLightClientKit
|
||||
|
||||
public struct FullBalanceTitle: View {
|
||||
let primary: String
|
||||
let secondary: String
|
||||
let fontName: String
|
||||
let primaryFontSize: CGFloat
|
||||
let secondaryFontSize: CGFloat
|
||||
|
||||
public init(
|
||||
primary: String,
|
||||
secondary: String,
|
||||
fontName: String,
|
||||
primaryFontSize: CGFloat,
|
||||
secondaryFontSize: CGFloat
|
||||
) {
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
self.fontName = fontName
|
||||
self.primaryFontSize = primaryFontSize
|
||||
self.secondaryFontSize = secondaryFontSize
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
Text(primary)
|
||||
.font(.custom(fontName, size: primaryFontSize))
|
||||
+ Text(secondary)
|
||||
.font(.custom(fontName, size: secondaryFontSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack {
|
||||
FullBalanceTitle(
|
||||
primary: "0.001",
|
||||
secondary: "35466",
|
||||
fontName: FontFamily.Inter.regular.name,
|
||||
primaryFontSize: 13,
|
||||
secondaryFontSize: 9
|
||||
)
|
||||
}
|
||||
}
|
|
@ -24,16 +24,16 @@ public struct ZcashButtonStyle: ButtonStyle {
|
|||
.frame(height: 60)
|
||||
.foregroundColor(
|
||||
appearance == .primary ? .clear
|
||||
: isEnabled ? Asset.Colors.primary.color : Asset.Colors.suppressed72.color
|
||||
: isEnabled ? Asset.Colors.primary.color : Asset.Colors.shade72.color
|
||||
)
|
||||
.border(isEnabled ? Asset.Colors.primary.color : Asset.Colors.suppressed72.color)
|
||||
.border(isEnabled ? Asset.Colors.primary.color : Asset.Colors.shade72.color)
|
||||
.offset(CGSize(width: 10, height: 10))
|
||||
|
||||
Rectangle()
|
||||
.frame(height: 60)
|
||||
.foregroundColor(
|
||||
appearance == .primary ?
|
||||
isEnabled ? Asset.Colors.primary.color : Asset.Colors.suppressed72.color
|
||||
isEnabled ? Asset.Colors.primary.color : Asset.Colors.shade72.color
|
||||
: Asset.Colors.secondary.color
|
||||
)
|
||||
.border(Asset.Colors.primary.color)
|
||||
|
@ -42,7 +42,7 @@ public struct ZcashButtonStyle: ButtonStyle {
|
|||
.font(.custom(FontFamily.Inter.medium.name, size: 14))
|
||||
.foregroundColor(
|
||||
appearance == .primary ? Asset.Colors.secondary.color
|
||||
: isEnabled ? Asset.Colors.primary.color : Asset.Colors.suppressed72.color
|
||||
: isEnabled ? Asset.Colors.primary.color : Asset.Colors.shade72.color
|
||||
)
|
||||
})
|
||||
.offset(CGSize(width: offset, height: offset))
|
||||
|
|
|
@ -8,15 +8,28 @@
|
|||
import SwiftUI
|
||||
import Generated
|
||||
|
||||
struct MessageShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
public struct MessageShape: Shape {
|
||||
public enum Orientation: Sendable {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
let orientation: MessageShape.Orientation
|
||||
|
||||
public func path(in rect: CGRect) -> Path {
|
||||
Path { path in
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
path.addLine(to: CGPoint(x: 0, y: rect.height))
|
||||
|
||||
path.addLine(to: CGPoint(x: 15, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: 15, y: rect.height + 7))
|
||||
path.addLine(to: CGPoint(x: 35, y: rect.height))
|
||||
if orientation == .left {
|
||||
path.addLine(to: CGPoint(x: 15, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: 15, y: rect.height + 7))
|
||||
path.addLine(to: CGPoint(x: 35, y: rect.height))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: rect.width - 35, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: rect.width - 15, y: rect.height + 7))
|
||||
path.addLine(to: CGPoint(x: rect.width - 15, y: rect.height))
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
|
||||
path.addLine(to: CGPoint(x: rect.width, y: 0))
|
||||
|
@ -25,33 +38,56 @@ struct MessageShape: Shape {
|
|||
}
|
||||
}
|
||||
|
||||
struct MessageShapeModifier: ViewModifier {
|
||||
let filled: Bool
|
||||
public struct MessageShapeModifier: ViewModifier {
|
||||
let filled: Color?
|
||||
let orientation: MessageShape.Orientation
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.overlay {
|
||||
if filled {
|
||||
MessageShape()
|
||||
.foregroundColor(Asset.Colors.suppressed72.color)
|
||||
MessageShape()
|
||||
.stroke()
|
||||
} else {
|
||||
MessageShape()
|
||||
.stroke()
|
||||
.background {
|
||||
if let filled {
|
||||
MessageShape(orientation: orientation)
|
||||
.foregroundColor(filled)
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
MessageShape(orientation: orientation)
|
||||
.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
public func messageShape(filled: Bool = false) -> some View {
|
||||
modifier(MessageShapeModifier(filled: filled))
|
||||
public func messageShape(
|
||||
filled: Color? = nil,
|
||||
orientation: MessageShape.Orientation = .left
|
||||
) -> some View {
|
||||
modifier(
|
||||
MessageShapeModifier(
|
||||
filled: filled,
|
||||
orientation: orientation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
Text("some message")
|
||||
.frame(width: 320, height: 145)
|
||||
.messageShape(filled: true)
|
||||
VStack {
|
||||
Text("some message")
|
||||
.padding(5)
|
||||
.messageShape(
|
||||
filled: Asset.Colors.messageBcgReceived.color,
|
||||
orientation: .right
|
||||
)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Text("some message")
|
||||
.padding(5)
|
||||
.messageShape()
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Text("some message")
|
||||
.frame(width: 320, height: 145)
|
||||
.messageShape(filled: Asset.Colors.shade72.color)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// ConditionalFont.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 08.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Text {
|
||||
public func conditionalFont(condition: Bool, true: Font, else: Font) -> Text {
|
||||
if condition {
|
||||
return self
|
||||
.font(`true`)
|
||||
} else {
|
||||
return self
|
||||
.font(`else`)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ConditionalStrikethrough.swift
|
||||
//
|
||||
//
|
||||
// Created by Lukáš Korba on 08.11.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Text {
|
||||
public func conditionalStrikethrough(_ on: Bool) -> Text {
|
||||
if on {
|
||||
return self
|
||||
.strikethrough()
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,14 +42,18 @@ public struct MessageEditor: View {
|
|||
.focused($isFocused)
|
||||
.padding(2)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 14))
|
||||
.messageShape(filled: !isEnabled)
|
||||
.messageShape(
|
||||
filled: isEnabled
|
||||
? nil
|
||||
: Asset.Colors.shade72.color
|
||||
)
|
||||
.overlay {
|
||||
if message.isEmpty || !isEnabled {
|
||||
HStack {
|
||||
VStack {
|
||||
Text(L10n.Send.memoPlaceholder)
|
||||
.font(.custom(FontFamily.Inter.regular.name, size: 13))
|
||||
.foregroundColor(Asset.Colors.suppressed72.color)
|
||||
.foregroundColor(Asset.Colors.shade72.color)
|
||||
.onTapGesture {
|
||||
isFocused = true
|
||||
}
|
||||
|
@ -80,7 +84,7 @@ public struct MessageEditor: View {
|
|||
.font(.custom(FontFamily.Inter.bold.name, size: 13))
|
||||
.foregroundColor(
|
||||
viewStore.isValid
|
||||
? Asset.Colors.suppressed72.color
|
||||
? Asset.Colors.shade72.color
|
||||
: Asset.Colors.error.color
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,4 +12,8 @@ extension Zatoshi {
|
|||
public func decimalZashiFormatted() -> String {
|
||||
NumberFormatter.zashiBalanceFormatter.string(from: decimalValue.roundedZec) ?? ""
|
||||
}
|
||||
|
||||
public func decimalZashiFullFormatted() -> String {
|
||||
NumberFormatter.zcashNumberFormatter8FractionDigits.string(from: decimalValue.roundedZec) ?? ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ extension Date {
|
|||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"//"d MMM h:mm a"
|
||||
formatter.amSymbol = "am"
|
||||
formatter.pmSymbol = "pm"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
34DA414928E439CD00F8CC61 /* sendingTransaction.json in Resources */ = {isa = PBXBuildFile; fileRef = 34DA414828E439CD00F8CC61 /* sendingTransaction.json */; };
|
||||
9E00319C2A272BB6003DFCEB /* SDKSynchronizer in Frameworks */ = {isa = PBXBuildFile; productRef = 9E00319B2A272BB6003DFCEB /* SDKSynchronizer */; };
|
||||
9E0031A42A272BC7003DFCEB /* SDKSynchronizer in Frameworks */ = {isa = PBXBuildFile; productRef = 9E0031A32A272BC7003DFCEB /* SDKSynchronizer */; };
|
||||
9E0C0D702AFB842B00D69A16 /* TransactionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0C0D6F2AFB842B00D69A16 /* TransactionStateTests.swift */; };
|
||||
9E1FAFB72AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1FAFB62AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift */; };
|
||||
9E1FAFBA2AF2C7F20084CA3D /* PrivateDataConsentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1FAFB92AF2C7F20084CA3D /* PrivateDataConsentTests.swift */; };
|
||||
9E2F1C8C280ED6A7004E65FE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8B280ED6A7004E65FE /* LaunchScreen.storyboard */; };
|
||||
|
@ -52,7 +53,7 @@
|
|||
9E3451B529C8565500177D16 /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0F574A2980260D005304FA /* LoggerTests.swift */; };
|
||||
9E3451B629C8565500177D16 /* ZatoshiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E39112D283F91600073DD9A /* ZatoshiTests.swift */; };
|
||||
9E3451B729C8565500177D16 /* DatabaseFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */; };
|
||||
9E3451B829C856E700177D16 /* WalletEventsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63E2819542C00BA3F17 /* WalletEventsTests.swift */; };
|
||||
9E3451B829C856E700177D16 /* TransactionListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF63E2819542C00BA3F17 /* TransactionListTests.swift */; };
|
||||
9E3451B929C857C000177D16 /* AddressDetailsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E207C352966EC77003E2C9B /* AddressDetailsSnapshotTests.swift */; };
|
||||
9E3451BA29C857C500177D16 /* BalanceBreakdownSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E94C62228AA7EE0008256E9 /* BalanceBreakdownSnapshotTests.swift */; };
|
||||
9E3451BB29C857C800177D16 /* HomeSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC8C28589E150099D5A2 /* HomeSnapshotTests.swift */; };
|
||||
|
@ -62,7 +63,7 @@
|
|||
9E3451C229C857DB00177D16 /* TransactionSendingSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34429C6D28E703CD00F2B929 /* TransactionSendingSnapshotTests.swift */; };
|
||||
9E3451C329C857DD00177D16 /* SettingsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */; };
|
||||
9E3451C429C857DF00177D16 /* View+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E92AF0728530EBF007367AD /* View+UIImage.swift */; };
|
||||
9E3451C529C857E400177D16 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
|
||||
9E3451C529C857E400177D16 /* TransactionListSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* TransactionListSnapshotTests.swift */; };
|
||||
9E3451C629C857E700177D16 /* WelcomeSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC8E28589E150099D5A2 /* WelcomeSnapshotTests.swift */; };
|
||||
9E46919A2AD5735E0082D7DF /* TabsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4691992AD5735E0082D7DF /* TabsTests.swift */; };
|
||||
9E4938D82ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4938D72ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift */; };
|
||||
|
@ -120,6 +121,7 @@
|
|||
34F039B229ABCE8500CF0053 /* WalletConfigProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConfigProviderTests.swift; sourceTree = "<group>"; };
|
||||
9E01F8272833CDA0000EFC57 /* ScanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanTests.swift; sourceTree = "<group>"; };
|
||||
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFilesTests.swift; sourceTree = "<group>"; };
|
||||
9E0C0D6F2AFB842B00D69A16 /* TransactionStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStateTests.swift; sourceTree = "<group>"; };
|
||||
9E0F574A2980260D005304FA /* LoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = "<group>"; };
|
||||
9E1FAFB62AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConsentSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E1FAFB92AF2C7F20084CA3D /* PrivateDataConsentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConsentTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -133,7 +135,7 @@
|
|||
9E4691992AD5735E0082D7DF /* TabsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTests.swift; sourceTree = "<group>"; };
|
||||
9E4938D72ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWarningSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E5AAEBF2A67CEC4003F283D /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
|
||||
9E5BF63E2819542C00BA3F17 /* WalletEventsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsTests.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>"; };
|
||||
9E6612352878345000C75B70 /* endlessCircleProgress.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = endlessCircleProgress.json; sourceTree = "<group>"; };
|
||||
|
@ -141,7 +143,7 @@
|
|||
9E6713F02897F81B00A6796F /* MultiLineTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineTextFieldTests.swift; sourceTree = "<group>"; };
|
||||
9E7225F02889539300DF7F17 /* SettingsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E74CCCF29DC0628003D6E32 /* ReviewRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewRequestTests.swift; sourceTree = "<group>"; };
|
||||
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E7CB6112869882D00A02233 /* TransactionListSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionListSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
9E852D6429B0A86300CF4AC1 /* DebugTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTests.swift; sourceTree = "<group>"; };
|
||||
9E90751D2A269BE300269308 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9E92AF0728530EBF007367AD /* View+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+UIImage.swift"; sourceTree = "<group>"; };
|
||||
|
@ -270,7 +272,7 @@
|
|||
9E4691982AD573420082D7DF /* TabsTests */,
|
||||
9EF8135927ECC25E0075AF48 /* UtilTests */,
|
||||
34F039B129ABCE8500CF0053 /* WalletConfigProviderTests */,
|
||||
9E5BF63D281953F900BA3F17 /* WalletEventsTests */,
|
||||
9E5BF63D281953F900BA3F17 /* TransactionListTests */,
|
||||
);
|
||||
path = secantTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -386,7 +388,7 @@
|
|||
346715A628E20FB30035F7C4 /* SendSnapshotTests */,
|
||||
9E7225EF2889537E00DF7F17 /* SettingsSnapshotTests */,
|
||||
9E92AF0728530EBF007367AD /* View+UIImage.swift */,
|
||||
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */,
|
||||
9E7CB6102869881300A02233 /* TransactionListSnapshotTests */,
|
||||
9E9ECC8D28589E150099D5A2 /* WelcomeSnapshotTests */,
|
||||
);
|
||||
path = SnapshotTests;
|
||||
|
@ -408,12 +410,13 @@
|
|||
path = SecurityWarningSnapshotTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E5BF63D281953F900BA3F17 /* WalletEventsTests */ = {
|
||||
9E5BF63D281953F900BA3F17 /* TransactionListTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E5BF63E2819542C00BA3F17 /* WalletEventsTests.swift */,
|
||||
9E5BF63E2819542C00BA3F17 /* TransactionListTests.swift */,
|
||||
9E0C0D6F2AFB842B00D69A16 /* TransactionStateTests.swift */,
|
||||
);
|
||||
path = WalletEventsTests;
|
||||
path = TransactionListTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E5BF642281FEC8700BA3F17 /* SendTests */ = {
|
||||
|
@ -476,12 +479,12 @@
|
|||
path = ReviewRequestTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */ = {
|
||||
9E7CB6102869881300A02233 /* TransactionListSnapshotTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */,
|
||||
9E7CB6112869882D00A02233 /* TransactionListSnapshotTests.swift */,
|
||||
);
|
||||
path = WalletEventsSnapshotTests;
|
||||
path = TransactionListSnapshotTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E7FE0B6282D1D9800C374E8 /* Resources */ = {
|
||||
|
@ -908,7 +911,7 @@
|
|||
9E34519E29C849B400177D16 /* WalletConfigProviderTests.swift in Sources */,
|
||||
9E3451C029C857D400177D16 /* RecoveryPhraseDisplaySnapshotTests.swift in Sources */,
|
||||
9E3451B629C8565500177D16 /* ZatoshiTests.swift in Sources */,
|
||||
9E3451B829C856E700177D16 /* WalletEventsTests.swift in Sources */,
|
||||
9E3451B829C856E700177D16 /* TransactionListTests.swift in Sources */,
|
||||
9E34519529C4A4BF00177D16 /* AddressDetailsTests.swift in Sources */,
|
||||
9E3451B929C857C000177D16 /* AddressDetailsSnapshotTests.swift in Sources */,
|
||||
9E34519B29C4A90700177D16 /* DeeplinkTests.swift in Sources */,
|
||||
|
@ -917,12 +920,13 @@
|
|||
9E4938D82ACE8E8F003C4C1D /* SecurityWarningSnapshotTests.swift in Sources */,
|
||||
9E3451C429C857DF00177D16 /* View+UIImage.swift in Sources */,
|
||||
9E34519729C4A51100177D16 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||
9E0C0D702AFB842B00D69A16 /* TransactionStateTests.swift in Sources */,
|
||||
9E3451BC29C857C800177D16 /* NotEnoughFeeSpaceSnapshots.swift in Sources */,
|
||||
9E3451B229C8565500177D16 /* SecItemClientTests.swift in Sources */,
|
||||
9E3451C629C857E700177D16 /* WelcomeSnapshotTests.swift in Sources */,
|
||||
9E3451B329C8565500177D16 /* WalletBalance+testing.swift in Sources */,
|
||||
9E3451AA29C84ED500177D16 /* CurrencySelectionTests.swift in Sources */,
|
||||
9E3451C529C857E400177D16 /* WalletEventsSnapshotTests.swift in Sources */,
|
||||
9E3451C529C857E400177D16 /* TransactionListSnapshotTests.swift in Sources */,
|
||||
9E34519829C4A51100177D16 /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9E34519C29C4A91A00177D16 /* HomeTests.swift in Sources */,
|
||||
9E1FAFB72AF2C6D40084CA3D /* PrivateDataConsentSnapshotTests.swift in Sources */,
|
||||
|
|
|
@ -50,8 +50,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "1cd556b33550982ec17f80e358253d905e756f0f",
|
||||
"version" : "7.11.6"
|
||||
"revision" : "f6532c8d65f8308cfdf2288cbe1971a509822680",
|
||||
"version" : "7.12.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -95,8 +95,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b",
|
||||
"version" : "1.22.2"
|
||||
"revision" : "9d108e9112aa1d65ce508facf804674546116d9c",
|
||||
"version" : "1.22.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -27,7 +27,7 @@ class HomeTests: XCTestCase {
|
|||
shieldedBalance: Balance.zero,
|
||||
synchronizerStatusSnapshot: mockSnapshot,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .emptyPlaceHolder
|
||||
transactionListState: .emptyPlaceHolder
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
|
|
@ -15,13 +15,13 @@ import Home
|
|||
class HomeSnapshotTests: XCTestCase {
|
||||
func testHomeSnapshot() throws {
|
||||
let transactionsHelper: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: true), uuid: "1"),
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid, uuid: "1"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), status: .sending, uuid: "2"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .received, uuid: "3"),
|
||||
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(4), status: .failed, uuid: "4")
|
||||
]
|
||||
|
||||
let walletEvents: [WalletEvent] = transactionsHelper.map {
|
||||
let transactionList: [TransactionState] = transactionsHelper.map {
|
||||
var transaction = TransactionState.placeholder(
|
||||
amount: $0.amount,
|
||||
fee: Zatoshi(10),
|
||||
|
@ -32,7 +32,7 @@ class HomeSnapshotTests: XCTestCase {
|
|||
)
|
||||
transaction.zAddress = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||
|
||||
return WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
return transaction
|
||||
}
|
||||
|
||||
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
|
||||
|
@ -43,7 +43,7 @@ class HomeSnapshotTests: XCTestCase {
|
|||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents))
|
||||
transactionListState: .init(transactionList: IdentifiedArrayOf(uniqueElements: transactionList))
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
.dependency(\.diskSpaceChecker, .mockEmptyDisk)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// TransactionListSnapshotTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 27.06.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import TransactionList
|
||||
import Home
|
||||
@testable import secant_testnet
|
||||
|
||||
class TransactionListSnapshotTests: XCTestCase {
|
||||
func testFullTransactionListSnapshot() throws {
|
||||
let store = TransactionListStore(
|
||||
initialState: .placeHolder,
|
||||
reducer: TransactionListReducer()
|
||||
.dependency(\.sdkSynchronizer, .mock)
|
||||
.dependency(\.mainQueue, .immediate)
|
||||
)
|
||||
|
||||
// landing wallet events screen
|
||||
addAttachments(
|
||||
name: "\(#function)_initial",
|
||||
TransactionListView(store: store, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
//
|
||||
// WalletEventsSnapshotTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 27.06.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import WalletEventsFlow
|
||||
import Home
|
||||
@testable import secant_testnet
|
||||
|
||||
class WalletEventsSnapshotTests: XCTestCase {
|
||||
func testFullWalletEventsSnapshot() throws {
|
||||
let store = WalletEventsFlowStore(
|
||||
initialState: .placeHolder,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
.dependency(\.sdkSynchronizer, .mock)
|
||||
.dependency(\.mainQueue, .immediate)
|
||||
)
|
||||
|
||||
// landing wallet events screen
|
||||
addAttachments(
|
||||
name: "\(#function)_initial",
|
||||
WalletEventsFlowView(store: store, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
|
||||
func testWalletEventDetailSnapshot_sent() throws {
|
||||
let memo = try? Memo(string:
|
||||
"""
|
||||
Testing some long memo so I can see many lines of text \
|
||||
instead of just one. This can take some time and I'm \
|
||||
bored to write all this stuff.
|
||||
""")
|
||||
guard let memo else {
|
||||
XCTFail("testWalletEventDetailSnapshot_sent: memo is expected to be successfuly initialized")
|
||||
return
|
||||
}
|
||||
|
||||
let transaction = TransactionState(
|
||||
memos: [memo],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .paid(success: true),
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
)
|
||||
|
||||
let walletEvent = WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
|
||||
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
|
||||
let store = HomeStore(
|
||||
initialState: .init(
|
||||
scanState: .placeholder,
|
||||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
||||
ViewStore(store).send(.walletEvents(.updateDestination(.showWalletEvent(walletEvent))))
|
||||
let walletEventsStore = WalletEventsFlowStore(
|
||||
initialState: .placeHolder,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
addAttachments(
|
||||
name: "\(#function)_WalletEventDetail",
|
||||
TransactionDetailView(store: walletEventsStore, transaction: transaction, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
|
||||
func testWalletEventDetailSnapshot_received() throws {
|
||||
let memo = try? Memo(string:
|
||||
"""
|
||||
Testing some long memo so I can see many lines of text \
|
||||
instead of just one. This can take some time and I'm \
|
||||
bored to write all this stuff.
|
||||
""")
|
||||
guard let memo else {
|
||||
XCTFail("testWalletEventDetailSnapshot_received: memo is expected to be successfuly initialized")
|
||||
return
|
||||
}
|
||||
|
||||
let transaction = TransactionState(
|
||||
memos: [memo],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .received,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
)
|
||||
|
||||
let walletEvent = WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
|
||||
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
|
||||
let store = HomeStore(
|
||||
initialState: .init(
|
||||
scanState: .placeholder,
|
||||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
||||
ViewStore(store).send(.walletEvents(.updateDestination(.showWalletEvent(walletEvent))))
|
||||
let walletEventsStore = WalletEventsFlowStore(
|
||||
initialState: .placeHolder,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
addAttachments(
|
||||
name: "\(#function)_WalletEventDetail",
|
||||
TransactionDetailView(store: walletEventsStore, transaction: transaction, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
|
||||
func testWalletEventDetailSnapshot_pending() throws {
|
||||
let memo = try? Memo(string:
|
||||
"""
|
||||
Testing some long memo so I can see many lines of text \
|
||||
instead of just one. This can take some time and I'm \
|
||||
bored to write all this stuff.
|
||||
""")
|
||||
guard let memo else {
|
||||
XCTFail("testWalletEventDetailSnapshot_pending: memo is expected to be successfuly initialized")
|
||||
return
|
||||
}
|
||||
|
||||
let transaction = TransactionState(
|
||||
memos: [memo],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .sending,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
)
|
||||
|
||||
let walletEvent = WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
|
||||
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
|
||||
let store = HomeStore(
|
||||
initialState: .init(
|
||||
scanState: .placeholder,
|
||||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
||||
let walletEventsState = WalletEventsFlowReducer.State(
|
||||
requiredTransactionConfirmations: 10,
|
||||
walletEvents: .placeholder
|
||||
)
|
||||
|
||||
ViewStore(store).send(.walletEvents(.updateDestination(.showWalletEvent(walletEvent))))
|
||||
let walletEventsStore = WalletEventsFlowStore(
|
||||
initialState: walletEventsState,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
addAttachments(
|
||||
name: "\(#function)_WalletEventDetail",
|
||||
TransactionDetailView(store: walletEventsStore, transaction: transaction, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
|
||||
func testWalletEventDetailSnapshot_failed() throws {
|
||||
let memo = try? Memo(string:
|
||||
"""
|
||||
Testing some long memo so I can see many lines of text \
|
||||
instead of just one. This can take some time and I'm \
|
||||
bored to write all this stuff.
|
||||
""")
|
||||
guard let memo else {
|
||||
XCTFail("testWalletEventDetailSnapshot_failed: memo is expected to be successfuly initialized")
|
||||
return
|
||||
}
|
||||
|
||||
let transaction = TransactionState(
|
||||
errorMessage: "possible roll back",
|
||||
memos: [memo],
|
||||
minedHeight: 1_875_256,
|
||||
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
|
||||
fee: Zatoshi(1_000_000),
|
||||
id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8",
|
||||
status: .failed,
|
||||
timestamp: 1234567,
|
||||
zecAmount: Zatoshi(25_000_000)
|
||||
)
|
||||
|
||||
let walletEvent = WalletEvent(id: transaction.id, state: .transaction(transaction), timestamp: transaction.timestamp)
|
||||
|
||||
let balance = WalletBalance(verified: 12_345_000, total: 12_345_000)
|
||||
let store = HomeStore(
|
||||
initialState: .init(
|
||||
scanState: .placeholder,
|
||||
shieldedBalance: balance.redacted,
|
||||
synchronizerStatusSnapshot: .default,
|
||||
walletConfig: .default,
|
||||
walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent]))
|
||||
),
|
||||
reducer: HomeReducer(networkType: .testnet)
|
||||
)
|
||||
|
||||
ViewStore(store).send(.walletEvents(.updateDestination(.showWalletEvent(walletEvent))))
|
||||
let walletEventsStore = WalletEventsFlowStore(
|
||||
initialState: .placeHolder,
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
addAttachments(
|
||||
name: "\(#function)_WalletEventDetail",
|
||||
TransactionDetailView(store: walletEventsStore, transaction: transaction, tokenName: "ZEC")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
//
|
||||
// TransactionListTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 27.04.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Pasteboard
|
||||
import Models
|
||||
import TransactionList
|
||||
@testable import secant_testnet
|
||||
|
||||
class TransactionListTests: XCTestCase {
|
||||
@MainActor func testSynchronizerSubscription() async throws {
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: []
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.dependencies.sdkSynchronizer = .mocked()
|
||||
store.dependencies.sdkSynchronizer.getAllTransactions = { [] }
|
||||
store.dependencies.mainQueue = .immediate
|
||||
|
||||
await store.send(.onAppear) { state in
|
||||
state.requiredTransactionConfirmations = 10
|
||||
}
|
||||
|
||||
await store.receive(.synchronizerStateChanged(.unprepared))
|
||||
|
||||
await store.receive(.updateTransactionList([]))
|
||||
|
||||
// ending the subscription
|
||||
await store.send(.onDisappear)
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
@MainActor func testSynchronizerStateChanged2Synced() async throws {
|
||||
let mocked: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid, uuid: "aa11"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), uuid: "bb22"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .paid, uuid: "cc33"),
|
||||
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(4), uuid: "dd44"),
|
||||
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(5), uuid: "ee55"),
|
||||
TransactionStateMockHelper(
|
||||
date: 1651039606,
|
||||
amount: Zatoshi(6),
|
||||
status: .paid,
|
||||
uuid: "ff66"
|
||||
),
|
||||
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(7), uuid: "gg77"),
|
||||
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(8), status: .paid, uuid: "hh88"),
|
||||
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(9), uuid: "ii99")
|
||||
]
|
||||
|
||||
let transactionList: [TransactionState] = mocked.map {
|
||||
let transaction = TransactionState.placeholder(
|
||||
amount: $0.amount,
|
||||
fee: Zatoshi(10),
|
||||
shielded: $0.shielded,
|
||||
status: $0.amount.amount > 5 ? .sending : $0.status,
|
||||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
return transaction
|
||||
}
|
||||
|
||||
let identifiedTransactionList = IdentifiedArrayOf(uniqueElements: transactionList)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: identifiedTransactionList
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.sdkSynchronizer = .mocked()
|
||||
|
||||
await store.send(.synchronizerStateChanged(.upToDate)) { state in
|
||||
state.latestMinedHeight = 0
|
||||
}
|
||||
|
||||
await store.receive(.updateTransactionList(transactionList)) { state in
|
||||
let receivedTransactionList = IdentifiedArrayOf(
|
||||
uniqueElements:
|
||||
transactionList
|
||||
.sorted(by: { lhs, rhs in
|
||||
guard let lhsTimestamp = lhs.timestamp, let rhsTimestamp = rhs.timestamp else {
|
||||
return false
|
||||
}
|
||||
return lhsTimestamp > rhsTimestamp
|
||||
})
|
||||
)
|
||||
|
||||
state.transactionList = receivedTransactionList
|
||||
state.latestTranassctionId = "ii99"
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testCopyToPasteboard() throws {
|
||||
let testPasteboard = PasteboardClient.testPasteboard
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: []
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.dependencies.pasteboard = testPasteboard
|
||||
|
||||
let testText = "test text".redacted
|
||||
store.send(.copyToPastboard(testText))
|
||||
|
||||
XCTAssertEqual(
|
||||
testPasteboard.getString()?.data,
|
||||
testText.data,
|
||||
"WalletEvetns: `testCopyToPasteboard` is expected to match the input `\(testText.data)`"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Expansion
|
||||
|
||||
func testTransactionExpansion() throws {
|
||||
let id = "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja"
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: id,
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: [transaction]
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.send(.transactionExpandRequested(id)) { state in
|
||||
state.transactionList[0].isExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
func testAddressExpansionRequestedButTransactionIsNot() throws {
|
||||
let id = "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja"
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: id,
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: [transaction]
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.send(.transactionAddressExpandRequested(id)) { state in
|
||||
state.transactionList[0].isExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
func testAddressExpansion() throws {
|
||||
let id = "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja"
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: id,
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: true,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: [transaction]
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.send(.transactionAddressExpandRequested(id)) { state in
|
||||
state.transactionList[0].isAddressExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
func testIdExpansionRequestedButTransactionIsNot() throws {
|
||||
let id = "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja"
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: id,
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: [transaction]
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.send(.transactionIdExpandRequested(id)) { state in
|
||||
state.transactionList[0].isExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
func testIdExpansion() throws {
|
||||
let id = "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja"
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: id,
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: true,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: TransactionListReducer.State(
|
||||
isScrollable: true,
|
||||
transactionList: [transaction]
|
||||
),
|
||||
reducer: TransactionListReducer()
|
||||
)
|
||||
|
||||
store.send(.transactionIdExpandRequested(id)) { state in
|
||||
state.transactionList[0].isIdExpanded = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,434 @@
|
|||
//
|
||||
// TransactionStateTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 08.11.2023.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ZcashLightClientKit
|
||||
import Models
|
||||
import Generated
|
||||
|
||||
final class TransactionStateTests: XCTestCase {
|
||||
// MARK: - Title tests (String & Color)
|
||||
|
||||
func testTitleSent() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.sent)
|
||||
}
|
||||
|
||||
func testTitleSentColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.primary.color)
|
||||
}
|
||||
|
||||
func testTitleReceived() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.received)
|
||||
}
|
||||
|
||||
func testTitleReceivedColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.primary.color)
|
||||
}
|
||||
|
||||
func testTitleSending() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .sending,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.sending)
|
||||
}
|
||||
|
||||
func testTitleSendingColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .sending,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.shade47.color)
|
||||
}
|
||||
|
||||
func testTitleReceiving() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .receiving,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.receiving)
|
||||
}
|
||||
|
||||
func testTitleReceivingColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .receiving,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.shade47.color)
|
||||
}
|
||||
|
||||
func testTitleFailedSend() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: true,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.failedSend)
|
||||
}
|
||||
|
||||
func testTitleFailedSendColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: true,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
func testTitleFailedReceived() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.title, L10n.Transaction.failedReceive)
|
||||
}
|
||||
|
||||
func testTitleFailedReceivedColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.titleColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
// MARK: - Balance color
|
||||
|
||||
func testBalanceSentColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
func testBalanceReceivedColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.primary.color)
|
||||
}
|
||||
|
||||
func testBalanceFailedSendColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: true,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
func testBalanceFailedReceivedColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .failed,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
func testBalanceSendingColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .sending,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.error.color)
|
||||
}
|
||||
|
||||
func testBalanceReceivingColor() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .receiving,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_000_000),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.balanceColor, Asset.Colors.primary.color)
|
||||
}
|
||||
|
||||
// MARK: - Balances and String representations for collapsed/expanded states
|
||||
|
||||
func testExpandedPaidAmountTupple() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_793_456),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
// 0.025793456
|
||||
let expandedString = transaction.expandedAmountString
|
||||
|
||||
XCTAssertEqual(expandedString.primary, "-0.257")
|
||||
XCTAssertEqual(expandedString.secondary, "93456")
|
||||
}
|
||||
|
||||
func testExpandedReceivedAmountTupple() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_793_456),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
let expandedString = transaction.expandedAmountString
|
||||
|
||||
XCTAssertEqual(expandedString.primary, "+0.257")
|
||||
XCTAssertEqual(expandedString.secondary, "93456")
|
||||
}
|
||||
|
||||
func testCollapsedPrimaryAmountRoundingForSend() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .paid,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_793_456),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.roundedAmountString, "-0.258")
|
||||
}
|
||||
|
||||
func testCollapsedPrimaryAmountRoundingForReceive() throws {
|
||||
let transaction = TransactionState(
|
||||
memos: [try! Memo(string: "Hi, pay me and I'll pay you")],
|
||||
minedHeight: BlockHeight(1),
|
||||
zAddress: "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE",
|
||||
fee: Zatoshi(10_000),
|
||||
id: "t1vergg5jkp4wy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzja",
|
||||
status: .received,
|
||||
timestamp: 1699290621,
|
||||
zecAmount: Zatoshi(25_793_456),
|
||||
isSentTransaction: false,
|
||||
isAddressExpanded: false,
|
||||
isExpanded: false,
|
||||
isIdExpanded: false
|
||||
)
|
||||
|
||||
XCTAssertEqual(transaction.roundedAmountString, "+0.258")
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
//
|
||||
// WalletEventsTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 27.04.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import ComposableArchitecture
|
||||
import ZcashLightClientKit
|
||||
import Pasteboard
|
||||
import Models
|
||||
import WalletEventsFlow
|
||||
@testable import secant_testnet
|
||||
|
||||
class WalletEventsTests: XCTestCase {
|
||||
@MainActor func testSynchronizerSubscription() async throws {
|
||||
let store = TestStore(
|
||||
initialState: WalletEventsFlowReducer.State(
|
||||
destination: .latest,
|
||||
isScrollable: true,
|
||||
walletEvents: []
|
||||
),
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
store.dependencies.sdkSynchronizer = .mocked()
|
||||
store.dependencies.sdkSynchronizer.getAllTransactions = { [] }
|
||||
store.dependencies.mainQueue = .immediate
|
||||
|
||||
await store.send(.onAppear) { state in
|
||||
state.requiredTransactionConfirmations = 10
|
||||
}
|
||||
|
||||
await store.receive(.synchronizerStateChanged(.unprepared))
|
||||
|
||||
await store.receive(.updateWalletEvents([]))
|
||||
|
||||
// ending the subscription
|
||||
await store.send(.onDisappear)
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
@MainActor func testSynchronizerStateChanged2Synced() async throws {
|
||||
let mocked: [TransactionStateMockHelper] = [
|
||||
TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: false), uuid: "aa11"),
|
||||
TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), uuid: "bb22"),
|
||||
TransactionStateMockHelper(date: 1651039000, amount: Zatoshi(3), status: .paid(success: true), uuid: "cc33"),
|
||||
TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(4), uuid: "dd44"),
|
||||
TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(5), uuid: "ee55"),
|
||||
TransactionStateMockHelper(
|
||||
date: 1651039606,
|
||||
amount: Zatoshi(6),
|
||||
status: .paid(success: false),
|
||||
uuid: "ff66"
|
||||
),
|
||||
TransactionStateMockHelper(date: 1651039303, amount: Zatoshi(7), uuid: "gg77"),
|
||||
TransactionStateMockHelper(date: 1651039707, amount: Zatoshi(8), status: .paid(success: true), uuid: "hh88"),
|
||||
TransactionStateMockHelper(date: 1651039808, amount: Zatoshi(9), uuid: "ii99")
|
||||
]
|
||||
|
||||
let walletEvents: [WalletEvent] = mocked.map {
|
||||
let transaction = TransactionState.placeholder(
|
||||
amount: $0.amount,
|
||||
fee: Zatoshi(10),
|
||||
shielded: $0.shielded,
|
||||
status: $0.amount.amount > 5 ? .sending : $0.status,
|
||||
timestamp: $0.date,
|
||||
uuid: $0.uuid
|
||||
)
|
||||
return WalletEvent(
|
||||
id: transaction.id,
|
||||
state: .transaction(transaction),
|
||||
timestamp: transaction.timestamp
|
||||
)
|
||||
}
|
||||
|
||||
let identifiedWalletEvents = IdentifiedArrayOf(uniqueElements: walletEvents)
|
||||
|
||||
let store = TestStore(
|
||||
initialState: WalletEventsFlowReducer.State(
|
||||
destination: .latest,
|
||||
isScrollable: true,
|
||||
walletEvents: identifiedWalletEvents
|
||||
),
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
store.dependencies.mainQueue = .immediate
|
||||
store.dependencies.sdkSynchronizer = .mocked()
|
||||
|
||||
await store.send(.synchronizerStateChanged(.upToDate)) { state in
|
||||
state.latestMinedHeight = 0
|
||||
}
|
||||
|
||||
await store.receive(.updateWalletEvents(walletEvents)) { state in
|
||||
let receivedWalletEvents = IdentifiedArrayOf(
|
||||
uniqueElements:
|
||||
walletEvents
|
||||
.sorted(by: { lhs, rhs in
|
||||
guard let lhsTimestamp = lhs.timestamp, let rhsTimestamp = rhs.timestamp else {
|
||||
return false
|
||||
}
|
||||
return lhsTimestamp > rhsTimestamp
|
||||
})
|
||||
)
|
||||
|
||||
state.walletEvents = receivedWalletEvents
|
||||
}
|
||||
|
||||
await store.finish()
|
||||
}
|
||||
|
||||
func testCopyToPasteboard() throws {
|
||||
let testPasteboard = PasteboardClient.testPasteboard
|
||||
|
||||
let store = TestStore(
|
||||
initialState: WalletEventsFlowReducer.State(
|
||||
destination: .latest,
|
||||
isScrollable: true,
|
||||
walletEvents: []
|
||||
),
|
||||
reducer: WalletEventsFlowReducer()
|
||||
)
|
||||
|
||||
store.dependencies.pasteboard = testPasteboard
|
||||
|
||||
let testText = "test text".redacted
|
||||
store.send(.copyToPastboard(testText))
|
||||
|
||||
XCTAssertEqual(
|
||||
testPasteboard.getString()?.data,
|
||||
testText.data,
|
||||
"WalletEvetns: `testCopyToPasteboard` is expected to match the input `\(testText.data)`"
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue