refactor(app): 앱 초기화 및 프론트 화면 개선
- app.dart 코드 간소화 - front_screen.dart 기능 확장
This commit is contained in:
196
lib/src/app.dart
196
lib/src/app.dart
@@ -28,6 +28,19 @@ class AskiiNeverDieApp extends StatefulWidget {
|
|||||||
State<AskiiNeverDieApp> createState() => _AskiiNeverDieAppState();
|
State<AskiiNeverDieApp> 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<AskiiNeverDieApp> {
|
class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
||||||
late final GameSessionController _controller;
|
late final GameSessionController _controller;
|
||||||
late final NotificationService _notificationService;
|
late final NotificationService _notificationService;
|
||||||
@@ -35,6 +48,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
late final AudioService _audioService;
|
late final AudioService _audioService;
|
||||||
bool _isCheckingSave = true;
|
bool _isCheckingSave = true;
|
||||||
bool _hasSave = false;
|
bool _hasSave = false;
|
||||||
|
SavedGamePreview? _savedGamePreview;
|
||||||
ThemeMode _themeMode = ThemeMode.system;
|
ThemeMode _themeMode = ThemeMode.system;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -77,17 +91,46 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
_settingsRepository.saveThemeMode(mode);
|
_settingsRepository.saveThemeMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 세이브 파일 존재 여부 확인 후 자동 로드
|
/// 세이브 파일 존재 여부 확인 및 미리보기 정보 로드
|
||||||
Future<void> _checkForExistingSave() async {
|
Future<void> _checkForExistingSave() async {
|
||||||
final exists = await _controller.saveManager.saveExists();
|
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) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_hasSave = exists;
|
_hasSave = exists;
|
||||||
|
_savedGamePreview = preview;
|
||||||
_isCheckingSave = false;
|
_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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
@@ -387,34 +430,21 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 홈 화면 결정: 세이브 확인 중 → 스플래시, 세이브 있음 → 자동 로드, 없음 → 프론트
|
/// 홈 화면 결정: 세이브 확인 중 → 스플래시, 그 외 → 프론트
|
||||||
Widget _buildHomeScreen() {
|
Widget _buildHomeScreen() {
|
||||||
// 세이브 확인 중이면 로딩 스플래시 표시
|
// 세이브 확인 중이면 로딩 스플래시 표시
|
||||||
if (_isCheckingSave) {
|
if (_isCheckingSave) {
|
||||||
return const _SplashScreen();
|
return const _SplashScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 세이브 파일이 있으면 자동 로드 화면
|
// 항상 프론트 화면 표시 (타이틀 BGM 재생)
|
||||||
if (_hasSave) {
|
|
||||||
return _AutoLoadScreen(
|
|
||||||
controller: _controller,
|
|
||||||
onLoadFailed: () {
|
|
||||||
// 로드 실패 시 프론트 화면으로
|
|
||||||
setState(() => _hasSave = false);
|
|
||||||
},
|
|
||||||
currentThemeMode: _themeMode,
|
|
||||||
onThemeModeChange: _changeThemeMode,
|
|
||||||
audioService: _audioService,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 세이브 파일이 없으면 기존 프론트 화면 (타이틀 BGM 재생)
|
|
||||||
_audioService.playBgm('title');
|
_audioService.playBgm('title');
|
||||||
return FrontScreen(
|
return FrontScreen(
|
||||||
onNewCharacter: _navigateToNewCharacter,
|
onNewCharacter: _navigateToNewCharacter,
|
||||||
onLoadSave: _loadSave,
|
onLoadSave: _loadSave,
|
||||||
onHallOfFame: _navigateToHallOfFame,
|
onHallOfFame: _navigateToHallOfFame,
|
||||||
hasSaveFile: _hasSave,
|
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<void> _autoLoad() async {
|
|
||||||
await widget.controller.loadAndStart(cheatsEnabled: false);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
if (widget.controller.status == GameSessionStatus.running) {
|
|
||||||
// 로드 성공 → 게임 화면으로 교체
|
|
||||||
Navigator.of(context).pushReplacement(
|
|
||||||
MaterialPageRoute<void>(
|
|
||||||
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 {
|
class _RetroLoadingBar extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||||
import 'package:asciineverdie/l10n/app_localizations.dart';
|
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/features/front/widgets/hero_vs_boss_animation.dart';
|
||||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||||
import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart';
|
import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart';
|
||||||
@@ -13,6 +14,7 @@ class FrontScreen extends StatelessWidget {
|
|||||||
this.onLoadSave,
|
this.onLoadSave,
|
||||||
this.onHallOfFame,
|
this.onHallOfFame,
|
||||||
this.hasSaveFile = false,
|
this.hasSaveFile = false,
|
||||||
|
this.savedGamePreview,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// "New character" 버튼 클릭 시 호출
|
/// "New character" 버튼 클릭 시 호출
|
||||||
@@ -27,6 +29,9 @@ class FrontScreen extends StatelessWidget {
|
|||||||
/// 세이브 파일 존재 여부 (새 캐릭터 시 경고용)
|
/// 세이브 파일 존재 여부 (새 캐릭터 시 경고용)
|
||||||
final bool hasSaveFile;
|
final bool hasSaveFile;
|
||||||
|
|
||||||
|
/// 저장된 게임 미리보기 정보
|
||||||
|
final SavedGamePreview? savedGamePreview;
|
||||||
|
|
||||||
/// 새 캐릭터 생성 시 세이브 파일 존재하면 경고 표시
|
/// 새 캐릭터 생성 시 세이브 파일 존재하면 경고 표시
|
||||||
void _handleNewCharacter(BuildContext context) {
|
void _handleNewCharacter(BuildContext context) {
|
||||||
if (hasSaveFile) {
|
if (hasSaveFile) {
|
||||||
@@ -91,6 +96,7 @@ class FrontScreen extends StatelessWidget {
|
|||||||
onHallOfFame: onHallOfFame != null
|
onHallOfFame: onHallOfFame != null
|
||||||
? () => onHallOfFame!(context)
|
? () => onHallOfFame!(context)
|
||||||
: null,
|
: null,
|
||||||
|
savedGamePreview: savedGamePreview,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -181,11 +187,13 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
this.onNewCharacter,
|
this.onNewCharacter,
|
||||||
this.onLoadSave,
|
this.onLoadSave,
|
||||||
this.onHallOfFame,
|
this.onHallOfFame,
|
||||||
|
this.savedGamePreview,
|
||||||
});
|
});
|
||||||
|
|
||||||
final VoidCallback? onNewCharacter;
|
final VoidCallback? onNewCharacter;
|
||||||
final VoidCallback? onLoadSave;
|
final VoidCallback? onLoadSave;
|
||||||
final VoidCallback? onHallOfFame;
|
final VoidCallback? onHallOfFame;
|
||||||
|
final SavedGamePreview? savedGamePreview;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -211,6 +219,11 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
onPressed: onLoadSave,
|
onPressed: onLoadSave,
|
||||||
isPrimary: false,
|
isPrimary: false,
|
||||||
),
|
),
|
||||||
|
// 저장된 게임 정보 표시
|
||||||
|
if (savedGamePreview != null) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
_SavedGameInfo(preview: savedGamePreview!),
|
||||||
|
],
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// 명예의 전당
|
// 명예의 전당
|
||||||
if (onHallOfFame != null)
|
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 {
|
class _CopyrightFooter extends StatelessWidget {
|
||||||
const _CopyrightFooter();
|
const _CopyrightFooter();
|
||||||
|
|||||||
Reference in New Issue
Block a user