feat(front): 프론트 화면 개선 및 설정 저장소 추가

- front_screen_animation.dart: 프론트 화면 애니메이션 추가
- settings_repository.dart: 설정 저장소 구현
- front/widgets/: 프론트 화면 위젯 분리
- mobile_carousel_layout.dart: 모바일 레이아웃 개선
- app.dart: 앱 설정 개선
- game_text_l10n.dart: 텍스트 추가
This commit is contained in:
JiWoong Sul
2025-12-23 18:52:46 +09:00
parent e6af7dd91a
commit 549851f693
10 changed files with 722 additions and 183 deletions

View File

@@ -10,6 +10,7 @@ import 'package:askiineverdie/src/core/model/pq_config.dart';
import 'package:askiineverdie/src/core/notification/notification_service.dart';
import 'package:askiineverdie/src/core/storage/save_manager.dart';
import 'package:askiineverdie/src/core/storage/save_repository.dart';
import 'package:askiineverdie/src/core/storage/settings_repository.dart';
import 'package:askiineverdie/src/features/front/front_screen.dart';
import 'package:askiineverdie/src/features/front/save_picker_dialog.dart';
import 'package:askiineverdie/src/features/game/game_play_screen.dart';
@@ -28,8 +29,10 @@ class AskiiNeverDieApp extends StatefulWidget {
class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
late final GameSessionController _controller;
late final NotificationService _notificationService;
late final SettingsRepository _settingsRepository;
bool _isCheckingSave = true;
bool _hasSave = false;
ThemeMode _themeMode = ThemeMode.system;
@override
void initState() {
@@ -47,11 +50,28 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
saveManager: SaveManager(SaveRepository()),
);
_notificationService = NotificationService();
_settingsRepository = SettingsRepository();
// 초기 설정 로드
_loadSettings();
// 세이브 파일 존재 여부 확인
_checkForExistingSave();
}
/// 저장된 설정 불러오기
Future<void> _loadSettings() async {
final themeMode = await _settingsRepository.loadThemeMode();
if (mounted) {
setState(() => _themeMode = themeMode);
}
}
/// 테마 모드 변경
void _changeThemeMode(ThemeMode mode) {
setState(() => _themeMode = mode);
_settingsRepository.saveThemeMode(mode);
}
/// 세이브 파일 존재 여부 확인 후 자동 로드
Future<void> _checkForExistingSave() async {
final exists = await _controller.saveManager.saveExists();
@@ -70,6 +90,32 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
super.dispose();
}
/// 라이트 테마
ThemeData get _lightTheme => ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF234361)),
scaffoldBackgroundColor: const Color(0xFFF4F5F7),
useMaterial3: true,
);
/// 다크 테마 (OLED 저전력 모드 - 순수 검정)
ThemeData get _darkTheme => ThemeData(
colorScheme: ColorScheme.dark(
surface: Colors.black,
primary: const Color(0xFF4FC3F7), // 시안
secondary: const Color(0xFFFF4081), // 마젠타
onSurface: Colors.white70,
primaryContainer: const Color(0xFF1A3A4A),
onPrimaryContainer: Colors.white,
),
scaffoldBackgroundColor: Colors.black,
useMaterial3: true,
// 카드/다이얼로그도 검정 배경 사용
cardColor: const Color(0xFF121212),
dialogTheme: const DialogThemeData(
backgroundColor: Color(0xFF121212),
),
);
@override
Widget build(BuildContext context) {
return MaterialApp(
@@ -77,11 +123,9 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
debugShowCheckedModeBanner: false,
localizationsDelegates: L10n.localizationsDelegates,
supportedLocales: L10n.supportedLocales,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF234361)),
scaffoldBackgroundColor: const Color(0xFFF4F5F7),
useMaterial3: true,
),
theme: _lightTheme,
darkTheme: _darkTheme,
themeMode: _themeMode,
builder: (context, child) {
// 현재 로케일을 게임 텍스트 l10n 시스템에 동기화
final locale = Localizations.localeOf(context);
@@ -110,6 +154,8 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
// 로드 실패 시 프론트 화면으로
setState(() => _hasSave = false);
},
currentThemeMode: _themeMode,
onThemeModeChange: _changeThemeMode,
);
}
@@ -118,6 +164,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
onNewCharacter: _navigateToNewCharacter,
onLoadSave: _loadSave,
onHallOfFame: _navigateToHallOfFame,
hasSaveFile: _hasSave,
);
}
@@ -187,6 +234,8 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
builder: (context) => GamePlayScreen(
controller: _controller,
forceCarouselLayout: testMode,
currentThemeMode: _themeMode,
onThemeModeChange: _changeThemeMode,
),
),
);
@@ -196,7 +245,11 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
void _navigateToGame(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => GamePlayScreen(controller: _controller),
builder: (context) => GamePlayScreen(
controller: _controller,
currentThemeMode: _themeMode,
onThemeModeChange: _changeThemeMode,
),
),
);
}
@@ -235,10 +288,17 @@ class _SplashScreen extends StatelessWidget {
/// 자동 로드 화면 (세이브 파일 자동 로드)
class _AutoLoadScreen extends StatefulWidget {
const _AutoLoadScreen({required this.controller, required this.onLoadFailed});
const _AutoLoadScreen({
required this.controller,
required this.onLoadFailed,
required this.currentThemeMode,
required this.onThemeModeChange,
});
final GameSessionController controller;
final VoidCallback onLoadFailed;
final ThemeMode currentThemeMode;
final void Function(ThemeMode mode) onThemeModeChange;
@override
State<_AutoLoadScreen> createState() => _AutoLoadScreenState();
@@ -262,7 +322,8 @@ class _AutoLoadScreenState extends State<_AutoLoadScreen> {
MaterialPageRoute<void>(
builder: (context) => GamePlayScreen(
controller: widget.controller,
// 자동 로드 시에는 플랫폼 기본값 사용 (모바일만 캐로셀)
currentThemeMode: widget.currentThemeMode,
onThemeModeChange: widget.onThemeModeChange,
),
),
);
@@ -274,19 +335,19 @@ class _AutoLoadScreenState extends State<_AutoLoadScreen> {
@override
Widget build(BuildContext context) {
return const Scaffold(
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
const Text(
'ASCII NEVER DIE',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 16),
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading...'),
const SizedBox(height: 16),
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(game_l10n.uiLoading),
],
),
),