import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; class StageProgress { const StageProgress({required this.stars, required this.bestScore}); final int stars; final int bestScore; } /// Versioned JSON-over-prefs persistence for progress. Total payload stays /// tiny (<100 KB), so a key-value blob beats a database here. class SaveRepository { SaveRepository(this._prefs) { final raw = _prefs.getString(_key); if (raw != null) { final json = jsonDecode(raw) as Map; final progress = json['progress'] as Map? ?? {}; for (final entry in progress.entries) { final value = entry.value as Map; _progress[entry.key] = StageProgress( stars: value['stars'] as int, bestScore: value['bestScore'] as int, ); } } } static const _key = 'save_v1'; static Future open() async => SaveRepository(await SharedPreferences.getInstance()); final SharedPreferences _prefs; final Map _progress = {}; static String _id(String seasonId, String stageId) => '$seasonId/$stageId'; /// Immutable copy of all progress, keyed `seasonId/stageId`. Map snapshot() => Map.unmodifiable(_progress); StageProgress? progressFor(String seasonId, String stageId) => _progress[_id(seasonId, stageId)]; Future recordResult({ required String seasonId, required String stageId, required int stars, required int score, }) async { final id = _id(seasonId, stageId); final old = _progress[id]; _progress[id] = StageProgress( stars: old == null || stars > old.stars ? stars : old.stars, bestScore: old == null || score > old.bestScore ? score : old.bestScore, ); await _flush(); } int totalStars(String seasonId) { var total = 0; for (final entry in _progress.entries) { if (entry.key.startsWith('$seasonId/')) total += entry.value.stars; } return total; } /// Index of the furthest playable stage: the first without a star, or the /// last stage when everything is starred. int highestUnlockedIndex(String seasonId, List stageIds) { for (var i = 0; i < stageIds.length; i++) { final progress = progressFor(seasonId, stageIds[i]); if (progress == null || progress.stars == 0) return i; } return stageIds.length - 1; } Future _flush() => _prefs.setString( _key, jsonEncode({ 'saveVersion': 1, 'progress': { for (final entry in _progress.entries) entry.key: { 'stars': entry.value.stars, 'bestScore': entry.value.bestScore, }, }, }), ); }