Files
asciinevrdie/lib/src/features/arena/widgets/arena_result_dialog.dart
JiWoong Sul a2e93efc97 feat(arena): 아레나 화면 구현
- ArenaScreen: 아레나 메인 화면
- ArenaSetupScreen: 전투 설정 화면
- ArenaBattleScreen: 전투 진행 화면
- 관련 위젯 추가
2026-01-06 17:55:02 +09:00

438 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
import 'package:asciineverdie/src/core/engine/item_service.dart';
import 'package:asciineverdie/src/core/model/arena_match.dart';
import 'package:asciineverdie/src/core/model/equipment_item.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart';
// 아레나 관련 임시 문자열 (추후 l10n으로 이동)
const _arenaVictory = 'VICTORY!';
const _arenaDefeat = 'DEFEAT...';
const _arenaExchange = 'EQUIPMENT EXCHANGE';
/// 아레나 결과 다이얼로그
///
/// 전투 승패 및 장비 교환 결과 표시
class ArenaResultDialog extends StatelessWidget {
const ArenaResultDialog({
super.key,
required this.result,
required this.onClose,
});
/// 대전 결과
final ArenaMatchResult result;
/// 닫기 콜백
final VoidCallback onClose;
@override
Widget build(BuildContext context) {
final isVictory = result.isVictory;
final resultColor = isVictory ? Colors.amber : Colors.red;
final slot = result.match.bettingSlot;
return AlertDialog(
backgroundColor: RetroColors.panelBgOf(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: resultColor, width: 2),
),
title: _buildTitle(context, isVictory, resultColor),
content: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 350),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 전투 정보
_buildBattleInfo(context),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
// 장비 교환 결과
_buildExchangeResult(context, slot),
],
),
),
actions: [
FilledButton(
onPressed: onClose,
style: FilledButton.styleFrom(
backgroundColor: resultColor,
),
child: Text(
l10n.buttonConfirm,
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
),
),
),
],
);
}
Widget _buildTitle(BuildContext context, bool isVictory, Color color) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isVictory ? Icons.emoji_events : Icons.sentiment_very_dissatisfied,
color: color,
size: 28,
),
const SizedBox(width: 8),
Text(
isVictory ? _arenaVictory : _arenaDefeat,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 12,
color: color,
),
),
const SizedBox(width: 8),
Icon(
isVictory ? Icons.emoji_events : Icons.sentiment_very_dissatisfied,
color: color,
size: 28,
),
],
);
}
Widget _buildBattleInfo(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 도전자 정보
_buildFighterInfo(
context,
result.match.challenger.characterName,
result.isVictory,
),
// VS
Text(
'VS',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 10,
color: RetroColors.textMutedOf(context),
),
),
// 상대 정보
_buildFighterInfo(
context,
result.match.opponent.characterName,
!result.isVictory,
),
],
);
}
Widget _buildFighterInfo(BuildContext context, String name, bool isWinner) {
return Column(
children: [
Icon(
isWinner ? Icons.emoji_events : Icons.close,
color: isWinner ? Colors.amber : Colors.grey,
size: 24,
),
const SizedBox(height: 4),
Text(
name,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
color: isWinner
? RetroColors.goldOf(context)
: RetroColors.textMutedOf(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
isWinner ? 'WINNER' : 'LOSER',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 6,
color: isWinner ? Colors.amber : Colors.grey,
),
),
],
);
}
Widget _buildExchangeResult(BuildContext context, EquipmentSlot slot) {
// 교환된 장비 찾기
final challengerOldItem = _findItem(
result.match.challenger.finalEquipment,
slot,
);
final opponentOldItem = _findItem(
result.match.opponent.finalEquipment,
slot,
);
final challengerNewItem = _findItem(
result.updatedChallenger.finalEquipment,
slot,
);
final opponentNewItem = _findItem(
result.updatedOpponent.finalEquipment,
slot,
);
return Column(
children: [
// 교환 타이틀
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.swap_horiz,
color: RetroColors.goldOf(context),
size: 20,
),
const SizedBox(width: 8),
Text(
_arenaExchange,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 8,
color: RetroColors.goldOf(context),
),
),
],
),
const SizedBox(height: 12),
// 슬롯 정보
Text(
_getSlotLabel(slot),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
color: RetroColors.textSecondaryOf(context),
),
),
const SizedBox(height: 12),
// 내 캐릭터 장비 변경
_buildExchangeRow(
context,
result.match.challenger.characterName,
challengerOldItem,
challengerNewItem,
result.isVictory,
),
const SizedBox(height: 8),
// 상대 장비 변경
_buildExchangeRow(
context,
result.match.opponent.characterName,
opponentOldItem,
opponentNewItem,
!result.isVictory,
),
],
);
}
Widget _buildExchangeRow(
BuildContext context,
String name,
EquipmentItem? oldItem,
EquipmentItem? newItem,
bool isWinner,
) {
final oldScore =
oldItem != null ? ItemService.calculateEquipmentScore(oldItem) : 0;
final newScore =
newItem != null ? ItemService.calculateEquipmentScore(newItem) : 0;
final scoreDiff = newScore - oldScore;
final isGain = scoreDiff > 0;
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isWinner
? Colors.green.withValues(alpha: 0.1)
: Colors.red.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isWinner
? Colors.green.withValues(alpha: 0.3)
: Colors.red.withValues(alpha: 0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 이름
Text(
name,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
color: RetroColors.textPrimaryOf(context),
),
),
const SizedBox(height: 4),
// 장비 변경
Row(
children: [
// 이전 장비
Expanded(
child: _buildItemChip(
context,
oldItem,
oldScore,
isOld: true,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.arrow_forward,
size: 14,
color: RetroColors.textMutedOf(context),
),
),
// 새 장비
Expanded(
child: _buildItemChip(
context,
newItem,
newScore,
isOld: false,
),
),
],
),
const SizedBox(height: 4),
// 점수 변화
Align(
alignment: Alignment.centerRight,
child: Text(
'${isGain ? '+' : ''}$scoreDiff pt',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 7,
color: isGain ? Colors.green : Colors.red,
),
),
),
],
),
);
}
Widget _buildItemChip(
BuildContext context,
EquipmentItem? item,
int score, {
required bool isOld,
}) {
if (item == null || item.isEmpty) {
return Text(
'(empty)',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 6,
color: RetroColors.textMutedOf(context),
),
);
}
final rarityColor = _getRarityColor(item.rarity);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: rarityColor.withValues(alpha: isOld ? 0.1 : 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: rarityColor.withValues(alpha: 0.5),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.name,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 5,
color: rarityColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
'$score pt',
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 5,
color: RetroColors.textMutedOf(context),
),
),
],
),
);
}
EquipmentItem? _findItem(List<EquipmentItem>? equipment, EquipmentSlot slot) {
if (equipment == null) return null;
for (final item in equipment) {
if (item.slot == slot) return item;
}
return null;
}
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,
};
}
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,
};
}
}
/// 아레나 결과 다이얼로그 표시
Future<void> showArenaResultDialog(
BuildContext context, {
required ArenaMatchResult result,
required VoidCallback onClose,
}) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (context) => ArenaResultDialog(
result: result,
onClose: () {
Navigator.of(context).pop();
onClose();
},
),
);
}