Files
BlockSeasons/lib/data/save_repository.dart
T
airkjw 7bc26447f7 Wire season flow: map screen, progress save, win recording, next stage
SaveRepository (versioned JSON over prefs) with best-result merging and
unlock walking; ContentRepository loads the bundled pack; SeasonFlow/
Progress notifiers orchestrate stage start -> win record -> advance.
Season map grid with stars/locks, Home -> Map -> Game navigation,
close button, next-stage action on the win overlay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:04:45 +09:00

94 lines
2.8 KiB
Dart

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<String, dynamic>;
final progress = json['progress'] as Map<String, dynamic>? ?? {};
for (final entry in progress.entries) {
final value = entry.value as Map<String, dynamic>;
_progress[entry.key] = StageProgress(
stars: value['stars'] as int,
bestScore: value['bestScore'] as int,
);
}
}
}
static const _key = 'save_v1';
static Future<SaveRepository> open() async =>
SaveRepository(await SharedPreferences.getInstance());
final SharedPreferences _prefs;
final Map<String, StageProgress> _progress = {};
static String _id(String seasonId, String stageId) => '$seasonId/$stageId';
/// Immutable copy of all progress, keyed `seasonId/stageId`.
Map<String, StageProgress> snapshot() => Map.unmodifiable(_progress);
StageProgress? progressFor(String seasonId, String stageId) =>
_progress[_id(seasonId, stageId)];
Future<void> 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<String> 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<void> _flush() => _prefs.setString(
_key,
jsonEncode({
'saveVersion': 1,
'progress': {
for (final entry in _progress.entries)
entry.key: {
'stars': entry.value.stars,
'bestScore': entry.value.bestScore,
},
},
}),
);
}