import 'dart:typed_data'; import 'package:flutter/services.dart' show AssetBundle, rootBundle; import 'radar_manifest.dart'; import 'radar_source.dart'; /// Radar frames bundled in the app, for demo mode and for tests. /// /// This is a real adapter, not test scaffolding. It is what lets the app run on /// a fresh clone with no network and no credentials, which in turn means the /// timeline, the prefetching, the cache eviction and the degraded states are /// all exercisable offline. /// /// The assets are generated by `tool/generate_mock_frames.py` and deliberately /// carry the same manifest shape the Python worker publishes, so nothing here /// is a special case. class MockRadarSource implements RadarSource { MockRadarSource({this.bundle, this.manifestAsset = defaultManifestAsset}); static const String defaultManifestAsset = 'assets/mock/manifest.json'; /// Overridden in tests; null means the real asset bundle. final AssetBundle? bundle; /// Asset path of the manifest; frame paths are resolved relative to it. final String manifestAsset; AssetBundle get _assets => bundle ?? rootBundle; /// Parsed once and reused: the assets cannot change while the app runs, and /// re-parsing on every timeline tick would be pure waste. RadarManifest? _cached; @override Future getLatestManifest() async { final cached = _cached; if (cached != null) return cached; final String source; try { source = await _assets.loadString(manifestAsset); } on Object catch (error) { throw RadarUnavailableException( 'mock manifest asset $manifestAsset could not be read', cause: error, ); } try { return _cached = RadarManifest.parse(source); } on FormatException catch (error) { // A malformed bundled asset is a packaging bug, but it still reaches the // caller as unavailability so demo mode degrades like any other source. throw RadarUnavailableException( 'mock manifest asset $manifestAsset is malformed', cause: error, ); } } @override Future> getFrames() async => (await getLatestManifest()).frames; @override Future loadFrameBytes(RadarFrame frame) async { final key = resolveAssetPath(frame); try { final data = await _assets.load(key); return data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); } on Object catch (error) { throw RadarUnavailableException( 'mock frame asset $key could not be read', cause: error, ); } } /// Resolves a frame's manifest-relative path against [manifestAsset]. String resolveAssetPath(RadarFrame frame) { final separator = manifestAsset.lastIndexOf('/'); if (separator < 0) return frame.path; return '${manifestAsset.substring(0, separator + 1)}${frame.path}'; } }