feat(ui): 도움말 다이얼로그 및 UI 개선

- HelpDialog 추가
- 게임 화면에 통계/도움말 버튼 추가
- CombatLog에 디버프 이벤트 표시
- AudioService mp3 확장자 지원
- 설정 텍스트 l10n 추가
This commit is contained in:
JiWoong Sul
2025-12-30 15:58:40 +09:00
parent d64b9654a3
commit 18af93824b
10 changed files with 1028 additions and 32 deletions

View File

@@ -39,6 +39,10 @@ class MobileCarouselLayout extends StatefulWidget {
this.specialAnimation,
this.currentThemeMode = ThemeMode.system,
this.onThemeModeChange,
this.bgmVolume = 0.7,
this.sfxVolume = 0.8,
this.onBgmVolumeChange,
this.onSfxVolumeChange,
});
final GameState state;
@@ -56,6 +60,18 @@ class MobileCarouselLayout extends StatefulWidget {
final ThemeMode currentThemeMode;
final void Function(ThemeMode mode)? onThemeModeChange;
/// BGM 볼륨 (0.0 ~ 1.0)
final double bgmVolume;
/// SFX 볼륨 (0.0 ~ 1.0)
final double sfxVolume;
/// BGM 볼륨 변경 콜백
final void Function(double volume)? onBgmVolumeChange;
/// SFX 볼륨 변경 콜백
final void Function(double volume)? onSfxVolumeChange;
@override
State<MobileCarouselLayout> createState() => _MobileCarouselLayoutState();
}
@@ -200,6 +216,108 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
);
}
/// 사운드 상태 텍스트 가져오기
String _getSoundStatus() {
final bgmPercent = (widget.bgmVolume * 100).round();
final sfxPercent = (widget.sfxVolume * 100).round();
if (bgmPercent == 0 && sfxPercent == 0) {
return l10n.uiSoundOff;
}
return 'BGM $bgmPercent% / SFX $sfxPercent%';
}
/// 사운드 설정 다이얼로그 표시
void _showSoundDialog(BuildContext context) {
// StatefulBuilder를 사용하여 다이얼로그 내 상태 관리
var bgmVolume = widget.bgmVolume;
var sfxVolume = widget.sfxVolume;
showDialog<void>(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: Text(l10n.uiSound),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// BGM 볼륨
Row(
children: [
Icon(
bgmVolume == 0 ? Icons.music_off : Icons.music_note,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(l10n.uiBgmVolume),
Text('${(bgmVolume * 100).round()}%'),
],
),
Slider(
value: bgmVolume,
onChanged: (value) {
setDialogState(() => bgmVolume = value);
widget.onBgmVolumeChange?.call(value);
},
divisions: 10,
),
],
),
),
],
),
const SizedBox(height: 8),
// SFX 볼륨
Row(
children: [
Icon(
sfxVolume == 0 ? Icons.volume_off : Icons.volume_up,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(l10n.uiSfxVolume),
Text('${(sfxVolume * 100).round()}%'),
],
),
Slider(
value: sfxVolume,
onChanged: (value) {
setDialogState(() => sfxVolume = value);
widget.onSfxVolumeChange?.call(value);
},
divisions: 10,
),
],
),
),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.buttonConfirm),
),
],
),
),
);
}
/// 세이브 삭제 확인 다이얼로그 표시
void _showDeleteConfirmDialog(BuildContext context) {
showDialog<void>(
@@ -324,6 +442,27 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
},
),
// 사운드 설정
if (widget.onBgmVolumeChange != null ||
widget.onSfxVolumeChange != null)
ListTile(
leading: Icon(
widget.bgmVolume == 0 && widget.sfxVolume == 0
? Icons.volume_off
: Icons.volume_up,
color: Colors.indigo,
),
title: Text(l10n.uiSound),
trailing: Text(
_getSoundStatus(),
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
onTap: () {
Navigator.pop(context);
_showSoundDialog(context);
},
),
const Divider(),
// 저장
@@ -381,7 +520,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
actions: [
// 옵션 버튼
IconButton(
icon: const Icon(Icons.more_vert),
icon: const Icon(Icons.settings),
onPressed: () => _showOptionsMenu(context),
tooltip: l10n.menuOptions,
),