diff --git a/Features/Assets/Sources/Scenes/AssetScene.swift b/Features/Assets/Sources/Scenes/AssetScene.swift index 6f23f60b2..23bbc456e 100644 --- a/Features/Assets/Sources/Scenes/AssetScene.swift +++ b/Features/Assets/Sources/Scenes/AssetScene.swift @@ -40,17 +40,7 @@ public struct AssetScene: View { if model.showStatus { Section { - NavigationCustomLink(with: - ListItemImageView( - title: Localized.Transaction.status, - subtitle: model.scoreViewModel.status, - subtitleStyle: model.scoreViewModel.statusStyle, - assetImage: model.scoreViewModel.assetImage, - infoAction: { model.onSelectTokenStatus() } - ) - ) { - model.onSelectTokenStatus() - } + AssetStatusView(model: model.scoreViewModel, action: model.onSelectTokenStatus) } } diff --git a/Features/ManageWallets/Sources/WalletAvatar/ViewModels/WalletImageViewModel.swift b/Features/ManageWallets/Sources/WalletAvatar/ViewModels/WalletImageViewModel.swift index 835506b66..306522458 100644 --- a/Features/ManageWallets/Sources/WalletAvatar/ViewModels/WalletImageViewModel.swift +++ b/Features/ManageWallets/Sources/WalletAvatar/ViewModels/WalletImageViewModel.swift @@ -41,7 +41,7 @@ public final class WalletImageViewModel: Sendable { } var nftAssetsRequest: NFTRequest { - NFTRequest(walletId: wallet.id, collectionId: nil) + NFTRequest(walletId: wallet.id, filter: .all) } var emptyContentModel: EmptyContentTypeViewModel { diff --git a/Features/NFT/Package.swift b/Features/NFT/Package.swift index 235465267..b3e07e8b8 100644 --- a/Features/NFT/Package.swift +++ b/Features/NFT/Package.swift @@ -24,6 +24,7 @@ let package = Package( .package(name: "Store", path: "../../Packages/Store"), .package(name: "Formatters", path: "../../Packages/Formatters"), .package(name: "ChainServices", path: "../../Packages/ChainServices"), + .package(name: "InfoSheet", path: "../InfoSheet"), ], targets: [ .target( @@ -41,7 +42,8 @@ let package = Package( .product(name: "WalletService", package: "FeatureServices"), "Formatters", .product(name: "ExplorerService", package: "ChainServices"), - .product(name: "AvatarService", package: "FeatureServices") + .product(name: "AvatarService", package: "FeatureServices"), + "InfoSheet", ], path: "Sources" ), diff --git a/Features/NFT/Sources/Protocols/CollectionsViewable.swift b/Features/NFT/Sources/Protocols/CollectionsViewable.swift new file mode 100644 index 000000000..25298b8ab --- /dev/null +++ b/Features/NFT/Sources/Protocols/CollectionsViewable.swift @@ -0,0 +1,72 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import SwiftUI +import Primitives +import Store +import Components +import PrimitivesComponents + +@MainActor +public protocol CollectionsViewable: AnyObject, Observable { + var request: NFTRequest { get set } + var nftDataList: [NFTData] { get set } + + var title: String { get } + var columns: [GridItem] { get } + var content: CollectionsContent { get } + var emptyContentModel: EmptyContentTypeViewModel { get } + + var wallet: Wallet { get set } + + var isPresentingReceiveSelectAssetType: SelectAssetType? { get set } + + func fetch() async + func onChangeWallet(_ oldWallet: Wallet?, _ newWallet: Wallet?) + func onSelectReceive() +} + +extension CollectionsViewable { + public var columns: [GridItem] { + Array(repeating: GridItem(spacing: .medium), count: 2) + } + + public var emptyContentModel: EmptyContentTypeViewModel { + EmptyContentTypeViewModel(type: .nfts(action: onSelectReceive)) + } + + public func fetch() async {} + + public func onSelectReceive() { + isPresentingReceiveSelectAssetType = .receive(.collection) + } + + public func onChangeWallet(_ oldWallet: Wallet?, _ newWallet: Wallet?) { + if let newWallet, wallet != newWallet { + wallet = newWallet + request = NFTRequest(walletId: newWallet.id, filter: .all) + } + } + + public func buildGridItem(from data: NFTData) -> GridPosterViewItem { + if data.assets.count == 1, let asset = data.assets.first { + return buildGridItem(collection: data.collection, asset: asset) + } + return GridPosterViewItem( + id: data.id, + destination: Scenes.Collection(id: data.collection.id, name: data.collection.name), + assetImage: AssetImage(type: data.collection.name, imageURL: data.collection.images.preview.url.asURL), + title: data.collection.name, + count: data.assets.count + ) + } + + public func buildGridItem(collection: NFTCollection, asset: NFTAsset) -> GridPosterViewItem { + GridPosterViewItem( + id: asset.id, + destination: Scenes.Collectible(assetData: NFTAssetData(collection: collection, asset: asset)), + assetImage: AssetImage(type: collection.name, imageURL: asset.images.preview.url.asURL), + title: asset.name + ) + } +} diff --git a/Features/NFT/Sources/Scenes/CollectibleScene.swift b/Features/NFT/Sources/Scenes/CollectibleScene.swift index e00a72199..ee3a04451 100644 --- a/Features/NFT/Sources/Scenes/CollectibleScene.swift +++ b/Features/NFT/Sources/Scenes/CollectibleScene.swift @@ -8,6 +8,7 @@ import Components import PrimitivesComponents import Localization import ImageGalleryService +import InfoSheet public struct CollectibleScene: View { @State private var model: CollectibleViewModel @@ -19,6 +20,9 @@ public struct CollectibleScene: View { public var body: some View { List { headerSectionView + if model.showStatus { + statusSectionView + } assetInfoSectionView if model.showAttributes { attributesSectionView @@ -41,6 +45,9 @@ public struct CollectibleScene: View { onComplete: model.onReportComplete ) ) + } + .sheet(item: $model.isPresentingInfoSheet) { + InfoSheetScene(model: InfoSheetModelFactory.create(from: $0)) } } } @@ -57,6 +64,7 @@ extension CollectibleScene { } footer: { HeaderButtonsView(buttons: model.headerButtons, action: model.onSelectHeaderButton(type:)) .padding(.top, .medium) + .padding(.bottom, .small) } .frame(maxWidth: .infinity) .textCase(nil) @@ -76,6 +84,12 @@ extension CollectibleScene { ]) } + private var statusSectionView: some View { + Section { + AssetStatusView(model: model.scoreViewModel, action: model.onSelectStatus) + } + } + private var assetInfoSectionView: some View { Section { ListItemView( diff --git a/Features/NFT/Sources/Scenes/CollectionsScene.swift b/Features/NFT/Sources/Scenes/CollectionsScene.swift index 65b61327f..f785af969 100644 --- a/Features/NFT/Sources/Scenes/CollectionsScene.swift +++ b/Features/NFT/Sources/Scenes/CollectionsScene.swift @@ -5,45 +5,55 @@ import SwiftUI import GRDBQuery import Primitives import Store -import NFTService import Components -import DeviceService import Style -import Localization import PrimitivesComponents +import Localization -public struct CollectionsScene: View { - private let model: CollectionsViewModel +public struct CollectionsScene: View { + @State private var model: ViewModel - @Query - private var nftDataList: [NFTData] - - public init(model: CollectionsViewModel) { - self.model = model - let request = Binding { - model.request - } set: { new in - model.request = new - } - _nftDataList = Query(request) + public init(model: ViewModel) { + _model = State(initialValue: model) } - + public var body: some View { - ScrollView { - LazyVGrid(columns: model.columns) { - collectionsView + GeometryReader { geometry in + ScrollView { + VStack(spacing: .zero) { + LazyVGrid(columns: model.columns) { + collectionsView + } + .padding(.horizontal, .medium) + + Spacer(minLength: .medium) + + if let unverifiedCount = model.content.unverifiedCount { + List { + NavigationLink(value: Scenes.UnverifiedCollections()) { + ListItemView( + title: Localized.Asset.Verification.unverified, + subtitle: unverifiedCount + ) + } + } + .scrollDisabled(true) + .frame(height: .list.minHeight) + } + } + .frame(minHeight: geometry.size.height) } } + .observeQuery(request: $model.request, value: $model.nftDataList) .overlay { - if nftDataList.isEmpty { + if model.content.items.isEmpty { EmptyContentView(model: model.emptyContentModel) } } - .padding(.horizontal, .medium) .background { Colors.insetGroupedListStyle.ignoresSafeArea() } .navigationBarTitleDisplayMode(.inline) .navigationTitle(model.title) - .refreshable(action: model.fetch) + .refreshable { await model.fetch() } .task { await model.fetch() } } } @@ -52,11 +62,12 @@ public struct CollectionsScene: View { extension CollectionsScene { private var collectionsView: some View { - ForEach(model.createGridItems(from: nftDataList)) { gridItem in - NavigationLink(value: gridItem.destination) { + ForEach(model.content.items) { item in + NavigationLink(value: item.destination) { GridPosterView( - assetImage: gridItem.assetImage, - title: gridItem.title + assetImage: item.assetImage, + title: item.title, + count: item.count ) } } diff --git a/Features/NFT/Sources/Types/CollectionsContent.swift b/Features/NFT/Sources/Types/CollectionsContent.swift new file mode 100644 index 000000000..8cc6afe7e --- /dev/null +++ b/Features/NFT/Sources/Types/CollectionsContent.swift @@ -0,0 +1,16 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation + +public struct CollectionsContent: Sendable { + public let items: [GridPosterViewItem] + public let unverifiedCount: String? + + public init( + items: [GridPosterViewItem], + unverifiedCount: String? = nil + ) { + self.items = items + self.unverifiedCount = unverifiedCount + } +} diff --git a/Features/NFT/Sources/Types/GridPosterViewItem.swift b/Features/NFT/Sources/Types/GridPosterViewItem.swift index 10447117d..2db6f988f 100644 --- a/Features/NFT/Sources/Types/GridPosterViewItem.swift +++ b/Features/NFT/Sources/Types/GridPosterViewItem.swift @@ -3,9 +3,24 @@ import Foundation import Components -struct GridPosterViewItem: Identifiable { - let id: String - let destination: any Hashable - let assetImage: AssetImage - let title: String +public struct GridPosterViewItem: Identifiable, Sendable { + public let id: String + public let destination: any Hashable & Sendable + public let assetImage: AssetImage + public let title: String + public let count: Int? + + public init( + id: String, + destination: any Hashable & Sendable, + assetImage: AssetImage, + title: String, + count: Int? = nil + ) { + self.id = id + self.destination = destination + self.assetImage = assetImage + self.title = title + self.count = count + } } diff --git a/Features/NFT/Sources/ViewModels/CollectibleViewModel.swift b/Features/NFT/Sources/ViewModels/CollectibleViewModel.swift index d0578c333..f400a1ea4 100644 --- a/Features/NFT/Sources/ViewModels/CollectibleViewModel.swift +++ b/Features/NFT/Sources/ViewModels/CollectibleViewModel.swift @@ -13,6 +13,7 @@ import AvatarService import Formatters import ExplorerService import NFTService +import InfoSheet @Observable @MainActor @@ -29,6 +30,7 @@ public final class CollectibleViewModel { var isPresentingTokenExplorerUrl: URL? var isPresentingSelectedAssetInput: Binding var isPresentingReportSheet = false + var isPresentingInfoSheet: InfoSheetType? public init( wallet: Wallet, @@ -103,7 +105,7 @@ public final class CollectibleViewModel { } var headerButtons: [HeaderButton] { - return [ + [ HeaderButton( type: .send, isEnabled: isSendEnabled @@ -124,11 +126,19 @@ public final class CollectibleViewModel { } var showAttributes: Bool { - !attributes.isEmpty + attributes.isNotEmpty } var showLinks: Bool { - !assetData.collection.links.isEmpty + assetData.collection.links.isNotEmpty + } + + var scoreViewModel: AssetScoreTypeViewModel { + AssetScoreTypeViewModel(scoreType: .unverified) + } + + var showStatus: Bool { + assetData.collection.isVerified == false } var socialLinksViewModel: SocialLinksViewModel { @@ -226,6 +236,10 @@ extension CollectibleViewModel { func onReportComplete() { isPresentingReportSheet = false isPresentingToast = .success(Localized.Transaction.Status.confirmed) + } + + func onSelectStatus() { + isPresentingInfoSheet = .assetStatus(scoreViewModel.scoreType) } } diff --git a/Features/NFT/Sources/ViewModels/CollectionViewModel.swift b/Features/NFT/Sources/ViewModels/CollectionViewModel.swift new file mode 100644 index 000000000..188598ed5 --- /dev/null +++ b/Features/NFT/Sources/ViewModels/CollectionViewModel.swift @@ -0,0 +1,42 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import Components +import Primitives +import Store +import SwiftUI +import PrimitivesComponents + +@Observable +@MainActor +public final class CollectionViewModel: CollectionsViewable, Sendable { + private let collectionName: String + + public var request: NFTRequest + public var nftDataList: [NFTData] = [] + + public var isPresentingReceiveSelectAssetType: SelectAssetType? + + public var wallet: Wallet + + public init( + wallet: Wallet, + collectionId: String, + collectionName: String + ) { + self.wallet = wallet + self.collectionName = collectionName + self.request = NFTRequest(walletId: wallet.id, filter: .collection(id: collectionId)) + } + + public var title: String { collectionName } + + public var content: CollectionsContent { + CollectionsContent( + items: nftDataList.flatMap { data in + data.assets.map { buildGridItem(collection: data.collection, asset: $0) } + } + ) + } + +} diff --git a/Features/NFT/Sources/ViewModels/CollectionsViewModel.swift b/Features/NFT/Sources/ViewModels/CollectionsViewModel.swift index 9f9768041..bc947bc3a 100644 --- a/Features/NFT/Sources/ViewModels/CollectionsViewModel.swift +++ b/Features/NFT/Sources/ViewModels/CollectionsViewModel.swift @@ -6,146 +6,71 @@ import NFTService import Primitives import Store import Localization -import Style import SwiftUI import PrimitivesComponents -import AvatarService import WalletService +import Style @Observable @MainActor -public final class CollectionsViewModel: Sendable { +public final class CollectionsViewModel: CollectionsViewable, Sendable { private let walletService: WalletService private let nftService: NFTService - let columns: [GridItem] = Array(repeating: GridItem(spacing: .medium), count: 2) - let sceneStep: Scenes.CollectionsScene.SceneStep - var request: NFTRequest + public var request: NFTRequest + public var nftDataList: [NFTData] = [] public var isPresentingReceiveSelectAssetType: SelectAssetType? - public var isPresentingSelectedAssetInput: Binding - public private(set) var wallet: Wallet + public var wallet: Wallet public init( nftService: NFTService, walletService: WalletService, - wallet: Wallet, - sceneStep: Scenes.CollectionsScene.SceneStep, - isPresentingSelectedAssetInput: Binding + wallet: Wallet ) { self.nftService = nftService self.walletService = walletService - self.wallet = wallet - self.sceneStep = sceneStep - self.request = Self.createNftRequest(for: wallet, sceneStep: sceneStep) - self.isPresentingSelectedAssetInput = isPresentingSelectedAssetInput + self.request = NFTRequest(walletId: wallet.id, filter: .all) } - var title: String { - switch sceneStep { - case .collections: Localized.Nft.collections - case .collection(let data): data.collection.name - } - } + public var title: String { Localized.Nft.collections } - var emptyContentModel: EmptyContentTypeViewModel { - EmptyContentTypeViewModel(type: .nfts(action: onSelectReceive)) - } - public var currentWallet: Wallet? { walletService.currentWallet } - // MARK: - Public methods - - public func onChangeWallet(_ oldWallet: Wallet?, _ newWallet: Wallet?) { - if let newWallet, wallet != newWallet { - wallet = newWallet - request = Self.createNftRequest(for: wallet, sceneStep: sceneStep) - } + public var content: CollectionsContent { + CollectionsContent( + items: verifiedItems, + unverifiedCount: unverifiedCount + ) } - public func onSelectReceive() { - isPresentingReceiveSelectAssetType = .receive(.collection) - } + // MARK: - Private - // MARK: - Internal methods - - func fetch() async { - switch sceneStep { - case .collections: - await updateCollection() - case .collection: - break - } - } - - func createGridItems(from list: [NFTData]) -> [GridPosterViewItem] { - switch sceneStep { - case .collections: - list.map { buildCollectionsGridItem(from: $0) } - case .collection(let data): - data.assets.map { asset in - buildAssetDetailsGridItem(collection: data.collection, asset: asset) - } - } + private var verifiedItems: [GridPosterViewItem] { + nftDataList + .filter { $0.collection.isVerified } + .map { buildGridItem(from: $0) } } - - // MARK: - Private methods - - private func buildCollectionsGridItem(from data: NFTData) -> GridPosterViewItem { - if data.assets.count == 1, let asset = data.assets.first { - buildAssetDetailsGridItem(collection: data.collection, asset: asset) - } else { - buildCollectionGridItem(from: data) - } - } - - private func buildCollectionGridItem(from data: NFTData) -> GridPosterViewItem { - GridPosterViewItem( - id: data.id, - destination: Scenes.CollectionsScene(sceneStep: .collection(data)), - assetImage: AssetImage(type: data.collection.name, imageURL: data.collection.images.preview.url.asURL), - title: data.collection.name - ) - } - - private func buildAssetDetailsGridItem(collection: NFTCollection, asset: NFTAsset) -> GridPosterViewItem { - GridPosterViewItem( - id: asset.id, - destination: Scenes.Collectible(assetData: NFTAssetData(collection: collection, asset: asset)), - assetImage: AssetImage(type: collection.name, imageURL: asset.images.preview.url.asURL), - title: asset.name - ) + + private var unverifiedCount: String? { + let unverified = nftDataList.filter { !$0.collection.isVerified } + guard unverified.isNotEmpty else { return nil } + return unverified.count.asString } - - private func updateCollection() async { + + // MARK: - Actions + + public func fetch() async { do { let count = try await nftService.updateAssets(wallet: wallet) - debugLog("update nfts: \(count)") } catch { debugLog("update nfts error: \(error)") } } - - private static func createNftRequest( - for wallet: Wallet, - sceneStep: Scenes.CollectionsScene.SceneStep - ) -> NFTRequest { - switch sceneStep { - case .collections: - NFTRequest( - walletId: wallet.id, - collectionId: nil - ) - case .collection(let data): - NFTRequest( - walletId: wallet.id, - collectionId: data.collection.id - ) - } - } } + diff --git a/Features/NFT/Sources/ViewModels/UnverifiedCollectionsViewModel.swift b/Features/NFT/Sources/ViewModels/UnverifiedCollectionsViewModel.swift new file mode 100644 index 000000000..c40cb816c --- /dev/null +++ b/Features/NFT/Sources/ViewModels/UnverifiedCollectionsViewModel.swift @@ -0,0 +1,31 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import Components +import Primitives +import Store +import Localization +import SwiftUI +import PrimitivesComponents + +@Observable +@MainActor +public final class UnverifiedCollectionsViewModel: CollectionsViewable, Sendable { + public var request: NFTRequest + public var nftDataList: [NFTData] = [] + + public var isPresentingReceiveSelectAssetType: SelectAssetType? + + public var wallet: Wallet + + public init(wallet: Wallet) { + self.wallet = wallet + self.request = NFTRequest(walletId: wallet.id, filter: .unverified) + } + + public var title: String { Localized.Asset.Verification.unverified } + + public var content: CollectionsContent { + CollectionsContent(items: nftDataList.map { buildGridItem(from: $0) }) + } +} diff --git a/Gem/Navigation/NFT/CollectionsNavigationStack.swift b/Gem/Navigation/NFT/CollectionsNavigationStack.swift index f66b21f45..a223c6270 100644 --- a/Gem/Navigation/NFT/CollectionsNavigationStack.swift +++ b/Gem/Navigation/NFT/CollectionsNavigationStack.swift @@ -12,16 +12,17 @@ import AssetsService struct CollectionsNavigationStack: View { @Environment(\.navigationState) private var navigationState - @Environment(\.nftService) private var nftService @Environment(\.walletsService) private var walletsService @Environment(\.avatarService) private var avatarService - @Environment(\.walletService) private var walletService @Environment(\.priceAlertService) private var priceAlertService @Environment(\.assetsService) private var assetsService @Environment(\.activityService) private var activityService + @Environment(\.nftService) private var nftService @State private var model: CollectionsViewModel + @Binding private var isPresentingSelectedAssetInput: SelectedAssetInput? + private var navigationPath: Binding { Binding( get: { navigationState.collections }, @@ -29,11 +30,15 @@ struct CollectionsNavigationStack: View { ) } - init(model: CollectionsViewModel) { + init( + model: CollectionsViewModel, + isPresentingSelectedAssetInput: Binding + ) { _model = State(initialValue: model) + _isPresentingSelectedAssetInput = isPresentingSelectedAssetInput } - public var body: some View { + public var body: some View { NavigationStack(path: navigationPath) { CollectionsScene(model: model) .onChange( @@ -41,17 +46,20 @@ struct CollectionsNavigationStack: View { initial: true, model.onChangeWallet ) - .navigationDestination(for: Scenes.CollectionsScene.self) { + .navigationDestination(for: Scenes.Collection.self) { scene in CollectionsScene( - model: CollectionsViewModel( - nftService: nftService, - walletService: walletService, + model: CollectionViewModel( wallet: model.wallet, - sceneStep: $0.sceneStep, - isPresentingSelectedAssetInput: model.isPresentingSelectedAssetInput + collectionId: scene.id, + collectionName: scene.name ) ) } + .navigationDestination(for: Scenes.UnverifiedCollections.self) { _ in + CollectionsScene( + model: UnverifiedCollectionsViewModel(wallet: model.wallet) + ) + } .navigationDestination(for: Scenes.Collectible.self) { CollectibleScene( model: CollectibleViewModel( @@ -59,7 +67,7 @@ struct CollectionsNavigationStack: View { assetData: $0.assetData, avatarService: avatarService, nftService: nftService, - isPresentingSelectedAssetInput: model.isPresentingSelectedAssetInput + isPresentingSelectedAssetInput: $isPresentingSelectedAssetInput ) ) } diff --git a/Gem/Views/MainTabView.swift b/Gem/Views/MainTabView.swift index 6ae75b1f4..31397a66b 100644 --- a/Gem/Views/MainTabView.swift +++ b/Gem/Views/MainTabView.swift @@ -79,10 +79,9 @@ struct MainTabView: View { model: CollectionsViewModel( nftService: nftService, walletService: walletService, - wallet: model.wallet, - sceneStep: .collections, - isPresentingSelectedAssetInput: $isPresentingSelectedAssetInput - ) + wallet: model.wallet + ), + isPresentingSelectedAssetInput: $isPresentingSelectedAssetInput ) .tabItem { tabItem(Localized.Nft.collections, Images.Tabs.collections) diff --git a/Packages/Components/Sources/Grid/GridPosterView.swift b/Packages/Components/Sources/Grid/GridPosterView.swift index f8ff964ad..7a384234a 100644 --- a/Packages/Components/Sources/Grid/GridPosterView.swift +++ b/Packages/Components/Sources/Grid/GridPosterView.swift @@ -5,25 +5,33 @@ import SwiftUI import Style public struct GridPosterView: View { - + private let assetImage: AssetImage private let title: String? - + private let count: Int? + public init( assetImage: AssetImage, - title: String? + title: String?, + count: Int? = nil ) { self.assetImage = assetImage self.title = title + self.count = count } - + public var body: some View { VStack(alignment: .leading) { NftImageView(assetImage: assetImage) - .cornerRadius(.medium) + .clipShape(RoundedRectangle(cornerRadius: .medium)) .aspectRatio(1, contentMode: .fit) - - if let title { + .overlay(alignment: .topTrailing) { + if let count { + countBadge(count) + } + } + + if let title { Text(title) .font(.body) .lineLimit(1) @@ -32,6 +40,17 @@ public struct GridPosterView: View { Spacer() } } + + private func countBadge(_ count: Int) -> some View { + Text(String(count)) + .font(.footnote.weight(.semibold)) + .foregroundStyle(Colors.whiteSolid) + .padding(.horizontal, .space6) + .frame(minWidth: .space24, minHeight: .space24) + .background(Colors.Empty.image) + .clipShape(RoundedRectangle(cornerRadius: .small)) + .padding(.space8) + } } #Preview { diff --git a/Packages/Components/Sources/NftImageView.swift b/Packages/Components/Sources/NftImageView.swift index 84a1aeef1..9ada91c68 100644 --- a/Packages/Components/Sources/NftImageView.swift +++ b/Packages/Components/Sources/NftImageView.swift @@ -19,7 +19,11 @@ public struct NftImageView: View { ZStack { Rectangle() .foregroundStyle(Colors.grayLight) - LoadingView() + if assetImage.placeholder != nil { + AssetImageView(assetImage: assetImage, size: .image.large) + } else { + LoadingView() + } } case .success(let image): image.resizable() @@ -37,9 +41,9 @@ public struct NftImageView: View { .foregroundStyle(Colors.grayLight) if let type = assetImage.type { Text(type) - .font(.title) - .lineLimit(1) + .font(.body) .foregroundStyle(Colors.black.opacity(0.8)) + .padding(.small) } } .frame(maxWidth: .infinity) diff --git a/Packages/Primitives/Sources/Scenes.swift b/Packages/Primitives/Sources/Scenes.swift index 6a6dd814a..6c79fbbfc 100644 --- a/Packages/Primitives/Sources/Scenes.swift +++ b/Packages/Primitives/Sources/Scenes.swift @@ -133,23 +133,23 @@ public struct Scenes { } } - public struct CollectionsScene: Hashable { + public struct Collection: Hashable, Sendable { + public let id: String + public let name: String - public enum SceneStep: Hashable, Sendable { - case collections - case collection(NFTData) + public init(id: String, name: String) { + self.id = id + self.name = name } - - public let sceneStep: SceneStep + } - public init(sceneStep: SceneStep) { - self.sceneStep = sceneStep - } + public struct UnverifiedCollections: Hashable, Sendable { + public init() {} } - + public struct Collectible: Hashable, Sendable { public let assetData: NFTAssetData - + public init(assetData: NFTAssetData) { self.assetData = assetData } diff --git a/Packages/PrimitivesComponents/Sources/Views/AssetStatusView.swift b/Packages/PrimitivesComponents/Sources/Views/AssetStatusView.swift new file mode 100644 index 000000000..a4ffe7419 --- /dev/null +++ b/Packages/PrimitivesComponents/Sources/Views/AssetStatusView.swift @@ -0,0 +1,32 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import SwiftUI +import Components +import Localization + +public struct AssetStatusView: View { + private let model: AssetScoreTypeViewModel + private let action: () -> Void + + public init( + model: AssetScoreTypeViewModel, + action: @escaping () -> Void + ) { + self.model = model + self.action = action + } + + public var body: some View { + NavigationCustomLink(with: + ListItemImageView( + title: Localized.Transaction.status, + subtitle: model.status, + subtitleStyle: model.statusStyle, + assetImage: model.assetImage, + infoAction: action + ) + ) { + action() + } + } +} diff --git a/Packages/Store/Sources/Requests/NFTRequest.swift b/Packages/Store/Sources/Requests/NFTRequest.swift index 6abcbb8e4..b732d4250 100644 --- a/Packages/Store/Sources/Requests/NFTRequest.swift +++ b/Packages/Store/Sources/Requests/NFTRequest.swift @@ -6,20 +6,23 @@ import GRDBQuery import Primitives import Combine +public enum NFTFilter: Sendable, Hashable { + case all + case unverified + case collection(id: String) +} + public struct NFTRequest: ValueObservationQueryable { public static var defaultValue: [NFTData] { [] } - + private let walletId: String - private let collectionId: String? - - public init( - walletId: String, - collectionId: String? - ) { + private let filter: NFTFilter + + public init(walletId: String, filter: NFTFilter) { self.walletId = walletId - self.collectionId = collectionId + self.filter = filter } - + public func fetch(_ db: Database) throws -> [NFTData] { var request = NFTCollectionRecord .including( @@ -32,8 +35,10 @@ public struct NFTRequest: ValueObservationQueryable { .distinct() .asRequest(of: NFTCollectionRecordInfo.self) - if let collectionId { - request = request.filter(NFTCollectionRecord.Columns.id == collectionId) + switch filter { + case .all: break + case .unverified: request = request.filter(NFTCollectionRecord.Columns.isVerified == false) + case .collection(let id): request = request.filter(NFTCollectionRecord.Columns.id == id) } return try request diff --git a/Packages/Style/Sources/Spacing.swift b/Packages/Style/Sources/Spacing.swift index c6388f3d1..ce29c9aa8 100644 --- a/Packages/Style/Sources/Spacing.swift +++ b/Packages/Style/Sources/Spacing.swift @@ -52,7 +52,6 @@ public extension Spacing { public static let top: CGFloat = space16 /// 8 public static let bottom: CGFloat = space8 - /// 72 public static let bannerHeight: CGFloat = 72 @@ -107,6 +106,8 @@ public extension Sizing { } struct list { + /// 100 + public static let minHeight: CGFloat = 100 /// 16 public static let accessory: CGFloat = 16 /// 22