206 lines
5.3 KiB
Dart
206 lines
5.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
|
import 'package:asciineverdie/src/core/model/skill.dart';
|
|
|
|
/// 활성 버프 패널 위젯
|
|
///
|
|
/// 현재 적용 중인 버프 목록과 남은 시간을 표시.
|
|
class ActiveBuffPanel extends StatelessWidget {
|
|
const ActiveBuffPanel({
|
|
super.key,
|
|
required this.activeBuffs,
|
|
required this.currentMs,
|
|
});
|
|
|
|
final List<ActiveBuff> activeBuffs;
|
|
final int currentMs;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (activeBuffs.isEmpty) {
|
|
return Center(
|
|
child: Text(
|
|
l10n.uiNoActiveBuffs,
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
color: Colors.grey,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
itemCount: activeBuffs.length,
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
itemBuilder: (context, index) {
|
|
final buff = activeBuffs[index];
|
|
return _BuffRow(buff: buff, currentMs: currentMs);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 개별 버프 행 위젯
|
|
class _BuffRow extends StatelessWidget {
|
|
const _BuffRow({required this.buff, required this.currentMs});
|
|
|
|
final ActiveBuff buff;
|
|
final int currentMs;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final remainingMs = buff.remainingDuration(currentMs);
|
|
final remainingSec = (remainingMs / 1000).toStringAsFixed(1);
|
|
final progress = remainingMs / buff.effect.durationMs;
|
|
final modifiers = _buildModifierList();
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
// 버프 아이콘
|
|
const Icon(Icons.trending_up, size: 14, color: Colors.lightBlue),
|
|
const SizedBox(width: 4),
|
|
|
|
// 버프 이름
|
|
Expanded(
|
|
child: Text(
|
|
buff.effect.name,
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.lightBlue,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
|
|
// 남은 시간
|
|
Text(
|
|
'${remainingSec}s',
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color: remainingMs < 3000 ? Colors.orange : Colors.grey,
|
|
fontWeight: remainingMs < 3000
|
|
? FontWeight.bold
|
|
: FontWeight.normal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 2),
|
|
|
|
// 남은 시간 프로그레스 바
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(2),
|
|
child: LinearProgressIndicator(
|
|
value: progress.clamp(0.0, 1.0),
|
|
minHeight: 3,
|
|
backgroundColor: Colors.grey.shade800,
|
|
valueColor: AlwaysStoppedAnimation(
|
|
progress > 0.3 ? Colors.lightBlue : Colors.orange,
|
|
),
|
|
),
|
|
),
|
|
|
|
// 효과 목록
|
|
if (modifiers.isNotEmpty) ...[
|
|
const SizedBox(height: 2),
|
|
Wrap(spacing: 6, runSpacing: 2, children: modifiers),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 버프 효과 목록 생성
|
|
List<Widget> _buildModifierList() {
|
|
final modifiers = <Widget>[];
|
|
final effect = buff.effect;
|
|
|
|
if (effect.atkModifier != 0) {
|
|
modifiers.add(
|
|
_ModifierChip(
|
|
label: l10n.statAtk,
|
|
value: effect.atkModifier,
|
|
isPositive: effect.atkModifier > 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (effect.defModifier != 0) {
|
|
modifiers.add(
|
|
_ModifierChip(
|
|
label: l10n.statDef,
|
|
value: effect.defModifier,
|
|
isPositive: effect.defModifier > 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (effect.criRateModifier != 0) {
|
|
modifiers.add(
|
|
_ModifierChip(
|
|
label: l10n.statCri,
|
|
value: effect.criRateModifier,
|
|
isPositive: effect.criRateModifier > 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (effect.evasionModifier != 0) {
|
|
modifiers.add(
|
|
_ModifierChip(
|
|
label: l10n.statEva,
|
|
value: effect.evasionModifier,
|
|
isPositive: effect.evasionModifier > 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
return modifiers;
|
|
}
|
|
}
|
|
|
|
/// 효과 칩 위젯
|
|
class _ModifierChip extends StatelessWidget {
|
|
const _ModifierChip({
|
|
required this.label,
|
|
required this.value,
|
|
required this.isPositive,
|
|
});
|
|
|
|
final String label;
|
|
final double value;
|
|
final bool isPositive;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = isPositive ? Colors.green : Colors.red;
|
|
final sign = isPositive ? '+' : '';
|
|
final percent = (value * 100).round();
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.2),
|
|
borderRadius: BorderRadius.circular(3),
|
|
),
|
|
child: Text(
|
|
'$label: $sign$percent%',
|
|
style: TextStyle(
|
|
fontSize: 8,
|
|
color: color,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|