diff --git a/ios/.DS_Store b/ios/.DS_Store
index 4d2c3b2..1ade408 100644
Binary files a/ios/.DS_Store and b/ios/.DS_Store differ
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index db0a89b..c7e6504 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -483,7 +483,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
- PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq;
+ PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -668,7 +668,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
- PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq;
+ PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -693,7 +693,7 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
- PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq;
+ PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index b850f8e..2401443 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -2,6 +2,8 @@
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
@@ -24,6 +26,8 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ UIApplicationSupportsIndirectInputEvents
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -41,9 +45,5 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
diff --git a/lib/ui/admin/admin_screen.dart b/lib/ui/admin/admin_screen.dart
new file mode 100644
index 0000000..f2a83eb
--- /dev/null
+++ b/lib/ui/admin/admin_screen.dart
@@ -0,0 +1,128 @@
+// ===========================================================================
+// FILE: lib/ui/admin/admin_screen.dart
+// ===========================================================================
+
+import 'package:flutter/material.dart';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:intl/intl.dart';
+import 'package:provider/provider.dart';
+import '../../core/theme_manager.dart';
+
+class AdminScreen extends StatelessWidget {
+ const AdminScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch().currentColors;
+
+ return Scaffold(
+ backgroundColor: theme.background,
+ appBar: AppBar(
+ title: Text("DASHBOARD ADMIN 🕵️♂️", style: TextStyle(color: theme.text, fontWeight: FontWeight.w900, letterSpacing: 2)),
+ backgroundColor: theme.background,
+ iconTheme: IconThemeData(color: theme.text),
+ elevation: 0,
+ ),
+ body: StreamBuilder(
+ // Ordiniamo per Ultimo Accesso, così i giocatori attivi di recente sono in cima!
+ stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('lastActive', descending: true).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("Nessun giocatore trovato nel database.", style: TextStyle(color: theme.text)));
+ }
+
+ var docs = snapshot.data!.docs;
+
+ return ListView.builder(
+ padding: const EdgeInsets.all(16),
+ itemCount: docs.length,
+ itemBuilder: (context, index) {
+ var data = docs[index].data() as Map;
+
+ String name = data['name'] ?? 'Fantasma';
+ int level = data['level'] ?? 1;
+ int xp = data['xp'] ?? 0;
+ int wins = data['wins'] ?? 0;
+ String platform = data['platform'] ?? 'Sconosciuta';
+
+ // Formattazione Date (Se esistono)
+ DateTime? created;
+ if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate();
+
+ DateTime? lastActive;
+ if (data['lastActive'] != null) lastActive = (data['lastActive'] as Timestamp).toDate();
+
+ String createdStr = created != null ? DateFormat('dd MMM yyyy').format(created) : 'N/D';
+ String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D';
+
+ IconData platformIcon = Icons.device_unknown;
+ if (platform == 'iOS') platformIcon = Icons.apple;
+ if (platform == 'Android') platformIcon = Icons.android;
+
+ return Card(
+ color: theme.text.withOpacity(0.05),
+ elevation: 0,
+ margin: const EdgeInsets.only(bottom: 12),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(15),
+ side: BorderSide(color: theme.gridLine.withOpacity(0.3))
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(name, style: TextStyle(color: theme.playerBlue, fontSize: 22, fontWeight: FontWeight.w900)),
+ Icon(platformIcon, color: theme.text.withOpacity(0.7)),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 16)),
+ const SizedBox(width: 15),
+ Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7))),
+ const SizedBox(width: 15),
+ Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold)),
+ ],
+ ),
+ const Padding(
+ padding: EdgeInsets.symmetric(vertical: 8.0),
+ child: Divider(),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("Registrato il:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)),
+ Text(createdStr, style: TextStyle(color: theme.text, fontSize: 12, fontWeight: FontWeight.bold)),
+ ],
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text("Ultimo Accesso:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)),
+ Text(lastActiveStr, style: TextStyle(color: Colors.green, fontSize: 12, fontWeight: FontWeight.bold)),
+ ],
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart
index 0925dbc..07b42c1 100644
--- a/lib/ui/home/home_screen.dart
+++ b/lib/ui/home/home_screen.dart
@@ -22,6 +22,7 @@ import '../multiplayer/lobby_screen.dart';
import 'history_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:tetraq/l10n/app_localizations.dart';
+import '../admin/admin_screen.dart';
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
if (themeType == AppThemeType.doodle) {
@@ -396,6 +397,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkPlayerName();
+ StorageService.instance.syncLeaderboard(); // <--- AGGIUNTO: Segna l'ultimo accesso ORA!
});
_checkClipboardForInvite();
}
@@ -1233,20 +1235,22 @@ class _HomeScreenState extends State with WidgetsBindingObserver {
// --- IL TRUCCO DELLO SVILUPPATORE PROTETTO ---
child: GestureDetector(
onTap: () {
- if (kReleaseMode) return;
_debugTapCount++;
- if (_debugTapCount >= 5) {
- _debugTapCount = 0;
+ if (_debugTapCount == 5) {
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))),
+ content: Text("🛠 DEBUG MODE: +20 Livelli!", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
backgroundColor: Colors.purpleAccent,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
)
);
+ } else if (_debugTapCount >= 7) {
+ _debugTapCount = 0; // Resetta il contatore
+ // APRE LA DASHBOARD SEGRETA!
+ Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen()));
}
},
child: FittedBox(