-
Notifications
You must be signed in to change notification settings - Fork 893
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Activity tab for displaying user transactions. * Update `TabbedPageViewController` so the tabs scroll horizontally now; this should resolve any potential truncation issues with new tabs after localization. * Improve initial load performance by displaying transactions prior to fetching Solana estimated transaction fees and asset prices. * Filter out rejected transactions
- Loading branch information
1 parent
4427aeb
commit 637ee44
Showing
9 changed files
with
493 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
217 changes: 217 additions & 0 deletions
217
Sources/BraveWallet/Crypto/Stores/TransactionsActivityStore.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* Copyright 2023 The Brave Authors. All rights reserved. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
import BraveCore | ||
import SwiftUI | ||
|
||
class TransactionsActivityStore: ObservableObject { | ||
@Published var transactionSummaries: [TransactionSummary] = [] | ||
|
||
@Published private(set) var currencyCode: String = CurrencyCode.usd.code { | ||
didSet { | ||
currencyFormatter.currencyCode = currencyCode | ||
guard oldValue != currencyCode else { return } | ||
update() | ||
} | ||
} | ||
|
||
let currencyFormatter: NumberFormatter = .usdCurrencyFormatter | ||
|
||
private var solEstimatedTxFeesCache: [String: UInt64] = [:] | ||
private var assetPricesCache: [String: Double] = [:] | ||
|
||
private let keyringService: BraveWalletKeyringService | ||
private let rpcService: BraveWalletJsonRpcService | ||
private let walletService: BraveWalletBraveWalletService | ||
private let assetRatioService: BraveWalletAssetRatioService | ||
private let blockchainRegistry: BraveWalletBlockchainRegistry | ||
private let txService: BraveWalletTxService | ||
private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy | ||
|
||
init( | ||
keyringService: BraveWalletKeyringService, | ||
rpcService: BraveWalletJsonRpcService, | ||
walletService: BraveWalletBraveWalletService, | ||
assetRatioService: BraveWalletAssetRatioService, | ||
blockchainRegistry: BraveWalletBlockchainRegistry, | ||
txService: BraveWalletTxService, | ||
solTxManagerProxy: BraveWalletSolanaTxManagerProxy | ||
) { | ||
self.keyringService = keyringService | ||
self.rpcService = rpcService | ||
self.walletService = walletService | ||
self.assetRatioService = assetRatioService | ||
self.blockchainRegistry = blockchainRegistry | ||
self.txService = txService | ||
self.solTxManagerProxy = solTxManagerProxy | ||
|
||
keyringService.add(self) | ||
txService.add(self) | ||
|
||
Task { @MainActor in | ||
self.currencyCode = await walletService.defaultBaseCurrency() | ||
} | ||
} | ||
|
||
private var updateTask: Task<Void, Never>? | ||
func update() { | ||
updateTask?.cancel() | ||
updateTask = Task { @MainActor in | ||
let allKeyrings = await keyringService.keyrings( | ||
for: WalletConstants.supportedCoinTypes | ||
) | ||
let allAccountInfos = allKeyrings.flatMap(\.accountInfos) | ||
// Only transactions for the selected network | ||
// for each coin type are returned | ||
var selectedNetworkForCoin: [BraveWallet.CoinType: BraveWallet.NetworkInfo] = [:] | ||
for coin in WalletConstants.supportedCoinTypes { | ||
selectedNetworkForCoin[coin] = await rpcService.network(coin) | ||
} | ||
let allTransactions = await txService.allTransactions( | ||
for: allKeyrings | ||
).filter { $0.txStatus != .rejected } | ||
let userVisibleTokens = await walletService.allVisibleUserAssets( | ||
in: Array(selectedNetworkForCoin.values) | ||
).flatMap(\.tokens) | ||
let allTokens = await blockchainRegistry.allTokens( | ||
in: Array(selectedNetworkForCoin.values) | ||
).flatMap(\.tokens) | ||
guard !Task.isCancelled else { return } | ||
// display transactions prior to network request to fetch | ||
// estimated solana tx fees & asset prices | ||
self.transactionSummaries = self.transactionSummaries( | ||
transactions: allTransactions, | ||
selectedNetworkForCoin: selectedNetworkForCoin, | ||
accountInfos: allAccountInfos, | ||
userVisibleTokens: userVisibleTokens, | ||
allTokens: allTokens, | ||
assetRatios: assetPricesCache, | ||
solEstimatedTxFees: solEstimatedTxFeesCache | ||
) | ||
guard !self.transactionSummaries.isEmpty else { return } | ||
|
||
if allTransactions.contains(where: { $0.coin == .sol }) { | ||
let solTransactionIds = allTransactions.filter { $0.coin == .sol }.map(\.id) | ||
await updateSolEstimatedTxFeesCache(solTransactionIds: solTransactionIds) | ||
} | ||
|
||
let allVisibleTokenAssetRatioIds = userVisibleTokens.map(\.assetRatioId) | ||
await updateAssetPricesCache(assetRatioIds: allVisibleTokenAssetRatioIds) | ||
|
||
guard !Task.isCancelled else { return } | ||
self.transactionSummaries = self.transactionSummaries( | ||
transactions: allTransactions, | ||
selectedNetworkForCoin: selectedNetworkForCoin, | ||
accountInfos: allAccountInfos, | ||
userVisibleTokens: userVisibleTokens, | ||
allTokens: allTokens, | ||
assetRatios: assetPricesCache, | ||
solEstimatedTxFees: solEstimatedTxFeesCache | ||
) | ||
} | ||
} | ||
|
||
private func transactionSummaries( | ||
transactions: [BraveWallet.TransactionInfo], | ||
selectedNetworkForCoin: [BraveWallet.CoinType: BraveWallet.NetworkInfo], | ||
accountInfos: [BraveWallet.AccountInfo], | ||
userVisibleTokens: [BraveWallet.BlockchainToken], | ||
allTokens: [BraveWallet.BlockchainToken], | ||
assetRatios: [String: Double], | ||
solEstimatedTxFees: [String: UInt64] | ||
) -> [TransactionSummary] { | ||
transactions.compactMap { transaction in | ||
guard let network = selectedNetworkForCoin[transaction.coin] else { | ||
return nil | ||
} | ||
return TransactionParser.transactionSummary( | ||
from: transaction, | ||
network: network, | ||
accountInfos: accountInfos, | ||
visibleTokens: userVisibleTokens, | ||
allTokens: allTokens, | ||
assetRatios: assetRatios, | ||
solEstimatedTxFee: solEstimatedTxFees[transaction.id], | ||
currencyFormatter: currencyFormatter | ||
) | ||
}.sorted(by: { $0.createdTime > $1.createdTime }) | ||
} | ||
|
||
@MainActor private func updateSolEstimatedTxFeesCache(solTransactionIds: [String]) async { | ||
let fees = await solTxManagerProxy.estimatedTxFees(for: solTransactionIds) | ||
for (key, value) in fees { // update cached values | ||
self.solEstimatedTxFeesCache[key] = value | ||
} | ||
} | ||
|
||
@MainActor private func updateAssetPricesCache(assetRatioIds: [String]) async { | ||
let prices = await assetRatioService.fetchPrices( | ||
for: assetRatioIds, | ||
toAssets: [currencyFormatter.currencyCode], | ||
timeframe: .oneDay | ||
).compactMapValues { Double($0) } | ||
for (key, value) in prices { // update cached values | ||
self.assetPricesCache[key] = value | ||
} | ||
} | ||
|
||
func transactionDetailsStore( | ||
for transaction: BraveWallet.TransactionInfo | ||
) -> TransactionDetailsStore { | ||
TransactionDetailsStore( | ||
transaction: transaction, | ||
keyringService: keyringService, | ||
walletService: walletService, | ||
rpcService: rpcService, | ||
assetRatioService: assetRatioService, | ||
blockchainRegistry: blockchainRegistry, | ||
solanaTxManagerProxy: solTxManagerProxy | ||
) | ||
} | ||
} | ||
|
||
extension TransactionsActivityStore: BraveWalletKeyringServiceObserver { | ||
func keyringCreated(_ keyringId: String) { } | ||
|
||
func keyringRestored(_ keyringId: String) { } | ||
|
||
func keyringReset() { } | ||
|
||
func locked() { } | ||
|
||
func unlocked() { } | ||
|
||
func backedUp() { } | ||
|
||
func accountsChanged() { | ||
update() | ||
} | ||
|
||
func accountsAdded(_ coin: BraveWallet.CoinType, addresses: [String]) { | ||
update() | ||
} | ||
|
||
func autoLockMinutesChanged() { } | ||
|
||
func selectedAccountChanged(_ coin: BraveWallet.CoinType) { } | ||
} | ||
|
||
extension TransactionsActivityStore: BraveWalletTxServiceObserver { | ||
func onNewUnapprovedTx(_ txInfo: BraveWallet.TransactionInfo) { | ||
update() | ||
} | ||
|
||
func onUnapprovedTxUpdated(_ txInfo: BraveWallet.TransactionInfo) { | ||
update() | ||
} | ||
|
||
func onTransactionStatusChanged(_ txInfo: BraveWallet.TransactionInfo) { | ||
update() | ||
} | ||
|
||
func onTxServiceReset() { | ||
update() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* Copyright 2023 The Brave Authors. All rights reserved. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
import BraveCore | ||
import SwiftUI | ||
|
||
struct TransactionsActivityView: View { | ||
|
||
@ObservedObject var store: TransactionsActivityStore | ||
@ObservedObject var networkStore: NetworkStore | ||
|
||
@State private var isPresentingNetworkFilter = false | ||
@State private var transactionDetails: TransactionDetailsStore? | ||
|
||
var body: some View { | ||
List { | ||
Section { | ||
if store.transactionSummaries.isEmpty { | ||
emptyState | ||
.listRowBackground(Color.clear) | ||
} else { | ||
ForEach(store.transactionSummaries) { txSummary in | ||
Button(action: { | ||
self.transactionDetails = store.transactionDetailsStore(for: txSummary.txInfo) | ||
}) { | ||
TransactionSummaryView(summary: txSummary) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
.onAppear { | ||
store.update() | ||
} | ||
.sheet( | ||
isPresented: Binding( | ||
get: { self.transactionDetails != nil }, | ||
set: { if !$0 { self.transactionDetails = nil } } | ||
) | ||
) { | ||
if let transactionDetailsStore = self.transactionDetails { | ||
TransactionDetailsView( | ||
transactionDetailsStore: transactionDetailsStore, | ||
networkStore: networkStore | ||
) | ||
} | ||
} | ||
} | ||
|
||
private var emptyState: some View { | ||
VStack(alignment: .center, spacing: 10) { | ||
Text(Strings.Wallet.activityPageEmptyTitle) | ||
.font(.headline.weight(.semibold)) | ||
.foregroundColor(Color(.braveLabel)) | ||
Text(Strings.Wallet.activityPageEmptyDescription) | ||
.font(.subheadline.weight(.semibold)) | ||
.foregroundColor(Color(.secondaryLabel)) | ||
} | ||
.multilineTextAlignment(.center) | ||
.frame(maxWidth: .infinity) | ||
.padding(.vertical, 60) | ||
.padding(.horizontal, 32) | ||
} | ||
} | ||
|
||
#if DEBUG | ||
struct TransactionsActivityViewView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
TransactionsActivityView( | ||
store: .preview, | ||
networkStore: .previewStore | ||
) | ||
} | ||
} | ||
#endif |
Oops, something went wrong.