Add cheapest-favourite price-drop alerts
This commit is contained in:
@@ -31,6 +31,16 @@ struct FavouriteEntry: Identifiable, Codable, Equatable {
|
||||
var id: String { "\(fuel.rawValue)|\(station.id)" }
|
||||
}
|
||||
|
||||
/// Persisted baseline for the per-fuel cheapest-favourite price-drop alert.
|
||||
/// Stored separately from the favourites list so refresh-time comparison can
|
||||
/// survive app relaunches without immediately alerting on first fetch.
|
||||
struct FavouriteAlertSnapshot: Codable, Equatable {
|
||||
let fuel: FuelType
|
||||
let stationID: String
|
||||
let stationName: String
|
||||
let price: Double
|
||||
}
|
||||
|
||||
enum SortMode: String, Codable, CaseIterable, Identifiable {
|
||||
case cheapest
|
||||
case closest
|
||||
@@ -318,6 +328,9 @@ struct FuelStore {
|
||||
static let alertsRadiusKey = "fuelboard.alertsRadius" // Double km
|
||||
static let alertsFuelKey = "fuelboard.alertsFuel" // FuelType raw value
|
||||
static let alertsFollowSearchKey = "fuelboard.alertsFollowSearch" // Bool — alerts mirror the Stations-tab distance
|
||||
static let favouriteDropAlertsEnabledKey = "fuelboard.favouriteDropAlertsEnabled" // Bool
|
||||
static let favouriteDropAlertsFuelKey = "fuelboard.favouriteDropAlertsFuel" // FuelType raw value
|
||||
static let favouriteAlertSnapshotsKey = "fuelboard.favouriteAlertSnapshots" // [fuel: FavouriteAlertSnapshot] JSON
|
||||
static let liveActivityFollowSearchKey = "fuelboard.liveActivityFollowSearch" // Bool — Live Activity mirrors the Stations-tab distance
|
||||
static let debugModeKey = "fuelboard.debugMode" // Bool — hidden dev flag
|
||||
static let relayFallbackKey = "fuelboard.relayFallback" // Bool — dev-only relay fallback
|
||||
@@ -627,6 +640,24 @@ struct FuelStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// The cheapest favourite for one fuel, preserving the user's stored order
|
||||
/// on price ties. Used by the favourite price-drop alert so a tie does not
|
||||
/// flap between equally-priced favourites.
|
||||
static func cheapestFavourite(in favourites: [FavouriteEntry], fuel: FuelType) -> FavouriteEntry? {
|
||||
var best: FavouriteEntry?
|
||||
for entry in favourites where entry.fuel == fuel {
|
||||
guard let price = entry.station.prices[fuel] else { continue }
|
||||
guard let current = best, let currentPrice = current.station.prices[fuel] else {
|
||||
best = entry
|
||||
continue
|
||||
}
|
||||
if price < currentPrice {
|
||||
best = entry
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
/// Reorders ONE fuel's favourites within the global array (drag-and-drop in
|
||||
/// the Favourites tab). The moved fuel's block stays at its original
|
||||
/// position in the array; other fuels keep their relative order. The array
|
||||
@@ -710,6 +741,48 @@ struct FuelStore {
|
||||
saveString(enabled ? "1" : "0", service: alertsFollowSearchKey)
|
||||
}
|
||||
|
||||
// MARK: Favourite price-drop alerts
|
||||
|
||||
static func loadFavouriteDropAlertsEnabled() -> Bool {
|
||||
loadString(service: favouriteDropAlertsEnabledKey) == "1"
|
||||
}
|
||||
|
||||
static func saveFavouriteDropAlertsEnabled(_ enabled: Bool) {
|
||||
saveString(enabled ? "1" : "0", service: favouriteDropAlertsEnabledKey)
|
||||
}
|
||||
|
||||
static func loadFavouriteDropAlertsFuel() -> FuelType {
|
||||
if let raw = loadString(service: favouriteDropAlertsFuelKey), let fuel = FuelType(rawValue: raw) {
|
||||
return fuel
|
||||
}
|
||||
return .e10
|
||||
}
|
||||
|
||||
static func saveFavouriteDropAlertsFuel(_ fuel: FuelType) {
|
||||
saveString(fuel.rawValue, service: favouriteDropAlertsFuelKey)
|
||||
}
|
||||
|
||||
static func loadFavouriteAlertSnapshots() -> [FuelType: FavouriteAlertSnapshot] {
|
||||
if let data = keychainData(service: favouriteAlertSnapshotsKey),
|
||||
let stored = try? JSONDecoder().decode([String: FavouriteAlertSnapshot].self, from: data) {
|
||||
var result: [FuelType: FavouriteAlertSnapshot] = [:]
|
||||
for (key, value) in stored {
|
||||
if let fuel = FuelType(rawValue: key) {
|
||||
result[fuel] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return [:]
|
||||
}
|
||||
|
||||
static func saveFavouriteAlertSnapshots(_ snapshots: [FuelType: FavouriteAlertSnapshot]) {
|
||||
let keyed = Dictionary(uniqueKeysWithValues: snapshots.map { ($0.key.rawValue, $0.value) })
|
||||
if let data = try? JSONEncoder().encode(keyed) {
|
||||
writeKeychain(data: data, service: favouriteAlertSnapshotsKey)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Live Activity
|
||||
|
||||
static func loadLiveActivityEnabled() -> Bool {
|
||||
|
||||
Reference in New Issue
Block a user