refactor(ui): mobile_carousel_layout.dart 분할 (1220→689 LOC)
- RetroSelectDialog, RetroOptionItem: 선택 다이얼로그 - RetroSoundDialog: 사운드 설정 다이얼로그 - RetroConfirmDialog: 확인 다이얼로그 - RetroMenuSection, RetroMenuItem, RetroSpeedChip: 메뉴 위젯
This commit is contained in:
@@ -15,7 +15,11 @@ import 'package:asciineverdie/src/features/game/pages/skills_page.dart';
|
||||
import 'package:asciineverdie/src/features/game/pages/story_page.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/carousel_nav_bar.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/dialogs/retro_confirm_dialog.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/dialogs/retro_select_dialog.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/dialogs/retro_sound_dialog.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/enhanced_animation_panel.dart';
|
||||
import 'package:asciineverdie/src/features/game/widgets/menu/retro_menu_widgets.dart';
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart';
|
||||
|
||||
@@ -177,7 +181,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
void _showLanguageDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => _RetroSelectDialog(
|
||||
builder: (context) => RetroSelectDialog(
|
||||
title: l10n.menuLanguage.toUpperCase(),
|
||||
children: [
|
||||
_buildLanguageOption(context, 'en', l10n.languageEnglish, '🇺🇸'),
|
||||
@@ -195,7 +199,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
String flag,
|
||||
) {
|
||||
final isSelected = l10n.currentGameLocale == locale;
|
||||
return _RetroOptionItem(
|
||||
return RetroOptionItem(
|
||||
label: label.toUpperCase(),
|
||||
prefix: flag,
|
||||
isSelected: isSelected,
|
||||
@@ -224,7 +228,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => _RetroSoundDialog(
|
||||
builder: (context, setDialogState) => RetroSoundDialog(
|
||||
bgmVolume: bgmVolume,
|
||||
sfxVolume: sfxVolume,
|
||||
onBgmChanged: (double value) {
|
||||
@@ -244,7 +248,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
void _showDeleteConfirmDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => _RetroConfirmDialog(
|
||||
builder: (context) => RetroConfirmDialog(
|
||||
title: l10n.confirmDeleteTitle.toUpperCase(),
|
||||
message: l10n.confirmDeleteMessage,
|
||||
confirmText: l10n.buttonConfirm.toUpperCase(),
|
||||
@@ -262,14 +266,11 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
Future<void> _showTestCharacterDialog(BuildContext context) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => _RetroConfirmDialog(
|
||||
title: 'CREATE TEST CHARACTER?',
|
||||
message: '현재 캐릭터가 레벨 100으로 변환되어\n'
|
||||
'명예의 전당에 등록됩니다.\n\n'
|
||||
'⚠️ 현재 세이브 파일이 삭제됩니다.\n'
|
||||
'이 작업은 되돌릴 수 없습니다.',
|
||||
confirmText: 'CREATE',
|
||||
cancelText: 'CANCEL',
|
||||
builder: (context) => RetroConfirmDialog(
|
||||
title: L10n.of(context).debugCreateTestCharacterTitle,
|
||||
message: L10n.of(context).debugCreateTestCharacterMessage,
|
||||
confirmText: L10n.of(context).createButton,
|
||||
cancelText: L10n.of(context).cancel.toUpperCase(),
|
||||
onConfirm: () => Navigator.of(context).pop(true),
|
||||
onCancel: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
@@ -326,7 +327,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
Icon(Icons.settings, color: gold, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'OPTIONS',
|
||||
L10n.of(context).optionsTitle,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 14,
|
||||
@@ -350,10 +351,10 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// === 게임 제어 ===
|
||||
const _RetroMenuSection(title: 'CONTROL'),
|
||||
RetroMenuSection(title: L10n.of(context).controlSection),
|
||||
const SizedBox(height: 8),
|
||||
// 일시정지/재개
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: widget.isPaused ? Icons.play_arrow : Icons.pause,
|
||||
iconColor: widget.isPaused
|
||||
? RetroColors.expOf(context)
|
||||
@@ -368,7 +369,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 속도 조절
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.speed,
|
||||
iconColor: gold,
|
||||
label: l10n.menuSpeed.toUpperCase(),
|
||||
@@ -377,10 +378,10 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// === 정보 ===
|
||||
const _RetroMenuSection(title: 'INFO'),
|
||||
RetroMenuSection(title: L10n.of(context).infoSection),
|
||||
const SizedBox(height: 8),
|
||||
if (widget.onShowStatistics != null)
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.bar_chart,
|
||||
iconColor: RetroColors.mpOf(context),
|
||||
label: l10n.uiStatistics.toUpperCase(),
|
||||
@@ -391,7 +392,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
),
|
||||
if (widget.onShowHelp != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.help_outline,
|
||||
iconColor: RetroColors.expOf(context),
|
||||
label: l10n.uiHelp.toUpperCase(),
|
||||
@@ -404,9 +405,9 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// === 설정 ===
|
||||
const _RetroMenuSection(title: 'SETTINGS'),
|
||||
RetroMenuSection(title: L10n.of(context).settingsSection),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.language,
|
||||
iconColor: RetroColors.mpOf(context),
|
||||
label: l10n.menuLanguage.toUpperCase(),
|
||||
@@ -419,7 +420,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
if (widget.onBgmVolumeChange != null ||
|
||||
widget.onSfxVolumeChange != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: widget.bgmVolume == 0 && widget.sfxVolume == 0
|
||||
? Icons.volume_off
|
||||
: Icons.volume_up,
|
||||
@@ -435,9 +436,9 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// === 저장/종료 ===
|
||||
const _RetroMenuSection(title: 'SAVE / EXIT'),
|
||||
RetroMenuSection(title: L10n.of(context).saveExitSection),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.save,
|
||||
iconColor: RetroColors.mpOf(context),
|
||||
label: l10n.menuSave.toUpperCase(),
|
||||
@@ -450,7 +451,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.refresh,
|
||||
iconColor: RetroColors.warningOf(context),
|
||||
label: l10n.menuNewGame.toUpperCase(),
|
||||
@@ -461,7 +462,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.exit_to_app,
|
||||
iconColor: RetroColors.hpOf(context),
|
||||
label: localizations.exitGame.toUpperCase(),
|
||||
@@ -474,38 +475,38 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
// === 치트 섹션 (디버그 모드에서만) ===
|
||||
if (widget.cheatsEnabled) ...[
|
||||
const SizedBox(height: 16),
|
||||
_RetroMenuSection(
|
||||
title: 'DEBUG CHEATS',
|
||||
RetroMenuSection(
|
||||
title: L10n.of(context).debugCheatsTitle,
|
||||
color: RetroColors.hpOf(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.fast_forward,
|
||||
iconColor: RetroColors.hpOf(context),
|
||||
label: 'SKIP TASK (L+1)',
|
||||
subtitle: '태스크 즉시 완료',
|
||||
label: L10n.of(context).debugSkipTask,
|
||||
subtitle: L10n.of(context).debugSkipTaskDesc,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
widget.onCheatTask?.call();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.skip_next,
|
||||
iconColor: RetroColors.hpOf(context),
|
||||
label: 'SKIP QUEST (Q!)',
|
||||
subtitle: '퀘스트 즉시 완료',
|
||||
label: L10n.of(context).debugSkipQuest,
|
||||
subtitle: L10n.of(context).debugSkipQuestDesc,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
widget.onCheatQuest?.call();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.double_arrow,
|
||||
iconColor: RetroColors.hpOf(context),
|
||||
label: 'SKIP ACT (P!)',
|
||||
subtitle: '액트 즉시 완료',
|
||||
label: L10n.of(context).debugSkipAct,
|
||||
subtitle: L10n.of(context).debugSkipActDesc,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
widget.onCheatPlot?.call();
|
||||
@@ -516,16 +517,16 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
// === 디버그 도구 섹션 ===
|
||||
if (kDebugMode && widget.onCreateTestCharacter != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
_RetroMenuSection(
|
||||
title: 'DEBUG TOOLS',
|
||||
RetroMenuSection(
|
||||
title: L10n.of(context).debugToolsTitle,
|
||||
color: RetroColors.warningOf(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_RetroMenuItem(
|
||||
RetroMenuItem(
|
||||
icon: Icons.science,
|
||||
iconColor: RetroColors.warningOf(context),
|
||||
label: 'CREATE TEST CHARACTER',
|
||||
subtitle: '레벨 100 캐릭터를 명예의 전당에 등록',
|
||||
label: L10n.of(context).debugCreateTestCharacter,
|
||||
subtitle: L10n.of(context).debugCreateTestCharacterDesc,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
_showTestCharacterDialog(context);
|
||||
@@ -553,7 +554,7 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
final isSpeedBoostActive = widget.isSpeedBoostActive;
|
||||
final adSpeed = widget.adSpeedMultiplier;
|
||||
|
||||
return _RetroSpeedChip(
|
||||
return RetroSpeedChip(
|
||||
speed: adSpeed,
|
||||
isSelected: isSpeedBoostActive,
|
||||
isAdBased: !isSpeedBoostActive && !widget.isPaidUser,
|
||||
@@ -685,539 +686,4 @@ class _MobileCarouselLayoutState extends State<MobileCarouselLayout> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// 레트로 스타일 옵션 메뉴 위젯들
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// 메뉴 섹션 타이틀
|
||||
class _RetroMenuSection extends StatelessWidget {
|
||||
const _RetroMenuSection({required this.title, this.color});
|
||||
|
||||
final String title;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = color ?? RetroColors.goldOf(context);
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: 4, height: 14, color: gold),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 9,
|
||||
color: gold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 메뉴 아이템
|
||||
class _RetroMenuItem extends StatelessWidget {
|
||||
const _RetroMenuItem({
|
||||
required this.icon,
|
||||
required this.iconColor,
|
||||
required this.label,
|
||||
this.value,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String? value;
|
||||
final String? subtitle;
|
||||
final Widget? trailing;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final border = RetroColors.borderOf(context);
|
||||
final panelBg = RetroColors.panelBgOf(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: panelBg,
|
||||
border: Border.all(color: border, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 12,
|
||||
color: RetroColors.textMutedOf(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (value != null)
|
||||
Text(
|
||||
value!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 14,
|
||||
color: RetroColors.goldOf(context),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 속도 선택 칩
|
||||
class _RetroSpeedChip extends StatelessWidget {
|
||||
const _RetroSpeedChip({
|
||||
required this.speed,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.isAdBased = false,
|
||||
this.isDisabled = false,
|
||||
});
|
||||
|
||||
final int speed;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool isAdBased;
|
||||
|
||||
/// 비활성 상태 (반투명, 탭 무시)
|
||||
final bool isDisabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final warning = RetroColors.warningOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
|
||||
// 비활성 상태면 반투명 처리
|
||||
final opacity = isDisabled ? 0.4 : 1.0;
|
||||
|
||||
final Color bgColor;
|
||||
final Color textColor;
|
||||
final Color borderColor;
|
||||
|
||||
if (isSelected) {
|
||||
bgColor = isAdBased
|
||||
? warning.withValues(alpha: 0.3 * opacity)
|
||||
: gold.withValues(alpha: 0.3 * opacity);
|
||||
textColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
borderColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
} else if (isAdBased) {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = warning.withValues(alpha: opacity);
|
||||
borderColor = warning.withValues(alpha: opacity);
|
||||
} else {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = RetroColors.textMutedOf(context).withValues(alpha: opacity);
|
||||
borderColor = border.withValues(alpha: opacity);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
// 비활성 상태면 탭 무시
|
||||
onTap: isDisabled ? null : onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
border: Border.all(color: borderColor, width: isSelected ? 2 : 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isAdBased && !isSelected && !isDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: Text(
|
||||
'▶',
|
||||
style: TextStyle(fontSize: 7, color: warning),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${speed}x',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 8,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 선택 다이얼로그
|
||||
class _RetroSelectDialog extends StatelessWidget {
|
||||
const _RetroSelectDialog({required this.title, required this.children});
|
||||
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 옵션 목록
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(children: children),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 선택 옵션 아이템
|
||||
class _RetroOptionItem extends StatelessWidget {
|
||||
const _RetroOptionItem({
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.prefix,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final String? prefix;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? gold.withValues(alpha: 0.15) : Colors.transparent,
|
||||
border: Border.all(
|
||||
color: isSelected ? gold : border,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (prefix != null) ...[
|
||||
Text(prefix!, style: const TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 18,
|
||||
color: isSelected ? gold : RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected) Icon(Icons.check, size: 16, color: gold),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 사운드 설정 다이얼로그
|
||||
class _RetroSoundDialog extends StatelessWidget {
|
||||
const _RetroSoundDialog({
|
||||
required this.bgmVolume,
|
||||
required this.sfxVolume,
|
||||
required this.onBgmChanged,
|
||||
required this.onSfxChanged,
|
||||
});
|
||||
|
||||
final double bgmVolume;
|
||||
final double sfxVolume;
|
||||
final ValueChanged<double> onBgmChanged;
|
||||
final ValueChanged<double> onSfxChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
'SOUND',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 슬라이더
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildVolumeSlider(
|
||||
context,
|
||||
icon: bgmVolume == 0 ? Icons.music_off : Icons.music_note,
|
||||
label: 'BGM',
|
||||
value: bgmVolume,
|
||||
onChanged: onBgmChanged,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildVolumeSlider(
|
||||
context,
|
||||
icon: sfxVolume == 0 ? Icons.volume_off : Icons.volume_up,
|
||||
label: 'SFX',
|
||||
value: sfxVolume,
|
||||
onChanged: onSfxChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 확인 버튼
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: RetroTextButton(
|
||||
text: 'OK',
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVolumeSlider(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required double value,
|
||||
required ValueChanged<double> onChanged,
|
||||
}) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
final percentage = (value * 100).round();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: gold),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'$percentage%',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: gold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: 8,
|
||||
activeTrackColor: gold,
|
||||
inactiveTrackColor: border,
|
||||
thumbColor: RetroColors.goldLightOf(context),
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||
overlayColor: gold.withValues(alpha: 0.2),
|
||||
trackShape: const RectangularSliderTrackShape(),
|
||||
),
|
||||
child: Slider(value: value, onChanged: onChanged, divisions: 10),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 확인 다이얼로그
|
||||
class _RetroConfirmDialog extends StatelessWidget {
|
||||
const _RetroConfirmDialog({
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.confirmText,
|
||||
required this.cancelText,
|
||||
required this.onConfirm,
|
||||
required this.onCancel,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String message;
|
||||
final String confirmText;
|
||||
final String cancelText;
|
||||
final VoidCallback onConfirm;
|
||||
final VoidCallback onCancel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 3),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 메시지
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
height: 1.8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 버튼
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RetroTextButton(
|
||||
text: cancelText,
|
||||
isPrimary: false,
|
||||
onPressed: onCancel,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: RetroTextButton(
|
||||
text: confirmText,
|
||||
onPressed: onConfirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart';
|
||||
|
||||
/// 레트로 스타일 확인 다이얼로그
|
||||
class RetroConfirmDialog extends StatelessWidget {
|
||||
const RetroConfirmDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.confirmText,
|
||||
required this.cancelText,
|
||||
required this.onConfirm,
|
||||
required this.onCancel,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String message;
|
||||
final String confirmText;
|
||||
final String cancelText;
|
||||
final VoidCallback onConfirm;
|
||||
final VoidCallback onCancel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 3),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 메시지
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
height: 1.8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 버튼
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RetroTextButton(
|
||||
text: cancelText,
|
||||
isPrimary: false,
|
||||
onPressed: onCancel,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: RetroTextButton(
|
||||
text: confirmText,
|
||||
onPressed: onConfirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
115
lib/src/features/game/widgets/dialogs/retro_select_dialog.dart
Normal file
115
lib/src/features/game/widgets/dialogs/retro_select_dialog.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
|
||||
/// 레트로 스타일 선택 다이얼로그
|
||||
class RetroSelectDialog extends StatelessWidget {
|
||||
const RetroSelectDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 옵션 목록
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(children: children),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 선택 옵션 아이템
|
||||
class RetroOptionItem extends StatelessWidget {
|
||||
const RetroOptionItem({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.prefix,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final String? prefix;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? gold.withValues(alpha: 0.15) : Colors.transparent,
|
||||
border: Border.all(
|
||||
color: isSelected ? gold : border,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (prefix != null) ...[
|
||||
Text(prefix!, style: const TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 18,
|
||||
color: isSelected ? gold : RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected) Icon(Icons.check, size: 16, color: gold),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
lib/src/features/game/widgets/dialogs/retro_sound_dialog.dart
Normal file
146
lib/src/features/game/widgets/dialogs/retro_sound_dialog.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:asciineverdie/l10n/app_localizations.dart';
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
import 'package:asciineverdie/src/shared/widgets/retro_widgets.dart';
|
||||
|
||||
/// 레트로 스타일 사운드 설정 다이얼로그
|
||||
class RetroSoundDialog extends StatelessWidget {
|
||||
const RetroSoundDialog({
|
||||
super.key,
|
||||
required this.bgmVolume,
|
||||
required this.sfxVolume,
|
||||
required this.onBgmChanged,
|
||||
required this.onSfxChanged,
|
||||
});
|
||||
|
||||
final double bgmVolume;
|
||||
final double sfxVolume;
|
||||
final ValueChanged<double> onBgmChanged;
|
||||
final ValueChanged<double> onSfxChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = RetroColors.backgroundOf(context);
|
||||
final gold = RetroColors.goldOf(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border.all(color: gold, width: 2),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 타이틀
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
color: gold.withValues(alpha: 0.2),
|
||||
child: Text(
|
||||
L10n.of(context).soundTitle,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 10,
|
||||
color: gold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 슬라이더
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildVolumeSlider(
|
||||
context,
|
||||
icon: bgmVolume == 0 ? Icons.music_off : Icons.music_note,
|
||||
label: L10n.of(context).bgmLabel,
|
||||
value: bgmVolume,
|
||||
onChanged: onBgmChanged,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildVolumeSlider(
|
||||
context,
|
||||
icon: sfxVolume == 0 ? Icons.volume_off : Icons.volume_up,
|
||||
label: L10n.of(context).sfxLabel,
|
||||
value: sfxVolume,
|
||||
onChanged: onSfxChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 확인 버튼
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: RetroTextButton(
|
||||
text: L10n.of(context).ok,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVolumeSlider(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required double value,
|
||||
required ValueChanged<double> onChanged,
|
||||
}) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
final percentage = (value * 100).round();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: gold),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'$percentage%',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: gold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: 8,
|
||||
activeTrackColor: gold,
|
||||
inactiveTrackColor: border,
|
||||
thumbColor: RetroColors.goldLightOf(context),
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||
overlayColor: gold.withValues(alpha: 0.2),
|
||||
trackShape: const RectangularSliderTrackShape(),
|
||||
),
|
||||
child: Slider(value: value, onChanged: onChanged, divisions: 10),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
204
lib/src/features/game/widgets/menu/retro_menu_widgets.dart
Normal file
204
lib/src/features/game/widgets/menu/retro_menu_widgets.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
|
||||
/// 메뉴 섹션 타이틀
|
||||
class RetroMenuSection extends StatelessWidget {
|
||||
const RetroMenuSection({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = color ?? RetroColors.goldOf(context);
|
||||
return Row(
|
||||
children: [
|
||||
Container(width: 4, height: 14, color: gold),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 9,
|
||||
color: gold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 메뉴 아이템
|
||||
class RetroMenuItem extends StatelessWidget {
|
||||
const RetroMenuItem({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.iconColor,
|
||||
required this.label,
|
||||
this.value,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String? value;
|
||||
final String? subtitle;
|
||||
final Widget? trailing;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final border = RetroColors.borderOf(context);
|
||||
final panelBg = RetroColors.panelBgOf(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: panelBg,
|
||||
border: Border.all(color: border, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 16,
|
||||
color: RetroColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 12,
|
||||
color: RetroColors.textMutedOf(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (value != null)
|
||||
Text(
|
||||
value!,
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 14,
|
||||
color: RetroColors.goldOf(context),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 속도 선택 칩
|
||||
///
|
||||
/// - 배속 토글 버튼
|
||||
/// - 부스트 활성화 중: 반투명, 비활성
|
||||
/// - 부스트 비활성화: 불투명, 활성
|
||||
class RetroSpeedChip extends StatelessWidget {
|
||||
const RetroSpeedChip({
|
||||
super.key,
|
||||
required this.speed,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
this.isAdBased = false,
|
||||
this.isDisabled = false,
|
||||
});
|
||||
|
||||
final int speed;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool isAdBased;
|
||||
|
||||
/// 비활성 상태 (반투명, 탭 무시)
|
||||
final bool isDisabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gold = RetroColors.goldOf(context);
|
||||
final warning = RetroColors.warningOf(context);
|
||||
final border = RetroColors.borderOf(context);
|
||||
|
||||
// 비활성 상태면 반투명 처리
|
||||
final opacity = isDisabled ? 0.4 : 1.0;
|
||||
|
||||
final Color bgColor;
|
||||
final Color textColor;
|
||||
final Color borderColor;
|
||||
|
||||
if (isSelected) {
|
||||
bgColor = isAdBased
|
||||
? warning.withValues(alpha: 0.3 * opacity)
|
||||
: gold.withValues(alpha: 0.3 * opacity);
|
||||
textColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
borderColor = (isAdBased ? warning : gold).withValues(alpha: opacity);
|
||||
} else if (isAdBased) {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = warning.withValues(alpha: opacity);
|
||||
borderColor = warning.withValues(alpha: opacity);
|
||||
} else {
|
||||
bgColor = Colors.transparent;
|
||||
textColor = RetroColors.textMutedOf(context).withValues(alpha: opacity);
|
||||
borderColor = border.withValues(alpha: opacity);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
// 비활성 상태면 탭 무시
|
||||
onTap: isDisabled ? null : onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
border: Border.all(color: borderColor, width: isSelected ? 2 : 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isAdBased && !isSelected && !isDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: Text(
|
||||
'▶',
|
||||
style: TextStyle(fontSize: 7, color: warning),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${speed}x',
|
||||
style: TextStyle(
|
||||
fontFamily: 'PressStart2P',
|
||||
fontSize: 8,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user