Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix #8006: NFT Spam Management #8055

Merged
merged 6 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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