Files
BlockSeasons/lib/data/content_repository.dart
T

76 lines
2.5 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import '../game/models/season.dart';
import 'remote/content_downloader.dart';
/// Resolves season content: bundled season 1 is always available offline;
/// remotely synced packs in [cacheDir] extend or override it. Load errors on
/// any single pack never break the list — worst case the player sees the
/// bundled content.
class ContentRepository {
ContentRepository({this.cacheDir, ContentDownloader? downloader})
: _downloader = downloader;
static const bundledSeasonIds = ['season_001'];
final Directory? cacheDir;
final ContentDownloader? _downloader;
bool _syncedThisSession = false;
Future<SeasonPack> loadBundledSeason(String seasonId) async {
final raw =
await rootBundle.loadString('assets/seasons/$seasonId/pack.json');
return SeasonPack.fromJson(jsonDecode(raw) as Map<String, dynamic>);
}
Future<SeasonPack?> _loadCachedSeason(Directory dir) async {
try {
final file = File('${dir.path}/pack.json');
if (!await file.exists()) return null;
return SeasonPack.fromJson(
jsonDecode(await file.readAsString()) as Map<String, dynamic>);
} catch (_) {
return null; // Corrupt or future-schema pack: ignore.
}
}
/// All playable seasons, sorted by seasonId. Cached packs win over the
/// bundled copy of the same season (they may carry balance fixes).
Future<List<SeasonPack>> availableSeasons() async {
final byId = <String, SeasonPack>{};
for (final id in bundledSeasonIds) {
try {
byId[id] = await loadBundledSeason(id);
} catch (_) {
// Bundled pack should never fail; if it does, skip rather than crash.
}
}
final seasonsDir =
cacheDir == null ? null : Directory('${cacheDir!.path}/seasons');
if (seasonsDir != null && await seasonsDir.exists()) {
await for (final entry in seasonsDir.list()) {
if (entry is! Directory) continue;
final pack = await _loadCachedSeason(entry);
if (pack != null) byId[pack.seasonId] = pack;
}
}
final list = byId.values.toList()
..sort((a, b) => a.seasonId.compareTo(b.seasonId));
return list;
}
/// Fire-once-per-session remote sync. Returns true when new content
/// arrived (callers then re-read [availableSeasons]).
Future<bool> refresh() async {
final downloader = _downloader;
if (downloader == null || _syncedThisSession) return false;
_syncedThisSession = true;
return downloader.sync();
}
}