diff --git a/.DS_Store b/.DS_Store index 5db0715..413fbec 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.firebase/hosting.cHVibGlj.cache b/.firebase/hosting.cHVibGlj.cache new file mode 100644 index 0000000..40f0243 --- /dev/null +++ b/.firebase/hosting.cHVibGlj.cache @@ -0,0 +1,2 @@ +index.html,1773344753424,0d5d4b835a7d632ad11d249230a15561286f2bfcd1da8305c6fb294d37e5da09 +404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3 diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..09a0123 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "tetraq-32a4a" + } +} diff --git a/firebase.json b/firebase.json index 76a181a..72af755 100644 --- a/firebase.json +++ b/firebase.json @@ -1 +1,47 @@ -{"flutter":{"platforms":{"android":{"default":{"projectId":"tetraq-32a4a","appId":"1:705460445314:android:ceac21bb06b7a9f07b949b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"tetraq-32a4a","appId":"1:705460445314:ios:54d64cb7592954327b949b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"tetraq-32a4a","appId":"1:705460445314:ios:da11cbca5d1f6bc27b949b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"tetraq-32a4a","configurations":{"android":"1:705460445314:android:ceac21bb06b7a9f07b949b","ios":"1:705460445314:ios:54d64cb7592954327b949b","macos":"1:705460445314:ios:da11cbca5d1f6bc27b949b"}}}}}} \ No newline at end of file +{ + "flutter": { + "platforms": { + "android": { + "default": { + "projectId": "tetraq-32a4a", + "appId": "1:705460445314:android:ceac21bb06b7a9f07b949b", + "fileOutput": "android/app/google-services.json" + } + }, + "ios": { + "default": { + "projectId": "tetraq-32a4a", + "appId": "1:705460445314:ios:54d64cb7592954327b949b", + "uploadDebugSymbols": false, + "fileOutput": "ios/Runner/GoogleService-Info.plist" + } + }, + "macos": { + "default": { + "projectId": "tetraq-32a4a", + "appId": "1:705460445314:ios:da11cbca5d1f6bc27b949b", + "uploadDebugSymbols": false, + "fileOutput": "macos/Runner/GoogleService-Info.plist" + } + }, + "dart": { + "lib/firebase_options.dart": { + "projectId": "tetraq-32a4a", + "configurations": { + "android": "1:705460445314:android:ceac21bb06b7a9f07b949b", + "ios": "1:705460445314:ios:54d64cb7592954327b949b", + "macos": "1:705460445314:ios:da11cbca5d1f6bc27b949b" + } + } + } + } + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} diff --git a/ios/.DS_Store b/ios/.DS_Store index 1ade408..1713241 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 c7e6504..d846e34 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4867B86862DC650EC26D5F9C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 52CB81B72F635109004C3F43 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 5C30E1EF56D9EC1CAEADBE23 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -146,6 +147,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 52CB81B72F635109004C3F43 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -473,6 +475,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 2BX6QRR7GG; ENABLE_BITCODE = NO; @@ -485,6 +490,7 @@ MARKETING_VERSION = 1.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -658,6 +664,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 2BX6QRR7GG; ENABLE_BITCODE = NO; @@ -670,6 +679,7 @@ MARKETING_VERSION = 1.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -683,6 +693,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 2BX6QRR7GG; ENABLE_BITCODE = NO; @@ -695,6 +708,7 @@ MARKETING_VERSION = 1.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 2401443..49b472a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,6 +22,19 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.sanza.tetraq + CFBundleURLSchemes + + tetraq + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..636733d --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + applinks:tetraq-32a4a.web.app + + + diff --git a/lib/logic/game_controller.dart b/lib/logic/game_controller.dart index 7300283..5116b18 100644 --- a/lib/logic/game_controller.dart +++ b/lib/logic/game_controller.dart @@ -50,6 +50,12 @@ class GameController extends ChangeNotifier { bool opponentWantsRematch = false; int lastMatchXP = 0; + // --- VARIABILI PER IL LEVEL UP --- + bool hasLeveledUp = false; + int newlyReachedLevel = 1; + List unlockedFeatures = []; + // --------------------------------- + bool isSetupPhase = true; bool myJokerPlaced = false; bool oppJokerPlaced = false; @@ -81,6 +87,10 @@ class GameController extends ChangeNotifier { _hasSavedResult = false; lastMatchXP = 0; + // Reset Level Up vars + hasLeveledUp = false; + unlockedFeatures.clear(); + myReaction = null; opponentReaction = null; _lastOpponentReactionTime = null; @@ -503,6 +513,22 @@ class GameController extends ChangeNotifier { } } + // --- LOGICA LEVEL UP E SBLOCCHI --- + List _getUnlocks(int oldLevel, int newLevel) { + List unlocks = []; + for(int i = oldLevel + 1; i <= newLevel; i++) { + if (i == 3) unlocks.add("Tema: Legno & Fiammiferi"); + if (i == 5) unlocks.add("Tema: Quaderno (Doodle)"); + if (i == 7) unlocks.add("Tema: Cyberpunk"); + if (i == 10) { + unlocks.add("Tema: 8-Bit Arcade"); + unlocks.add("Forma Arena: Caos"); + } + if (i == 15) unlocks.add("Tema: Grimorio"); + } + return unlocks; + } + void _saveMatchResult() { if (_hasSavedResult) return; _hasSavedResult = true; @@ -512,6 +538,8 @@ class GameController extends ChangeNotifier { String myRealName = StorageService.instance.playerName; if (myRealName.isEmpty) myRealName = "IO"; + int oldLevel = StorageService.instance.playerLevel; // Salviamo il vecchio livello + if (isOnline) { bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed; calculatedXP = isWin ? 20 : (isDraw ? 5 : 2); @@ -520,7 +548,7 @@ class GameController extends ChangeNotifier { int oppScore = isHost ? board.scoreBlue : board.scoreRed; StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: oppName, myScore: myScore, oppScore: oppScore, isOnline: true); - if (isWin) StorageService.instance.updateQuestProgress(0, 1); // Missione: Vinci Online + if (isWin) StorageService.instance.updateQuestProgress(0, 1); } else if (isVsCPU) { int myScore = board.scoreRed; int cpuScore = board.scoreBlue; @@ -529,7 +557,7 @@ class GameController extends ChangeNotifier { if (isWin) { StorageService.instance.addWin(); - StorageService.instance.updateQuestProgress(1, 1); // Missione: Vinci vs CPU + StorageService.instance.updateQuestProgress(1, 1); } else if (cpuScore > myScore) { StorageService.instance.addLoss(); } @@ -539,12 +567,22 @@ class GameController extends ChangeNotifier { StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: "Ospite (Locale)", myScore: board.scoreRed, oppScore: board.scoreBlue, isOnline: false); } - // Se si sta giocando in una forma speciale (non classica) if (board.shape != ArenaShape.classic) { - StorageService.instance.updateQuestProgress(2, 1); // Missione: Usa forme speciali + StorageService.instance.updateQuestProgress(2, 1); } - lastMatchXP = calculatedXP; StorageService.instance.addXP(calculatedXP); notifyListeners(); + lastMatchXP = calculatedXP; + StorageService.instance.addXP(calculatedXP); + + // --- CONTROLLO LEVEL UP DOPO AVER DATO GLI XP --- + int newLevel = StorageService.instance.playerLevel; + if (newLevel > oldLevel) { + hasLeveledUp = true; + newlyReachedLevel = newLevel; + unlockedFeatures = _getUnlocks(oldLevel, newLevel); + } + + notifyListeners(); } void increaseLevelAndRestart() { diff --git a/lib/services/multiplayer_service.dart b/lib/services/multiplayer_service.dart index bb79d26..5656c80 100644 --- a/lib/services/multiplayer_service.dart +++ b/lib/services/multiplayer_service.dart @@ -63,7 +63,10 @@ class MultiplayerService { } void shareInviteLink(String roomCode) { - String message = "Ehi! Giochiamo a TetraQ? ๐ŸŽฎ\nCopia questo intero messaggio e apri l'app per entrare direttamente, oppure inserisci manualmente il codice: $roomCode"; + String message = "Ehi! Giochiamo a TetraQ? ๐ŸŽฎ\n\n" + "Clicca su questo link per entrare direttamente in stanza:\n" + "tetraq://join?code=$roomCode\n\n" + "Oppure apri l'app e inserisci manualmente il codice: $roomCode"; Share.share(message); } diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 07b42c1..11d5916 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -23,6 +23,8 @@ import 'history_screen.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:tetraq/l10n/app_localizations.dart'; import '../admin/admin_screen.dart'; +import 'dart:async'; +import 'package:app_links/app_links.dart'; TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { if (themeType == AppThemeType.doodle) { @@ -391,20 +393,25 @@ class _HomeScreenState extends State with WidgetsBindingObserver { int _debugTapCount = 0; + late AppLinks _appLinks; + StreamSubscription? _linkSubscription; + @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { _checkPlayerName(); - StorageService.instance.syncLeaderboard(); // <--- AGGIUNTO: Segna l'ultimo accesso ORA! + StorageService.instance.syncLeaderboard(); }); _checkClipboardForInvite(); + _initDeepLinks(); // <--- AGGIUNGI QUESTO } @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _linkSubscription?.cancel(); // <--- AGGIUNGI QUESTO super.dispose(); } @@ -534,6 +541,44 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } + // --- LOGICA DEEP LINKS --- + Future _initDeepLinks() async { + _appLinks = AppLinks(); + + // 1. Controlla se l'app รจ stata aperta DA CHIUSA tramite un link + try { + final initialUri = await _appLinks.getInitialLink(); // <--- ECCO LA PAROLA CORRETTA! + if (initialUri != null) { + _handleDeepLink(initialUri); + } + } catch (e) { + debugPrint("Errore lettura link iniziale: $e"); + } + + // 2. Rimane in ascolto se l'app era IN BACKGROUND + _linkSubscription = _appLinks.uriLinkStream.listen((uri) { + _handleDeepLink(uri); + }, onError: (err) { + debugPrint("Errore stream link: $err"); + }); + } + + void _handleDeepLink(Uri uri) { + debugPrint("Link ricevuto: $uri"); + if (uri.scheme == 'tetraq' && uri.host == 'join') { + String? code = uri.queryParameters['code']; + if (code != null && code.length == 5) { + // Usa un piccolo delay per assicurarsi che l'app sia pronta + Future.delayed(const Duration(milliseconds: 500), () { + if (mounted) { + _promptJoinRoom(code.toUpperCase()); + } + }); + } + } + } + // ------------------------- + Future _checkClipboardForInvite() async { try { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..829eda8 --- /dev/null +++ b/public/404.html @@ -0,0 +1,33 @@ + + + + + + Page Not Found + + + + +
+

404

+

Page Not Found

+

The specified file was not found on this website. Please check the URL for mistakes and try again.

+

Why am I seeing this?

+

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

+
+ + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..4237f9e --- /dev/null +++ b/public/index.html @@ -0,0 +1,89 @@ + + + + + + Welcome to Firebase Hosting + + + + + + + + + + + + + + + + + + + +
+

Welcome

+

Firebase Hosting Setup Complete

+

You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!

+ Open Hosting Documentation +
+

Firebase SDK Loading…

+ + + + diff --git a/tetraq_website/.well-known/apple-app-site-association b/tetraq_website/.well-known/apple-app-site-association new file mode 100644 index 0000000..5694ce1 --- /dev/null +++ b/tetraq_website/.well-known/apple-app-site-association @@ -0,0 +1,11 @@ +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "2BX6QRR7GG.com.sanza.tetraq", + "paths": [ "/join" ] + } + ] + } +} diff --git a/tetraq_website/.well-known/assetlinks.json b/tetraq_website/.well-known/assetlinks.json new file mode 100644 index 0000000..8b192f7 --- /dev/null +++ b/tetraq_website/.well-known/assetlinks.json @@ -0,0 +1,8 @@ +[{ + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "com.amastra.tetraq", + "sha256_cert_fingerprints": ["DA_INSERIRE_IN_FUTURO"] + } +}]