This commit is contained in:
2025-12-04 16:50:57 +09:00
parent 0a7c50e6c9
commit 37a634643e
23 changed files with 1062 additions and 663 deletions
+139 -591
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/battle_provider.dart';
import '../game/model/entity.dart';
import '../game/enums.dart';
import '../game/model/item.dart';
import '../game/model/damage_event.dart';
@@ -9,6 +9,10 @@ import '../game/model/effect_event.dart';
import 'dart:async';
import '../widgets/responsive_container.dart';
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';
class BattleScreen extends StatefulWidget {
const BattleScreen({super.key});
@@ -18,25 +22,19 @@ class BattleScreen extends StatefulWidget {
}
class _BattleScreenState extends State<BattleScreen> {
final ScrollController _scrollController = ScrollController();
final List<_DamageTextData> _floatingDamageTexts = [];
final List<_FloatingEffectData> _floatingEffects = [];
final List<_FeedbackTextData> _floatingFeedbackTexts = [];
final List<DamageTextData> _floatingDamageTexts = [];
final List<FloatingEffectData> _floatingEffects = [];
final List<FeedbackTextData> _floatingFeedbackTexts = [];
StreamSubscription<DamageEvent>? _damageSubscription;
StreamSubscription<EffectEvent>? _effectSubscription;
final GlobalKey _playerKey = GlobalKey();
final GlobalKey _enemyKey = GlobalKey();
final GlobalKey _stackKey = GlobalKey();
bool _showLogs = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
});
final battleProvider = context.read<BattleProvider>();
_damageSubscription = battleProvider.damageStream.listen(
_addFloatingDamageText,
@@ -48,7 +46,6 @@ class _BattleScreenState extends State<BattleScreen> {
@override
void dispose() {
_scrollController.dispose();
_damageSubscription?.cancel();
_effectSubscription?.cancel();
super.dispose();
@@ -82,12 +79,12 @@ class _BattleScreenState extends State<BattleScreen> {
setState(() {
_floatingDamageTexts.add(
_DamageTextData(
DamageTextData(
id: id,
widget: Positioned(
left: position.dx,
top: position.dy,
child: _FloatingDamageText(
child: FloatingDamageText(
key: ValueKey(id),
damage: event.damage.toString(),
color: event.color,
@@ -153,12 +150,12 @@ class _BattleScreenState extends State<BattleScreen> {
final String id = UniqueKey().toString();
setState(() {
_floatingFeedbackTexts.add(
_FeedbackTextData(
FeedbackTextData(
id: id,
widget: Positioned(
left: position.dx,
top: position.dy,
child: _FloatingFeedbackText(
child: FloatingFeedbackText(
key: ValueKey(id),
feedback: feedbackText,
color: feedbackColor,
@@ -213,12 +210,12 @@ class _BattleScreenState extends State<BattleScreen> {
setState(() {
_floatingEffects.add(
_FloatingEffectData(
FloatingEffectData(
id: id,
widget: Positioned(
left: position.dx,
top: position.dy,
child: _FloatingEffect(
child: FloatingEffect(
key: ValueKey(id),
icon: icon,
color: color,
@@ -295,13 +292,6 @@ class _BattleScreenState extends State<BattleScreen> {
onPressed: () {
context.read<BattleProvider>().playerAction(actionType, risk);
Navigator.pop(context);
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -329,15 +319,18 @@ class _BattleScreenState extends State<BattleScreen> {
child: Consumer<BattleProvider>(
builder: (context, battleProvider, child) {
if (battleProvider.currentStage.type == StageType.shop) {
return _buildShopUI(context, battleProvider);
return ShopUI(battleProvider: battleProvider);
} else if (battleProvider.currentStage.type == StageType.rest) {
return _buildRestUI(context, battleProvider);
return RestUI(battleProvider: battleProvider);
}
return Stack(
key: _stackKey,
children: [
// 1. Background (Black)
Container(color: Colors.black87),
// 2. Battle Content (Top Bar + Characters)
Column(
children: [
// Top Bar
@@ -346,125 +339,135 @@ class _BattleScreenState extends State<BattleScreen> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Stage ${battleProvider.stage}",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Stage ${battleProvider.stage}",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
Text(
"Turn ${battleProvider.turnCount}",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Turn ${battleProvider.turnCount}",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
),
],
),
),
// Battle Area
// Battle Area (Characters) - Expanded to fill available space
Expanded(
child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 40.0),
padding: const EdgeInsets.all(70.0),
child: Column(
padding: const EdgeInsets.all(16.0),
child: Stack(
children: [
// 적 영역 (우측 상단)
Expanded(
child: Align(
alignment: Alignment.topRight,
child: _buildCharacterStatus(
battleProvider.enemy,
isPlayer: false,
isTurn: !battleProvider.isPlayerTurn,
key: _enemyKey,
),
// Enemy (Top Right)
Positioned(
top: 0,
right: 0,
child: CharacterStatusCard(
character: battleProvider.enemy,
isPlayer: false,
isTurn: !battleProvider.isPlayerTurn,
key: _enemyKey,
),
),
// 플레이어 영역 (좌측 하단)
Expanded(
child: Align(
alignment: Alignment.bottomLeft,
child: _buildCharacterStatus(
battleProvider.player,
isPlayer: true,
isTurn: battleProvider.isPlayerTurn,
key: _playerKey,
),
// Player (Bottom Left)
Positioned(
bottom: 80, // Space for FABs
left: 0,
child: CharacterStatusCard(
character: battleProvider.player,
isPlayer: true,
isTurn: battleProvider.isPlayerTurn,
key: _playerKey,
),
),
],
),
),
),
// Action Buttons
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
if (battleProvider.logs.isNotEmpty)
Container(
height: 60,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: ListView.builder(
reverse: true,
itemCount: battleProvider.logs.length,
itemBuilder: (context, index) {
final logIndex =
battleProvider.logs.length - 1 - index;
return Text(
battleProvider.logs[logIndex],
style: const TextStyle(
color: Colors.white70,
fontSize: 12,
),
);
},
),
),
const SizedBox(height: 16),
Card(
color: Colors.grey[900],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildActionButton(
context,
"ATTACK",
ActionType.attack,
battleProvider.isPlayerTurn &&
!battleProvider.player.isDead &&
!battleProvider.enemy.isDead &&
!battleProvider.showRewardPopup,
),
_buildActionButton(
context,
"DEFEND",
ActionType.defend,
battleProvider.isPlayerTurn &&
!battleProvider.player.isDead &&
!battleProvider.enemy.isDead &&
!battleProvider.showRewardPopup,
),
],
),
),
),
],
),
),
],
),
// 3. Logs Overlay
if (_showLogs && battleProvider.logs.isNotEmpty)
Positioned(
top: 60,
left: 16,
right: 16,
height: 150,
child: BattleLogOverlay(logs: battleProvider.logs),
),
// 4. Floating Action Buttons (Bottom Right)
Positioned(
bottom: 20,
right: 20,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildFloatingActionButton(
context,
"ATK",
Icons.whatshot,
Colors.redAccent,
ActionType.attack,
battleProvider.isPlayerTurn &&
!battleProvider.player.isDead &&
!battleProvider.enemy.isDead &&
!battleProvider.showRewardPopup,
),
const SizedBox(height: 16),
_buildFloatingActionButton(
context,
"DEF",
Icons.shield,
Colors.blueAccent,
ActionType.defend,
battleProvider.isPlayerTurn &&
!battleProvider.player.isDead &&
!battleProvider.enemy.isDead &&
!battleProvider.showRewardPopup,
),
],
),
),
// 5. Log Toggle Button (Bottom Left)
Positioned(
bottom: 20,
left: 20,
child: FloatingActionButton(
heroTag: "logToggle",
mini: true,
backgroundColor: Colors.grey[800],
onPressed: () {
setState(() {
_showLogs = !_showLogs;
});
},
child: Icon(
_showLogs ? Icons.visibility_off : Icons.visibility,
color: Colors.white,
),
),
),
// Reward Popup
if (battleProvider.showRewardPopup)
Container(
color: Colors.black54,
@@ -519,9 +522,11 @@ class _BattleScreenState extends State<BattleScreen> {
),
),
),
// Floating Effects
..._floatingDamageTexts.map((e) => e.widget),
..._floatingEffects.map((e) => e.widget),
..._floatingFeedbackTexts.map((e) => e.widget), // 새로운 피드백 텍스트 추가
..._floatingFeedbackTexts.map((e) => e.widget),
],
);
},
@@ -529,49 +534,6 @@ class _BattleScreenState extends State<BattleScreen> {
);
}
Widget _buildShopUI(BuildContext context, BattleProvider battleProvider) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.store, size: 64, color: Colors.amber),
const SizedBox(height: 16),
const Text("Merchant Shop", style: TextStyle(fontSize: 24)),
const SizedBox(height: 8),
const Text("Buying/Selling feature coming soon!"),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => battleProvider.proceedToNextStage(),
child: const Text("Leave Shop"),
),
],
),
);
}
Widget _buildRestUI(BuildContext context, BattleProvider battleProvider) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.local_hotel, size: 64, color: Colors.blue),
const SizedBox(height: 16),
const Text("Rest Area", style: TextStyle(fontSize: 24)),
const SizedBox(height: 8),
const Text("Take a breath and heal."),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
battleProvider.player.heal(20);
battleProvider.proceedToNextStage();
},
child: const Text("Rest & Leave (+20 HP)"),
),
],
),
);
}
Widget _buildItemStatText(Item item) {
List<String> stats = [];
if (item.atkBonus > 0) stats.add("+${item.atkBonus} ATK");
@@ -605,435 +567,21 @@ class _BattleScreenState extends State<BattleScreen> {
);
}
Widget _buildCharacterStatus(
Character character, {
bool isPlayer = false,
bool isTurn = false,
Key? key,
}) {
return Column(
key: key,
children: [
Text("Armor: ${character.armor}"),
Text(
"${character.name}: HP ${character.hp}/${character.totalMaxHp}",
style: TextStyle(
color: character.isDead ? Colors.red : Colors.white,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 100,
child: LinearProgressIndicator(
value: character.totalMaxHp > 0
? character.hp / character.totalMaxHp
: 0,
color: !isPlayer ? Colors.red : Colors.green,
backgroundColor: Colors.grey,
),
),
if (character.statusEffects.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Wrap(
spacing: 4.0,
children: character.statusEffects.map((effect) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.deepOrange,
borderRadius: BorderRadius.circular(4),
),
child: Text(
"${effect.type.name.toUpperCase()} (${effect.duration})",
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
);
}).toList(),
),
),
Text("ATK: ${character.totalAtk}"),
Text("DEF: ${character.totalDefense}"),
// 캐릭터 아이콘/이미지 영역 추가
Container(
width: 100, // 임시 크기
height: 100, // 임시 크기
decoration: BoxDecoration(
color: isPlayer
? Colors.lightBlue
: Colors.deepOrange, // 플레이어/적 구분 색상
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: isPlayer
? const Icon(
Icons.person,
size: 60,
color: Colors.white,
) // 플레이어 아이콘
: const Icon(
Icons.psychology,
size: 60,
color: Colors.white,
), // 적 아이콘 (몬스터 대신)
),
),
const SizedBox(height: 8), // 아이콘과 정보 사이 간격
if (!isPlayer)
Consumer<BattleProvider>(
builder: (context, provider, child) {
if (provider.currentEnemyIntent != null && !character.isDead) {
final intent = provider.currentEnemyIntent!;
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.redAccent),
),
child: Column(
children: [
Text(
"INTENT",
style: TextStyle(
color: Colors.redAccent,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
intent.type == EnemyActionType.attack
? Icons.flash_on
: Icons.shield,
color: Colors.yellow,
size: 16,
),
const SizedBox(width: 4),
Text(
intent.description,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
],
),
],
),
),
);
}
return const SizedBox.shrink();
},
),
],
);
}
Widget _buildActionButton(
Widget _buildFloatingActionButton(
BuildContext context,
String text,
String label,
IconData icon,
Color color,
ActionType actionType,
bool isEnabled,
) {
return ElevatedButton(
return FloatingActionButton(
heroTag: label,
onPressed: isEnabled
? () => _showRiskLevelSelection(context, actionType)
: null,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
backgroundColor: Colors.blueGrey,
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
child: Text(text),
backgroundColor: isEnabled ? color : Colors.grey,
child: Icon(icon),
);
}
}
class _FloatingDamageText extends StatefulWidget {
final String damage;
final Color color;
final VoidCallback onRemove;
const _FloatingDamageText({
Key? key,
required this.damage,
required this.color,
required this.onRemove,
}) : super(key: key);
@override
__FloatingDamageTextState createState() => __FloatingDamageTextState();
}
class __FloatingDamageTextState extends State<_FloatingDamageText>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -1.5),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
),
);
_controller.forward().then((_) {
if (mounted) {
widget.onRemove();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FractionalTranslation(
translation: _offsetAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Material(
color: Colors.transparent,
child: Text(
widget.damage,
style: TextStyle(
color: widget.color,
fontSize: 20,
fontWeight: FontWeight.bold,
shadows: const [
Shadow(
blurRadius: 2.0,
color: Colors.black,
offset: Offset(1.0, 1.0),
),
],
),
),
),
),
);
},
);
}
}
class _DamageTextData {
final String id;
final Widget widget;
_DamageTextData({required this.id, required this.widget});
}
class _FloatingEffect extends StatefulWidget {
final IconData icon;
final Color color;
final double size;
final VoidCallback onRemove;
const _FloatingEffect({
Key? key,
required this.icon,
required this.color,
required this.size,
required this.onRemove,
}) : super(key: key);
@override
__FloatingEffectState createState() => __FloatingEffectState();
}
class __FloatingEffectState extends State<_FloatingEffect>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 0.5,
end: 1.5,
).animate(CurvedAnimation(parent: _controller, curve: Curves.elasticOut));
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
),
);
_controller.forward().then((_) {
if (mounted) {
widget.onRemove();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Icon(widget.icon, color: widget.color, size: widget.size),
),
);
},
);
}
}
class _FloatingEffectData {
final String id;
final Widget widget;
_FloatingEffectData({required this.id, required this.widget});
}
// 새로운 _FloatingFeedbackText 위젯
class _FloatingFeedbackText extends StatefulWidget {
final String feedback;
final Color color;
final VoidCallback onRemove;
const _FloatingFeedbackText({
Key? key,
required this.feedback,
required this.color,
required this.onRemove,
}) : super(key: key);
@override
__FloatingFeedbackTextState createState() => __FloatingFeedbackTextState();
}
class __FloatingFeedbackTextState extends State<_FloatingFeedbackText>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -1.5),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut),
),
);
_controller.forward().then((_) {
if (mounted) {
widget.onRemove();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FractionalTranslation(
translation: _offsetAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Material(
color: Colors.transparent,
child: Text(
widget.feedback,
style: TextStyle(
color: widget.color,
fontSize: 20,
fontWeight: FontWeight.bold,
shadows: const [
Shadow(
blurRadius: 2.0,
color: Colors.black,
offset: Offset(1.0, 1.0),
),
],
),
),
),
),
);
},
);
}
}
class _FeedbackTextData {
final String id;
final Widget widget;
_FeedbackTextData({required this.id, required this.widget});
}
+29 -13
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/battle_provider.dart';
import '../game/data/player_table.dart';
import 'main_wrapper.dart';
import '../widgets/responsive_container.dart';
@@ -9,6 +10,15 @@ class CharacterSelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Fetch Warrior data
final warrior = PlayerTable.get("warrior");
if (warrior == null) {
return const Scaffold(
body: Center(child: Text("Error: Player data not found")),
);
}
return Scaffold(
backgroundColor: Colors.black, // Outer background
body: Center(
@@ -51,37 +61,43 @@ class CharacterSelectionScreen extends StatelessWidget {
color: Colors.blue,
),
const SizedBox(height: 16),
const Text(
"Warrior",
style: TextStyle(
Text(
warrior.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
const Text(
"A balanced fighter with a sword and shield. Great for beginners.",
Text(
warrior.description,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
style: const TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
const Row(
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
"HP: 80",
style: TextStyle(fontWeight: FontWeight.bold),
"HP: ${warrior.baseHp}",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Text(
"ATK: 5",
style: TextStyle(fontWeight: FontWeight.bold),
"ATK: ${warrior.baseAtk}",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Text(
"DEF: 5",
style: TextStyle(fontWeight: FontWeight.bold),
"DEF: ${warrior.baseDefense}",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
+97 -40
View File
@@ -88,41 +88,73 @@ class InventoryScreen extends StatelessWidget {
color: item != null
? Colors.blueGrey[600]
: Colors.grey[800],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
child: Stack(
children: [
// Slot Name (Top Right)
Positioned(
right: 4,
top: 4,
child: Text(
slot.name.toUpperCase(),
style: const TextStyle(
fontSize: 10,
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.grey,
color: Colors.white30,
),
),
const SizedBox(height: 4),
Icon(
ItemUtils.getIcon(slot),
size: 24,
color: item != null
? ItemUtils.getColor(slot)
: Colors.grey,
),
const SizedBox(height: 4),
Text(
item?.name ?? "Empty",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
),
// Faded Icon (Top Left)
Positioned(
left: 4,
top: 4,
child: Opacity(
opacity: item != null ? 0.2 : 0.1,
child: Icon(
ItemUtils.getIcon(slot),
size: 40,
color: item != null
? Colors.white
? ItemUtils.getColor(slot)
: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),
if (item != null) _buildItemStatText(item),
],
),
),
// Content
Center(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const SizedBox(
height: 12,
), // Spacing for top elements
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
item?.name ?? "Empty",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: item != null
? Colors.white
: Colors.grey,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
if (item != null)
FittedBox(
fit: BoxFit.scaleDown,
child: _buildItemStatText(item),
),
],
),
),
),
],
),
),
),
@@ -169,24 +201,49 @@ class InventoryScreen extends StatelessWidget {
},
child: Card(
color: Colors.blueGrey[700],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
child: Stack(
children: [
Icon(
ItemUtils.getIcon(item.slot),
size: 32,
color: ItemUtils.getColor(item.slot),
// Faded Icon in Top-Left
Positioned(
left: 4,
top: 4,
child: Opacity(
opacity: 0.2,
child: Icon(
ItemUtils.getIcon(item.slot),
size: 40,
color: ItemUtils.getColor(item.slot),
),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
item.name,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 10),
overflow: TextOverflow.ellipsis,
// Centered Content
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: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
FittedBox(
fit: BoxFit.scaleDown,
child: _buildItemStatText(item),
),
],
),
),
),
_buildItemStatText(item),
],
),
),