From 221ea8346ebdefac680745483c0e5b6c72c6786a Mon Sep 17 00:00:00 2001 From: airkjw Date: Thu, 18 Jun 2026 12:08:17 +0900 Subject: [PATCH] feat(save): persist booster inventory --- lib/data/save_repository.dart | 25 ++++++++++++++ test/data/save_repository_booster_test.dart | 37 +++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 test/data/save_repository_booster_test.dart diff --git a/lib/data/save_repository.dart b/lib/data/save_repository.dart index 26978ba..181d726 100644 --- a/lib/data/save_repository.dart +++ b/lib/data/save_repository.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; +import '../game/models/booster.dart'; import 'streak.dart'; class StageProgress { @@ -51,6 +52,10 @@ class SaveRepository { _reviewRequested = (json['flags'] as Map?)?['reviewRequested'] as bool? ?? false; + final boosters = json['boosters'] as Map? ?? {}; + for (final t in BoosterType.values) { + _boosters[t] = boosters[t.name] as int? ?? 0; + } } } @@ -68,6 +73,9 @@ class SaveRepository { bool _soundEnabled = true; bool _musicEnabled = true; bool _reviewRequested = false; + final Map _boosters = { + for (final t in BoosterType.values) t: 0, + }; StreakState get streak => _streak; bool get tutorialDone => _tutorialDone; @@ -109,6 +117,22 @@ class SaveRepository { return _flush(); } + int boosterCount(BoosterType type) => _boosters[type] ?? 0; + + Future grantBooster(BoosterType type, [int n = 1]) { + _boosters[type] = (_boosters[type] ?? 0) + n; + return _flush(); + } + + /// Spends one booster. Returns false (and changes nothing) when none are left. + Future consumeBooster(BoosterType type) async { + final have = _boosters[type] ?? 0; + if (have <= 0) return false; + _boosters[type] = have - 1; + await _flush(); + return true; + } + Future recordEndlessScore(int score) { if (score > _endlessBest) _endlessBest = score; return _flush(); @@ -185,6 +209,7 @@ class SaveRepository { 'reviewRequested': _reviewRequested, }, 'endless': {'best': _endlessBest}, + 'boosters': {for (final t in BoosterType.values) t.name: _boosters[t]}, }), ); } diff --git a/test/data/save_repository_booster_test.dart b/test/data/save_repository_booster_test.dart new file mode 100644 index 0000000..95f4dd7 --- /dev/null +++ b/test/data/save_repository_booster_test.dart @@ -0,0 +1,37 @@ +import 'package:block_seasons/data/save_repository.dart'; +import 'package:block_seasons/game/models/booster.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + Future fresh() async { + SharedPreferences.setMockInitialValues({}); + return SaveRepository(await SharedPreferences.getInstance()); + } + + test('booster counts start at zero', () async { + final repo = await fresh(); + for (final t in BoosterType.values) { + expect(repo.boosterCount(t), 0); + } + }); + + test('grantBooster adds and persists across reload', () async { + final repo = await fresh(); + await repo.grantBooster(BoosterType.hammer, 2); + expect(repo.boosterCount(BoosterType.hammer), 2); + + final reloaded = SaveRepository(await SharedPreferences.getInstance()); + expect(reloaded.boosterCount(BoosterType.hammer), 2); + }); + + test('consumeBooster decrements and returns false at zero', () async { + final repo = await fresh(); + await repo.grantBooster(BoosterType.shuffle, 1); + expect(await repo.consumeBooster(BoosterType.shuffle), isTrue); + expect(repo.boosterCount(BoosterType.shuffle), 0); + expect(await repo.consumeBooster(BoosterType.shuffle), isFalse); + }); +}