refactor(ui): HallOfFameScreen 위젯 분해
- HeroDetailDialog: 상세 정보 다이얼로그 분리 (690줄) - HallOfFameEntryCard: 엔트리 카드 분리 (263줄) - GameClearDialog: 게임 클리어 다이얼로그 분리 (255줄) - 메인 화면 264줄로 경량화
This commit is contained in:
255
lib/src/features/hall_of_fame/game_clear_dialog.dart
Normal file
255
lib/src/features/hall_of_fame/game_clear_dialog.dart
Normal file
@@ -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<void> 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
263
lib/src/features/hall_of_fame/hall_of_fame_entry_card.dart
Normal file
263
lib/src/features/hall_of_fame/hall_of_fame_entry_card.dart
Normal file
@@ -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<void>(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
690
lib/src/features/hall_of_fame/hero_detail_dialog.dart
Normal file
690
lib/src/features/hall_of_fame/hero_detail_dialog.dart
Normal file
@@ -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<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: 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user