refactor(ui): 화면 및 위젯 정리

- GamePlayScreen build() 메서드 분할 (300→15 LOC)
- 애니메이션/프로그레스 패널 개선
- 설정 화면 정리
This commit is contained in:
JiWoong Sul
2026-01-21 17:34:47 +09:00
parent faf87eccb0
commit d9a2fe358c
7 changed files with 275 additions and 294 deletions

View File

@@ -1,5 +1,7 @@
import 'dart:async';
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
/// 알림 타입 (Notification Type)
enum NotificationType {
levelUp, // 레벨업
@@ -62,8 +64,8 @@ class NotificationService {
show(
GameNotification(
type: NotificationType.levelUp,
title: 'LEVEL UP!',
subtitle: 'Level $newLevel',
title: game_l10n.notifyLevelUp,
subtitle: game_l10n.notifyLevel(newLevel),
data: {'level': newLevel},
duration: const Duration(seconds: 2),
),
@@ -75,7 +77,7 @@ class NotificationService {
show(
GameNotification(
type: NotificationType.questComplete,
title: 'QUEST COMPLETE!',
title: game_l10n.notifyQuestComplete,
subtitle: questName,
data: {'quest': questName},
duration: const Duration(seconds: 2),
@@ -87,8 +89,8 @@ class NotificationService {
/// actNumber: 0=프롤로그, 1=Act I, 2=Act II, ...
void showActComplete(int actNumber) {
final title = actNumber == 0
? 'PROLOGUE COMPLETE!'
: 'ACT $actNumber COMPLETE!';
? game_l10n.notifyPrologueComplete
: game_l10n.notifyActComplete(actNumber);
show(
GameNotification(
type: NotificationType.actComplete,
@@ -103,7 +105,7 @@ class NotificationService {
show(
GameNotification(
type: NotificationType.newSpell,
title: 'NEW SPELL!',
title: game_l10n.notifyNewSpell,
subtitle: spellName,
data: {'spell': spellName},
duration: const Duration(seconds: 2),
@@ -116,7 +118,7 @@ class NotificationService {
show(
GameNotification(
type: NotificationType.newEquipment,
title: 'NEW EQUIPMENT!',
title: game_l10n.notifyNewEquipment,
subtitle: equipmentName,
data: {'equipment': equipmentName, 'slot': slot},
duration: const Duration(seconds: 2),
@@ -129,7 +131,7 @@ class NotificationService {
show(
GameNotification(
type: NotificationType.bossDefeat,
title: 'BOSS DEFEATED!',
title: game_l10n.notifyBossDefeated,
subtitle: bossName,
data: {'boss': bossName},
duration: const Duration(seconds: 3),

View File

@@ -217,9 +217,9 @@ class _AnimationPanel extends StatelessWidget {
children: [
const Icon(Icons.auto_awesome, color: RetroColors.gold, size: 18),
const SizedBox(width: 10),
const Text(
'ASCII NEVER DIE',
style: TextStyle(
Text(
L10n.of(context).appTitle.toUpperCase(),
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
color: RetroColors.gold,
@@ -272,7 +272,7 @@ class _ActionButtons extends StatelessWidget {
final l10n = L10n.of(context);
return RetroPanel(
title: 'MENU',
title: L10n.of(context).menuTitle,
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@@ -551,150 +551,135 @@ class _GamePlayScreenState extends State<GamePlayScreen>
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
// 로케일 변경 시 전체 위젯 트리 강제 리빌드를 위한 Key
final localeKey = ValueKey(game_l10n.currentGameLocale);
// 캐로셀 레이아웃 사용 여부 확인
if (_shouldUseCarouselLayout(context)) {
return NotificationOverlay(
key: localeKey,
notificationService: _notificationService,
child: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldPop = await _onPopInvoked();
if (shouldPop && context.mounted) {
await widget.controller.pause(saveOnStop: false);
if (context.mounted) {
Navigator.of(context).pop();
}
}
},
child: Stack(
children: [
MobileCarouselLayout(
state: state,
combatLogEntries: _combatLogController.entries,
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
onSpeedCycle: () {
widget.controller.loop?.cycleSpeed();
setState(() {});
},
onSetSpeed: (speed) {
widget.controller.loop?.setSpeed(speed);
setState(() {});
},
// 특수 애니메이션 중에는 일시정지 상태로 표시하지 않음
isPaused:
!widget.controller.isRunning && _specialAnimation == null,
onPauseToggle: () async {
await widget.controller.togglePause();
setState(() {});
},
onSave: _saveGameState,
onExit: () async {
final shouldExit = await _onPopInvoked();
if (shouldExit && context.mounted) {
await widget.controller.pause(saveOnStop: false);
if (context.mounted) {
Navigator.of(context).pop();
}
}
},
notificationService: _notificationService,
specialAnimation: _specialAnimation,
onLanguageChange: (locale) async {
// navigator 참조를 async gap 전에 저장
final navigator = Navigator.of(context);
// 1. 현재 상태 저장
await widget.controller.pause(saveOnStop: true);
// 2. 로케일 변경
game_l10n.setGameLocale(locale);
// 3. 화면 재생성 (전체 UI 재구성)
if (context.mounted) {
await widget.controller.resume();
navigator.pushReplacement(
MaterialPageRoute<void>(
builder: (_) => GamePlayScreen(
controller: widget.controller,
audioService: widget.audioService,
),
),
);
}
},
onDeleteSaveAndNewGame: () async {
// 게임 루프 중지
await widget.controller.pause(saveOnStop: false);
// 세이브 파일 삭제
await widget.controller.saveManager.deleteSave();
// 캐릭터 생성 화면으로 돌아가기
if (context.mounted) {
Navigator.of(context).pop();
}
},
// 사운드 설정
bgmVolume: _audioController.bgmVolume,
sfxVolume: _audioController.sfxVolume,
onBgmVolumeChange: (volume) {
_audioController.setBgmVolume(volume);
setState(() {});
},
onSfxVolumeChange: (volume) {
_audioController.setSfxVolume(volume);
setState(() {});
},
// 통계 및 도움말
onShowStatistics: () => _showStatisticsDialog(context),
onShowHelp: () => HelpDialog.show(context),
// 치트 (디버그 모드)
cheatsEnabled: widget.controller.cheatsEnabled,
onCheatTask: () => widget.controller.loop?.cheatCompleteTask(),
onCheatQuest: () =>
widget.controller.loop?.cheatCompleteQuest(),
onCheatPlot: () => widget.controller.loop?.cheatCompletePlot(),
onCreateTestCharacter: () async {
final navigator = Navigator.of(context);
final success = await widget.controller.createTestCharacter();
if (success && mounted) {
navigator.popUntil((route) => route.isFirst);
}
},
// 수익화 버프 (자동부활, 광고배속)
autoReviveEndMs: widget.controller.monetization.autoReviveEndMs,
speedBoostEndMs: widget.controller.monetization.speedBoostEndMs,
isPaidUser: widget.controller.monetization.isPaidUser,
onSpeedBoostActivate: _handleSpeedBoost,
isSpeedBoostActive: widget.controller.isSpeedBoostActive,
adSpeedMultiplier: widget.controller.adSpeedMultiplier,
has2xUnlocked: widget.controller.has2xUnlocked,
),
// 사망 오버레이
if (state.isDead && state.deathInfo != null)
DeathOverlay(
deathInfo: state.deathInfo!,
traits: state.traits,
onResurrect: _handleResurrect,
onAdRevive: _handleAdRevive,
isPaidUser: IAPService.instance.isAdRemovalPurchased,
),
// 승리 오버레이 (게임 클리어)
if (widget.controller.isComplete)
VictoryOverlay(
traits: state.traits,
stats: state.stats,
progress: state.progress,
elapsedMs: state.skillSystem.elapsedMs,
onComplete: _handleVictoryComplete,
),
],
),
),
);
return _buildMobileLayout(context, state, localeKey);
}
// 기존 데스크톱 레이아웃 (레트로 스타일)
return _buildDesktopLayout(context, state, localeKey);
}
/// 모바일 캐로셀 레이아웃
Widget _buildMobileLayout(
BuildContext context,
GameState state,
ValueKey<String> localeKey,
) {
return NotificationOverlay(
key: localeKey,
notificationService: _notificationService,
child: PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldPop = await _onPopInvoked();
if (shouldPop && context.mounted) {
await widget.controller.pause(saveOnStop: false);
if (context.mounted) {
Navigator.of(context).pop();
}
}
},
child: Stack(
children: [
MobileCarouselLayout(
state: state,
combatLogEntries: _combatLogController.entries,
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
onSpeedCycle: () {
widget.controller.loop?.cycleSpeed();
setState(() {});
},
onSetSpeed: (speed) {
widget.controller.loop?.setSpeed(speed);
setState(() {});
},
isPaused:
!widget.controller.isRunning && _specialAnimation == null,
onPauseToggle: () async {
await widget.controller.togglePause();
setState(() {});
},
onSave: _saveGameState,
onExit: () async {
final shouldExit = await _onPopInvoked();
if (shouldExit && context.mounted) {
await widget.controller.pause(saveOnStop: false);
if (context.mounted) {
Navigator.of(context).pop();
}
}
},
notificationService: _notificationService,
specialAnimation: _specialAnimation,
onLanguageChange: (locale) async {
final navigator = Navigator.of(context);
await widget.controller.pause(saveOnStop: true);
game_l10n.setGameLocale(locale);
if (context.mounted) {
await widget.controller.resume();
navigator.pushReplacement(
MaterialPageRoute<void>(
builder: (_) => GamePlayScreen(
controller: widget.controller,
audioService: widget.audioService,
),
),
);
}
},
onDeleteSaveAndNewGame: () async {
await widget.controller.pause(saveOnStop: false);
await widget.controller.saveManager.deleteSave();
if (context.mounted) {
Navigator.of(context).pop();
}
},
bgmVolume: _audioController.bgmVolume,
sfxVolume: _audioController.sfxVolume,
onBgmVolumeChange: (volume) {
_audioController.setBgmVolume(volume);
setState(() {});
},
onSfxVolumeChange: (volume) {
_audioController.setSfxVolume(volume);
setState(() {});
},
onShowStatistics: () => _showStatisticsDialog(context),
onShowHelp: () => HelpDialog.show(context),
cheatsEnabled: widget.controller.cheatsEnabled,
onCheatTask: () => widget.controller.loop?.cheatCompleteTask(),
onCheatQuest: () => widget.controller.loop?.cheatCompleteQuest(),
onCheatPlot: () => widget.controller.loop?.cheatCompletePlot(),
onCreateTestCharacter: () async {
final navigator = Navigator.of(context);
final success = await widget.controller.createTestCharacter();
if (success && mounted) {
navigator.popUntil((route) => route.isFirst);
}
},
autoReviveEndMs: widget.controller.monetization.autoReviveEndMs,
speedBoostEndMs: widget.controller.monetization.speedBoostEndMs,
isPaidUser: widget.controller.monetization.isPaidUser,
onSpeedBoostActivate: _handleSpeedBoost,
isSpeedBoostActive: widget.controller.isSpeedBoostActive,
adSpeedMultiplier: widget.controller.adSpeedMultiplier,
has2xUnlocked: widget.controller.has2xUnlocked,
),
..._buildOverlays(state),
],
),
),
);
}
/// 데스크톱 3패널 레이아웃
Widget _buildDesktopLayout(
BuildContext context,
GameState state,
ValueKey<String> localeKey,
) {
return NotificationOverlay(
key: localeKey,
notificationService: _notificationService,
@@ -710,135 +695,16 @@ class _GamePlayScreenState extends State<GamePlayScreen>
}
}
},
// 웹/데스크톱 키보드 단축키 지원
child: Focus(
autofocus: true,
onKeyEvent: (node, event) => _handleKeyboardShortcut(event, context),
child: Scaffold(
backgroundColor: RetroColors.deepBrown,
appBar: AppBar(
backgroundColor: RetroColors.darkBrown,
title: Text(
L10n.of(context).progressQuestTitle(state.traits.name),
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: RetroColors.gold,
),
),
actions: [
// 치트 버튼 (디버그용)
if (widget.controller.cheatsEnabled) ...[
IconButton(
icon: const Text('L+1'),
tooltip: L10n.of(context).levelUp,
onPressed: () =>
widget.controller.loop?.cheatCompleteTask(),
),
IconButton(
icon: const Text('Q!'),
tooltip: L10n.of(context).completeQuest,
onPressed: () =>
widget.controller.loop?.cheatCompleteQuest(),
),
IconButton(
icon: const Text('P!'),
tooltip: L10n.of(context).completePlot,
onPressed: () =>
widget.controller.loop?.cheatCompletePlot(),
),
],
// 통계 버튼
IconButton(
icon: const Icon(Icons.bar_chart),
tooltip: game_l10n.uiStatistics,
onPressed: () => _showStatisticsDialog(context),
),
// 도움말 버튼
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: game_l10n.uiHelp,
onPressed: () => HelpDialog.show(context),
),
// 설정 버튼
IconButton(
icon: const Icon(Icons.settings),
tooltip: game_l10n.uiSettings,
onPressed: () => _showSettingsScreen(context),
),
],
),
appBar: _buildDesktopAppBar(context, state),
body: Stack(
children: [
// 메인 게임 UI
Column(
children: [
// 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트)
TaskProgressPanel(
progress: state.progress,
speedMultiplier:
widget.controller.loop?.speedMultiplier ?? 1,
onSpeedCycle: () {
widget.controller.loop?.cycleSpeed();
setState(() {});
},
// 특수 애니메이션 중에는 일시정지 상태로 표시하지 않음
isPaused:
!widget.controller.isRunning &&
_specialAnimation == null,
onPauseToggle: () async {
await widget.controller.togglePause();
setState(() {});
},
specialAnimation: _specialAnimation,
weaponName: state.equipment.weapon,
shieldName: state.equipment.shield,
characterLevel: state.traits.level,
monsterLevel: state.progress.currentTask.monsterLevel,
monsterGrade: state.progress.currentTask.monsterGrade,
monsterSize: state.progress.currentTask.monsterSize,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,
),
// 메인 3패널 영역
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 좌측 패널: Character Sheet
Expanded(flex: 2, child: _buildCharacterPanel(state)),
// 중앙 패널: Equipment/Inventory
Expanded(flex: 3, child: _buildEquipmentPanel(state)),
// 우측 패널: Plot/Quest
Expanded(flex: 2, child: _buildQuestPanel(state)),
],
),
),
],
),
// 사망 오버레이
if (state.isDead && state.deathInfo != null)
DeathOverlay(
deathInfo: state.deathInfo!,
traits: state.traits,
onResurrect: _handleResurrect,
onAdRevive: _handleAdRevive,
isPaidUser: IAPService.instance.isAdRemovalPurchased,
),
// 승리 오버레이 (게임 클리어)
if (widget.controller.isComplete)
VictoryOverlay(
traits: state.traits,
stats: state.stats,
progress: state.progress,
elapsedMs: state.skillSystem.elapsedMs,
onComplete: _handleVictoryComplete,
),
_buildDesktopMainContent(state),
..._buildOverlays(state),
],
),
),
@@ -847,6 +713,118 @@ class _GamePlayScreenState extends State<GamePlayScreen>
);
}
/// 데스크톱 앱바
PreferredSizeWidget _buildDesktopAppBar(BuildContext context, GameState state) {
return AppBar(
backgroundColor: RetroColors.darkBrown,
title: Text(
L10n.of(context).progressQuestTitle(state.traits.name),
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: RetroColors.gold,
),
),
actions: [
if (widget.controller.cheatsEnabled) ...[
IconButton(
icon: const Text('L+1'),
tooltip: L10n.of(context).levelUp,
onPressed: () => widget.controller.loop?.cheatCompleteTask(),
),
IconButton(
icon: const Text('Q!'),
tooltip: L10n.of(context).completeQuest,
onPressed: () => widget.controller.loop?.cheatCompleteQuest(),
),
IconButton(
icon: const Text('P!'),
tooltip: L10n.of(context).completePlot,
onPressed: () => widget.controller.loop?.cheatCompletePlot(),
),
],
IconButton(
icon: const Icon(Icons.bar_chart),
tooltip: game_l10n.uiStatistics,
onPressed: () => _showStatisticsDialog(context),
),
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: game_l10n.uiHelp,
onPressed: () => HelpDialog.show(context),
),
IconButton(
icon: const Icon(Icons.settings),
tooltip: game_l10n.uiSettings,
onPressed: () => _showSettingsScreen(context),
),
],
);
}
/// 데스크톱 메인 컨텐츠 (3패널)
Widget _buildDesktopMainContent(GameState state) {
return Column(
children: [
TaskProgressPanel(
progress: state.progress,
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
onSpeedCycle: () {
widget.controller.loop?.cycleSpeed();
setState(() {});
},
isPaused: !widget.controller.isRunning && _specialAnimation == null,
onPauseToggle: () async {
await widget.controller.togglePause();
setState(() {});
},
specialAnimation: _specialAnimation,
weaponName: state.equipment.weapon,
shieldName: state.equipment.shield,
characterLevel: state.traits.level,
monsterLevel: state.progress.currentTask.monsterLevel,
monsterGrade: state.progress.currentTask.monsterGrade,
monsterSize: state.progress.currentTask.monsterSize,
latestCombatEvent:
state.progress.currentCombat?.recentEvents.lastOrNull,
raceId: state.traits.raceId,
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(flex: 2, child: _buildCharacterPanel(state)),
Expanded(flex: 3, child: _buildEquipmentPanel(state)),
Expanded(flex: 2, child: _buildQuestPanel(state)),
],
),
),
],
);
}
/// 공통 오버레이 (사망, 승리)
List<Widget> _buildOverlays(GameState state) {
return [
if (state.isDead && state.deathInfo != null)
DeathOverlay(
deathInfo: state.deathInfo!,
traits: state.traits,
onResurrect: _handleResurrect,
onAdRevive: _handleAdRevive,
isPaidUser: IAPService.instance.isAdRemovalPurchased,
),
if (widget.controller.isComplete)
VictoryOverlay(
traits: state.traits,
stats: state.stats,
progress: state.progress,
elapsedMs: state.skillSystem.elapsedMs,
onComplete: _handleVictoryComplete,
),
];
}
/// 키보드 단축키 핸들러 (웹/데스크톱)
/// Space: 일시정지/재개, S: 속도 변경, H: 도움말, Esc: 설정
KeyEventResult _handleKeyboardShortcut(KeyEvent event, BuildContext context) {

View File

@@ -785,7 +785,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
child: AsciiDisintegrateWidget(
characterLines: _deathAnimationMonsterLines!,
duration: const Duration(milliseconds: 800),
textColor: widget.monsterGrade?.displayColor,
textColor: widget.monsterGrade?.displayColorCode != null
? Color(widget.monsterGrade!.displayColorCode!)
: null,
onComplete: _onDeathAnimationComplete,
),
),

View File

@@ -756,9 +756,10 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
final gradePrefix = (isKillTask && grade != null)
? grade.displayPrefix
: '';
final gradeColor = (isKillTask && grade != null)
? grade.displayColor
final gradeColorCode = (isKillTask && grade != null)
? grade.displayColorCode
: null;
final gradeColor = gradeColorCode != null ? Color(gradeColorCode) : null;
return Row(
crossAxisAlignment: CrossAxisAlignment.center,

View File

@@ -169,9 +169,10 @@ class TaskProgressPanel extends StatelessWidget {
final gradePrefix = (isKillTask && grade != null)
? grade.displayPrefix
: '';
final gradeColor = (isKillTask && grade != null)
? grade.displayColor
final gradeColorCode = (isKillTask && grade != null)
? grade.displayColorCode
: null;
final gradeColor = gradeColorCode != null ? Color(gradeColorCode) : null;
return Text.rich(
TextSpan(

View File

@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
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/core/engine/debug_settings_service.dart';
import 'package:asciineverdie/src/core/storage/settings_repository.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart';
@@ -145,7 +146,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
// 디버그 섹션 (디버그 모드에서만 표시)
if (kDebugMode) ...[
const SizedBox(height: 16),
const _RetroSectionTitle(title: 'DEBUG'),
_RetroSectionTitle(title: L10n.of(context).debugTitle),
const SizedBox(height: 8),
_buildDebugSection(context),
],
@@ -308,7 +309,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
const SizedBox(width: 8),
Text(
'DEVELOPER TOOLS',
L10n.of(context).debugDeveloperTools,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
@@ -322,8 +323,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// IAP 시뮬레이션 토글
_RetroDebugToggle(
icon: Icons.shopping_cart,
label: 'IAP PURCHASED',
description: 'ON: 유료 유저로 동작 (광고 제거)',
label: L10n.of(context).debugIapPurchased,
description: L10n.of(context).debugIapPurchasedDesc,
value: _debugIapSimulated,
onChanged: (value) async {
await DebugSettingsService.instance.setIapSimulated(value);
@@ -346,7 +347,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
// 테스트 캐릭터 생성
if (widget.onCreateTestCharacter != null) ...[
Text(
'현재 캐릭터를 레벨 100으로 수정하여\n명예의 전당에 등록합니다.',
L10n.of(context).debugTestCharacterDesc,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
@@ -358,7 +359,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
SizedBox(
width: double.infinity,
child: RetroTextButton(
text: 'CREATE TEST CHARACTER',
text: L10n.of(context).debugCreateTestCharacter,
icon: Icons.science,
onPressed: _handleCreateTestCharacter,
),
@@ -388,7 +389,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'OFFLINE HOURS',
L10n.of(context).debugOfflineHours,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
@@ -397,7 +398,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
const SizedBox(height: 2),
Text(
'복귀 보상 테스트 (재시작 시 적용)',
L10n.of(context).debugOfflineHoursDesc,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 6,
@@ -435,14 +436,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => _RetroConfirmDialog(
title: 'CREATE TEST CHARACTER?',
message:
'현재 캐릭터가 레벨 100으로 변환되어\n'
'명예의 전당에 등록됩니다.\n\n'
'⚠️ 현재 세이브 파일이 삭제됩니다.\n'
'이 작업은 되돌릴 수 없습니다.',
confirmText: 'CREATE',
cancelText: 'CANCEL',
title: L10n.of(context).debugCreateTestCharacterTitle,
message: L10n.of(context).debugCreateTestCharacterMessage,
confirmText: L10n.of(context).createButton,
cancelText: L10n.of(context).cancel.toUpperCase(),
),
);