From afbd4e685351c49cf494919dd0bca60f45ef46cf Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Fri, 2 Jan 2026 16:52:52 +0900 Subject: [PATCH] =?UTF-8?q?refactor(app):=20=EC=95=B1=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=ED=94=84=EB=A1=A0=ED=8A=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - app.dart 코드 간소화 - front_screen.dart 기능 확장 --- lib/src/app.dart | 196 ++++++----------------- lib/src/features/front/front_screen.dart | 46 ++++++ 2 files changed, 93 insertions(+), 149 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index f5d0db2..4e8f2ed 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -28,6 +28,19 @@ class AskiiNeverDieApp extends StatefulWidget { State createState() => _AskiiNeverDieAppState(); } +/// 저장된 게임 미리보기 정보 +class SavedGamePreview { + const SavedGamePreview({ + required this.characterName, + required this.level, + required this.actName, + }); + + final String characterName; + final int level; + final String actName; +} + class _AskiiNeverDieAppState extends State { late final GameSessionController _controller; late final NotificationService _notificationService; @@ -35,6 +48,7 @@ class _AskiiNeverDieAppState extends State { late final AudioService _audioService; bool _isCheckingSave = true; bool _hasSave = false; + SavedGamePreview? _savedGamePreview; ThemeMode _themeMode = ThemeMode.system; @override @@ -77,17 +91,46 @@ class _AskiiNeverDieAppState extends State { _settingsRepository.saveThemeMode(mode); } - /// 세이브 파일 존재 여부 확인 후 자동 로드 + /// 세이브 파일 존재 여부 확인 및 미리보기 정보 로드 Future _checkForExistingSave() async { final exists = await _controller.saveManager.saveExists(); + SavedGamePreview? preview; + + if (exists) { + // 세이브 파일에서 미리보기 정보 추출 + final (outcome, state) = await _controller.saveManager.loadState(); + if (outcome.success && state != null) { + final actName = _getActName(state.progress.plotStageCount); + preview = SavedGamePreview( + characterName: state.traits.name, + level: state.traits.level, + actName: actName, + ); + } + } + if (mounted) { setState(() { _hasSave = exists; + _savedGamePreview = preview; _isCheckingSave = false; }); } } + /// plotStageCount를 Act 이름으로 변환 + String _getActName(int plotStageCount) { + return switch (plotStageCount) { + 1 => 'Prologue', + 2 => 'Act I', + 3 => 'Act II', + 4 => 'Act III', + 5 => 'Act IV', + 6 => 'Act V', + _ => 'Act V', + }; + } + @override void dispose() { _controller.dispose(); @@ -387,34 +430,21 @@ class _AskiiNeverDieAppState extends State { ); } - /// 홈 화면 결정: 세이브 확인 중 → 스플래시, 세이브 있음 → 자동 로드, 없음 → 프론트 + /// 홈 화면 결정: 세이브 확인 중 → 스플래시, 그 외 → 프론트 Widget _buildHomeScreen() { // 세이브 확인 중이면 로딩 스플래시 표시 if (_isCheckingSave) { return const _SplashScreen(); } - // 세이브 파일이 있으면 자동 로드 화면 - if (_hasSave) { - return _AutoLoadScreen( - controller: _controller, - onLoadFailed: () { - // 로드 실패 시 프론트 화면으로 - setState(() => _hasSave = false); - }, - currentThemeMode: _themeMode, - onThemeModeChange: _changeThemeMode, - audioService: _audioService, - ); - } - - // 세이브 파일이 없으면 기존 프론트 화면 (타이틀 BGM 재생) + // 항상 프론트 화면 표시 (타이틀 BGM 재생) _audioService.playBgm('title'); return FrontScreen( onNewCharacter: _navigateToNewCharacter, onLoadSave: _loadSave, onHallOfFame: _navigateToHallOfFame, hasSaveFile: _hasSave, + savedGamePreview: _savedGamePreview, ); } @@ -588,138 +618,6 @@ class _SplashScreen extends StatelessWidget { } } -/// 자동 로드 화면 (세이브 파일 자동 로드) - 레트로 스타일 -class _AutoLoadScreen extends StatefulWidget { - const _AutoLoadScreen({ - required this.controller, - required this.onLoadFailed, - required this.currentThemeMode, - required this.onThemeModeChange, - this.audioService, - }); - - final GameSessionController controller; - final VoidCallback onLoadFailed; - final ThemeMode currentThemeMode; - final void Function(ThemeMode mode) onThemeModeChange; - final AudioService? audioService; - - @override - State<_AutoLoadScreen> createState() => _AutoLoadScreenState(); -} - -class _AutoLoadScreenState extends State<_AutoLoadScreen> { - @override - void initState() { - super.initState(); - // 로딩 중에도 타이틀 BGM 재생 - widget.audioService?.playBgm('title'); - _autoLoad(); - } - - Future _autoLoad() async { - await widget.controller.loadAndStart(cheatsEnabled: false); - - if (!mounted) return; - - if (widget.controller.status == GameSessionStatus.running) { - // 로드 성공 → 게임 화면으로 교체 - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => GamePlayScreen( - controller: widget.controller, - audioService: widget.audioService, - currentThemeMode: widget.currentThemeMode, - onThemeModeChange: widget.onThemeModeChange, - ), - ), - ); - } else { - // 로드 실패 → 프론트 화면으로 돌아가기 - widget.onLoadFailed(); - } - } - - @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: 20, - 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: 14, - color: RetroColors.cream, - shadows: [ - Shadow( - color: RetroColors.brown, - offset: Offset(1, 1), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 32), - // 레트로 로딩 바 - SizedBox( - width: 160, - child: _RetroLoadingBar(), - ), - const SizedBox(height: 16), - // 로딩 텍스트 - Text( - game_l10n.uiLoading, - style: const TextStyle( - fontFamily: 'PressStart2P', - fontSize: 8, - color: RetroColors.textDisabled, - ), - ), - ], - ), - ), - ); - } -} - /// 레트로 스타일 로딩 바 (애니메이션) class _RetroLoadingBar extends StatefulWidget { @override diff --git a/lib/src/features/front/front_screen.dart b/lib/src/features/front/front_screen.dart index de70fc1..6dc6761 100644 --- a/lib/src/features/front/front_screen.dart +++ b/lib/src/features/front/front_screen.dart @@ -2,6 +2,7 @@ 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'; @@ -13,6 +14,7 @@ class FrontScreen extends StatelessWidget { this.onLoadSave, this.onHallOfFame, this.hasSaveFile = false, + this.savedGamePreview, }); /// "New character" 버튼 클릭 시 호출 @@ -27,6 +29,9 @@ class FrontScreen extends StatelessWidget { /// 세이브 파일 존재 여부 (새 캐릭터 시 경고용) final bool hasSaveFile; + /// 저장된 게임 미리보기 정보 + final SavedGamePreview? savedGamePreview; + /// 새 캐릭터 생성 시 세이브 파일 존재하면 경고 표시 void _handleNewCharacter(BuildContext context) { if (hasSaveFile) { @@ -91,6 +96,7 @@ class FrontScreen extends StatelessWidget { onHallOfFame: onHallOfFame != null ? () => onHallOfFame!(context) : null, + savedGamePreview: savedGamePreview, ), ], ), @@ -181,11 +187,13 @@ class _ActionButtons extends StatelessWidget { this.onNewCharacter, this.onLoadSave, this.onHallOfFame, + this.savedGamePreview, }); final VoidCallback? onNewCharacter; final VoidCallback? onLoadSave; final VoidCallback? onHallOfFame; + final SavedGamePreview? savedGamePreview; @override Widget build(BuildContext context) { @@ -211,6 +219,11 @@ class _ActionButtons extends StatelessWidget { onPressed: onLoadSave, isPrimary: false, ), + // 저장된 게임 정보 표시 + if (savedGamePreview != null) ...[ + const SizedBox(height: 6), + _SavedGameInfo(preview: savedGamePreview!), + ], const SizedBox(height: 12), // 명예의 전당 if (onHallOfFame != null) @@ -226,6 +239,39 @@ class _ActionButtons extends StatelessWidget { } } +/// 저장된 게임 미리보기 정보 위젯 +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: 7, + color: RetroColors.textDisabled, + ), + ), + ], + ), + ); + } +} + /// 카피라이트 푸터 class _CopyrightFooter extends StatelessWidget { const _CopyrightFooter();