update : icon image

This commit is contained in:
2025-12-07 16:48:03 +09:00
parent 8771f2c1af
commit d5609aff0f
21 changed files with 684 additions and 394 deletions
-277
View File
@@ -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)"),
),
],
),
);
}
}
+37
View File
@@ -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)"),
),
],
),
);
}
}
+333
View File
@@ -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,
),
],
);
}
}