refactor(ui): 기타 화면 정리
- FrontScreen, HallOfFameScreen 개선 - NewCharacterScreen, SettingsScreen 정리 - App 초기화 로직 정리
This commit is contained in:
102
lib/src/app.dart
102
lib/src/app.dart
@@ -469,18 +469,20 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToNewCharacter(BuildContext context) {
|
void _navigateToNewCharacter(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context)
|
||||||
MaterialPageRoute<void>(
|
.push(
|
||||||
builder: (context) => NewCharacterScreen(
|
MaterialPageRoute<void>(
|
||||||
onCharacterCreated: (initialState, {bool testMode = false}) {
|
builder: (context) => NewCharacterScreen(
|
||||||
_startGame(context, initialState, testMode: testMode);
|
onCharacterCreated: (initialState, {bool testMode = false}) {
|
||||||
},
|
_startGame(context, initialState, testMode: testMode);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
).then((_) {
|
),
|
||||||
// 새 게임 후 돌아오면 세이브 정보 갱신 (BGM은 _checkForExistingSave에서 재생)
|
)
|
||||||
_checkForExistingSave();
|
.then((_) {
|
||||||
});
|
// 새 게임 후 돌아오면 세이브 정보 갱신 (BGM은 _checkForExistingSave에서 재생)
|
||||||
|
_checkForExistingSave();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadSave(BuildContext context) async {
|
Future<void> _loadSave(BuildContext context) async {
|
||||||
@@ -544,44 +546,50 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToGame(BuildContext context) {
|
void _navigateToGame(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context)
|
||||||
MaterialPageRoute<void>(
|
.push(
|
||||||
builder: (context) => GamePlayScreen(
|
MaterialPageRoute<void>(
|
||||||
controller: _controller,
|
builder: (context) => GamePlayScreen(
|
||||||
audioService: _audioService,
|
controller: _controller,
|
||||||
// 디버그 모드로 저장된 게임 로드 시 캐로셀 레이아웃 강제
|
audioService: _audioService,
|
||||||
forceCarouselLayout: _controller.cheatsEnabled,
|
// 디버그 모드로 저장된 게임 로드 시 캐로셀 레이아웃 강제
|
||||||
currentThemeMode: _themeMode,
|
forceCarouselLayout: _controller.cheatsEnabled,
|
||||||
onThemeModeChange: _changeThemeMode,
|
currentThemeMode: _themeMode,
|
||||||
),
|
onThemeModeChange: _changeThemeMode,
|
||||||
),
|
),
|
||||||
).then((_) {
|
),
|
||||||
// 게임에서 돌아오면 세이브 정보 갱신 (BGM은 _checkForExistingSave에서 재생)
|
)
|
||||||
_checkForExistingSave();
|
.then((_) {
|
||||||
});
|
// 게임에서 돌아오면 세이브 정보 갱신 (BGM은 _checkForExistingSave에서 재생)
|
||||||
|
_checkForExistingSave();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 10: 명예의 전당 화면으로 이동
|
/// Phase 10: 명예의 전당 화면으로 이동
|
||||||
void _navigateToHallOfFame(BuildContext context) {
|
void _navigateToHallOfFame(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context)
|
||||||
MaterialPageRoute<void>(builder: (context) => const HallOfFameScreen()),
|
.push(
|
||||||
).then((_) {
|
MaterialPageRoute<void>(
|
||||||
// 명예의 전당에서 돌아오면 타이틀 BGM 재생
|
builder: (context) => const HallOfFameScreen(),
|
||||||
_audioService.playBgm('title');
|
),
|
||||||
});
|
)
|
||||||
|
.then((_) {
|
||||||
|
// 명예의 전당에서 돌아오면 타이틀 BGM 재생
|
||||||
|
_audioService.playBgm('title');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 로컬 아레나 화면으로 이동
|
/// 로컬 아레나 화면으로 이동
|
||||||
void _navigateToArena(BuildContext context) {
|
void _navigateToArena(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context)
|
||||||
MaterialPageRoute<void>(
|
.push(
|
||||||
builder: (context) => const ArenaScreen(),
|
MaterialPageRoute<void>(builder: (context) => const ArenaScreen()),
|
||||||
),
|
)
|
||||||
).then((_) {
|
.then((_) {
|
||||||
// 아레나에서 돌아오면 명예의 전당 다시 로드 및 타이틀 BGM 재생
|
// 아레나에서 돌아오면 명예의 전당 다시 로드 및 타이틀 BGM 재생
|
||||||
_loadHallOfFame();
|
_loadHallOfFame();
|
||||||
_audioService.playBgm('title');
|
_audioService.playBgm('title');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,10 +644,7 @@ class _SplashScreen extends StatelessWidget {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: RetroColors.cream,
|
color: RetroColors.cream,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(color: RetroColors.brown, offset: Offset(1, 1)),
|
||||||
color: RetroColors.brown,
|
|
||||||
offset: Offset(1, 1),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -648,10 +653,7 @@ class _SplashScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
// 레트로 로딩 바
|
// 레트로 로딩 바
|
||||||
SizedBox(
|
SizedBox(width: 160, child: _RetroLoadingBar()),
|
||||||
width: 160,
|
|
||||||
child: _RetroLoadingBar(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ class FrontScreen extends StatelessWidget {
|
|||||||
onHallOfFame: onHallOfFame != null
|
onHallOfFame: onHallOfFame != null
|
||||||
? () => onHallOfFame!(context)
|
? () => onHallOfFame!(context)
|
||||||
: null,
|
: null,
|
||||||
onLocalArena: onLocalArena != null &&
|
onLocalArena:
|
||||||
hallOfFameCount >= 2
|
onLocalArena != null && hallOfFameCount >= 2
|
||||||
? () => onLocalArena!(context)
|
? () => onLocalArena!(context)
|
||||||
: null,
|
: null,
|
||||||
savedGamePreview: savedGamePreview,
|
savedGamePreview: savedGamePreview,
|
||||||
@@ -153,10 +153,7 @@ class _RetroHeader extends StatelessWidget {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: RetroColors.gold,
|
color: RetroColors.gold,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(color: RetroColors.goldDark, offset: Offset(2, 2)),
|
||||||
color: RetroColors.goldDark,
|
|
||||||
offset: Offset(2, 2),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -169,7 +166,10 @@ class _RetroHeader extends StatelessWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
_RetroTag(icon: Icons.cloud_off_outlined, label: l10n.tagNoNetwork),
|
_RetroTag(
|
||||||
|
icon: Icons.cloud_off_outlined,
|
||||||
|
label: l10n.tagNoNetwork,
|
||||||
|
),
|
||||||
_RetroTag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg),
|
_RetroTag(icon: Icons.timer_outlined, label: l10n.tagIdleRpg),
|
||||||
_RetroTag(icon: Icons.storage_rounded, label: l10n.tagLocalSaves),
|
_RetroTag(icon: Icons.storage_rounded, label: l10n.tagLocalSaves),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -159,9 +159,7 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final frame = _applyGlitchEffect(
|
final frame = _applyGlitchEffect(frontScreenAnimationFrames[_currentFrame]);
|
||||||
frontScreenAnimationFrames[_currentFrame],
|
|
||||||
);
|
|
||||||
|
|
||||||
// 현재 종족 이름 (UI 표시용)
|
// 현재 종족 이름 (UI 표시용)
|
||||||
final raceName = RaceData.findById(_currentRaceId)?.name ?? 'Hero';
|
final raceName = RaceData.findById(_currentRaceId)?.name ?? 'Hero';
|
||||||
@@ -172,10 +170,7 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
|||||||
// 항상 검은 배경
|
// 항상 검은 배경
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(color: Colors.white24, width: 1),
|
||||||
color: Colors.white24,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
// 은은한 글로우 효과
|
// 은은한 글로우 효과
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
@@ -192,9 +187,7 @@ class _HeroVsBossAnimationState extends State<HeroVsBossAnimation> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: RichText(
|
child: RichText(text: _buildColoredTextSpan(frame)),
|
||||||
text: _buildColoredTextSpan(frame),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
||||||
@@ -136,9 +137,7 @@ class _HallOfFameScreenState extends State<HallOfFameScreen> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: goldColor.withValues(alpha: 0.2),
|
color: goldColor.withValues(alpha: 0.2),
|
||||||
border: Border(
|
border: Border(bottom: BorderSide(color: goldColor, width: 2)),
|
||||||
bottom: BorderSide(color: goldColor, width: 2),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -166,7 +165,11 @@ class _HallOfFameScreenState extends State<HallOfFameScreen> {
|
|||||||
itemCount: hallOfFame.entries.length,
|
itemCount: hallOfFame.entries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final entry = hallOfFame.entries[index];
|
final entry = hallOfFame.entries[index];
|
||||||
return _HallOfFameEntryCard(entry: entry, rank: index + 1);
|
return _HallOfFameEntryCard(
|
||||||
|
entry: entry,
|
||||||
|
rank: index + 1,
|
||||||
|
onDeleteRequest: () => _confirmDelete(entry),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -174,14 +177,103 @@ class _HallOfFameScreenState extends State<HallOfFameScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 삭제 확인 다이얼로그 (디버그 모드 전용)
|
||||||
|
Future<void> _confirmDelete(HallOfFameEntry entry) async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => RetroDialog(
|
||||||
|
title: l10n.uiConfirm.toUpperCase(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${entry.characterName} (Lv.${entry.level})\n'
|
||||||
|
'${l10n.uiConfirmDelete}',
|
||||||
|
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 7),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: Text(l10n.uiCancel),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: RetroColors.hpOf(context),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: Text(l10n.uiDelete),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed == true && mounted) {
|
||||||
|
await _deleteEntry(entry.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 엔트리 삭제 실행
|
||||||
|
Future<void> _deleteEntry(String id) async {
|
||||||
|
final success = await _storage.deleteEntry(id);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 성공 시 목록 새로고침
|
||||||
|
await _loadHallOfFame();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
l10n.uiDeleted,
|
||||||
|
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 6),
|
||||||
|
),
|
||||||
|
backgroundColor: RetroColors.mpOf(context),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 실패 시 에러 메시지
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
l10n.uiError,
|
||||||
|
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 6),
|
||||||
|
),
|
||||||
|
backgroundColor: RetroColors.hpOf(context),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 명예의 전당 엔트리 카드
|
/// 명예의 전당 엔트리 카드
|
||||||
class _HallOfFameEntryCard extends StatelessWidget {
|
class _HallOfFameEntryCard extends StatelessWidget {
|
||||||
const _HallOfFameEntryCard({required this.entry, required this.rank});
|
const _HallOfFameEntryCard({
|
||||||
|
required this.entry,
|
||||||
|
required this.rank,
|
||||||
|
required this.onDeleteRequest,
|
||||||
|
});
|
||||||
|
|
||||||
final HallOfFameEntry entry;
|
final HallOfFameEntry entry;
|
||||||
final int rank;
|
final int rank;
|
||||||
|
final VoidCallback onDeleteRequest;
|
||||||
|
|
||||||
void _showDetailDialog(BuildContext context) {
|
void _showDetailDialog(BuildContext context) {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
@@ -259,8 +351,9 @@ class _HallOfFameEntryCard extends StatelessWidget {
|
|||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: RetroColors.mpOf(context)
|
color: RetroColors.mpOf(
|
||||||
.withValues(alpha: 0.2),
|
context,
|
||||||
|
).withValues(alpha: 0.2),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: RetroColors.mpOf(context),
|
color: RetroColors.mpOf(context),
|
||||||
width: 1,
|
width: 1,
|
||||||
@@ -337,6 +430,36 @@ class _HallOfFameEntryCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// 삭제 버튼 (디버그 모드 전용)
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
// 이벤트 전파 중지 (카드 클릭 방지)
|
||||||
|
onDeleteRequest();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: RetroColors.hpOf(
|
||||||
|
context,
|
||||||
|
).withValues(alpha: 0.2),
|
||||||
|
border: Border.all(
|
||||||
|
color: RetroColors.hpOf(context),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
size: 16,
|
||||||
|
color: RetroColors.hpOf(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -459,9 +582,7 @@ class _GameClearDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: goldColor.withValues(alpha: 0.2),
|
color: goldColor.withValues(alpha: 0.2),
|
||||||
border: Border(
|
border: Border(bottom: BorderSide(color: goldColor, width: 2)),
|
||||||
bottom: BorderSide(color: goldColor, width: 2),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -496,10 +617,7 @@ class _GameClearDialog extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Container(
|
Container(height: 2, color: borderColor),
|
||||||
height: 2,
|
|
||||||
color: borderColor,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 캐릭터 정보
|
// 캐릭터 정보
|
||||||
Text(
|
Text(
|
||||||
@@ -528,9 +646,21 @@ class _GameClearDialog extends StatelessWidget {
|
|||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
_buildStat(context, l10n.hofLevel, '${entry.level}'),
|
_buildStat(context, l10n.hofLevel, '${entry.level}'),
|
||||||
_buildStat(context, l10n.hofTime, entry.formattedPlayTime),
|
_buildStat(
|
||||||
_buildStat(context, l10n.hofDeaths, '${entry.totalDeaths}'),
|
context,
|
||||||
_buildStat(context, l10n.hofQuests, '${entry.questsCompleted}'),
|
l10n.hofTime,
|
||||||
|
entry.formattedPlayTime,
|
||||||
|
),
|
||||||
|
_buildStat(
|
||||||
|
context,
|
||||||
|
l10n.hofDeaths,
|
||||||
|
'${entry.totalDeaths}',
|
||||||
|
),
|
||||||
|
_buildStat(
|
||||||
|
context,
|
||||||
|
l10n.hofQuests,
|
||||||
|
'${entry.questsCompleted}',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -817,7 +947,12 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 6,
|
runSpacing: 6,
|
||||||
children: [
|
children: [
|
||||||
_buildStatItem(context, Icons.timer, l10n.hofTime, entry.formattedPlayTime),
|
_buildStatItem(
|
||||||
|
context,
|
||||||
|
Icons.timer,
|
||||||
|
l10n.hofTime,
|
||||||
|
entry.formattedPlayTime,
|
||||||
|
),
|
||||||
_buildStatItem(
|
_buildStatItem(
|
||||||
context,
|
context,
|
||||||
Icons.pest_control,
|
Icons.pest_control,
|
||||||
@@ -860,7 +995,11 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
_buildCombatStatChip(l10n.statStr, '${stats.str}', Colors.red),
|
_buildCombatStatChip(l10n.statStr, '${stats.str}', Colors.red),
|
||||||
_buildCombatStatChip(l10n.statCon, '${stats.con}', Colors.orange),
|
_buildCombatStatChip(l10n.statCon, '${stats.con}', Colors.orange),
|
||||||
_buildCombatStatChip(l10n.statDex, '${stats.dex}', Colors.green),
|
_buildCombatStatChip(l10n.statDex, '${stats.dex}', Colors.green),
|
||||||
_buildCombatStatChip(l10n.statInt, '${stats.intelligence}', Colors.blue),
|
_buildCombatStatChip(
|
||||||
|
l10n.statInt,
|
||||||
|
'${stats.intelligence}',
|
||||||
|
Colors.blue,
|
||||||
|
),
|
||||||
_buildCombatStatChip(l10n.statWis, '${stats.wis}', Colors.purple),
|
_buildCombatStatChip(l10n.statWis, '${stats.wis}', Colors.purple),
|
||||||
_buildCombatStatChip(l10n.statCha, '${stats.cha}', Colors.pink),
|
_buildCombatStatChip(l10n.statCha, '${stats.cha}', Colors.pink),
|
||||||
],
|
],
|
||||||
@@ -873,8 +1012,16 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
spacing: 6,
|
spacing: 6,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
_buildCombatStatChip(l10n.statAtk, '${stats.atk}', Colors.red.shade700),
|
_buildCombatStatChip(
|
||||||
_buildCombatStatChip(l10n.statMAtk, '${stats.magAtk}', Colors.blue.shade700),
|
l10n.statAtk,
|
||||||
|
'${stats.atk}',
|
||||||
|
Colors.red.shade700,
|
||||||
|
),
|
||||||
|
_buildCombatStatChip(
|
||||||
|
l10n.statMAtk,
|
||||||
|
'${stats.magAtk}',
|
||||||
|
Colors.blue.shade700,
|
||||||
|
),
|
||||||
_buildCombatStatChip(
|
_buildCombatStatChip(
|
||||||
l10n.statCri,
|
l10n.statCri,
|
||||||
'${(stats.criRate * 100).toStringAsFixed(1)}%',
|
'${(stats.criRate * 100).toStringAsFixed(1)}%',
|
||||||
@@ -889,7 +1036,11 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
_buildCombatStatChip(l10n.statDef, '${stats.def}', Colors.brown),
|
_buildCombatStatChip(l10n.statDef, '${stats.def}', Colors.brown),
|
||||||
_buildCombatStatChip(l10n.statMDef, '${stats.magDef}', Colors.indigo),
|
_buildCombatStatChip(
|
||||||
|
l10n.statMDef,
|
||||||
|
'${stats.magDef}',
|
||||||
|
Colors.indigo,
|
||||||
|
),
|
||||||
_buildCombatStatChip(
|
_buildCombatStatChip(
|
||||||
l10n.statEva,
|
l10n.statEva,
|
||||||
'${(stats.evasion * 100).toStringAsFixed(1)}%',
|
'${(stats.evasion * 100).toStringAsFixed(1)}%',
|
||||||
@@ -908,8 +1059,16 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
spacing: 6,
|
spacing: 6,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
_buildCombatStatChip(l10n.statHp, '${stats.hpMax}', Colors.red.shade400),
|
_buildCombatStatChip(
|
||||||
_buildCombatStatChip(l10n.statMp, '${stats.mpMax}', Colors.blue.shade400),
|
l10n.statHp,
|
||||||
|
'${stats.hpMax}',
|
||||||
|
Colors.red.shade400,
|
||||||
|
),
|
||||||
|
_buildCombatStatChip(
|
||||||
|
l10n.statMp,
|
||||||
|
'${stats.mpMax}',
|
||||||
|
Colors.blue.shade400,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1021,7 +1180,10 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: rarityColor.withValues(alpha: 0.1),
|
color: rarityColor.withValues(alpha: 0.1),
|
||||||
border: Border.all(color: rarityColor.withValues(alpha: 0.3), width: 1),
|
border: Border.all(
|
||||||
|
color: rarityColor.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1076,11 +1238,7 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
// 스탯 요약
|
// 스탯 요약
|
||||||
if (statSummary.isNotEmpty) ...[
|
if (statSummary.isNotEmpty) ...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Wrap(
|
Wrap(spacing: 6, runSpacing: 2, children: statSummary),
|
||||||
spacing: 6,
|
|
||||||
runSpacing: 2,
|
|
||||||
children: statSummary,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1180,16 +1338,32 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
|
|
||||||
// 확률 스탯
|
// 확률 스탯
|
||||||
if (stats.criRate > 0) {
|
if (stats.criRate > 0) {
|
||||||
addStat(l10n.statCri, '+${(stats.criRate * 100).toStringAsFixed(0)}%', Colors.amber);
|
addStat(
|
||||||
|
l10n.statCri,
|
||||||
|
'+${(stats.criRate * 100).toStringAsFixed(0)}%',
|
||||||
|
Colors.amber,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (stats.blockRate > 0) {
|
if (stats.blockRate > 0) {
|
||||||
addStat(l10n.statBlock, '+${(stats.blockRate * 100).toStringAsFixed(0)}%', Colors.blueGrey);
|
addStat(
|
||||||
|
l10n.statBlock,
|
||||||
|
'+${(stats.blockRate * 100).toStringAsFixed(0)}%',
|
||||||
|
Colors.blueGrey,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (stats.evasion > 0) {
|
if (stats.evasion > 0) {
|
||||||
addStat(l10n.statEva, '+${(stats.evasion * 100).toStringAsFixed(0)}%', Colors.teal);
|
addStat(
|
||||||
|
l10n.statEva,
|
||||||
|
'+${(stats.evasion * 100).toStringAsFixed(0)}%',
|
||||||
|
Colors.teal,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (stats.parryRate > 0) {
|
if (stats.parryRate > 0) {
|
||||||
addStat(l10n.statParry, '+${(stats.parryRate * 100).toStringAsFixed(0)}%', Colors.cyan);
|
addStat(
|
||||||
|
l10n.statParry,
|
||||||
|
'+${(stats.parryRate * 100).toStringAsFixed(0)}%',
|
||||||
|
Colors.cyan,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 보너스 스탯
|
// 보너스 스탯
|
||||||
@@ -1227,10 +1401,7 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: RetroColors.backgroundOf(context),
|
color: RetroColors.backgroundOf(context),
|
||||||
border: Border.all(
|
border: Border.all(color: RetroColors.borderOf(context), width: 1),
|
||||||
color: RetroColors.borderOf(context),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.hofNoSkills.toUpperCase(),
|
l10n.hofNoSkills.toUpperCase(),
|
||||||
@@ -1255,7 +1426,10 @@ class _HallOfFameDetailDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: skillColor.withValues(alpha: 0.1),
|
color: skillColor.withValues(alpha: 0.1),
|
||||||
border: Border.all(color: skillColor.withValues(alpha: 0.3), width: 1),
|
border: Border.all(
|
||||||
|
color: skillColor.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$translatedName $rank',
|
'$translatedName $rank',
|
||||||
|
|||||||
@@ -784,5 +784,4 @@ class _NewCharacterScreenState extends State<NewCharacterScreen> {
|
|||||||
ClassPassiveType.firstStrikeBonus => passive.description,
|
ClassPassiveType.firstStrikeBonus => passive.description,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import 'package:asciineverdie/src/core/animation/race_character_frames.dart';
|
|||||||
/// 새 캐릭터 생성 화면에서 선택한 종족의 idle 애니메이션을 보여줌.
|
/// 새 캐릭터 생성 화면에서 선택한 종족의 idle 애니메이션을 보여줌.
|
||||||
/// RichText 기반 색상 적용.
|
/// RichText 기반 색상 적용.
|
||||||
class RacePreview extends StatefulWidget {
|
class RacePreview extends StatefulWidget {
|
||||||
const RacePreview({
|
const RacePreview({super.key, required this.raceId});
|
||||||
super.key,
|
|
||||||
required this.raceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// 종족 ID (예: "byte_human", "kernel_giant")
|
/// 종족 ID (예: "byte_human", "kernel_giant")
|
||||||
final String raceId;
|
final String raceId;
|
||||||
@@ -121,9 +118,7 @@ class _RacePreviewState extends State<RacePreview> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RichText(
|
return RichText(text: TextSpan(children: spans));
|
||||||
text: TextSpan(children: spans),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 문자별 색상 매핑
|
/// 문자별 색상 매핑
|
||||||
|
|||||||
@@ -122,10 +122,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.settings, color: theme.colorScheme.primary),
|
Icon(Icons.settings, color: theme.colorScheme.primary),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(game_l10n.uiSettings, style: theme.textTheme.titleLarge),
|
||||||
game_l10n.uiSettings,
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
@@ -196,9 +193,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(
|
||||||
fontWeight: FontWeight.bold,
|
context,
|
||||||
),
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -292,7 +289,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
leading: Text(lang.$3, style: const TextStyle(fontSize: 24)),
|
leading: Text(lang.$3, style: const TextStyle(fontSize: 24)),
|
||||||
title: Text(lang.$2),
|
title: Text(lang.$2),
|
||||||
trailing: isSelected
|
trailing: isSelected
|
||||||
? Icon(Icons.check, color: Theme.of(context).colorScheme.primary)
|
? Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
: null,
|
: null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
game_l10n.setGameLocale(lang.$1);
|
game_l10n.setGameLocale(lang.$1);
|
||||||
@@ -331,16 +331,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [Text(label), Text('$percentage%')],
|
||||||
Text(label),
|
|
||||||
Text('$percentage%'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
value: value,
|
|
||||||
onChanged: onChanged,
|
|
||||||
divisions: 10,
|
|
||||||
),
|
),
|
||||||
|
Slider(value: value, onChanged: onChanged, divisions: 10),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user