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(