Skip to content
100 changes: 100 additions & 0 deletions Features/WalletTab/Sources/Types/WalletSceneSheetType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c). Gem Wallet. All rights reserved.

import Foundation
import InfoSheet
import Primitives
import SwiftUI

public enum WalletSceneSheetType: Identifiable, Sendable {
case wallets
case selectAssetType(SelectAssetType)
case info(InfoSheetType)
case url(URL)
case transferData(TransferData)
case perpetualRecipientData(PerpetualRecipientData)
case setPriceAlert(AssetId)

public var id: String {
switch self {
case .wallets: "wallets"
case let .selectAssetType(type): "select-asset-type-\(type.id)"
case let .info(type): "info-\(type.id)"
case let .url(url): "url-\(url)"
case let .transferData(data): "transfer-data-\(data.id)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The id for the .transferData case is constructed using data.id, but the TransferData type does not have an id property and does not conform to Identifiable. This will result in a compilation error.

Since TransferData conforms to Hashable, you can use data.hashValue to generate a unique identifier. This avoids having to modify the TransferData struct if that's outside the scope of this PR.

Suggested change
case let .transferData(data): "transfer-data-\(data.id)"
case let .transferData(data): "transfer-data-\(data.hashValue)"

case let .perpetualRecipientData(data): "perpetual-recipient-data-\(data.id)"
case let .setPriceAlert(assetId): "set-price-alert-\(assetId.identifier)"
}
}
}

// MARK: - Binding extensions

extension Binding where Value == WalletSceneSheetType? {
public var selectAssetType: Binding<SelectAssetType?> {
Binding<SelectAssetType?>(
get: {
if case .selectAssetType(let type) = wrappedValue {
return type
}
return nil
},
set: { newValue in
wrappedValue = newValue.map { .selectAssetType($0) }
}
)
}

public var transferData: Binding<TransferData?> {
Binding<TransferData?>(
get: {
if case .transferData(let data) = wrappedValue {
return data
}
return nil
},
set: { newValue in
wrappedValue = newValue.map { .transferData($0) }
}
)
}

public var perpetualRecipientData: Binding<PerpetualRecipientData?> {
Binding<PerpetualRecipientData?>(
get: {
if case .perpetualRecipientData(let data) = wrappedValue {
return data
}
return nil
},
set: { newValue in
wrappedValue = newValue.map { .perpetualRecipientData($0) }
}
)
}

public var wallets: Binding<Bool> {
Binding<Bool>(
get: {
if case .wallets = wrappedValue { return true }
return false
},
set: { newValue in
wrappedValue = newValue ? .wallets : nil
}
)
}

public var setPriceAlert: Binding<AssetId?> {
Binding<AssetId?>(
get: {
if case .setPriceAlert(let assetId) = wrappedValue {
return assetId
}
return nil
},
set: { newValue in
wrappedValue = newValue.map { .setPriceAlert($0) }
}
)
}
}
36 changes: 15 additions & 21 deletions Features/WalletTab/Sources/ViewModels/WalletSceneViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,8 @@ public final class WalletSceneViewModel: Sendable {
public var assets: [AssetData] = []
public var banners: [Banner] = []

// TODO: - separate presenting sheet state logic to separate type
public var isPresentingSelectedAssetInput: Binding<SelectedAssetInput?>
public var isPresentingWallets = false
public var isPresentingSelectAssetType: SelectAssetType?
public var isPresentingInfoSheet: InfoSheetType?
public var isPresentingUrl: URL? = nil
public var isPresentingTransferData: TransferData?
public var isPresentingPerpetualRecipientData: PerpetualRecipientData?
public var isPresentingSetPriceAlert: AssetId?
public var isPresentingSheet: WalletSceneSheetType?
public var isPresentingToastMessage: ToastMessage?
public var isPresentingSearch = false

Expand Down Expand Up @@ -143,13 +136,13 @@ extension WalletSceneViewModel {
isLoadingAssets = false
}
}

public func onSelectWalletBar() {
isPresentingWallets.toggle()
isPresentingSheet = .wallets
}

public func onSelectManage() {
isPresentingSelectAssetType = .manage
isPresentingSheet = .selectAssetType(.manage)
}

public func onToggleSearch() {
Expand All @@ -164,15 +157,15 @@ extension WalletSceneViewModel {
case .sell, .swap, .more, .stake, .deposit, .withdraw:
fatalError()
}
isPresentingSelectAssetType = selectType
isPresentingSheet = .selectAssetType(selectType)
}

func onCloseBanner(banner: Banner) {
bannerService.onClose(banner)
}

func onSelectWatchWalletInfo() {
isPresentingInfoSheet = .watchWallet
isPresentingSheet = .info(.watchWallet)
}

func onBanner(action: BannerAction) {
Expand All @@ -181,11 +174,13 @@ extension WalletSceneViewModel {
Task { try await handleBanner(action: action) }
case .button(let bannerButton):
switch bannerButton {
case .buy: isPresentingSelectAssetType = .buy
case .receive: isPresentingSelectAssetType = .receive(.asset)
case .buy: isPresentingSheet = .selectAssetType(.buy)
case .receive: isPresentingSheet = .selectAssetType(.receive(.asset))
}
}
isPresentingUrl = action.url
if let url = action.url {
isPresentingSheet = .url(url)
}
}

