feat(ui): 게임 화면 및 설정 화면 개선
- GamePlayScreen 개선 - GameSessionController 확장 - MobileCarouselLayout 기능 추가 - SettingsScreen 테스트 기능 추가
This commit is contained in:
@@ -61,7 +61,7 @@ class _AskiiNeverDieAppState extends State<AskiiNeverDieApp> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
const config = PqConfig();
|
const config = PqConfig();
|
||||||
final mutations = GameMutations(config);
|
final mutations = GameMutations(config);
|
||||||
final rewards = RewardService(mutations);
|
final rewards = RewardService(mutations, config);
|
||||||
|
|
||||||
_controller = GameSessionController(
|
_controller = GameSessionController(
|
||||||
progressService: ProgressService(
|
progressService: ProgressService(
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
|
|
||||||
// 사망/엔딩 상태 추적 (BGM 전환용)
|
// 사망/엔딩 상태 추적 (BGM 전환용)
|
||||||
bool _wasDead = false;
|
bool _wasDead = false;
|
||||||
|
|
||||||
|
// 사운드 디바운스 추적 (배속 시 사운드 누락 방지)
|
||||||
|
final Map<String, int> _lastSfxPlayTime = {};
|
||||||
bool _wasComplete = false;
|
bool _wasComplete = false;
|
||||||
|
|
||||||
// 사운드 볼륨 상태 (모바일 설정 UI용)
|
// 사운드 볼륨 상태 (모바일 설정 UI용)
|
||||||
@@ -344,47 +347,53 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
_wasInBattleTask = isInBattleTask;
|
_wasInBattleTask = isInBattleTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 전투 이벤트별 SFX 재생 (채널 분리)
|
/// 전투 이벤트별 SFX 재생 (채널 분리 + 디바운스)
|
||||||
///
|
///
|
||||||
/// 플레이어 이펙트와 몬스터 이펙트를 별도 채널에서 재생하여
|
/// 플레이어 이펙트와 몬스터 이펙트를 별도 채널에서 재생하여
|
||||||
/// 사운드 충돌을 방지하고 완료를 보장합니다.
|
/// 사운드 충돌을 방지하고 완료를 보장합니다.
|
||||||
|
/// 배속 모드에서는 디바운스를 적용하여 사운드 누락을 방지합니다.
|
||||||
void _playCombatEventSfx(CombatEvent event) {
|
void _playCombatEventSfx(CombatEvent event) {
|
||||||
final audio = widget.audioService;
|
final audio = widget.audioService;
|
||||||
if (audio == null) return;
|
if (audio == null) return;
|
||||||
|
|
||||||
switch (event.type) {
|
// 사운드 이름 결정
|
||||||
// 플레이어 채널: 플레이어가 발생시키는 이펙트
|
final sfxName = switch (event.type) {
|
||||||
case CombatEventType.playerAttack:
|
CombatEventType.playerAttack => 'attack',
|
||||||
audio.playPlayerSfx('attack');
|
CombatEventType.playerSkill => 'skill',
|
||||||
case CombatEventType.playerSkill:
|
CombatEventType.playerHeal => 'item',
|
||||||
audio.playPlayerSfx('skill');
|
CombatEventType.playerPotion => 'item',
|
||||||
case CombatEventType.playerHeal:
|
CombatEventType.potionDrop => 'item',
|
||||||
case CombatEventType.playerPotion:
|
CombatEventType.playerBuff => 'skill',
|
||||||
case CombatEventType.potionDrop:
|
CombatEventType.playerDebuff => 'skill',
|
||||||
audio.playPlayerSfx('item');
|
CombatEventType.monsterAttack => 'hit',
|
||||||
case CombatEventType.playerBuff:
|
CombatEventType.playerEvade => 'evade',
|
||||||
case CombatEventType.playerDebuff:
|
CombatEventType.monsterEvade => 'evade',
|
||||||
audio.playPlayerSfx('skill');
|
CombatEventType.playerBlock => 'block',
|
||||||
|
CombatEventType.playerParry => 'parry',
|
||||||
|
CombatEventType.dotTick => null, // DOT 틱은 SFX 없음
|
||||||
|
};
|
||||||
|
|
||||||
// 몬스터 채널: 몬스터가 발생시키는 이펙트 (플레이어 피격)
|
if (sfxName == null) return;
|
||||||
case CombatEventType.monsterAttack:
|
|
||||||
audio.playMonsterSfx('hit');
|
|
||||||
|
|
||||||
// 회피/방어 SFX (Phase 11)
|
// 디바운스 체크 (배속 시 같은 사운드 100ms 내 중복 재생 방지)
|
||||||
case CombatEventType.playerEvade:
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
audio.playPlayerSfx('evade');
|
final lastTime = _lastSfxPlayTime[sfxName] ?? 0;
|
||||||
case CombatEventType.monsterEvade:
|
final speedMultiplier = widget.controller.loop?.speedMultiplier ?? 1;
|
||||||
// 몬스터 회피 = 플레이어 공격 빗나감 (evade SFX)
|
|
||||||
audio.playPlayerSfx('evade');
|
|
||||||
case CombatEventType.playerBlock:
|
|
||||||
audio.playPlayerSfx('block');
|
|
||||||
case CombatEventType.playerParry:
|
|
||||||
audio.playPlayerSfx('parry');
|
|
||||||
|
|
||||||
// SFX 없음
|
// 배속이 높을수록 디바운스 간격 증가 (1x=50ms, 8x=150ms)
|
||||||
case CombatEventType.dotTick:
|
final debounceMs = 50 + (speedMultiplier - 1) * 15;
|
||||||
// DOT 틱은 SFX 없음 (너무 자주 발생)
|
|
||||||
break;
|
if (now - lastTime < debounceMs) {
|
||||||
|
return; // 디바운스 기간 내 → 스킵
|
||||||
|
}
|
||||||
|
_lastSfxPlayTime[sfxName] = now;
|
||||||
|
|
||||||
|
// 채널별 재생
|
||||||
|
final isMonsterSfx = event.type == CombatEventType.monsterAttack;
|
||||||
|
if (isMonsterSfx) {
|
||||||
|
audio.playMonsterSfx(sfxName);
|
||||||
|
} else {
|
||||||
|
audio.playPlayerSfx(sfxName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -743,6 +752,14 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
setState(() => _sfxVolume = volume);
|
setState(() => _sfxVolume = volume);
|
||||||
widget.audioService?.setSfxVolume(volume);
|
widget.audioService?.setSfxVolume(volume);
|
||||||
},
|
},
|
||||||
|
onCreateTestCharacter: () async {
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
final success = await widget.controller.createTestCharacter();
|
||||||
|
if (success && mounted) {
|
||||||
|
// 프론트 화면으로 이동
|
||||||
|
navigator.popUntil((route) => route.isFirst);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,6 +903,13 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
onCheatQuest: () =>
|
onCheatQuest: () =>
|
||||||
widget.controller.loop?.cheatCompleteQuest(),
|
widget.controller.loop?.cheatCompleteQuest(),
|
||||||
onCheatPlot: () => widget.controller.loop?.cheatCompletePlot(),
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
// 사망 오버레이
|
// 사망 오버레이
|
||||||
if (state.isDead && state.deathInfo != null)
|
if (state.isDead && state.deathInfo != null)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:asciineverdie/src/core/engine/progress_loop.dart';
|
|||||||
import 'package:asciineverdie/src/core/engine/progress_service.dart';
|
import 'package:asciineverdie/src/core/engine/progress_service.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/resurrection_service.dart';
|
import 'package:asciineverdie/src/core/engine/resurrection_service.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/shop_service.dart';
|
import 'package:asciineverdie/src/core/engine/shop_service.dart';
|
||||||
|
import 'package:asciineverdie/src/core/engine/test_character_service.dart';
|
||||||
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
import 'package:asciineverdie/src/core/model/combat_stats.dart';
|
||||||
import 'package:asciineverdie/src/core/model/game_state.dart';
|
import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||||
import 'package:asciineverdie/src/core/model/game_statistics.dart';
|
import 'package:asciineverdie/src/core/model/game_statistics.dart';
|
||||||
@@ -367,6 +368,58 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 테스트 캐릭터 생성 (디버그 모드 전용)
|
||||||
|
///
|
||||||
|
/// 현재 캐릭터를 레벨 100, 고급 장비, 다수의 스킬을 가진
|
||||||
|
/// 캐릭터로 변환하여 명예의 전당에 등록하고 세이브를 삭제함.
|
||||||
|
Future<bool> createTestCharacter() async {
|
||||||
|
if (_state == null) {
|
||||||
|
debugPrint('[TestCharacter] _state is null');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
debugPrint('[TestCharacter] Creating test character...');
|
||||||
|
|
||||||
|
// 게임 일시정지
|
||||||
|
await _stopLoop(saveOnStop: false);
|
||||||
|
|
||||||
|
// TestCharacterService로 테스트 캐릭터 생성
|
||||||
|
final testService = TestCharacterService(
|
||||||
|
config: progressService.config,
|
||||||
|
rng: _state!.rng,
|
||||||
|
);
|
||||||
|
|
||||||
|
final entry = testService.createTestCharacter(_state!);
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
'[TestCharacter] Entry created: ${entry.characterName} Lv.${entry.level}',
|
||||||
|
);
|
||||||
|
|
||||||
|
// 명예의 전당에 등록
|
||||||
|
final success = await _hallOfFameStorage.addEntry(entry);
|
||||||
|
debugPrint('[TestCharacter] HallOfFame save result: $success');
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 세이브 파일 삭제
|
||||||
|
final deleteResult = await saveManager.deleteSave();
|
||||||
|
debugPrint('[TestCharacter] Save deleted: ${deleteResult.success}');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태 초기화
|
||||||
|
_state = null;
|
||||||
|
_status = GameSessionStatus.idle;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
debugPrint('[TestCharacter] Complete');
|
||||||
|
return success;
|
||||||
|
} catch (e, st) {
|
||||||
|
debugPrint('[TestCharacter] ERROR: $e');
|
||||||
|
debugPrint('[TestCharacter] StackTrace: $st');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 플레이어 부활 처리 (상태만 업데이트, 게임 재개는 별도로)
|
/// 플레이어 부활 처리 (상태만 업데이트, 게임 재개는 별도로)
|
||||||
///
|
///
|
||||||
/// HP/MP 회복, 빈 슬롯에 장비 자동 구매
|
/// HP/MP 회복, 빈 슬롯에 장비 자동 구매
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:asciineverdie/src/core/notification/notification_service.dart';
|
import 'package:asciineverdie/src/core/notification/notification_service.dart';
|
||||||
@@ -50,6 +51,7 @@ class MobileCarouselLayout extends StatefulWidget {
|
|||||||
this.onCheatTask,
|
this.onCheatTask,
|
||||||
this.onCheatQuest,
|
this.onCheatQuest,
|
||||||
this.onCheatPlot,
|
this.onCheatPlot,
|
||||||
|
this.onCreateTestCharacter,
|
||||||
});
|
});
|
||||||
|
|
||||||
final GameState state;
|
final GameState state;
|
||||||
@@ -97,6 +99,9 @@ class MobileCarouselLayout extends StatefulWidget {
|
|||||||
/// 치트: 액트(플롯) 완료
|
/// 치트: 액트(플롯) 완료
|
||||||
final VoidCallback? onCheatPlot;
|
final VoidCallback? onCheatPlot;
|
||||||
|
|
||||||
|
/// 테스트 캐릭터 생성 콜백 (디버그 모드 전용)
|
||||||
|
final Future<void> Function()? onCreateTestCharacter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MobileCarouselLayout> createState() => _MobileCarouselLayoutState();
|
State<MobileCarouselLayout> createState() => _MobileCarouselLayoutState();
|
||||||
}
|
}
|
||||||
@@ -364,6 +369,39 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 테스트 캐릭터 생성 확인 다이얼로그
|
||||||
|
Future<void> _showTestCharacterDialog(BuildContext context) async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Create Test Character?'),
|
||||||
|
content: const Text(
|
||||||
|
'현재 캐릭터가 레벨 100으로 변환되어 명예의 전당에 등록됩니다.\n\n'
|
||||||
|
'⚠️ 현재 세이브 파일이 삭제됩니다.\n'
|
||||||
|
'이 작업은 되돌릴 수 없습니다.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
child: const Text('Create'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed == true && mounted) {
|
||||||
|
await widget.onCreateTestCharacter?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 옵션 메뉴 표시
|
/// 옵션 메뉴 표시
|
||||||
void _showOptionsMenu(BuildContext context) {
|
void _showOptionsMenu(BuildContext context) {
|
||||||
final localizations = L10n.of(context);
|
final localizations = L10n.of(context);
|
||||||
@@ -609,6 +647,34 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 디버그 도구 섹션 (kDebugMode에서만 표시)
|
||||||
|
if (kDebugMode && widget.onCreateTestCharacter != null) ...[
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'DEBUG TOOLS',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 8,
|
||||||
|
color: Colors.orange.shade300,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.science, color: Colors.orange),
|
||||||
|
title: const Text('Create Test Character'),
|
||||||
|
subtitle: const Text('레벨 100 캐릭터를 명예의 전당에 등록'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_showTestCharacterDialog(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
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 game_l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||||
@@ -15,6 +16,7 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
this.onLocaleChange,
|
this.onLocaleChange,
|
||||||
this.onBgmVolumeChange,
|
this.onBgmVolumeChange,
|
||||||
this.onSfxVolumeChange,
|
this.onSfxVolumeChange,
|
||||||
|
this.onCreateTestCharacter,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SettingsRepository settingsRepository;
|
final SettingsRepository settingsRepository;
|
||||||
@@ -28,6 +30,11 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
/// SFX 볼륨 변경 콜백 (AudioService 연동용)
|
/// SFX 볼륨 변경 콜백 (AudioService 연동용)
|
||||||
final void Function(double volume)? onSfxVolumeChange;
|
final void Function(double volume)? onSfxVolumeChange;
|
||||||
|
|
||||||
|
/// 테스트 캐릭터 생성 콜백 (디버그 모드 전용)
|
||||||
|
///
|
||||||
|
/// 현재 캐릭터를 레벨 100으로 만들어 명예의 전당에 등록하고 세이브 삭제
|
||||||
|
final Future<void> Function()? onCreateTestCharacter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||||
|
|
||||||
@@ -40,6 +47,7 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
void Function(String locale)? onLocaleChange,
|
void Function(String locale)? onLocaleChange,
|
||||||
void Function(double volume)? onBgmVolumeChange,
|
void Function(double volume)? onBgmVolumeChange,
|
||||||
void Function(double volume)? onSfxVolumeChange,
|
void Function(double volume)? onSfxVolumeChange,
|
||||||
|
Future<void> Function()? onCreateTestCharacter,
|
||||||
}) {
|
}) {
|
||||||
return showModalBottomSheet<void>(
|
return showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -57,6 +65,7 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
onLocaleChange: onLocaleChange,
|
onLocaleChange: onLocaleChange,
|
||||||
onBgmVolumeChange: onBgmVolumeChange,
|
onBgmVolumeChange: onBgmVolumeChange,
|
||||||
onSfxVolumeChange: onSfxVolumeChange,
|
onSfxVolumeChange: onSfxVolumeChange,
|
||||||
|
onCreateTestCharacter: onCreateTestCharacter,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -180,6 +189,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
// 정보
|
// 정보
|
||||||
_buildSectionTitle(game_l10n.uiAbout),
|
_buildSectionTitle(game_l10n.uiAbout),
|
||||||
_buildAboutCard(),
|
_buildAboutCard(),
|
||||||
|
|
||||||
|
// 디버그 섹션 (디버그 모드에서만 표시)
|
||||||
|
if (kDebugMode && widget.onCreateTestCharacter != null) ...[
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildSectionTitle('Debug'),
|
||||||
|
_buildDebugSection(),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -188,6 +204,90 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDebugSection() {
|
||||||
|
return Card(
|
||||||
|
color: Theme.of(context).colorScheme.errorContainer.withValues(alpha: 0.3),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.bug_report,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Developer Tools',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'현재 캐릭터를 레벨 100으로 수정하여 명예의 전당에 등록합니다. '
|
||||||
|
'등록 후 현재 세이브 파일이 삭제됩니다.',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: _handleCreateTestCharacter,
|
||||||
|
icon: const Icon(Icons.science),
|
||||||
|
label: const Text('Create Test Character'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleCreateTestCharacter() async {
|
||||||
|
// 확인 다이얼로그 표시
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Create Test Character?'),
|
||||||
|
content: const Text(
|
||||||
|
'현재 캐릭터가 레벨 100으로 변환되어 명예의 전당에 등록됩니다.\n\n'
|
||||||
|
'⚠️ 현재 세이브 파일이 삭제됩니다.\n'
|
||||||
|
'이 작업은 되돌릴 수 없습니다.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
child: const Text('Create'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed == true && mounted) {
|
||||||
|
await widget.onCreateTestCharacter?.call();
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop(); // 설정 화면 닫기
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSectionTitle(String title) {
|
Widget _buildSectionTitle(String title) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
|||||||
Reference in New Issue
Block a user