Skip to content
Merged
12 changes: 1 addition & 11 deletions Features/Assets/Sources/Scenes/AssetScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion Features/NFT/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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"
),
Expand Down
72 changes: 72 additions & 0 deletions Features/NFT/Sources/Protocols/CollectionsViewable.swift
Original file line number Diff line number Diff line change
@@ -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
)
}
}
14 changes: 14 additions & 0 deletions Features/NFT/Sources/Scenes/CollectibleScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Components
import PrimitivesComponents
import Localization
import ImageGalleryService
import InfoSheet

public struct CollectibleScene: View {
@State private var model: CollectibleViewModel
Expand All @@ -19,6 +20,9 @@ public struct CollectibleScene: View {
public var body: some View {
List {
headerSectionView
if model.showStatus {
statusSectionView
}
assetInfoSectionView
if model.showAttributes {
attributesSectionView
Expand All @@ -41,6 +45,9 @@ public struct CollectibleScene: View {
onComplete: model.onReportComplete
)
)
}
.sheet(item: $model.isPresentingInfoSheet) {
InfoSheetScene(model: InfoSheetModelFactory.create(from: $0))
}
}
}
Expand All @@ -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)
Expand All @@ -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(
Expand Down
65 changes: 38 additions & 27 deletions Features/NFT/Sources/Scenes/CollectionsScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ViewModel: CollectionsViewable>: View {
@State private var model: ViewModel

@Query<NFTRequest>
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() }
}
}
Expand All @@ -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
)
}
}
Expand Down
16 changes: 16 additions & 0 deletions Features/NFT/Sources/Types/CollectionsContent.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
25 changes: 20 additions & 5 deletions Features/NFT/Sources/Types/GridPosterViewItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
20 changes: 17 additions & 3 deletions Features/NFT/Sources/ViewModels/CollectibleViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import AvatarService
import Formatters
import ExplorerService
import NFTService
import InfoSheet

@Observable
@MainActor
Expand All @@ -29,6 +30,7 @@ public final class CollectibleViewModel {
var isPresentingTokenExplorerUrl: URL?
var isPresentingSelectedAssetInput: Binding<SelectedAssetInput?>
var isPresentingReportSheet = false
var isPresentingInfoSheet: InfoSheetType?

public init(
wallet: Wallet,
Expand Down Expand Up @@ -103,7 +105,7 @@ public final class CollectibleViewModel {
}

var headerButtons: [HeaderButton] {
return [
[
HeaderButton(
type: .send,
isEnabled: isSendEnabled
Expand All @@ -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 {
Expand Down Expand Up @@ -226,6 +236,10 @@ extension CollectibleViewModel {
func onReportComplete() {
isPresentingReportSheet = false
isPresentingToast = .success(Localized.Transaction.Status.confirmed)
}

func onSelectStatus() {
isPresentingInfoSheet = .assetStatus(scoreViewModel.scoreType)
}
}

Expand Down
Loading
Loading