From 2b44dcd812613c7d733d870026b8398ce7a6457b Mon Sep 17 00:00:00 2001 From: airkjw Date: Thu, 11 Jun 2026 23:30:22 +0900 Subject: [PATCH] fix: journey map season-complete styling, scroll callback guard, palette constant - Guard addPostFrameCallback with !_autoScrolled so it only fires once per widget lifetime instead of on every LayoutBuilder rebuild. - Derive seasonComplete flag; pass it into _node so the last stage uses gold "done" styling (not "current/next") when the season is fully 3-starred. - Extract const Color(0xFF232B4A) to GamePalette.lockedNode. - Remove warnIfMissed: false from tap call in season_map_screen_test; ensureVisible already guarantees hit-testing succeeds (confirmed: test still passes cleanly). Co-Authored-By: Claude Sonnet 4.6 --- lib/ui/screens/season_map_screen.dart | 17 +++++++++++------ lib/ui/theme/palette.dart | 1 + test/ui/season_map_screen_test.dart | 5 +---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/ui/screens/season_map_screen.dart b/lib/ui/screens/season_map_screen.dart index 8d86014..4776bb0 100644 --- a/lib/ui/screens/season_map_screen.dart +++ b/lib/ui/screens/season_map_screen.dart @@ -65,6 +65,8 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> { final ids = [for (final stage in pack.stages) stage.id]; final unlocked = repo.highestUnlockedIndex(pack.seasonId, ids); final totalStars = repo.totalStars(pack.seasonId); + final seasonComplete = totalStars == pack.stages.length * 3 && + pack.stages.isNotEmpty; final locale = Localizations.localeOf(context).languageCode; final colors = ThemeColors(pack.theme); @@ -78,9 +80,11 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> { builder: (context, constraints) { final layout = MapLayout(width: constraints.maxWidth); final count = pack.stages.length; - WidgetsBinding.instance.addPostFrameCallback((_) => - _autoScrollTo( - layout, unlocked, count, constraints.maxHeight)); + if (!_autoScrolled) { + WidgetsBinding.instance.addPostFrameCallback((_) => + _autoScrollTo( + layout, unlocked, count, constraints.maxHeight)); + } return SingleChildScrollView( controller: _scroll, reverse: true, @@ -104,6 +108,7 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> { unlocked, repo.progressFor(pack.seasonId, ids[i])?.stars ?? 0, colors, + seasonComplete, ), ], ), @@ -166,9 +171,9 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> { } Widget _node(BuildContext context, MapLayout layout, int i, int count, - int unlocked, int stars, ThemeColors colors) { + int unlocked, int stars, ThemeColors colors, bool seasonComplete) { final center = layout.nodeCenter(i, count); - final isCurrent = i == unlocked; + final isCurrent = i == unlocked && !seasonComplete; final isUnlocked = i <= unlocked; final size = isCurrent ? 64.0 : 52.0; @@ -213,7 +218,7 @@ class _JourneyMapState extends ConsumerState<_JourneyMap> { ], ) : null, - color: isUnlocked ? null : const Color(0xFF232B4A), + color: isUnlocked ? null : GamePalette.lockedNode, boxShadow: isCurrent ? [ BoxShadow( diff --git a/lib/ui/theme/palette.dart b/lib/ui/theme/palette.dart index 12620ea..9a2b8b1 100644 --- a/lib/ui/theme/palette.dart +++ b/lib/ui/theme/palette.dart @@ -22,6 +22,7 @@ class GamePalette { static Color tile(int colorId) => tileColors[colorId % tileColors.length]; + static const lockedNode = Color(0xFF232B4A); static const gem = Color(0xFF7CF5FF); static const ghostLegal = Color(0x66FFFFFF); static const ghostIllegal = Color(0x55FF5252); diff --git a/test/ui/season_map_screen_test.dart b/test/ui/season_map_screen_test.dart index ed5bf9f..c0a19f2 100644 --- a/test/ui/season_map_screen_test.dart +++ b/test/ui/season_map_screen_test.dart @@ -89,10 +89,7 @@ void main() { // Stage 1 is starred, so stage 2 (index 1) is unlocked and playable. // Ensure the node is visible before tapping. await tester.ensureVisible(find.byKey(const Key('stage_node_1'))); - await tester.tap( - find.byKey(const Key('stage_node_1')), - warnIfMissed: false, - ); + await tester.tap(find.byKey(const Key('stage_node_1'))); await tester.pumpAndSettle(); expect(find.byType(GameScreen), findsOneWidget);