diff --git a/lib/src/features/hall_of_fame/game_clear_dialog.dart b/lib/src/features/hall_of_fame/game_clear_dialog.dart new file mode 100644 index 0000000..21c4f60 --- /dev/null +++ b/lib/src/features/hall_of_fame/game_clear_dialog.dart @@ -0,0 +1,255 @@ +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; + +/// 게임 클리어 축하 다이얼로그 표시 함수 +Future showGameClearDialog( + BuildContext context, { + required HallOfFameEntry entry, + required VoidCallback onNewGame, + required VoidCallback onViewHallOfFame, +}) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (context) => GameClearDialog( + entry: entry, + onNewGame: onNewGame, + onViewHallOfFame: onViewHallOfFame, + ), + ); +} + +/// 게임 클리어 다이얼로그 위젯 +class GameClearDialog extends StatelessWidget { + const GameClearDialog({ + super.key, + required this.entry, + required this.onNewGame, + required this.onViewHallOfFame, + }); + + final HallOfFameEntry entry; + final VoidCallback onNewGame; + final VoidCallback onViewHallOfFame; + + @override + Widget build(BuildContext context) { + final goldColor = RetroColors.goldOf(context); + final panelBg = RetroColors.panelBgOf(context); + final borderColor = RetroColors.borderOf(context); + + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + constraints: const BoxConstraints(maxWidth: 400), + decoration: BoxDecoration( + color: panelBg, + border: Border( + top: BorderSide(color: goldColor, width: 3), + left: BorderSide(color: goldColor, width: 3), + bottom: BorderSide(color: borderColor, width: 3), + right: BorderSide(color: borderColor, width: 3), + ), + boxShadow: [ + BoxShadow( + color: goldColor.withValues(alpha: 0.4), + blurRadius: 24, + spreadRadius: 4, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 헤더 + Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: goldColor.withValues(alpha: 0.2), + border: Border(bottom: BorderSide(color: goldColor, width: 2)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.emoji_events, color: goldColor, size: 20), + const SizedBox(width: 8), + Text( + l10n.hofVictory.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 12, + color: goldColor, + ), + ), + const SizedBox(width: 8), + Icon(Icons.emoji_events, color: goldColor, size: 20), + ], + ), + ), + // 컨텐츠 + Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + l10n.hofDefeatedGlitchGod, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: RetroColors.textPrimaryOf(context), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Container(height: 2, color: borderColor), + const SizedBox(height: 16), + // 캐릭터 정보 + Text( + entry.characterName, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: goldColor, + ), + ), + const SizedBox(height: 6), + Text( + '${GameDataL10n.getRaceName(context, entry.race)} ' + '${GameDataL10n.getKlassName(context, entry.klass)}', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textSecondaryOf(context), + ), + ), + const SizedBox(height: 16), + // 통계 + Wrap( + spacing: 12, + runSpacing: 8, + alignment: WrapAlignment.center, + children: [ + _buildStat(context, l10n.hofLevel, '${entry.level}'), + _buildStat( + context, + l10n.hofTime, + entry.formattedPlayTime, + ), + _buildStat( + context, + l10n.hofDeaths, + '${entry.totalDeaths}', + ), + _buildStat( + context, + l10n.hofQuests, + '${entry.questsCompleted}', + ), + ], + ), + const SizedBox(height: 16), + Text( + l10n.hofLegendEnshrined, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + fontStyle: FontStyle.italic, + color: goldColor, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + // 버튼 + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () { + Navigator.of(context).pop(); + onViewHallOfFame(); + }, + style: OutlinedButton.styleFrom( + side: BorderSide(color: borderColor, width: 2), + padding: const EdgeInsets.symmetric(vertical: 10), + ), + child: Text( + l10n.hofViewHallOfFame.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textSecondaryOf(context), + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: FilledButton( + onPressed: () { + Navigator.of(context).pop(); + onNewGame(); + }, + style: FilledButton.styleFrom( + backgroundColor: goldColor, + padding: const EdgeInsets.symmetric(vertical: 10), + ), + child: Text( + l10n.hofNewGame.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.backgroundOf(context), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildStat(BuildContext context, String label, String value) { + final goldColor = RetroColors.goldOf(context); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: goldColor.withValues(alpha: 0.1), + border: Border.all(color: goldColor.withValues(alpha: 0.3), width: 1), + ), + child: Column( + children: [ + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: goldColor, + ), + ), + const SizedBox(height: 2), + Text( + label, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: RetroColors.textMutedOf(context), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/hall_of_fame/hall_of_fame_entry_card.dart b/lib/src/features/hall_of_fame/hall_of_fame_entry_card.dart new file mode 100644 index 0000000..2289179 --- /dev/null +++ b/lib/src/features/hall_of_fame/hall_of_fame_entry_card.dart @@ -0,0 +1,263 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; +import 'package:asciineverdie/src/features/hall_of_fame/hero_detail_dialog.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; + +/// 명예의 전당 엔트리 카드 +class HallOfFameEntryCard extends StatelessWidget { + const HallOfFameEntryCard({ + super.key, + required this.entry, + required this.rank, + required this.onDeleteRequest, + }); + + final HallOfFameEntry entry; + final int rank; + final VoidCallback onDeleteRequest; + + void _showDetailDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => HeroDetailDialog(entry: entry), + ); + } + + @override + Widget build(BuildContext context) { + final rankColor = _getRankColor(context, rank); + final rankIcon = _getRankIcon(rank); + final borderColor = RetroColors.borderOf(context); + final panelBg = RetroColors.panelBgOf(context); + + return Container( + margin: const EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + color: panelBg, + border: Border.all(color: borderColor, width: 1), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _showDetailDialog(context), + child: Padding( + padding: const EdgeInsets.all(10), + child: Row( + children: [ + // 순위 + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: rankColor.withValues(alpha: 0.2), + border: Border.all(color: rankColor, width: 2), + ), + child: Center( + child: rankIcon != null + ? Icon(rankIcon, color: rankColor, size: 18) + : Text( + '$rank', + style: TextStyle( + fontFamily: 'PressStart2P', + color: rankColor, + fontSize: 10, + ), + ), + ), + ), + const SizedBox(width: 10), + // 캐릭터 정보 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 이름 + 레벨 + Row( + children: [ + Flexible( + child: Text( + entry.characterName, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: RetroColors.goldOf(context), + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: RetroColors.mpOf( + context, + ).withValues(alpha: 0.2), + border: Border.all( + color: RetroColors.mpOf(context), + width: 1, + ), + ), + child: Text( + 'Lv.${entry.level}', + style: TextStyle( + fontFamily: 'PressStart2P', + color: RetroColors.mpOf(context), + fontSize: 6, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + // 종족/클래스 + Text( + '${GameDataL10n.getRaceName(context, entry.race)} ' + '${GameDataL10n.getKlassName(context, entry.klass)}', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textSecondaryOf(context), + ), + ), + const SizedBox(height: 6), + // 통계 + Row( + children: [ + _buildStatChip( + context, + Icons.timer, + entry.formattedPlayTime, + RetroColors.expOf(context), + ), + const SizedBox(width: 6), + _buildStatChip( + context, + Icons.heart_broken, + '${entry.totalDeaths}', + RetroColors.hpOf(context), + ), + const SizedBox(width: 6), + _buildStatChip( + context, + Icons.check_circle, + '${entry.questsCompleted}Q', + RetroColors.warningOf(context), + ), + ], + ), + ], + ), + ), + // 클리어 날짜 + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Icon( + Icons.calendar_today, + size: 12, + color: RetroColors.textMutedOf(context), + ), + const SizedBox(height: 4), + Text( + entry.formattedClearedDate, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: RetroColors.textMutedOf(context), + ), + ), + ], + ), + // 삭제 버튼 (디버그 모드 전용) + 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), + ), + ), + ), + ), + ], + ], + ), + ), + ), + ), + ); + } + + Widget _buildStatChip( + BuildContext context, + IconData icon, + String value, + Color color, + ) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 10, color: color), + const SizedBox(width: 2), + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: color, + ), + ), + ], + ); + } + + Color _getRankColor(BuildContext context, int rank) { + switch (rank) { + case 1: + return RetroColors.goldOf(context); + case 2: + return Colors.grey.shade400; + case 3: + return Colors.brown.shade400; + default: + return RetroColors.mpOf(context); + } + } + + IconData? _getRankIcon(int rank) { + switch (rank) { + case 1: + return Icons.emoji_events; + case 2: + return Icons.workspace_premium; + case 3: + return Icons.military_tech; + default: + return null; + } + } +} diff --git a/lib/src/features/hall_of_fame/hall_of_fame_screen.dart b/lib/src/features/hall_of_fame/hall_of_fame_screen.dart index 1e1ce4b..015ca90 100644 --- a/lib/src/features/hall_of_fame/hall_of_fame_screen.dart +++ b/lib/src/features/hall_of_fame/hall_of_fame_screen.dart @@ -1,17 +1,17 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; -import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; -import 'package:asciineverdie/src/core/model/equipment_slot.dart'; -import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; -import 'package:asciineverdie/src/core/model/item_stats.dart'; import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart'; -import 'package:asciineverdie/src/features/game/widgets/ascii_animation_card.dart'; +import 'package:asciineverdie/src/features/hall_of_fame/hall_of_fame_entry_card.dart'; import 'package:asciineverdie/src/shared/retro_colors.dart'; import 'package:asciineverdie/src/shared/widgets/retro_dialog.dart'; +// 다른 파일에서 사용할 수 있도록 재export +export 'package:asciineverdie/src/features/hall_of_fame/game_clear_dialog.dart'; +export 'package:asciineverdie/src/features/hall_of_fame/hall_of_fame_entry_card.dart'; +export 'package:asciineverdie/src/features/hall_of_fame/hero_detail_dialog.dart'; + /// 명예의 전당 화면 (Phase 10: Hall of Fame Screen) class HallOfFameScreen extends StatefulWidget { const HallOfFameScreen({super.key}); @@ -165,7 +165,7 @@ class _HallOfFameScreenState extends State { itemCount: hallOfFame.entries.length, itemBuilder: (context, index) { final entry = hallOfFame.entries[index]; - return _HallOfFameEntryCard( + return HallOfFameEntryCard( entry: entry, rank: index + 1, onDeleteRequest: () => _confirmDelete(entry), @@ -262,1185 +262,3 @@ class _HallOfFameScreenState extends State { } } } - -/// 명예의 전당 엔트리 카드 -class _HallOfFameEntryCard extends StatelessWidget { - const _HallOfFameEntryCard({ - required this.entry, - required this.rank, - required this.onDeleteRequest, - }); - - final HallOfFameEntry entry; - final int rank; - final VoidCallback onDeleteRequest; - - void _showDetailDialog(BuildContext context) { - showDialog( - context: context, - builder: (context) => _HallOfFameDetailDialog(entry: entry), - ); - } - - @override - Widget build(BuildContext context) { - final rankColor = _getRankColor(context, rank); - final rankIcon = _getRankIcon(rank); - final borderColor = RetroColors.borderOf(context); - final panelBg = RetroColors.panelBgOf(context); - - return Container( - margin: const EdgeInsets.symmetric(vertical: 4), - decoration: BoxDecoration( - color: panelBg, - border: Border.all(color: borderColor, width: 1), - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => _showDetailDialog(context), - child: Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - // 순위 표시 - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: rankColor.withValues(alpha: 0.2), - shape: BoxShape.circle, - border: Border.all(color: rankColor, width: 2), - ), - child: Center( - child: rankIcon != null - ? Icon(rankIcon, color: rankColor, size: 16) - : Text( - '$rank', - style: TextStyle( - fontFamily: 'PressStart2P', - color: rankColor, - fontSize: 10, - ), - ), - ), - ), - const SizedBox(width: 10), - // 캐릭터 정보 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 이름과 레벨 - Row( - children: [ - Expanded( - child: Text( - entry.characterName, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 8, - color: RetroColors.textPrimaryOf(context), - ), - overflow: TextOverflow.ellipsis, - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: RetroColors.mpOf( - context, - ).withValues(alpha: 0.2), - border: Border.all( - color: RetroColors.mpOf(context), - width: 1, - ), - ), - child: Text( - 'Lv.${entry.level}', - style: TextStyle( - fontFamily: 'PressStart2P', - color: RetroColors.mpOf(context), - fontSize: 6, - ), - ), - ), - ], - ), - const SizedBox(height: 4), - // 종족/클래스 - Text( - '${GameDataL10n.getRaceName(context, entry.race)} ' - '${GameDataL10n.getKlassName(context, entry.klass)}', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textSecondaryOf(context), - ), - ), - const SizedBox(height: 6), - // 통계 - Row( - children: [ - _buildStatChip( - context, - Icons.timer, - entry.formattedPlayTime, - RetroColors.expOf(context), - ), - const SizedBox(width: 6), - _buildStatChip( - context, - Icons.heart_broken, - '${entry.totalDeaths}', - RetroColors.hpOf(context), - ), - const SizedBox(width: 6), - _buildStatChip( - context, - Icons.check_circle, - '${entry.questsCompleted}Q', - RetroColors.warningOf(context), - ), - ], - ), - ], - ), - ), - // 클리어 날짜 - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Icon( - Icons.calendar_today, - size: 12, - color: RetroColors.textMutedOf(context), - ), - const SizedBox(height: 4), - Text( - entry.formattedClearedDate, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: RetroColors.textMutedOf(context), - ), - ), - ], - ), - // 삭제 버튼 (디버그 모드 전용) - 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), - ), - ), - ), - ), - ], - ], - ), - ), - ), - ), - ); - } - - Widget _buildStatChip( - BuildContext context, - IconData icon, - String value, - Color color, - ) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 10, color: color), - const SizedBox(width: 2), - Text( - value, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: color, - ), - ), - ], - ); - } - - Color _getRankColor(BuildContext context, int rank) { - switch (rank) { - case 1: - return RetroColors.goldOf(context); - case 2: - return Colors.grey.shade400; - case 3: - return Colors.brown.shade400; - default: - return RetroColors.mpOf(context); - } - } - - IconData? _getRankIcon(int rank) { - switch (rank) { - case 1: - return Icons.emoji_events; - case 2: - return Icons.workspace_premium; - case 3: - return Icons.military_tech; - default: - return null; - } - } -} - -/// 게임 클리어 축하 다이얼로그 -Future showGameClearDialog( - BuildContext context, { - required HallOfFameEntry entry, - required VoidCallback onNewGame, - required VoidCallback onViewHallOfFame, -}) { - return showDialog( - context: context, - barrierDismissible: false, - builder: (context) => _GameClearDialog( - entry: entry, - onNewGame: onNewGame, - onViewHallOfFame: onViewHallOfFame, - ), - ); -} - -/// 게임 클리어 다이얼로그 위젯 -class _GameClearDialog extends StatelessWidget { - const _GameClearDialog({ - required this.entry, - required this.onNewGame, - required this.onViewHallOfFame, - }); - - final HallOfFameEntry entry; - final VoidCallback onNewGame; - final VoidCallback onViewHallOfFame; - - @override - Widget build(BuildContext context) { - final goldColor = RetroColors.goldOf(context); - final panelBg = RetroColors.panelBgOf(context); - final borderColor = RetroColors.borderOf(context); - - return Dialog( - backgroundColor: Colors.transparent, - child: Container( - constraints: const BoxConstraints(maxWidth: 400), - decoration: BoxDecoration( - color: panelBg, - border: Border( - top: BorderSide(color: goldColor, width: 3), - left: BorderSide(color: goldColor, width: 3), - bottom: BorderSide(color: borderColor, width: 3), - right: BorderSide(color: borderColor, width: 3), - ), - boxShadow: [ - BoxShadow( - color: goldColor.withValues(alpha: 0.4), - blurRadius: 24, - spreadRadius: 4, - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 헤더 - Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: goldColor.withValues(alpha: 0.2), - border: Border(bottom: BorderSide(color: goldColor, width: 2)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.emoji_events, color: goldColor, size: 20), - const SizedBox(width: 8), - Text( - l10n.hofVictory.toUpperCase(), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 12, - color: goldColor, - ), - ), - const SizedBox(width: 8), - Icon(Icons.emoji_events, color: goldColor, size: 20), - ], - ), - ), - // 컨텐츠 - Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Text( - l10n.hofDefeatedGlitchGod, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 7, - color: RetroColors.textPrimaryOf(context), - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - Container(height: 2, color: borderColor), - const SizedBox(height: 16), - // 캐릭터 정보 - Text( - entry.characterName, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 10, - color: goldColor, - ), - ), - const SizedBox(height: 6), - Text( - '${GameDataL10n.getRaceName(context, entry.race)} ' - '${GameDataL10n.getKlassName(context, entry.klass)}', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textSecondaryOf(context), - ), - ), - const SizedBox(height: 16), - // 통계 - Wrap( - spacing: 12, - runSpacing: 8, - alignment: WrapAlignment.center, - children: [ - _buildStat(context, l10n.hofLevel, '${entry.level}'), - _buildStat( - context, - l10n.hofTime, - entry.formattedPlayTime, - ), - _buildStat( - context, - l10n.hofDeaths, - '${entry.totalDeaths}', - ), - _buildStat( - context, - l10n.hofQuests, - '${entry.questsCompleted}', - ), - ], - ), - const SizedBox(height: 16), - Text( - l10n.hofLegendEnshrined, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - fontStyle: FontStyle.italic, - color: goldColor, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - // 버튼 - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () { - Navigator.of(context).pop(); - onViewHallOfFame(); - }, - style: OutlinedButton.styleFrom( - side: BorderSide(color: borderColor, width: 2), - padding: const EdgeInsets.symmetric(vertical: 10), - ), - child: Text( - l10n.hofViewHallOfFame.toUpperCase(), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textSecondaryOf(context), - ), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: FilledButton( - onPressed: () { - Navigator.of(context).pop(); - onNewGame(); - }, - style: FilledButton.styleFrom( - backgroundColor: goldColor, - padding: const EdgeInsets.symmetric(vertical: 10), - ), - child: Text( - l10n.hofNewGame.toUpperCase(), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.backgroundOf(context), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildStat(BuildContext context, String label, String value) { - final goldColor = RetroColors.goldOf(context); - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: goldColor.withValues(alpha: 0.1), - border: Border.all(color: goldColor.withValues(alpha: 0.3), width: 1), - ), - child: Column( - children: [ - Text( - value, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 8, - color: goldColor, - ), - ), - const SizedBox(height: 2), - Text( - label, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: RetroColors.textMutedOf(context), - ), - ), - ], - ), - ); - } -} - -/// 명예의 전당 상세 정보 다이얼로그 -class _HallOfFameDetailDialog extends StatelessWidget { - const _HallOfFameDetailDialog({required this.entry}); - - final HallOfFameEntry entry; - - @override - Widget build(BuildContext context) { - return RetroDialog( - title: entry.characterName, - titleIcon: '👑', - maxWidth: 420, - maxHeight: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // 서브 타이틀 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Text( - '${GameDataL10n.getRaceName(context, entry.race)} ' - '${GameDataL10n.getKlassName(context, entry.klass)} - ' - 'Lv.${entry.level}', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textSecondaryOf(context), - ), - ), - ), - // 스크롤 가능한 컨텐츠 - Flexible( - child: SingleChildScrollView( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // 캐릭터 애니메이션 섹션 - _buildSection( - context, - icon: Icons.movie, - title: l10n.hofCharacterPreview, - child: _buildAnimationPreview(context), - ), - const SizedBox(height: 12), - // 통계 섹션 - _buildSection( - context, - icon: Icons.analytics, - title: l10n.hofStats, - child: _buildStatsGrid(context), - ), - const SizedBox(height: 12), - // 전투 스탯 섹션 - if (entry.finalStats != null) ...[ - _buildSection( - context, - icon: Icons.sports_mma, - title: l10n.hofCombatStats, - child: _buildCombatStatsGrid(context), - ), - const SizedBox(height: 12), - ], - // 장비 섹션 - if (entry.finalEquipment != null) ...[ - _buildSection( - context, - icon: Icons.shield, - title: l10n.navEquipment, - child: _buildEquipmentList(context), - ), - const SizedBox(height: 12), - ], - // 스킬 섹션 (스킬이 없어도 표시) - _buildSection( - context, - icon: Icons.auto_fix_high, - title: l10n.hofSkills, - child: _buildSkillList(context), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildSection( - BuildContext context, { - required IconData icon, - required String title, - required Widget child, - }) { - final goldColor = RetroColors.goldOf(context); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(icon, size: 14, color: goldColor), - const SizedBox(width: 6), - Text( - title.toUpperCase(), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 7, - color: goldColor, - ), - ), - const SizedBox(width: 8), - Expanded( - child: Container( - height: 1, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - goldColor, - goldColor.withValues(alpha: 0.3), - Colors.transparent, - ], - ), - ), - ), - ), - ], - ), - const SizedBox(height: 8), - child, - ], - ); - } - - /// 캐릭터 애니메이션 미리보기 위젯 - Widget _buildAnimationPreview(BuildContext context) { - // 장비에서 무기와 방패 이름 추출 - String? weaponName; - String? shieldName; - - if (entry.finalEquipment != null) { - for (final item in entry.finalEquipment!) { - if (item.slot == EquipmentSlot.weapon && item.isNotEmpty) { - weaponName = item.name; - } else if (item.slot == EquipmentSlot.shield && item.isNotEmpty) { - shieldName = item.name; - } - } - } - - final borderColor = RetroColors.borderOf(context); - - return Center( - child: Container( - decoration: BoxDecoration( - border: Border.all(color: borderColor, width: 1), - ), - child: SizedBox( - width: 360, - height: 80, - child: AsciiAnimationCard( - taskType: TaskType.kill, - raceId: entry.race, - weaponName: weaponName, - shieldName: shieldName, - characterLevel: entry.level, - monsterLevel: entry.level, - monsterBaseName: 'Glitch God', - ), - ), - ), - ); - } - - Widget _buildStatsGrid(BuildContext context) { - return Wrap( - spacing: 8, - runSpacing: 6, - children: [ - _buildStatItem( - context, - Icons.timer, - l10n.hofTime, - entry.formattedPlayTime, - ), - _buildStatItem( - context, - Icons.pest_control, - l10n.hofMonsters, - '${entry.monstersKilled}', - ), - _buildStatItem( - context, - Icons.heart_broken, - l10n.hofDeaths, - '${entry.totalDeaths}', - ), - _buildStatItem( - context, - Icons.check_circle, - l10n.hofQuests, - '${entry.questsCompleted}', - ), - _buildStatItem( - context, - Icons.calendar_today, - l10n.hofCleared, - entry.formattedClearedDate, - ), - ], - ); - } - - Widget _buildCombatStatsGrid(BuildContext context) { - final stats = entry.finalStats!; - final borderColor = RetroColors.borderOf(context); - - return Column( - children: [ - // 기본 스탯 행 - Wrap( - spacing: 6, - runSpacing: 4, - children: [ - _buildCombatStatChip(l10n.statStr, '${stats.str}', Colors.red), - _buildCombatStatChip(l10n.statCon, '${stats.con}', Colors.orange), - _buildCombatStatChip(l10n.statDex, '${stats.dex}', Colors.green), - _buildCombatStatChip( - l10n.statInt, - '${stats.intelligence}', - Colors.blue, - ), - _buildCombatStatChip(l10n.statWis, '${stats.wis}', Colors.purple), - _buildCombatStatChip(l10n.statCha, '${stats.cha}', Colors.pink), - ], - ), - const SizedBox(height: 6), - Container(height: 1, color: borderColor), - const SizedBox(height: 6), - // 공격 스탯 행 - Wrap( - spacing: 6, - runSpacing: 4, - children: [ - _buildCombatStatChip( - l10n.statAtk, - '${stats.atk}', - Colors.red.shade700, - ), - _buildCombatStatChip( - l10n.statMAtk, - '${stats.magAtk}', - Colors.blue.shade700, - ), - _buildCombatStatChip( - l10n.statCri, - '${(stats.criRate * 100).toStringAsFixed(1)}%', - Colors.amber.shade700, - ), - ], - ), - const SizedBox(height: 6), - // 방어 스탯 행 - Wrap( - spacing: 6, - runSpacing: 4, - children: [ - _buildCombatStatChip(l10n.statDef, '${stats.def}', Colors.brown), - _buildCombatStatChip( - l10n.statMDef, - '${stats.magDef}', - Colors.indigo, - ), - _buildCombatStatChip( - l10n.statEva, - '${(stats.evasion * 100).toStringAsFixed(1)}%', - Colors.teal, - ), - _buildCombatStatChip( - l10n.statBlock, - '${(stats.blockRate * 100).toStringAsFixed(1)}%', - Colors.blueGrey, - ), - ], - ), - const SizedBox(height: 6), - // HP/MP 행 - Wrap( - spacing: 6, - runSpacing: 4, - children: [ - _buildCombatStatChip( - l10n.statHp, - '${stats.hpMax}', - Colors.red.shade400, - ), - _buildCombatStatChip( - l10n.statMp, - '${stats.mpMax}', - Colors.blue.shade400, - ), - ], - ), - ], - ); - } - - Widget _buildCombatStatChip(String label, String value, Color color) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), - decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - border: Border.all(color: color.withValues(alpha: 0.3), width: 1), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '$label ', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: color, - ), - ), - Text( - value, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: color, - ), - ), - ], - ), - ); - } - - Widget _buildStatItem( - BuildContext context, - IconData icon, - String label, - String value, - ) { - final goldColor = RetroColors.goldOf(context); - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: goldColor.withValues(alpha: 0.1), - border: Border.all(color: goldColor.withValues(alpha: 0.3), width: 1), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 12, color: goldColor), - const SizedBox(width: 4), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - value, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: goldColor, - ), - ), - Text( - label, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 4, - color: RetroColors.textMutedOf(context), - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildEquipmentList(BuildContext context) { - final equipment = entry.finalEquipment!; - final mutedColor = RetroColors.textMutedOf(context); - - return Column( - children: equipment.map((item) { - if (item.isEmpty) return const SizedBox.shrink(); - - final slotLabel = _getSlotLabel(item.slot); - final slotIcon = _getSlotIcon(item.slot); - final slotIndex = _getSlotIndex(item.slot); - final rarityColor = _getRarityColor(item.rarity); - - // 장비 이름 번역 - final translatedName = GameDataL10n.translateEquipString( - context, - item.name, - slotIndex, - ); - - // 주요 스탯 요약 - final statSummary = _buildStatSummary(item.stats, item.slot); - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 3), - child: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: rarityColor.withValues(alpha: 0.1), - border: Border.all( - color: rarityColor.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 슬롯 + 희귀도 표시 - Row( - children: [ - Icon(slotIcon, size: 12, color: rarityColor), - const SizedBox(width: 4), - Text( - slotLabel, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: mutedColor, - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 1, - ), - decoration: BoxDecoration( - color: rarityColor.withValues(alpha: 0.2), - border: Border.all( - color: rarityColor.withValues(alpha: 0.4), - width: 1, - ), - ), - child: Text( - _getRarityLabel(item.rarity), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 4, - color: rarityColor, - ), - ), - ), - ], - ), - const SizedBox(height: 4), - // 장비 이름 - Text( - translatedName, - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: rarityColor, - ), - ), - // 스탯 요약 - if (statSummary.isNotEmpty) ...[ - const SizedBox(height: 4), - Wrap(spacing: 6, runSpacing: 2, children: statSummary), - ], - ], - ), - ), - ); - }).toList(), - ); - } - - String _getSlotLabel(EquipmentSlot slot) { - return switch (slot) { - EquipmentSlot.weapon => l10n.slotWeapon, - EquipmentSlot.shield => l10n.slotShield, - EquipmentSlot.helm => l10n.slotHelm, - EquipmentSlot.hauberk => l10n.slotHauberk, - EquipmentSlot.brassairts => l10n.slotBrassairts, - EquipmentSlot.vambraces => l10n.slotVambraces, - EquipmentSlot.gauntlets => l10n.slotGauntlets, - EquipmentSlot.gambeson => l10n.slotGambeson, - EquipmentSlot.cuisses => l10n.slotCuisses, - EquipmentSlot.greaves => l10n.slotGreaves, - EquipmentSlot.sollerets => l10n.slotSollerets, - }; - } - - IconData _getSlotIcon(EquipmentSlot slot) { - return switch (slot) { - EquipmentSlot.weapon => Icons.gavel, - EquipmentSlot.shield => Icons.shield, - EquipmentSlot.helm => Icons.sports_mma, - EquipmentSlot.hauberk => Icons.checkroom, - EquipmentSlot.brassairts => Icons.front_hand, - EquipmentSlot.vambraces => Icons.back_hand, - EquipmentSlot.gauntlets => Icons.sports_handball, - EquipmentSlot.gambeson => Icons.dry_cleaning, - EquipmentSlot.cuisses => Icons.airline_seat_legroom_normal, - EquipmentSlot.greaves => Icons.snowshoeing, - EquipmentSlot.sollerets => Icons.do_not_step, - }; - } - - int _getSlotIndex(EquipmentSlot slot) { - return switch (slot) { - EquipmentSlot.weapon => 0, - EquipmentSlot.shield => 1, - _ => 2, - }; - } - - Color _getRarityColor(ItemRarity rarity) { - return switch (rarity) { - ItemRarity.common => Colors.grey.shade600, - ItemRarity.uncommon => Colors.green.shade600, - ItemRarity.rare => Colors.blue.shade600, - ItemRarity.epic => Colors.purple.shade600, - ItemRarity.legendary => Colors.orange.shade700, - }; - } - - String _getRarityLabel(ItemRarity rarity) { - return switch (rarity) { - ItemRarity.common => l10n.rarityCommon, - ItemRarity.uncommon => l10n.rarityUncommon, - ItemRarity.rare => l10n.rarityRare, - ItemRarity.epic => l10n.rarityEpic, - ItemRarity.legendary => l10n.rarityLegendary, - }; - } - - List _buildStatSummary(ItemStats stats, EquipmentSlot slot) { - final widgets = []; - - void addStat(String label, String value, Color color) { - widgets.add( - Text( - '$label $value', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: color, - ), - ), - ); - } - - // 공격 스탯 - if (stats.atk > 0) addStat(l10n.statAtk, '+${stats.atk}', Colors.red); - if (stats.magAtk > 0) { - addStat(l10n.statMAtk, '+${stats.magAtk}', Colors.blue); - } - - // 방어 스탯 - if (stats.def > 0) addStat(l10n.statDef, '+${stats.def}', Colors.brown); - if (stats.magDef > 0) { - addStat(l10n.statMDef, '+${stats.magDef}', Colors.indigo); - } - - // 확률 스탯 - if (stats.criRate > 0) { - addStat( - l10n.statCri, - '+${(stats.criRate * 100).toStringAsFixed(0)}%', - Colors.amber, - ); - } - if (stats.blockRate > 0) { - addStat( - l10n.statBlock, - '+${(stats.blockRate * 100).toStringAsFixed(0)}%', - Colors.blueGrey, - ); - } - if (stats.evasion > 0) { - addStat( - l10n.statEva, - '+${(stats.evasion * 100).toStringAsFixed(0)}%', - Colors.teal, - ); - } - if (stats.parryRate > 0) { - addStat( - l10n.statParry, - '+${(stats.parryRate * 100).toStringAsFixed(0)}%', - Colors.cyan, - ); - } - - // 보너스 스탯 - if (stats.hpBonus > 0) { - addStat(l10n.statHp, '+${stats.hpBonus}', Colors.red.shade400); - } - if (stats.mpBonus > 0) { - addStat(l10n.statMp, '+${stats.mpBonus}', Colors.blue.shade400); - } - - // 능력치 보너스 - if (stats.strBonus > 0) { - addStat(l10n.statStr, '+${stats.strBonus}', Colors.red.shade700); - } - if (stats.conBonus > 0) { - addStat(l10n.statCon, '+${stats.conBonus}', Colors.orange.shade700); - } - if (stats.dexBonus > 0) { - addStat(l10n.statDex, '+${stats.dexBonus}', Colors.green.shade700); - } - if (stats.intBonus > 0) { - addStat(l10n.statInt, '+${stats.intBonus}', Colors.blue.shade700); - } - - return widgets; - } - - Widget _buildSkillList(BuildContext context) { - final skills = entry.finalSkills; - const skillColor = Colors.purple; - - // 스킬이 없는 경우 빈 상태 표시 - if (skills == null || skills.isEmpty) { - return Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: RetroColors.backgroundOf(context), - border: Border.all(color: RetroColors.borderOf(context), width: 1), - ), - child: Text( - l10n.hofNoSkills.toUpperCase(), - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 6, - color: RetroColors.textMutedOf(context), - ), - ), - ); - } - - return Wrap( - spacing: 6, - runSpacing: 4, - children: skills.map((skill) { - final name = skill['name'] ?? ''; - final rank = skill['rank'] ?? ''; - // 스킬 이름 번역 적용 - final translatedName = GameDataL10n.getSpellName(context, name); - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), - decoration: BoxDecoration( - color: skillColor.withValues(alpha: 0.1), - border: Border.all( - color: skillColor.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Text( - '$translatedName $rank', - style: TextStyle( - fontFamily: 'PressStart2P', - fontSize: 5, - color: skillColor.shade400, - ), - ), - ); - }).toList(), - ); - } -} diff --git a/lib/src/features/hall_of_fame/hero_detail_dialog.dart b/lib/src/features/hall_of_fame/hero_detail_dialog.dart new file mode 100644 index 0000000..74b54ce --- /dev/null +++ b/lib/src/features/hall_of_fame/hero_detail_dialog.dart @@ -0,0 +1,690 @@ +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/data/game_text_l10n.dart' as l10n; +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/model/equipment_slot.dart'; +import 'package:asciineverdie/src/core/model/game_state.dart'; +import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; +import 'package:asciineverdie/src/core/model/item_stats.dart'; +import 'package:asciineverdie/src/features/game/widgets/ascii_animation_card.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; +import 'package:asciineverdie/src/shared/widgets/retro_dialog.dart'; + +/// 명예의 전당 상세 정보 다이얼로그 +class HeroDetailDialog extends StatelessWidget { + const HeroDetailDialog({super.key, required this.entry}); + + final HallOfFameEntry entry; + + @override + Widget build(BuildContext context) { + return RetroDialog( + title: entry.characterName, + titleIcon: '👑', + maxWidth: 420, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 서브 타이틀 + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Text( + '${GameDataL10n.getRaceName(context, entry.race)} ' + '${GameDataL10n.getKlassName(context, entry.klass)} - ' + 'Lv.${entry.level}', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textSecondaryOf(context), + ), + ), + ), + // 스크롤 가능한 컨텐츠 + Flexible( + child: SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // 캐릭터 애니메이션 섹션 + _buildSection( + context, + icon: Icons.movie, + title: l10n.hofCharacterPreview, + child: _buildAnimationPreview(context), + ), + const SizedBox(height: 12), + // 통계 섹션 + _buildSection( + context, + icon: Icons.analytics, + title: l10n.hofStats, + child: _buildStatsGrid(context), + ), + const SizedBox(height: 12), + // 전투 스탯 섹션 + if (entry.finalStats != null) ...[ + _buildSection( + context, + icon: Icons.sports_mma, + title: l10n.hofCombatStats, + child: _buildCombatStatsGrid(context), + ), + const SizedBox(height: 12), + ], + // 장비 섹션 + if (entry.finalEquipment != null) ...[ + _buildSection( + context, + icon: Icons.shield, + title: l10n.navEquipment, + child: _buildEquipmentList(context), + ), + const SizedBox(height: 12), + ], + // 스킬 섹션 (스킬이 없어도 표시) + _buildSection( + context, + icon: Icons.auto_fix_high, + title: l10n.hofSkills, + child: _buildSkillList(context), + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildSection( + BuildContext context, { + required IconData icon, + required String title, + required Widget child, + }) { + final goldColor = RetroColors.goldOf(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 14, color: goldColor), + const SizedBox(width: 6), + Text( + title.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 7, + color: goldColor, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Container( + height: 1, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + goldColor, + goldColor.withValues(alpha: 0.3), + Colors.transparent, + ], + ), + ), + ), + ), + ], + ), + const SizedBox(height: 8), + child, + ], + ); + } + + /// 캐릭터 애니메이션 미리보기 위젯 + Widget _buildAnimationPreview(BuildContext context) { + // 장비에서 무기와 방패 이름 추출 + String? weaponName; + String? shieldName; + + if (entry.finalEquipment != null) { + for (final item in entry.finalEquipment!) { + if (item.slot == EquipmentSlot.weapon && item.isNotEmpty) { + weaponName = item.name; + } else if (item.slot == EquipmentSlot.shield && item.isNotEmpty) { + shieldName = item.name; + } + } + } + + final borderColor = RetroColors.borderOf(context); + + return Center( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: borderColor, width: 1), + ), + child: SizedBox( + width: 360, + height: 80, + child: AsciiAnimationCard( + taskType: TaskType.kill, + raceId: entry.race, + weaponName: weaponName, + shieldName: shieldName, + characterLevel: entry.level, + monsterLevel: entry.level, + monsterBaseName: 'Glitch God', + ), + ), + ), + ); + } + + Widget _buildStatsGrid(BuildContext context) { + return Wrap( + spacing: 8, + runSpacing: 6, + children: [ + _buildStatItem( + context, + Icons.timer, + l10n.hofTime, + entry.formattedPlayTime, + ), + _buildStatItem( + context, + Icons.pest_control, + l10n.hofMonsters, + '${entry.monstersKilled}', + ), + _buildStatItem( + context, + Icons.heart_broken, + l10n.hofDeaths, + '${entry.totalDeaths}', + ), + _buildStatItem( + context, + Icons.check_circle, + l10n.hofQuests, + '${entry.questsCompleted}', + ), + _buildStatItem( + context, + Icons.calendar_today, + l10n.hofCleared, + entry.formattedClearedDate, + ), + ], + ); + } + + Widget _buildCombatStatsGrid(BuildContext context) { + final stats = entry.finalStats!; + final borderColor = RetroColors.borderOf(context); + + return Column( + children: [ + // 기본 스탯 행 + Wrap( + spacing: 6, + runSpacing: 4, + children: [ + _buildCombatStatChip(l10n.statStr, '${stats.str}', Colors.red), + _buildCombatStatChip(l10n.statCon, '${stats.con}', Colors.orange), + _buildCombatStatChip(l10n.statDex, '${stats.dex}', Colors.green), + _buildCombatStatChip( + l10n.statInt, + '${stats.intelligence}', + Colors.blue, + ), + _buildCombatStatChip(l10n.statWis, '${stats.wis}', Colors.purple), + _buildCombatStatChip(l10n.statCha, '${stats.cha}', Colors.pink), + ], + ), + const SizedBox(height: 6), + Container(height: 1, color: borderColor), + const SizedBox(height: 6), + // 공격 스탯 행 + Wrap( + spacing: 6, + runSpacing: 4, + children: [ + _buildCombatStatChip( + l10n.statAtk, + '${stats.atk}', + Colors.red.shade700, + ), + _buildCombatStatChip( + l10n.statMAtk, + '${stats.magAtk}', + Colors.blue.shade700, + ), + _buildCombatStatChip( + l10n.statCri, + '${(stats.criRate * 100).toStringAsFixed(1)}%', + Colors.amber.shade700, + ), + ], + ), + const SizedBox(height: 6), + // 방어 스탯 행 + Wrap( + spacing: 6, + runSpacing: 4, + children: [ + _buildCombatStatChip(l10n.statDef, '${stats.def}', Colors.brown), + _buildCombatStatChip( + l10n.statMDef, + '${stats.magDef}', + Colors.indigo, + ), + _buildCombatStatChip( + l10n.statEva, + '${(stats.evasion * 100).toStringAsFixed(1)}%', + Colors.teal, + ), + _buildCombatStatChip( + l10n.statBlock, + '${(stats.blockRate * 100).toStringAsFixed(1)}%', + Colors.blueGrey, + ), + ], + ), + const SizedBox(height: 6), + // HP/MP 행 + Wrap( + spacing: 6, + runSpacing: 4, + children: [ + _buildCombatStatChip( + l10n.statHp, + '${stats.hpMax}', + Colors.red.shade400, + ), + _buildCombatStatChip( + l10n.statMp, + '${stats.mpMax}', + Colors.blue.shade400, + ), + ], + ), + ], + ); + } + + Widget _buildCombatStatChip(String label, String value, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + border: Border.all(color: color.withValues(alpha: 0.3), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$label ', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: color, + ), + ), + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: color, + ), + ), + ], + ), + ); + } + + Widget _buildStatItem( + BuildContext context, + IconData icon, + String label, + String value, + ) { + final goldColor = RetroColors.goldOf(context); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: goldColor.withValues(alpha: 0.1), + border: Border.all(color: goldColor.withValues(alpha: 0.3), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 12, color: goldColor), + const SizedBox(width: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: goldColor, + ), + ), + Text( + label, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 4, + color: RetroColors.textMutedOf(context), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildEquipmentList(BuildContext context) { + final equipment = entry.finalEquipment!; + final mutedColor = RetroColors.textMutedOf(context); + + return Column( + children: equipment.map((item) { + if (item.isEmpty) return const SizedBox.shrink(); + + final slotLabel = _getSlotLabel(item.slot); + final slotIcon = _getSlotIcon(item.slot); + final slotIndex = _getSlotIndex(item.slot); + final rarityColor = _getRarityColor(item.rarity); + + // 장비 이름 번역 + final translatedName = GameDataL10n.translateEquipString( + context, + item.name, + slotIndex, + ); + + // 주요 스탯 요약 + final statSummary = _buildStatSummary(item.stats, item.slot); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 3), + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: rarityColor.withValues(alpha: 0.1), + border: Border.all( + color: rarityColor.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 슬롯 + 희귀도 표시 + Row( + children: [ + Icon(slotIcon, size: 12, color: rarityColor), + const SizedBox(width: 4), + Text( + slotLabel, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: mutedColor, + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 1, + ), + decoration: BoxDecoration( + color: rarityColor.withValues(alpha: 0.2), + border: Border.all( + color: rarityColor.withValues(alpha: 0.4), + width: 1, + ), + ), + child: Text( + _getRarityLabel(item.rarity), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 4, + color: rarityColor, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + // 장비 이름 + Text( + translatedName, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: rarityColor, + ), + ), + // 스탯 요약 + if (statSummary.isNotEmpty) ...[ + const SizedBox(height: 4), + Wrap(spacing: 6, runSpacing: 2, children: statSummary), + ], + ], + ), + ), + ); + }).toList(), + ); + } + + String _getSlotLabel(EquipmentSlot slot) { + return switch (slot) { + EquipmentSlot.weapon => l10n.slotWeapon, + EquipmentSlot.shield => l10n.slotShield, + EquipmentSlot.helm => l10n.slotHelm, + EquipmentSlot.hauberk => l10n.slotHauberk, + EquipmentSlot.brassairts => l10n.slotBrassairts, + EquipmentSlot.vambraces => l10n.slotVambraces, + EquipmentSlot.gauntlets => l10n.slotGauntlets, + EquipmentSlot.gambeson => l10n.slotGambeson, + EquipmentSlot.cuisses => l10n.slotCuisses, + EquipmentSlot.greaves => l10n.slotGreaves, + EquipmentSlot.sollerets => l10n.slotSollerets, + }; + } + + IconData _getSlotIcon(EquipmentSlot slot) { + return switch (slot) { + EquipmentSlot.weapon => Icons.gavel, + EquipmentSlot.shield => Icons.shield, + EquipmentSlot.helm => Icons.sports_mma, + EquipmentSlot.hauberk => Icons.checkroom, + EquipmentSlot.brassairts => Icons.front_hand, + EquipmentSlot.vambraces => Icons.back_hand, + EquipmentSlot.gauntlets => Icons.sports_handball, + EquipmentSlot.gambeson => Icons.dry_cleaning, + EquipmentSlot.cuisses => Icons.airline_seat_legroom_normal, + EquipmentSlot.greaves => Icons.snowshoeing, + EquipmentSlot.sollerets => Icons.do_not_step, + }; + } + + int _getSlotIndex(EquipmentSlot slot) { + return switch (slot) { + EquipmentSlot.weapon => 0, + EquipmentSlot.shield => 1, + _ => 2, + }; + } + + Color _getRarityColor(ItemRarity rarity) { + return switch (rarity) { + ItemRarity.common => Colors.grey.shade600, + ItemRarity.uncommon => Colors.green.shade600, + ItemRarity.rare => Colors.blue.shade600, + ItemRarity.epic => Colors.purple.shade600, + ItemRarity.legendary => Colors.orange.shade700, + }; + } + + String _getRarityLabel(ItemRarity rarity) { + return switch (rarity) { + ItemRarity.common => l10n.rarityCommon, + ItemRarity.uncommon => l10n.rarityUncommon, + ItemRarity.rare => l10n.rarityRare, + ItemRarity.epic => l10n.rarityEpic, + ItemRarity.legendary => l10n.rarityLegendary, + }; + } + + List _buildStatSummary(ItemStats stats, EquipmentSlot slot) { + final widgets = []; + + void addStat(String label, String value, Color color) { + widgets.add( + Text( + '$label $value', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: color, + ), + ), + ); + } + + // 공격 스탯 + if (stats.atk > 0) addStat(l10n.statAtk, '+${stats.atk}', Colors.red); + if (stats.magAtk > 0) { + addStat(l10n.statMAtk, '+${stats.magAtk}', Colors.blue); + } + + // 방어 스탯 + if (stats.def > 0) addStat(l10n.statDef, '+${stats.def}', Colors.brown); + if (stats.magDef > 0) { + addStat(l10n.statMDef, '+${stats.magDef}', Colors.indigo); + } + + // 확률 스탯 + if (stats.criRate > 0) { + addStat( + l10n.statCri, + '+${(stats.criRate * 100).toStringAsFixed(0)}%', + Colors.amber, + ); + } + if (stats.blockRate > 0) { + addStat( + l10n.statBlock, + '+${(stats.blockRate * 100).toStringAsFixed(0)}%', + Colors.blueGrey, + ); + } + if (stats.evasion > 0) { + addStat( + l10n.statEva, + '+${(stats.evasion * 100).toStringAsFixed(0)}%', + Colors.teal, + ); + } + if (stats.parryRate > 0) { + addStat( + l10n.statParry, + '+${(stats.parryRate * 100).toStringAsFixed(0)}%', + Colors.cyan, + ); + } + + // 보너스 스탯 + if (stats.hpBonus > 0) { + addStat(l10n.statHp, '+${stats.hpBonus}', Colors.red.shade400); + } + if (stats.mpBonus > 0) { + addStat(l10n.statMp, '+${stats.mpBonus}', Colors.blue.shade400); + } + + // 능력치 보너스 + if (stats.strBonus > 0) { + addStat(l10n.statStr, '+${stats.strBonus}', Colors.red.shade700); + } + if (stats.conBonus > 0) { + addStat(l10n.statCon, '+${stats.conBonus}', Colors.orange.shade700); + } + if (stats.dexBonus > 0) { + addStat(l10n.statDex, '+${stats.dexBonus}', Colors.green.shade700); + } + if (stats.intBonus > 0) { + addStat(l10n.statInt, '+${stats.intBonus}', Colors.blue.shade700); + } + + return widgets; + } + + Widget _buildSkillList(BuildContext context) { + final skills = entry.finalSkills; + const skillColor = Colors.purple; + + // 스킬이 없는 경우 빈 상태 표시 + if (skills == null || skills.isEmpty) { + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: RetroColors.backgroundOf(context), + border: Border.all(color: RetroColors.borderOf(context), width: 1), + ), + child: Text( + l10n.hofNoSkills.toUpperCase(), + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 6, + color: RetroColors.textMutedOf(context), + ), + ), + ); + } + + return Wrap( + spacing: 6, + runSpacing: 4, + children: skills.map((skill) { + final name = skill['name'] ?? ''; + final rank = skill['rank'] ?? ''; + // 스킬 이름 번역 적용 + final translatedName = GameDataL10n.getSpellName(context, name); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: skillColor.withValues(alpha: 0.1), + border: Border.all( + color: skillColor.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Text( + '$translatedName $rank', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 5, + color: skillColor.shade400, + ), + ), + ); + }).toList(), + ); + } +}