import 'dart:typed_data'; import 'radar_manifest.dart'; /// A source of radar frames. /// /// The three implementations differ only in where the bytes come from, never in /// what they mean: all of them parse the same manifest document, so switching /// source is configuration rather than a separate code path. /// /// See `MockRadarSource` (offline demo), `DpcRadarSource` (live, via our CDN) /// and `ArpaRadarSource` (disabled stub). abstract interface class RadarSource { /// The most recent published index of frames. /// /// Throws [RadarUnavailableException] when the manifest cannot be reached or /// read. Callers are expected to fall back to the last good manifest rather /// than showing an empty map. Future getLatestManifest(); /// The frames of the latest manifest, ascending by timestamp. Future> getFrames(); /// The PNG bytes for [frame]. /// /// Throws [RadarUnavailableException] if that single frame is missing. A gap /// in the middle of a timeline is normal — the worker can be mid-publish — so /// callers hold the previous frame instead of blanking the map. Future loadFrameBytes(RadarFrame frame); } /// Radar data could not be obtained. /// /// Deliberately one exception type for every cause — offline, HTTP error, /// malformed document, adapter disabled — because the app's response is the /// same in all of them: keep showing the last good frame and say how old it is. /// The [cause] is kept for logging, not for branching. class RadarUnavailableException implements Exception { const RadarUnavailableException(this.message, {this.cause}); final String message; final Object? cause; @override String toString() => cause == null ? 'RadarUnavailableException: $message' : 'RadarUnavailableException: $message ($cause)'; }