feat: session content sync trigger and newest-season selection
Add seasonRefreshProvider (once-per-session FutureProvider) and activeSeason() helper; HomeScreen listens and invalidates seasonsProvider when new packs arrive; season_map_screen and season_title_screen switch from list.first to activeSeason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,16 @@ final seasonsProvider = FutureProvider<List<SeasonPack>>(
|
||||
(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<bool>(
|
||||
(ref) => ref.read(contentRepositoryProvider).refresh(),
|
||||
);
|
||||
|
||||
/// The season players land in by default: the newest available.
|
||||
/// (availableSeasons is sorted by seasonId ascending.)
|
||||
SeasonPack activeSeason(List<SeasonPack> seasons) => seasons.last;
|
||||
|
||||
final streakProvider = NotifierProvider<StreakNotifier, StreakState>(
|
||||
StreakNotifier.new,
|
||||
);
|
||||
|
||||
@@ -21,6 +21,11 @@ class HomeScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.listen(seasonRefreshProvider, (_, next) {
|
||||
if (next is AsyncData<bool> && next.value == true) {
|
||||
ref.invalidate(seasonsProvider);
|
||||
}
|
||||
});
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final streak = ref.watch(streakProvider);
|
||||
final best = ref.watch(endlessBestProvider);
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class _SeasonTitleScreenState extends ConsumerState<SeasonTitleScreen> {
|
||||
_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(
|
||||
|
||||
@@ -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<bool> 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');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user