Auto-sync: 20260324_140000
This commit is contained in:
parent
1395fc32f6
commit
027c41a75c
22 changed files with 166301 additions and 76 deletions
|
|
@ -549,7 +549,7 @@ class GameController extends ChangeNotifier {
|
|||
|
||||
if (!forced) _playEffects(newClosed, newGhosts: newGhosts, isOpponent: false);
|
||||
|
||||
_startTimer(); notifyListeners();
|
||||
notifyListeners(); // Modificato per non riavviare ciecamente il timer
|
||||
|
||||
if (isOnline && roomCode != null) {
|
||||
Map<String, dynamic> moveData = {
|
||||
|
|
@ -567,17 +567,27 @@ class GameController extends ChangeNotifier {
|
|||
if (board.isGameOver) {
|
||||
_saveMatchResult();
|
||||
if (isHost) FirebaseFirestore.instance.collection('games').doc(roomCode).update({'status': 'finished'});
|
||||
} else {
|
||||
_startTimer(); // Rimesso il timer se si continua a giocare online
|
||||
}
|
||||
} else {
|
||||
if (board.isGameOver) _saveMatchResult();
|
||||
else if (isVsCPU && board.currentPlayer == Player.blue) _checkCPUTurn();
|
||||
if (board.isGameOver) {
|
||||
_saveMatchResult();
|
||||
} else if (isVsCPU && board.currentPlayer == Player.blue) {
|
||||
_checkCPUTurn(); // Se tocca alla CPU, la CPU fermerà il timer internamente
|
||||
} else {
|
||||
_startTimer(); // Se tocca all'umano, facciamo (ri)partire il timer!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkCPUTurn() async {
|
||||
if (isVsCPU && board.currentPlayer == Player.blue && !board.isGameOver) {
|
||||
isCPUThinking = true; _blitzTimer?.cancel(); notifyListeners();
|
||||
isCPUThinking = true;
|
||||
_blitzTimer?.cancel(); // La CPU inizia a pensare, congela il timer del giocatore
|
||||
notifyListeners();
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
|
||||
if (!board.isGameOver) {
|
||||
|
|
@ -592,10 +602,18 @@ class GameController extends ChangeNotifier {
|
|||
|
||||
_playEffects(newClosed, newGhosts: newGhosts, isOpponent: true);
|
||||
|
||||
isCPUThinking = false; _startTimer(); notifyListeners();
|
||||
isCPUThinking = false;
|
||||
notifyListeners();
|
||||
|
||||
if (board.isGameOver) _saveMatchResult();
|
||||
else _checkCPUTurn();
|
||||
if (board.isGameOver) {
|
||||
_saveMatchResult();
|
||||
} else if (board.currentPlayer == Player.blue) {
|
||||
// La CPU ha chiuso un quadrato e ha diritto a un'altra mossa
|
||||
_checkCPUTurn();
|
||||
} else {
|
||||
// Turno passato all'umano: il timer riparte!
|
||||
_startTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Platform, HttpClient;
|
||||
import 'dart:async'; // <--- AGGIUNTO PER IL TIMER DELL'HEARTBEAT
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import '../core/app_colors.dart';
|
||||
|
|
@ -18,6 +19,7 @@ class StorageService {
|
|||
|
||||
late SharedPreferences _prefs;
|
||||
int _sessionStart = 0;
|
||||
Timer? _heartbeatTimer; // <--- IL NOSTRO BATTITO CARDIACO
|
||||
|
||||
Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
|
|
@ -26,6 +28,20 @@ class StorageService {
|
|||
_sessionStart = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
// --- NUOVI METODI PER GESTIRE LA PRESENZA ---
|
||||
void startHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
// Esegue il sync leggero ogni 60 secondi
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 120), (_) {
|
||||
syncLeaderboard(isHeartbeat: true);
|
||||
});
|
||||
}
|
||||
|
||||
void stopHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
}
|
||||
// ----------------------------------------------
|
||||
|
||||
Future<void> _fetchLocationData() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
|
|
@ -112,9 +128,9 @@ class StorageService {
|
|||
}
|
||||
|
||||
// ======================================================================
|
||||
// FIX: ORA IL SYNC MANDA I DATI REALI ALLA DASHBOARD ADMIN!
|
||||
// LOGICA SYNC AGGIORNATA: GESTIONE HEARTBEAT LEGGERO
|
||||
// ======================================================================
|
||||
Future<void> syncLeaderboard() async {
|
||||
Future<void> syncLeaderboard({bool isHeartbeat = false}) async {
|
||||
try {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
if (user == null) return;
|
||||
|
|
@ -124,57 +140,60 @@ class StorageService {
|
|||
|
||||
String targetUid = user.uid;
|
||||
|
||||
// 1. Recupero Versione App e Modello Dispositivo
|
||||
String appVer = "N/D";
|
||||
String devModel = "N/D";
|
||||
String osName = kIsWeb ? "Web" : Platform.operatingSystem;
|
||||
|
||||
try {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVer = "${packageInfo.version}+${packageInfo.buildNumber}";
|
||||
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
devModel = "${androidInfo.brand} ${androidInfo.model}".toUpperCase();
|
||||
osName = "Android";
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
devModel = iosInfo.utsname.machine; // Es. "iPhone13,2"
|
||||
osName = "iOS";
|
||||
} else if (Platform.isMacOS) {
|
||||
MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo;
|
||||
devModel = macInfo.model; // Es. "MacBookPro17,1"
|
||||
osName = "macOS";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Errore device info: $e");
|
||||
}
|
||||
|
||||
// 2. Calcolo del Playtime effettivo (aggiornato ad ogni sync)
|
||||
// 1. Calcolo del Playtime effettivo (aggiornato ad ogni sync)
|
||||
int sessionDurationSec = (DateTime.now().millisecondsSinceEpoch - _sessionStart) ~/ 1000;
|
||||
int savedPlaytime = _prefs.getInt('total_playtime') ?? 0;
|
||||
int totalPlaytime = savedPlaytime + sessionDurationSec;
|
||||
await _prefs.setInt('total_playtime', totalPlaytime);
|
||||
_sessionStart = DateTime.now().millisecondsSinceEpoch; // Resetta il timer di sessione
|
||||
|
||||
// 3. Creazione del payload per Firebase
|
||||
// 2. Creazione del payload di base (dati leggeri che cambiano spesso)
|
||||
Map<String, dynamic> dataToSave = {
|
||||
'name': name,
|
||||
'level': playerLevel,
|
||||
'lastActive': FieldValue.serverTimestamp(),
|
||||
'appVersion': appVer,
|
||||
'deviceModel': devModel,
|
||||
'platform': osName,
|
||||
'ip': lastIp,
|
||||
'city': lastCity,
|
||||
'playtime': totalPlaytime,
|
||||
};
|
||||
|
||||
if (user.metadata.creationTime != null) {
|
||||
dataToSave['accountCreated'] = Timestamp.fromDate(user.metadata.creationTime!);
|
||||
// 3. Se NON è un heartbeat, raccogliamo anche i dati "pesanti" (Device info, ecc.)
|
||||
if (!isHeartbeat) {
|
||||
String appVer = "N/D";
|
||||
String devModel = "N/D";
|
||||
String osName = kIsWeb ? "Web" : Platform.operatingSystem;
|
||||
|
||||
try {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVer = "${packageInfo.version}+${packageInfo.buildNumber}";
|
||||
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
devModel = "${androidInfo.brand} ${androidInfo.model}".toUpperCase();
|
||||
osName = "Android";
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
devModel = iosInfo.utsname.machine; // Es. "iPhone13,2"
|
||||
osName = "iOS";
|
||||
} else if (Platform.isMacOS) {
|
||||
MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo;
|
||||
devModel = macInfo.model; // Es. "MacBookPro17,1"
|
||||
osName = "macOS";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Errore device info: $e");
|
||||
}
|
||||
|
||||
dataToSave['appVersion'] = appVer;
|
||||
dataToSave['deviceModel'] = devModel;
|
||||
dataToSave['platform'] = osName;
|
||||
dataToSave['ip'] = lastIp;
|
||||
dataToSave['city'] = lastCity;
|
||||
|
||||
if (user.metadata.creationTime != null) {
|
||||
dataToSave['accountCreated'] = Timestamp.fromDate(user.metadata.creationTime!);
|
||||
}
|
||||
}
|
||||
|
||||
await FirebaseFirestore.instance.collection('leaderboard').doc(targetUid).set(dataToSave, SetOptions(merge: true));
|
||||
|
|
|
|||
|
|
@ -57,6 +57,60 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
@override
|
||||
void dispose() { _blinkController.dispose(); super.dispose(); }
|
||||
|
||||
// --- NUOVO DIALOG: CONFERMA USCITA (ANTI-FUGA) ---
|
||||
void _showExitConfirmationDialog(BuildContext context, GameController gameController, ThemeColors theme, AppThemeType themeType) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
backgroundColor: theme.background,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
side: BorderSide(color: theme.playerRed, width: 2),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.warning_amber_rounded, color: theme.playerRed),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"ABBANDONARE?",
|
||||
style: _getTextStyle(themeType, TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 18))
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
"Se esci ora, la partita verrà registrata automaticamente come una SCONFITTA.\n\nSei sicuro di voler fuggire?",
|
||||
style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 15, height: 1.4)),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontWeight: FontWeight.bold))),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.playerRed,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
// 1. Assegna la sconfitta!
|
||||
StorageService.instance.addLoss();
|
||||
// 2. Disconnette e pulisce
|
||||
gameController.disconnectOnlineGame();
|
||||
// 3. Chiude il dialog
|
||||
Navigator.pop(ctx);
|
||||
// 4. Torna al menu
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("SÌ, ESCI", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showGameOverDialog(BuildContext context, GameController game, ThemeColors theme, AppThemeType themeType) {
|
||||
_gameOverDialogShown = true;
|
||||
|
||||
|
|
@ -406,7 +460,18 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))),
|
||||
icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20),
|
||||
onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); },
|
||||
|
||||
// --- NUOVO ON PRESSED ANTI-FUGA ---
|
||||
onPressed: () {
|
||||
if (!gameController.isGameOver && !gameController.isSetupPhase) {
|
||||
_showExitConfirmationDialog(context, gameController, theme, themeType);
|
||||
} else {
|
||||
gameController.disconnectOnlineGame();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
// ------------------------
|
||||
|
||||
label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))),
|
||||
),
|
||||
),
|
||||
|
|
@ -424,9 +489,20 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
),
|
||||
);
|
||||
|
||||
// --- NUOVA LOGICA: Impedisce la chiusura accidentale o la fuga (Tasto Indietro) ---
|
||||
bool shouldConfirmExit = !gameController.isGameOver && !gameController.isSetupPhase;
|
||||
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); },
|
||||
canPop: !shouldConfirmExit,
|
||||
onPopInvoked: (didPop) {
|
||||
if (didPop) {
|
||||
gameController.disconnectOnlineGame();
|
||||
return;
|
||||
}
|
||||
if (shouldConfirmExit) {
|
||||
_showExitConfirmationDialog(context, gameController, theme, themeType);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: themeType == AppThemeType.doodle ? Colors.white : (bgImage != null ? Colors.transparent : theme.background),
|
||||
body: Stack(
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// --- AVVIA IL BATTITO CARDIACO ---
|
||||
StorageService.instance.startHeartbeat();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (StorageService.instance.playerName.isEmpty) {
|
||||
HomeModals.showNameDialog(context, () {
|
||||
|
|
@ -105,7 +109,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
}
|
||||
|
||||
Future<void> _checkStoreForUpdate() async {
|
||||
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
|
|
@ -123,7 +126,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
} catch (e) {
|
||||
debugPrint("Errore controllo aggiornamenti: $e");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _triggerUpdate() async {
|
||||
|
|
@ -153,6 +155,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
StorageService.instance.stopHeartbeat(); // <-- Assicurati di fermarlo
|
||||
_cleanupGhostRoom();
|
||||
_linkSubscription?.cancel();
|
||||
_favoritesSubscription?.cancel();
|
||||
|
|
@ -163,9 +166,19 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
// --- L'UTENTE TORNA NELL'APP: RIPRENDI IL BATTITO E AGGIORNA SUBITO ---
|
||||
StorageService.instance.syncLeaderboard();
|
||||
StorageService.instance.startHeartbeat();
|
||||
|
||||
_checkClipboardForInvite();
|
||||
_listenToFavoritesOnline();
|
||||
} else if (state == AppLifecycleState.detached) {
|
||||
}
|
||||
else if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) {
|
||||
// --- L'UTENTE ESCE DALL'APP O LA METTE IN BACKGROUND: FERMA IL BATTITO ---
|
||||
StorageService.instance.stopHeartbeat();
|
||||
}
|
||||
else if (state == AppLifecycleState.detached) {
|
||||
StorageService.instance.stopHeartbeat();
|
||||
_cleanupGhostRoom();
|
||||
}
|
||||
}
|
||||
|
|
@ -252,8 +265,9 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
void _showFavoriteOnlinePopup(String name) {
|
||||
if (!mounted) return;
|
||||
|
||||
// Se lo abbiamo già notificato nell'ultima ora, ignoriamo l'aggiornamento
|
||||
if (_lastOnlineNotifications.containsKey(name)) {
|
||||
if (DateTime.now().difference(_lastOnlineNotifications[name]!).inMinutes < 1) return;
|
||||
if (DateTime.now().difference(_lastOnlineNotifications[name]!).inMinutes < 60) return;
|
||||
}
|
||||
_lastOnlineNotifications[name] = DateTime.now();
|
||||
|
||||
|
|
|
|||
|
|
@ -186,35 +186,93 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
borderRadius: BorderRadius.circular(10)
|
||||
),
|
||||
child: favs.isEmpty
|
||||
? Center(child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))),
|
||||
))
|
||||
: ListView.builder(
|
||||
itemCount: favs.length,
|
||||
itemBuilder: (c, i) {
|
||||
return ListTile(
|
||||
title: Text(favs[i]['name']!, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))),
|
||||
trailing: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_createRoomAndInvite(favs[i]['uid']!, favs[i]['name']!);
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))),
|
||||
)
|
||||
)
|
||||
: StreamBuilder<QuerySnapshot>(
|
||||
// Interroghiamo Firebase solo per gli UID dei nostri preferiti (max 10 per limiti di Firestore)
|
||||
stream: FirebaseFirestore.instance.collection('leaderboard')
|
||||
.where(FieldPath.documentId, whereIn: favs.map((f) => f['uid']).take(10).toList())
|
||||
.snapshots(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return Center(child: CircularProgressIndicator(color: theme.playerBlue));
|
||||
}
|
||||
|
||||
// Mappiamo i risultati di Firebase per un accesso rapido
|
||||
Map<String, Map<String, dynamic>> liveData = {};
|
||||
for (var doc in snapshot.data!.docs) {
|
||||
liveData[doc.id] = doc.data() as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: favs.length,
|
||||
itemBuilder: (c, i) {
|
||||
String uid = favs[i]['uid']!;
|
||||
String name = favs[i]['name']!;
|
||||
|
||||
bool isOnline = false;
|
||||
if (liveData.containsKey(uid) && liveData[uid]!['lastActive'] != null) {
|
||||
Timestamp lastActive = liveData[uid]!['lastActive'];
|
||||
int diffInSeconds = DateTime.now().difference(lastActive.toDate()).inSeconds;
|
||||
// Se ha fatto un'azione negli ultimi 3 minuti, lo consideriamo online
|
||||
if (diffInSeconds.abs() < 180) isOnline = true;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.circle,
|
||||
color: isOnline ? Colors.greenAccent : Colors.redAccent.withOpacity(0.5),
|
||||
size: 14
|
||||
),
|
||||
title: Text(
|
||||
name,
|
||||
style: getLobbyTextStyle(themeType, TextStyle(
|
||||
color: isOnline ? theme.text : theme.text.withOpacity(0.5),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold
|
||||
))
|
||||
),
|
||||
trailing: isOnline
|
||||
? ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.playerBlue,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_createRoomAndInvite(uid, name);
|
||||
},
|
||||
child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10)
|
||||
),
|
||||
child: Text("OFFLINE", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.grey, fontSize: 12, fontWeight: FontWeight.bold))),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.playerRed))))
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text("CHIUDI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold))),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _showWaitingDialog(String code) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
@ -434,9 +492,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
|
||||
Row(
|
||||
children: [
|
||||
_buildTimeOption('PRO', '-2s A PARTITA', 'dynamic', theme, themeType),
|
||||
_buildTimeOption('10s', 'FISSO', 'fixed', theme, themeType),
|
||||
_buildTimeOption('RELAX', 'INFINITO', 'relax', theme, themeType),
|
||||
_buildTimeOption('DINAMICO', '-2s', 'dynamic', theme, themeType),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
|
@ -618,7 +676,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
String tMode = data['timeMode'] is String ? data['timeMode'] : (data['timeMode'] == true ? 'fixed' : 'relax');
|
||||
String prettyTime = "10s";
|
||||
if (tMode == 'relax') prettyTime = "Relax";
|
||||
else if (tMode == 'dynamic') prettyTime = "Dinamico";
|
||||
else if (tMode == 'dynamic') prettyTime = "PRO";
|
||||
|
||||
String prettyShape = "Rombo";
|
||||
if (shapeStr == 'cross') prettyShape = "Croce";
|
||||
|
|
|
|||
8573
report/TetraQ_15-03-26_11-48.txt
Normal file
8573
report/TetraQ_15-03-26_11-48.txt
Normal file
File diff suppressed because it is too large
Load diff
8573
report/TetraQ_15-03-26_11.51.txt
Normal file
8573
report/TetraQ_15-03-26_11.51.txt
Normal file
File diff suppressed because it is too large
Load diff
8930
report/TetraQ_15-03-26_14.47.txt
Normal file
8930
report/TetraQ_15-03-26_14.47.txt
Normal file
File diff suppressed because it is too large
Load diff
9070
report/TetraQ_15-03-26_16.25.txt
Normal file
9070
report/TetraQ_15-03-26_16.25.txt
Normal file
File diff suppressed because it is too large
Load diff
9060
report/TetraQ_15-03-26_20.51.txt
Normal file
9060
report/TetraQ_15-03-26_20.51.txt
Normal file
File diff suppressed because it is too large
Load diff
9073
report/TetraQ_18-03-26_13.14.txt
Normal file
9073
report/TetraQ_18-03-26_13.14.txt
Normal file
File diff suppressed because it is too large
Load diff
9145
report/TetraQ_20-03-26_12.51.txt
Normal file
9145
report/TetraQ_20-03-26_12.51.txt
Normal file
File diff suppressed because it is too large
Load diff
9833
report/TetraQ_20-03-26_21.39.txt
Normal file
9833
report/TetraQ_20-03-26_21.39.txt
Normal file
File diff suppressed because it is too large
Load diff
9897
report/TetraQ_20-03-26_21.47.txt
Normal file
9897
report/TetraQ_20-03-26_21.47.txt
Normal file
File diff suppressed because it is too large
Load diff
9914
report/TetraQ_20-03-26_22.02.txt
Normal file
9914
report/TetraQ_20-03-26_22.02.txt
Normal file
File diff suppressed because it is too large
Load diff
9970
report/TetraQ_20-03-26_23.00.txt
Normal file
9970
report/TetraQ_20-03-26_23.00.txt
Normal file
File diff suppressed because it is too large
Load diff
10424
report/TetraQ_20-03-26_23.29.txt
Normal file
10424
report/TetraQ_20-03-26_23.29.txt
Normal file
File diff suppressed because it is too large
Load diff
10429
report/TetraQ_21-03-26_10.28.txt
Normal file
10429
report/TetraQ_21-03-26_10.28.txt
Normal file
File diff suppressed because it is too large
Load diff
10677
report/TetraQ_21-03-26_10.43.txt
Normal file
10677
report/TetraQ_21-03-26_10.43.txt
Normal file
File diff suppressed because it is too large
Load diff
10425
report/TetraQ_22-03-26_23.54.txt
Normal file
10425
report/TetraQ_22-03-26_23.54.txt
Normal file
File diff suppressed because it is too large
Load diff
10564
report/TetraQ_23-03-26_00.16.txt
Normal file
10564
report/TetraQ_23-03-26_00.16.txt
Normal file
File diff suppressed because it is too large
Load diff
11483
report/TetraQ_24-03-26_13.52.txt
Normal file
11483
report/TetraQ_24-03-26_13.52.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue