// =========================================================================== // FILE: lib/services/storage_service.dart // =========================================================================== import 'dart:convert'; import 'dart:io' show Platform, HttpClient; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import '../core/app_colors.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; class StorageService { static final StorageService instance = StorageService._internal(); StorageService._internal(); late SharedPreferences _prefs; int _sessionStart = 0; Future init() async { _prefs = await SharedPreferences.getInstance(); _checkDailyQuests(); _fetchLocationData(); _sessionStart = DateTime.now().millisecondsSinceEpoch; } Future _fetchLocationData() async { if (kIsWeb) return; try { final request = await HttpClient().getUrl(Uri.parse('http://ip-api.com/json/')); final response = await request.close(); final responseBody = await response.transform(utf8.decoder).join(); final data = jsonDecode(responseBody); await _prefs.setString('last_ip', data['query'] ?? 'Sconosciuto'); await _prefs.setString('last_city', data['city'] ?? 'Sconosciuta'); } catch (e) { debugPrint("Errore recupero IP: $e"); } } String get lastIp => _prefs.getString('last_ip') ?? 'Sconosciuto'; String get lastCity => _prefs.getString('last_city') ?? 'Sconosciuta'; // --- METODI TEMA AGGIORNATI CON GESTIONE MIGRAZIONE SICURA --- String getTheme() { final Object? savedTheme = _prefs.get('theme'); if (savedTheme is String) { return savedTheme; } else if (savedTheme is int) { // Trovato un vecchio salvataggio in formato intero (causa del crash). // Puliamo la memoria per evitare futuri problemi. _prefs.remove('theme'); return AppThemeType.doodle.toString(); } return AppThemeType.doodle.toString(); } Future saveTheme(String themeStr) async => await _prefs.setString('theme', themeStr); int get savedRadius => _prefs.getInt('radius') ?? 2; Future saveRadius(int radius) async => await _prefs.setInt('radius', radius); bool get isMuted => _prefs.getBool('isMuted') ?? false; Future saveMuted(bool muted) async => await _prefs.setBool('isMuted', muted); int get totalXP => _prefs.getInt('totalXP') ?? 0; Future addXP(int xp) async { await _prefs.setInt('totalXP', totalXP + xp); syncLeaderboard(); } int get playerLevel => (totalXP / 100).floor() + 1; int get wins => _prefs.getInt('wins') ?? 0; Future addWin() async { await _prefs.setInt('wins', wins + 1); syncLeaderboard(); } int get losses => _prefs.getInt('losses') ?? 0; Future addLoss() async => await _prefs.setInt('losses', losses + 1); int get cpuLevel => _prefs.getInt('cpuLevel') ?? 1; Future saveCpuLevel(int level) async => await _prefs.setInt('cpuLevel', level); String get playerName => _prefs.getString('playerName') ?? ''; Future savePlayerName(String name) async { await _prefs.setString('playerName', name); syncLeaderboard(); } Future syncLeaderboard() async { if (playerName.isNotEmpty) { try { final user = FirebaseAuth.instance.currentUser; if (user != null) { String currentPlatform = "Sconosciuta"; String appVersion = "N/D"; String deviceModel = "Sconosciuto"; if (!kIsWeb) { if (Platform.isAndroid) currentPlatform = "Android"; else if (Platform.isIOS) currentPlatform = "iOS"; else if (Platform.isMacOS) currentPlatform = "macOS"; else if (Platform.isWindows) currentPlatform = "Windows"; try { PackageInfo packageInfo = await PackageInfo.fromPlatform(); appVersion = "${packageInfo.version}+${packageInfo.buildNumber}"; } catch(e) { debugPrint("Errore lettura versione: $e"); } try { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; deviceModel = "${androidInfo.manufacturer} ${androidInfo.model}"; } else if (Platform.isIOS) { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; deviceModel = iosInfo.utsname.machine; } else if (Platform.isMacOS) { MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo; deviceModel = macInfo.model; } } catch(e) { debugPrint("Errore lettura hardware: $e"); } } if (_sessionStart != 0) { int now = DateTime.now().millisecondsSinceEpoch; int sessionSeconds = (now - _sessionStart) ~/ 1000; await _prefs.setInt('totalPlaytime', (_prefs.getInt('totalPlaytime') ?? 0) + sessionSeconds); _sessionStart = now; } int totalPlaytime = _prefs.getInt('totalPlaytime') ?? 0; await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ 'name': playerName, 'xp': totalXP, 'level': playerLevel, 'wins': wins, 'lastActive': FieldValue.serverTimestamp(), 'platform': currentPlatform, 'ip': lastIp, 'city': lastCity, 'playtime': totalPlaytime, 'appVersion': appVersion, 'deviceModel': deviceModel, }, SetOptions(merge: true)); } } catch(e) { debugPrint("Errore sinc. classifica: $e"); } } } List> get favorites { List favs = _prefs.getStringList('favorites') ?? []; return favs.map((e) => Map.from(jsonDecode(e))).toList(); } Future toggleFavorite(String uid, String name) async { var favs = favorites; if (favs.any((f) => f['uid'] == uid)) { favs.removeWhere((f) => f['uid'] == uid); } else { favs.add({'uid': uid, 'name': name}); } await _prefs.setStringList('favorites', favs.map((e) => jsonEncode(e)).toList()); } bool isFavorite(String uid) { return favorites.any((f) => f['uid'] == uid); } void _checkDailyQuests() { String today = DateTime.now().toIso8601String().substring(0, 10); String lastDate = _prefs.getString('quest_date') ?? ''; if (today != lastDate) { _prefs.setString('quest_date', today); _prefs.setInt('q1_type', 0); _prefs.setInt('q1_prog', 0); _prefs.setInt('q1_target', 3); _prefs.setInt('q2_type', 1); _prefs.setInt('q2_prog', 0); _prefs.setInt('q2_target', 2); _prefs.setInt('q3_type', 2); _prefs.setInt('q3_prog', 0); _prefs.setInt('q3_target', 2); } } Future updateQuestProgress(int type, int amount) async { for(int i=1; i<=3; i++) { if (_prefs.getInt('q${i}_type') == type) { int prog = _prefs.getInt('q${i}_prog') ?? 0; int target = _prefs.getInt('q${i}_target') ?? 1; if (prog < target) { _prefs.setInt('q${i}_prog', prog + amount); } } } } List> get matchHistory { List history = _prefs.getStringList('matchHistory') ?? []; return history.map((e) => jsonDecode(e) as Map).toList(); } Future saveMatchToHistory({required String myName, required String opponent, required int myScore, required int oppScore, required bool isOnline}) async { List history = _prefs.getStringList('matchHistory') ?? []; Map match = { 'date': DateTime.now().toIso8601String(), 'myName': myName, 'opponent': opponent, 'myScore': myScore, 'oppScore': oppScore, 'isOnline': isOnline, }; history.insert(0, jsonEncode(match)); if (history.length > 50) history = history.sublist(0, 50); await _prefs.setStringList('matchHistory', history); } }