refactoring : barrel pattern
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
export 'battle/battle_animation_widget.dart';
|
||||
export 'battle/battle_controls.dart';
|
||||
export 'battle/battle_header.dart';
|
||||
export 'battle/battle_log_overlay.dart';
|
||||
export 'battle/character_status_card.dart';
|
||||
export 'battle/explosion_widget.dart';
|
||||
export 'battle/floating_battle_texts.dart';
|
||||
export 'battle/risk_selection_dialog.dart';
|
||||
export 'battle/shake_widget.dart';
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class BattleControls extends StatelessWidget {
|
||||
final bool isAttackEnabled;
|
||||
final bool isDefendEnabled;
|
||||
final VoidCallback onAttackPressed;
|
||||
final VoidCallback onDefendPressed;
|
||||
|
||||
const BattleControls({
|
||||
super.key,
|
||||
required this.isAttackEnabled,
|
||||
required this.isDefendEnabled,
|
||||
required this.onAttackPressed,
|
||||
required this.onDefendPressed,
|
||||
});
|
||||
|
||||
Widget _buildFloatingActionButton({
|
||||
required String label,
|
||||
required Color color,
|
||||
required ActionType actionType,
|
||||
required bool isEnabled,
|
||||
required VoidCallback onPressed,
|
||||
}) {
|
||||
String iconPath;
|
||||
if (actionType == ActionType.attack) {
|
||||
iconPath = 'assets/data/icon/icon_weapon.png';
|
||||
} else {
|
||||
iconPath = 'assets/data/icon/icon_shield.png';
|
||||
}
|
||||
|
||||
return FloatingActionButton(
|
||||
heroTag: label,
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
backgroundColor: isEnabled ? color : ThemeConfig.btnDisabled,
|
||||
child: Image.asset(
|
||||
iconPath,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: ThemeConfig.textColorWhite, // Tint icon white
|
||||
fit: BoxFit.contain,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildFloatingActionButton(
|
||||
label: "ATK",
|
||||
color: ThemeConfig.btnActionActive,
|
||||
actionType: ActionType.attack,
|
||||
isEnabled: isAttackEnabled,
|
||||
onPressed: onAttackPressed,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildFloatingActionButton(
|
||||
label: "DEF",
|
||||
color: ThemeConfig.btnDefendActive,
|
||||
actionType: ActionType.defend,
|
||||
isEnabled: isDefendEnabled,
|
||||
onPressed: onDefendPressed,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../providers.dart';
|
||||
import '../../game/config.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BattleHeader extends StatelessWidget {
|
||||
const BattleHeader({super.key});
|
||||
|
||||
String _getStageDisplayString(int stage) {
|
||||
// 3 Stages per Tier logic
|
||||
// Tier 1: 1, 2, 3 -> Underground Illegal Arena
|
||||
// Tier 2: 4, 5, 6 -> Colosseum
|
||||
// Tier 3: 7, 8, 9 -> King's Arena
|
||||
|
||||
final int tier = (stage - 1) ~/ 3 + 1;
|
||||
final int round = (stage - 1) % 3 + 1;
|
||||
String tierName = "";
|
||||
|
||||
switch (tier) {
|
||||
case 1:
|
||||
tierName = "지하 불법 투기장";
|
||||
break;
|
||||
case 2:
|
||||
tierName = "콜로세움";
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
tierName = "왕의 투기장";
|
||||
break;
|
||||
}
|
||||
|
||||
return "Tier $tier $tierName - $round";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final battleProvider = context.watch<BattleProvider>();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
_getStageDisplayString(battleProvider.stage),
|
||||
style: const TextStyle(
|
||||
color: ThemeConfig.textColorWhite,
|
||||
fontSize: ThemeConfig.fontSizeLarge,
|
||||
fontWeight: ThemeConfig.fontWeightBold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"${AppStrings.turn} ${battleProvider.turnCount}",
|
||||
style: const TextStyle(
|
||||
color: ThemeConfig.textColorWhite,
|
||||
fontSize: ThemeConfig.fontSizeHeader,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../game/model/entity.dart';
|
||||
import '../../game/models.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../providers/battle_provider.dart';
|
||||
import '../../providers.dart';
|
||||
import 'battle_animation_widget.dart';
|
||||
import '../../game/config/theme_config.dart';
|
||||
import '../../game/config/animation_config.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class CharacterStatusCard extends StatelessWidget {
|
||||
final Character character;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../game/config/theme_config.dart';
|
||||
import '../../game/config/animation_config.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class FloatingDamageText extends StatefulWidget {
|
||||
final String damage;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../game/models.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class RiskSelectionDialog extends StatelessWidget {
|
||||
final ActionType actionType;
|
||||
final Character player;
|
||||
final Function(RiskLevel) onSelected;
|
||||
|
||||
const RiskSelectionDialog({
|
||||
super.key,
|
||||
required this.actionType,
|
||||
required this.player,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseValue = actionType == ActionType.attack
|
||||
? player.totalAtk
|
||||
: player.totalDefense;
|
||||
|
||||
return SimpleDialog(
|
||||
title: Text("Select Risk Level for ${actionType.name}"),
|
||||
children: RiskLevel.values.map((risk) {
|
||||
String infoText = "";
|
||||
Color infoColor = Colors.black;
|
||||
double efficiency = 0.0;
|
||||
int expectedValue = 0;
|
||||
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackSafeEfficiency
|
||||
: BattleConfig.defendSafeEfficiency;
|
||||
infoColor = ThemeConfig.riskSafe;
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackNormalEfficiency
|
||||
: BattleConfig.defendNormalEfficiency;
|
||||
infoColor = ThemeConfig.riskNormal;
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackRiskyEfficiency
|
||||
: BattleConfig.defendRiskyEfficiency;
|
||||
infoColor = ThemeConfig.riskRisky;
|
||||
break;
|
||||
}
|
||||
|
||||
expectedValue = (baseValue * efficiency).toInt();
|
||||
String valueUnit = actionType == ActionType.attack ? "Dmg" : "Armor";
|
||||
|
||||
double baseChance = 0.0;
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
baseChance = BattleConfig.safeBaseChance;
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
baseChance = BattleConfig.normalBaseChance;
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
baseChance = BattleConfig.riskyBaseChance;
|
||||
break;
|
||||
}
|
||||
|
||||
double finalChance = baseChance + (player.totalLuck / 100.0);
|
||||
if (finalChance > 1.0) finalChance = 1.0;
|
||||
String successRate = "${(finalChance * 100).toInt()}%";
|
||||
|
||||
infoText =
|
||||
"Success: $successRate, Eff: ${(efficiency * 100).toInt()}% ($expectedValue $valueUnit)";
|
||||
|
||||
return SimpleDialogOption(
|
||||
onPressed: () => onSelected(risk),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
risk.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(infoText, style: TextStyle(fontSize: 12, color: infoColor)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export 'common/item_card_widget.dart';
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../game/models.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../utils.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class ItemCardWidget extends StatelessWidget {
|
||||
final Item item;
|
||||
final VoidCallback? onTap;
|
||||
final bool showPrice;
|
||||
final bool canBuy;
|
||||
|
||||
const ItemCardWidget({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.onTap,
|
||||
this.showPrice = false,
|
||||
this.canBuy = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Card(
|
||||
color: ThemeConfig.shopItemCardBg, // Configurable if needed
|
||||
shape: item.rarity != ItemRarity.magic
|
||||
? RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: ItemUtils.getRarityColor(item.rarity),
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ItemUtils.getRarityColor(item.rarity),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
// Show Item Stats (Fixed to show negative values)
|
||||
FittedBox(fit: BoxFit.scaleDown, child: _buildItemStatText(item)),
|
||||
if (showPrice) ...[
|
||||
const Spacer(),
|
||||
Text(
|
||||
"${item.price} G",
|
||||
style: TextStyle(
|
||||
color: canBuy
|
||||
? ThemeConfig.statGoldColor
|
||||
: ThemeConfig.textColorGrey,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemStatText(Item item) {
|
||||
List<String> stats = [];
|
||||
|
||||
// Helper to format stat string
|
||||
String formatStat(int value, String label) {
|
||||
String sign = value > 0 ? "+" : ""; // Negative values already have '-'
|
||||
return "$sign$value $label";
|
||||
}
|
||||
|
||||
if (item.atkBonus != 0) {
|
||||
stats.add(formatStat(item.atkBonus, AppStrings.atk));
|
||||
}
|
||||
if (item.hpBonus != 0) {
|
||||
stats.add(formatStat(item.hpBonus, AppStrings.hp));
|
||||
}
|
||||
if (item.armorBonus != 0) {
|
||||
stats.add(formatStat(item.armorBonus, AppStrings.def));
|
||||
}
|
||||
if (item.luck != 0) {
|
||||
stats.add(formatStat(item.luck, AppStrings.luck));
|
||||
}
|
||||
|
||||
List<String> effectTexts = item.effects.map((e) => e.description).toList();
|
||||
|
||||
if (stats.isEmpty && effectTexts.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeSmall,
|
||||
color: ThemeConfig.statAtkColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (effectTexts.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: Text(
|
||||
effectTexts.join("\n"),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeTiny,
|
||||
color: ThemeConfig.rarityLegendary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'inventory/character_stats_widget.dart';
|
||||
export 'inventory/inventory_grid_widget.dart';
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/battle_provider.dart';
|
||||
import '../../game/config/theme_config.dart';
|
||||
import '../../game/config/app_strings.dart';
|
||||
import '../../providers.dart';
|
||||
import '../../game/config.dart';
|
||||
|
||||
class CharacterStatsWidget extends StatelessWidget {
|
||||
const CharacterStatsWidget({super.key});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/battle_provider.dart';
|
||||
import '../../game/model/item.dart';
|
||||
import '../../providers.dart';
|
||||
import '../../game/models.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../utils/item_utils.dart';
|
||||
import '../../game/config/theme_config.dart';
|
||||
import '../../game/config/app_strings.dart';
|
||||
import '../../game/config.dart';
|
||||
import '../common/item_card_widget.dart';
|
||||
|
||||
class InventoryGridWidget extends StatelessWidget {
|
||||
const InventoryGridWidget({super.key});
|
||||
@@ -49,66 +48,13 @@ class InventoryGridWidget extends StatelessWidget {
|
||||
onTap: () {
|
||||
_showItemActionDialog(context, battleProvider, item);
|
||||
},
|
||||
child: Card(
|
||||
color: ThemeConfig.inventoryCardBg,
|
||||
shape: item.rarity != ItemRarity.magic
|
||||
? RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: ItemUtils.getRarityColor(item.rarity),
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
)
|
||||
: null,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 4,
|
||||
top: 4,
|
||||
child: Opacity(
|
||||
opacity: 0.5,
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
item.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeSmall,
|
||||
fontWeight:
|
||||
ThemeConfig.fontWeightBold,
|
||||
color: ItemUtils.getRarityColor(
|
||||
item.rarity,
|
||||
),
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: _buildItemStatText(item),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ItemCardWidget(
|
||||
item: item,
|
||||
// Inventory items usually don't show price unless in sell mode,
|
||||
// but logic here implies standard view.
|
||||
// If needed, we can toggle showPrice based on context.
|
||||
showPrice: false,
|
||||
canBuy: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -381,44 +327,4 @@ class InventoryGridWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemStatText(Item item) {
|
||||
List<String> stats = [];
|
||||
if (item.atkBonus > 0) stats.add("+${item.atkBonus} ${AppStrings.atk}");
|
||||
if (item.hpBonus > 0) stats.add("+${item.hpBonus} ${AppStrings.hp}");
|
||||
if (item.armorBonus > 0) stats.add("+${item.armorBonus} ${AppStrings.def}");
|
||||
if (item.luck > 0) stats.add("+${item.luck} ${AppStrings.luck}");
|
||||
|
||||
List<String> effectTexts = item.effects.map((e) => e.description).toList();
|
||||
|
||||
if (stats.isEmpty && effectTexts.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeSmall,
|
||||
color: ThemeConfig.statAtkColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (effectTexts.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: Text(
|
||||
effectTexts.join("\n"),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeTiny,
|
||||
color: ThemeConfig.rarityLegendary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'stage/rest_ui.dart';
|
||||
export 'stage/shop_ui.dart';
|
||||
@@ -3,12 +3,11 @@ import 'package:provider/provider.dart';
|
||||
import '../../../providers/battle_provider.dart';
|
||||
import '../../../providers/shop_provider.dart';
|
||||
import '../../../game/model/item.dart';
|
||||
import '../../../utils/item_utils.dart';
|
||||
import '../../../game/enums.dart';
|
||||
import '../../../game/config/theme_config.dart';
|
||||
import '../../../game/config/game_config.dart';
|
||||
import '../../../game/model/entity.dart';
|
||||
import '../inventory/inventory_grid_widget.dart';
|
||||
import '../common/item_card_widget.dart';
|
||||
|
||||
class ShopUI extends StatelessWidget {
|
||||
final BattleProvider battleProvider;
|
||||
@@ -117,58 +116,10 @@ class ShopUI extends StatelessWidget {
|
||||
shopProvider,
|
||||
player,
|
||||
),
|
||||
child: Card(
|
||||
color: ThemeConfig.shopItemCardBg,
|
||||
shape: item.rarity != ItemRarity.magic
|
||||
? RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: ItemUtils.getRarityColor(
|
||||
item.rarity,
|
||||
),
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
8.0,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ItemUtils.getRarityColor(
|
||||
item.rarity,
|
||||
),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${item.price} G",
|
||||
style: TextStyle(
|
||||
color: canBuy
|
||||
? ThemeConfig.statGoldColor
|
||||
: ThemeConfig.textColorGrey,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ItemCardWidget(
|
||||
item: item,
|
||||
showPrice: true,
|
||||
canBuy: canBuy,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user