From 93b1b8c01f98d5f5a60fd69b8b6147055e77bcac Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Wed, 22 Feb 2023 14:50:59 +0100 Subject: [PATCH] [#564] Add transaction history as standalone screen (#569) - the transaction history is now separated - unit tests fixed - snapshot test updated --- .../UserPreferencesStorageInterface.swift | 2 +- secant/Features/Home/HomeStore.swift | 33 +------ secant/Features/Home/HomeView.swift | 40 ++++---- .../WalletEventsFlowStore.swift | 4 +- .../WalletEventsFlowView.swift | 72 ++------------ secantTests/HomeTests/HomeTests.swift | 94 +------------------ .../HomeCircularProgressSnapshotTests.swift | 3 - .../HomeSnapshotTests/HomeSnapshotTests.swift | 1 - .../WalletEventsSnapshotTests.swift | 54 ++--------- .../WalletEventsTests/WalletEventsTests.swift | 13 ++- 10 files changed, 50 insertions(+), 266 deletions(-) diff --git a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift index 9148449..2ebfb53 100644 --- a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift +++ b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift @@ -10,7 +10,7 @@ import ComposableArchitecture extension DependencyValues { var userStoredPreferences: UserPreferencesStorageClient { - get { self [UserPreferencesStorageClient.self] } + get { self[UserPreferencesStorageClient.self] } set { self[UserPreferencesStorageClient.self] = newValue } } } diff --git a/secant/Features/Home/HomeStore.swift b/secant/Features/Home/HomeStore.swift index 930cd23..e18943a 100644 --- a/secant/Features/Home/HomeStore.swift +++ b/secant/Features/Home/HomeStore.swift @@ -16,6 +16,7 @@ struct HomeReducer: ReducerProtocol { case notEnoughFreeDiskSpace case profile case request + case transactionHistory case send case scan case balanceBreakdown @@ -24,7 +25,6 @@ struct HomeReducer: ReducerProtocol { var destination: Destination? var balanceBreakdownState: BalanceBreakdownReducer.State - var drawerOverlay: DrawerOverlay var profileState: ProfileReducer.State var requestState: RequestReducer.State var requiredTransactionConfirmations = 0 @@ -67,7 +67,6 @@ struct HomeReducer: ReducerProtocol { case scan(ScanReducer.Action) case synchronizerStateChanged(SDKSynchronizerState) case walletEvents(WalletEventsFlowReducer.Action) - case updateDrawer(DrawerOverlay) case updateDestination(HomeReducer.State.Destination?) case updateSynchronizerStatus case updateWalletEvents([WalletEvent]) @@ -118,23 +117,9 @@ struct HomeReducer: ReducerProtocol { case .onDisappear: return .cancel(id: CancelId.self) - case .synchronizerStateChanged(.synced): - return .merge( - sdkSynchronizer.getAllTransactions() - .receive(on: mainQueue) - .map(HomeReducer.Action.updateWalletEvents) - .eraseToEffect(), - EffectTask(value: .updateSynchronizerStatus) - ) - case .synchronizerStateChanged: return EffectTask(value: .updateSynchronizerStatus) - - case .updateDrawer(let drawerOverlay): - state.drawerOverlay = drawerOverlay - state.walletEventsState.isScrollable = drawerOverlay == .full ? true : false - return .none - + case .updateWalletEvents: return .none @@ -190,12 +175,6 @@ struct HomeReducer: ReducerProtocol { // TODO: [#221] error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221) return .none - case .walletEvents(.updateDestination(.all)): - return state.drawerOverlay != .full ? EffectTask(value: .updateDrawer(.full)) : .none - - case .walletEvents(.updateDestination(.latest)): - return state.drawerOverlay != .partial ? EffectTask(value: .updateDrawer(.partial)) : .none - case .walletEvents: return .none @@ -283,13 +262,6 @@ extension HomeViewStore { } ) } - - func bindingForDrawer() -> Binding { - self.binding( - get: { $0.drawerOverlay }, - send: { .updateDrawer($0) } - ) - } } // MARK: Placeholders @@ -298,7 +270,6 @@ extension HomeReducer.State { static var placeholder: Self { .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, diff --git a/secant/Features/Home/HomeView.swift b/secant/Features/Home/HomeView.swift index 281f0cd..6581c9c 100644 --- a/secant/Features/Home/HomeView.swift +++ b/secant/Features/Home/HomeView.swift @@ -6,35 +6,38 @@ struct HomeView: View { var body: some View { WithViewStore(store) { viewStore in - GeometryReader { proxy in + VStack { ZStack { scanButton(viewStore) profileButton(viewStore) - - circularArea(viewStore, proxy.size) - - sendButton(viewStore) - if proxy.size.height > 0 { - Drawer(overlay: viewStore.bindingForDrawer(), maxHeight: proxy.size.height) { - WalletEventsFlowView(store: store.historyStore()) - .applyScreenBackground() - } - } + circularArea(viewStore) + + sendButton(viewStore) } - .applyScreenBackground() - .navigationBarHidden(true) - .onAppear(perform: { viewStore.send(.onAppear) }) - .onDisappear(perform: { viewStore.send(.onDisappear) }) - .fullScreenCover(isPresented: viewStore.bindingForDestination(.balanceBreakdown)) { - BalanceBreakdownView(store: store.balanceBreakdownStore()) + + Button { + viewStore.send(.updateDestination(.transactionHistory)) + } label: { + Text("See transaction history") } } + .applyScreenBackground() + .navigationBarHidden(true) + .onAppear(perform: { viewStore.send(.onAppear) }) + .onDisappear(perform: { viewStore.send(.onDisappear) }) + .fullScreenCover(isPresented: viewStore.bindingForDestination(.balanceBreakdown)) { + BalanceBreakdownView(store: store.balanceBreakdownStore()) + } .navigationLinkEmpty( isActive: viewStore.bindingForDestination(.notEnoughFreeDiskSpace), destination: { NotEnoughFreeSpaceView(viewStore: viewStore) } ) + .navigationLinkEmpty( + isActive: viewStore.bindingForDestination(.transactionHistory), + destination: { WalletEventsFlowView(store: store.historyStore()) } + ) } } } @@ -114,7 +117,7 @@ extension HomeView { } } - func circularArea(_ viewStore: HomeViewStore, _ size: CGSize) -> some View { + func circularArea(_ viewStore: HomeViewStore) -> some View { VStack { ZStack { CircularProgress( @@ -123,7 +126,6 @@ extension HomeView { maxSegments: viewStore.requiredTransactionConfirmations, innerCircleHidden: viewStore.isUpToDate ) - .frame(width: size.width * 0.65, height: size.width * 0.65) .padding(.top, 50) VStack { diff --git a/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift b/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift index 1921eb1..712cb0f 100644 --- a/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift +++ b/secant/Features/WalletEventsFlow/WalletEventsFlowStore.swift @@ -19,7 +19,7 @@ struct WalletEventsFlowReducer: ReducerProtocol { @BindingState var alert: AlertState? var latestMinedHeight: BlockHeight? - var isScrollable = false + var isScrollable = true var requiredTransactionConfirmations = 0 var walletEvents = IdentifiedArrayOf.placeholder var selectedWalletEvent: WalletEvent? @@ -60,7 +60,7 @@ struct WalletEventsFlowReducer: ReducerProtocol { if let latestMinedHeight = sdkSynchronizer.synchronizer?.latestScannedHeight { state.latestMinedHeight = latestMinedHeight } - return sdkSynchronizer.getAllClearedTransactions() + return sdkSynchronizer.getAllTransactions() .receive(on: mainQueue) .map(WalletEventsFlowReducer.Action.updateWalletEvents) .eraseToEffect() diff --git a/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift b/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift index 1d37b66..64354ca 100644 --- a/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift +++ b/secant/Features/WalletEventsFlow/WalletEventsFlowView.swift @@ -3,32 +3,15 @@ import ComposableArchitecture struct WalletEventsFlowView: View { let store: WalletEventsFlowStore - @State var flag = true var body: some View { return WithViewStore(store) { viewStore in - VStack { - header(with: viewStore) - - if viewStore.isScrollable { - List { - walletEventsList(with: viewStore) - } - .listStyle(.plain) - .padding(.bottom, 60) - } else { - walletEventsList(with: viewStore) - } - - Spacer() + List { + walletEventsList(with: viewStore) } - .onAppear( - perform: { - UITableView.appearance().backgroundColor = .clear - UITableView.appearance().separatorColor = .clear - viewStore.send(.onAppear) - } - ) + .navigationTitle("Transactions") + .listStyle(.plain) + .onAppear { viewStore.send(.onAppear) } .onDisappear(perform: { viewStore.send(.onDisappear) }) .navigationLinkEmpty(isActive: viewStore.bindingForSelectedWalletEvent(viewStore.selectedWalletEvent)) { viewStore.selectedWalletEvent?.detailView(store) @@ -47,52 +30,9 @@ extension WalletEventsFlowView { .listRowInsets(EdgeInsets()) .foregroundColor(Asset.Colors.Text.body.color) .listRowBackground(Color.clear) + .frame(height: 60) } } - - func header(with viewStore: WalletEventsFlowViewStore) -> some View { - HStack(spacing: 0) { - VStack { - Button { - viewStore.send(.updateDestination(.latest)) - } label: { - Text("Latest") - .font(.custom(FontFamily.Rubik.regular.name, size: 18)) - } - .frame(width: 100) - .foregroundColor(Asset.Colors.Text.drawerTabsText.color) - .opacity(viewStore.isScrollable ? 0.23 : 1.0) - - Rectangle() - .frame(height: 1.5) - .foregroundColor(latestUnderline(viewStore)) - } - - VStack { - Button { - viewStore.send(.updateDestination(.all)) - } label: { - Text("All") - .font(.custom(FontFamily.Rubik.regular.name, size: 18)) - } - .frame(width: 100) - .foregroundColor(Asset.Colors.Text.drawerTabsText.color) - .opacity(viewStore.isScrollable ? 1.0 : 0.23) - - Rectangle() - .frame(height: 1.5) - .foregroundColor(allUnderline(viewStore)) - } - } - } - - private func latestUnderline(_ viewStore: WalletEventsFlowViewStore) -> Color { - viewStore.isScrollable ? Asset.Colors.TextField.Underline.gray.color : Asset.Colors.TextField.Underline.purple.color - } - - private func allUnderline(_ viewStore: WalletEventsFlowViewStore) -> Color { - viewStore.isScrollable ? Asset.Colors.TextField.Underline.purple.color : Asset.Colors.TextField.Underline.gray.color - } } // MARK: - Previews diff --git a/secantTests/HomeTests/HomeTests.swift b/secantTests/HomeTests/HomeTests.swift index dcde5de..b325305 100644 --- a/secantTests/HomeTests/HomeTests.swift +++ b/secantTests/HomeTests/HomeTests.swift @@ -22,9 +22,7 @@ class HomeTests: XCTestCase { store.receive(.updateSynchronizerStatus) } - /// When the synchronizer status change to .synced, several things happen - /// 1. the .updateSynchronizerStatus is called - /// 2. the side effect to update the transactions history is called + /// When the synchronizer status change to .synced, the .updateSynchronizerStatus is called func testSynchronizerStateChanged_Synced() throws { // setup the store and environment to be fully mocked let testScheduler = DispatchQueue.test @@ -41,95 +39,7 @@ class HomeTests: XCTestCase { testScheduler.advance(by: 0.01) - // ad 1. store.receive(.updateSynchronizerStatus) - - // ad 2. - let transactionsHelper: [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] = transactionsHelper.map { - let transaction = TransactionState.placeholder( - amount: $0.amount, - fee: Zatoshi(10), - shielded: $0.shielded, - status: $0.amount.amount > 5 ? .pending : $0.status, - timestamp: $0.date, - uuid: $0.uuid - ) - return WalletEvent(id: transaction.id, state: $0.amount.amount > 5 ? .pending(transaction) : .send(transaction), timestamp: transaction.timestamp) - } - - store.receive(.updateWalletEvents(walletEvents)) - } - - func testWalletEventsPartial_to_FullDrawer() throws { - let homeState = HomeReducer.State( - balanceBreakdownState: .placeholder, - drawerOverlay: .partial, - profileState: .placeholder, - requestState: .placeholder, - scanState: .placeholder, - sendState: .placeholder, - shieldedBalance: Balance.zero, - synchronizerStatusSnapshot: .default, - walletEventsState: .emptyPlaceHolder - ) - - let store = TestStore( - initialState: homeState, - reducer: HomeReducer() - ) - - store.send(.walletEvents(.updateDestination(.all))) { state in - state.walletEventsState.destination = .all - } - - store.receive(.updateDrawer(.full)) { state in - state.drawerOverlay = .full - state.walletEventsState.isScrollable = true - } - } - - func testWalletEventsFull_to_PartialDrawer() throws { - let homeState = HomeReducer.State( - balanceBreakdownState: .placeholder, - drawerOverlay: .full, - profileState: .placeholder, - requestState: .placeholder, - scanState: .placeholder, - sendState: .placeholder, - shieldedBalance: Balance.zero, - synchronizerStatusSnapshot: .default, - walletEventsState: .emptyPlaceHolder - ) - - let store = TestStore( - initialState: homeState, - reducer: HomeReducer() - ) - - store.send(.walletEvents(.updateDestination(.latest))) { state in - state.walletEventsState.destination = .latest - } - - store.receive(.updateDrawer(.partial)) { state in - state.drawerOverlay = .partial - state.walletEventsState.isScrollable = false - } } /// The .onAppear action is important to register for the synchronizer state updates. @@ -182,7 +92,6 @@ class HomeTests: XCTestCase { let homeState = HomeReducer.State( destination: .profile, balanceBreakdownState: .placeholder, - drawerOverlay: .full, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -208,7 +117,6 @@ class HomeTests: XCTestCase { let homeState = HomeReducer.State( destination: .profile, balanceBreakdownState: .placeholder, - drawerOverlay: .full, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, diff --git a/secantTests/SnapshotTests/HomeSnapshotTests/HomeCircularProgressSnapshotTests.swift b/secantTests/SnapshotTests/HomeSnapshotTests/HomeCircularProgressSnapshotTests.swift index 5ec1ee3..a780faf 100644 --- a/secantTests/SnapshotTests/HomeSnapshotTests/HomeCircularProgressSnapshotTests.swift +++ b/secantTests/SnapshotTests/HomeSnapshotTests/HomeCircularProgressSnapshotTests.swift @@ -30,7 +30,6 @@ class HomeCircularProgressSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -66,7 +65,6 @@ class HomeCircularProgressSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -94,7 +92,6 @@ class HomeCircularProgressSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, diff --git a/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift b/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift index 021eddd..0986918 100644 --- a/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift +++ b/secantTests/SnapshotTests/HomeSnapshotTests/HomeSnapshotTests.swift @@ -38,7 +38,6 @@ class HomeSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, diff --git a/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift b/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift index 1ec7b05..bae790e 100644 --- a/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift +++ b/secantTests/SnapshotTests/WalletEventsSnapshotTests/WalletEventsSnapshotTests.swift @@ -12,54 +12,16 @@ import ZcashLightClientKit class WalletEventsSnapshotTests: XCTestCase { func testFullWalletEventsSnapshot() throws { - let transactionsHelper: [TransactionStateMockHelper] = [ - TransactionStateMockHelper(date: 1651039202, amount: Zatoshi(1), status: .paid(success: true), uuid: "1"), - TransactionStateMockHelper(date: 1651039101, amount: Zatoshi(2), status: .pending, 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 { - var transaction = TransactionState.placeholder( - amount: $0.amount, - fee: Zatoshi(10), - shielded: $0.shielded, - status: $0.status, - timestamp: $0.date, - uuid: $0.uuid - ) - transaction.zAddress = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po" - - return WalletEvent(id: transaction.id, state: .send(transaction), timestamp: transaction.timestamp) - } - - let balance = WalletBalance(verified: 12_345_000, total: 12_345_000) - - let store = HomeStore( - initialState: .init( - balanceBreakdownState: .placeholder, - drawerOverlay: .partial, - profileState: .placeholder, - requestState: .placeholder, - scanState: .placeholder, - sendState: .placeholder, - shieldedBalance: balance.redacted, - synchronizerStatusSnapshot: .default, - walletEventsState: .init(walletEvents: IdentifiedArrayOf(uniqueElements: walletEvents)) - ), - reducer: HomeReducer() - .dependency(\.diskSpaceChecker, .mockEmptyDisk) + let store = WalletEventsFlowStore( + initialState: .placeHolder, + reducer: WalletEventsFlowReducer() ) - - // landing home screen + + // landing wallet events screen addAttachments( name: "\(#function)_initial", - HomeView(store: store) + WalletEventsFlowView(store: store) ) - - // all transactions - ViewStore(store).send(.updateDrawer(.full)) - addAttachments(HomeView(store: store)) } func testWalletEventDetailSnapshot_sent() throws { @@ -91,7 +53,6 @@ class WalletEventsSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -144,7 +105,6 @@ class WalletEventsSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -197,7 +157,6 @@ class WalletEventsSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, @@ -256,7 +215,6 @@ class WalletEventsSnapshotTests: XCTestCase { let store = HomeStore( initialState: .init( balanceBreakdownState: .placeholder, - drawerOverlay: .partial, profileState: .placeholder, requestState: .placeholder, scanState: .placeholder, diff --git a/secantTests/WalletEventsTests/WalletEventsTests.swift b/secantTests/WalletEventsTests/WalletEventsTests.swift index 04ae763..42dda09 100644 --- a/secantTests/WalletEventsTests/WalletEventsTests.swift +++ b/secantTests/WalletEventsTests/WalletEventsTests.swift @@ -39,7 +39,16 @@ class WalletEventsTests: XCTestCase { 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: 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 { @@ -47,7 +56,7 @@ class WalletEventsTests: XCTestCase { amount: $0.amount, fee: Zatoshi(10), shielded: $0.shielded, - status: $0.status, + status: $0.amount.amount > 5 ? .pending : $0.status, timestamp: $0.date, uuid: $0.uuid )