- 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 경로 업데이트
409 lines
11 KiB
Dart
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();
|
|
}
|