refactoring : barrel pattern

This commit is contained in:
2025-12-09 14:40:25 +09:00
parent ac4df02654
commit bb3b34c9b1
36 changed files with 708 additions and 497 deletions
+9
View File
@@ -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';
+71
View File
@@ -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,
),
],
);
}
}
+73
View File
@@ -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(),
);
}
}
+1
View File
@@ -0,0 +1 @@
export 'common/item_card_widget.dart';
+133
View File
@@ -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,
),
),
),
],
);
}
}
+2
View File
@@ -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});
+11 -105
View File
@@ -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,
),
),
),
],
);
}
}
+2
View File
@@ -0,0 +1,2 @@
export 'stage/rest_ui.dart';
export 'stage/shop_ui.dart';
+5 -54
View File
@@ -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,
),
);
},