Files
asciinevrdie/lib/src/features/game/widgets/statistics_dialog.dart
JiWoong Sul 864a866039 refactor(ui): 위젯 분리 및 화면 개선
- game_play_screen에서 desktop 패널 위젯 분리
- death_overlay에서 death_buttons, death_combat_log 분리
- mobile_carousel_layout에서 mobile_options_menu 분리
- 아레나 위젯 개선 (arena_hp_bar, result_panel 등)
- settings_screen에서 retro_settings_widgets 분리
- 기타 위젯 리팩토링 및 import 경로 업데이트
2026-02-23 15:49:38 +09:00

409 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/core/model/game_statistics.dart';
import 'package:asciineverdie/src/shared/widgets/retro_dialog.dart';
/// 게임 통계 다이얼로그 (Statistics Dialog)
///
/// 세션 통계와 누적 통계를 탭으로 표시
class StatisticsDialog extends StatefulWidget {
const StatisticsDialog({
super.key,
required this.session,
required this.cumulative,
});
final SessionStatistics session;
final CumulativeStatistics cumulative;
/// 다이얼로그 표시
static Future<void> show(
BuildContext context, {
required SessionStatistics session,
required CumulativeStatistics cumulative,
}) {
return showDialog(
context: context,
barrierColor: Colors.black87,
builder: (_) =>
StatisticsDialog(session: session, cumulative: cumulative),
);
}
@override
State<StatisticsDialog> createState() => _StatisticsDialogState();
}
class _StatisticsDialogState extends State<StatisticsDialog>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
return RetroDialog(
title: l10n.statsStatistics,
titleIcon: '📊',
maxWidth: 420,
maxHeight: 520,
child: Column(
children: [
// 탭 바
RetroTabBar(
controller: _tabController,
tabs: [l10n.statsSession, l10n.statsAccumulated],
),
// 탭 내용
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_SessionStatisticsView(stats: widget.session),
_CumulativeStatisticsView(stats: widget.cumulative),
],
),
),
],
),
);
}
}
/// 세션 통계 뷰
class _SessionStatisticsView extends StatelessWidget {
const _SessionStatisticsView({required this.stats});
final SessionStatistics stats;
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
return ListView(
padding: const EdgeInsets.all(12),
children: [
_StatSection(
title: l10n.statsCombat,
icon: '',
items: [
_StatItem(
label: l10n.statsPlayTime,
value: stats.formattedPlayTime,
),
_StatItem(
label: l10n.statsMonstersKilled,
value: _formatNumber(stats.monstersKilled),
),
_StatItem(
label: l10n.statsBossesDefeated,
value: _formatNumber(stats.bossesDefeated),
),
_StatItem(
label: l10n.statsDeaths,
value: _formatNumber(stats.deathCount),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsDamage,
icon: '',
items: [
_StatItem(
label: l10n.statsDamageDealt,
value: _formatNumber(stats.totalDamageDealt),
),
_StatItem(
label: l10n.statsDamageTaken,
value: _formatNumber(stats.totalDamageTaken),
),
_StatItem(
label: l10n.statsAverageDps,
value: stats.averageDps.toStringAsFixed(1),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsSkills,
icon: '',
items: [
_StatItem(
label: l10n.statsSkillsUsed,
value: _formatNumber(stats.skillsUsed),
),
_StatItem(
label: l10n.statsCriticalHits,
value: _formatNumber(stats.criticalHits),
),
_StatItem(
label: l10n.statsMaxCriticalStreak,
value: _formatNumber(stats.maxCriticalStreak),
),
_StatItem(
label: l10n.statsCriticalRate,
value: '${(stats.criticalRate * 100).toStringAsFixed(1)}%',
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsEconomy,
icon: '💰',
items: [
_StatItem(
label: l10n.statsGoldEarned,
value: _formatNumber(stats.goldEarned),
),
_StatItem(
label: l10n.statsGoldSpent,
value: _formatNumber(stats.goldSpent),
),
_StatItem(
label: l10n.statsItemsSold,
value: _formatNumber(stats.itemsSold),
),
_StatItem(
label: l10n.statsPotionsUsed,
value: _formatNumber(stats.potionsUsed),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsProgress,
icon: '',
items: [
_StatItem(
label: l10n.statsLevelUps,
value: _formatNumber(stats.levelUps),
),
_StatItem(
label: l10n.statsQuestsCompleted,
value: _formatNumber(stats.questsCompleted),
),
],
),
],
);
}
}
/// 누적 통계 뷰
class _CumulativeStatisticsView extends StatelessWidget {
const _CumulativeStatisticsView({required this.stats});
final CumulativeStatistics stats;
@override
Widget build(BuildContext context) {
final l10n = L10n.of(context);
return ListView(
padding: const EdgeInsets.all(12),
children: [
_StatSection(
title: l10n.statsRecords,
icon: '🏆',
items: [
_StatItem(
label: l10n.statsHighestLevel,
value: _formatNumber(stats.highestLevel),
highlight: true,
),
_StatItem(
label: l10n.statsHighestGoldHeld,
value: _formatNumber(stats.highestGoldHeld),
highlight: true,
),
_StatItem(
label: l10n.statsBestCriticalStreak,
value: _formatNumber(stats.bestCriticalStreak),
highlight: true,
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsTotalPlay,
icon: '',
items: [
_StatItem(
label: l10n.statsTotalPlayTime,
value: stats.formattedTotalPlayTime,
),
_StatItem(
label: l10n.statsGamesStarted,
value: _formatNumber(stats.gamesStarted),
),
_StatItem(
label: l10n.statsGamesCompleted,
value: _formatNumber(stats.gamesCompleted),
),
_StatItem(
label: l10n.statsCompletionRate,
value: '${(stats.completionRate * 100).toStringAsFixed(1)}%',
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsTotalCombat,
icon: '',
items: [
_StatItem(
label: l10n.statsMonstersKilled,
value: _formatNumber(stats.totalMonstersKilled),
),
_StatItem(
label: l10n.statsBossesDefeated,
value: _formatNumber(stats.totalBossesDefeated),
),
_StatItem(
label: l10n.statsTotalDeaths,
value: _formatNumber(stats.totalDeaths),
),
_StatItem(
label: l10n.statsTotalLevelUps,
value: _formatNumber(stats.totalLevelUps),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsTotalDamage,
icon: '',
items: [
_StatItem(
label: l10n.statsDamageDealt,
value: _formatNumber(stats.totalDamageDealt),
),
_StatItem(
label: l10n.statsDamageTaken,
value: _formatNumber(stats.totalDamageTaken),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsTotalSkills,
icon: '',
items: [
_StatItem(
label: l10n.statsSkillsUsed,
value: _formatNumber(stats.totalSkillsUsed),
),
_StatItem(
label: l10n.statsCriticalHits,
value: _formatNumber(stats.totalCriticalHits),
),
],
),
const SizedBox(height: 12),
_StatSection(
title: l10n.statsTotalEconomy,
icon: '💰',
items: [
_StatItem(
label: l10n.statsGoldEarned,
value: _formatNumber(stats.totalGoldEarned),
),
_StatItem(
label: l10n.statsGoldSpent,
value: _formatNumber(stats.totalGoldSpent),
),
_StatItem(
label: l10n.statsItemsSold,
value: _formatNumber(stats.totalItemsSold),
),
_StatItem(
label: l10n.statsPotionsUsed,
value: _formatNumber(stats.totalPotionsUsed),
),
_StatItem(
label: l10n.statsQuestsCompleted,
value: _formatNumber(stats.totalQuestsCompleted),
),
],
),
],
);
}
}
/// 레트로 스타일 통계 섹션 위젯
class _StatSection extends StatelessWidget {
const _StatSection({
required this.title,
required this.icon,
required this.items,
});
final String title;
final String icon;
final List<_StatItem> items;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 섹션 헤더 (테마에서 자동 결정)
RetroSectionHeader(title: title, icon: icon),
// 통계 항목들
...items,
],
);
}
}
/// 레트로 스타일 개별 통계 항목 위젯
class _StatItem extends StatelessWidget {
const _StatItem({
required this.label,
required this.value,
this.highlight = false,
});
final String label;
final String value;
final bool highlight;
@override
Widget build(BuildContext context) {
return RetroStatRow(label: label, value: value, highlight: highlight);
}
}
/// 숫자 포맷팅 (천 단위 콤마)
String _formatNumber(int value) {
if (value < 1000) return value.toString();
final result = StringBuffer();
final str = value.toString();
final length = str.length;
for (var i = 0; i < length; i++) {
if (i > 0 && (length - i) % 3 == 0) {
result.write(',');
}
result.write(str[i]);
}
return result.toString();
}