Auto-sync: 20260314_000001
This commit is contained in:
parent
321810a6b4
commit
e1861031a8
13 changed files with 1051 additions and 1388 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
BIN
assets/.DS_Store
vendored
BIN
assets/.DS_Store
vendored
Binary file not shown.
BIN
assets/images/egizi_bg.jpg
Normal file
BIN
assets/images/egizi_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
lib/.DS_Store
vendored
BIN
lib/.DS_Store
vendored
Binary file not shown.
BIN
lib/ui/.DS_Store
vendored
Normal file
BIN
lib/ui/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -54,6 +54,29 @@ class BoardPainter extends CustomPainter {
|
||||||
double offset = spacing / 2;
|
double offset = spacing / 2;
|
||||||
Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset);
|
Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset);
|
||||||
|
|
||||||
|
// --- NUOVO SFONDO LUMINOSO SAGOMATO (Solo per tema Musica) ---
|
||||||
|
if (themeType == AppThemeType.music) {
|
||||||
|
Path arenaShape = Path();
|
||||||
|
// Uniamo la forma di ogni box giocabile per creare il tappeto
|
||||||
|
for (var box in board.boxes) {
|
||||||
|
if (box.type != BoxType.invisible) { // Ignora i buchi
|
||||||
|
Offset p1 = getScreenPos(box.x, box.y);
|
||||||
|
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
|
||||||
|
arenaShape.addRect(Rect.fromPoints(p1, p2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disegniamo lo sfondo unito, chiaro e con un po' di blur
|
||||||
|
canvas.drawPath(
|
||||||
|
arenaShape,
|
||||||
|
Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.08) // Colore chiarissimo
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0), // Effetto stacco/glow
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
|
||||||
for (var box in board.boxes) {
|
for (var box in board.boxes) {
|
||||||
Offset p1 = getScreenPos(box.x, box.y);
|
Offset p1 = getScreenPos(box.x, box.y);
|
||||||
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
|
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
|
||||||
|
|
@ -66,6 +89,7 @@ class BoardPainter extends CustomPainter {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sfondo azzurrino se è di ghiaccio (anche prima di chiuderla)
|
||||||
if (box.type == BoxType.ice && box.owner == Player.none) {
|
if (box.type == BoxType.ice && box.owner == Player.none) {
|
||||||
canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill);
|
canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill);
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +134,7 @@ class BoardPainter extends CustomPainter {
|
||||||
if (box.type == BoxType.gold) {
|
if (box.type == BoxType.gold) {
|
||||||
_drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber);
|
_drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber);
|
||||||
} else if (box.type == BoxType.bomb) {
|
} else if (box.type == BoxType.bomb) {
|
||||||
_drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple);
|
_drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.greenAccent : Colors.deepPurple);
|
||||||
} else if (box.type == BoxType.swap) {
|
} else if (box.type == BoxType.swap) {
|
||||||
_drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent);
|
_drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent);
|
||||||
} else if (box.type == BoxType.ice) {
|
} else if (box.type == BoxType.ice) {
|
||||||
|
|
@ -126,9 +150,10 @@ class BoardPainter extends CustomPainter {
|
||||||
Offset p1 = getScreenPos(line.p1.x, line.p1.y);
|
Offset p1 = getScreenPos(line.p1.x, line.p1.y);
|
||||||
Offset p2 = getScreenPos(line.p2.x, line.p2.y);
|
Offset p2 = getScreenPos(line.p2.x, line.p2.y);
|
||||||
|
|
||||||
|
// --- DISEGNO DELLA LINEA "INCRINATA" DAL GHIACCIO ---
|
||||||
if (line.isIceCracked) {
|
if (line.isIceCracked) {
|
||||||
_drawCrackedIceLine(canvas, p1, p2, blinkValue);
|
_drawCrackedIceLine(canvas, p1, p2, blinkValue);
|
||||||
continue;
|
continue; // Non ha ancora un proprietario, passiamo alla prossima!
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isLastMove = (line == board.lastMove);
|
bool isLastMove = (line == board.lastMove);
|
||||||
|
|
@ -158,6 +183,10 @@ class BoardPainter extends CustomPainter {
|
||||||
_drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
_drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
} else if (themeType == AppThemeType.grimorio) {
|
} else if (themeType == AppThemeType.grimorio) {
|
||||||
_drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
_drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
|
} else if (themeType == AppThemeType.music) {
|
||||||
|
// Linee nere per la base nel tema musica
|
||||||
|
if (line.owner == Player.none) lineColor = Colors.black.withOpacity(0.4);
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
|
||||||
} else {
|
} else {
|
||||||
if (isLastMove && line.owner != Player.none) lineColor = Color.lerp(lineColor, Colors.white, blinkValue * 0.5) ?? lineColor;
|
if (isLastMove && line.owner != Player.none) lineColor = Color.lerp(lineColor, Colors.white, blinkValue * 0.5) ?? lineColor;
|
||||||
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
|
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
|
||||||
|
|
@ -188,6 +217,9 @@ class BoardPainter extends CustomPainter {
|
||||||
canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0));
|
canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0));
|
||||||
Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close();
|
Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close();
|
||||||
canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8));
|
canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8));
|
||||||
|
} else if (themeType == AppThemeType.music) {
|
||||||
|
// Pallini (dots) neri per staccare dal fondo chiaro
|
||||||
|
canvas.drawCircle(pos, 4.5, dotPaint..color = Colors.black87);
|
||||||
} else {
|
} else {
|
||||||
canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6));
|
canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6));
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +250,7 @@ class BoardPainter extends CustomPainter {
|
||||||
..strokeCap = StrokeCap.round
|
..strokeCap = StrokeCap.round
|
||||||
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
|
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
|
||||||
|
|
||||||
|
// Effetto linea frammentata
|
||||||
canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
|
canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
|
||||||
|
|
||||||
Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy);
|
Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
|
||||||
import '../../logic/game_controller.dart';
|
import '../../logic/game_controller.dart';
|
||||||
import '../../core/theme_manager.dart';
|
import '../../core/theme_manager.dart';
|
||||||
import '../../core/app_colors.dart';
|
import '../../core/app_colors.dart';
|
||||||
|
import '../../models/game_board.dart';
|
||||||
import 'board_painter.dart';
|
import 'board_painter.dart';
|
||||||
import 'score_board.dart';
|
import 'score_board.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
@ -338,7 +339,24 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: actualWidth, height: actualHeight,
|
width: actualWidth, height: actualHeight,
|
||||||
child: GestureDetector(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// --- IL VERO SFONDO SFOCATO SAGOMATO ---
|
||||||
|
// Ritaglia il filtro di sfocatura esattamente sulla forma dell'arena
|
||||||
|
if (themeType == AppThemeType.music)
|
||||||
|
Positioned.fill(
|
||||||
|
child: ClipPath(
|
||||||
|
clipper: _ArenaClipper(gameController.board),
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white.withOpacity(0.08), // La patina chiara
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
|
onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
|
|
@ -356,6 +374,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
@ -495,6 +515,38 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// CLIPPER MAGICO: Ritaglia l'effetto sfocatura sull'esatta forma dell'arena
|
||||||
|
// ===========================================================================
|
||||||
|
class _ArenaClipper extends CustomClipper<Path> {
|
||||||
|
final GameBoard board;
|
||||||
|
_ArenaClipper(this.board);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Path getClip(Size size) {
|
||||||
|
int cols = board.columns + 1;
|
||||||
|
double spacing = size.width / cols;
|
||||||
|
double offset = spacing / 2;
|
||||||
|
Path path = Path();
|
||||||
|
|
||||||
|
for (var box in board.boxes) {
|
||||||
|
// Ignora i buchi in modo che non vengano sfocati!
|
||||||
|
if (box.type != BoxType.invisible) {
|
||||||
|
path.addRect(Rect.fromLTWH(
|
||||||
|
box.x * spacing + offset,
|
||||||
|
box.y * spacing + offset,
|
||||||
|
spacing,
|
||||||
|
spacing
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReclip(covariant _ArenaClipper oldClipper) => true;
|
||||||
|
}
|
||||||
|
|
||||||
class _Particle {
|
class _Particle {
|
||||||
double x, y, vx, vy, size, angle, spin;
|
double x, y, vx, vy, size, angle, spin;
|
||||||
Color color; int type;
|
Color color; int type;
|
||||||
|
|
|
||||||
375
lib/ui/home/dialog.dart
Normal file
375
lib/ui/home/dialog.dart
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
// ===========================================================================
|
||||||
|
// FILE: lib/ui/home/dialogs/dialog.dart
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
|
import '../../../core/theme_manager.dart';
|
||||||
|
import '../../../core/app_colors.dart';
|
||||||
|
import '../../../l10n/app_localizations.dart';
|
||||||
|
import '../../../widgets/painters.dart';
|
||||||
|
import '../../../widgets/cyber_border.dart';
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 1. DIALOGO MISSIONI (QUESTS)
|
||||||
|
// ===========================================================================
|
||||||
|
class QuestsDialog extends StatelessWidget {
|
||||||
|
const QuestsDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final themeManager = context.watch<ThemeManager>();
|
||||||
|
final theme = themeManager.currentColors;
|
||||||
|
final themeType = themeManager.currentThemeType;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return FutureBuilder<SharedPreferences>(
|
||||||
|
future: SharedPreferences.getInstance(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) return const SizedBox();
|
||||||
|
final prefs = snapshot.data!;
|
||||||
|
|
||||||
|
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(loc.questsTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
|
||||||
|
const SizedBox(height: 25),
|
||||||
|
|
||||||
|
...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: getSharedTextStyle(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: getSharedTextStyle(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(context),
|
||||||
|
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 2. DIALOGO CLASSIFICA (LEADERBOARD)
|
||||||
|
// ===========================================================================
|
||||||
|
class LeaderboardDialog extends StatelessWidget {
|
||||||
|
const LeaderboardDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final themeManager = context.watch<ThemeManager>();
|
||||||
|
final theme = themeManager.currentColors;
|
||||||
|
final themeType = themeManager.currentThemeType;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
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: [
|
||||||
|
const Icon(Icons.emoji_events, size: 50, color: Colors.amber),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(loc.leaderboardTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
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(),
|
||||||
|
itemCount: docs.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
var data = docs[index].data() as Map<String, dynamic>;
|
||||||
|
String? myUid = FirebaseAuth.instance.currentUser?.uid;
|
||||||
|
bool isMe = docs[index].id == myUid;
|
||||||
|
|
||||||
|
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: getSharedTextStyle(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: getSharedTextStyle(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(context),
|
||||||
|
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
|
||||||
|
content = AnimatedCyberBorder(child: content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 3. DIALOGO TUTORIAL
|
||||||
|
// ===========================================================================
|
||||||
|
class TutorialDialog extends StatelessWidget {
|
||||||
|
const TutorialDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final themeManager = context.watch<ThemeManager>();
|
||||||
|
final theme = themeManager.currentColors;
|
||||||
|
final themeType = themeManager.currentThemeType;
|
||||||
|
Color inkColor = const Color(0xFF111122);
|
||||||
|
|
||||||
|
String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:";
|
||||||
|
String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:";
|
||||||
|
String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:";
|
||||||
|
|
||||||
|
Widget dialogContent = themeType == AppThemeType.doodle
|
||||||
|
? Transform.rotate(
|
||||||
|
angle: -0.01,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade50, strokeColor: inkColor, seed: 400),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(25.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Center(child: Text("COME GIOCARE", style: getSharedTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
TutorialStep(icon: Icons.line_axis, text: "Chiudi i 4 lati di un quadrato per conquistare 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.black26, thickness: 2),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Center(child: Text("GLOSSARIO ARENA", style: getSharedTextStyle(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(context),
|
||||||
|
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: getSharedTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: 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: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5),
|
||||||
|
boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))],
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Center(child: Text("COME GIOCARE", style: getSharedTextStyle(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 per conquistare 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: getSharedTextStyle(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(context),
|
||||||
|
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
|
||||||
|
dialogContent = AnimatedCyberBorder(child: dialogContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: dialogContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TutorialStep extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final Color? iconColor;
|
||||||
|
final String text;
|
||||||
|
final AppThemeType themeType;
|
||||||
|
final Color inkColor;
|
||||||
|
final ThemeColors theme;
|
||||||
|
|
||||||
|
const TutorialStep({super.key, required this.icon, this.iconColor, required this.text, required this.themeType, required this.inkColor, required this.theme});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: iconColor ?? (themeType == AppThemeType.doodle ? inkColor : theme.playerBlue), size: 28),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
Expanded(
|
||||||
|
child: Text(text, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, color: themeType == AppThemeType.doodle ? inkColor : theme.text.withOpacity(0.8), height: 1.3))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
57
lib/widgets/cyber_border.dart
Normal file
57
lib/widgets/cyber_border.dart
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../core/theme_manager.dart'; // Import aggiornato
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
class AnimatedCyberBorder extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
const AnimatedCyberBorder({super.key, 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(
|
||||||
|
decoration: BoxDecoration(color: theme.background.withOpacity(0.9), borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 25, spreadRadius: 2)]),
|
||||||
|
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;
|
||||||
|
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(15));
|
||||||
|
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;
|
||||||
|
}
|
||||||
90
lib/widgets/home_buttons.dart
Normal file
90
lib/widgets/home_buttons.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../core/app_colors.dart'; // Import aggiornato
|
||||||
|
import 'painters.dart';
|
||||||
|
|
||||||
|
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;
|
||||||
|
final bool compact;
|
||||||
|
|
||||||
|
const FeatureCard({super.key, 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});
|
||||||
|
|
||||||
|
@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(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 22.0, vertical: compact ? 12.0 : 16.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: inkColor, size: compact ? 24 : 32),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: compact ? 16 : 24, fontWeight: FontWeight.w900)))),
|
||||||
|
if (!compact) ...[ const SizedBox(height: 2), Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.bold))) ]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!compact) Icon(Icons.chevron_right_rounded, color: inkColor.withOpacity(0.6), size: 32),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 20.0, vertical: compact ? 10.0 : 14.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isFeatured ? [color.withOpacity(0.9), color.withOpacity(0.6)] : [color.withOpacity(0.25), color.withOpacity(0.05)]),
|
||||||
|
borderRadius: BorderRadius.circular(15), border: Border.all(color: color.withOpacity(isFeatured ? 0.5 : 0.2), width: 1.5),
|
||||||
|
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(0, 8), blurRadius: 15), BoxShadow(color: color.withOpacity(isFeatured ? 0.3 : 0.05), offset: const Offset(-1, -1), blurRadius: 5)]
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(compact ? 6 : 10),
|
||||||
|
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))]),
|
||||||
|
child: Icon(icon, color: isFeatured ? Colors.white : color, size: compact ? 20 : 26),
|
||||||
|
),
|
||||||
|
SizedBox(width: compact ? 10 : 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(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: getSharedTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white.withOpacity(0.8) : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: FontWeight.bold))) ]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!compact) Icon(Icons.chevron_right_rounded, color: isFeatured ? Colors.white.withOpacity(0.7) : color.withOpacity(0.5), size: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
lib/widgets/music_theme_widgets.dart
Normal file
129
lib/widgets/music_theme_widgets.dart
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
// ===========================================================================
|
||||||
|
// FILE: lib/widgets/music_theme_widgets.dart
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../core/app_colors.dart';
|
||||||
|
import 'painters.dart';
|
||||||
|
|
||||||
|
class MusicCassetteCard extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final Color neonColor;
|
||||||
|
final double angle;
|
||||||
|
final IconData leftIcon;
|
||||||
|
final IconData rightIcon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final AppThemeType themeType;
|
||||||
|
|
||||||
|
const MusicCassetteCard({super.key, required this.title, required this.subtitle, required this.neonColor, required this.angle, required this.leftIcon, required this.rightIcon, required this.onTap, required this.themeType});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Transform.rotate(
|
||||||
|
angle: angle,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
// Aumentato leggermente l'altezza a 125 per evitare l'overflow
|
||||||
|
height: 125, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF22222A), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 2),
|
||||||
|
boxShadow: [ BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2), const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15) ]
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(color: neonColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
// Aggiunto minAxisSize per far stare il contenuto stretto
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)]))))),
|
||||||
|
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(subtitle, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold))))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
height: 35, width: 180, decoration: BoxDecoration(color: const Color(0xFF0D0D12), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white24, width: 1)),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Container(height: 2, width: 120, color: const Color(0xFF333333)),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildSpool(), _buildSpool() ]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSpool() {
|
||||||
|
return Container(
|
||||||
|
width: 26, height: 26, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white70, border: Border.all(color: Colors.black87, width: 5)),
|
||||||
|
child: Center(child: Container(width: 6, height: 6, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MusicKnobCard extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final AppThemeType themeType;
|
||||||
|
final Color? iconColor;
|
||||||
|
|
||||||
|
const MusicKnobCard({super.key, required this.title, required this.icon, required this.onTap, required this.themeType, this.iconColor});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 65, height: 65,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle, color: const Color(0xFF222222), border: Border.all(color: const Color(0xFF111111), width: 2),
|
||||||
|
boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)), BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1))],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(6.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle, border: Border.all(color: Colors.black54, width: 1),
|
||||||
|
gradient: const SweepGradient(colors: [Color(0xFF555555), Color(0xFFAAAAAA), Color(0xFF555555), Color(0xFF222222), Color(0xFF555555)]),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xFF1A1A1A)),
|
||||||
|
child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0)))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
lib/widgets/painters.dart
Normal file
73
lib/widgets/painters.dart
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import '../core/app_colors.dart'; // Import aggiornato
|
||||||
|
|
||||||
|
TextStyle getSharedTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
||||||
|
if (themeType == AppThemeType.doodle) {
|
||||||
|
return GoogleFonts.permanentMarker(textStyle: baseStyle);
|
||||||
|
} 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));
|
||||||
|
} else if (themeType == AppThemeType.music) {
|
||||||
|
return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5));
|
||||||
|
}
|
||||||
|
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()..moveTo(wobble(), wobble())..lineTo(size.width + wobble(), wobble())..lineTo(size.width + wobble(), size.height + wobble())..lineTo(wobble(), size.height + wobble())..close();
|
||||||
|
final Path fillPath = Path()..moveTo(wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, size.height + wobble() * 1.5)..lineTo(wobble() * 1.5, size.height + wobble() * 1.5)..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) => oldDelegate.fillColor != fillColor || oldDelegate.strokeColor != strokeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioCablesPainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()..color = const Color(0xFF151515)..style = PaintingStyle.stroke..strokeWidth = 8.0..strokeCap = StrokeCap.round;
|
||||||
|
final highlight = Paint()..color = const Color(0xFF3A3A3A)..style = PaintingStyle.stroke..strokeWidth = 2.0..strokeCap = StrokeCap.round;
|
||||||
|
void drawCable(Path path) { canvas.drawPath(path, paint); canvas.drawPath(path, highlight); }
|
||||||
|
|
||||||
|
Path c1 = Path()..moveTo(-20, size.height * 0.2)..quadraticBezierTo(100, size.height * 0.25, 50, size.height * 0.4)..quadraticBezierTo(0, size.height * 0.5, -20, size.height * 0.55); drawCable(c1);
|
||||||
|
Path c2 = Path()..moveTo(size.width + 20, size.height * 0.4)..quadraticBezierTo(size.width - 100, size.height * 0.5, size.width - 50, size.height * 0.7)..quadraticBezierTo(size.width, size.height * 0.8, size.width + 20, size.height * 0.85); drawCable(c2);
|
||||||
|
Path c3 = Path()..moveTo(size.width * 0.2, size.height + 20)..quadraticBezierTo(size.width * 0.3, size.height - 80, size.width * 0.5, size.height - 60)..quadraticBezierTo(size.width * 0.7, size.height - 40, size.width * 0.8, size.height + 20); drawCable(c3);
|
||||||
|
|
||||||
|
_drawJack(canvas, Offset(80, size.height * 0.38), -0.5);
|
||||||
|
_drawJack(canvas, Offset(size.width - 60, size.height * 0.68), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawJack(Canvas canvas, Offset pos, double angle) {
|
||||||
|
canvas.save(); canvas.translate(pos.dx, pos.dy); canvas.rotate(angle);
|
||||||
|
canvas.drawRect(const Rect.fromLTWH(-15, -4, 15, 8), Paint()..color = const Color(0xFF151515));
|
||||||
|
canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(0, -6, 25, 12), const Radius.circular(2)), Paint()..color = const Color(0xFF222222));
|
||||||
|
canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(2, -4, 21, 8), const Radius.circular(2)), Paint()..color = const Color(0xFF444444));
|
||||||
|
canvas.drawRect(const Rect.fromLTWH(25, -2, 15, 4), Paint()..color = const Color(0xFFCCCCCC));
|
||||||
|
canvas.drawRect(const Rect.fromLTWH(40, -1.5, 5, 3), Paint()..color = const Color(0xFFAAAAAA));
|
||||||
|
canvas.drawLine(const Offset(30, -2), const Offset(30, 2), Paint()..color = Colors.black..strokeWidth = 1.5);
|
||||||
|
canvas.drawLine(const Offset(35, -2), const Offset(35, 2), Paint()..color = Colors.black..strokeWidth = 1.5);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
@override bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue