Files
asciinevrdie/lib/src/features/hall_of_fame/hero_detail_dialog.dart
JiWoong Sul cbf96c2c0e style(ui): 전체 화면 폰트 크기 증가
- arena: 전투/결과/랭크 화면 폰트 조정
- front: 메인 화면 폰트 조정
- game: 게임플레이 위젯 전반 폰트 조정 (스킬, 장비, 인벤토리 등)
- hall_of_fame: 명예의 전당 폰트 조정
- new_character: 캐릭터 생성 화면 폰트 조정
- settings: 설정 화면 폰트 조정
- 전반적인 가독성 향상
2026-01-15 19:07:34 +09:00

691 lines
20 KiB
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/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: 10,
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: 10,
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: 10,
color: color,
),
),
Text(
value,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
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: 10,
color: goldColor,
),
),
Text(
label,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
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: 10,
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: 10,
color: rarityColor,
),
),
),
],
),
const SizedBox(height: 4),
// 장비 이름
Text(
translatedName,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
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<Widget> _buildStatSummary(ItemStats stats, EquipmentSlot slot) {
final widgets = <Widget>[];
void addStat(String label, String value, Color color) {
widgets.add(
Text(
'$label $value',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
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: 10,
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: 10,
color: skillColor.shade400,
),
),
);
}).toList(),
);
}
}