Skip to content

Commit

Permalink
Fix brave/brave-ios#8006: NFT Spam Management (brave/brave-ios#8055)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuo-xu authored Oct 10, 2023
1 parent 0082c2f commit 0c52c94
Show file tree
Hide file tree
Showing 35 changed files with 942 additions and 335 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ struct AccountActivityView: View {
header: WalletListHeaderView(title: Text(Strings.Wallet.assetsTitle))
) {
Group {
if activityStore.userVisibleAssets.isEmpty {
if activityStore.userAssets.isEmpty {
emptyTextView(Strings.Wallet.noAssets)
} else {
ForEach(activityStore.userVisibleAssets) { asset in
ForEach(activityStore.userAssets) { asset in
PortfolioAssetView(
image: AssetIconView(
token: asset.token,
Expand All @@ -82,10 +82,10 @@ struct AccountActivityView: View {
}
.listRowBackground(Color(.secondaryBraveGroupedBackground))
}
if !activityStore.userVisibleNFTs.isEmpty {
if !activityStore.userNFTs.isEmpty {
Section(content: {
Group {
ForEach(activityStore.userVisibleNFTs) { nftAsset in
ForEach(activityStore.userNFTs) { nftAsset in
NFTAssetView(
image: NFTIconView(
token: nftAsset.token,
Expand Down
52 changes: 44 additions & 8 deletions Sources/BraveWallet/Crypto/NFT/NFTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import SwiftUI
import DesignSystem
import Preferences
import BraveCore

struct NFTView: View {
var cryptoStore: CryptoStore
Expand All @@ -26,16 +27,19 @@ struct NFTView: View {

private var emptyView: some View {
VStack(alignment: .center, spacing: 10) {
Text(Strings.Wallet.nftPageEmptyTitle)
Text(nftStore.displayType.emptyTitle)
.font(.headline.weight(.semibold))
.foregroundColor(Color(.braveLabel))
Text(Strings.Wallet.nftPageEmptyDescription)
.font(.subheadline.weight(.semibold))
.foregroundColor(Color(.secondaryLabel))
if let description = nftStore.displayType.emptyDescription {
Text(description)
.font(.subheadline.weight(.semibold))
.foregroundColor(Color(.secondaryLabel))
}
Button(Strings.Wallet.nftEmptyImportNFT) {
isShowingAddCustomNFT = true
}
.buttonStyle(BraveFilledButtonStyle(size: .normal))
.hidden(isHidden: nftStore.displayType != .visible)
.padding(.top, 8)
}
.multilineTextAlignment(.center)
Expand Down Expand Up @@ -142,6 +146,17 @@ struct NFTView: View {
.padding(.leading, 5)
}
Spacer()
Picker(selection: $nftStore.displayType) {
ForEach(NFTStore.NFTDisplayType.allCases) { type in
Text(type.dropdownTitle)
.foregroundColor(Color(.secondaryBraveLabel))
.tag(type)
}
} label: {
Text(nftStore.displayType.dropdownTitle)
.font(.footnote)
.foregroundColor(Color(.braveLabel))
}
filtersButton
.padding(.trailing, 10)
addCustomAssetButton
Expand Down Expand Up @@ -176,12 +191,12 @@ struct NFTView: View {
}

@ViewBuilder var nftGridsView: some View {
if nftStore.userVisibleNFTs.isEmpty {
if nftStore.displayNFTs.isEmpty {
emptyView
.listRowBackground(Color(.clear))
} else {
LazyVGrid(columns: nftGrids) {
ForEach(nftStore.userVisibleNFTs) { nft in
ForEach(nftStore.displayNFTs) { nft in
Button(action: {
selectedNFTViewModel = nft
}) {
Expand All @@ -202,9 +217,14 @@ struct NFTView: View {
}
.contextMenu {
Button(action: {
nftStore.updateVisibility(nft.token, visible: false)
nftStore.updateNFTStatus(nft.token, visible: isHiddenNFT(nft.token), isSpam: false)
}) {
Label(Strings.recentSearchHide, braveSystemImage: "leo.eye.off")
Label(isHiddenNFT(nft.token) ? Strings.Wallet.nftUnhide : Strings.recentSearchHide, braveSystemImage: isHiddenNFT(nft.token) ? "leo.eye.on" : "leo.eye.off")
}
Button(action: {
nftStore.updateNFTStatus(nft.token, visible: isSpamNFT(nft.token), isSpam: !isSpamNFT(nft.token))
}) {
Label(isSpamNFT(nft.token) ? Strings.Wallet.nftUnspam : Strings.Wallet.nftMoveToSpam, braveSystemImage: "leo.disable.outline")
}
}
}
Expand Down Expand Up @@ -314,6 +334,22 @@ struct NFTView: View {
}
}
}

private func isSpamNFT(_ nft: BraveWallet.BlockchainToken) -> Bool {
if nftStore.displayType == .spam {
return true
} else {
return nft.isSpam
}
}

private func isHiddenNFT(_ nft: BraveWallet.BlockchainToken) -> Bool {
if nftStore.displayType == .spam {
return false
} else {
return !nft.visible
}
}
}

#if DEBUG
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraveWallet/Crypto/Search/SendTokenSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct SendTokenSearchView: View {
var network: BraveWallet.NetworkInfo

var body: some View {
TokenList(tokens: sendTokenStore.userAssets) { token in
TokenList(tokens: sendTokenStore.userVisibleAssets) { token in
Button(action: {
sendTokenStore.selectedSendNFTMetadata = allNFTMetadata[token.id]
sendTokenStore.selectedSendToken = token
Expand All @@ -44,7 +44,7 @@ struct SendTokenSearchView: View {
}
.onAppear {
Task { @MainActor in
self.allNFTMetadata = await sendTokenStore.fetchNFTMetadata(tokens: sendTokenStore.userAssets.filter { $0.isErc721 || $0.isNft })
self.allNFTMetadata = await sendTokenStore.fetchNFTMetadata(tokens: sendTokenStore.userVisibleAssets.filter { $0.isErc721 || $0.isNft })
}
}
.navigationTitle(Strings.Wallet.searchTitle)
Expand Down
56 changes: 28 additions & 28 deletions Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
/// selected account changes (ex. when removing an account).
let observeAccountUpdates: Bool
private(set) var account: BraveWallet.AccountInfo
@Published private(set) var userVisibleAssets: [AssetViewModel] = []
@Published private(set) var userVisibleNFTs: [NFTAssetViewModel] = []
@Published private(set) var userAssets: [AssetViewModel] = []
@Published private(set) var userNFTs: [NFTAssetViewModel] = []
@Published var transactionSummaries: [TransactionSummary] = []
@Published private(set) var currencyCode: String = CurrencyCode.usd.code {
didSet {
Expand Down Expand Up @@ -141,22 +141,22 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
let tokens: [BraveWallet.BlockchainToken]
let sortOrder: Int
}
let allVisibleUserAssets = assetManager.getAllVisibleAssetsInNetworkAssets(networks: networksForAccount)
let allUserAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: networksForAccount, includingSpam: true)
let allTokens = await blockchainRegistry.allTokens(in: networksForAccountCoin).flatMap(\.tokens)
var updatedUserVisibleAssets: [AssetViewModel] = []
var updatedUserVisibleNFTs: [NFTAssetViewModel] = []
for networkAssets in allVisibleUserAssets {
var updatedUserAssets: [AssetViewModel] = []
var updatedUserNFTs: [NFTAssetViewModel] = []
for networkAssets in allUserAssets {
for token in networkAssets.tokens {
if token.isErc721 || token.isNft {
updatedUserVisibleNFTs.append(
updatedUserNFTs.append(
NFTAssetViewModel(
token: token,
network: networkAssets.network,
balanceForAccounts: [:]
)
)
} else {
updatedUserVisibleAssets.append(
updatedUserAssets.append(
AssetViewModel(
groupType: .none,
token: token,
Expand All @@ -169,12 +169,12 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
}
}
}
self.userVisibleAssets = updatedUserVisibleAssets
self.userVisibleNFTs = updatedUserVisibleNFTs
self.userAssets = updatedUserAssets
self.userNFTs = updatedUserNFTs

let keyringForAccount = await keyringService.keyringInfo(account.keyringId)
typealias TokenNetworkAccounts = (token: BraveWallet.BlockchainToken, network: BraveWallet.NetworkInfo, accounts: [BraveWallet.AccountInfo])
let allTokenNetworkAccounts = allVisibleUserAssets.flatMap { networkAssets in
let allTokenNetworkAccounts = allUserAssets.flatMap { networkAssets in
networkAssets.tokens.map { token in
TokenNetworkAccounts(
token: token,
Expand All @@ -201,30 +201,30 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
})
})

// fetch price for every visible token
let allVisibleTokens = allVisibleUserAssets.flatMap(\.tokens)
let allVisibleTokenAssetRatioIds = allVisibleTokens.map(\.assetRatioId)
// fetch price for every user asset
let allUserAssetsInToken = allUserAssets.flatMap(\.tokens)
let allUserAssetsAssetRatioIds = allUserAssetsInToken.map(\.assetRatioId)
let prices: [String: String] = await assetRatioService.fetchPrices(
for: allVisibleTokenAssetRatioIds,
for: allUserAssetsAssetRatioIds,
toAssets: [currencyFormatter.currencyCode],
timeframe: .oneDay
)

// fetch NFTs metadata
let allNFTMetadata = await rpcService.fetchNFTMetadata(
tokens: userVisibleNFTs
tokens: userNFTs
.map(\.token)
.filter({ $0.isErc721 || $0.isNft }),
ipfsApi: ipfsApi
)

guard !Task.isCancelled else { return }
updatedUserVisibleAssets.removeAll()
updatedUserVisibleNFTs.removeAll()
for networkAssets in allVisibleUserAssets {
updatedUserAssets.removeAll()
updatedUserNFTs.removeAll()
for networkAssets in allUserAssets {
for token in networkAssets.tokens {
if token.isErc721 || token.isNft {
updatedUserVisibleNFTs.append(
updatedUserNFTs.append(
NFTAssetViewModel(
token: token,
network: networkAssets.network,
Expand All @@ -233,7 +233,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
)
)
} else {
updatedUserVisibleAssets.append(
updatedUserAssets.append(
AssetViewModel(
groupType: .none,
token: token,
Expand All @@ -246,17 +246,17 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
}
}
}
self.userVisibleAssets = updatedUserVisibleAssets
self.userVisibleNFTs = updatedUserVisibleNFTs
self.userAssets = updatedUserAssets
self.userNFTs = updatedUserNFTs

let assetRatios = self.userVisibleAssets.reduce(into: [String: Double](), {
let assetRatios = self.userAssets.reduce(into: [String: Double](), {
$0[$1.token.assetRatioId.lowercased()] = Double($1.price)
})

self.transactionSummaries = await fetchTransactionSummarys(
networksForAccountCoin: networksForAccountCoin,
accountInfos: keyringForAccount.accountInfos,
userVisibleTokens: userVisibleAssets.map(\.token),
userAssets: userAssets.map(\.token),
allTokens: allTokens,
assetRatios: assetRatios
)
Expand All @@ -266,7 +266,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
@MainActor private func fetchTransactionSummarys(
networksForAccountCoin: [BraveWallet.NetworkInfo],
accountInfos: [BraveWallet.AccountInfo],
userVisibleTokens: [BraveWallet.BlockchainToken],
userAssets: [BraveWallet.BlockchainToken],
allTokens: [BraveWallet.BlockchainToken],
assetRatios: [String: Double]
) async -> [TransactionSummary] {
Expand All @@ -278,7 +278,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
let unknownTokenContractAddresses = transactions
.flatMap { $0.tokenContractAddresses }
.filter { contractAddress in
!userVisibleTokens.contains(where: { $0.contractAddress.caseInsensitiveCompare(contractAddress) == .orderedSame })
!userAssets.contains(where: { $0.contractAddress.caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !allTokens.contains(where: { $0.contractAddress.caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !tokenInfoCache.keys.contains(where: { $0.caseInsensitiveCompare(contractAddress) == .orderedSame })
}
Expand All @@ -299,7 +299,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
from: transaction,
network: network,
accountInfos: accountInfos,
visibleTokens: userVisibleTokens,
userAssets: userAssets,
allTokens: allTokens,
assetRatios: assetRatios,
solEstimatedTxFee: solEstimatedTxFees[transaction.id],
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
) async -> [TransactionSummary] {
guard case let .blockchainToken(token) = assetDetailType
else { return [] }
let userVisibleAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: [network]).flatMap { $0.tokens }
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: [network], includingSpam: true).flatMap { $0.tokens }
let allTokens = await blockchainRegistry.allTokens(network.chainId, coin: network.coin)
let allTransactions = await withTaskGroup(of: [BraveWallet.TransactionInfo].self) { @MainActor group -> [BraveWallet.TransactionInfo] in
for account in keyring.accountInfos {
Expand Down Expand Up @@ -371,7 +371,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
from: transaction,
network: network,
accountInfos: keyring.accountInfos,
visibleTokens: userVisibleAssets,
userAssets: userAssets,
allTokens: allTokens,
assetRatios: assetRatios,
solEstimatedTxFee: solEstimatedTxFees[transaction.id],
Expand Down
Loading

0 comments on commit 0c52c94

Please sign in to comment.