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

Commit

Permalink
Fix #8073: Add NFT grouping by none/accounts/networks (#8289)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuo-xu committed Oct 23, 2023
1 parent 0ceb0b4 commit e233aef
Show file tree
Hide file tree
Showing 9 changed files with 921 additions and 294 deletions.
19 changes: 18 additions & 1 deletion Sources/BraveWallet/Crypto/FiltersDisplaySettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ struct FiltersDisplaySettingsView: View {
@State var isHidingUnownedNFTs: Bool
/// If we are showing the network logo on NFTs. Default is true.
@State var isShowingNFTNetworkLogo: Bool
/// If we should disable `Hide Unowned`
@State var isHidingUnownedNFTsDisabled: Bool

/// All accounts and if they are currently selected. Default is all accounts selected.
@State var accounts: [Selectable<BraveWallet.AccountInfo>]
Expand Down Expand Up @@ -152,8 +154,9 @@ struct FiltersDisplaySettingsView: View {
self._groupBy = State(initialValue: filters.groupBy)
self._sortOrder = State(initialValue: filters.sortOrder)
self._isHidingSmallBalances = State(initialValue: filters.isHidingSmallBalances)
self._isHidingUnownedNFTs = State(initialValue: filters.isHidingUnownedNFTs)
self._isHidingUnownedNFTs = State(initialValue: filters.groupBy == .accounts ? true : filters.isHidingUnownedNFTs)
self._isShowingNFTNetworkLogo = State(initialValue: filters.isShowingNFTNetworkLogo)
self._isHidingUnownedNFTsDisabled = State(initialValue: filters.groupBy == .accounts)
self._accounts = State(initialValue: filters.accounts)
self._networks = State(initialValue: filters.networks)
self.isNFTFilters = isNFTFilters
Expand All @@ -167,6 +170,9 @@ struct FiltersDisplaySettingsView: View {
ScrollView {
LazyVStack(spacing: 0) {
if isNFTFilters {
groupByRow
.padding(.vertical, rowPadding)

showNFTNetworkLogo
.padding(.vertical, rowPadding)

Expand Down Expand Up @@ -194,6 +200,16 @@ struct FiltersDisplaySettingsView: View {
}
.padding(.horizontal)
}
.onChange(of: groupBy, perform: { newValue in
if isNFTFilters {
if newValue == .accounts {
isHidingUnownedNFTs = true
isHidingUnownedNFTsDisabled = true
} else {
isHidingUnownedNFTsDisabled = false
}
}
})
.background(Color(uiColor: WalletV2Design.containerBackground))
.safeAreaInset(edge: .bottom, content: {
saveChangesContainer
Expand Down Expand Up @@ -268,6 +284,7 @@ struct FiltersDisplaySettingsView: View {
)
}
.tint(Color(.braveBlurpleTint))
.disabled(isHidingUnownedNFTsDisabled)
}

private var showNFTNetworkLogo: some View {
Expand Down
251 changes: 146 additions & 105 deletions Sources/BraveWallet/Crypto/NFT/NFTView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct NFTView: View {
@State private var isShowingAddCustomNFT: Bool = false
@State private var isNFTDiscoveryEnabled: Bool = false
@State private var nftToBeRemoved: NFTAssetViewModel?
@State private var groupToggleState: [NFTGroupViewModel.ID: Bool] = [:]

@Environment(\.buySendSwapDestination)
private var buySendSwapDestination: Binding<BuySendSwapDestination?>
Expand Down Expand Up @@ -108,53 +109,44 @@ struct NFTView: View {
AssetButton(braveSystemName: "leo.filter.settings", action: {
isPresentingFiltersDisplaySettings = true
})
.sheet(isPresented: $isPresentingFiltersDisplaySettings) {
FiltersDisplaySettingsView(
filters: nftStore.filters,
isNFTFilters: true,
networkStore: networkStore,
save: { filters in
nftStore.saveFilters(filters)
}
)
.osAvailabilityModifiers({ view in
if #available(iOS 16, *) {
view
.presentationDetents([
.fraction(0.6),
.large
])
} else {
view
}
})
}
}

private var nftHeaderView: some View {
HStack {
Text(Strings.Wallet.assetsTitle)
.font(.title3.weight(.semibold))
.foregroundColor(Color(braveSystemName: .textPrimary))
Menu {
Picker("", selection: $nftStore.displayType) {
ForEach(NFTStore.NFTDisplayType.allCases) { type in
Text(type.dropdownTitle)
.foregroundColor(Color(.secondaryBraveLabel))
.tag(type)
}
}
.pickerStyle(.inline)
} label: {
HStack(spacing: 12) {
Text(nftStore.displayType.dropdownTitle)
.font(.subheadline.weight(.semibold))
Text("\(nftStore.totalDisplayedNFTCount)")
.padding(.horizontal, 8)
.padding(.vertical, 4)
.font(.caption2.weight(.semibold))
.background(
Color(braveSystemName: .primary20)
.cornerRadius(4)
)
Image(braveSystemName: "leo.carat.down")
.font(.subheadline.weight(.semibold))
}
.foregroundColor(Color(.braveBlurpleTint))
}
if nftStore.isLoadingDiscoverAssets && isNFTDiscoveryEnabled {
ProgressView()
.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
.padding(.trailing, 10)
filtersButton
}
.padding(.horizontal)
.frame(maxWidth: .infinity, alignment: .leading)
Expand Down Expand Up @@ -182,89 +174,117 @@ struct NFTView: View {
return attributedString
}

@ViewBuilder var nftGridsView: some View {
if nftStore.displayNFTs.isEmpty {
emptyView
.listRowBackground(Color(.clear))
} else {
LazyVGrid(columns: nftGrids) {
ForEach(nftStore.displayNFTs) { nft in
Button(action: {
selectedNFTViewModel = nft
}) {
VStack(alignment: .leading, spacing: 4) {
nftImage(nft)
.padding(.bottom, 8)
Text(nft.token.nftTokenTitle)
.font(.callout.weight(.medium))
.foregroundColor(Color(.braveLabel))
/// Builds the grids of NFTs without any grouping or expandable / collapse behaviour.
@ViewBuilder private func nftGridsPlainView(_ group: NFTGroupViewModel) -> some View {
LazyVGrid(columns: nftGrids) {
ForEach(group.assets) { nft in
Button(action: {
selectedNFTViewModel = nft
}) {
VStack(alignment: .leading, spacing: 4) {
nftImage(nft)
.padding(.bottom, 8)
Text(nft.token.nftTokenTitle)
.font(.callout.weight(.medium))
.foregroundColor(Color(.braveLabel))
.multilineTextAlignment(.leading)
if !nft.token.symbol.isEmpty {
Text(nft.token.symbol)
.font(.caption)
.foregroundColor(Color(.secondaryBraveLabel))
.multilineTextAlignment(.leading)
if !nft.token.symbol.isEmpty {
Text(nft.token.symbol)
.font(.caption)
.foregroundColor(Color(.secondaryBraveLabel))
.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)
}
.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: {
if nft.token.visible { // a collected visible NFT, mark as hidden
nftStore.updateNFTStatus(nft.token, visible: false, isSpam: false, isDeletedByUser: false)
} else { // either a hidden NFT or a junk NFT, mark as visible
nftStore.updateNFTStatus(nft.token, visible: true, isSpam: false, isDeletedByUser: false)
}
}) {
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")
}
}
.contextMenu {
Button(action: {
if nft.token.visible { // a collected visible NFT, mark as hidden
nftStore.updateNFTStatus(nft.token, visible: false, isSpam: false, isDeletedByUser: false)
} else { // either a hidden NFT or a junk NFT, mark as visible
nftStore.updateNFTStatus(nft.token, visible: true, isSpam: false, isDeletedByUser: false)
}
Button(action: {
nftToBeRemoved = nft
}) {
Label(Strings.Wallet.nftRemoveFromWallet, braveSystemImage: "leo.trash")
}) {
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: {
nftToBeRemoved = nft
}) {
Label(Strings.Wallet.nftRemoveFromWallet, braveSystemImage: "leo.trash")
}
}
}
.padding(.horizontal)
VStack(spacing: 16) {
Divider()
editUserAssetsButton
}
.padding(.top, 20)
}
}

/// Builds the expandable / collapseable section content for a given group.
@ViewBuilder private func groupedNFTSection(_ group: NFTGroupViewModel) -> some View {
if group.assets.isEmpty {
EmptyView()
} else {
WalletDisclosureGroup(
isNFTGroup: true,
isExpanded: Binding(
get: { groupToggleState[group.id, default: true] },
set: { groupToggleState[group.id] = $0 }
),
content: {
nftGridsPlainView(group)
.padding(.top)
},
label: {
if case let .account(account) = group.groupType {
AddressView(address: account.address) {
PortfolioAssetGroupHeaderView(group: group)
}
} else {
PortfolioAssetGroupHeaderView(group: group)
}
}
)
}
}

var body: some View {
VStack(spacing: 16) {
LazyVStack(spacing: 16) {
nftHeaderView

nftGridsView
if nftStore.isShowingNFTEmptyState {
emptyView
} else {
ForEach(nftStore.displayNFTGroups) { group in
if group.groupType == .none {
nftGridsPlainView(group)
.padding(.horizontal)
} else {
groupedNFTSection(group)
}
}
}
}
.background(
NavigationLink(
Expand Down Expand Up @@ -375,6 +395,27 @@ struct NFTView: View {
cryptoStore.updateAssets()
}
}
.sheet(isPresented: $isPresentingFiltersDisplaySettings) {
FiltersDisplaySettingsView(
filters: nftStore.filters,
isNFTFilters: true,
networkStore: networkStore,
save: { filters in
nftStore.saveFilters(filters)
}
)
.osAvailabilityModifiers({ view in
if #available(iOS 16, *) {
view
.presentationDetents([
.fraction(0.6),
.large
])
} else {
view
}
})
}
.onAppear {
Task {
isNFTDiscoveryEnabled = await nftStore.isNFTDiscoveryEnabled()
Expand Down
Loading

0 comments on commit e233aef

Please sign in to comment.