banner: top-floating; fix offline-trigger cache; onboarding prompt timing

- Banner floats at the very top (over the nav/title area), never pushing the
  content below and staying clear of the list/pill.
- Offline banner now fires on refresh in airplane mode / no data: the mirror
  live chain bypassed the HTTP cache (+10s timeout) so an offline fetch really
  fails instead of silently re-serving a cached pointer+dump as a 'success'
  (which kept dataStatus .live and hid the banner).
- Onboarding permissions fire at the Continue/Allow tap BEFORE advancing, so
  the system prompt appears after the page is read and never covers the next
  page's animation (grant auto-advances).
This commit is contained in:
FuelBoard Contributor
2026-08-19 10:17:24 +01:00
parent 8936643e0e
commit cfd435d12a
4 changed files with 37 additions and 17 deletions
+11 -3
View File
@@ -216,10 +216,18 @@ enum FuelHistoryStore {
// MARK: Network
/// The mirror pointer used for the empty-state hint ("first snapshot
/// landed "). Non-fatal: nil just means no hint.
static func fetchLatest(base: URL = mirrorBase,
/// landed "). Non-fatal: nil just means no hint. Deliberately bypasses
/// the HTTP cache (reloadIgnoringLocalCacheData) so an OFFLINE fetch
/// really fails instead of silently re-serving a cached pointer as a
/// success otherwise "no network" would look like fresh data and the
/// offline banner would never fire. A short timeout surfaces the failure
/// (and the banner) quickly on a connected-but-dead network.
static func fetchLatest(base: URL = mirrorBase, timeout: TimeInterval = 10,
session: URLSession = .shared) async -> MirrorLatest? {
guard let (data, response) = try? await session.data(from: latestFileURL(base: base)),
var req = URLRequest(url: latestFileURL(base: base))
req.cachePolicy = .reloadIgnoringLocalCacheData
req.timeoutInterval = timeout
guard let (data, response) = try? await session.data(for: req),
(response as? HTTPURLResponse)?.statusCode == 200,
let latest = try? JSONDecoder().decode(MirrorLatest.self, from: data) else {
return nil
+7 -3
View File
@@ -96,9 +96,13 @@ struct MirrorFuelProvider: FuelPriceProviding {
if Self.canReuseCache(cachedDay: cache?.day, latestDay: day), let cached = cache?.data {
data = cached
} else {
let (fetched, response) = try await URLSession.shared.data(
from: FuelHistoryStore.historyFileURL(day: day, base: baseURL)
)
// Bypass the HTTP cache + short timeout: an offline refresh must
// FAIL ( offline banner), never re-serve a stale cached dump as
// a "successful" live fetch.
var req = URLRequest(url: FuelHistoryStore.historyFileURL(day: day, base: baseURL))
req.cachePolicy = .reloadIgnoringLocalCacheData
req.timeoutInterval = 10
let (fetched, response) = try await URLSession.shared.data(for: req)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw FuelProviderError.mirrorUnavailable
}