From 94e62d3e4111009a51d6b3a9a1f456857d6c8ac4 Mon Sep 17 00:00:00 2001 From: airkjw Date: Fri, 12 Jun 2026 07:18:42 +0900 Subject: [PATCH] feat: redesigned home with adventure/classic entries and endless best MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add adventure/classic l10n keys; rewrite HomeScreen with SeasonBackground, 2×2 glossy logo mark, Adventure→SeasonMap + Classic→endless GameScreen buttons, and conditional best-score caption. Update widget_test assertions accordingly. Co-Authored-By: Claude Sonnet 4.6 --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_ko.arb | 4 +- lib/ui/screens/home_screen.dart | 167 ++++++++++++++++++++++++-------- test/widget_test.dart | 3 +- 4 files changed, 133 insertions(+), 45 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 75a77bd..d6efbb6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -52,5 +52,7 @@ } } }, - "newBest": "NEW BEST!" + "newBest": "NEW BEST!", + "adventure": "Adventure", + "classic": "Classic" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 2cb4038..0afe322 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -24,5 +24,7 @@ "tutorialHud": "이동 횟수가 끝나기 전에 목표를 달성하세요. 이제 직접!", "gameOver": "게임 오버", "bestScore": "최고 {score}", - "newBest": "신기록!" + "newBest": "신기록!", + "adventure": "어드벤처", + "classic": "클래식" } diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index d91d944..18e532f 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -1,64 +1,147 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../game/models/season.dart'; +import '../../game/models/stage.dart'; import '../../l10n/gen/app_localizations.dart'; import '../../state/providers.dart'; +import '../widgets/season_background.dart'; +import 'game_screen.dart'; import 'season_map_screen.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); + static const _logoColors = [ + Color(0xFFFF7EB3), + Color(0xFFFFD166), + Color(0xFF6FCDF5), + Color(0xFF7EDB9C), + ]; + @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; final streak = ref.watch(streakProvider); + final best = ref.watch(endlessBestProvider); + return Scaffold( - body: SafeArea( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - l10n.appTitle, - style: Theme.of(context).textTheme.displaySmall?.copyWith( - fontWeight: FontWeight.bold, + backgroundColor: Colors.transparent, + body: Stack( + fit: StackFit.expand, + children: [ + const SeasonBackground(theme: SeasonTheme.fallback), + SafeArea( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _logoMark(), + const SizedBox(height: 18), + Text( + l10n.appTitle, + style: Theme.of(context) + .textTheme + .displaySmall + ?.copyWith(fontWeight: FontWeight.w900), + ), + if (streak.current > 0) ...[ + const SizedBox(height: 10), + Chip( + avatar: const Icon( + Icons.local_fire_department, + color: Colors.deepOrange, + size: 20, + ), + label: Text( + '${streak.current}', + style: Theme.of(context).textTheme.titleMedium, + ), ), - ), - if (streak.current > 0) ...[ - const SizedBox(height: 12), - Chip( - avatar: const Icon( - Icons.local_fire_department, - color: Colors.deepOrange, - size: 20, - ), - label: Text( - '${streak.current}', - style: Theme.of(context).textTheme.titleMedium, - ), - ), - ], - const SizedBox(height: 48), - FilledButton( - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 48, - vertical: 16, - ), - textStyle: Theme.of(context).textTheme.titleLarge, - ), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => const SeasonMapScreen(), + ], + const SizedBox(height: 44), + FilledButton( + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 56, vertical: 18), + textStyle: Theme.of(context).textTheme.titleLarge, ), - ); - }, - child: Text(l10n.play), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const SeasonMapScreen()), + ), + child: Text(l10n.adventure), + ), + const SizedBox(height: 14), + OutlinedButton( + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 40, vertical: 14), + textStyle: Theme.of(context).textTheme.titleMedium, + ), + onPressed: () { + ref.read(gameSessionProvider.notifier).startStage( + StageConfig.endless( + seed: DateTime.now().millisecondsSinceEpoch, + ), + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const GameScreen()), + ); + }, + child: Text(l10n.classic), + ), + if (best > 0) ...[ + const SizedBox(height: 10), + Text( + l10n.bestScore(best), + style: TextStyle( + color: Colors.white.withValues(alpha: 0.55), + ), + ), + ], + ], ), - ], + ), ), - ), + ], + ), + ); + } + + Widget _logoMark() { + return SizedBox( + width: 96, + height: 96, + child: GridView.count( + crossAxisCount: 2, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + physics: const NeverScrollableScrollPhysics(), + children: [ + for (final color in _logoColors) + DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(11), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color.lerp(color, Colors.white, 0.28)!, + color, + Color.lerp(color, Colors.black, 0.22)!, + ], + ), + boxShadow: [ + BoxShadow( + color: color.withValues(alpha: 0.45), + blurRadius: 14, + ), + ], + ), + ), + ], ), ); } diff --git a/test/widget_test.dart b/test/widget_test.dart index 8183482..fe2671a 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -23,6 +23,7 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Block Seasons'), findsOneWidget); - expect(find.text('Play'), findsOneWidget); + expect(find.text('Adventure'), findsOneWidget); + expect(find.text('Classic'), findsOneWidget); }); }