706 lines
21 KiB
Dart
706 lines
21 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:askiineverdie/src/core/model/game_statistics.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,
|
|
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 theme = Theme.of(context);
|
|
final isKorean = Localizations.localeOf(context).languageCode == 'ko';
|
|
final isJapanese = Localizations.localeOf(context).languageCode == 'ja';
|
|
|
|
return Dialog(
|
|
child: Container(
|
|
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 500),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// 헤더
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.primaryContainer,
|
|
borderRadius: const BorderRadius.vertical(
|
|
top: Radius.circular(28),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.bar_chart,
|
|
color: theme.colorScheme.onPrimaryContainer,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
isKorean
|
|
? '게임 통계'
|
|
: isJapanese
|
|
? 'ゲーム統計'
|
|
: 'Game Statistics',
|
|
style: theme.textTheme.titleLarge?.copyWith(
|
|
color: theme.colorScheme.onPrimaryContainer,
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
color: theme.colorScheme.onPrimaryContainer,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// 탭 바
|
|
TabBar(
|
|
controller: _tabController,
|
|
tabs: [
|
|
Tab(
|
|
text: isKorean
|
|
? '현재 세션'
|
|
: isJapanese
|
|
? '現在のセッション'
|
|
: 'Session',
|
|
),
|
|
Tab(
|
|
text: isKorean
|
|
? '누적 통계'
|
|
: isJapanese
|
|
? '累積統計'
|
|
: 'Cumulative',
|
|
),
|
|
],
|
|
),
|
|
// 탭 내용
|
|
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 isKorean = Localizations.localeOf(context).languageCode == 'ko';
|
|
final isJapanese = Localizations.localeOf(context).languageCode == 'ja';
|
|
|
|
return ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
_StatSection(
|
|
title: isKorean
|
|
? '전투'
|
|
: isJapanese
|
|
? '戦闘'
|
|
: 'Combat',
|
|
icon: Icons.sports_mma,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '플레이 시간'
|
|
: isJapanese
|
|
? 'プレイ時間'
|
|
: 'Play Time',
|
|
value: stats.formattedPlayTime,
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '처치한 몬스터'
|
|
: isJapanese
|
|
? '倒したモンスター'
|
|
: 'Monsters Killed',
|
|
value: _formatNumber(stats.monstersKilled),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '보스 처치'
|
|
: isJapanese
|
|
? 'ボス討伐'
|
|
: 'Bosses Defeated',
|
|
value: _formatNumber(stats.bossesDefeated),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '사망 횟수'
|
|
: isJapanese
|
|
? '死亡回数'
|
|
: 'Deaths',
|
|
value: _formatNumber(stats.deathCount),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '데미지'
|
|
: isJapanese
|
|
? 'ダメージ'
|
|
: 'Damage',
|
|
icon: Icons.flash_on,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '입힌 데미지'
|
|
: isJapanese
|
|
? '与えたダメージ'
|
|
: 'Damage Dealt',
|
|
value: _formatNumber(stats.totalDamageDealt),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '받은 데미지'
|
|
: isJapanese
|
|
? '受けたダメージ'
|
|
: 'Damage Taken',
|
|
value: _formatNumber(stats.totalDamageTaken),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '평균 DPS'
|
|
: isJapanese
|
|
? '平均DPS'
|
|
: 'Average DPS',
|
|
value: stats.averageDps.toStringAsFixed(1),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '스킬'
|
|
: isJapanese
|
|
? 'スキル'
|
|
: 'Skills',
|
|
icon: Icons.auto_awesome,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '스킬 사용'
|
|
: isJapanese
|
|
? 'スキル使用'
|
|
: 'Skills Used',
|
|
value: _formatNumber(stats.skillsUsed),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '크리티컬 히트'
|
|
: isJapanese
|
|
? 'クリティカルヒット'
|
|
: 'Critical Hits',
|
|
value: _formatNumber(stats.criticalHits),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '최대 연속 크리티컬'
|
|
: isJapanese
|
|
? '最大連続クリティカル'
|
|
: 'Max Critical Streak',
|
|
value: _formatNumber(stats.maxCriticalStreak),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '크리티컬 비율'
|
|
: isJapanese
|
|
? 'クリティカル率'
|
|
: 'Critical Rate',
|
|
value: '${(stats.criticalRate * 100).toStringAsFixed(1)}%',
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '경제'
|
|
: isJapanese
|
|
? '経済'
|
|
: 'Economy',
|
|
icon: Icons.monetization_on,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '획득 골드'
|
|
: isJapanese
|
|
? '獲得ゴールド'
|
|
: 'Gold Earned',
|
|
value: _formatNumber(stats.goldEarned),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '소비 골드'
|
|
: isJapanese
|
|
? '消費ゴールド'
|
|
: 'Gold Spent',
|
|
value: _formatNumber(stats.goldSpent),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '판매 아이템'
|
|
: isJapanese
|
|
? '売却アイテム'
|
|
: 'Items Sold',
|
|
value: _formatNumber(stats.itemsSold),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '물약 사용'
|
|
: isJapanese
|
|
? 'ポーション使用'
|
|
: 'Potions Used',
|
|
value: _formatNumber(stats.potionsUsed),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '진행'
|
|
: isJapanese
|
|
? '進行'
|
|
: 'Progress',
|
|
icon: Icons.trending_up,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '레벨업'
|
|
: isJapanese
|
|
? 'レベルアップ'
|
|
: 'Level Ups',
|
|
value: _formatNumber(stats.levelUps),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '완료한 퀘스트'
|
|
: isJapanese
|
|
? '完了したクエスト'
|
|
: 'Quests Completed',
|
|
value: _formatNumber(stats.questsCompleted),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 누적 통계 뷰
|
|
class _CumulativeStatisticsView extends StatelessWidget {
|
|
const _CumulativeStatisticsView({required this.stats});
|
|
|
|
final CumulativeStatistics stats;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isKorean = Localizations.localeOf(context).languageCode == 'ko';
|
|
final isJapanese = Localizations.localeOf(context).languageCode == 'ja';
|
|
|
|
return ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
_StatSection(
|
|
title: isKorean
|
|
? '기록'
|
|
: isJapanese
|
|
? '記録'
|
|
: 'Records',
|
|
icon: Icons.emoji_events,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '최고 레벨'
|
|
: isJapanese
|
|
? '最高レベル'
|
|
: 'Highest Level',
|
|
value: _formatNumber(stats.highestLevel),
|
|
highlight: true,
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '최대 보유 골드'
|
|
: isJapanese
|
|
? '最大所持ゴールド'
|
|
: 'Highest Gold Held',
|
|
value: _formatNumber(stats.highestGoldHeld),
|
|
highlight: true,
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '최고 연속 크리티컬'
|
|
: isJapanese
|
|
? '最高連続クリティカル'
|
|
: 'Best Critical Streak',
|
|
value: _formatNumber(stats.bestCriticalStreak),
|
|
highlight: true,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '총 플레이'
|
|
: isJapanese
|
|
? '総プレイ'
|
|
: 'Total Play',
|
|
icon: Icons.access_time,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '총 플레이 시간'
|
|
: isJapanese
|
|
? '総プレイ時間'
|
|
: 'Total Play Time',
|
|
value: stats.formattedTotalPlayTime,
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '시작한 게임'
|
|
: isJapanese
|
|
? '開始したゲーム'
|
|
: 'Games Started',
|
|
value: _formatNumber(stats.gamesStarted),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '클리어한 게임'
|
|
: isJapanese
|
|
? 'クリアしたゲーム'
|
|
: 'Games Completed',
|
|
value: _formatNumber(stats.gamesCompleted),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '클리어율'
|
|
: isJapanese
|
|
? 'クリア率'
|
|
: 'Completion Rate',
|
|
value: '${(stats.completionRate * 100).toStringAsFixed(1)}%',
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '총 전투'
|
|
: isJapanese
|
|
? '総戦闘'
|
|
: 'Total Combat',
|
|
icon: Icons.sports_mma,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '처치한 몬스터'
|
|
: isJapanese
|
|
? '倒したモンスター'
|
|
: 'Monsters Killed',
|
|
value: _formatNumber(stats.totalMonstersKilled),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '보스 처치'
|
|
: isJapanese
|
|
? 'ボス討伐'
|
|
: 'Bosses Defeated',
|
|
value: _formatNumber(stats.totalBossesDefeated),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '총 사망'
|
|
: isJapanese
|
|
? '総死亡'
|
|
: 'Total Deaths',
|
|
value: _formatNumber(stats.totalDeaths),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '총 레벨업'
|
|
: isJapanese
|
|
? '総レベルアップ'
|
|
: 'Total Level Ups',
|
|
value: _formatNumber(stats.totalLevelUps),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '총 데미지'
|
|
: isJapanese
|
|
? '総ダメージ'
|
|
: 'Total Damage',
|
|
icon: Icons.flash_on,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '입힌 데미지'
|
|
: isJapanese
|
|
? '与えたダメージ'
|
|
: 'Damage Dealt',
|
|
value: _formatNumber(stats.totalDamageDealt),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '받은 데미지'
|
|
: isJapanese
|
|
? '受けたダメージ'
|
|
: 'Damage Taken',
|
|
value: _formatNumber(stats.totalDamageTaken),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '총 스킬'
|
|
: isJapanese
|
|
? '総スキル'
|
|
: 'Total Skills',
|
|
icon: Icons.auto_awesome,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '스킬 사용'
|
|
: isJapanese
|
|
? 'スキル使用'
|
|
: 'Skills Used',
|
|
value: _formatNumber(stats.totalSkillsUsed),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '크리티컬 히트'
|
|
: isJapanese
|
|
? 'クリティカルヒット'
|
|
: 'Critical Hits',
|
|
value: _formatNumber(stats.totalCriticalHits),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_StatSection(
|
|
title: isKorean
|
|
? '총 경제'
|
|
: isJapanese
|
|
? '総経済'
|
|
: 'Total Economy',
|
|
icon: Icons.monetization_on,
|
|
items: [
|
|
_StatItem(
|
|
label: isKorean
|
|
? '획득 골드'
|
|
: isJapanese
|
|
? '獲得ゴールド'
|
|
: 'Gold Earned',
|
|
value: _formatNumber(stats.totalGoldEarned),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '소비 골드'
|
|
: isJapanese
|
|
? '消費ゴールド'
|
|
: 'Gold Spent',
|
|
value: _formatNumber(stats.totalGoldSpent),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '판매 아이템'
|
|
: isJapanese
|
|
? '売却アイテム'
|
|
: 'Items Sold',
|
|
value: _formatNumber(stats.totalItemsSold),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '물약 사용'
|
|
: isJapanese
|
|
? 'ポーション使用'
|
|
: 'Potions Used',
|
|
value: _formatNumber(stats.totalPotionsUsed),
|
|
),
|
|
_StatItem(
|
|
label: isKorean
|
|
? '완료 퀘스트'
|
|
: isJapanese
|
|
? '完了クエスト'
|
|
: 'Quests Completed',
|
|
value: _formatNumber(stats.totalQuestsCompleted),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 통계 섹션 위젯
|
|
class _StatSection extends StatelessWidget {
|
|
const _StatSection({
|
|
required this.title,
|
|
required this.icon,
|
|
required this.items,
|
|
});
|
|
|
|
final String title;
|
|
final IconData icon;
|
|
final List<_StatItem> items;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 섹션 헤더
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 18, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
title,
|
|
style: theme.textTheme.titleSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Divider(height: 8),
|
|
// 통계 항목들
|
|
...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) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
Container(
|
|
padding: highlight
|
|
? const EdgeInsets.symmetric(horizontal: 8, vertical: 2)
|
|
: null,
|
|
decoration: highlight
|
|
? BoxDecoration(
|
|
color: theme.colorScheme.primaryContainer,
|
|
borderRadius: BorderRadius.circular(4),
|
|
)
|
|
: null,
|
|
child: Text(
|
|
value,
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: 'monospace',
|
|
color: highlight
|
|
? theme.colorScheme.onPrimaryContainer
|
|
: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 숫자 포맷팅 (천 단위 콤마)
|
|
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();
|
|
}
|