diff --git a/lib/src/app.dart b/lib/src/app.dart index d902bdd..5e32344 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -6,7 +6,8 @@ import 'package:asciineverdie/src/core/audio/audio_service.dart'; import 'package:asciineverdie/src/core/engine/ad_service.dart'; import 'package:asciineverdie/src/core/engine/debug_settings_service.dart'; import 'package:asciineverdie/src/core/engine/iap_service.dart'; -import 'package:asciineverdie/src/shared/retro_colors.dart'; +import 'package:asciineverdie/src/app_theme.dart'; +import 'package:asciineverdie/src/splash_screen.dart'; import 'package:asciineverdie/src/core/engine/game_mutations.dart'; import 'package:asciineverdie/src/core/engine/progress_service.dart'; import 'package:asciineverdie/src/core/engine/reward_service.dart'; @@ -221,140 +222,6 @@ class _AskiiNeverDieAppState extends State } } - /// 앱 테마 (Dark Fantasy 스타일) - ThemeData get _theme => ThemeData( - colorScheme: RetroColors.darkColorScheme, - scaffoldBackgroundColor: RetroColors.deepBrown, - useMaterial3: true, - // 카드/다이얼로그 레트로 배경 - cardColor: RetroColors.darkBrown, - dialogTheme: const DialogThemeData( - backgroundColor: Color(0xFF24283B), - titleTextStyle: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 15, - color: Color(0xFFE0AF68), - ), - ), - // 앱바 레트로 스타일 - appBarTheme: const AppBarTheme( - backgroundColor: Color(0xFF24283B), - foregroundColor: Color(0xFFC0CAF5), - titleTextStyle: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 15, - color: Color(0xFFE0AF68), - ), - ), - // 버튼 테마 (inherit: false로 애니메이션 lerp 오류 방지) - filledButtonTheme: FilledButtonThemeData( - style: FilledButton.styleFrom( - backgroundColor: const Color(0xFF3D4260), - foregroundColor: const Color(0xFFC0CAF5), - textStyle: const TextStyle( - inherit: false, - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFE0AF68), - side: const BorderSide(color: Color(0xFFE0AF68), width: 2), - textStyle: const TextStyle( - inherit: false, - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFE0AF68), - ), - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: const Color(0xFFC0CAF5), - textStyle: const TextStyle( - inherit: false, - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - ), - ), - // 텍스트 테마 - textTheme: const TextTheme( - headlineLarge: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 20, - color: Color(0xFFE0AF68), - ), - headlineMedium: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 16, - color: Color(0xFFE0AF68), - ), - headlineSmall: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 15, - color: Color(0xFFE0AF68), - ), - titleLarge: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 15, - color: Color(0xFFC0CAF5), - ), - titleMedium: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - titleSmall: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - bodyLarge: TextStyle(fontSize: 18, color: Color(0xFFC0CAF5)), - bodyMedium: TextStyle(fontSize: 17, color: Color(0xFFC0CAF5)), - bodySmall: TextStyle(fontSize: 15, color: Color(0xFFC0CAF5)), - labelLarge: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - labelMedium: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - labelSmall: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 13, - color: Color(0xFFC0CAF5), - ), - ), - // 칩 테마 - chipTheme: const ChipThemeData( - backgroundColor: Color(0xFF2A2E3F), - labelStyle: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 14, - color: Color(0xFFC0CAF5), - ), - side: BorderSide(color: Color(0xFF545C7E)), - ), - // 리스트 타일 테마 - listTileTheme: const ListTileThemeData( - textColor: Color(0xFFC0CAF5), - iconColor: Color(0xFFE0AF68), - ), - // 프로그레스 인디케이터 - progressIndicatorTheme: const ProgressIndicatorThemeData( - color: Color(0xFFE0AF68), - linearTrackColor: Color(0xFF3B4261), - ), - ); - @override Widget build(BuildContext context) { return MaterialApp( @@ -363,7 +230,7 @@ class _AskiiNeverDieAppState extends State localizationsDelegates: L10n.localizationsDelegates, supportedLocales: L10n.supportedLocales, locale: _locale, // 사용자 선택 로케일 (null이면 시스템 기본값) - theme: _theme, + theme: buildAppTheme(), navigatorObservers: [_routeObserver], builder: (context, child) { // 현재 로케일을 게임 텍스트 l10n 시스템에 동기화 @@ -382,7 +249,7 @@ class _AskiiNeverDieAppState extends State Widget _buildHomeScreen() { // 세이브 확인 중이면 로딩 스플래시 표시 if (_isCheckingSave) { - return const _SplashScreen(); + return const SplashScreen(); } return FrontScreen( @@ -591,133 +458,3 @@ class _AskiiNeverDieAppState extends State } } -/// 스플래시 화면 (세이브 파일 확인 중) - 레트로 스타일 -class _SplashScreen extends StatelessWidget { - const _SplashScreen(); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: RetroColors.deepBrown, - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // 타이틀 로고 - Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - decoration: BoxDecoration( - color: RetroColors.panelBg, - border: Border.all(color: RetroColors.gold, width: 3), - ), - child: Column( - children: [ - // 아이콘 - const Icon( - Icons.auto_awesome, - size: 32, - color: RetroColors.gold, - ), - const SizedBox(height: 12), - // 타이틀 - const Text( - 'ASCII', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 22, - color: RetroColors.gold, - shadows: [ - Shadow( - color: RetroColors.goldDark, - offset: Offset(2, 2), - ), - ], - ), - ), - const SizedBox(height: 4), - const Text( - 'NEVER DIE', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 16, - color: RetroColors.cream, - shadows: [ - Shadow(color: RetroColors.brown, offset: Offset(1, 1)), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 32), - // 레트로 로딩 바 - SizedBox(width: 160, child: _RetroLoadingBar()), - ], - ), - ), - ); - } -} - -/// 레트로 스타일 로딩 바 (애니메이션) -class _RetroLoadingBar extends StatefulWidget { - @override - State<_RetroLoadingBar> createState() => _RetroLoadingBarState(); -} - -class _RetroLoadingBarState extends State<_RetroLoadingBar> - with SingleTickerProviderStateMixin { - late AnimationController _controller; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: const Duration(milliseconds: 1500), - vsync: this, - )..repeat(); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - const segmentCount = 10; - - return AnimatedBuilder( - animation: _controller, - builder: (context, child) { - // 웨이브 효과: 각 세그먼트가 순차적으로 켜지고 꺼짐 - return Container( - height: 16, - decoration: BoxDecoration( - color: RetroColors.panelBg, - border: Border.all(color: RetroColors.panelBorderOuter, width: 2), - ), - child: Row( - children: List.generate(segmentCount, (index) { - // 웨이브 패턴 계산 - final progress = _controller.value * segmentCount; - final distance = (index - progress).abs(); - final isLit = distance < 2 || (segmentCount - distance) < 2; - final opacity = isLit ? 1.0 : 0.2; - - return Expanded( - child: Container( - margin: const EdgeInsets.all(1), - decoration: BoxDecoration( - color: RetroColors.gold.withValues(alpha: opacity), - ), - ), - ); - }), - ), - ); - }, - ); - } -} diff --git a/lib/src/app_theme.dart b/lib/src/app_theme.dart new file mode 100644 index 0000000..abccb01 --- /dev/null +++ b/lib/src/app_theme.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/src/shared/retro_colors.dart'; + +/// 앱 테마 (Dark Fantasy 스타일) +ThemeData buildAppTheme() => ThemeData( + colorScheme: RetroColors.darkColorScheme, + scaffoldBackgroundColor: RetroColors.deepBrown, + useMaterial3: true, + // 카드/다이얼로그 레트로 배경 + cardColor: RetroColors.darkBrown, + dialogTheme: const DialogThemeData( + backgroundColor: Color(0xFF24283B), + titleTextStyle: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 15, + color: Color(0xFFE0AF68), + ), + ), + // 앱바 레트로 스타일 + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF24283B), + foregroundColor: Color(0xFFC0CAF5), + titleTextStyle: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 15, + color: Color(0xFFE0AF68), + ), + ), + // 버튼 테마 (inherit: false로 애니메이션 lerp 오류 방지) + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: const Color(0xFF3D4260), + foregroundColor: const Color(0xFFC0CAF5), + textStyle: const TextStyle( + inherit: false, + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: const Color(0xFFE0AF68), + side: const BorderSide(color: Color(0xFFE0AF68), width: 2), + textStyle: const TextStyle( + inherit: false, + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFE0AF68), + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: const Color(0xFFC0CAF5), + textStyle: const TextStyle( + inherit: false, + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + ), + ), + // 텍스트 테마 + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 20, + color: Color(0xFFE0AF68), + ), + headlineMedium: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 16, + color: Color(0xFFE0AF68), + ), + headlineSmall: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 15, + color: Color(0xFFE0AF68), + ), + titleLarge: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 15, + color: Color(0xFFC0CAF5), + ), + titleMedium: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + titleSmall: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + bodyLarge: TextStyle(fontSize: 18, color: Color(0xFFC0CAF5)), + bodyMedium: TextStyle(fontSize: 17, color: Color(0xFFC0CAF5)), + bodySmall: TextStyle(fontSize: 15, color: Color(0xFFC0CAF5)), + labelLarge: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + labelMedium: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + labelSmall: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 13, + color: Color(0xFFC0CAF5), + ), + ), + // 칩 테마 + chipTheme: const ChipThemeData( + backgroundColor: Color(0xFF2A2E3F), + labelStyle: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: Color(0xFFC0CAF5), + ), + side: BorderSide(color: Color(0xFF545C7E)), + ), + // 리스트 타일 테마 + listTileTheme: const ListTileThemeData( + textColor: Color(0xFFC0CAF5), + iconColor: Color(0xFFE0AF68), + ), + // 프로그레스 인디케이터 + progressIndicatorTheme: const ProgressIndicatorThemeData( + color: Color(0xFFE0AF68), + linearTrackColor: Color(0xFF3B4261), + ), +); diff --git a/lib/src/splash_screen.dart b/lib/src/splash_screen.dart new file mode 100644 index 0000000..b3fce5d --- /dev/null +++ b/lib/src/splash_screen.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/src/shared/retro_colors.dart'; + +/// 스플래시 화면 (세이브 파일 확인 중) - 레트로 스타일 +class SplashScreen extends StatelessWidget { + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: RetroColors.deepBrown, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 타이틀 로고 + Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + decoration: BoxDecoration( + color: RetroColors.panelBg, + border: Border.all(color: RetroColors.gold, width: 3), + ), + child: const Column( + children: [ + Icon(Icons.auto_awesome, size: 32, color: RetroColors.gold), + SizedBox(height: 12), + Text( + 'ASCII', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 22, + color: RetroColors.gold, + shadows: [ + Shadow( + color: RetroColors.goldDark, + offset: Offset(2, 2), + ), + ], + ), + ), + SizedBox(height: 4), + Text( + 'NEVER DIE', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 16, + color: RetroColors.cream, + shadows: [ + Shadow( + color: RetroColors.brown, + offset: Offset(1, 1), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 32), + // 레트로 로딩 바 + const SizedBox(width: 160, child: _RetroLoadingBar()), + ], + ), + ), + ); + } +} + +/// 레트로 스타일 로딩 바 (애니메이션) +class _RetroLoadingBar extends StatefulWidget { + const _RetroLoadingBar(); + + @override + State<_RetroLoadingBar> createState() => _RetroLoadingBarState(); +} + +class _RetroLoadingBarState extends State<_RetroLoadingBar> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + const segmentCount = 10; + + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Container( + height: 16, + decoration: BoxDecoration( + color: RetroColors.panelBg, + border: Border.all(color: RetroColors.panelBorderOuter, width: 2), + ), + child: Row( + children: List.generate(segmentCount, (index) { + final progress = _controller.value * segmentCount; + final distance = (index - progress).abs(); + final isLit = distance < 2 || (segmentCount - distance) < 2; + final opacity = isLit ? 1.0 : 0.2; + + return Expanded( + child: Container( + margin: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: RetroColors.gold.withValues(alpha: opacity), + ), + ), + ); + }), + ), + ); + }, + ); + } +}