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

Commit

Permalink
support NFT removal. updates for NFT spam management new design
Browse files Browse the repository at this point in the history
  • Loading branch information
nuo-xu committed Oct 11, 2023
1 parent 522de3a commit c2b3912
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 74 deletions.
54 changes: 34 additions & 20 deletions Sources/BraveWallet/Crypto/NFT/NFTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,17 +205,47 @@ struct NFTView: View {
.multilineTextAlignment(.leading)
}
}
.overlay(alignment: .topLeading) {
if nft.token.isSpam {
HStack(spacing: 4) {
Text(Strings.Wallet.nftSpam)
.padding(.vertical, 4)
.padding(.leading, 6)
.foregroundColor(Color(.braveErrorLabel))
Image(braveSystemName: "leo.warning.triangle-outline")
.padding(.vertical, 4)
.padding(.trailing, 6)
.foregroundColor(Color(.braveErrorBorder))
}
.font(.system(size: 13).weight(.semibold))
.background(
Color(uiColor: WalletV2Design.spamNFTLabelBackground)
.cornerRadius(4)
)
.padding(12)
}
}
}
.contextMenu {
Button(action: {
nftStore.updateNFTStatus(nft.token, visible: isHiddenNFT(nft.token), isSpam: false)
if nft.token.visible { // a collected visible NFT
nftStore.updateNFTStatus(nft.token, visible: false, isDeletedByUser: false)
} else { // including hidden NFTs and junk NFTs
nftStore.updateNFTStatus(nft.token, visible: true, isDeletedByUser: false)
}
}) {
Label(isHiddenNFT(nft.token) ? Strings.Wallet.nftUnhide : Strings.recentSearchHide, braveSystemImage: isHiddenNFT(nft.token) ? "leo.eye.on" : "leo.eye.off")
if nft.token.visible { // a collected visible NFT
Label(Strings.recentSearchHide, braveSystemImage: "leo.eye.off")
} else if nft.token.isSpam { // a spam NFT
Label(Strings.Wallet.nftUnspam, braveSystemImage: "leo.disable.outline")
} else { // a hidden but not spam NFT
Label(Strings.Wallet.nftUnhide, braveSystemImage: "leo.eye.on")
}
}
Button(action: {
nftStore.updateNFTStatus(nft.token, visible: isSpamNFT(nft.token), isSpam: !isSpamNFT(nft.token))
nftStore.updateNFTStatus(nft.token, visible: false, isDeletedByUser: true)
}) {
Label(isSpamNFT(nft.token) ? Strings.Wallet.nftUnspam : Strings.Wallet.nftMoveToSpam, braveSystemImage: "leo.disable.outline")
Label(Strings.Wallet.nftRemoveFromWallet, braveSystemImage: "leo.trash")
}
}
}
Expand Down Expand Up @@ -321,22 +351,6 @@ 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
27 changes: 12 additions & 15 deletions Sources/BraveWallet/Crypto/Stores/NFTStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ public class NFTStore: ObservableObject, WalletObserverStore {
case .visible:
return userNFTs.filter(\.token.visible)
case .hidden:
return userNFTs.filter { !$0.token.visible && !$0.token.isSpam }
case .spam:
return userNFTs.filter(\.token.isSpam)
return userNFTs.filter { !$0.token.visible }
}
}
/// All User Accounts
Expand Down Expand Up @@ -75,7 +73,6 @@ public class NFTStore: ObservableObject, WalletObserverStore {
enum NFTDisplayType: Int, CaseIterable, Identifiable {
case visible
case hidden
case spam

var id: Int {
rawValue
Expand All @@ -84,11 +81,9 @@ public class NFTStore: ObservableObject, WalletObserverStore {
var dropdownTitle: String {
switch self {
case .visible:
return Strings.Wallet.nftsTitle
return Strings.Wallet.nftCollected
case .hidden:
return Strings.Wallet.nftHidden
case .spam:
return Strings.Wallet.nftSpam
}
}

Expand All @@ -98,16 +93,14 @@ public class NFTStore: ObservableObject, WalletObserverStore {
return Strings.Wallet.nftPageEmptyTitle
case .hidden:
return Strings.Wallet.nftInvisiblePageEmptyTitle
case .spam:
return Strings.Wallet.nftSpamPageEmptyTitle
}
}

var emptyDescription: String? {
switch self {
case .visible:
return Strings.Wallet.nftPageEmptyDescription
case .hidden, .spam:
case .hidden:
return nil
}
}
Expand Down Expand Up @@ -344,17 +337,21 @@ public class NFTStore: ObservableObject, WalletObserverStore {
selectedAccounts: [BraveWallet.AccountInfo],
simpleHashSpamNFTs: [NetworkAssets]
) -> [NetworkAssets] {
// all user marked deleted NFTs
let allUserMarkedDeletedNFTs = assetManager.getAllUserDeletedNFTs()
// all spam NFTs marked by user
let allUserMarkedSpamNFTs = assetManager.getAllUserNFTs(networks: selectedNetworks, isSpam: true)
// filter out any spam NFTs from `simpleHashSpamNFTs` that are marked
// not-spam by user
// not-spam or deleted by user
var updatedSimpleHashSpamNFTs: [NetworkAssets] = []
for simpleHashSpamNFTsOnNetwork in simpleHashSpamNFTs {
let userMarkedNotSpamTokensOnNetwork = assetManager.getAllUserNFTs(networks: [simpleHashSpamNFTsOnNetwork.network], isSpam: false).flatMap(\.tokens)
let filteredSimpleHashSpamTokens = simpleHashSpamNFTsOnNetwork.tokens.filter { simpleHashSpamToken in
!userMarkedNotSpamTokensOnNetwork.contains { token in
return !userMarkedNotSpamTokensOnNetwork.contains { token in
token.id == simpleHashSpamToken.id
}
} && !allUserMarkedDeletedNFTs.contains(where: { deletedNFT in
deletedNFT.contractAddress == simpleHashSpamToken.contractAddress && deletedNFT.chainId == simpleHashSpamToken.chainId && deletedNFT.tokenId == simpleHashSpamToken.tokenId
})
}
updatedSimpleHashSpamNFTs.append(NetworkAssets(network: simpleHashSpamNFTsOnNetwork.network, tokens: filteredSimpleHashSpamTokens, sortOrder: simpleHashSpamNFTsOnNetwork.sortOrder))
}
Expand Down Expand Up @@ -387,8 +384,8 @@ public class NFTStore: ObservableObject, WalletObserverStore {
walletService.setNftDiscoveryEnabled(true)
}

func updateNFTStatus(_ token: BraveWallet.BlockchainToken, visible: Bool, isSpam: Bool) {
assetManager.updateUserAsset(for: token, visible: visible, isSpam: isSpam) { [weak self] in
func updateNFTStatus(_ token: BraveWallet.BlockchainToken, visible: Bool, isDeletedByUser: Bool) {
assetManager.updateUserAsset(for: token, visible: visible, isDeletedByUser: isDeletedByUser) { [weak self] in
guard let self else { return }
let selectedAccounts = self.filters.accounts.filter(\.isSelected).map(\.model)
let selectedNetworks = self.filters.networks.filter(\.isSelected).map(\.model)
Expand Down
4 changes: 2 additions & 2 deletions Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class AssetStore: ObservableObject, Equatable, WalletObserverStore {
@Published var token: BraveWallet.BlockchainToken
@Published var isVisible: Bool {
didSet {
assetManager.updateUserAsset(for: token, visible: isVisible, isSpam: false, completion: nil)
assetManager.updateUserAsset(for: token, visible: isVisible, isDeletedByUser: false, completion: nil)
}
}
var network: BraveWallet.NetworkInfo
Expand Down Expand Up @@ -183,7 +183,7 @@ public class UserAssetsStore: ObservableObject, WalletObserverStore {
_ asset: BraveWallet.BlockchainToken,
completion: @escaping (_ success: Bool) -> Void
) {
if assetManager.getUserAsset(asset) != nil {
if let existedAsset = assetManager.getUserAsset(asset), !existedAsset.isDeletedByUser {
completion(false)
} else {
assetManager.addUserAsset(asset) { [weak self] in
Expand Down
7 changes: 7 additions & 0 deletions Sources/BraveWallet/Extensions/WalletColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,11 @@ enum WalletV2Design {
static let passwordMediumYellow = UIColor(rgb: 0xbd9600)

static let passwordStrongGreen = UIColor(rgb: 0x31803e)

static let spamNFTLabelBackground = UIColor(
red: 255 / 255,
green: 209 / 255,
blue: 207 / 255,
alpha: 1
)
}
34 changes: 17 additions & 17 deletions Sources/BraveWallet/WalletStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4213,13 +4213,6 @@ extension Strings {
value: "No hidden NFTs here yet.",
comment: "The title of the empty state inside NFT tab under Hidden group."
)
public static let nftSpamPageEmptyTitle = NSLocalizedString(
"wallet.nftSpamPageEmptyTitle",
tableName: "BraveWallet",
bundle: .module,
value: "No NFTs have been marked as spam.",
comment: "The title of the empty state inside NFT tab under Spam group."
)
public static let nftPageEmptyDescription = NSLocalizedString(
"wallet.nftPageEmptyDescription",
tableName: "BraveWallet",
Expand Down Expand Up @@ -4325,6 +4318,13 @@ extension Strings {
value: "Import NFT",
comment: "The title of the button that user clicks to add his/her first NFT"
)
public static let nftCollected = NSLocalizedString(
"wallet.nftCollected",
tableName: "BraveWallet",
bundle: .module,
value: "Collected",
comment: "The title of one of the dropdown options to group NFTs. This group will display all user's visible NFTs."
)
public static let nftHidden = NSLocalizedString(
"wallet.nftHidden",
tableName: "BraveWallet",
Expand All @@ -4336,8 +4336,8 @@ extension Strings {
"wallet.nftSpam",
tableName: "BraveWallet",
bundle: .module,
value: "Spam",
comment: "The title of one of the dropdown options to group NFTs. This group will display all user's marked spam NFTs and SimpleHash marked spam NFTs."
value: "Junk",
comment: "The title of an overlay on top left of the junk NFT grid."
)
public static let nftUnhide = NSLocalizedString(
"wallet.nftUnhide",
Expand All @@ -4346,20 +4346,20 @@ extension Strings {
value: "Unhide",
comment: "The title of context button for user to unhide visible NFT."
)
public static let nftMoveToSpam = NSLocalizedString(
"wallet.nftMoveToSpam",
tableName: "BraveWallet",
bundle: .module,
value: "Move to Spam",
comment: "The title of context button for user to move a NFT to the `Spam` group."
)
public static let nftUnspam = NSLocalizedString(
"wallet.nftUnspam",
tableName: "BraveWallet",
bundle: .module,
value: "Unspam",
value: "Mark as not junk",
comment: "The title of context button for user to unspam a NFT."
)
public static let nftRemoveFromWallet = NSLocalizedString(
"wallet.nftRemoveFromWallet",
tableName: "BraveWallet",
bundle: .module,
value: "Don't show in wallet",
comment: "The title of context button for user to do not show a NFT in wallet at all."
)
public static let selectTokenToSendTitle = NSLocalizedString(
"wallet.selectTokenToSendTitle",
tableName: "BraveWallet",
Expand Down
38 changes: 27 additions & 11 deletions Sources/BraveWallet/WalletUserAssetManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public protocol WalletUserAssetManagerType: AnyObject {
func getAllUserAssetsInNetworkAssets(networks: [BraveWallet.NetworkInfo], includingSpam: Bool) -> [NetworkAssets]
/// Return all spam or non-spam user assets in form of `NetworkAssets`
func getAllUserNFTs(networks: [BraveWallet.NetworkInfo], isSpam: Bool) -> [NetworkAssets]
/// Return all user marked deleted user assets
func getAllUserDeletedNFTs() -> [WalletUserAsset]
/// Return a `WalletUserAsset` with a given `BraveWallet.BlockchainToken`
func getUserAsset(_ asset: BraveWallet.BlockchainToken) -> WalletUserAsset?
/// Add a `WalletUserAsset` representation of the given
Expand All @@ -26,8 +28,8 @@ public protocol WalletUserAssetManagerType: AnyObject {
func removeUserAsset(_ asset: BraveWallet.BlockchainToken, completion: (() -> Void)?)
/// Remove an entire `WalletUserAssetGroup` with a given `groupId`
func removeGroup(for groupId: String, completion: (() -> Void)?)
/// Update a `WalletUserAsset`'s visible and spam status
func updateUserAsset(for asset: BraveWallet.BlockchainToken, visible: Bool, isSpam: Bool, completion: (() -> Void)?)
/// Update a `WalletUserAsset`'s `visible` and `isDeletedByUser` status
func updateUserAsset(for asset: BraveWallet.BlockchainToken, visible: Bool, isDeletedByUser: Bool, completion: (() -> Void)?)
}

public class WalletUserAssetManager: WalletUserAssetManagerType {
Expand Down Expand Up @@ -71,7 +73,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType {
var allVisibleUserAssets: [NetworkAssets] = []
for (index, network) in networks.enumerated() {
let groupId = network.walletUserAssetGroupId
if let walletUserAssets = WalletUserAssetGroup.getGroup(groupId: groupId)?.walletUserAssets?.filter({ $0.visible == visible && $0.isSpam == false }) {
if let walletUserAssets = WalletUserAssetGroup.getGroup(groupId: groupId)?.walletUserAssets?.filter({ $0.visible == visible && $0.isSpam == false && $0.isDeletedByUser == false }) {
let networkAsset = NetworkAssets(
network: network,
tokens: walletUserAssets.map(\.blockchainToken),
Expand All @@ -88,7 +90,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType {
var allUserSpamAssets: [NetworkAssets] = []
for (index, network) in networks.enumerated() {
let groupId = network.walletUserAssetGroupId
if let walletUserAssets = WalletUserAssetGroup.getGroup(groupId: groupId)?.walletUserAssets?.filter({ $0.isSpam == isSpam && ($0.isERC721 || $0.isNFT) }) { // Even though users can only spam/unspam NFTs, but we put the NFT filter here to make sure only NFTs are returned
if let walletUserAssets = WalletUserAssetGroup.getGroup(groupId: groupId)?.walletUserAssets?.filter({ $0.isSpam == isSpam && ($0.isERC721 || $0.isNFT) && $0.isDeletedByUser == false }) { // Even though users can only spam/unspam NFTs, but we put the NFT filter here to make sure only NFTs are returned
let networkAsset = NetworkAssets(
network: network,
tokens: walletUserAssets.map(\.blockchainToken),
Expand All @@ -100,16 +102,25 @@ public class WalletUserAssetManager: WalletUserAssetManagerType {
return allUserSpamAssets.sorted(by: { $0.sortOrder < $1.sortOrder })
}

public func getAllUserDeletedNFTs() -> [WalletUserAsset] {
WalletUserAsset.getAllUserDeletedUserAssets() ?? []
}

public func getUserAsset(_ asset: BraveWallet.BlockchainToken) -> WalletUserAsset? {
WalletUserAsset.getUserAsset(asset: asset)
}

public func addUserAsset(_ asset: BraveWallet.BlockchainToken, completion: (() -> Void)?) {
guard WalletUserAsset.getUserAsset(asset: asset) == nil else {
completion?()
return
if let existedAsset = WalletUserAsset.getUserAsset(asset: asset) {
if existedAsset.isDeletedByUser { // this asset was added before but user marked as deleted after
WalletUserAsset.updateUserAsset(for: asset, visible: true, isDeletedByUser: false, completion: completion)
} else { // this asset exists, either in `Collected` or `Hidden` based on its `visible` value
completion?()
return
}
} else { // asset does not exist in database
WalletUserAsset.addUserAsset(asset: asset, completion: completion)
}
WalletUserAsset.addUserAsset(asset: asset, completion: completion)
}

public func removeUserAsset(_ asset: BraveWallet.BlockchainToken, completion: (() -> Void)?) {
Expand All @@ -119,13 +130,13 @@ public class WalletUserAssetManager: WalletUserAssetManagerType {
public func updateUserAsset(
for asset: BraveWallet.BlockchainToken,
visible: Bool,
isSpam: Bool,
isDeletedByUser: Bool,
completion: (() -> Void)?
) {
WalletUserAsset.updateUserAsset(
for: asset,
visible: visible,
isSpam: isSpam,
isDeletedByUser: isDeletedByUser,
completion: completion
)
}
Expand Down Expand Up @@ -184,6 +195,7 @@ public class TestableWalletUserAssetManager: WalletUserAssetManagerType {
public var _getAllUserAssetsInNetworkAssetsByVisibility: ((_ networks: [BraveWallet.NetworkInfo], _ visible: Bool) -> [NetworkAssets])?
public var _getAllUserAssetsInNetworkAssets: ((_ networks: [BraveWallet.NetworkInfo], _ includingSpam: Bool) -> [NetworkAssets])?
public var _getAllUserNFTs: ((_ networks: [BraveWallet.NetworkInfo], _ spamStatus: Bool) -> [NetworkAssets])?
public var _getAllUserDeletedNFTs: (() -> [WalletUserAsset])?

public init() {}

Expand All @@ -206,6 +218,10 @@ public class TestableWalletUserAssetManager: WalletUserAssetManagerType {
_getAllUserNFTs?(networks, isSpam) ?? []
}

public func getAllUserDeletedNFTs() -> [WalletUserAsset] {
_getAllUserDeletedNFTs?() ?? []
}

public func getUserAsset(_ asset: BraveWallet.BlockchainToken) -> WalletUserAsset? {
return nil
}
Expand All @@ -222,7 +238,7 @@ public class TestableWalletUserAssetManager: WalletUserAssetManagerType {
public func updateUserAsset(
for asset: BraveWallet.BlockchainToken,
visible: Bool,
isSpam: Bool,
isDeletedByUser: Bool,
completion: (() -> Void)?
) {
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Data/models/Model.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Model23.xcdatamodel</string>
<string>Model24.xcdatamodel</string>
</dict>
</plist>
Loading

0 comments on commit c2b3912

Please sign in to comment.