import 'package:flutter/material.dart'; import 'package:askiineverdie/data/game_text_l10n.dart' as l10n; import 'package:askiineverdie/src/core/engine/item_service.dart'; import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart'; import 'package:askiineverdie/src/core/model/equipment_item.dart'; import 'package:askiineverdie/src/core/model/equipment_slot.dart'; import 'package:askiineverdie/src/core/model/game_state.dart'; import 'package:askiineverdie/src/core/model/item_stats.dart'; import 'package:askiineverdie/src/shared/retro_colors.dart'; /// 장비 스탯 표시 패널 /// /// 각 장비 슬롯의 아이템과 스탯을 확장 가능한 형태로 표시. /// 접힌 상태: 슬롯명 + 아이템명 /// 펼친 상태: 전체 스탯 및 점수 class EquipmentStatsPanel extends StatelessWidget { const EquipmentStatsPanel({ super.key, required this.equipment, this.initiallyExpanded = false, }); final Equipment equipment; final bool initiallyExpanded; @override Widget build(BuildContext context) { final totalScore = _calculateTotalScore(); final equippedCount = equipment.items.where((e) => e.isNotEmpty).length; return ListView.builder( // +1 for header itemCount: equipment.items.length + 1, padding: const EdgeInsets.all(4), itemBuilder: (context, index) { // 첫 번째 아이템은 총합 헤더 if (index == 0) { return _TotalScoreHeader( totalScore: totalScore, equippedCount: equippedCount, totalSlots: equipment.items.length, ); } final item = equipment.items[index - 1]; return _EquipmentSlotTile( item: item, initiallyExpanded: initiallyExpanded, ); }, ); } /// 모든 장비의 점수 합산 int _calculateTotalScore() { var total = 0; for (final item in equipment.items) { if (item.isNotEmpty) { total += ItemService.calculateEquipmentScore(item); } } return total; } } /// 개별 장비 슬롯 타일 class _EquipmentSlotTile extends StatelessWidget { const _EquipmentSlotTile({ required this.item, this.initiallyExpanded = false, }); final EquipmentItem item; final bool initiallyExpanded; @override Widget build(BuildContext context) { if (item.isEmpty) { return _EmptySlotTile(slot: item.slot); } final score = ItemService.calculateEquipmentScore(item); final rarityColor = _getRarityColor(item.rarity); // 슬롯 인덱스로 아이템 이름 번역 (0: weapon, 1: shield, 2+: armor) final translatedName = GameDataL10n.translateEquipString( context, item.name, item.slot.index, ); return ExpansionTile( initiallyExpanded: initiallyExpanded, tilePadding: const EdgeInsets.symmetric(horizontal: 8), childrenPadding: const EdgeInsets.only(left: 16, right: 8, bottom: 8), dense: true, title: Row( children: [ _SlotIcon(slot: item.slot), const SizedBox(width: 4), Expanded( child: Text( translatedName, style: TextStyle( fontSize: 11, color: rarityColor, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), _ScoreBadge(score: score), ], ), children: [ _StatsGrid(stats: item.stats, slot: item.slot), const SizedBox(height: 4), _ItemMetaRow(item: item), ], ); } Color _getRarityColor(ItemRarity rarity) { return switch (rarity) { ItemRarity.common => Colors.grey, ItemRarity.uncommon => Colors.green, ItemRarity.rare => Colors.blue, ItemRarity.epic => Colors.purple, ItemRarity.legendary => Colors.orange, }; } } /// 빈 슬롯 타일 (레트로 스타일) class _EmptySlotTile extends StatelessWidget { const _EmptySlotTile({required this.slot}); final EquipmentSlot slot; @override Widget build(BuildContext context) { final textMuted = RetroColors.textMutedOf(context); return ListTile( dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 8), leading: _SlotIcon(slot: slot, isEmpty: true), title: Text( '[${_getSlotName(slot)}] ${l10n.uiEmpty}', style: TextStyle( fontSize: 11, color: textMuted, fontStyle: FontStyle.italic, ), ), ); } } /// 슬롯 아이콘 (레트로 스타일) class _SlotIcon extends StatelessWidget { const _SlotIcon({required this.slot, this.isEmpty = false}); final EquipmentSlot slot; final bool isEmpty; @override Widget build(BuildContext context) { final icon = switch (slot) { EquipmentSlot.weapon => Icons.gavel, EquipmentSlot.shield => Icons.shield, EquipmentSlot.helm => Icons.sports_martial_arts, EquipmentSlot.hauberk => Icons.checkroom, EquipmentSlot.brassairts => Icons.back_hand, EquipmentSlot.vambraces => Icons.front_hand, EquipmentSlot.gauntlets => Icons.pan_tool, EquipmentSlot.gambeson => Icons.dry_cleaning, EquipmentSlot.cuisses => Icons.airline_seat_legroom_normal, EquipmentSlot.greaves => Icons.snowshoeing, EquipmentSlot.sollerets => Icons.do_not_step, }; final color = isEmpty ? RetroColors.textMutedOf(context) : RetroColors.textSecondaryOf(context); return Icon(icon, size: 16, color: color); } } /// 점수 배지 (레트로 스타일) class _ScoreBadge extends StatelessWidget { const _ScoreBadge({required this.score}); final int score; @override Widget build(BuildContext context) { final gold = RetroColors.goldOf(context); final surface = RetroColors.surfaceOf(context); return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: surface, border: Border.all(color: gold, width: 1), borderRadius: BorderRadius.circular(4), ), child: Text( '$score', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, color: gold, ), ), ); } } /// 장비 점수 총합 헤더 (레트로 스타일) class _TotalScoreHeader extends StatelessWidget { const _TotalScoreHeader({ required this.totalScore, required this.equippedCount, required this.totalSlots, }); final int totalScore; final int equippedCount; final int totalSlots; @override Widget build(BuildContext context) { final gold = RetroColors.goldOf(context); final goldDark = RetroColors.goldDarkOf(context); final panelBg = RetroColors.panelBgOf(context); final border = RetroColors.borderOf(context); final textPrimary = RetroColors.textPrimaryOf(context); final textSecondary = RetroColors.textSecondaryOf(context); return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: panelBg, border: Border( top: BorderSide(color: gold, width: 2), left: BorderSide(color: gold, width: 2), bottom: BorderSide(color: goldDark, width: 2), right: BorderSide(color: goldDark, width: 2), ), ), child: Row( children: [ // 장비 아이콘 Icon(Icons.shield, size: 20, color: gold), const SizedBox(width: 8), // 총합 점수 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.uiEquipmentScore, style: TextStyle(fontSize: 10, color: textSecondary), ), Text( '$totalScore', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: gold, ), ), ], ), ), // 장착 현황 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: border, borderRadius: BorderRadius.circular(4), ), child: Text( '$equippedCount / $totalSlots', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, color: textPrimary, ), ), ), ], ), ); } } /// 스탯 그리드 class _StatsGrid extends StatelessWidget { const _StatsGrid({required this.stats, required this.slot}); final ItemStats stats; final EquipmentSlot slot; @override Widget build(BuildContext context) { final entries = <_StatEntry>[]; // 공격 스탯 if (stats.atk > 0) entries.add(_StatEntry(l10n.statAtk, '+${stats.atk}')); if (stats.magAtk > 0) { entries.add(_StatEntry(l10n.statMAtk, '+${stats.magAtk}')); } if (stats.criRate > 0) { entries.add( _StatEntry( l10n.statCri, '${(stats.criRate * 100).toStringAsFixed(1)}%', ), ); } if (stats.parryRate > 0) { entries.add( _StatEntry( l10n.statParry, '${(stats.parryRate * 100).toStringAsFixed(1)}%', ), ); } // 방어 스탯 if (stats.def > 0) entries.add(_StatEntry(l10n.statDef, '+${stats.def}')); if (stats.magDef > 0) { entries.add(_StatEntry(l10n.statMDef, '+${stats.magDef}')); } if (stats.blockRate > 0) { entries.add( _StatEntry( l10n.statBlock, '${(stats.blockRate * 100).toStringAsFixed(1)}%', ), ); } if (stats.evasion > 0) { entries.add( _StatEntry( l10n.statEva, '${(stats.evasion * 100).toStringAsFixed(1)}%', ), ); } // 자원 스탯 if (stats.hpBonus > 0) { entries.add(_StatEntry(l10n.statHp, '+${stats.hpBonus}')); } if (stats.mpBonus > 0) { entries.add(_StatEntry(l10n.statMp, '+${stats.mpBonus}')); } // 능력치 보너스 if (stats.strBonus > 0) { entries.add(_StatEntry(l10n.statStr, '+${stats.strBonus}')); } if (stats.conBonus > 0) { entries.add(_StatEntry(l10n.statCon, '+${stats.conBonus}')); } if (stats.dexBonus > 0) { entries.add(_StatEntry(l10n.statDex, '+${stats.dexBonus}')); } if (stats.intBonus > 0) { entries.add(_StatEntry(l10n.statInt, '+${stats.intBonus}')); } if (stats.wisBonus > 0) { entries.add(_StatEntry(l10n.statWis, '+${stats.wisBonus}')); } if (stats.chaBonus > 0) { entries.add(_StatEntry(l10n.statCha, '+${stats.chaBonus}')); } // 무기 공속 if (slot == EquipmentSlot.weapon && stats.attackSpeed > 0) { entries.add(_StatEntry(l10n.statSpeed, '${stats.attackSpeed}ms')); } if (entries.isEmpty) { return Text( l10n.uiNoBonusStats, style: TextStyle(fontSize: 10, color: RetroColors.textMutedOf(context)), ); } return Wrap( spacing: 8, runSpacing: 4, children: entries.map((e) => _StatChip(entry: e)).toList(), ); } } /// 스탯 엔트리 class _StatEntry { const _StatEntry(this.label, this.value); final String label; final String value; } /// 스탯 칩 (레트로 스타일) class _StatChip extends StatelessWidget { const _StatChip({required this.entry}); final _StatEntry entry; @override Widget build(BuildContext context) { final surface = RetroColors.surfaceOf(context); final border = RetroColors.borderOf(context); final textMuted = RetroColors.textMutedOf(context); final textPrimary = RetroColors.textPrimaryOf(context); return Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( color: surface, border: Border.all(color: border, width: 1), borderRadius: BorderRadius.circular(2), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( '${entry.label}: ', style: TextStyle(fontSize: 9, color: textMuted), ), Text( entry.value, style: TextStyle( fontSize: 9, fontWeight: FontWeight.bold, color: textPrimary, ), ), ], ), ); } } /// 아이템 메타 정보 행 (레트로 스타일) class _ItemMetaRow extends StatelessWidget { const _ItemMetaRow({required this.item}); final EquipmentItem item; @override Widget build(BuildContext context) { final rarityName = _getTranslatedRarity(item.rarity); final textMuted = RetroColors.textMutedOf(context); return Row( children: [ Text( l10n.uiLevel(item.level), style: TextStyle(fontSize: 9, color: textMuted), ), const SizedBox(width: 8), Text( rarityName, style: TextStyle( fontSize: 9, color: _getRarityColor(item.rarity), fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), Text( l10n.uiWeight(item.weight), style: TextStyle(fontSize: 9, color: textMuted), ), ], ); } Color _getRarityColor(ItemRarity rarity) { return switch (rarity) { ItemRarity.common => Colors.grey, ItemRarity.uncommon => Colors.green, ItemRarity.rare => Colors.blue, ItemRarity.epic => Colors.purple, ItemRarity.legendary => Colors.orange, }; } } /// 슬롯 이름 반환 String _getSlotName(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, }; } /// 희귀도 번역 반환 String _getTranslatedRarity(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, }; }