update : icon image
This commit is contained in:
+11
-1
@@ -4,6 +4,7 @@ import 'game/data/item_table.dart';
|
||||
import 'game/data/enemy_table.dart';
|
||||
import 'game/data/player_table.dart';
|
||||
import 'providers/battle_provider.dart';
|
||||
import 'providers/shop_provider.dart'; // Import ShopProvider
|
||||
import 'screens/main_menu_screen.dart';
|
||||
|
||||
void main() async {
|
||||
@@ -20,7 +21,16 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider(create: (_) => BattleProvider())],
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => ShopProvider()),
|
||||
ChangeNotifierProxyProvider<ShopProvider, BattleProvider>(
|
||||
create: (context) => BattleProvider(
|
||||
shopProvider: Provider.of<ShopProvider>(context, listen: false),
|
||||
),
|
||||
update: (context, shopProvider, battleProvider) =>
|
||||
battleProvider ?? BattleProvider(shopProvider: shopProvider),
|
||||
),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: "Colosseum's Choice",
|
||||
theme: ThemeData.dark(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async'; // StreamController 사용을 위해 import
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart'; // For context.read in _prepareNextStage
|
||||
import '../game/model/entity.dart';
|
||||
import '../game/model/item.dart';
|
||||
import '../game/model/status_effect.dart';
|
||||
@@ -17,6 +18,7 @@ import '../game/model/effect_event.dart'; // EffectEvent import
|
||||
|
||||
import '../game/save_manager.dart';
|
||||
import '../game/config/game_config.dart';
|
||||
import 'shop_provider.dart'; // Import ShopProvider
|
||||
|
||||
class EnemyIntent {
|
||||
final EnemyActionType type;
|
||||
@@ -63,7 +65,10 @@ class BattleProvider with ChangeNotifier {
|
||||
final _effectEventController = StreamController<EffectEvent>.broadcast();
|
||||
Stream<EffectEvent> get effectStream => _effectEventController.stream;
|
||||
|
||||
BattleProvider() {
|
||||
// Dependency injection
|
||||
final ShopProvider shopProvider;
|
||||
|
||||
BattleProvider({required this.shopProvider}) {
|
||||
// initializeBattle(); // Do not auto-start logic
|
||||
}
|
||||
|
||||
@@ -231,8 +236,9 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
_addLog("Stage $stage ($type) started! A wild ${enemy.name} appeared.");
|
||||
} else if (type == StageType.shop) {
|
||||
// Generate random items for shop
|
||||
shopItems = _generateShopItems();
|
||||
// Generate random items for shop using ShopProvider
|
||||
shopProvider.generateShopItems(stage);
|
||||
shopItems = shopProvider.availableItems;
|
||||
|
||||
// Dummy enemy to prevent null errors in existing UI (until UI is fully updated)
|
||||
enemy = Character(name: "Merchant", maxHp: 9999, armor: 0, atk: 0);
|
||||
@@ -247,60 +253,13 @@ class BattleProvider with ChangeNotifier {
|
||||
currentStage = StageModel(
|
||||
type: type,
|
||||
enemy: newEnemy,
|
||||
shopItems: shopItems,
|
||||
shopItems: shopItems, // Pass items from ShopProvider
|
||||
);
|
||||
turnCount = 1;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Generate 4 random items for the shop based on current stage tier
|
||||
List<Item> _generateShopItems() {
|
||||
ItemTier currentTier = ItemTier.tier1;
|
||||
if (stage > GameConfig.tier2StageMax)
|
||||
currentTier = ItemTier.tier3;
|
||||
else if (stage > GameConfig.tier1StageMax)
|
||||
currentTier = ItemTier.tier2;
|
||||
|
||||
List<Item> items = [];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ItemTemplate? template = ItemTable.getRandomItem(tier: currentTier);
|
||||
if (template != null) {
|
||||
items.add(template.createItem(stage: stage));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
void rerollShopItems() {
|
||||
const int rerollCost = GameConfig.shopRerollCost;
|
||||
if (player.gold >= rerollCost) {
|
||||
player.gold -= rerollCost;
|
||||
// Modify the existing list because shopItems is final
|
||||
currentStage.shopItems.clear();
|
||||
currentStage.shopItems.addAll(_generateShopItems());
|
||||
|
||||
_addLog("Shop items rerolled for $rerollCost G.");
|
||||
notifyListeners();
|
||||
} else {
|
||||
_addLog("Not enough gold to reroll!");
|
||||
}
|
||||
}
|
||||
|
||||
void buyItem(Item item) {
|
||||
if (player.gold >= item.price) {
|
||||
bool added = player.addToInventory(item);
|
||||
if (added) {
|
||||
player.gold -= item.price;
|
||||
currentStage.shopItems.remove(item); // Remove from shop
|
||||
_addLog("Bought ${item.name} for ${item.price} G.");
|
||||
} else {
|
||||
_addLog("Inventory is full!");
|
||||
}
|
||||
notifyListeners();
|
||||
} else {
|
||||
_addLog("Not enough gold!");
|
||||
}
|
||||
}
|
||||
// Shop-related methods are now handled by ShopProvider
|
||||
|
||||
// Replaces _spawnEnemy
|
||||
// void _spawnEnemy() { ... } - Removed
|
||||
@@ -737,18 +696,25 @@ class BattleProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void selectReward(Item item) {
|
||||
bool selectReward(Item item) {
|
||||
if (item.id == "reward_skip") {
|
||||
_addLog("Skipped reward.");
|
||||
_completeStage();
|
||||
return true;
|
||||
} else {
|
||||
bool added = player.addToInventory(item);
|
||||
if (added) {
|
||||
_addLog("Added ${item.name} to inventory.");
|
||||
_completeStage();
|
||||
return true;
|
||||
} else {
|
||||
_addLog("Inventory is full! ${item.name} discarded.");
|
||||
_addLog("Inventory is full! Could not take ${item.name}.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _completeStage() {
|
||||
// Heal player after selecting reward
|
||||
int healAmount = GameMath.floor(player.totalMaxHp * GameConfig.stageHealRatio);
|
||||
player.heal(healAmount);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../game/model/item.dart';
|
||||
import '../game/model/entity.dart';
|
||||
import '../game/data/item_table.dart';
|
||||
import '../game/enums.dart';
|
||||
import '../game/config/game_config.dart';
|
||||
import '../utils/game_math.dart';
|
||||
|
||||
class ShopProvider with ChangeNotifier {
|
||||
List<Item> availableItems = [];
|
||||
String _lastShopMessage = '';
|
||||
|
||||
String get lastShopMessage => _lastShopMessage;
|
||||
|
||||
void clearMessage() {
|
||||
_lastShopMessage = '';
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void generateShopItems(int stage) {
|
||||
ItemTier currentTier = ItemTier.tier1;
|
||||
if (stage > GameConfig.tier2StageMax)
|
||||
currentTier = ItemTier.tier3;
|
||||
else if (stage > GameConfig.tier1StageMax)
|
||||
currentTier = ItemTier.tier2;
|
||||
|
||||
availableItems = [];
|
||||
for (int i = 0; i < 4; i++) { // Generate 4 items
|
||||
ItemTemplate? template = ItemTable.getRandomItem(tier: currentTier);
|
||||
if (template != null) {
|
||||
availableItems.add(template.createItem(stage: stage));
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool rerollShopItems(Character player, int currentStageNumber) {
|
||||
const int rerollCost = GameConfig.shopRerollCost;
|
||||
if (player.gold >= rerollCost) {
|
||||
player.gold -= rerollCost;
|
||||
generateShopItems(currentStageNumber); // Regenerate based on current stage
|
||||
_lastShopMessage = "Shop items rerolled for $rerollCost G.";
|
||||
notifyListeners();
|
||||
return true;
|
||||
} else {
|
||||
_lastShopMessage = "Not enough gold to reroll!";
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool buyItem(Item item, Character player) {
|
||||
if (player.gold >= item.price) {
|
||||
if (player.inventory.length < player.maxInventorySize) {
|
||||
player.gold -= item.price;
|
||||
player.addToInventory(item);
|
||||
availableItems.remove(item); // Remove from shop
|
||||
_lastShopMessage = "Bought ${item.name} for ${item.price} G.";
|
||||
notifyListeners();
|
||||
return true;
|
||||
} else {
|
||||
_lastShopMessage = "Inventory is full! Cannot buy ${item.name}.";
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_lastShopMessage = "Not enough gold!";
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import '../utils/item_utils.dart';
|
||||
import '../widgets/battle/character_status_card.dart';
|
||||
import '../widgets/battle/battle_log_overlay.dart';
|
||||
import '../widgets/battle/floating_battle_texts.dart';
|
||||
import '../widgets/battle/stage_ui.dart';
|
||||
import '../widgets/stage/shop_ui.dart';
|
||||
import '../widgets/stage/rest_ui.dart';
|
||||
import '../widgets/battle/shake_widget.dart';
|
||||
import '../widgets/battle/battle_animation_widget.dart';
|
||||
import '../widgets/battle/explosion_widget.dart';
|
||||
@@ -489,7 +490,6 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"ATK",
|
||||
Icons.whatshot,
|
||||
ThemeConfig.btnActionActive,
|
||||
ActionType.attack,
|
||||
battleProvider.isPlayerTurn &&
|
||||
@@ -501,7 +501,6 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
_buildFloatingActionButton(
|
||||
context,
|
||||
"DEF",
|
||||
Icons.shield,
|
||||
ThemeConfig.btnDefendActive,
|
||||
ActionType.defend,
|
||||
battleProvider.isPlayerTurn &&
|
||||
@@ -564,7 +563,17 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
bool isSkip = item.id == "reward_skip";
|
||||
return SimpleDialogOption(
|
||||
onPressed: () {
|
||||
battleProvider.selectReward(item);
|
||||
bool success = battleProvider.selectReward(item);
|
||||
if (!success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
"Inventory is full! Cannot take item.",
|
||||
),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -587,10 +596,11 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
: ThemeConfig.rarityCommon,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(item.slot),
|
||||
color: ItemUtils.getColor(item.slot),
|
||||
size: 24,
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
width: 24,
|
||||
height: 24,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
if (!isSkip) const SizedBox(width: 12),
|
||||
@@ -722,18 +732,30 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
Widget _buildFloatingActionButton(
|
||||
BuildContext context,
|
||||
String label,
|
||||
IconData icon,
|
||||
Color color,
|
||||
ActionType actionType,
|
||||
bool isEnabled,
|
||||
) {
|
||||
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
|
||||
? () => _showRiskLevelSelection(context, actionType)
|
||||
: null,
|
||||
backgroundColor: isEnabled ? color : ThemeConfig.btnDisabled,
|
||||
child: Icon(icon),
|
||||
child: Image.asset(
|
||||
iconPath,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: ThemeConfig.textColorWhite, // Tint icon white
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,13 +125,12 @@ class InventoryScreen extends StatelessWidget {
|
||||
left: 4,
|
||||
top: 4,
|
||||
child: Opacity(
|
||||
opacity: item != null ? 0.2 : 0.1,
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(slot),
|
||||
size: 40,
|
||||
color: item != null
|
||||
? ItemUtils.getColor(slot)
|
||||
: ThemeConfig.textColorGrey,
|
||||
opacity: item != null ? 0.5 : 0.2, // Increase opacity slightly for images
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(slot),
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -238,11 +237,12 @@ class InventoryScreen extends StatelessWidget {
|
||||
left: 4,
|
||||
top: 4,
|
||||
child: Opacity(
|
||||
opacity: 0.2,
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(item.slot),
|
||||
size: 40,
|
||||
color: ItemUtils.getColor(item.slot),
|
||||
opacity: 0.5, // Adjusted opacity for image visibility
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -37,6 +37,7 @@ class _MainMenuScreenState extends State<MainMenuScreen> {
|
||||
Future<void> _continueGame() async {
|
||||
final data = await SaveManager.loadGame();
|
||||
if (data != null && mounted) {
|
||||
// BattleProvider is already provided with ShopProvider via ProxyProvider in main.dart
|
||||
context.read<BattleProvider>().loadFromSave(data);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
|
||||
@@ -16,29 +16,16 @@ class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static IconData getIcon(EquipmentSlot slot) {
|
||||
static String getIconPath(EquipmentSlot slot) {
|
||||
switch (slot) {
|
||||
case EquipmentSlot.weapon:
|
||||
return Icons.change_history; // Triangle
|
||||
return 'assets/data/icon/icon_weapon.png';
|
||||
case EquipmentSlot.shield:
|
||||
return Icons.shield;
|
||||
return 'assets/data/icon/icon_shield.png';
|
||||
case EquipmentSlot.armor:
|
||||
return Icons.checkroom;
|
||||
return 'assets/data/icon/icon_armor.png';
|
||||
case EquipmentSlot.accessory:
|
||||
return Icons.diamond;
|
||||
}
|
||||
}
|
||||
|
||||
static Color getColor(EquipmentSlot slot) {
|
||||
switch (slot) {
|
||||
case EquipmentSlot.weapon:
|
||||
return Colors.red;
|
||||
case EquipmentSlot.shield:
|
||||
return Colors.blue;
|
||||
case EquipmentSlot.armor:
|
||||
return Colors.blue;
|
||||
case EquipmentSlot.accessory:
|
||||
return Colors.orange;
|
||||
return 'assets/data/icon/icon_accessory.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../providers/battle_provider.dart';
|
||||
import '../../game/model/item.dart';
|
||||
import '../../utils/item_utils.dart';
|
||||
import '../../game/enums.dart';
|
||||
import '../../game/config/theme_config.dart';
|
||||
|
||||
class ShopUI extends StatelessWidget {
|
||||
final BattleProvider battleProvider;
|
||||
|
||||
const ShopUI({super.key, required this.battleProvider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final player = battleProvider.player;
|
||||
final shopItems = battleProvider.currentStage.shopItems;
|
||||
|
||||
return Container(
|
||||
color: ThemeConfig.shopBg,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header: Merchant Icon & Player Gold
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.store, size: 32, color: ThemeConfig.mainIconColor),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Merchant",
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.monetization_on, color: ThemeConfig.statGoldColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"${player.gold} G",
|
||||
style: const TextStyle(
|
||||
color: ThemeConfig.statGoldColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(color: ThemeConfig.textColorGrey),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Shop Items Grid
|
||||
Expanded(
|
||||
child: shopItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Sold Out",
|
||||
style: TextStyle(color: ThemeConfig.textColorGrey, fontSize: 24),
|
||||
),
|
||||
)
|
||||
: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2, // 2 columns
|
||||
crossAxisSpacing: 16.0,
|
||||
mainAxisSpacing: 16.0,
|
||||
childAspectRatio: 0.8, // Taller cards
|
||||
),
|
||||
itemCount: shopItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = shopItems[index];
|
||||
final canBuy = player.gold >= item.price;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => _showBuyConfirmation(context, item),
|
||||
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(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Icon
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
ItemUtils.getIcon(item.slot),
|
||||
size: 48,
|
||||
color: ItemUtils.getColor(item.slot),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Name
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ItemUtils.getRarityColor(item.rarity),
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Stats
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: _buildItemStatText(item),
|
||||
),
|
||||
// Price Button
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: canBuy ? ThemeConfig.statGoldColor : ThemeConfig.btnDisabled,
|
||||
foregroundColor: Colors.black,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: canBuy
|
||||
? () => _showBuyConfirmation(context, item)
|
||||
: null,
|
||||
child: Text(
|
||||
"${item.price} G",
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Footer Buttons (Reroll & Leave)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ThemeConfig.btnRerollBg,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
onPressed: player.gold >= 50
|
||||
? () => battleProvider.rerollShopItems()
|
||||
: null,
|
||||
icon: const Icon(Icons.refresh, color: ThemeConfig.textColorWhite),
|
||||
label: const Text(
|
||||
"Reroll (50 G)",
|
||||
style: TextStyle(color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ThemeConfig.btnLeaveBg,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
onPressed: () => battleProvider.proceedToNextStage(),
|
||||
icon: const Icon(Icons.exit_to_app, color: ThemeConfig.textColorWhite),
|
||||
label: const Text(
|
||||
"Leave Shop",
|
||||
style: TextStyle(color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showBuyConfirmation(BuildContext context, Item item) {
|
||||
if (battleProvider.player.gold < item.price) return;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("Buy Item"),
|
||||
content: Text("Buy ${item.name} for ${item.price} G?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: ThemeConfig.statGoldColor),
|
||||
onPressed: () {
|
||||
battleProvider.buyItem(item);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text("Buy", style: TextStyle(color: Colors.black)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemStatText(Item item) {
|
||||
List<String> stats = [];
|
||||
if (item.atkBonus > 0) stats.add("ATK +${item.atkBonus}");
|
||||
if (item.hpBonus > 0) stats.add("HP +${item.hpBonus}");
|
||||
if (item.armorBonus > 0) stats.add("DEF +${item.armorBonus}");
|
||||
if (item.luck > 0) stats.add("LUCK +${item.luck}");
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(fontSize: 10, color: Colors.white70),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (item.effects.isNotEmpty)
|
||||
Text(
|
||||
item.effects.first.type.name.toUpperCase(),
|
||||
style: const TextStyle(fontSize: 9, color: ThemeConfig.rarityLegendary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RestUI extends StatelessWidget {
|
||||
final BattleProvider battleProvider;
|
||||
|
||||
const RestUI({super.key, required this.battleProvider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.local_hotel, size: 64, color: ThemeConfig.btnRestBg),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Rest Area", style: TextStyle(fontSize: 24, color: ThemeConfig.textColorWhite)),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Take a breath and heal.", style: TextStyle(color: ThemeConfig.textColorWhite)),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
battleProvider.player.heal(20);
|
||||
battleProvider.proceedToNextStage();
|
||||
},
|
||||
child: const Text("Rest & Leave (+20 HP)"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../providers/battle_provider.dart';
|
||||
import '../../../game/config/theme_config.dart';
|
||||
|
||||
class RestUI extends StatelessWidget {
|
||||
final BattleProvider battleProvider;
|
||||
|
||||
const RestUI({super.key, required this.battleProvider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.local_hotel, size: 64, color: ThemeConfig.btnRestBg),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Rest Area", style: TextStyle(fontSize: 24, color: ThemeConfig.textColorWhite)),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Take a breath and heal.", style: TextStyle(color: ThemeConfig.textColorWhite)),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Use GameConfig for heal amount if possible, or keep hardcoded for now?
|
||||
// Let's use GameConfig.stageHealRatio * 2 or fixed 20?
|
||||
// Previous logic was hardcoded 20. Let's keep it simple for now or use a better logic.
|
||||
// "Rest & Leave (+20 HP)" -> Hardcoded in text too.
|
||||
battleProvider.player.heal(20);
|
||||
battleProvider.proceedToNextStage();
|
||||
},
|
||||
child: const Text("Rest & Leave (+20 HP)"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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';
|
||||
|
||||
class ShopUI extends StatelessWidget {
|
||||
final BattleProvider battleProvider;
|
||||
|
||||
const ShopUI({super.key, required this.battleProvider});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ShopProvider>(
|
||||
builder: (context, shopProvider, child) {
|
||||
final player = battleProvider.player;
|
||||
final shopItems = shopProvider.availableItems;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (shopProvider.lastShopMessage.isNotEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(shopProvider.lastShopMessage),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
shopProvider.clearMessage();
|
||||
}
|
||||
});
|
||||
|
||||
return Container(
|
||||
color: ThemeConfig.shopBg,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.store,
|
||||
size: 32,
|
||||
color: ThemeConfig.mainIconColor,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Merchant",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ThemeConfig.textColorWhite,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.monetization_on,
|
||||
color: ThemeConfig.statGoldColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"${player.gold} G",
|
||||
style: const TextStyle(
|
||||
color: ThemeConfig.statGoldColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(color: ThemeConfig.textColorGrey),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Expanded(
|
||||
child: shopItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Sold Out",
|
||||
style: TextStyle(
|
||||
color: ThemeConfig.textColorGrey,
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
)
|
||||
: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16.0,
|
||||
mainAxisSpacing: 16.0,
|
||||
childAspectRatio: 0.8,
|
||||
),
|
||||
itemCount: shopItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = shopItems[index];
|
||||
final canBuy = player.gold >= item.price;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => _showBuyConfirmation(
|
||||
context,
|
||||
item,
|
||||
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(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
ItemUtils.getIconPath(item.slot),
|
||||
width: 48,
|
||||
height: 48,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
ThemeConfig.fontWeightBold,
|
||||
color: ItemUtils.getRarityColor(
|
||||
item.rarity,
|
||||
),
|
||||
fontSize: ThemeConfig.fontSizeMedium,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: _buildItemStatText(item),
|
||||
),
|
||||
SizedBox(
|
||||
height: 32,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: canBuy
|
||||
? ThemeConfig.statGoldColor
|
||||
: ThemeConfig.btnDisabled,
|
||||
foregroundColor: Colors.black,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: canBuy
|
||||
? () => _showBuyConfirmation(
|
||||
context,
|
||||
item,
|
||||
shopProvider,
|
||||
player,
|
||||
)
|
||||
: null,
|
||||
child: Text(
|
||||
"${item.price} G",
|
||||
style: const TextStyle(
|
||||
fontWeight:
|
||||
ThemeConfig.fontWeightBold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ThemeConfig.btnRerollBg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onPressed: player.gold >= GameConfig.shopRerollCost
|
||||
? () => shopProvider.rerollShopItems(
|
||||
player,
|
||||
battleProvider.stage,
|
||||
)
|
||||
: null,
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
color: ThemeConfig.textColorWhite,
|
||||
),
|
||||
label: Text(
|
||||
"Reroll (${GameConfig.shopRerollCost} G)",
|
||||
style: const TextStyle(color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ThemeConfig.btnLeaveBg,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onPressed: () => battleProvider.proceedToNextStage(),
|
||||
icon: const Icon(
|
||||
Icons.exit_to_app,
|
||||
color: ThemeConfig.textColorWhite,
|
||||
),
|
||||
label: const Text(
|
||||
"Leave Shop",
|
||||
style: TextStyle(color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showBuyConfirmation(
|
||||
BuildContext context,
|
||||
Item item,
|
||||
ShopProvider shopProvider,
|
||||
Character player,
|
||||
) {
|
||||
if (player.gold < item.price) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Not enough gold!"),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("Buy Item"),
|
||||
content: Text("Buy ${item.name} for ${item.price} G?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ThemeConfig.statGoldColor,
|
||||
),
|
||||
onPressed: () {
|
||||
shopProvider.buyItem(item, player);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text("Buy", style: TextStyle(color: Colors.black)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemStatText(Item item) {
|
||||
List<String> stats = [];
|
||||
if (item.atkBonus > 0) stats.add("ATK +${item.atkBonus}");
|
||||
if (item.hpBonus > 0) stats.add("HP +${item.hpBonus}");
|
||||
if (item.armorBonus > 0) stats.add("DEF +${item.armorBonus}");
|
||||
if (item.luck > 0) stats.add("LUCK +${item.luck}");
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeSmall,
|
||||
color: Colors.white70,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (item.effects.isNotEmpty)
|
||||
Text(
|
||||
item.effects.first.type.name.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontSize: ThemeConfig.fontSizeTiny,
|
||||
color: ThemeConfig.rarityLegendary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user