import 'package:flutter/material.dart'; /// 전투 로그 엔트리 (Combat Log Entry) class CombatLogEntry { const CombatLogEntry({ required this.message, required this.timestamp, this.type = CombatLogType.normal, }); final String message; final DateTime timestamp; final CombatLogType type; } /// 로그 타입에 따른 스타일 구분 enum CombatLogType { normal, // 일반 메시지 damage, // 피해 입힘 heal, // 회복 levelUp, // 레벨업 questComplete, // 퀘스트 완료 loot, // 전리품 획득 spell, // 주문 습득 } /// 전투 로그 위젯 (Phase 8: 실시간 전투 이벤트 표시) /// /// 최근 전투 이벤트를 스크롤 가능한 리스트로 표시 class CombatLog extends StatefulWidget { const CombatLog({super.key, required this.entries, this.maxEntries = 50}); final List entries; final int maxEntries; @override State createState() => _CombatLogState(); } class _CombatLogState extends State { final ScrollController _scrollController = ScrollController(); int _previousLength = 0; @override void didUpdateWidget(CombatLog oldWidget) { super.didUpdateWidget(oldWidget); // 새 로그 추가 시 자동 스크롤 if (widget.entries.length > _previousLength) { _scrollToBottom(); } _previousLength = widget.entries.length; } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); } }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ListView.builder( controller: _scrollController, itemCount: widget.entries.length, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), itemBuilder: (context, index) { final entry = widget.entries[index]; return _LogEntryTile(entry: entry); }, ); } } /// 개별 로그 엔트리 타일 class _LogEntryTile extends StatelessWidget { const _LogEntryTile({required this.entry}); final CombatLogEntry entry; @override Widget build(BuildContext context) { final (color, icon) = _getStyleForType(entry.type); return Padding( padding: const EdgeInsets.symmetric(vertical: 1), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 타임스탬프(timestamp) Text( _formatTime(entry.timestamp), style: TextStyle( fontSize: 10, color: Theme.of(context).colorScheme.outline, fontFamily: 'monospace', ), ), const SizedBox(width: 4), // 아이콘 if (icon != null) Padding( padding: const EdgeInsets.only(right: 4), child: Icon(icon, size: 12, color: color), ), // 메시지 Expanded( child: Text( entry.message, style: TextStyle( fontSize: 11, color: color ?? Theme.of(context).colorScheme.onSurface, ), ), ), ], ), ); } String _formatTime(DateTime time) { return '${time.hour.toString().padLeft(2, '0')}:' '${time.minute.toString().padLeft(2, '0')}:' '${time.second.toString().padLeft(2, '0')}'; } (Color?, IconData?) _getStyleForType(CombatLogType type) { return switch (type) { CombatLogType.normal => (null, null), CombatLogType.damage => (Colors.red.shade300, Icons.local_fire_department), CombatLogType.heal => (Colors.green.shade300, Icons.healing), CombatLogType.levelUp => (Colors.amber, Icons.arrow_upward), CombatLogType.questComplete => (Colors.blue.shade300, Icons.check_circle), CombatLogType.loot => (Colors.orange.shade300, Icons.inventory_2), CombatLogType.spell => (Colors.purple.shade300, Icons.auto_fix_high), }; } }