Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit ef495a4

Browse files
committed
Add auto-alert toggle and priceQuery
Replace conditional auto-alert row with a Toggle-backed view and add a priceQuery to support showing current price. Introduces autoAlertItemModel and isAutoAlertEnabledBinding in the view model, plus toggleAutoAlert(enabled:) to add/delete the default auto alert and handle permissions. Scene now binds priceQuery (instead of query), exposes a dedicated autoAlertToggleView, removes the empty-state overlay, and makes onDelete private. Tests updated to assert the new binding behavior.
1 parent 11b434c commit ef495a4

3 files changed

Lines changed: 49 additions & 37 deletions

File tree

Features/PriceAlerts/Sources/Scenes/AssetPriceAlertsScene.swift

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ public struct AssetPriceAlertsScene: View {
1717

1818
public var body: some View {
1919
List {
20-
if let autoAlert = model.autoAlertModel {
21-
Section {
22-
alertView(model: autoAlert)
23-
} footer: {
24-
Text(Localized.PriceAlerts.autoFooter)
25-
}
20+
Section {
21+
autoAlertToggleView
22+
} footer: {
23+
Text(Localized.PriceAlerts.autoFooter)
2624
}
27-
25+
2826
if model.alertsModel.isNotEmpty {
2927
Section {
3028
ForEach(model.alertsModel, id: \.data.priceAlert.id) { alertModel in
@@ -35,12 +33,8 @@ public struct AssetPriceAlertsScene: View {
3533
}
3634
}
3735
}
38-
.overlay {
39-
if model.showEmptyState {
40-
EmptyContentView(model: model.emptyContentModel)
41-
}
42-
}
4336
.bindQuery(model.query)
37+
.bindQuery(model.priceQuery)
4438
.listSectionSpacing(.compact)
4539
.refreshable { await model.fetch() }
4640
.task { await model.fetch() }
@@ -65,6 +59,13 @@ public struct AssetPriceAlertsScene: View {
6559
.toast(message: $model.isPresentingToastMessage)
6660
}
6761

62+
private var autoAlertToggleView: some View {
63+
Toggle(isOn: model.isAutoAlertEnabledBinding) {
64+
ListAssetItemView(model: model.autoAlertItemModel)
65+
}
66+
.toggleStyle(AppToggleStyle())
67+
}
68+
6869
private func alertView(model: PriceAlertItemViewModel) -> some View {
6970
ListAssetItemView(model: model)
7071
.swipeActions(edge: .trailing) {
@@ -79,7 +80,7 @@ public struct AssetPriceAlertsScene: View {
7980
// MARK: - Actions
8081

8182
extension AssetPriceAlertsScene {
82-
func onDelete(alert: PriceAlert) {
83+
private func onDelete(alert: PriceAlert) {
8384
Task {
8485
await model.deletePriceAlert(priceAlert: alert)
8586
}

Features/PriceAlerts/Sources/ViewModels/AssetPriceAlertsViewModel.swift

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import PriceAlertService
88
import PrimitivesComponents
99
import Components
1010
import Style
11+
import Preferences
1112

1213
@Observable
1314
@MainActor
@@ -17,6 +18,7 @@ public final class AssetPriceAlertsViewModel: Sendable {
1718
let asset: Asset
1819

1920
public let query: ObservableQuery<PriceAlertsRequest>
21+
public let priceQuery: ObservableQuery<PriceRequest>
2022
var priceAlerts: [PriceAlertData] { query.value }
2123

2224
var isPresentingSetPriceAlert: Bool = false
@@ -31,14 +33,26 @@ public final class AssetPriceAlertsViewModel: Sendable {
3133
self.walletId = walletId
3234
self.asset = asset
3335
self.query = ObservableQuery(PriceAlertsRequest(assetId: asset.id), initialValue: [])
36+
self.priceQuery = ObservableQuery(PriceRequest(assetId: asset.id), initialValue: nil)
3437
}
3538

3639
var title: String { Localized.Settings.PriceAlerts.title }
37-
38-
var autoAlertModel: PriceAlertItemViewModel? {
39-
priceAlerts
40-
.first(where: { $0.priceAlert.type == .auto })
41-
.map { PriceAlertItemViewModel(data: $0) }
40+
41+
var autoAlertItemModel: PriceAlertItemViewModel {
42+
PriceAlertItemViewModel(data: PriceAlertData(
43+
asset: asset,
44+
price: priceQuery.value?.price,
45+
priceAlert: .default(for: asset.id, currency: Preferences.standard.currency)
46+
))
47+
}
48+
49+
var isAutoAlertEnabledBinding: Binding<Bool> {
50+
Binding(
51+
get: { self.priceAlerts.contains(where: { $0.priceAlert.type == .auto }) },
52+
set: { newValue in
53+
Task { await self.toggleAutoAlert(enabled: newValue) }
54+
}
55+
)
4256
}
4357

4458
var alertsModel: [PriceAlertItemViewModel] {
@@ -51,14 +65,6 @@ public final class AssetPriceAlertsViewModel: Sendable {
5165
])
5266
.map { PriceAlertItemViewModel(data: $0) }
5367
}
54-
55-
var showEmptyState: Bool {
56-
alertsModel.isEmpty && autoAlertModel == nil
57-
}
58-
59-
var emptyContentModel: EmptyContentTypeViewModel {
60-
EmptyContentTypeViewModel(type: .priceAlerts)
61-
}
6268
}
6369

6470
// MARK: - Business Logic
@@ -72,6 +78,21 @@ extension AssetPriceAlertsViewModel {
7278
}
7379
}
7480

81+
func toggleAutoAlert(enabled: Bool) async {
82+
let currency = Preferences.standard.currency
83+
do {
84+
if enabled {
85+
try await priceAlertService.add(priceAlert: .default(for: asset.id, currency: currency))
86+
try await priceAlertService.requestPermissions()
87+
try await priceAlertService.enablePriceAlerts()
88+
} else {
89+
try await priceAlertService.delete(priceAlerts: [.default(for: asset.id, currency: currency)])
90+
}
91+
} catch {
92+
debugLog("toggleAutoAlert error: \(error)")
93+
}
94+
}
95+
7596
func deletePriceAlert(priceAlert: PriceAlert) async {
7697
do {
7798
try await priceAlertService.delete(priceAlerts: [priceAlert])

Features/PriceAlerts/Tests/PriceAlertsTests/AssetPriceAlertsViewModelTests.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,7 @@ struct AssetPriceAlertsViewModelTests {
2323
model.query.value = [alert1, alert2, alert3, autoAlert]
2424

2525
#expect(model.alertsModel.map { $0.data } == [alert3, alert2, alert1])
26-
#expect(model.autoAlertModel?.data == autoAlert)
27-
}
28-
29-
@Test
30-
func showEmptyState() {
31-
let emptyModel = AssetPriceAlertsViewModel.mock()
32-
#expect(emptyModel.showEmptyState == true)
33-
34-
let modelWithAlerts = AssetPriceAlertsViewModel.mock()
35-
modelWithAlerts.query.value = [PriceAlertData.mock()]
36-
#expect(modelWithAlerts.showEmptyState == false)
26+
#expect(model.isAutoAlertEnabledBinding.wrappedValue == true)
3727
}
3828
}
3929

0 commit comments

Comments
 (0)