func onHideAsset(_ assetId: AssetId) {
Expand Down Expand Up @@ -229,18 +224,17 @@ extension WalletSceneViewModel {
let preferences = WalletPreferences(walletId: wallet.id)
isLoadingAssets = !preferences.completeInitialLoadAssets && preferences.assetsTimestamp == .zero
}

public func onTransferComplete() {
isPresentingTransferData = nil
isPresentingSheet = nil
}

public func onSetPriceAlertComplete(message: String) {
isPresentingSetPriceAlert = nil
isPresentingSheet = nil
isPresentingToastMessage = ToastMessage(title: message, image: SystemImage.bellFill)
}
}


// MARK: - Private

extension WalletSceneViewModel {
Expand Down
95 changes: 47 additions & 48 deletions Gem/Navigation/Wallet/WalletNavigationStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,24 +121,24 @@ struct WalletNavigationStack: View {
assetModel: AssetViewModel(asset: $0.asset),
priceAlertService: priceAlertService,
walletId: model.wallet.walletId,
isPresentingSetPriceAlert: $model.isPresentingSetPriceAlert
isPresentingSetPriceAlert: $model.isPresentingSheet.setPriceAlert
)
)
}
.navigationDestination(for: Scenes.Perpetuals.self) { _ in
PerpetualsNavigationView(
wallet: model.wallet,
perpetualService: perpetualService,
isPresentingSelectAssetType: $model.isPresentingSelectAssetType
isPresentingSelectAssetType: $model.isPresentingSheet.selectAssetType
)
}
.navigationDestination(for: Scenes.Perpetual.self) {
.navigationDestination(for: Scenes.Perpetual.self) { scene in
PerpetualNavigationView(
perpetualData: $0.perpetualData,
perpetualData: scene.perpetualData,
wallet: model.wallet,
perpetualService: perpetualService,
isPresentingTransferData: $model.isPresentingTransferData,
isPresentingPerpetualRecipientData: $model.isPresentingPerpetualRecipientData
isPresentingTransferData: $model.isPresentingSheet.transferData,
isPresentingPerpetualRecipientData: $model.isPresentingSheet.perpetualRecipientData
)
}
.navigationDestination(for: Scenes.AssetPriceAlert.self) {
Expand All @@ -150,50 +150,49 @@ struct WalletNavigationStack: View {
)
)
}
.sheet(item: $model.isPresentingSelectAssetType) {
SelectAssetSceneNavigationStack(
model: SelectAssetViewModel(
.sheet(item: $model.isPresentingSheet) { sheet in
switch sheet {
case let .selectAssetType(value):
SelectAssetSceneNavigationStack(
model: SelectAssetViewModel(
wallet: model.wallet,
selectType: value,
searchService: AssetSearchService(assetsService: assetsService),
walletsService: walletsService,
priceAlertService: priceAlertService
),
isPresentingSelectType: $model.isPresentingSheet.selectAssetType
)
case let .info(type):
InfoSheetScene(type: type)
case let .perpetualRecipientData(perpetualRecipientData):
PerpetualPositionNavigationStack(
perpetualRecipientData: perpetualRecipientData,
wallet: model.wallet,
selectType: $0,
searchService: AssetSearchService(assetsService: assetsService),
walletsService: walletsService,
priceAlertService: priceAlertService
),
isPresentingSelectType: $model.isPresentingSelectAssetType
)
}
.sheet(isPresented: $model.isPresentingWallets) {
WalletsNavigationStack(isPresentingWallets: $model.isPresentingWallets)
}
.sheet(item: $model.isPresentingInfoSheet) {
InfoSheetScene(type: $0)
}
.sheet(item: $model.isPresentingTransferData) {
ConfirmTransferNavigationStack(
wallet: model.wallet,
transferData: $0,
onComplete: model.onTransferComplete
)
}
.sheet(item: $model.isPresentingPerpetualRecipientData) {
PerpetualPositionNavigationStack(
perpetualRecipientData: $0,
wallet: model.wallet,
onComplete: {
model.isPresentingPerpetualRecipientData = nil
}
)
}
.sheet(item: $model.isPresentingSetPriceAlert) { assetId in
SetPriceAlertNavigationStack(
model: SetPriceAlertViewModel(
walletId: model.wallet.walletId,
assetId: assetId,
priceAlertService: priceAlertService
) { model.onSetPriceAlertComplete(message: $0) }
)
onComplete: {
model.isPresentingSheet = nil
}
)
case let .transferData(transferData):
ConfirmTransferNavigationStack(
wallet: model.wallet,
transferData: transferData,
onComplete: model.onTransferComplete
)
case let .url(url):
SFSafariView(url: url)
case .wallets:
WalletsNavigationStack(isPresentingWallets: $model.isPresentingSheet.wallets)
case let .setPriceAlert(assetId):
SetPriceAlertNavigationStack(
model: SetPriceAlertViewModel(
walletId: model.wallet.walletId,
assetId: assetId,
priceAlertService: priceAlertService
) { model.onSetPriceAlertComplete(message: $0) }
)
}
}
.safariSheet(url: $model.isPresentingUrl)
.toast(message: $model.isPresentingToastMessage)
}
}
Expand Down
Loading