From d985d40f09dc640e317e379ed6db06e7feb92d7f Mon Sep 17 00:00:00 2001 From: airkjw Date: Thu, 11 Jun 2026 21:25:55 +0900 Subject: [PATCH] feat: persist tutorial completion and endless best score Co-Authored-By: Claude Sonnet 4.6 --- lib/data/save_repository.dart | 21 +++++++++++++++++ test/data/save_repository_test.dart | 36 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/lib/data/save_repository.dart b/lib/data/save_repository.dart index 0543a00..d9ab677 100644 --- a/lib/data/save_repository.dart +++ b/lib/data/save_repository.dart @@ -34,6 +34,11 @@ class SaveRepository { lastYmd: streak['lastYmd'] as String?, ); } + _tutorialDone = + (json['flags'] as Map?)?['tutorialDone'] as bool? ?? + false; + _endlessBest = + (json['endless'] as Map?)?['best'] as int? ?? 0; } } @@ -45,8 +50,22 @@ class SaveRepository { final SharedPreferences _prefs; final Map _progress = {}; StreakState _streak = StreakState.initial; + bool _tutorialDone = false; + int _endlessBest = 0; StreakState get streak => _streak; + bool get tutorialDone => _tutorialDone; + int get endlessBest => _endlessBest; + + Future markTutorialDone() { + _tutorialDone = true; + return _flush(); + } + + Future recordEndlessScore(int score) { + if (score > _endlessBest) _endlessBest = score; + return _flush(); + } Future saveStreak(StreakState streak) { _streak = streak; @@ -111,6 +130,8 @@ class SaveRepository { 'best': _streak.best, 'lastYmd': _streak.lastYmd, }, + 'flags': {'tutorialDone': _tutorialDone}, + 'endless': {'best': _endlessBest}, }), ); } diff --git a/test/data/save_repository_test.dart b/test/data/save_repository_test.dart index 7b2ec3c..2e83fd0 100644 --- a/test/data/save_repository_test.dart +++ b/test/data/save_repository_test.dart @@ -67,4 +67,40 @@ void main() { expect(second.progressFor('season_001', 's1')!.stars, 3); expect(second.progressFor('season_001', 's1')!.bestScore, 777); }); + + group('tutorial flag and endless best', () { + test('defaults: tutorial not done, endless best 0', () async { + SharedPreferences.setMockInitialValues({}); + final repo = SaveRepository(await SharedPreferences.getInstance()); + expect(repo.tutorialDone, isFalse); + expect(repo.endlessBest, 0); + }); + + test('markTutorialDone persists across reload', () async { + SharedPreferences.setMockInitialValues({}); + final prefs = await SharedPreferences.getInstance(); + await SaveRepository(prefs).markTutorialDone(); + expect(SaveRepository(prefs).tutorialDone, isTrue); + }); + + test('recordEndlessScore keeps the max', () async { + SharedPreferences.setMockInitialValues({}); + final prefs = await SharedPreferences.getInstance(); + final repo = SaveRepository(prefs); + await repo.recordEndlessScore(500); + await repo.recordEndlessScore(300); + expect(repo.endlessBest, 500); + expect(SaveRepository(prefs).endlessBest, 500); + }); + + test('legacy blob without new keys still loads', () async { + SharedPreferences.setMockInitialValues({ + 'save_v1': + '{"saveVersion":1,"progress":{},"streak":{"current":0,"best":0,"lastYmd":null}}', + }); + final repo = SaveRepository(await SharedPreferences.getInstance()); + expect(repo.tutorialDone, isFalse); + expect(repo.endlessBest, 0); + }); + }); }