tetraq/lib/ui/home/home_screen.dart

1535 lines
80 KiB
Dart
Raw Normal View History

2026-02-27 23:35:54 +01:00
// ===========================================================================
// FILE: lib/ui/home/home_screen.dart
// ===========================================================================
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
2026-03-01 20:59:06 +01:00
import 'package:flutter/foundation.dart';
2026-02-27 23:35:54 +01:00
import 'dart:math' as math;
import 'package:google_fonts/google_fonts.dart';
2026-03-01 20:59:06 +01:00
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../logic/game_controller.dart';
2026-02-27 23:35:54 +01:00
import '../../core/theme_manager.dart';
import '../../core/app_colors.dart';
import '../game/game_screen.dart';
import '../settings/settings_screen.dart';
import '../../services/storage_service.dart';
import '../multiplayer/lobby_screen.dart';
import 'history_screen.dart';
2026-03-04 18:00:01 +01:00
import 'package:firebase_auth/firebase_auth.dart';
2026-02-27 23:35:54 +01:00
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
if (themeType == AppThemeType.doodle) {
return GoogleFonts.permanentMarker(textStyle: baseStyle);
2026-03-01 20:59:06 +01:00
} else if (themeType == AppThemeType.arcade) {
return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(
fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null,
letterSpacing: 0.5,
));
} else if (themeType == AppThemeType.grimorio) {
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
2026-02-27 23:35:54 +01:00
}
return baseStyle;
}
class _DoodleBackgroundPainter extends CustomPainter {
final Color fillColor;
final Color strokeColor;
final int seed;
final bool isCircle;
_DoodleBackgroundPainter({required this.fillColor, required this.strokeColor, required this.seed, this.isCircle = false});
@override
void paint(Canvas canvas, Size size) {
final math.Random random = math.Random(seed);
double wobble() => random.nextDouble() * 6 - 3;
final Paint fillPaint = Paint()
..color = fillColor
..style = PaintingStyle.fill;
final Paint strokePaint = Paint()
..color = strokeColor
..strokeWidth = 2.5
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
if (isCircle) {
final Rect rect = Rect.fromLTWH(wobble(), wobble(), size.width + wobble(), size.height + wobble());
canvas.save();
canvas.translate(wobble(), wobble());
canvas.drawOval(rect, fillPaint);
canvas.restore();
canvas.drawOval(rect, strokePaint);
canvas.save();
canvas.translate(random.nextDouble() * 4 - 2, random.nextDouble() * 4 - 2);
canvas.drawOval(rect, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6));
canvas.restore();
} else {
final Path path = Path();
path.moveTo(wobble(), wobble());
path.lineTo(size.width + wobble(), wobble());
path.lineTo(size.width + wobble(), size.height + wobble());
path.lineTo(wobble(), size.height + wobble());
path.close();
final Path fillPath = Path();
fillPath.moveTo(wobble() * 1.5, wobble() * 1.5);
fillPath.lineTo(size.width + wobble() * 1.5, wobble() * 1.5);
fillPath.lineTo(size.width + wobble() * 1.5, size.height + wobble() * 1.5);
fillPath.lineTo(wobble() * 1.5, size.height + wobble() * 1.5);
fillPath.close();
canvas.drawPath(fillPath, fillPaint);
canvas.drawPath(path, strokePaint);
canvas.save();
canvas.translate(random.nextDouble() * 3 - 1.5, random.nextDouble() * 3 - 1.5);
canvas.drawPath(path, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6));
canvas.restore();
}
}
@override
bool shouldRepaint(covariant _DoodleBackgroundPainter oldDelegate) {
return oldDelegate.fillColor != fillColor || oldDelegate.strokeColor != strokeColor;
}
}
class _NeonShapeButton extends StatelessWidget {
final IconData icon;
final String label;
final bool isSelected;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
final bool isLocked;
final bool isSpecial;
const _NeonShapeButton({
required this.icon, required this.label, required this.isSelected,
required this.theme, required this.themeType, required this.onTap,
this.isLocked = false, this.isSpecial = false
});
Color _getDoodleColor() {
switch (label) {
case 'Rombo': return Colors.lightBlue.shade200;
case 'Croce': return Colors.green.shade200;
case 'Buco': return Colors.pink.shade200;
case 'Clessidra': return Colors.purple.shade200;
case 'Caos': return Colors.grey.shade300;
default: return Colors.lightBlue.shade200;
}
}
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = isLocked ? Colors.grey : _getDoodleColor();
Color inkColor = const Color(0xFF111122);
double tilt = (label.length % 2 == 0) ? -0.05 : 0.04;
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: isLocked ? null : onTap,
child: CustomPaint(
painter: _DoodleBackgroundPainter(
fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8),
strokeColor: inkColor,
seed: label.length * 3,
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(isLocked ? Icons.lock : icon, color: inkColor, size: 24),
const SizedBox(height: 2),
Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.5))),
],
),
),
),
),
);
}
Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue;
return GestureDetector(
onTap: isLocked ? null : onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOutCubic,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isLocked
? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)]
: isSelected
? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)]
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
),
border: Border.all(
color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)),
width: isSelected ? 2 : 1,
),
boxShadow: isLocked ? [] : isSelected
? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))]
: [
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 24),
const SizedBox(height: 6),
Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 11, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))),
],
),
),
);
}
}
class _NeonSizeButton extends StatelessWidget {
final String label;
final bool isSelected;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
const _NeonSizeButton({required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = label == 'MAX' ? Colors.red.shade200 : Colors.cyan.shade100;
Color inkColor = const Color(0xFF111122);
double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04;
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: onTap,
child: CustomPaint(
painter: _DoodleBackgroundPainter(
fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8),
strokeColor: inkColor,
seed: label.codeUnitAt(0),
isCircle: true,
),
child: SizedBox(
width: 50, height: 50,
child: Center(
child: Text(label, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 18, fontWeight: FontWeight.w900))),
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOutCubic,
width: 50, height: 50,
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isSelected
? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)]
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
),
border: Border.all(
color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1),
width: isSelected ? 2 : 1,
),
boxShadow: isSelected
? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)]
: [
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
],
),
child: Center(
child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 14, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))),
),
),
);
}
}
class _NeonTimeSwitch extends StatelessWidget {
final bool isTimeMode;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
const _NeonTimeSwitch({required this.isTimeMode, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = Colors.orange.shade200;
Color inkColor = const Color(0xFF111122);
return Transform.rotate(
angle: -0.015,
child: GestureDetector(
onTap: onTap,
child: CustomPaint(
painter: _DoodleBackgroundPainter(
fillColor: isTimeMode ? doodleColor : Colors.white.withOpacity(0.8),
strokeColor: inkColor,
seed: 42,
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: inkColor, size: 28),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 2.0))),
Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 13, fontWeight: FontWeight.bold))),
],
),
],
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isTimeMode
? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)]
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
),
border: Border.all(
color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1),
width: isTimeMode ? 2 : 1,
),
boxShadow: isTimeMode
? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
: [
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 28),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 14, letterSpacing: 1.5))),
Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 11, fontWeight: FontWeight.bold))),
],
),
],
),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
2026-03-01 20:59:06 +01:00
int _debugTapCount = 0;
2026-02-27 23:35:54 +01:00
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkPlayerName();
});
_checkClipboardForInvite();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_checkClipboardForInvite();
}
}
void _checkPlayerName() {
if (StorageService.instance.playerName.isEmpty) {
_showNameDialog();
}
}
void _showNameDialog() {
final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName);
showDialog(
context: context,
barrierDismissible: false,
barrierColor: Colors.black.withOpacity(0.8),
builder: (context) {
final themeManager = context.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122);
Widget dialogContent = themeType == AppThemeType.doodle
? CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('BENVENUTO!', style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 28, letterSpacing: 2.0)), textAlign: TextAlign.center),
const SizedBox(height: 20),
Text('Scegli il tuo nome da battaglia', style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 18)), textAlign: TextAlign.center),
const SizedBox(height: 30),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 36, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: 'NOME', hintStyle: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)),
filled: false, counterText: "",
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)),
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5)),
),
),
const SizedBox(height: 40),
GestureDetector(
onTap: () {
final name = nameController.text.trim();
if (name.isNotEmpty) {
StorageService.instance.savePlayerName(name);
Navigator.of(context).pop();
setState(() {});
}
},
child: CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 101),
child: Container(
width: double.infinity, height: 60,
alignment: Alignment.center,
child: Text('SALVA E GIOCA', style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 1.5))),
),
),
),
],
),
),
)
: Container(
decoration: BoxDecoration(
color: theme.background,
borderRadius: BorderRadius.circular(25),
border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2),
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 20, spreadRadius: 5)]
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('BENVENUTO IN TETRAQ!', style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 1.5)), textAlign: TextAlign.center),
const SizedBox(height: 20),
Text('Scegli il tuo nome da battaglia per sfidare i tuoi amici online.', style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 16)), textAlign: TextAlign.center),
const SizedBox(height: 40),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: 'NOME', hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)),
filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "",
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)),
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15)),
),
),
const SizedBox(height: 40),
SizedBox(
width: double.infinity, height: 55,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () {
final name = nameController.text.trim();
if (name.isNotEmpty) {
StorageService.instance.savePlayerName(name);
Navigator.of(context).pop();
setState(() {});
}
},
child: Text('SALVA E GIOCA', style: _getTextStyle(themeType, const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1.5))),
),
),
],
),
),
),
);
if (themeType == AppThemeType.cyberpunk) dialogContent = _AnimatedCyberBorder(child: dialogContent);
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent);
},
);
}
Future<void> _checkClipboardForInvite() async {
try {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
String? text = data?.text;
if (text != null && text.contains("TetraQ") && text.contains("codice:")) {
RegExp regExp = RegExp(r'codice:\s*([A-Z0-9]{5})', caseSensitive: false);
Match? match = regExp.firstMatch(text);
if (match != null) {
String roomCode = match.group(1)!.toUpperCase();
await Clipboard.setData(const ClipboardData(text: ''));
if (mounted && ModalRoute.of(context)?.isCurrent == true) {
_promptJoinRoom(roomCode);
}
}
}
} catch (e) {
debugPrint("Errore lettura appunti: $e");
}
}
void _promptJoinRoom(String roomCode) {
showDialog(
context: context,
builder: (context) {
final theme = context.watch<ThemeManager>().currentColors;
final themeType = context.read<ThemeManager>().currentThemeType;
return AlertDialog(
backgroundColor: themeType == AppThemeType.doodle ? Colors.white : theme.background,
shape: themeType == AppThemeType.doodle ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(15), side: BorderSide(color: theme.text, width: 2)) : null,
title: Text("Invito Trovato!", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))),
content: Text("Vuoi unirti alla stanza $roomCode?", style: _getTextStyle(themeType, TextStyle(color: theme.text))),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text("No", style: _getTextStyle(themeType, const TextStyle(color: Colors.red)))),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: themeType == AppThemeType.doodle ? Colors.transparent : theme.playerBlue,
elevation: 0,
side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 1.5) : BorderSide.none,
),
onPressed: () {
Navigator.of(context).pop();
Navigator.push(context, MaterialPageRoute(builder: (_) => LobbyScreen(initialRoomCode: roomCode)));
},
child: Text("Unisciti", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : Colors.white, fontWeight: FontWeight.bold))),
),
],
);
}
);
}
void _showMatchSetupDialog(bool isVsCPU) {
int localRadius = 4;
ArenaShape localShape = ArenaShape.classic;
bool localTimeMode = true;
2026-03-01 20:59:06 +01:00
bool isChaosUnlocked = StorageService.instance.playerLevel >= 10;
2026-02-27 23:35:54 +01:00
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.8),
builder: (ctx) {
final themeManager = ctx.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122);
return StatefulBuilder(
builder: (context, setStateDialog) {
Widget dialogContent = themeType == AppThemeType.doodle
? Transform.rotate(
angle: 0.015,
child: CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.95), strokeColor: inkColor, seed: 200),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(isVsCPU ? "SFIDA IA" : "MODALITÀ LOCALE", style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))),
const SizedBox(height: 25),
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
const SizedBox(height: 15),
Wrap(
spacing: 12, runSpacing: 12, alignment: WrapAlignment.center,
children: [
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
],
),
const SizedBox(height: 25),
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
const SizedBox(height: 20),
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
],
),
const SizedBox(height: 25),
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
const SizedBox(height: 20),
Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
const SizedBox(height: 10),
_NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)),
const SizedBox(height: 35),
Transform.rotate(
angle: -0.02,
child: GestureDetector(
onTap: () {
Navigator.pop(ctx);
context.read<GameController>().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode);
Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen()));
},
child: CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 300),
child: Container(
height: 65, width: double.infinity,
alignment: Alignment.center,
child: Text("AVVIA PARTITA", style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: inkColor))),
),
),
),
)
],
),
),
),
),
)
: Container(
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
2026-03-01 20:59:06 +01:00
border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5),
boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))],
2026-02-27 23:35:54 +01:00
),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(isVsCPU ? "SFIDA IA" : "MODALITÀ LOCALE", style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
const SizedBox(height: 20),
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
const SizedBox(height: 10),
Wrap(
spacing: 10, runSpacing: 10, alignment: WrapAlignment.center,
children: [
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
],
),
const SizedBox(height: 20),
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
const SizedBox(height: 20),
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
],
),
const SizedBox(height: 20),
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
const SizedBox(height: 20),
Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
const SizedBox(height: 10),
_NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)),
const SizedBox(height: 30),
SizedBox(
width: double.infinity, height: 60,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: isVsCPU ? Colors.purple.shade400 : theme.playerRed, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
onPressed: () {
Navigator.pop(ctx);
context.read<GameController>().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode);
Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen()));
},
child: const Text("AVVIA PARTITA", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
),
),
);
if (themeType == AppThemeType.cyberpunk) {
dialogContent = _AnimatedCyberBorder(child: dialogContent);
}
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), child: dialogContent);
},
);
}
);
}
2026-03-01 20:59:06 +01:00
// --- NUOVA FUNZIONE: MOSTRA LE SFIDE GIORNALIERE ---
Future<void> _showDailyQuestsDialog() async {
final prefs = await SharedPreferences.getInstance();
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.8),
builder: (ctx) {
final themeManager = ctx.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122);
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.all(20),
child: Container(
padding: const EdgeInsets.all(25.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2),
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)]
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.assignment_turned_in, size: 50, color: theme.playerBlue),
const SizedBox(height: 10),
Text("SFIDE GIORNALIERE", style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
const SizedBox(height: 25),
// Generiamo dinamicamente le 3 missioni salvate in memoria
...List.generate(3, (index) {
int i = index + 1;
int type = prefs.getInt('q${i}_type') ?? 0;
int prog = prefs.getInt('q${i}_prog') ?? 0;
int target = prefs.getInt('q${i}_target') ?? 1;
String title = "";
IconData icon = Icons.star;
if (type == 0) { title = "Vinci partite Online"; icon = Icons.public; }
else if (type == 1) { title = "Vinci contro la CPU"; icon = Icons.smart_toy; }
else { title = "Gioca in Arene Speciali"; icon = Icons.extension; }
bool completed = prog >= target;
double percent = (prog / target).clamp(0.0, 1.0);
return Container(
margin: const EdgeInsets.only(bottom: 15),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: completed ? Colors.green.withOpacity(0.1) : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: completed ? Colors.green : theme.gridLine.withOpacity(0.3)),
),
child: Row(
children: [
Icon(icon, color: completed ? Colors.green : theme.text.withOpacity(0.6), size: 30),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: completed ? Colors.green : theme.text))),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
value: percent,
backgroundColor: theme.gridLine.withOpacity(0.2),
color: completed ? Colors.green : theme.playerBlue,
minHeight: 8,
),
)
],
),
),
const SizedBox(width: 10),
Text("$prog / $target", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6)))),
],
),
);
}),
const SizedBox(height: 15),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(ctx),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
),
);
}
);
}
// --- NUOVA FUNZIONE: MOSTRA LA CLASSIFICA GLOBALE ---
void _showLeaderboardDialog() {
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.8),
builder: (ctx) {
final themeManager = ctx.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Widget content = Container(
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
border: Border.all(color: Colors.amber.withOpacity(0.8), width: 2),
boxShadow: [BoxShadow(color: Colors.amber.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)]
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.emoji_events, size: 50, color: Colors.amber),
const SizedBox(height: 10),
Text("CLASSIFICA MONDIALE", style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
const SizedBox(height: 20),
// Lista giocatori pescata da Firebase!
SizedBox(
height: 350,
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('xp', descending: true).limit(50).snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator(color: theme.playerBlue));
}
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
}
final docs = snapshot.data!.docs;
return ListView.builder(
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) {
var data = docs[index].data() as Map<String, dynamic>;
2026-03-04 18:00:01 +01:00
// Ora controlliamo se l'ID del documento su Firebase è uguale al nostro ID segreto!
String? myUid = FirebaseAuth.instance.currentUser?.uid;
bool isMe = docs[index].id == myUid;
2026-03-01 20:59:06 +01:00
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(10),
border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null
),
child: Row(
children: [
Text("#${index + 1}", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.w900, color: index == 0 ? Colors.amber : (index == 1 ? Colors.grey.shade400 : (index == 2 ? Colors.brown.shade300 : theme.text.withOpacity(0.5)))))),
const SizedBox(width: 15),
Expanded(child: Text(data['name'] ?? 'Unknown', style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text("Lv. ${data['level'] ?? 1}", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 12)),
Text("${data['xp'] ?? 0} XP", style: TextStyle(color: theme.text.withOpacity(0.6), fontSize: 10)),
],
)
],
),
);
}
);
}
),
),
const SizedBox(height: 15),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.amber.shade700, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(ctx),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
);
if (themeType == AppThemeType.cyberpunk) content = _AnimatedCyberBorder(child: content);
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content);
}
);
}
2026-02-27 23:35:54 +01:00
void _showTutorialDialog() {
showDialog(
context: context,
barrierColor: Colors.black.withOpacity(0.8),
builder: (ctx) {
final themeManager = ctx.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122);
2026-03-01 20:59:06 +01:00
String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:";
String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:";
String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:";
2026-02-27 23:35:54 +01:00
Widget dialogContent = themeType == AppThemeType.doodle
? Transform.rotate(
angle: -0.01,
child: CustomPaint(
2026-03-01 20:59:06 +01:00
painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade50, strokeColor: inkColor, seed: 400),
2026-02-27 23:35:54 +01:00
child: Padding(
padding: const EdgeInsets.all(25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
2026-03-01 20:59:06 +01:00
crossAxisAlignment: CrossAxisAlignment.start,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Center(child: Text("COME GIOCARE", style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))),
2026-02-27 23:35:54 +01:00
const SizedBox(height: 20),
2026-03-01 20:59:06 +01:00
_TutorialStep(icon: Icons.line_axis, text: "Chiudi i 4 lati di un quadrato e conquisti 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme),
2026-02-27 23:35:54 +01:00
const SizedBox(height: 15),
2026-03-01 20:59:06 +01:00
_TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! ogni quadrato nasconde un insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme),
2026-02-27 23:35:54 +01:00
const SizedBox(height: 15),
2026-03-01 20:59:06 +01:00
const Divider(color: Colors.black26, thickness: 2),
const SizedBox(height: 10),
Center(child: Text("GLOSSARIO ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor)))),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber.shade700, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: Colors.green.shade600, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 25),
Center(
child: GestureDetector(
onTap: () => Navigator.pop(ctx),
child: CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.red.shade200, strokeColor: inkColor, seed: 401),
child: Container(
height: 50, width: 150,
alignment: Alignment.center,
child: Text("HO CAPITO!", style: _getTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor))),
),
2026-02-27 23:35:54 +01:00
),
),
)
],
),
),
),
)
: Container(
padding: const EdgeInsets.all(25.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
2026-03-01 20:59:06 +01:00
border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5),
boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))],
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Text("COME GIOCARE", style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2)))),
const SizedBox(height: 20),
_TutorialStep(icon: Icons.grid_4x4, text: "Chiudi i 4 lati di un quadrato e conquisti 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
_TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! ogni quadrato nasconde un insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
const Divider(color: Colors.white24, thickness: 1.5),
const SizedBox(height: 10),
Center(child: Text("GLOSSARIO ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.7), letterSpacing: 1.5)))),
const SizedBox(height: 15),
_TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: theme.playerBlue, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
_TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 30),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(ctx),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
2026-02-27 23:35:54 +01:00
),
);
if (themeType == AppThemeType.cyberpunk) dialogContent = _AnimatedCyberBorder(child: dialogContent);
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: dialogContent);
},
);
}
Widget _buildCyberCard(Widget card, AppThemeType themeType) {
if (themeType == AppThemeType.cyberpunk) {
return _AnimatedCyberBorder(child: card);
}
return card;
}
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
final themeManager = context.watch<ThemeManager>();
final themeType = themeManager.currentThemeType;
final theme = themeManager.currentColors;
Color inkColor = const Color(0xFF111122);
String? bgImage;
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
int wins = StorageService.instance.wins;
int losses = StorageService.instance.losses;
String playerName = StorageService.instance.playerName;
if (playerName.isEmpty) playerName = "GUEST";
2026-03-01 20:59:06 +01:00
int level = StorageService.instance.playerLevel;
int currentXP = StorageService.instance.totalXP;
double xpProgress = (currentXP % 100) / 100.0;
2026-02-27 23:35:54 +01:00
Widget uiContent = SafeArea(
2026-03-01 20:59:06 +01:00
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: _showNameDialog,
child: Row(
children: [
themeType == AppThemeType.doodle
? CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true),
child: SizedBox(width: 50, height: 50, child: Icon(Icons.person, color: inkColor, size: 30)),
)
: SizedBox(
width: 50, height: 50,
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(value: xpProgress, color: theme.playerBlue, strokeWidth: 3, backgroundColor: theme.gridLine.withOpacity(0.2)),
Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4))]),
child: CircleAvatar(backgroundColor: theme.playerBlue.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerBlue, size: 26)),
),
),
],
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(playerName, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 1.5, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))),
Text("LIV. $level", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1))),
],
),
],
),
),
GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HistoryScreen())),
child: themeType == AppThemeType.doodle
? Transform.rotate(
angle: 0.04,
child: CustomPaint(
painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Icon(Icons.emoji_events, color: inkColor, size: 20), const SizedBox(width: 6),
Text("$wins", style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), const SizedBox(width: 12),
Icon(Icons.sentiment_very_dissatisfied, color: inkColor, size: 20), const SizedBox(width: 6),
Text("$losses", style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))),
],
),
),
),
)
: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.text.withOpacity(0.15), theme.text.withOpacity(0.02)]),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white.withOpacity(0.1), width: 1.5),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), offset: const Offset(2, 4), blurRadius: 8), BoxShadow(color: Colors.white.withOpacity(0.05), offset: const Offset(-1, -1), blurRadius: 2)],
),
child: Row(
children: [
Icon(Icons.emoji_events, color: Colors.amber.shade600, size: 20), const SizedBox(width: 6),
Text("$wins", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), const SizedBox(width: 12),
Icon(Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 20), const SizedBox(width: 6),
Text("$losses", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))),
],
),
),
)
],
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
const Spacer(),
Center(
child: Transform.rotate(
angle: themeType == AppThemeType.doodle ? -0.04 : 0,
// --- IL TRUCCO DELLO SVILUPPATORE PROTETTO ---
child: GestureDetector(
onTap: () {
if (kReleaseMode) return;
_debugTapCount++;
if (_debugTapCount >= 5) {
_debugTapCount = 0;
StorageService.instance.addXP(2000);
setState(() {});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("🛠 DEBUG MODE: +20 Livelli! Tutto sbloccato.", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
backgroundColor: Colors.purpleAccent,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
)
);
}
},
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"TETRAQ",
style: _getTextStyle(themeType, TextStyle(
fontSize: 65,
fontWeight: FontWeight.w900,
color: themeType == AppThemeType.doodle ? inkColor : theme.text,
letterSpacing: 10,
shadows: themeType == AppThemeType.doodle || themeType == AppThemeType.arcade ? [] : [
BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8),
BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20),
]
))
),
),
),
2026-02-27 23:35:54 +01:00
),
),
2026-03-01 20:59:06 +01:00
const Spacer(),
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
// --- NUOVA LISTA BOTTONI CON CLASSIFICHE E SFIDE ---
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCyberCard(_FeatureCard(title: "ONLINE", subtitle: "Sfida il mondo", icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType),
const SizedBox(height: 12),
_buildCyberCard(_FeatureCard(title: "VS CPU", subtitle: "Allenati con l'IA", icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType),
const SizedBox(height: 12),
_buildCyberCard(_FeatureCard(title: "LOCALE", subtitle: "Stesso schermo", icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType),
const SizedBox(height: 12),
// NUOVI BOTTONI PER LA VERSIONE 2.0
Row(
children: [
Expanded(child: _buildCyberCard(_FeatureCard(title: "CLASSIFICA", subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: _showLeaderboardDialog, compact: true), themeType)),
const SizedBox(width: 12),
Expanded(child: _buildCyberCard(_FeatureCard(title: "SFIDE", subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: _showDailyQuestsDialog, compact: true), themeType)),
],
),
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildCyberCard(_FeatureCard(title: "TEMI", subtitle: "Personalizza", icon: Icons.palette, color: Colors.teal.shade200, theme: theme, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), compact: true), themeType)),
const SizedBox(width: 12),
Expanded(child: _buildCyberCard(_FeatureCard(title: "TUTORIAL", subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: _showTutorialDialog, compact: true), themeType)),
],
),
],
),
const SizedBox(height: 10),
],
),
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
),
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
);
},
2026-02-27 23:35:54 +01:00
),
);
return Scaffold(
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
body: Stack(
children: [
Container(color: theme.background),
if (bgImage != null)
Positioned.fill(
child: Image.asset(bgImage, fit: BoxFit.cover, alignment: Alignment.center),
),
if (bgImage != null)
Positioned.fill(
child: themeType == AppThemeType.cyberpunk
? Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter, end: Alignment.bottomCenter,
colors: [Colors.black.withOpacity(0.2), Colors.black.withOpacity(0.7)]
)
),
)
: const SizedBox(),
),
Positioned.fill(child: uiContent),
],
),
);
}
}
class _TutorialStep extends StatelessWidget {
final IconData icon;
2026-03-01 20:59:06 +01:00
final Color? iconColor;
2026-02-27 23:35:54 +01:00
final String text;
final AppThemeType themeType;
final Color inkColor;
final ThemeColors theme;
2026-03-01 20:59:06 +01:00
const _TutorialStep({required this.icon, this.iconColor, required this.text, required this.themeType, required this.inkColor, required this.theme});
2026-02-27 23:35:54 +01:00
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
2026-03-01 20:59:06 +01:00
Icon(icon, color: iconColor ?? (themeType == AppThemeType.doodle ? inkColor : theme.playerBlue), size: 28),
2026-02-27 23:35:54 +01:00
const SizedBox(width: 15),
Expanded(
2026-03-01 20:59:06 +01:00
child: Text(text, style: _getTextStyle(themeType, TextStyle(fontSize: 14, color: themeType == AppThemeType.doodle ? inkColor : theme.text.withOpacity(0.8), height: 1.3))),
2026-02-27 23:35:54 +01:00
),
],
);
}
}
class _FeatureCard extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
final bool isFeatured;
2026-03-01 20:59:06 +01:00
final bool compact;
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
const _FeatureCard({required this.title, required this.subtitle, required this.icon, required this.color, required this.theme, required this.themeType, required this.onTap, this.isFeatured = false, this.compact = false});
2026-02-27 23:35:54 +01:00
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
double tilt = (title.length % 2 == 0) ? -0.015 : 0.02;
Color inkColor = const Color(0xFF111122);
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: onTap,
child: CustomPaint(
painter: _DoodleBackgroundPainter(
fillColor: color,
strokeColor: inkColor,
seed: title.length * 5,
),
child: Padding(
2026-03-01 20:59:06 +01:00
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 22.0, vertical: compact ? 12.0 : 16.0),
2026-02-27 23:35:54 +01:00
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
2026-03-01 20:59:06 +01:00
Icon(icon, color: inkColor, size: compact ? 24 : 32),
const SizedBox(width: 12),
2026-02-27 23:35:54 +01:00
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
2026-03-01 20:59:06 +01:00
Text(title, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: compact ? 16 : 24, fontWeight: FontWeight.w900))),
if (!compact) ...[
const SizedBox(height: 2),
Text(subtitle, style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.bold))),
]
2026-02-27 23:35:54 +01:00
],
),
),
2026-03-01 20:59:06 +01:00
if (!compact) Icon(Icons.chevron_right_rounded, color: inkColor.withOpacity(0.6), size: 32),
2026-02-27 23:35:54 +01:00
],
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: Container(
2026-03-01 20:59:06 +01:00
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 20.0, vertical: compact ? 10.0 : 14.0),
2026-02-27 23:35:54 +01:00
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isFeatured
? [color.withOpacity(0.9), color.withOpacity(0.6)]
2026-03-01 20:59:06 +01:00
: [color.withOpacity(0.25), color.withOpacity(0.05)],
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
borderRadius: BorderRadius.circular(15),
border: Border.all(color: color.withOpacity(isFeatured ? 0.5 : 0.2), width: 1.5),
2026-02-27 23:35:54 +01:00
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(0, 8), blurRadius: 15),
2026-03-01 20:59:06 +01:00
BoxShadow(color: color.withOpacity(isFeatured ? 0.3 : 0.05), offset: const Offset(-1, -1), blurRadius: 5),
2026-02-27 23:35:54 +01:00
]
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
2026-03-01 20:59:06 +01:00
padding: EdgeInsets.all(compact ? 6 : 10),
2026-02-27 23:35:54 +01:00
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.05)],
),
shape: BoxShape.circle,
border: Border.all(color: Colors.white.withOpacity(0.2)),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 5, offset: const Offset(2, 4))]
),
2026-03-01 20:59:06 +01:00
child: Icon(icon, color: isFeatured ? Colors.white : color, size: compact ? 20 : 26),
2026-02-27 23:35:54 +01:00
),
2026-03-01 20:59:06 +01:00
SizedBox(width: compact ? 10 : 20),
2026-02-27 23:35:54 +01:00
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
2026-03-01 20:59:06 +01:00
Text(title, style: _getTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white : theme.text, fontSize: compact ? 14 : 18, fontWeight: FontWeight.w900, shadows: [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))),
if (!compact) ...[
const SizedBox(height: 2),
Text(subtitle, style: _getTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white.withOpacity(0.8) : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: FontWeight.bold))),
]
2026-02-27 23:35:54 +01:00
],
),
),
2026-03-01 20:59:06 +01:00
if (!compact) Icon(Icons.chevron_right_rounded, color: isFeatured ? Colors.white.withOpacity(0.7) : color.withOpacity(0.5), size: 30),
2026-02-27 23:35:54 +01:00
],
),
),
);
}
}
class _AnimatedCyberBorder extends StatefulWidget {
final Widget child;
const _AnimatedCyberBorder({required this.child});
@override
State<_AnimatedCyberBorder> createState() => _AnimatedCyberBorderState();
}
class _AnimatedCyberBorderState extends State<_AnimatedCyberBorder> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); }
@override
void dispose() { _controller.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
final theme = context.watch<ThemeManager>().currentColors;
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed),
child: Container(
2026-03-01 20:59:06 +01:00
decoration: BoxDecoration(color: theme.background.withOpacity(0.9), borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 25, spreadRadius: 2)]),
2026-02-27 23:35:54 +01:00
padding: const EdgeInsets.all(3),
child: widget.child,
),
);
},
child: widget.child,
);
}
}
class _CyberBorderPainter extends CustomPainter {
final double animationValue;
final Color color1;
final Color color2;
_CyberBorderPainter({required this.animationValue, required this.color1, required this.color2});
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
2026-03-01 20:59:06 +01:00
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(15));
2026-02-27 23:35:54 +01:00
final Paint paint = Paint()
..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect)
..style = PaintingStyle.stroke
..strokeWidth = 4.0
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 4);
canvas.drawRRect(rrect, paint);
}
@override
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
}