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

Commit

Permalink
address or comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuo-xu committed Nov 15, 2023
1 parent 3c3dc76 commit 883a39f
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 66 deletions.
46 changes: 20 additions & 26 deletions Sources/BraveWallet/Crypto/NFT/NFTDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import BraveUI
import SDWebImageSwiftUI

struct NFTDetailView: View {
@ObservedObject var keyringStore: KeyringStore
@ObservedObject var nftDetailStore: NFTDetailStore
@Binding var buySendSwapDestination: BuySendSwapDestination?
var onNFTMetadataRefreshed: ((NFTMetadata) -> Void)?
Expand Down Expand Up @@ -95,7 +96,7 @@ struct NFTDetailView: View {
.offset(y: 16)
}
VStack(alignment: .leading, spacing: 8) {
Text(nftDetailStore.nft.nftTokenTitle)
Text(nftDetailStore.nft.nftDetailTitle)
.font(.title3.weight(.semibold))
.foregroundColor(Color(.braveLabel))
Text(nftDetailStore.nft.name)
Expand Down Expand Up @@ -130,35 +131,21 @@ struct NFTDetailView: View {
NFTDetailRow(title: nftDetailStore.nft.isErc721 ? Strings.Wallet.contractAddressAccessibilityLabel : Strings.Wallet.tokenMintAddress) {
Button {
if nftDetailStore.nft.isErc721 {
if let explorerURL = nftDetailStore.networkInfo.blockExplorerUrls.first {
let baseURL = "\(explorerURL)/token/\(nftDetailStore.nft.contractAddress)"
var nftURL = URL(string: baseURL)
if let tokenId = Int(nftDetailStore.nft.tokenId.removingHexPrefix, radix: 16) {
nftURL = URL(string: "\(baseURL)?a=\(tokenId)")
}

if let url = nftURL {
openWalletURL(url)
}
if let url = nftDetailStore.networkInfo.erc721TokenBlockExplorerURL(nftDetailStore.nft) {
openWalletURL(url)
}
} else {
if WalletConstants.supportedTestNetworkChainIds.contains(nftDetailStore.networkInfo.chainId) {
if let components = nftDetailStore.networkInfo.blockExplorerUrls.first?.separatedBy("/?cluster="), let baseURL = components.first {
let cluster = components.last ?? ""
if let nftURL = URL(string: "\(baseURL)/address/\(nftDetailStore.nft.contractAddress)/?cluster=\(cluster)") {
openWalletURL(nftURL)
}
}
} else {
if let explorerURL = nftDetailStore.networkInfo.blockExplorerUrls.first, let nftURL = URL(string: "\(explorerURL)/address/\(nftDetailStore.nft.contractAddress)") {
openWalletURL(nftURL)
}
if let url = nftDetailStore.networkInfo.splTokenBlockExplorerURL(nftDetailStore.nft) {
openWalletURL(url)
}
}
} label: {
Text(nftDetailStore.nft.contractAddress.truncatedAddress)
.font(.subheadline)
.foregroundColor(Color(.braveBlurpleTint))
HStack {
Text(nftDetailStore.nft.contractAddress.truncatedAddress)
Image(systemName: "arrow.up.forward.square")
}
.font(.subheadline)
.foregroundColor(Color(.braveBlurpleTint))
}
}
NFTDetailRow(title: Strings.Wallet.nftDetailBlockchain) {
Expand Down Expand Up @@ -212,11 +199,17 @@ struct NFTDetailView: View {
onNFTMetadataRefreshed?(newMetadata)
}
})
.onChange(of: keyringStore.isWalletLocked, perform: { isLocked in
guard isLocked else { return }
if isPresentingRemoveAlert {
isPresentingRemoveAlert = false
}
})
.onAppear {
nftDetailStore.update()
}
.background(Color(UIColor.braveGroupedBackground).ignoresSafeArea())
.navigationBarTitle(nftDetailStore.nft.nftTokenTitle)
.navigationBarTitle(nftDetailStore.nft.nftDetailTitle)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
Expand Down Expand Up @@ -291,6 +284,7 @@ struct NFTDetailView: View {
.font(.footnote)
.foregroundStyle(Color(.secondaryBraveLabel))
}
.padding(.bottom, 28)
})
)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/BraveWallet/Crypto/NFT/NFTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ struct NFTView: View {
destination: {
if let nftViewModel = selectedNFTViewModel {
NFTDetailView(
keyringStore: keyringStore,
nftDetailStore: cryptoStore.nftDetailStore(for: nftViewModel.token, nftMetadata: nftViewModel.nftMetadata, owner: nftStore.owner(for: nftViewModel.token)),
buySendSwapDestination: buySendSwapDestination,
onNFTMetadataRefreshed: { nftMetadata in
Expand Down
1 change: 1 addition & 0 deletions Sources/BraveWallet/Crypto/Search/AssetSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ struct AssetSearchView: View {
if let selectedToken {
if selectedToken.isErc721 {
NFTDetailView(
keyringStore: keyringStore,
nftDetailStore: cryptoStore.nftDetailStore(for: selectedToken, nftMetadata: allNFTMetadata[selectedToken.id], owner: nil),
buySendSwapDestination: .constant(nil)
) { metadata in
Expand Down
1 change: 1 addition & 0 deletions Sources/BraveWallet/Crypto/Stores/CryptoStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore {
}
let store = NFTDetailStore(
assetManager: userAssetManager,
keyringService: keyringService,
rpcService: rpcService,
ipfsApi: ipfsApi,
nft: nft,
Expand Down
37 changes: 36 additions & 1 deletion Sources/BraveWallet/Crypto/Stores/NFTDetailStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ struct NFTAttribute: Codable, Equatable, Identifiable {

class NFTDetailStore: ObservableObject, WalletObserverStore {
private let assetManager: WalletUserAssetManagerType
private let keyringService: BraveWalletKeyringService
private let rpcService: BraveWalletJsonRpcService
private let ipfsApi: IpfsAPI
let owner: BraveWallet.AccountInfo?
@Published var owner: BraveWallet.AccountInfo?
@Published var nft: BraveWallet.BlockchainToken
@Published var isLoading: Bool = false
@Published var nftMetadata: NFTMetadata?
Expand All @@ -79,13 +80,15 @@ class NFTDetailStore: ObservableObject, WalletObserverStore {

init(
assetManager: WalletUserAssetManagerType,
keyringService: BraveWalletKeyringService,
rpcService: BraveWalletJsonRpcService,
ipfsApi: IpfsAPI,
nft: BraveWallet.BlockchainToken,
nftMetadata: NFTMetadata?,
owner: BraveWallet.AccountInfo?
) {
self.assetManager = assetManager
self.keyringService = keyringService
self.rpcService = rpcService
self.ipfsApi = ipfsApi
self.nft = nft
Expand All @@ -100,6 +103,38 @@ class NFTDetailStore: ObservableObject, WalletObserverStore {
networkInfo = network
}

if owner == nil {
let accounts = await keyringService.allAccounts().accounts
let nftBalances: [String: Int] = await withTaskGroup(
of: [String: Int].self,
body: { @MainActor [rpcService, networkInfo, nft] group in
for account in accounts where account.coin == nft.coin {
group.addTask { @MainActor in
let balanceForToken = await rpcService.balance(
for: nft,
in: account,
network: networkInfo
)
return [account.address: Int(balanceForToken ?? 0)]
}
}
return await group.reduce(into: [String: Int](), { partialResult, new in
for key in new.keys {
partialResult[key] = new[key]
}
})
}
)
if let address = nftBalances.first(where: { address, balance in
balance > 0
})?.key,
let account = accounts.first(where: { accountInfo in
accountInfo.address.caseInsensitiveCompare(address) == .orderedSame
}) {
owner = account
}
}

if nftMetadata == nil {
isLoading = true
nftMetadata = await rpcService.fetchNFTMetadata(for: nft, ipfsApi: self.ipfsApi)
Expand Down
66 changes: 34 additions & 32 deletions Sources/BraveWallet/Crypto/Stores/NFTStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,42 +303,44 @@ public class NFTStore: ObservableObject, WalletObserverStore {
for networkAssets in [userVisibleNFTs, userHiddenNFTs, unionedSpamNFTs] {
allNFTs.append(contentsOf: networkAssets.flatMap(\.tokens))
}
// fetch balance for all NFTs
let allAccounts = filters.accounts.map(\.model)
nftBalancesCache = await withTaskGroup(
of: [String: [String: Int]].self,
body: { @MainActor [nftBalancesCache, rpcService] group in
for nft in allNFTs { // for each NFT
guard let networkForNFT = allNetworks.first(where: { $0.chainId == nft.chainId }) else {
continue
}
group.addTask { @MainActor in
let updatedBalances = await withTaskGroup(
of: [String: Int].self,
body: { @MainActor group in
for account in allAccounts where account.coin == nft.coin {
group.addTask { @MainActor in
let balanceForToken = await rpcService.balance(
for: nft,
in: account,
network: networkForNFT
)
return [account.address: Int(balanceForToken ?? 0)]
// if we're not hiding unowned or grouping by account, balance isn't needed
if filters.isHidingUnownedNFTs {
let allAccounts = filters.accounts.map(\.model)
nftBalancesCache = await withTaskGroup(
of: [String: [String: Int]].self,
body: { @MainActor [nftBalancesCache, rpcService] group in
for nft in allNFTs { // for each NFT
guard let networkForNFT = allNetworks.first(where: { $0.chainId == nft.chainId }) else {
continue
}
group.addTask { @MainActor in
let updatedBalances = await withTaskGroup(
of: [String: Int].self,
body: { @MainActor group in
for account in allAccounts where account.coin == nft.coin {
group.addTask { @MainActor in
let balanceForToken = await rpcService.balance(
for: nft,
in: account,
network: networkForNFT
)
return [account.address: Int(balanceForToken ?? 0)]
}
}
}
return await group.reduce(into: [String: Int](), { partialResult, new in
partialResult.merge(with: new)
return await group.reduce(into: [String: Int](), { partialResult, new in
partialResult.merge(with: new)
})
})
})
var tokenBalances = nftBalancesCache[nft.id] ?? [:]
tokenBalances.merge(with: updatedBalances)
return [nft.id: tokenBalances]
var tokenBalances = nftBalancesCache[nft.id] ?? [:]
tokenBalances.merge(with: updatedBalances)
return [nft.id: tokenBalances]
}
}
}
return await group.reduce(into: [String: [String: Int]](), { partialResult, new in
partialResult.merge(with: new)
return await group.reduce(into: [String: [String: Int]](), { partialResult, new in
partialResult.merge(with: new)
})
})
})
}

guard !Task.isCancelled else { return }
userNFTGroups = buildNFTGroupModels(
Expand Down
56 changes: 50 additions & 6 deletions Sources/BraveWallet/Extensions/BraveWalletExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ extension BraveWallet.CoinType {
return [.filecoin, .filecoinTestnet]
case .btc:
return [.bitcoin84, .bitcoin84Testnet]
case .zec:
return [.zCashMainnet, .zCashTestnet]
@unknown default:
return [.default]
}
Expand All @@ -134,7 +136,7 @@ extension BraveWallet.CoinType {
return Strings.Wallet.coinTypeSolana
case .fil:
return Strings.Wallet.coinTypeFilecoin
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return Strings.Wallet.coinTypeUnknown
Expand All @@ -149,7 +151,7 @@ extension BraveWallet.CoinType {
return Strings.Wallet.coinTypeSolanaDescription
case .fil:
return Strings.Wallet.coinTypeFilecoinDescription
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return Strings.Wallet.coinTypeUnknown
Expand All @@ -164,7 +166,7 @@ extension BraveWallet.CoinType {
return "sol-asset-icon"
case .fil:
return "filecoin-asset-icon"
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return ""
Expand All @@ -179,7 +181,7 @@ extension BraveWallet.CoinType {
return Strings.Wallet.defaultSolAccountName
case .fil:
return Strings.Wallet.defaultFilAccountName
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return ""
Expand All @@ -194,7 +196,7 @@ extension BraveWallet.CoinType {
return Strings.Wallet.defaultSecondarySolAccountName
case .fil:
return Strings.Wallet.defaultSecondaryFilAccountName
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return ""
Expand All @@ -210,7 +212,7 @@ extension BraveWallet.CoinType {
return 2
case .fil:
return 3
case .btc:
case .btc, .zec:
fallthrough
@unknown default:
return 10
Expand Down Expand Up @@ -259,6 +261,38 @@ extension BraveWallet.NetworkInfo {
}
return nil
}

/// Generate the explorer link for the given ERC721 token with the current NetworkInfo
func erc721TokenBlockExplorerURL(_ token: BraveWallet.BlockchainToken) -> URL? {
guard token.isErc721,
let explorerURL = blockExplorerUrls.first
else { return nil }

let baseURL = "\(explorerURL)/token/\(token.contractAddress)"
var tokenURL = URL(string: baseURL)
if let tokenId = Int(token.tokenId.removingHexPrefix, radix: 16) {
tokenURL = URL(string: "\(baseURL)?a=\(tokenId)")
}
return tokenURL
}

/// Generate the explorer link for the given SPL token with the current NetworkInfo
func splTokenBlockExplorerURL(_ token: BraveWallet.BlockchainToken) -> URL? {
guard !token.isErc721, !token.isErc20, !token.isErc1155 else { return nil }
if WalletConstants.supportedTestNetworkChainIds.contains(chainId) {
if let components = blockExplorerUrls.first?.separatedBy("/?cluster="), let baseURL = components.first {
let cluster = components.last ?? ""
if let tokenURL = URL(string: "\(baseURL)/address/\(token.contractAddress)/?cluster=\(cluster)") {
return tokenURL
}
}
} else {
if let explorerURL = blockExplorerUrls.first, let tokenURL = URL(string: "\(explorerURL)/address/\(token.contractAddress)") {
return tokenURL
}
}
return nil
}
}

extension BraveWallet.BlockchainToken {
Expand Down Expand Up @@ -287,6 +321,14 @@ extension BraveWallet.BlockchainToken {
}

var nftTokenTitle: String {
if isErc721, let tokenId = Int(tokenId.removingHexPrefix, radix: 16) {
return "\(name) #\(tokenId)"
} else {
return name
}
}

var nftDetailTitle: String {
if isErc721, let tokenId = Int(tokenId.removingHexPrefix, radix: 16) {
return "\(symbol) #\(tokenId)"
} else {
Expand Down Expand Up @@ -419,6 +461,8 @@ extension BraveWallet.KeyringId {
return chainId == BraveWallet.FilecoinMainnet ? .filecoin : .filecoinTestnet
case.btc:
return chainId == BraveWallet.BitcoinMainnet ? .bitcoin84 : .bitcoin84Testnet
case .zec:
return chainId == BraveWallet.ZCashMainnet ? .zCashMainnet: .zCashTestnet
@unknown default:
return .default
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/BraveWallet/WalletStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3765,7 +3765,7 @@ extension Strings {
"wallet.nftDetailOwnedBy",
tableName: "BraveWallet",
bundle: .module,
value: "Owned by",
value: "Owned By",
comment: "The title of the row under `Overview` section in NFT details screen. When this NFT has an owner."
)
public static let signTransactionSignRisk = NSLocalizedString(
Expand Down

0 comments on commit 883a39f

Please sign in to comment.