diff --git a/lib/state/providers.dart b/lib/state/providers.dart index c7eaeac..c4b6003 100644 --- a/lib/state/providers.dart +++ b/lib/state/providers.dart @@ -44,6 +44,16 @@ final seasonsProvider = FutureProvider>( (ref) => ref.read(contentRepositoryProvider).availableSeasons(), ); +/// One background content sync per app session. Home listens and refreshes +/// the season list when new packs arrived. +final seasonRefreshProvider = FutureProvider( + (ref) => ref.read(contentRepositoryProvider).refresh(), +); + +/// The season players land in by default: the newest available. +/// (availableSeasons is sorted by seasonId ascending.) +SeasonPack activeSeason(List seasons) => seasons.last; + final streakProvider = NotifierProvider( StreakNotifier.new, ); diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index 47a6863..67396f7 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -21,6 +21,11 @@ class HomeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ref.listen(seasonRefreshProvider, (_, next) { + if (next is AsyncData && next.value == true) { + ref.invalidate(seasonsProvider); + } + }); final l10n = AppLocalizations.of(context)!; final streak = ref.watch(streakProvider); final best = ref.watch(endlessBestProvider); diff --git a/lib/ui/screens/season_map_screen.dart b/lib/ui/screens/season_map_screen.dart index 4776bb0..f73803b 100644 --- a/lib/ui/screens/season_map_screen.dart +++ b/lib/ui/screens/season_map_screen.dart @@ -21,7 +21,7 @@ class SeasonMapScreen extends ConsumerWidget { loading: () => const Scaffold(body: Center(child: CircularProgressIndicator())), error: (e, _) => Scaffold(body: Center(child: Text('$e'))), - data: (list) => _JourneyMap(pack: list.first), + data: (list) => _JourneyMap(pack: activeSeason(list)), ); } } diff --git a/lib/ui/screens/season_title_screen.dart b/lib/ui/screens/season_title_screen.dart index 02b5d5a..45c0600 100644 --- a/lib/ui/screens/season_title_screen.dart +++ b/lib/ui/screens/season_title_screen.dart @@ -63,7 +63,7 @@ class _SeasonTitleScreenState extends ConsumerState { _auto?.cancel(); _auto = Timer(const Duration(milliseconds: 1600), _go); } - final pack = list.first; + final pack = activeSeason(list); final locale = Localizations.localeOf(context).languageCode; final number = int.tryParse(pack.seasonId.split('_').last) ?? 1; return GestureDetector( diff --git a/test/state/season_refresh_test.dart b/test/state/season_refresh_test.dart new file mode 100644 index 0000000..5c41b9a --- /dev/null +++ b/test/state/season_refresh_test.dart @@ -0,0 +1,59 @@ +import 'package:block_seasons/data/content_repository.dart'; +import 'package:block_seasons/game/models/season.dart'; +import 'package:block_seasons/game/models/stage.dart'; +import 'package:block_seasons/state/providers.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class _FakeRepo extends ContentRepository { + _FakeRepo(this.result); + final bool result; + int calls = 0; + + @override + Future refresh() async { + calls++; + return result; + } +} + +SeasonPack _pack(String id) => SeasonPack( + schemaVersion: 1, + seasonId: id, + version: 1, + title: const {'en': 'Test Season', 'ko': '테스트 시즌'}, + theme: SeasonTheme.fallback, + stages: [ + StageConfig( + id: 's1', + seed: 1, + moveLimit: 10, + preset: const [], + objectives: const [], + stars: const StarThresholds(twoMovesLeft: 2, threeMovesLeft: 4), + generatorProfile: 'mid', + ), + ], + ); + +void main() { + test('seasonRefreshProvider runs refresh once and exposes the result', + () async { + final repo = _FakeRepo(true); + final container = ProviderContainer( + overrides: [contentRepositoryProvider.overrideWithValue(repo)], + ); + addTearDown(container.dispose); + + expect(await container.read(seasonRefreshProvider.future), isTrue); + // Re-reading does not re-run (FutureProvider caches). + expect(await container.read(seasonRefreshProvider.future), isTrue); + expect(repo.calls, 1); + }); + + test('activeSeason picks the newest by id', () { + final p1 = _pack('season_001'); + final p2 = _pack('season_002'); + expect(activeSeason([p1, p2]).seasonId, 'season_002'); + }); +}