76 lines
2.5 KiB
Dart
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();
|
|
}
|
|
}
|