refactor(arena): 아레나 화면 및 위젯 정리
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:asciineverdie/src/core/engine/item_service.dart';
|
||||
@@ -6,6 +11,7 @@ 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/features/game/widgets/combat_log.dart';
|
||||
import 'package:asciineverdie/src/shared/retro_colors.dart';
|
||||
|
||||
// 임시 문자열
|
||||
@@ -23,6 +29,7 @@ class ArenaResultPanel extends StatefulWidget {
|
||||
required this.result,
|
||||
required this.turnCount,
|
||||
required this.onContinue,
|
||||
this.battleLog,
|
||||
});
|
||||
|
||||
/// 대전 결과
|
||||
@@ -34,6 +41,9 @@ class ArenaResultPanel extends StatefulWidget {
|
||||
/// Continue 콜백
|
||||
final VoidCallback onContinue;
|
||||
|
||||
/// 배틀 로그 (디버그 모드 저장용)
|
||||
final List<CombatLogEntry>? battleLog;
|
||||
|
||||
@override
|
||||
State<ArenaResultPanel> createState() => _ArenaResultPanelState();
|
||||
}
|
||||
@@ -52,21 +62,18 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: const Offset(0, 1), // 아래에서 위로
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _slideController,
|
||||
curve: Curves.easeOutCubic,
|
||||
));
|
||||
_slideAnimation =
|
||||
Tween<Offset>(
|
||||
begin: const Offset(0, 1), // 아래에서 위로
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic),
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _slideController,
|
||||
curve: Curves.easeOut,
|
||||
));
|
||||
).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOut));
|
||||
|
||||
// 약간 지연 후 애니메이션 시작 (분해 애니메이션과 동기화)
|
||||
Future.delayed(const Duration(milliseconds: 800), () {
|
||||
@@ -82,6 +89,63 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 배틀 로그 JSON 저장 (macOS 디버그 모드 전용)
|
||||
Future<void> _saveBattleLog() async {
|
||||
if (widget.battleLog == null || widget.battleLog!.isEmpty) return;
|
||||
|
||||
try {
|
||||
// macOS: Downloads 폴더에 저장 (사용자가 쉽게 찾을 수 있도록)
|
||||
final directory = await getDownloadsDirectory() ??
|
||||
await getApplicationDocumentsDirectory();
|
||||
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
||||
final challenger = widget.result.match.challenger.characterName;
|
||||
final opponent = widget.result.match.opponent.characterName;
|
||||
final fileName = 'arena_${challenger}_vs_${opponent}_$timestamp.json';
|
||||
final file = File('${directory.path}/$fileName');
|
||||
|
||||
final jsonData = {
|
||||
'match': {
|
||||
'challenger': challenger,
|
||||
'opponent': opponent,
|
||||
'isVictory': widget.result.isVictory,
|
||||
'turnCount': widget.turnCount,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
},
|
||||
'battleLog': widget.battleLog!.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
await file.writeAsString(
|
||||
const JsonEncoder.withIndent(' ').convert(jsonData),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${l10n.uiSaved}: $fileName',
|
||||
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 6),
|
||||
),
|
||||
backgroundColor: RetroColors.mpOf(context),
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${l10n.uiError}: $e',
|
||||
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 6),
|
||||
),
|
||||
backgroundColor: RetroColors.hpOf(context),
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isVictory = widget.result.isVictory;
|
||||
@@ -137,6 +201,13 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
// 장비 교환
|
||||
_buildExchangeSection(context),
|
||||
const SizedBox(height: 12),
|
||||
// 배틀로그 저장 버튼 (macOS 디버그 모드 전용)
|
||||
if (kDebugMode &&
|
||||
Platform.isMacOS &&
|
||||
widget.battleLog != null) ...[
|
||||
_buildSaveLogButton(context),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
// Continue 버튼
|
||||
_buildContinueButton(context, resultColor),
|
||||
],
|
||||
@@ -262,10 +333,12 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
slot,
|
||||
);
|
||||
|
||||
final oldScore =
|
||||
oldItem != null ? ItemService.calculateEquipmentScore(oldItem) : 0;
|
||||
final newScore =
|
||||
newItem != null ? ItemService.calculateEquipmentScore(newItem) : 0;
|
||||
final oldScore = oldItem != null
|
||||
? ItemService.calculateEquipmentScore(oldItem)
|
||||
: 0;
|
||||
final newScore = newItem != null
|
||||
? ItemService.calculateEquipmentScore(newItem)
|
||||
: 0;
|
||||
final scoreDiff = newScore - oldScore;
|
||||
|
||||
return Container(
|
||||
@@ -344,7 +417,9 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
scoreDiff >= 0 ? Icons.arrow_upward : Icons.arrow_downward,
|
||||
scoreDiff >= 0
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward,
|
||||
size: 10,
|
||||
color: scoreDiff >= 0 ? Colors.green : Colors.red,
|
||||
),
|
||||
@@ -366,11 +441,7 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemBadge(
|
||||
BuildContext context,
|
||||
EquipmentItem? item,
|
||||
int score,
|
||||
) {
|
||||
Widget _buildItemBadge(BuildContext context, EquipmentItem? item, int score) {
|
||||
if (item == null || item.isEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
@@ -433,9 +504,7 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: color,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||
),
|
||||
child: Text(
|
||||
l10n.buttonConfirm,
|
||||
@@ -449,6 +518,27 @@ class _ArenaResultPanelState extends State<ArenaResultPanel>
|
||||
);
|
||||
}
|
||||
|
||||
/// 배틀로그 저장 버튼 (macOS 디버그 모드 전용)
|
||||
Widget _buildSaveLogButton(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _saveBattleLog,
|
||||
icon: const Icon(Icons.save_alt, size: 14),
|
||||
label: Text(
|
||||
l10n.uiSaveBattleLog,
|
||||
style: const TextStyle(fontFamily: 'PressStart2P', fontSize: 6),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: RetroColors.mpOf(context),
|
||||
side: BorderSide(color: RetroColors.mpOf(context)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
EquipmentItem? _findItem(List<EquipmentItem>? equipment, EquipmentSlot slot) {
|
||||
if (equipment == null) return null;
|
||||
for (final item in equipment) {
|
||||
|
||||
Reference in New Issue
Block a user