import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n; import 'package:asciineverdie/l10n/app_localizations.dart'; import 'package:asciineverdie/src/app.dart' show SavedGamePreview; import 'package:asciineverdie/src/features/front/widgets/hero_vs_boss_animation.dart'; import 'package:asciineverdie/src/shared/retro_colors.dart'; import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart'; class FrontScreen extends StatefulWidget { const FrontScreen({ super.key, this.onNewCharacter, this.onLoadSave, this.onHallOfFame, this.onLocalArena, this.onSettings, this.hasSaveFile = false, this.savedGamePreview, this.hallOfFameCount = 0, this.routeObserver, this.onRefresh, }); /// "New character" 버튼 클릭 시 호출 final void Function(BuildContext context)? onNewCharacter; /// "Load save" 버튼 클릭 시 호출 final Future Function(BuildContext context)? onLoadSave; /// "Hall of Fame" 버튼 클릭 시 호출 final void Function(BuildContext context)? onHallOfFame; /// "Local Arena" 버튼 클릭 시 호출 final void Function(BuildContext context)? onLocalArena; /// "Settings" 버튼 클릭 시 호출 (언어, 테마, 사운드) final void Function(BuildContext context)? onSettings; /// 세이브 파일 존재 여부 (새 캐릭터 시 경고용) final bool hasSaveFile; /// 저장된 게임 미리보기 정보 final SavedGamePreview? savedGamePreview; /// 명예의 전당 캐릭터 수 (아레나 활성화 조건: 2명 이상) final int hallOfFameCount; /// RouteObserver (화면 복귀 시 갱신용) final RouteObserver>? routeObserver; /// 화면 복귀 시 호출할 콜백 final VoidCallback? onRefresh; @override State createState() => _FrontScreenState(); } class _FrontScreenState extends State with RouteAware { @override void didChangeDependencies() { super.didChangeDependencies(); // RouteObserver 구독 final route = ModalRoute.of(context); if (route != null) { widget.routeObserver?.subscribe(this, route); } } @override void dispose() { widget.routeObserver?.unsubscribe(this); super.dispose(); } @override void didPopNext() { // 다른 화면에서 돌아왔을 때 갱신 widget.onRefresh?.call(); } /// 새 캐릭터 생성 시 세이브 파일 존재하면 경고 표시 void _handleNewCharacter(BuildContext context) { if (widget.hasSaveFile) { _showDeleteWarningDialog(context); } else { widget.onNewCharacter?.call(context); } } /// 세이브 삭제 경고 다이얼로그 void _showDeleteWarningDialog(BuildContext context) { showDialog( context: context, builder: (dialogContext) => AlertDialog( title: Text(game_l10n.uiWarning), content: Text(game_l10n.warningDeleteSave), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: Text(game_l10n.buttonCancel), ), FilledButton( onPressed: () { Navigator.pop(dialogContext); widget.onNewCharacter?.call(context); }, child: Text(game_l10n.buttonConfirm), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: RetroColors.deepBrown, body: SafeArea( child: Column( children: [ // 스크롤 영역 (헤더, 애니메이션, 버튼) Expanded( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 800), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const _RetroHeader(), const SizedBox(height: 16), const _AnimationPanel(), const SizedBox(height: 16), _ActionButtons( onNewCharacter: widget.onNewCharacter != null ? () => _handleNewCharacter(context) : null, onLoadSave: widget.onLoadSave != null ? () => widget.onLoadSave!(context) : null, onHallOfFame: widget.onHallOfFame != null ? () => widget.onHallOfFame!(context) : null, onLocalArena: widget.onLocalArena != null && widget.hallOfFameCount >= 2 ? () => widget.onLocalArena!(context) : null, onSettings: widget.onSettings != null ? () => widget.onSettings!(context) : null, savedGamePreview: widget.savedGamePreview, hallOfFameCount: widget.hallOfFameCount, ), ], ), ), ), ), ), // 카피라이트 푸터 (하단 고정) const _CopyrightFooter(), ], ), ), ); } } /// 레트로 스타일 헤더 (타이틀 + 태그) class _RetroHeader extends StatelessWidget { const _RetroHeader(); @override Widget build(BuildContext context) { final l10n = L10n.of(context); return RetroGoldPanel( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: Column( children: [ // 타이틀 (픽셀 폰트) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.auto_awesome, color: RetroColors.gold, size: 20), const SizedBox(width: 12), Text( l10n.appTitle, style: const TextStyle( fontFamily: 'PressStart2P', fontSize: 13, color: RetroColors.gold, shadows: [ Shadow(color: RetroColors.goldDark, offset: Offset(2, 2)), ], ), ), ], ), const SizedBox(height: 16), // 태그 (레트로 스타일) Wrap( alignment: WrapAlignment.center, spacing: 8, runSpacing: 8, children: [ _RetroTag( icon: Icons.cloud_off_outlined, label: l10n.tagNoNetwork, ), _RetroTag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg), _RetroTag(icon: Icons.storage_rounded, label: l10n.tagLocalSaves), ], ), ], ), ); } } /// 애니메이션 패널 class _AnimationPanel extends StatelessWidget { const _AnimationPanel(); /// 플랫폼별 비율 반환 (데스크톱: 넓게, 모바일: 높게) double _getAspectRatio() { if (kIsWeb) return 2.5; // 웹: 넓은 비율 if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { return 2.5; // 데스크톱: 넓은 비율 } return 16 / 9; // 모바일(iOS/Android): 16:9 } @override Widget build(BuildContext context) { return RetroPanel( title: 'BATTLE', padding: const EdgeInsets.all(8), child: AspectRatio( aspectRatio: _getAspectRatio(), child: const HeroVsBossAnimation(), ), ); } } /// 액션 버튼 (레트로 스타일) class _ActionButtons extends StatelessWidget { const _ActionButtons({ this.onNewCharacter, this.onLoadSave, this.onHallOfFame, this.onLocalArena, this.onSettings, this.savedGamePreview, this.hallOfFameCount = 0, }); final VoidCallback? onNewCharacter; final VoidCallback? onLoadSave; final VoidCallback? onHallOfFame; final VoidCallback? onLocalArena; final VoidCallback? onSettings; final SavedGamePreview? savedGamePreview; final int hallOfFameCount; @override Widget build(BuildContext context) { final l10n = L10n.of(context); return RetroPanel( title: 'MENU', padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 새 캐릭터 (Primary) RetroTextButton( text: l10n.newCharacter, icon: Icons.casino_outlined, onPressed: onNewCharacter, ), const SizedBox(height: 12), // 불러오기 (Secondary) RetroTextButton( text: l10n.loadSave, icon: Icons.folder_open, onPressed: onLoadSave, isPrimary: false, ), // 저장된 게임 정보 표시 if (savedGamePreview != null) ...[ const SizedBox(height: 6), _SavedGameInfo(preview: savedGamePreview!), ], const SizedBox(height: 12), // 명예의 전당 if (onHallOfFame != null) RetroTextButton( text: game_l10n.uiHallOfFame, icon: Icons.emoji_events_outlined, onPressed: onHallOfFame, isPrimary: false, ), // 로컬 아레나 (항상 표시, 2명 이상일 때만 활성화) const SizedBox(height: 12), RetroTextButton( text: game_l10n.uiLocalArena, icon: Icons.sports_kabaddi, onPressed: hallOfFameCount >= 2 ? onLocalArena : null, isPrimary: false, ), // 설정 const SizedBox(height: 12), RetroTextButton( text: game_l10n.uiSettings, icon: Icons.settings, onPressed: onSettings, isPrimary: false, ), ], ), ); } } /// 저장된 게임 미리보기 정보 위젯 class _SavedGameInfo extends StatelessWidget { const _SavedGameInfo({required this.preview}); final SavedGamePreview preview; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.person_outline, size: 10, color: RetroColors.textDisabled, ), const SizedBox(width: 4), Text( '${preview.characterName} Lv.${preview.level} ${preview.actName}', style: const TextStyle( fontFamily: 'PressStart2P', fontSize: 11, color: RetroColors.textDisabled, ), ), ], ), ); } } /// 카피라이트 푸터 class _CopyrightFooter extends StatelessWidget { const _CopyrightFooter(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text( game_l10n.copyrightText, textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'PressStart2P', fontSize: 7, color: RetroColors.textDisabled, ), ), ); } } /// 레트로 태그 칩 class _RetroTag extends StatelessWidget { const _RetroTag({required this.icon, required this.label}); final IconData icon; final String label; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: RetroColors.panelBgLight, border: Border.all(color: RetroColors.panelBorderInner, width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: RetroColors.gold, size: 12), const SizedBox(width: 6), Text( label, style: const TextStyle( fontFamily: 'PressStart2P', fontSize: 11, color: RetroColors.textLight, ), ), ], ), ); } }