From df2c721d327c8fa369ce1d2d53c8a5292a250a75 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 28 Jun 2022 09:38:08 +0200 Subject: [PATCH] [96] [Scaffold] Received Transaction Details (#378) - the view holding all requested (mocked) data is prepared - copy to pasteboard implemented - deeplink 'reply-to' memo implemented - view online implemented and button visible only when the URL is valid - asset colors for the texts - unit tests - snapshot tests --- secant.xcodeproj/project.pbxproj | 12 ++ secant/Features/App/AppStore.swift | 6 + secant/Features/Home/HomeStore.swift | 3 +- secant/Features/Sandbox/SandboxStore.swift | 3 +- .../Views/TransactionDetailView.swift | 172 +++++++++++++++++- .../WalletEventsFlowStore.swift | 17 +- .../WalletEventsFlowView.swift | 4 +- secant/Models/TransactionState.swift | 13 +- secant/Models/WalletEvent.swift | 6 +- .../Contents.json | 38 ++++ .../Generated/XCAssets+Generated.swift | 1 + secant/Wrappers/WrappedSDKSynchronizer.swift | 5 + secantTests/AppTests/AppTests.swift | 15 ++ secantTests/HomeTests/HomeTests.swift | 1 + secantTests/SendTests/SendTests.swift | 1 + .../HomeSnapshotTests/HomeSnapshotTests.swift | 63 ++++++- .../WalletEventsSnapshotTests.swift | 69 +++++++ .../WalletEventsTests/WalletEventsTests.swift | 37 +++- 18 files changed, 446 insertions(+), 20 deletions(-) create mode 100644 secant/Resources/Colors.xcassets/Text/TransactionDetailText.colorset/Contents.json create mode 100644 secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index d14e11a..004ad5d 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -120,6 +120,7 @@ 9E5BF64F2823E94900BA3F17 /* TransactionAddressTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */; }; 9E5BF6502823E94900BA3F17 /* TransactionAddressTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */; }; 9E69A24D27FB002800A55317 /* WelcomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* WelcomeStore.swift */; }; + 9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; }; 9E7FE0CF282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */; }; 9E7FE0D3282D274E00C374E8 /* Date+Readable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */; }; 9E7FE0D5282D281800C374E8 /* Array+Chunked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */; }; @@ -329,6 +330,7 @@ 9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextField.swift; sourceTree = ""; }; 9E5BF64E2823E94900BA3F17 /* TransactionAddressTextFieldStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextFieldStore.swift; sourceTree = ""; }; 9E69A24C27FB002800A55317 /* WelcomeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeStore.swift; sourceTree = ""; }; + 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletEventsSnapshotTests.swift; sourceTree = ""; }; 9E7FE0CE282D257400C374E8 /* SDKSynchronizer+SyncStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SDKSynchronizer+SyncStatus.swift"; sourceTree = ""; }; 9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Readable.swift"; sourceTree = ""; }; 9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Chunked.swift"; sourceTree = ""; }; @@ -810,6 +812,7 @@ 9E391162284E3ECF0073DD9A /* SnapshotTests */ = { isa = PBXGroup; children = ( + 9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */, 9E9ECC8B28589E150099D5A2 /* HomeSnapshotTests */, 9E9ECC9328589E150099D5A2 /* ImportWalletSnapshotTests */, 9E9ECC9528589E150099D5A2 /* OnboardingSnapshotTests */, @@ -858,6 +861,14 @@ path = TransactionAddress; sourceTree = ""; }; + 9E7CB6102869881300A02233 /* WalletEventsSnapshotTests */ = { + isa = PBXGroup; + children = ( + 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */, + ); + path = WalletEventsSnapshotTests; + sourceTree = ""; + }; 9E7FE0B6282D1D9800C374E8 /* Resources */ = { isa = PBXGroup; children = ( @@ -1543,6 +1554,7 @@ 9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */, 9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */, 9E9ECC9828589E150099D5A2 /* WelcomeSnapshotTests.swift in Sources */, + 9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */, 9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */, 0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */, 9E9ECC9A28589E150099D5A2 /* RecoveryPhraseValidationFlowSnapshotTests.swift in Sources */, diff --git a/secant/Features/App/AppStore.swift b/secant/Features/App/AppStore.swift index af71449..87e7854 100644 --- a/secant/Features/App/AppStore.swift +++ b/secant/Features/App/AppStore.swift @@ -342,6 +342,12 @@ extension AppReducer { state.homeState.sendState.memo = memo return .none + case .home(.walletEvents(.replyTo(let address))): + guard let url = URL(string: "zcash:\(address)") else { + return .none + } + return Effect(value: .deeplink(url)) + /// Default is meaningful here because there's `appReducer` handling actions and this reducer is handling only routes. We don't here plenty of unused cases. default: break diff --git a/secant/Features/Home/HomeStore.swift b/secant/Features/Home/HomeStore.swift index e0f77f6..d01d38d 100644 --- a/secant/Features/Home/HomeStore.swift +++ b/secant/Features/Home/HomeStore.swift @@ -181,7 +181,8 @@ extension HomeReducer { environment: { environment in WalletEventsFlowEnvironment( scheduler: environment.scheduler, - SDKSynchronizer: environment.SDKSynchronizer + SDKSynchronizer: environment.SDKSynchronizer, + pasteboard: .live ) } ) diff --git a/secant/Features/Sandbox/SandboxStore.swift b/secant/Features/Sandbox/SandboxStore.swift index 3b752a8..a104843 100644 --- a/secant/Features/Sandbox/SandboxStore.swift +++ b/secant/Features/Sandbox/SandboxStore.swift @@ -50,7 +50,8 @@ extension SandboxReducer { walletEventsAction, WalletEventsFlowEnvironment( scheduler: DispatchQueue.main.eraseToAnyScheduler(), - SDKSynchronizer: LiveWrappedSDKSynchronizer() + SDKSynchronizer: LiveWrappedSDKSynchronizer(), + pasteboard: .live ) ) .map(SandboxAction.walletEvents) diff --git a/secant/Features/WalletEventsFlow/Views/TransactionDetailView.swift b/secant/Features/WalletEventsFlow/Views/TransactionDetailView.swift index bb6bacb..1b1fd21 100644 --- a/secant/Features/WalletEventsFlow/Views/TransactionDetailView.swift +++ b/secant/Features/WalletEventsFlow/Views/TransactionDetailView.swift @@ -1,11 +1,146 @@ import SwiftUI +import ComposableArchitecture struct TransactionDetailView: View { var transaction: TransactionState + var viewStore: WalletEventsFlowViewStore + var body: some View { - Text(String(dumping: transaction)) + ScrollView { + HStack { + Text("\(transaction.date.asHumanReadable())") + Spacer() + Text("HEIGHT \(heightText)") + } .padding() - .navigationTitle("Transaction: \(transaction.id)") + + Text("\(amountPrefixText) \(transaction.zecAmount.decimalString()) ZEC") + .transactionDetailRow() + + Text("fee \(transaction.fee.decimalString()) ZEC") + .transactionDetailRow() + + Text("total amount \(transaction.totalAmount.decimalString()) ZEC") + .transactionDetailRow() + + Button { + viewStore.send(.copyToPastboard(transaction.address)) + } label: { + Text("\(addressPrefixText) \(transaction.address)") + .lineLimit(1) + .truncationMode(.middle) + .transactionDetailRow() + } + + if let memo = transaction.memo { + Button { + viewStore.send(.copyToPastboard(memo)) + } label: { + VStack { + Text("\(memo)") + .multilineTextAlignment(.leading) + + HStack { + Text("reply-to address included") + Spacer() + Button { + viewStore.send(.replyTo(transaction.address)) + } label: { + Text("reply now") + .padding(5) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Asset.Colors.Text.transactionDetailText.color, lineWidth: 1) + ) + } + } + } + .transactionDetailRow() + } + } + + HStack { + Text("Confirmed") + Spacer() + Text("\(transaction.confirmations) times") + } + .transactionDetailRow() + + Spacer() + + Button { + viewStore.send(.copyToPastboard(transaction.id)) + } label: { + Text("txn: \(transaction.id)") + .foregroundColor(Asset.Colors.Text.transactionDetailText.color) + .font(.system(size: 14)) + .fontWeight(.thin) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 60) + .padding(.vertical, 10) + .background(Asset.Colors.BackgroundColors.numberedChip.color) + .padding(.vertical, 30) + } + + Button { } label: { + // TODO: Warn users that they will leave the App when they follow a Block explorer + // https://github.com/zcash/secant-ios-wallet/issues/379 + if let viewOnlineURL = transaction.viewOnlineURL { + Link("View online", destination: viewOnlineURL) + } + } + .activeButtonStyle + .frame(height: 50) + .padding(.horizontal, 30) + } + .applyScreenBackground() + .navigationTitle("Transaction detail") + } +} + +extension TransactionDetailView { + var amountPrefixText: String { + transaction.status == .received ? "You received" : "You sent" + } + + var addressPrefixText: String { + transaction.status == .received ? "from" : "to" + } + + var heightText: String { + transaction.minedHeight > 0 ? String(transaction.minedHeight) : "unconfirmed" + } +} + +struct TransactionDetailRow: ViewModifier { + let tint: Color + 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(tint) + } +} + +extension View { + func transactionDetailRow( + _ tint: Color = Asset.Colors.BackgroundColors.red.color, + _ textColor: Color = Asset.Colors.Text.transactionDetailText.color, + _ backgroundColor: Color = Asset.Colors.BackgroundColors.numberedChip.color + ) -> some View { + modifier( + TransactionDetailRow( + tint: tint, + textColor: textColor, + backgroundColor: backgroundColor + ) + ) } } @@ -14,7 +149,38 @@ struct TransactionDetailView: View { struct TransactionDetail_Previews: PreviewProvider { static var previews: some View { NavigationView { - TransactionDetailView(transaction: .placeholder) + TransactionDetailView( + transaction: + TransactionState( + memo: + """ + 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. + """, + minedHeight: 1_875_256, + zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po", + fee: Zatoshi(amount: 1_000_000), + id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", + status: .paid(success: true), + subtitle: "", + timestamp: 1234567, + zecAmount: Zatoshi(amount: 25_000_000) + ), + viewStore: ViewStore( + WalletEventsFlowStore( + initialState: .placeHolder, + reducer: .default, + environment: + WalletEventsFlowEnvironment( + scheduler: DispatchQueue.main.eraseToAnyScheduler(), + SDKSynchronizer: MockWrappedSDKSynchronizer(), + pasteboard: .test + ) + ) + ) + ) + .preferredColorScheme(.dark) } } } diff --git a/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift b/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift index 5d38ce8..6b432ba 100644 --- a/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift +++ b/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift @@ -23,9 +23,11 @@ struct WalletEventsFlowState: Equatable { // MARK: - Action enum WalletEventsFlowAction: Equatable { + case copyToPastboard(String) case onAppear case onDisappear case updateRoute(WalletEventsFlowState.Route?) + case replyTo(String) case synchronizerStateChanged(WrappedSDKSynchronizerState) case updateWalletEvents([WalletEvent]) } @@ -35,6 +37,7 @@ enum WalletEventsFlowAction: Equatable { struct WalletEventsFlowEnvironment { let scheduler: AnySchedulerOf let SDKSynchronizer: WrappedSDKSynchronizer + let pasteboard: WrappedPasteboard } // MARK: - Reducer @@ -70,9 +73,16 @@ extension WalletEventsFlowReducer { state.walletEvents = IdentifiedArrayOf(uniqueElements: sortedWalletEvents) return .none - case let .updateRoute(route): + case .updateRoute(let route): state.route = route return .none + + case .copyToPastboard(let value): + environment.pasteboard.setString(value) + return .none + + case .replyTo(let address): + return .none } } } @@ -97,6 +107,7 @@ extension WalletEventsFlowViewStore { extension TransactionState { static var placeholder: Self { .init( + fee: Zatoshi(amount: 10), id: "2", status: .paid(success: true), subtitle: "", @@ -123,7 +134,8 @@ extension WalletEventsFlowStore { reducer: .default, environment: WalletEventsFlowEnvironment( scheduler: DispatchQueue.main.eraseToAnyScheduler(), - SDKSynchronizer: LiveWrappedSDKSynchronizer() + SDKSynchronizer: LiveWrappedSDKSynchronizer(), + pasteboard: .live ) ) } @@ -134,6 +146,7 @@ extension IdentifiedArrayOf where Element == TransactionState { return .init( uniqueElements: (0..<30).map { TransactionState( + fee: Zatoshi(amount: 10), id: String($0), status: .paid(success: true), subtitle: "", diff --git a/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift b/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift index de4f444..9482e55 100644 --- a/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift +++ b/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift @@ -33,11 +33,11 @@ extension WalletEventsFlowView { ForEach(viewStore.walletEvents) { walletEvent in WithStateBinding(binding: viewStore.bindingForSelectingWalletEvent(walletEvent)) { active in VStack { - walletEvent.rowView() + walletEvent.rowView(viewStore) } .navigationLink( isActive: active, - destination: { walletEvent.detailView() } + destination: { walletEvent.detailView(viewStore) } ) .foregroundColor(Asset.Colors.Text.body.color) .listRowBackground(Color.clear) diff --git a/secant/Models/TransactionState.swift b/secant/Models/TransactionState.swift index f2cba7d..d486bfa 100644 --- a/secant/Models/TransactionState.swift +++ b/secant/Models/TransactionState.swift @@ -16,19 +16,26 @@ struct TransactionState: Equatable, Identifiable { case failed } + var confirmations = 0 var expirationHeight = -1 var memo: String? var minedHeight = -1 var shielded = true var zAddress: String? + var fee: Zatoshi var id: String var status: Status var subtitle: String var timestamp: TimeInterval var zecAmount: Zatoshi - + var address: String { zAddress ?? "" } + var date: Date { Date(timeIntervalSince1970: timestamp) } + var totalAmount: Zatoshi { Zatoshi(amount: zecAmount.amount + fee.amount) } + var viewOnlineURL: URL? { + URL(string: "https://blockchair.com/zcash/transaction/\(id)") + } } extension TransactionState { @@ -40,6 +47,7 @@ extension TransactionState { subtitle = "sent" zAddress = confirmedTransaction.toAddress zecAmount = sent ? Zatoshi(amount: -Int64(confirmedTransaction.value)) : Zatoshi(amount: Int64(confirmedTransaction.value)) + fee = Zatoshi(amount: 10) if let memo = confirmedTransaction.memo { self.memo = memo.asZcashTransactionMemo() } @@ -55,6 +63,7 @@ extension TransactionState { subtitle = "pending" zAddress = pendingTransaction.toAddress zecAmount = Zatoshi(amount: -Int64(pendingTransaction.value)) + fee = Zatoshi(amount: 10) if let memo = pendingTransaction.memo { self.memo = memo.asZcashTransactionMemo() } @@ -67,6 +76,7 @@ extension TransactionState { extension TransactionState { static func placeholder( amount: Zatoshi, + fee: Zatoshi, shielded: Bool = true, status: Status = .received, subtitle: String = "", @@ -79,6 +89,7 @@ extension TransactionState { minedHeight: -1, shielded: shielded, zAddress: nil, + fee: fee, id: uuid, status: status, subtitle: subtitle, diff --git a/secant/Models/WalletEvent.swift b/secant/Models/WalletEvent.swift index 5c201d5..dd1173b 100644 --- a/secant/Models/WalletEvent.swift +++ b/secant/Models/WalletEvent.swift @@ -30,7 +30,7 @@ struct WalletEvent: Equatable, Identifiable { // MARK: - Rows extension WalletEvent { - @ViewBuilder func rowView() -> some View { + @ViewBuilder func rowView(_ viewStore: WalletEventsFlowViewStore) -> some View { switch state { case .send(let transaction): TransactionRowView(transaction: transaction) @@ -51,10 +51,10 @@ extension WalletEvent { // MARK: - Details extension WalletEvent { - @ViewBuilder func detailView() -> some View { + @ViewBuilder func detailView(_ viewStore: WalletEventsFlowViewStore) -> some View { switch state { case .send(let transaction): - TransactionDetailView(transaction: transaction) + TransactionDetailView(transaction: transaction, viewStore: viewStore) case .pending: Text("pending transaction detail") case .received: diff --git a/secant/Resources/Colors.xcassets/Text/TransactionDetailText.colorset/Contents.json b/secant/Resources/Colors.xcassets/Text/TransactionDetailText.colorset/Contents.json new file mode 100644 index 0000000..9a5a287 --- /dev/null +++ b/secant/Resources/Colors.xcassets/Text/TransactionDetailText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x52", + "green" : "0x31", + "red" : "0x26" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Resources/Generated/XCAssets+Generated.swift b/secant/Resources/Generated/XCAssets+Generated.swift index 8ed5b97..1d40ec4 100644 --- a/secant/Resources/Generated/XCAssets+Generated.swift +++ b/secant/Resources/Generated/XCAssets+Generated.swift @@ -111,6 +111,7 @@ internal enum Asset { internal static let regular = ColorAsset(name: "Regular") internal static let secondaryButtonText = ColorAsset(name: "SecondaryButtonText") internal static let titleText = ColorAsset(name: "TitleText") + internal static let transactionDetailText = ColorAsset(name: "TransactionDetailText") internal static let validMnemonic = ColorAsset(name: "ValidMnemonic") internal static let captionText = ColorAsset(name: "captionText") internal static let captionTextShadow = ColorAsset(name: "captionTextShadow") diff --git a/secant/Wrappers/WrappedSDKSynchronizer.swift b/secant/Wrappers/WrappedSDKSynchronizer.swift index 6193af0..12cb09a 100644 --- a/secant/Wrappers/WrappedSDKSynchronizer.swift +++ b/secant/Wrappers/WrappedSDKSynchronizer.swift @@ -307,6 +307,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer { mocked.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, @@ -331,6 +332,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer { mocked.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, @@ -368,6 +370,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer { minedHeight: 50, shielded: true, zAddress: "tteafadlamnelkqe", + fee: Zatoshi(amount: 10), id: "id", status: .paid(success: true), subtitle: "sub", @@ -417,6 +420,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer { mocked.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, @@ -447,6 +451,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer { mocked.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, diff --git a/secantTests/AppTests/AppTests.swift b/secantTests/AppTests/AppTests.swift index d47c9de..971b153 100644 --- a/secantTests/AppTests/AppTests.swift +++ b/secantTests/AppTests/AppTests.swift @@ -166,4 +166,19 @@ class AppTests: XCTestCase { store.receive(.checkBackupPhraseValidation) } + + func testWalletEventReplyTo_validAddress() throws { + let store = TestStore( + initialState: .placeholder, + reducer: AppReducer.default, + environment: testEnvironment + ) + + let address = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po" + store.send(.home(.walletEvents(.replyTo(address)))) + + if let url = URL(string: "zcash:\(address)") { + store.receive(.deeplink(url)) + } + } } diff --git a/secantTests/HomeTests/HomeTests.swift b/secantTests/HomeTests/HomeTests.swift index c91de99..609f527 100644 --- a/secantTests/HomeTests/HomeTests.swift +++ b/secantTests/HomeTests/HomeTests.swift @@ -77,6 +77,7 @@ class HomeTests: XCTestCase { let walletEvents: [WalletEvent] = transactionsHelper.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, diff --git a/secantTests/SendTests/SendTests.swift b/secantTests/SendTests/SendTests.swift index e847cce..dbdbebe 100644 --- a/secantTests/SendTests/SendTests.swift +++ b/secantTests/SendTests/SendTests.swift @@ -64,6 +64,7 @@ class SendTests: XCTestCase { minedHeight: 50, shielded: true, zAddress: "tteafadlamnelkqe", + fee: Zatoshi(amount: 10), id: "id", status: .paid(success: true), subtitle: "sub", diff --git a/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift b/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift index fcb62df..d5b3999 100644 --- a/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift +++ b/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift @@ -18,9 +18,11 @@ class HomeSnapshotTests: XCTestCase { TransactionStateMockHelper(date: 1651039505, amount: Zatoshi(amount: 4), uuid: "4"), TransactionStateMockHelper(date: 1651039404, amount: Zatoshi(amount: 5), uuid: "5") ] - let transactions: [WalletEvent] = transactionsHelper.map { + + let walletEvents: [WalletEvent] = transactionsHelper.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, @@ -41,7 +43,7 @@ class HomeSnapshotTests: XCTestCase { scanState: .placeholder, synchronizerStatus: "", totalBalance: Zatoshi(amount: balance.total), - walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: transactions)), + walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)), verifiedBalance: Zatoshi(amount: balance.verified) ), reducer: .default, @@ -61,4 +63,61 @@ class HomeSnapshotTests: XCTestCase { HomeView(store: store) ) } + + func testWalletEventDetailSnapshot() throws { + let transaction = TransactionState( + memo: + """ + 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. + """, + minedHeight: 1_875_256, + zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po", + fee: Zatoshi(amount: 1_000_000), + id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", + status: .paid(success: true), + subtitle: "", + timestamp: 1234567, + zecAmount: Zatoshi(amount: 25_000_000) + ) + + let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp) + + let balance = Balance(verified: 12_345_000, total: 12_345_000) + let store = HomeStore( + initialState: .init( + drawerOverlay: .partial, + profileState: .placeholder, + requestState: .placeholder, + sendState: .placeholder, + scanState: .placeholder, + synchronizerStatus: "", + totalBalance: Zatoshi(amount: balance.total), + walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])), + verifiedBalance: Zatoshi(amount: balance.verified) + ), + reducer: .default, + environment: .demo + ) + + // wallet event detail + let testEnvironment = WalletEventsFlowEnvironment( + scheduler: DispatchQueue.test.eraseToAnyScheduler(), + SDKSynchronizer: TestWrappedSDKSynchronizer(), + pasteboard: .test + ) + + ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent)))) + let walletEventsStore = WalletEventsFlowStore( + initialState: .placeHolder, + reducer: .default, + environment: testEnvironment + ) + + addAttachments( + name: "\(#function)_WalletEventDetail", + TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore)) + ) + } } diff --git a/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift b/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift new file mode 100644 index 0000000..fc35c07 --- /dev/null +++ b/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift @@ -0,0 +1,69 @@ +// +// WalletEventsSnapshotTests.swift +// secantTests +// +// Created by Lukáš Korba on 27.06.2022. +// + +import XCTest +@testable import secant_testnet +import ComposableArchitecture + +class WalletEventsSnapshotTests: XCTestCase { + func testWalletEventDetailSnapshot() throws { + let transaction = TransactionState( + memo: + """ + 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. + """, + minedHeight: 1_875_256, + zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po", + fee: Zatoshi(amount: 1_000_000), + id: "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8", + status: .paid(success: true), + subtitle: "", + timestamp: 1234567, + zecAmount: Zatoshi(amount: 25_000_000) + ) + + let walletEvent = WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp) + + let balance = Balance(verified: 12_345_000, total: 12_345_000) + let store = HomeStore( + initialState: .init( + drawerOverlay: .partial, + profileState: .placeholder, + requestState: .placeholder, + sendState: .placeholder, + scanState: .placeholder, + synchronizerStatus: "", + totalBalance: Zatoshi(amount: balance.total), + walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: [walletEvent])), + verifiedBalance: Zatoshi(amount: balance.verified) + ), + reducer: .default, + environment: .demo + ) + + // wallet event detail + let testEnvironment = WalletEventsFlowEnvironment( + scheduler: DispatchQueue.test.eraseToAnyScheduler(), + SDKSynchronizer: TestWrappedSDKSynchronizer(), + pasteboard: .test + ) + + ViewStore(store).send(.walletEvents(.updateRoute(.showWalletEvent(walletEvent)))) + let walletEventsStore = WalletEventsFlowStore( + initialState: .placeHolder, + reducer: .default, + environment: testEnvironment + ) + + addAttachments( + name: "\(#function)_WalletEventDetail", + TransactionDetailView(transaction: transaction, viewStore: ViewStore(walletEventsStore)) + ) + } +} diff --git a/secantTests/WalletEventsTests/WalletEventsTests.swift b/secantTests/WalletEventsTests/WalletEventsTests.swift index ea89ca5..401ea4f 100644 --- a/secantTests/WalletEventsTests/WalletEventsTests.swift +++ b/secantTests/WalletEventsTests/WalletEventsTests.swift @@ -14,7 +14,8 @@ class WalletEventsTests: XCTestCase { let testEnvironment = WalletEventsFlowEnvironment( scheduler: testScheduler.eraseToAnyScheduler(), - SDKSynchronizer: TestWrappedSDKSynchronizer() + SDKSynchronizer: TestWrappedSDKSynchronizer(), + pasteboard: .test ) func testSynchronizerSubscription() throws { @@ -58,6 +59,7 @@ class WalletEventsTests: XCTestCase { let walletEvents: [WalletEvent] = mocked.map { let transaction = TransactionState.placeholder( amount: $0.amount, + fee: Zatoshi(amount: 10), shielded: $0.shielded, status: $0.status, subtitle: $0.subtitle, @@ -71,13 +73,13 @@ class WalletEventsTests: XCTestCase { ) } - let identifiedTransactions = IdentifiedArrayOf(uniqueElements: walletEvents) + let identifiedWalletEvents = IdentifiedArrayOf(uniqueElements: walletEvents) let store = TestStore( initialState: WalletEventsFlowState( route: .latest, isScrollable: true, - walletEvents: identifiedTransactions + walletEvents: identifiedWalletEvents ), reducer: WalletEventsFlowReducer.default, environment: testEnvironment @@ -88,7 +90,7 @@ class WalletEventsTests: XCTestCase { Self.testScheduler.advance(by: 0.01) store.receive(.updateWalletEvents(walletEvents)) { state in - let receivedTransactions = IdentifiedArrayOf( + let receivedWalletEvents = IdentifiedArrayOf( uniqueElements: walletEvents .sorted(by: { lhs, rhs in @@ -96,7 +98,32 @@ class WalletEventsTests: XCTestCase { }) ) - state.walletEvents = receivedTransactions + state.walletEvents = receivedWalletEvents } } + + func testCopyToPasteboard() throws { + let pasteboard = WrappedPasteboard.test + + let testEnvironment = WalletEventsFlowEnvironment( + scheduler: DispatchQueue.test.eraseToAnyScheduler(), + SDKSynchronizer: TestWrappedSDKSynchronizer(), + pasteboard: pasteboard + ) + + let store = TestStore( + initialState: WalletEventsFlowState( + route: .latest, + isScrollable: true, + walletEvents: [] + ), + reducer: WalletEventsFlowReducer.default, + environment: testEnvironment + ) + + let testText = "test text" + store.send(.copyToPastboard(testText)) + + XCTAssertEqual(pasteboard.getString(), testText, "WalletEvetns: `testCopyToPasteboard` is expected to match the input `\(testText)`") + } }