update
This commit is contained in:
+276
-239
@@ -50,10 +50,12 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
bool _showLogs = false;
|
||||
bool _isPlayerAttacking = false; // Player Attack Animation State
|
||||
bool _isEnemyAttacking = false; // Enemy Attack Animation State
|
||||
DateTime? _lastFeedbackTime; // Cooldown to prevent duplicate feedback texts
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
print("[UI Debug] BattleScreen initialized: ${hashCode}"); // Debug Log
|
||||
final battleProvider = context.read<BattleProvider>();
|
||||
_damageSubscription = battleProvider.damageStream.listen(
|
||||
_addFloatingDamageText,
|
||||
@@ -71,54 +73,52 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
}
|
||||
|
||||
void _addFloatingDamageText(DamageEvent event) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (!mounted) return;
|
||||
|
||||
GlobalKey targetKey = event.target == DamageTarget.player
|
||||
? _playerKey
|
||||
: _enemyKey;
|
||||
GlobalKey targetKey = event.target == DamageTarget.player
|
||||
? _playerKey
|
||||
: _enemyKey;
|
||||
|
||||
if (targetKey.currentContext == null) return;
|
||||
RenderBox? renderBox =
|
||||
targetKey.currentContext!.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
if (targetKey.currentContext == null) return;
|
||||
RenderBox? renderBox =
|
||||
targetKey.currentContext!.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
|
||||
Offset position = renderBox.localToGlobal(Offset.zero);
|
||||
Offset position = renderBox.localToGlobal(Offset.zero);
|
||||
|
||||
RenderBox? stackRenderBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackRenderBox != null) {
|
||||
Offset stackOffset = stackRenderBox.localToGlobal(Offset.zero);
|
||||
position = position - stackOffset;
|
||||
}
|
||||
RenderBox? stackRenderBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackRenderBox != null) {
|
||||
Offset stackOffset = stackRenderBox.localToGlobal(Offset.zero);
|
||||
position = position - stackOffset;
|
||||
}
|
||||
|
||||
position = position + Offset(renderBox.size.width / 2 - 20, -20);
|
||||
position = position + Offset(renderBox.size.width / 2 - 20, -20);
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
setState(() {
|
||||
_floatingDamageTexts.add(
|
||||
DamageTextData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingDamageText(
|
||||
key: ValueKey(id),
|
||||
damage: event.damage.toString(),
|
||||
color: event.color,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingDamageTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
setState(() {
|
||||
_floatingDamageTexts.add(
|
||||
DamageTextData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingDamageText(
|
||||
key: ValueKey(id),
|
||||
damage: event.damage.toString(),
|
||||
color: event.color,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingDamageTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,108 +128,92 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
if (_processedEffectIds.contains(event.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_processedEffectIds.add(event.id);
|
||||
if (_processedEffectIds.length > 20) {
|
||||
_processedEffectIds.remove(_processedEffectIds.first);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
|
||||
// Feedback Text Cooldown
|
||||
if (event.feedbackType != null) {
|
||||
print("[UI Debug] Feedback Event: ${event.id}, Type: ${event.feedbackType}");
|
||||
if (_lastFeedbackTime != null &&
|
||||
DateTime.now().difference(_lastFeedbackTime!).inMilliseconds < 300) {
|
||||
return; // Skip if too soon
|
||||
}
|
||||
_lastFeedbackTime = DateTime.now();
|
||||
}
|
||||
|
||||
GlobalKey targetKey = event.target == EffectTarget.player
|
||||
? _playerKey
|
||||
: _enemyKey;
|
||||
if (targetKey.currentContext == null) return;
|
||||
|
||||
RenderBox? renderBox =
|
||||
targetKey.currentContext!.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
|
||||
Offset position = renderBox.localToGlobal(Offset.zero);
|
||||
|
||||
RenderBox? stackRenderBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackRenderBox != null) {
|
||||
Offset stackOffset = stackRenderBox.localToGlobal(Offset.zero);
|
||||
position = position - stackOffset;
|
||||
}
|
||||
|
||||
position =
|
||||
position +
|
||||
Offset(renderBox.size.width / 2 - 30, renderBox.size.height / 2 - 30);
|
||||
|
||||
// 0. Prepare Effect Function
|
||||
void showEffect() {
|
||||
if (!mounted) return;
|
||||
|
||||
GlobalKey targetKey = event.target == EffectTarget.player
|
||||
? _playerKey
|
||||
: _enemyKey;
|
||||
if (targetKey.currentContext == null) return;
|
||||
|
||||
RenderBox? renderBox =
|
||||
targetKey.currentContext!.findRenderObject() as RenderBox?;
|
||||
if (renderBox == null) return;
|
||||
|
||||
Offset position = renderBox.localToGlobal(Offset.zero);
|
||||
|
||||
RenderBox? stackRenderBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackRenderBox != null) {
|
||||
Offset stackOffset = stackRenderBox.localToGlobal(Offset.zero);
|
||||
position = position - stackOffset;
|
||||
}
|
||||
|
||||
position =
|
||||
position +
|
||||
Offset(renderBox.size.width / 2 - 30, renderBox.size.height / 2 - 30);
|
||||
|
||||
// 0. Prepare Effect Function
|
||||
void showEffect() {
|
||||
if (!mounted) return;
|
||||
|
||||
// feedbackType이 존재하면 해당 텍스트를 표시하고 기존 이펙트 아이콘은 건너뜜
|
||||
if (event.feedbackType != null) {
|
||||
String feedbackText;
|
||||
Color feedbackColor;
|
||||
switch (event.feedbackType) {
|
||||
case BattleFeedbackType.miss:
|
||||
feedbackText = "MISS";
|
||||
feedbackColor = ThemeConfig.missText;
|
||||
break;
|
||||
case BattleFeedbackType.failed:
|
||||
feedbackText = "FAILED";
|
||||
feedbackColor = ThemeConfig.failedText;
|
||||
break;
|
||||
default:
|
||||
feedbackText = ""; // Should not happen with current enums
|
||||
feedbackColor = ThemeConfig.textColorWhite;
|
||||
}
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.add(
|
||||
FeedbackTextData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingFeedbackText(
|
||||
key: ValueKey(id),
|
||||
feedback: feedbackText,
|
||||
color: feedbackColor,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingFeedbackTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return; // feedbackType이 있으면 아이콘 이펙트는 표시하지 않음
|
||||
// Handle Feedback Text (MISS / FAILED)
|
||||
if (event.feedbackType != null) {
|
||||
String feedbackText;
|
||||
Color feedbackColor;
|
||||
switch (event.feedbackType) {
|
||||
case BattleFeedbackType.miss:
|
||||
feedbackText = "MISS";
|
||||
feedbackColor = ThemeConfig.missText;
|
||||
break;
|
||||
case BattleFeedbackType.failed:
|
||||
feedbackText = "FAILED";
|
||||
feedbackColor = ThemeConfig.failedText;
|
||||
break;
|
||||
default:
|
||||
feedbackText = "";
|
||||
feedbackColor = ThemeConfig.textColorWhite;
|
||||
}
|
||||
|
||||
// Use BattleConfig for Icon, Color, and Size
|
||||
IconData icon = BattleConfig.getIcon(event.type);
|
||||
Color color = BattleConfig.getColor(event.type, event.risk);
|
||||
double size = BattleConfig.getSize(event.risk);
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
// Prevent duplicate feedback texts for the same event ID (UI Level)
|
||||
if (_floatingFeedbackTexts.any((e) => e.eventId == event.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_floatingEffects.add(
|
||||
FloatingEffectData(
|
||||
_floatingFeedbackTexts.clear(); // Clear previous texts
|
||||
_floatingFeedbackTexts.add(
|
||||
FeedbackTextData(
|
||||
id: id,
|
||||
eventId: event.id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingEffect(
|
||||
child: FloatingFeedbackText(
|
||||
key: ValueKey(id),
|
||||
icon: icon,
|
||||
color: color,
|
||||
size: size,
|
||||
feedback: "$feedbackText (${event.id.substring(0, 4)})",
|
||||
color: feedbackColor,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingEffects.removeWhere((e) => e.id == id);
|
||||
_floatingFeedbackTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -238,133 +222,179 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
),
|
||||
);
|
||||
});
|
||||
return; // Return early for feedback
|
||||
}
|
||||
|
||||
// 1. Attack Animation Trigger (All Risk Levels)
|
||||
if (event.type == ActionType.attack &&
|
||||
event.target == EffectTarget.enemy &&
|
||||
event.feedbackType == null) {
|
||||
// Calculate target position (Enemy) relative to Player
|
||||
final RenderBox? playerBox =
|
||||
_playerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final RenderBox? enemyBox =
|
||||
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
// Handle Icon Effect
|
||||
IconData icon = BattleConfig.getIcon(event.type);
|
||||
Color color = BattleConfig.getColor(event.type, event.risk);
|
||||
double size = BattleConfig.getSize(event.risk);
|
||||
|
||||
if (playerBox != null && enemyBox != null) {
|
||||
final playerPos = playerBox.localToGlobal(Offset.zero);
|
||||
final enemyPos = enemyBox.localToGlobal(Offset.zero);
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
final offset = enemyPos - playerPos;
|
||||
|
||||
// Start Animation: Hide Stats
|
||||
setState(() {
|
||||
_isPlayerAttacking = true;
|
||||
});
|
||||
|
||||
_playerAnimKey.currentState
|
||||
?.animateAttack(offset, () {
|
||||
showEffect(); // Show Effect at Impact!
|
||||
// Trigger impact logic in provider
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
|
||||
// Shake and Explosion ONLY for Risky
|
||||
if (event.risk == RiskLevel.risky) {
|
||||
_shakeKey.currentState?.shake();
|
||||
|
||||
RenderBox? stackBox =
|
||||
_stackKey.currentContext?.findRenderObject()
|
||||
as RenderBox?;
|
||||
if (stackBox != null) {
|
||||
Offset localEnemyPos = stackBox.globalToLocal(enemyPos);
|
||||
// Center of the enemy card roughly
|
||||
localEnemyPos += Offset(
|
||||
enemyBox.size.width / 2,
|
||||
enemyBox.size.height / 2,
|
||||
);
|
||||
_explosionKey.currentState?.explode(localEnemyPos);
|
||||
setState(() {
|
||||
_floatingEffects.add(
|
||||
FloatingEffectData(
|
||||
id: id,
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingEffect(
|
||||
key: ValueKey(id),
|
||||
icon: icon,
|
||||
color: color,
|
||||
size: size,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingEffects.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Player Attack Animation Trigger (Success or Miss)
|
||||
if (event.type == ActionType.attack &&
|
||||
event.target == EffectTarget.enemy) {
|
||||
|
||||
final RenderBox? playerBox =
|
||||
_playerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final RenderBox? enemyBox =
|
||||
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (playerBox != null && enemyBox != null) {
|
||||
final playerPos = playerBox.localToGlobal(Offset.zero);
|
||||
final enemyPos = enemyBox.localToGlobal(Offset.zero);
|
||||
final offset = enemyPos - playerPos;
|
||||
|
||||
setState(() {
|
||||
_isPlayerAttacking = true;
|
||||
});
|
||||
|
||||
// Force SAFE animation for MISS, otherwise use event risk
|
||||
final RiskLevel animRisk = event.feedbackType != null ? RiskLevel.safe : event.risk;
|
||||
|
||||
_playerAnimKey.currentState
|
||||
?.animateAttack(offset, () {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
|
||||
if (event.risk == RiskLevel.risky && event.feedbackType == null) {
|
||||
_shakeKey.currentState?.shake();
|
||||
RenderBox? stackBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackBox != null) {
|
||||
Offset localEnemyPos = stackBox.globalToLocal(enemyPos);
|
||||
localEnemyPos += Offset(
|
||||
enemyBox.size.width / 2,
|
||||
enemyBox.size.height / 2,
|
||||
);
|
||||
_explosionKey.currentState?.explode(localEnemyPos);
|
||||
}
|
||||
}, event.risk)
|
||||
.then((_) {
|
||||
// End Animation: Show Stats
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isPlayerAttacking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (event.type == ActionType.attack &&
|
||||
event.target == EffectTarget.player &&
|
||||
event.feedbackType == null) {
|
||||
|
||||
// Check Settings
|
||||
bool enableAnim = context.read<SettingsProvider>().enableEnemyAnimations;
|
||||
}
|
||||
}, animRisk)
|
||||
.then((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isPlayerAttacking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 2. Enemy Attack Animation Trigger (Success or Miss)
|
||||
else if (event.type == ActionType.attack &&
|
||||
event.target == EffectTarget.player) {
|
||||
|
||||
bool enableAnim = context.read<SettingsProvider>().enableEnemyAnimations;
|
||||
|
||||
if (!enableAnim) {
|
||||
showEffect(); // Just show effect if anim disabled
|
||||
context.read<BattleProvider>().handleImpact(event); // Process impact immediately if anim disabled
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Enemy Attack Animation Trigger
|
||||
final RenderBox? playerBox =
|
||||
_playerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final RenderBox? enemyBox =
|
||||
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (playerBox != null && enemyBox != null) {
|
||||
final playerPos = playerBox.localToGlobal(Offset.zero);
|
||||
final enemyPos = enemyBox.localToGlobal(Offset.zero);
|
||||
|
||||
// Enemy moves TO Player
|
||||
final offset = playerPos - enemyPos;
|
||||
|
||||
// Start Animation: Hide Stats
|
||||
setState(() {
|
||||
_isEnemyAttacking = true;
|
||||
});
|
||||
|
||||
_enemyAnimKey.currentState
|
||||
?.animateAttack(offset, () {
|
||||
showEffect(); // Show Effect at Impact!
|
||||
// Trigger impact logic in provider
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
|
||||
// Shake and Explosion ONLY for Risky (Enemy can also do risky attacks)
|
||||
if (event.risk == RiskLevel.risky) {
|
||||
_shakeKey.currentState?.shake();
|
||||
|
||||
// Explosion on Player
|
||||
RenderBox? stackBox =
|
||||
_stackKey.currentContext?.findRenderObject()
|
||||
as RenderBox?;
|
||||
if (stackBox != null) {
|
||||
Offset localPlayerPos = stackBox.globalToLocal(playerPos);
|
||||
localPlayerPos += Offset(
|
||||
playerBox.size.width / 2,
|
||||
playerBox.size.height / 2,
|
||||
);
|
||||
_explosionKey.currentState?.explode(localPlayerPos);
|
||||
}
|
||||
}
|
||||
}, event.risk)
|
||||
.then((_) {
|
||||
// End Animation: Show Stats
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isEnemyAttacking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Not a player/enemy attack movement, show immediately
|
||||
if (!enableAnim) {
|
||||
showEffect();
|
||||
// Also process impact immediately for non-animating effects (e.g., failed defense, stun)
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
final RenderBox? playerBox =
|
||||
_playerKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final RenderBox? enemyBox =
|
||||
_enemyKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
if (playerBox != null && enemyBox != null) {
|
||||
final playerPos = playerBox.localToGlobal(Offset.zero);
|
||||
final enemyPos = enemyBox.localToGlobal(Offset.zero);
|
||||
final offset = playerPos - enemyPos;
|
||||
|
||||
setState(() {
|
||||
_isEnemyAttacking = true;
|
||||
});
|
||||
|
||||
// Force SAFE animation for MISS
|
||||
final RiskLevel animRisk = event.feedbackType != null ? RiskLevel.safe : event.risk;
|
||||
|
||||
_enemyAnimKey.currentState
|
||||
?.animateAttack(offset, () {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
|
||||
if (event.risk == RiskLevel.risky && event.feedbackType == null) {
|
||||
_shakeKey.currentState?.shake();
|
||||
RenderBox? stackBox =
|
||||
_stackKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
if (stackBox != null) {
|
||||
Offset localPlayerPos = stackBox.globalToLocal(playerPos);
|
||||
localPlayerPos += Offset(
|
||||
playerBox.size.width / 2,
|
||||
playerBox.size.height / 2,
|
||||
);
|
||||
_explosionKey.currentState?.explode(localPlayerPos);
|
||||
}
|
||||
}
|
||||
}, animRisk)
|
||||
.then((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isEnemyAttacking = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 3. Defend Animation Trigger (Success OR Failure)
|
||||
else if (event.type == ActionType.defend) {
|
||||
if (event.target == EffectTarget.player) {
|
||||
_playerAnimKey.currentState?.animateDefense(() {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
});
|
||||
} else if (event.target == EffectTarget.enemy) {
|
||||
_enemyAnimKey.currentState?.animateDefense(() {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
});
|
||||
} else {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
}
|
||||
}
|
||||
// 4. Others (Feedback for attacks, Buffs, etc.)
|
||||
else {
|
||||
showEffect();
|
||||
|
||||
// If it's a feedback event (MISS/FAILED for attacks), wait 500ms.
|
||||
if (event.feedbackType != null) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) context.read<BattleProvider>().handleImpact(event);
|
||||
});
|
||||
} else {
|
||||
// Success events (Icon)
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showRiskLevelSelection(BuildContext context, ActionType actionType) {
|
||||
@@ -452,6 +482,11 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Sync animation setting to provider logic
|
||||
final settings = context.watch<SettingsProvider>();
|
||||
context.read<BattleProvider>().skipAnimations =
|
||||
!settings.enableEnemyAnimations;
|
||||
|
||||
return ResponsiveContainer(
|
||||
child: Consumer<BattleProvider>(
|
||||
builder: (context, battleProvider, child) {
|
||||
@@ -616,7 +651,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
child: SimpleDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Text("${AppStrings.victory} ${AppStrings.chooseReward}"),
|
||||
const Text(
|
||||
"${AppStrings.victory} ${AppStrings.chooseReward}",
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -24,7 +24,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
|
||||
// Enemy Animation Toggle
|
||||
Consumer<SettingsProvider>(
|
||||
builder: (context, settings, child) {
|
||||
@@ -51,7 +51,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
style: TextStyle(color: ThemeConfig.textColorWhite),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
|
||||
// Restart Button
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -69,7 +69,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
const SnackBar(content: Text('Game Restarted!')),
|
||||
);
|
||||
// Optionally switch tab back to Battle (index 0)
|
||||
// But MainWrapper controls the index.
|
||||
// But MainWrapper controls the index.
|
||||
// We can't easily switch tab from here without a callback or Provider.
|
||||
// For now, just restart logic is enough.
|
||||
},
|
||||
@@ -89,10 +89,13 @@ class SettingsScreen extends StatelessWidget {
|
||||
_showConfirmationDialog(
|
||||
context,
|
||||
title: '${AppStrings.returnToMenu}?',
|
||||
content: 'Unsaved progress may be lost. (Progress is saved automatically after each stage)',
|
||||
content:
|
||||
'Unsaved progress may be lost. (Progress is saved automatically after each stage)',
|
||||
onConfirm: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (context) => const MainMenuScreen()),
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MainMenuScreen(),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
@@ -105,7 +108,12 @@ class SettingsScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showConfirmationDialog(BuildContext context, {required String title, required String content, required VoidCallback onConfirm}) {
|
||||
void _showConfirmationDialog(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String content,
|
||||
required VoidCallback onConfirm,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
@@ -121,7 +129,10 @@ class SettingsScreen extends StatelessWidget {
|
||||
Navigator.pop(context);
|
||||
onConfirm();
|
||||
},
|
||||
child: const Text(AppStrings.confirm, style: TextStyle(color: Colors.red)),
|
||||
child: const Text(
|
||||
AppStrings.confirm,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user