Compare commits

...

21 commits

Author SHA1 Message Date
3c28c1f0c2 Auto-sync: 20260314_190000 2026-03-14 19:00:00 +01:00
bbcd80a6a9 Auto-sync: 20260314_180000 2026-03-14 18:00:00 +01:00
e1861031a8 Auto-sync: 20260314_000001 2026-03-14 00:00:01 +01:00
321810a6b4 Auto-sync: 20260313_230001 2026-03-13 23:00:01 +01:00
518cb22ec4 Auto-sync: 20260313_220000 2026-03-13 22:00:00 +01:00
da52ad9d80 Auto-sync: 20260312_213211 2026-03-12 21:32:11 +01:00
7c7bf0ef2d Auto-sync: 20260312_210001 2026-03-12 21:00:08 +01:00
99ae270818 Auto-sync: 20260312_150000 2026-03-12 15:00:00 +01:00
a84c0b51b0 Auto-sync: 20260312_141056 2026-03-12 14:10:56 +01:00
384a9d7bd7 Auto-sync: 20260312_140000 2026-03-12 14:00:01 +01:00
50f67320a5 Auto-sync: 20260311_230000 2026-03-11 23:00:01 +01:00
5aa831f750 Auto-sync: 20260311_220000 2026-03-11 22:00:01 +01:00
37e59d5652 Auto-sync: 20260305_200000 2026-03-05 20:00:01 +01:00
18b0965aae Auto-sync: 20260305_160000 2026-03-05 16:00:01 +01:00
b2b94a5e72 Auto-sync: 20260305_150000 2026-03-05 15:00:00 +01:00
434ea96444 Auto-sync: 20260304_222820 2026-03-04 22:28:20 +01:00
a711b3dec2 Auto-sync: 20260304_220742 2026-03-04 22:07:43 +01:00
3acfb76fac Auto-sync: 20260304_220000 2026-03-04 22:00:00 +01:00
c83cc6b9ae Auto-sync: 20260304_210000 2026-03-04 21:00:00 +01:00
37d638816a Auto-sync: 20260304_190000 2026-03-04 19:00:00 +01:00
a3a30ebf1e Auto-sync: 20260304_180001 2026-03-04 18:00:01 +01:00
76 changed files with 4282 additions and 1468 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -0,0 +1,2 @@
index.html,1773344753424,0d5d4b835a7d632ad11d249230a15561286f2bfcd1da8305c6fb294d37e5da09
404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3

5
.firebaserc Normal file
View file

@ -0,0 +1,5 @@
{
"projects": {
"default": "tetraq-32a4a"
}
}

View file

@ -8,8 +8,19 @@ plugins {
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
// Aggiungiamo esplicitamente gli import richiesti da Kotlin
import java.io.FileInputStream
import java.util.Properties
// Carichiamo il file con le password
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android { android {
namespace = "com.sanza.tetraq" namespace = "com.amastra.tetraq"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
@ -18,13 +29,16 @@ android {
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { // Sintassi aggiornata come richiesto dal compilatore Kotlin
jvmTarget = JavaVersion.VERSION_17.toString() kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.sanza.tetraq" applicationId = "com.amastra.tetraq"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
@ -33,11 +47,24 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
// Aggiunto il blocco per la firma in formato Kotlin DSL
signingConfigs {
create("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
val storeFileString = keystoreProperties.getProperty("storeFile")
if (storeFileString != null) {
storeFile = file(storeFileString)
}
storePassword = keystoreProperties.getProperty("storePassword")
}
}
buildTypes { buildTypes {
release { getByName("release") {
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Ora usiamo la chiave di release appena creata
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("release")
} }
} }
} }

View file

@ -5,6 +5,25 @@
"storage_bucket": "tetraq-32a4a.firebasestorage.app" "storage_bucket": "tetraq-32a4a.firebasestorage.app"
}, },
"client": [ "client": [
{
"client_info": {
"mobilesdk_app_id": "1:705460445314:android:ceac21bb06b7a9f07b949b",
"android_client_info": {
"package_name": "com.amastra.tetraq"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBsXO595xVITDPrRnXrW8HPQLOe7Rz4Gg4"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{ {
"client_info": { "client_info": {
"mobilesdk_app_id": "1:705460445314:android:4d35fef29cfd63727b949b", "mobilesdk_app_id": "1:705460445314:android:4d35fef29cfd63727b949b",

View file

@ -1,4 +1,4 @@
package com.sanza.tetraq package com.amastra.tetraq
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

BIN
assets/.DS_Store vendored

Binary file not shown.

BIN
assets/audio/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/images/egizi_bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
assets/images/music_bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -1 +1,47 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"tetraq-32a4a","appId":"1:705460445314:android:4d35fef29cfd63727b949b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"tetraq-32a4a","appId":"1:705460445314:ios:da11cbca5d1f6bc27b949b","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:4d35fef29cfd63727b949b","ios":"1:705460445314:ios:da11cbca5d1f6bc27b949b","macos":"1:705460445314:ios:da11cbca5d1f6bc27b949b"}}}}}} {
"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/**"
]
}
}

27
genera_lingue.dart Normal file
View file

@ -0,0 +1,27 @@
import 'dart:io';
import 'dart:convert';
void main() async {
final dir = Directory('lib/l10n');
if (!await dir.exists()) await dir.create(recursive: true);
final Map<String, Map<String, String>> translations = {
'it': {"appTitle": "TetraQ", "welcomeTitle": "BENVENUTO IN TETRAQ!", "nameHint": "NOME", "saveAndPlay": "SALVA E GIOCA", "onlineTitle": "ONLINE", "onlineSub": "Sfida il mondo", "cpuTitle": "VS CPU", "cpuSub": "Allenati con l'IA", "localTitle": "LOCALE", "localSub": "Stesso schermo", "leaderboardTitle": "CLASSIFICA", "questsTitle": "SFIDE", "themesTitle": "TEMI", "tutorialTitle": "TUTORIAL", "startGame": "AVVIA PARTITA", "createMatch": "CREA PARTITA", "joinMatch": "UNISCITI", "gameOver": "FINE PARTITA", "mainMenu": "TORNA AL MENU", "exit": "ESCI"},
'en': {"appTitle": "TetraQ", "welcomeTitle": "WELCOME TO TETRAQ!", "nameHint": "NAME", "saveAndPlay": "SAVE & PLAY", "onlineTitle": "ONLINE", "onlineSub": "Challenge the world", "cpuTitle": "VS CPU", "cpuSub": "Train with AI", "localTitle": "LOCAL", "localSub": "Same screen", "leaderboardTitle": "LEADERBOARD", "questsTitle": "QUESTS", "themesTitle": "THEMES", "tutorialTitle": "TUTORIAL", "startGame": "START GAME", "createMatch": "CREATE MATCH", "joinMatch": "JOIN", "gameOver": "GAME OVER", "mainMenu": "BACK TO MENU", "exit": "EXIT"},
'es': {"appTitle": "TetraQ", "welcomeTitle": "¡BIENVENIDO A TETRAQ!", "nameHint": "NOMBRE", "saveAndPlay": "GUARDAR Y JUGAR", "onlineTitle": "ONLINE", "onlineSub": "Desafía al mundo", "cpuTitle": "VS CPU", "cpuSub": "Entrena con IA", "localTitle": "LOCAL", "localSub": "Misma pantalla", "leaderboardTitle": "RANKING", "questsTitle": "MISIONES", "themesTitle": "TEMAS", "tutorialTitle": "TUTORIAL", "startGame": "INICIAR JUEGO", "createMatch": "CREAR PARTIDA", "joinMatch": "UNIRSE", "gameOver": "FIN DEL JUEGO", "mainMenu": "VOLVER AL MENÚ", "exit": "SALIR"},
'fr': {"appTitle": "TetraQ", "welcomeTitle": "BIENVENUE DANS TETRAQ !", "nameHint": "NOM", "saveAndPlay": "SAUVEGARDER ET JOUER", "onlineTitle": "EN LIGNE", "onlineSub": "Défiez le monde", "cpuTitle": "VS CPU", "cpuSub": "Entraînez avec l'IA", "localTitle": "LOCAL", "localSub": "Même écran", "leaderboardTitle": "CLASSEMENT", "questsTitle": "QUÊTES", "themesTitle": "THÈMES", "tutorialTitle": "TUTORIEL", "startGame": "JOUER", "createMatch": "CRÉER UN MATCH", "joinMatch": "REJOINDRE", "gameOver": "FIN DE PARTIE", "mainMenu": "RETOUR AU MENU", "exit": "QUITTER"},
'de': {"appTitle": "TetraQ", "welcomeTitle": "WILLKOMMEN BEI TETRAQ!", "nameHint": "NAME", "saveAndPlay": "SPEICHERN & SPIELEN", "onlineTitle": "ONLINE", "onlineSub": "Fordere die Welt heraus", "cpuTitle": "VS CPU", "cpuSub": "Trainiere mit KI", "localTitle": "LOKAL", "localSub": "Gleicher Bildschirm", "leaderboardTitle": "RANGLISTE", "questsTitle": "MISSIONEN", "themesTitle": "THEMEN", "tutorialTitle": "TUTORIAL", "startGame": "SPIEL STARTEN", "createMatch": "SPIEL ERSTELLEN", "joinMatch": "BEITRETEN", "gameOver": "SPIELENDE", "mainMenu": "ZURÜCK ZUM MENÜ", "exit": "BEENDEN"},
'pt': {"appTitle": "TetraQ", "welcomeTitle": "BEM-VINDO AO TETRAQ!", "nameHint": "NOME", "saveAndPlay": "SALVAR E JOGAR", "onlineTitle": "ONLINE", "onlineSub": "Desafie o mundo", "cpuTitle": "VS CPU", "cpuSub": "Treine com a IA", "localTitle": "LOCAL", "localSub": "Mesma tela", "leaderboardTitle": "CLASSIFICAÇÃO", "questsTitle": "DESAFIOS", "themesTitle": "TEMAS", "tutorialTitle": "TUTORIAL", "startGame": "INICIAR JOGO", "createMatch": "CRIAR PARTIDA", "joinMatch": "ENTRAR", "gameOver": "FIM DE JOGO", "mainMenu": "VOLTAR AO MENU", "exit": "SAIR"},
'ru': {"appTitle": "TetraQ", "welcomeTitle": "ДОБРО ПОЖАЛОВАТЬ В TETRAQ!", "nameHint": "ИМЯ", "saveAndPlay": "СОХРАНИТЬ И ИГРАТЬ", "onlineTitle": "ОНЛАЙН", "onlineSub": "Брось вызов миру", "cpuTitle": "VS ИИ", "cpuSub": "Тренировка с ИИ", "localTitle": "ЛОКАЛЬНО", "localSub": "Один экран", "leaderboardTitle": "РЕЙТИНГ", "questsTitle": "ЗАДАНИЯ", "themesTitle": "ТЕМЫ", "tutorialTitle": "ОБУЧЕНИЕ", "startGame": "НАЧАТЬ ИГРУ", "createMatch": "СОЗДАТЬ ИГРУ", "joinMatch": "ПРИСОЕДИНИТЬСЯ", "gameOver": "ИГРА ОКОНЧЕНА", "mainMenu": "В ГЛАВНОЕ МЕНЮ", "exit": "ВЫХОД"},
'zh': {"appTitle": "TetraQ", "welcomeTitle": "欢迎来到 TETRAQ", "nameHint": "名字", "saveAndPlay": "保存并开始", "onlineTitle": "在线匹配", "onlineSub": "挑战世界", "cpuTitle": "人机对战", "cpuSub": "与AI训练", "localTitle": "本地游戏", "localSub": "同屏对战", "leaderboardTitle": "排行榜", "questsTitle": "任务", "themesTitle": "主题", "tutorialTitle": "教程", "startGame": "开始游戏", "createMatch": "创建比赛", "joinMatch": "加入", "gameOver": "游戏结束", "mainMenu": "返回主菜单", "exit": "退出"}
};
for (var lang in translations.keys) {
final file = File('lib/l10n/app_$lang.arb');
final Map<String, dynamic> finalContent = {"@@locale": lang, ...translations[lang]!};
await file.writeAsString(JsonEncoder.withIndent(' ').convert(finalContent));
}
// Crea anche il file di configurazione
await File('l10n.yaml').writeAsString("arb-dir: lib/l10n\ntemplate-arb-file: app_it.arb\noutput-localization-file: app_localizations.dart\n");
}

BIN
ios/.DS_Store vendored

Binary file not shown.

View file

@ -1190,6 +1190,10 @@ PODS:
- abseil/xcprivacy (1.20240722.0) - abseil/xcprivacy (1.20240722.0)
- app_links (7.0.0): - app_links (7.0.0):
- Flutter - Flutter
- AppCheckCore (11.2.0):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- audioplayers_darwin (0.0.1): - audioplayers_darwin (0.0.1):
- Flutter - Flutter
- BoringSSL-GRPC (0.0.37): - BoringSSL-GRPC (0.0.37):
@ -1199,32 +1203,60 @@ PODS:
- BoringSSL-GRPC/Interface (= 0.0.37) - BoringSSL-GRPC/Interface (= 0.0.37)
- BoringSSL-GRPC/Interface (0.0.37) - BoringSSL-GRPC/Interface (0.0.37)
- cloud_firestore (6.1.2): - cloud_firestore (6.1.2):
- Firebase/Firestore (= 12.8.0) - Firebase/Firestore (= 12.9.0)
- firebase_core - firebase_core
- Flutter - Flutter
- Firebase/CoreOnly (12.8.0): - Firebase/Auth (12.9.0):
- FirebaseCore (~> 12.8.0)
- Firebase/Firestore (12.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseFirestore (~> 12.8.0) - FirebaseAuth (~> 12.9.0)
- firebase_core (4.4.0): - Firebase/CoreOnly (12.9.0):
- Firebase/CoreOnly (= 12.8.0) - FirebaseCore (~> 12.9.0)
- Firebase/Firestore (12.9.0):
- Firebase/CoreOnly
- FirebaseFirestore (~> 12.9.0)
- firebase_app_check (0.4.1-5):
- Firebase/CoreOnly (~> 12.9.0)
- firebase_core
- FirebaseAppCheck (~> 12.9.0)
- Flutter - Flutter
- FirebaseAppCheckInterop (12.8.0) - firebase_auth (6.1.4):
- FirebaseCore (12.8.0): - Firebase/Auth (= 12.9.0)
- FirebaseCoreInternal (~> 12.8.0) - firebase_core
- Flutter
- firebase_core (4.5.0):
- Firebase/CoreOnly (= 12.9.0)
- Flutter
- FirebaseAppCheck (12.9.0):
- AppCheckCore (~> 11.0)
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- FirebaseAppCheckInterop (12.9.0)
- FirebaseAuth (12.9.0):
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseAuthInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.9.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1)
- GTMSessionFetcher/Core (< 6.0, >= 3.4)
- RecaptchaInterop (~> 101.0)
- FirebaseAuthInterop (12.9.0)
- FirebaseCore (12.9.0):
- FirebaseCoreInternal (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1) - GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreExtension (12.8.0): - FirebaseCoreExtension (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreInternal (12.8.0): - FirebaseCoreInternal (12.9.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)" - "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseFirestore (12.8.0): - FirebaseFirestore (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.8.0) - FirebaseCoreExtension (~> 12.9.0)
- FirebaseFirestoreInternal (~> 12.8.0) - FirebaseFirestoreInternal (~> 12.9.0)
- FirebaseSharedSwift (~> 12.8.0) - FirebaseSharedSwift (~> 12.9.0)
- FirebaseFirestoreInternal (12.8.0): - FirebaseFirestoreInternal (12.9.0):
- abseil/algorithm (~> 1.20240722.0) - abseil/algorithm (~> 1.20240722.0)
- abseil/base (~> 1.20240722.0) - abseil/base (~> 1.20240722.0)
- abseil/container/flat_hash_map (~> 1.20240722.0) - abseil/container/flat_hash_map (~> 1.20240722.0)
@ -1233,22 +1265,38 @@ PODS:
- abseil/strings/strings (~> 1.20240722.0) - abseil/strings/strings (~> 1.20240722.0)
- abseil/time (~> 1.20240722.0) - abseil/time (~> 1.20240722.0)
- abseil/types (~> 1.20240722.0) - abseil/types (~> 1.20240722.0)
- FirebaseAppCheckInterop (~> 12.8.0) - FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- "gRPC-C++ (~> 1.69.0)" - "gRPC-C++ (~> 1.69.0)"
- gRPC-Core (~> 1.69.0) - gRPC-Core (~> 1.69.0)
- leveldb-library (~> 1.22) - leveldb-library (~> 1.22)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseSharedSwift (12.8.0) - FirebaseSharedSwift (12.9.0)
- Flutter (1.0.0) - Flutter (1.0.0)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0): - GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0): - GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)": - "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0) - GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- "gRPC-C++ (1.69.0)": - "gRPC-C++ (1.69.0)":
- "gRPC-C++/Implementation (= 1.69.0)" - "gRPC-C++/Implementation (= 1.69.0)"
- "gRPC-C++/Interface (= 1.69.0)" - "gRPC-C++/Interface (= 1.69.0)"
@ -1341,12 +1389,15 @@ PODS:
- gRPC-Core/Privacy (= 1.69.0) - gRPC-Core/Privacy (= 1.69.0)
- gRPC-Core/Interface (1.69.0) - gRPC-Core/Interface (1.69.0)
- gRPC-Core/Privacy (1.69.0) - gRPC-Core/Privacy (1.69.0)
- GTMSessionFetcher/Core (5.1.0)
- leveldb-library (1.22.6) - leveldb-library (1.22.6)
- nanopb (3.30910.0): - nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0) - nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0) - nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0) - nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0) - nanopb/encode (3.30910.0)
- PromisesObjC (2.4.0)
- RecaptchaInterop (101.0.0)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@ -1357,6 +1408,8 @@ DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`) - app_links (from `.symlinks/plugins/app_links/ios`)
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
- firebase_app_check (from `.symlinks/plugins/firebase_app_check/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
@ -1365,9 +1418,13 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- abseil - abseil
- AppCheckCore
- BoringSSL-GRPC - BoringSSL-GRPC
- Firebase - Firebase
- FirebaseAppCheck
- FirebaseAppCheckInterop - FirebaseAppCheckInterop
- FirebaseAuth
- FirebaseAuthInterop
- FirebaseCore - FirebaseCore
- FirebaseCoreExtension - FirebaseCoreExtension
- FirebaseCoreInternal - FirebaseCoreInternal
@ -1377,8 +1434,11 @@ SPEC REPOS:
- GoogleUtilities - GoogleUtilities
- "gRPC-C++" - "gRPC-C++"
- gRPC-Core - gRPC-Core
- GTMSessionFetcher
- leveldb-library - leveldb-library
- nanopb - nanopb
- PromisesObjC
- RecaptchaInterop
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: app_links:
@ -1387,6 +1447,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/audioplayers_darwin/ios" :path: ".symlinks/plugins/audioplayers_darwin/ios"
cloud_firestore: cloud_firestore:
:path: ".symlinks/plugins/cloud_firestore/ios" :path: ".symlinks/plugins/cloud_firestore/ios"
firebase_app_check:
:path: ".symlinks/plugins/firebase_app_check/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core: firebase_core:
:path: ".symlinks/plugins/firebase_core/ios" :path: ".symlinks/plugins/firebase_core/ios"
Flutter: Flutter:
@ -1399,24 +1463,33 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
abseil: a05cc83bf02079535e17169a73c5be5ba47f714b abseil: a05cc83bf02079535e17169a73c5be5ba47f714b
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8 app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab
BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508
cloud_firestore: 4bd00c3464706d9e09dabac0bb8e9610456109f5 cloud_firestore: 81f6c428ecee874dc3808afe0e0c48a87beb5bdf
Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398 firebase_app_check: 33f1df6830ec8ebadee0db0120956c44a65c7213
FirebaseAppCheckInterop: ba3dc604a89815379e61ec2365101608d365cf7d firebase_auth: fecf9fe293464b52063f5f2a7110e63ff2ab3403
FirebaseCore: 0dbad74bda10b8fb9ca34ad8f375fb9dd3ebef7c firebase_core: afac1aac13c931e0401c7e74ed1276112030efab
FirebaseCoreExtension: 6605938d51f765d8b18bfcafd2085276a252bee2 FirebaseAppCheck: 94dae4d9bb682bdef85a778b0c1024a4613f1e89
FirebaseCoreInternal: fe5fa466aeb314787093a7dce9f0beeaad5a2a21 FirebaseAppCheckInterop: 4bade10286cc977e516f75d2d8312cbdfa534789
FirebaseFirestore: 67f23000ca238ccbab79127ed59636a9a2689e74 FirebaseAuth: 3a39f6436c21ebfd7919b698228b4f89ff94c23b
FirebaseFirestoreInternal: a0e7382af3d208898dcd1d4d52d8a7870632e881 FirebaseAuthInterop: f8f6ff72dc24621906497fbe5cf3c42ee815e59c
FirebaseSharedSwift: f57ed48f4542b2d7eb4738f4f23ba443f78b3780 FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
FirebaseFirestore: d8b76ca1feb4ca0b0f078c45f7d1bd8014a49ef1
FirebaseFirestoreInternal: 02341a9ba87f6309227b04685022a5e16307bbf7
FirebaseSharedSwift: 9d2fa84a46676302b89dbd5e6e62bce2fe376909
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
"gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8
gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb

View file

@ -52,6 +52,7 @@
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4867B86862DC650EC26D5F9C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -146,6 +147,7 @@
97C146F01CF9000F007C117D /* Runner */ = { 97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
52CB81B72F635109004C3F43 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@ -473,6 +475,9 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 2BX6QRR7GG; DEVELOPMENT_TEAM = 2BX6QRR7GG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -485,6 +490,7 @@
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -500,7 +506,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -518,7 +524,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -534,7 +540,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.amastra.tetraq.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -658,6 +664,9 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 2BX6QRR7GG; DEVELOPMENT_TEAM = 2BX6QRR7GG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -670,6 +679,7 @@
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -683,6 +693,9 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = 2BX6QRR7GG; DEVELOPMENT_TEAM = 2BX6QRR7GG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -695,6 +708,7 @@
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq; PRODUCT_BUNDLE_IDENTIFIER = com.sanza.tetraq;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";

View file

@ -9,7 +9,7 @@
<key>PLIST_VERSION</key> <key>PLIST_VERSION</key>
<string>1</string> <string>1</string>
<key>BUNDLE_ID</key> <key>BUNDLE_ID</key>
<string>com.sanza.tetraq</string> <string>com.amastra.tetraq</string>
<key>PROJECT_ID</key> <key>PROJECT_ID</key>
<string>tetraq-32a4a</string> <string>tetraq-32a4a</string>
<key>STORAGE_BUCKET</key> <key>STORAGE_BUCKET</key>
@ -25,6 +25,6 @@
<key>IS_SIGNIN_ENABLED</key> <key>IS_SIGNIN_ENABLED</key>
<true></true> <true></true>
<key>GOOGLE_APP_ID</key> <key>GOOGLE_APP_ID</key>
<string>1:705460445314:ios:da11cbca5d1f6bc27b949b</string> <string>1:705460445314:ios:54d64cb7592954327b949b</string>
</dict> </dict>
</plist> </plist>

View file

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -20,10 +22,25 @@
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.sanza.tetraq</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tetraq</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
@ -41,9 +58,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:tetraq-32a4a.web.app</string>
</array>
</dict>
</plist>

4
l10n.yaml Normal file
View file

@ -0,0 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_it.arb
output-localization-file: app_localizations.dart
output-dir: lib/l10n

BIN
lib/.DS_Store vendored

Binary file not shown.

View file

@ -5,7 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
enum AppThemeType { minimal, doodle, cyberpunk, wood, arcade, grimorio } enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio, music } // <-- Aggiunto 'music'
class ThemeColors { class ThemeColors {
final Color background; final Color background;
@ -24,11 +24,6 @@ class ThemeColors {
} }
class AppColors { class AppColors {
static const ThemeColors minimal = ThemeColors(
background: Color(0xFFF5F7FA), gridLine: Color(0xFFCFD8DC),
playerRed: Color(0xFFE53935), playerBlue: Color(0xFF1E88E5), text: Color(0xFF263238),
);
static const ThemeColors doodle = ThemeColors( static const ThemeColors doodle = ThemeColors(
background: Color(0xFFFFF9E6), gridLine: Color(0xFFB0BEC5), background: Color(0xFFFFF9E6), gridLine: Color(0xFFB0BEC5),
playerRed: Color(0xFFD32F2F), playerBlue: Color(0xFF1565C0), text: Color(0xFF37474F), playerRed: Color(0xFFD32F2F), playerBlue: Color(0xFF1565C0), text: Color(0xFF37474F),
@ -54,14 +49,23 @@ class AppColors {
playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0), playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0),
); );
// --- NUOVO TEMA MUSICA ---
static const ThemeColors music = ThemeColors(
background: Color(0xFF120B29), // Viola scuro (stile Synthwave)
gridLine: Color(0xFF6A1B9A), // Viola elettrico
playerRed: Color(0xFFFF2A6D), // Rosa acceso
playerBlue: Color(0xFF05D5FF), // Ciano
text: Color(0xFFE0E0E0),
);
static ThemeColors getTheme(AppThemeType type) { static ThemeColors getTheme(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return minimal;
case AppThemeType.doodle: return doodle; case AppThemeType.doodle: return doodle;
case AppThemeType.cyberpunk: return cyberpunk;
case AppThemeType.wood: return wood; case AppThemeType.wood: return wood;
case AppThemeType.cyberpunk: return cyberpunk;
case AppThemeType.arcade: return arcade; case AppThemeType.arcade: return arcade;
case AppThemeType.grimorio: return grimorio; case AppThemeType.grimorio: return grimorio;
case AppThemeType.music: return music;
} }
} }
} }
@ -69,65 +73,66 @@ class AppColors {
class ThemeIcons { class ThemeIcons {
static IconData gold(AppThemeType type) { static IconData gold(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return Icons.star_rounded;
case AppThemeType.doodle: return FontAwesomeIcons.star; case AppThemeType.doodle: return FontAwesomeIcons.star;
case AppThemeType.wood: return FontAwesomeIcons.gem; case AppThemeType.wood: return FontAwesomeIcons.gem;
case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip; case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip;
case AppThemeType.arcade: return FontAwesomeIcons.coins; case AppThemeType.arcade: return FontAwesomeIcons.coins;
case AppThemeType.grimorio: return FontAwesomeIcons.crown; case AppThemeType.grimorio: return FontAwesomeIcons.crown;
case AppThemeType.music: return FontAwesomeIcons.compactDisc; // CD/Vinile per i punti
} }
} }
static IconData bomb(AppThemeType type) { static IconData bomb(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return Icons.mood_bad_rounded;
case AppThemeType.doodle: return FontAwesomeIcons.virus; case AppThemeType.doodle: return FontAwesomeIcons.virus;
case AppThemeType.wood: return FontAwesomeIcons.fire; case AppThemeType.wood: return FontAwesomeIcons.fire;
case AppThemeType.cyberpunk: return FontAwesomeIcons.bug; case AppThemeType.cyberpunk: return FontAwesomeIcons.bug;
case AppThemeType.arcade: return FontAwesomeIcons.ghost; case AppThemeType.arcade: return FontAwesomeIcons.ghost;
case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard; case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard;
case AppThemeType.music: return FontAwesomeIcons.volumeXmark; // Muto/Errore per la bomba
} }
} }
static IconData swap(AppThemeType type) { static IconData swap(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return Icons.sync_rounded;
case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate; case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate;
case AppThemeType.wood: return FontAwesomeIcons.rightLeft; case AppThemeType.wood: return FontAwesomeIcons.rightLeft;
case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired; case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired;
case AppThemeType.arcade: return FontAwesomeIcons.shuffle; case AppThemeType.arcade: return FontAwesomeIcons.shuffle;
case AppThemeType.grimorio: return FontAwesomeIcons.hurricane; case AppThemeType.grimorio: return FontAwesomeIcons.hurricane;
case AppThemeType.music: return FontAwesomeIcons.sliders; // Fader da DJ
} }
} }
static IconData joker(AppThemeType type) { static IconData joker(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return Icons.sentiment_satisfied_alt;
case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam; case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam;
case AppThemeType.wood: return FontAwesomeIcons.key; case AppThemeType.wood: return FontAwesomeIcons.key;
case AppThemeType.cyberpunk: return FontAwesomeIcons.robot; case AppThemeType.cyberpunk: return FontAwesomeIcons.robot;
case AppThemeType.arcade: return FontAwesomeIcons.gamepad; case AppThemeType.arcade: return FontAwesomeIcons.gamepad;
case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater; case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater;
case AppThemeType.music: return FontAwesomeIcons.headphones; // Cuffie per il Jolly
} }
} }
static IconData block(AppThemeType type) { static IconData block(AppThemeType type) {
switch (type) { switch (type) {
case AppThemeType.minimal: return Icons.block;
case AppThemeType.doodle: return FontAwesomeIcons.squareXmark; case AppThemeType.doodle: return FontAwesomeIcons.squareXmark;
case AppThemeType.wood: return FontAwesomeIcons.ban; case AppThemeType.wood: return FontAwesomeIcons.ban;
case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved; case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved;
case AppThemeType.arcade: return FontAwesomeIcons.powerOff; case AppThemeType.arcade: return FontAwesomeIcons.powerOff;
case AppThemeType.grimorio: return FontAwesomeIcons.meteor; case AppThemeType.grimorio: return FontAwesomeIcons.meteor;
case AppThemeType.music: return FontAwesomeIcons.pause; // Pausa per il blocco vuoto
} }
} }
// --- NUOVE ICONE ---
static IconData ice(AppThemeType type) { static IconData ice(AppThemeType type) {
if (type == AppThemeType.music) return FontAwesomeIcons.music; // Nota musicale ghiacciata
return FontAwesomeIcons.snowflake; return FontAwesomeIcons.snowflake;
} }
static IconData multiplier(AppThemeType type) { static IconData multiplier(AppThemeType type) {
if (type == AppThemeType.music) return FontAwesomeIcons.forwardFast; // Fast Forward per il x2
return FontAwesomeIcons.bolt; return FontAwesomeIcons.bolt;
} }
} }

View file

@ -1,6 +1,11 @@
// ===========================================================================
// FILE: lib/core/theme_manager.dart
// ===========================================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'app_colors.dart'; import 'app_colors.dart';
import '../services/storage_service.dart'; import '../services/storage_service.dart';
import '../services/audio_service.dart'; // <-- NUOVO IMPORT PER LA MUSICA
class ThemeManager extends ChangeNotifier { class ThemeManager extends ChangeNotifier {
late AppThemeType _currentThemeType; late AppThemeType _currentThemeType;
@ -8,6 +13,9 @@ class ThemeManager extends ChangeNotifier {
ThemeManager() { ThemeManager() {
// Quando l'app parte, legge il tema dalla memoria! // Quando l'app parte, legge il tema dalla memoria!
_currentThemeType = AppThemeType.values[StorageService.instance.savedThemeIndex]; _currentThemeType = AppThemeType.values[StorageService.instance.savedThemeIndex];
// Fai partire subito la colonna sonora del tema salvato!
AudioService.instance.playBgm(_currentThemeType);
} }
AppThemeType get currentThemeType => _currentThemeType; AppThemeType get currentThemeType => _currentThemeType;
@ -16,6 +24,10 @@ class ThemeManager extends ChangeNotifier {
void setTheme(AppThemeType type) { void setTheme(AppThemeType type) {
_currentThemeType = type; _currentThemeType = type;
StorageService.instance.saveTheme(type); // Salva la scelta nel "disco fisso" StorageService.instance.saveTheme(type); // Salva la scelta nel "disco fisso"
// Cambia magicamente la canzone in sottofondo!
AudioService.instance.playBgm(type);
notifyListeners(); notifyListeners();
} }
} }

View file

@ -48,7 +48,7 @@ class DefaultFirebaseOptions {
static const FirebaseOptions android = FirebaseOptions( static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyBsXO595xVITDPrRnXrW8HPQLOe7Rz4Gg4', apiKey: 'AIzaSyBsXO595xVITDPrRnXrW8HPQLOe7Rz4Gg4',
appId: '1:705460445314:android:4d35fef29cfd63727b949b', appId: '1:705460445314:android:ceac21bb06b7a9f07b949b',
messagingSenderId: '705460445314', messagingSenderId: '705460445314',
projectId: 'tetraq-32a4a', projectId: 'tetraq-32a4a',
storageBucket: 'tetraq-32a4a.firebasestorage.app', storageBucket: 'tetraq-32a4a.firebasestorage.app',
@ -56,11 +56,11 @@ class DefaultFirebaseOptions {
static const FirebaseOptions ios = FirebaseOptions( static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyB77j18Jgeb9gBAEwp-uyOQvr4m-RJ_rAE', apiKey: 'AIzaSyB77j18Jgeb9gBAEwp-uyOQvr4m-RJ_rAE',
appId: '1:705460445314:ios:da11cbca5d1f6bc27b949b', appId: '1:705460445314:ios:54d64cb7592954327b949b',
messagingSenderId: '705460445314', messagingSenderId: '705460445314',
projectId: 'tetraq-32a4a', projectId: 'tetraq-32a4a',
storageBucket: 'tetraq-32a4a.firebasestorage.app', storageBucket: 'tetraq-32a4a.firebasestorage.app',
iosBundleId: 'com.sanza.tetraq', iosBundleId: 'com.amastra.tetraq',
); );
static const FirebaseOptions macos = FirebaseOptions( static const FirebaseOptions macos = FirebaseOptions(
@ -71,4 +71,5 @@ class DefaultFirebaseOptions {
storageBucket: 'tetraq-32a4a.firebasestorage.app', storageBucket: 'tetraq-32a4a.firebasestorage.app',
iosBundleId: 'com.sanza.tetraq', iosBundleId: 'com.sanza.tetraq',
); );
} }

23
lib/l10n/app_de.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "de",
"appTitle": "TetraQ",
"welcomeTitle": "WILLKOMMEN BEI TETRAQ!",
"nameHint": "NAME",
"saveAndPlay": "SPEICHERN & SPIELEN",
"onlineTitle": "ONLINE",
"onlineSub": "Fordere die Welt heraus",
"cpuTitle": "VS CPU",
"cpuSub": "Trainiere mit KI",
"localTitle": "LOKAL",
"localSub": "Gleicher Bildschirm",
"leaderboardTitle": "RANGLISTE",
"questsTitle": "MISSIONEN",
"themesTitle": "THEMEN",
"tutorialTitle": "TUTORIAL",
"startGame": "SPIEL STARTEN",
"createMatch": "SPIEL ERSTELLEN",
"joinMatch": "BEITRETEN",
"gameOver": "SPIELENDE",
"mainMenu": "ZURÜCK ZUM MENÜ",
"exit": "BEENDEN"
}

View file

@ -1,4 +1,23 @@
{ {
"@@locale": "en",
"appTitle": "TetraQ", "appTitle": "TetraQ",
"playLocal": "PASS & PLAY (Local)" "welcomeTitle": "WELCOME TO TETRAQ!",
"nameHint": "NAME",
"saveAndPlay": "SAVE & PLAY",
"onlineTitle": "ONLINE",
"onlineSub": "Challenge the world",
"cpuTitle": "VS CPU",
"cpuSub": "Train with AI",
"localTitle": "LOCAL",
"localSub": "Same screen",
"leaderboardTitle": "LEADERBOARD",
"questsTitle": "QUESTS",
"themesTitle": "THEMES",
"tutorialTitle": "TUTORIAL",
"startGame": "START GAME",
"createMatch": "CREATE MATCH",
"joinMatch": "JOIN",
"gameOver": "GAME OVER",
"mainMenu": "BACK TO MENU",
"exit": "EXIT"
} }

23
lib/l10n/app_es.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "es",
"appTitle": "TetraQ",
"welcomeTitle": "¡BIENVENIDO A TETRAQ!",
"nameHint": "NOMBRE",
"saveAndPlay": "GUARDAR Y JUGAR",
"onlineTitle": "ONLINE",
"onlineSub": "Desafía al mundo",
"cpuTitle": "VS CPU",
"cpuSub": "Entrena con IA",
"localTitle": "LOCAL",
"localSub": "Misma pantalla",
"leaderboardTitle": "RANKING",
"questsTitle": "MISIONES",
"themesTitle": "TEMAS",
"tutorialTitle": "TUTORIAL",
"startGame": "INICIAR JUEGO",
"createMatch": "CREAR PARTIDA",
"joinMatch": "UNIRSE",
"gameOver": "FIN DEL JUEGO",
"mainMenu": "VOLVER AL MENÚ",
"exit": "SALIR"
}

23
lib/l10n/app_fr.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "fr",
"appTitle": "TetraQ",
"welcomeTitle": "BIENVENUE DANS TETRAQ !",
"nameHint": "NOM",
"saveAndPlay": "SAUVEGARDER ET JOUER",
"onlineTitle": "EN LIGNE",
"onlineSub": "Défiez le monde",
"cpuTitle": "VS CPU",
"cpuSub": "Entraînez avec l'IA",
"localTitle": "LOCAL",
"localSub": "Même écran",
"leaderboardTitle": "CLASSEMENT",
"questsTitle": "QUÊTES",
"themesTitle": "THÈMES",
"tutorialTitle": "TUTORIEL",
"startGame": "JOUER",
"createMatch": "CRÉER UN MATCH",
"joinMatch": "REJOINDRE",
"gameOver": "FIN DE PARTIE",
"mainMenu": "RETOUR AU MENU",
"exit": "QUITTER"
}

View file

@ -1,4 +1,23 @@
{ {
"@@locale": "it",
"appTitle": "TetraQ", "appTitle": "TetraQ",
"playLocal": "PASS & PLAY (Locale)" "welcomeTitle": "BENVENUTO IN TETRAQ!",
"nameHint": "NOME",
"saveAndPlay": "SALVA E GIOCA",
"onlineTitle": "ONLINE",
"onlineSub": "Sfida il mondo",
"cpuTitle": "VS CPU",
"cpuSub": "Allenati con l'IA",
"localTitle": "LOCALE",
"localSub": "Stesso schermo",
"leaderboardTitle": "CLASSIFICA",
"questsTitle": "SFIDE",
"themesTitle": "TEMI",
"tutorialTitle": "TUTORIAL",
"startGame": "AVVIA PARTITA",
"createMatch": "CREA PARTITA",
"joinMatch": "UNISCITI",
"gameOver": "FINE PARTITA",
"mainMenu": "TORNA AL MENU",
"exit": "ESCI"
} }

View file

@ -0,0 +1,286 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
import 'app_localizations_es.dart';
import 'app_localizations_fr.dart';
import 'app_localizations_it.dart';
import 'app_localizations_pt.dart';
import 'app_localizations_ru.dart';
import 'app_localizations_zh.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('de'),
Locale('en'),
Locale('es'),
Locale('fr'),
Locale('it'),
Locale('pt'),
Locale('ru'),
Locale('zh'),
];
/// No description provided for @appTitle.
///
/// In it, this message translates to:
/// **'TetraQ'**
String get appTitle;
/// No description provided for @welcomeTitle.
///
/// In it, this message translates to:
/// **'BENVENUTO IN TETRAQ!'**
String get welcomeTitle;
/// No description provided for @nameHint.
///
/// In it, this message translates to:
/// **'NOME'**
String get nameHint;
/// No description provided for @saveAndPlay.
///
/// In it, this message translates to:
/// **'SALVA E GIOCA'**
String get saveAndPlay;
/// No description provided for @onlineTitle.
///
/// In it, this message translates to:
/// **'ONLINE'**
String get onlineTitle;
/// No description provided for @onlineSub.
///
/// In it, this message translates to:
/// **'Sfida il mondo'**
String get onlineSub;
/// No description provided for @cpuTitle.
///
/// In it, this message translates to:
/// **'VS CPU'**
String get cpuTitle;
/// No description provided for @cpuSub.
///
/// In it, this message translates to:
/// **'Allenati con l\'IA'**
String get cpuSub;
/// No description provided for @localTitle.
///
/// In it, this message translates to:
/// **'LOCALE'**
String get localTitle;
/// No description provided for @localSub.
///
/// In it, this message translates to:
/// **'Stesso schermo'**
String get localSub;
/// No description provided for @leaderboardTitle.
///
/// In it, this message translates to:
/// **'CLASSIFICA'**
String get leaderboardTitle;
/// No description provided for @questsTitle.
///
/// In it, this message translates to:
/// **'SFIDE'**
String get questsTitle;
/// No description provided for @themesTitle.
///
/// In it, this message translates to:
/// **'TEMI'**
String get themesTitle;
/// No description provided for @tutorialTitle.
///
/// In it, this message translates to:
/// **'TUTORIAL'**
String get tutorialTitle;
/// No description provided for @startGame.
///
/// In it, this message translates to:
/// **'AVVIA PARTITA'**
String get startGame;
/// No description provided for @createMatch.
///
/// In it, this message translates to:
/// **'CREA PARTITA'**
String get createMatch;
/// No description provided for @joinMatch.
///
/// In it, this message translates to:
/// **'UNISCITI'**
String get joinMatch;
/// No description provided for @gameOver.
///
/// In it, this message translates to:
/// **'FINE PARTITA'**
String get gameOver;
/// No description provided for @mainMenu.
///
/// In it, this message translates to:
/// **'TORNA AL MENU'**
String get mainMenu;
/// No description provided for @exit.
///
/// In it, this message translates to:
/// **'ESCI'**
String get exit;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) => <String>[
'de',
'en',
'es',
'fr',
'it',
'pt',
'ru',
'zh',
].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
case 'es':
return AppLocalizationsEs();
case 'fr':
return AppLocalizationsFr();
case 'it':
return AppLocalizationsIt();
case 'pt':
return AppLocalizationsPt();
case 'ru':
return AppLocalizationsRu();
case 'zh':
return AppLocalizationsZh();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.',
);
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for German (`de`).
class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'WILLKOMMEN BEI TETRAQ!';
@override
String get nameHint => 'NAME';
@override
String get saveAndPlay => 'SPEICHERN & SPIELEN';
@override
String get onlineTitle => 'ONLINE';
@override
String get onlineSub => 'Fordere die Welt heraus';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Trainiere mit KI';
@override
String get localTitle => 'LOKAL';
@override
String get localSub => 'Gleicher Bildschirm';
@override
String get leaderboardTitle => 'RANGLISTE';
@override
String get questsTitle => 'MISSIONEN';
@override
String get themesTitle => 'THEMEN';
@override
String get tutorialTitle => 'TUTORIAL';
@override
String get startGame => 'SPIEL STARTEN';
@override
String get createMatch => 'SPIEL ERSTELLEN';
@override
String get joinMatch => 'BEITRETEN';
@override
String get gameOver => 'SPIELENDE';
@override
String get mainMenu => 'ZURÜCK ZUM MENÜ';
@override
String get exit => 'BEENDEN';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'WELCOME TO TETRAQ!';
@override
String get nameHint => 'NAME';
@override
String get saveAndPlay => 'SAVE & PLAY';
@override
String get onlineTitle => 'ONLINE';
@override
String get onlineSub => 'Challenge the world';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Train with AI';
@override
String get localTitle => 'LOCAL';
@override
String get localSub => 'Same screen';
@override
String get leaderboardTitle => 'LEADERBOARD';
@override
String get questsTitle => 'QUESTS';
@override
String get themesTitle => 'THEMES';
@override
String get tutorialTitle => 'TUTORIAL';
@override
String get startGame => 'START GAME';
@override
String get createMatch => 'CREATE MATCH';
@override
String get joinMatch => 'JOIN';
@override
String get gameOver => 'GAME OVER';
@override
String get mainMenu => 'BACK TO MENU';
@override
String get exit => 'EXIT';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Spanish Castilian (`es`).
class AppLocalizationsEs extends AppLocalizations {
AppLocalizationsEs([String locale = 'es']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => '¡BIENVENIDO A TETRAQ!';
@override
String get nameHint => 'NOMBRE';
@override
String get saveAndPlay => 'GUARDAR Y JUGAR';
@override
String get onlineTitle => 'ONLINE';
@override
String get onlineSub => 'Desafía al mundo';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Entrena con IA';
@override
String get localTitle => 'LOCAL';
@override
String get localSub => 'Misma pantalla';
@override
String get leaderboardTitle => 'RANKING';
@override
String get questsTitle => 'MISIONES';
@override
String get themesTitle => 'TEMAS';
@override
String get tutorialTitle => 'TUTORIAL';
@override
String get startGame => 'INICIAR JUEGO';
@override
String get createMatch => 'CREAR PARTIDA';
@override
String get joinMatch => 'UNIRSE';
@override
String get gameOver => 'FIN DEL JUEGO';
@override
String get mainMenu => 'VOLVER AL MENÚ';
@override
String get exit => 'SALIR';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for French (`fr`).
class AppLocalizationsFr extends AppLocalizations {
AppLocalizationsFr([String locale = 'fr']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'BIENVENUE DANS TETRAQ !';
@override
String get nameHint => 'NOM';
@override
String get saveAndPlay => 'SAUVEGARDER ET JOUER';
@override
String get onlineTitle => 'EN LIGNE';
@override
String get onlineSub => 'Défiez le monde';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Entraînez avec l\'IA';
@override
String get localTitle => 'LOCAL';
@override
String get localSub => 'Même écran';
@override
String get leaderboardTitle => 'CLASSEMENT';
@override
String get questsTitle => 'QUÊTES';
@override
String get themesTitle => 'THÈMES';
@override
String get tutorialTitle => 'TUTORIEL';
@override
String get startGame => 'JOUER';
@override
String get createMatch => 'CRÉER UN MATCH';
@override
String get joinMatch => 'REJOINDRE';
@override
String get gameOver => 'FIN DE PARTIE';
@override
String get mainMenu => 'RETOUR AU MENU';
@override
String get exit => 'QUITTER';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Italian (`it`).
class AppLocalizationsIt extends AppLocalizations {
AppLocalizationsIt([String locale = 'it']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'BENVENUTO IN TETRAQ!';
@override
String get nameHint => 'NOME';
@override
String get saveAndPlay => 'SALVA E GIOCA';
@override
String get onlineTitle => 'ONLINE';
@override
String get onlineSub => 'Sfida il mondo';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Allenati con l\'IA';
@override
String get localTitle => 'LOCALE';
@override
String get localSub => 'Stesso schermo';
@override
String get leaderboardTitle => 'CLASSIFICA';
@override
String get questsTitle => 'SFIDE';
@override
String get themesTitle => 'TEMI';
@override
String get tutorialTitle => 'TUTORIAL';
@override
String get startGame => 'AVVIA PARTITA';
@override
String get createMatch => 'CREA PARTITA';
@override
String get joinMatch => 'UNISCITI';
@override
String get gameOver => 'FINE PARTITA';
@override
String get mainMenu => 'TORNA AL MENU';
@override
String get exit => 'ESCI';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Portuguese (`pt`).
class AppLocalizationsPt extends AppLocalizations {
AppLocalizationsPt([String locale = 'pt']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'BEM-VINDO AO TETRAQ!';
@override
String get nameHint => 'NOME';
@override
String get saveAndPlay => 'SALVAR E JOGAR';
@override
String get onlineTitle => 'ONLINE';
@override
String get onlineSub => 'Desafie o mundo';
@override
String get cpuTitle => 'VS CPU';
@override
String get cpuSub => 'Treine com a IA';
@override
String get localTitle => 'LOCAL';
@override
String get localSub => 'Mesma tela';
@override
String get leaderboardTitle => 'CLASSIFICAÇÃO';
@override
String get questsTitle => 'DESAFIOS';
@override
String get themesTitle => 'TEMAS';
@override
String get tutorialTitle => 'TUTORIAL';
@override
String get startGame => 'INICIAR JOGO';
@override
String get createMatch => 'CRIAR PARTIDA';
@override
String get joinMatch => 'ENTRAR';
@override
String get gameOver => 'FIM DE JOGO';
@override
String get mainMenu => 'VOLTAR AO MENU';
@override
String get exit => 'SAIR';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Russian (`ru`).
class AppLocalizationsRu extends AppLocalizations {
AppLocalizationsRu([String locale = 'ru']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => 'ДОБРО ПОЖАЛОВАТЬ В TETRAQ!';
@override
String get nameHint => 'ИМЯ';
@override
String get saveAndPlay => 'СОХРАНИТЬ И ИГРАТЬ';
@override
String get onlineTitle => 'ОНЛАЙН';
@override
String get onlineSub => 'Брось вызов миру';
@override
String get cpuTitle => 'VS ИИ';
@override
String get cpuSub => 'Тренировка с ИИ';
@override
String get localTitle => 'ЛОКАЛЬНО';
@override
String get localSub => 'Один экран';
@override
String get leaderboardTitle => 'РЕЙТИНГ';
@override
String get questsTitle => 'ЗАДАНИЯ';
@override
String get themesTitle => 'ТЕМЫ';
@override
String get tutorialTitle => 'ОБУЧЕНИЕ';
@override
String get startGame => 'НАЧАТЬ ИГРУ';
@override
String get createMatch => 'СОЗДАТЬ ИГРУ';
@override
String get joinMatch => 'ПРИСОЕДИНИТЬСЯ';
@override
String get gameOver => 'ИГРА ОКОНЧЕНА';
@override
String get mainMenu => 'В ГЛАВНОЕ МЕНЮ';
@override
String get exit => 'ВЫХОД';
}

View file

@ -0,0 +1,70 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Chinese (`zh`).
class AppLocalizationsZh extends AppLocalizations {
AppLocalizationsZh([String locale = 'zh']) : super(locale);
@override
String get appTitle => 'TetraQ';
@override
String get welcomeTitle => '欢迎来到 TETRAQ';
@override
String get nameHint => '名字';
@override
String get saveAndPlay => '保存并开始';
@override
String get onlineTitle => '在线匹配';
@override
String get onlineSub => '挑战世界';
@override
String get cpuTitle => '人机对战';
@override
String get cpuSub => '与AI训练';
@override
String get localTitle => '本地游戏';
@override
String get localSub => '同屏对战';
@override
String get leaderboardTitle => '排行榜';
@override
String get questsTitle => '任务';
@override
String get themesTitle => '主题';
@override
String get tutorialTitle => '教程';
@override
String get startGame => '开始游戏';
@override
String get createMatch => '创建比赛';
@override
String get joinMatch => '加入';
@override
String get gameOver => '游戏结束';
@override
String get mainMenu => '返回主菜单';
@override
String get exit => '退出';
}

23
lib/l10n/app_pt.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "pt",
"appTitle": "TetraQ",
"welcomeTitle": "BEM-VINDO AO TETRAQ!",
"nameHint": "NOME",
"saveAndPlay": "SALVAR E JOGAR",
"onlineTitle": "ONLINE",
"onlineSub": "Desafie o mundo",
"cpuTitle": "VS CPU",
"cpuSub": "Treine com a IA",
"localTitle": "LOCAL",
"localSub": "Mesma tela",
"leaderboardTitle": "CLASSIFICAÇÃO",
"questsTitle": "DESAFIOS",
"themesTitle": "TEMAS",
"tutorialTitle": "TUTORIAL",
"startGame": "INICIAR JOGO",
"createMatch": "CRIAR PARTIDA",
"joinMatch": "ENTRAR",
"gameOver": "FIM DE JOGO",
"mainMenu": "VOLTAR AO MENU",
"exit": "SAIR"
}

23
lib/l10n/app_ru.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "ru",
"appTitle": "TetraQ",
"welcomeTitle": "ДОБРО ПОЖАЛОВАТЬ В TETRAQ!",
"nameHint": "ИМЯ",
"saveAndPlay": "СОХРАНИТЬ И ИГРАТЬ",
"onlineTitle": "ОНЛАЙН",
"onlineSub": "Брось вызов миру",
"cpuTitle": "VS ИИ",
"cpuSub": "Тренировка с ИИ",
"localTitle": "ЛОКАЛЬНО",
"localSub": "Один экран",
"leaderboardTitle": "РЕЙТИНГ",
"questsTitle": "ЗАДАНИЯ",
"themesTitle": "ТЕМЫ",
"tutorialTitle": "ОБУЧЕНИЕ",
"startGame": "НАЧАТЬ ИГРУ",
"createMatch": "СОЗДАТЬ ИГРУ",
"joinMatch": "ПРИСОЕДИНИТЬСЯ",
"gameOver": "ИГРА ОКОНЧЕНА",
"mainMenu": "В ГЛАВНОЕ МЕНЮ",
"exit": "ВЫХОД"
}

23
lib/l10n/app_zh.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "zh",
"appTitle": "TetraQ",
"welcomeTitle": "欢迎来到 TETRAQ",
"nameHint": "名字",
"saveAndPlay": "保存并开始",
"onlineTitle": "在线匹配",
"onlineSub": "挑战世界",
"cpuTitle": "人机对战",
"cpuSub": "与AI训练",
"localTitle": "本地游戏",
"localSub": "同屏对战",
"leaderboardTitle": "排行榜",
"questsTitle": "任务",
"themesTitle": "主题",
"tutorialTitle": "教程",
"startGame": "开始游戏",
"createMatch": "创建比赛",
"joinMatch": "加入",
"gameOver": "游戏结束",
"mainMenu": "返回主菜单",
"exit": "退出"
}

View file

@ -17,6 +17,12 @@ import '../services/storage_service.dart';
import '../services/multiplayer_service.dart'; import '../services/multiplayer_service.dart';
import '../core/app_colors.dart'; import '../core/app_colors.dart';
class CpuMatchSetup {
final int radius;
final ArenaShape shape;
CpuMatchSetup(this.radius, this.shape);
}
class GameController extends ChangeNotifier { class GameController extends ChangeNotifier {
late GameBoard board; late GameBoard board;
bool isVsCPU = false; bool isVsCPU = false;
@ -50,6 +56,10 @@ class GameController extends ChangeNotifier {
bool opponentWantsRematch = false; bool opponentWantsRematch = false;
int lastMatchXP = 0; int lastMatchXP = 0;
bool hasLeveledUp = false;
int newlyReachedLevel = 1;
List<String> unlockedFeatures = [];
bool isSetupPhase = true; bool isSetupPhase = true;
bool myJokerPlaced = false; bool myJokerPlaced = false;
bool oppJokerPlaced = false; bool oppJokerPlaced = false;
@ -61,7 +71,7 @@ class GameController extends ChangeNotifier {
int cpuLevel = 1; int cpuLevel = 1;
int currentMatchLevel = 1; int currentMatchLevel = 1;
int? currentSeed; int? currentSeed;
AppThemeType _activeTheme = AppThemeType.cyberpunk; AppThemeType _activeTheme = AppThemeType.doodle;
String onlineHostName = "ROSSO"; String onlineHostName = "ROSSO";
String onlineGuestName = "BLU"; String onlineGuestName = "BLU";
@ -72,6 +82,25 @@ class GameController extends ChangeNotifier {
startNewGame(radius); startNewGame(radius);
} }
CpuMatchSetup _getSetupForCpuLevel(int level) {
final rand = Random();
if (level == 1) return CpuMatchSetup(3, ArenaShape.classic);
if (level == 2) return CpuMatchSetup(4, ArenaShape.classic);
if (level == 3) return CpuMatchSetup(4, ArenaShape.cross);
if (level == 4) return CpuMatchSetup(4, ArenaShape.donut);
if (level == 5) return CpuMatchSetup(5, ArenaShape.classic);
if (level == 6) return CpuMatchSetup(4, ArenaShape.hourglass);
if (level == 7) return CpuMatchSetup(5, ArenaShape.cross);
if (level == 8) return CpuMatchSetup(5, ArenaShape.donut);
if (level == 9) return CpuMatchSetup(5, ArenaShape.hourglass);
List<ArenaShape> hardShapes = [ArenaShape.classic, ArenaShape.cross, ArenaShape.donut, ArenaShape.hourglass, ArenaShape.chaos];
ArenaShape chosenShape = hardShapes[rand.nextInt(hardShapes.length)];
int chosenRadius = (chosenShape == ArenaShape.chaos) ? (rand.nextInt(2) + 4) : (rand.nextInt(2) + 5);
return CpuMatchSetup(chosenRadius, chosenShape);
}
void startNewGame(int radius, {bool vsCPU = false, bool isOnline = false, String? roomCode, bool isHost = false, ArenaShape shape = ArenaShape.classic, bool timeMode = true}) { void startNewGame(int radius, {bool vsCPU = false, bool isOnline = false, String? roomCode, bool isHost = false, ArenaShape shape = ArenaShape.classic, bool timeMode = true}) {
_onlineSubscription?.cancel(); _onlineSubscription?.cancel();
_onlineSubscription = null; _onlineSubscription = null;
@ -81,6 +110,9 @@ class GameController extends ChangeNotifier {
_hasSavedResult = false; _hasSavedResult = false;
lastMatchXP = 0; lastMatchXP = 0;
hasLeveledUp = false;
unlockedFeatures.clear();
myReaction = null; myReaction = null;
opponentReaction = null; opponentReaction = null;
_lastOpponentReactionTime = null; _lastOpponentReactionTime = null;
@ -98,10 +130,19 @@ class GameController extends ChangeNotifier {
this.isHost = isHost; this.isHost = isHost;
this.isTimeMode = timeMode; this.isTimeMode = timeMode;
onlineShape = shape; int finalRadius = radius;
ArenaShape finalShape = shape;
if (this.isVsCPU) {
CpuMatchSetup setup = _getSetupForCpuLevel(cpuLevel);
finalRadius = setup.radius;
finalShape = setup.shape;
}
onlineShape = finalShape;
int levelToUse = isOnline ? (currentMatchLevel == 1 ? 2 : currentMatchLevel) : cpuLevel; int levelToUse = isOnline ? (currentMatchLevel == 1 ? 2 : currentMatchLevel) : cpuLevel;
board = GameBoard(radius: radius, level: levelToUse, seed: currentSeed, shape: onlineShape); board = GameBoard(radius: finalRadius, level: levelToUse, seed: currentSeed, shape: finalShape);
board.currentPlayer = Player.red; board.currentPlayer = Player.red;
isCPUThinking = false; isCPUThinking = false;
@ -285,16 +326,21 @@ class GameController extends ChangeNotifier {
void _handleTimeOut() { void _handleTimeOut() {
if (!isTimeMode || isSetupPhase) return; if (!isTimeMode || isSetupPhase) return;
if (isOnline) { // Solo chi deve giocare può subire il timeout (se è online)
Line randomMove = AIEngine.getBestMove(board, 5); if (isOnline && board.currentPlayer != myPlayer) return;
// 1. Raccogliamo TUTTE le linee ancora libere e giocabili
List<Line> availableLines = board.lines.where((l) => l.owner == Player.none && l.isPlayable).toList();
// Sicurezza: se non ci sono mosse, non facciamo nulla
if (availableLines.isEmpty) return;
// 2. Scegliamo una linea in modo PURAMENTE CASUALE (nessuna intelligenza artificiale)
final random = Random();
Line randomMove = availableLines[random.nextInt(availableLines.length)];
// 3. Eseguiamo la mossa forzata
handleLineTap(randomMove, _activeTheme, forced: true); handleLineTap(randomMove, _activeTheme, forced: true);
} else if (isVsCPU && board.currentPlayer == Player.red) {
Line randomMove = AIEngine.getBestMove(board, cpuLevel);
handleLineTap(randomMove, _activeTheme, forced: true);
} else if (!isVsCPU) {
Line randomMove = AIEngine.getBestMove(board, 5);
handleLineTap(randomMove, _activeTheme, forced: true);
}
} }
void disconnectOnlineGame() { void disconnectOnlineGame() {
@ -503,6 +549,20 @@ class GameController extends ChangeNotifier {
} }
} }
List<String> _getUnlocks(int oldLevel, int newLevel) {
List<String> unlocks = [];
for(int i = oldLevel + 1; i <= newLevel; i++) {
if (i == 3) unlocks.add("Tema: Legno & Fiammiferi");
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() { void _saveMatchResult() {
if (_hasSavedResult) return; if (_hasSavedResult) return;
_hasSavedResult = true; _hasSavedResult = true;
@ -512,6 +572,8 @@ class GameController extends ChangeNotifier {
String myRealName = StorageService.instance.playerName; String myRealName = StorageService.instance.playerName;
if (myRealName.isEmpty) myRealName = "IO"; if (myRealName.isEmpty) myRealName = "IO";
int oldLevel = StorageService.instance.playerLevel;
if (isOnline) { if (isOnline) {
bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed; bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed;
calculatedXP = isWin ? 20 : (isDraw ? 5 : 2); calculatedXP = isWin ? 20 : (isDraw ? 5 : 2);
@ -520,7 +582,7 @@ class GameController extends ChangeNotifier {
int oppScore = isHost ? board.scoreBlue : board.scoreRed; int oppScore = isHost ? board.scoreBlue : board.scoreRed;
StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: oppName, myScore: myScore, oppScore: oppScore, isOnline: true); 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) { } else if (isVsCPU) {
int myScore = board.scoreRed; int cpuScore = board.scoreBlue; int myScore = board.scoreRed; int cpuScore = board.scoreBlue;
@ -529,7 +591,7 @@ class GameController extends ChangeNotifier {
if (isWin) { if (isWin) {
StorageService.instance.addWin(); StorageService.instance.addWin();
StorageService.instance.updateQuestProgress(1, 1); // Missione: Vinci vs CPU StorageService.instance.updateQuestProgress(1, 1);
} else if (cpuScore > myScore) { } else if (cpuScore > myScore) {
StorageService.instance.addLoss(); StorageService.instance.addLoss();
} }
@ -539,12 +601,21 @@ class GameController extends ChangeNotifier {
StorageService.instance.saveMatchToHistory(myName: myRealName, opponent: "Ospite (Locale)", myScore: board.scoreRed, oppScore: board.scoreBlue, isOnline: false); 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) { 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);
int newLevel = StorageService.instance.playerLevel;
if (newLevel > oldLevel) {
hasLeveledUp = true;
newlyReachedLevel = newLevel;
unlockedFeatures = _getUnlocks(oldLevel, newLevel);
}
notifyListeners();
} }
void increaseLevelAndRestart() { void increaseLevelAndRestart() {

View file

@ -1,23 +1,44 @@
// ===========================================================================
// FILE: lib/main.dart
// ===========================================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'core/theme_manager.dart'; import 'core/theme_manager.dart';
import 'logic/game_controller.dart'; import 'logic/game_controller.dart';
import 'ui/home/home_screen.dart'; import 'ui/home/home_screen.dart';
import 'services/storage_service.dart'; // <-- Importiamo il servizio import 'services/storage_service.dart';
import 'services/audio_service.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
import 'package:firebase_app_check/firebase_app_check.dart';
// --- IMPORT PER IL SUPPORTO MULTILINGUA ---
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:tetraq/l10n/app_localizations.dart';
void main() async { void main() async {
// Assicuriamoci che i motori di Flutter siano pronti
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// 1. Accendiamo Firebase! (Questo ti era sfuggito)
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, options: DefaultFirebaseOptions.currentPlatform,
); );
// 2. Accendiamo la Memoria Locale! await FirebaseAppCheck.instance.activate(
androidProvider: kDebugMode ? AndroidProvider.debug : AndroidProvider.playIntegrity,
appleProvider: kDebugMode ? AppleProvider.debug : AppleProvider.deviceCheck,
);
try {
await FirebaseAuth.instance.signInAnonymously();
} catch (e) {
debugPrint("Errore Auth: $e");
}
await StorageService.instance.init(); await StorageService.instance.init();
await AudioService.instance.init();
runApp( runApp(
MultiProvider( MultiProvider(
@ -42,6 +63,14 @@ class TetraQApp extends StatelessWidget {
fontFamily: 'Roboto', fontFamily: 'Roboto',
useMaterial3: true, useMaterial3: true,
), ),
// --- BIVIO DELLE LINGUE ATTIVATO! ---
// Flutter si occuperà di caricare automaticamente tutte le lingue
// che hai generato tramite lo script.
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
// ------------------------------------
home: const HomeScreen(), home: const HomeScreen(),
); );
} }

View file

@ -5,6 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import '../core/app_colors.dart'; import '../core/app_colors.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AudioService extends ChangeNotifier { class AudioService extends ChangeNotifier {
static final AudioService instance = AudioService._internal(); static final AudioService instance = AudioService._internal();
@ -12,35 +13,104 @@ class AudioService extends ChangeNotifier {
bool isMuted = false; bool isMuted = false;
final AudioPlayer _sfxPlayer = AudioPlayer(); final AudioPlayer _sfxPlayer = AudioPlayer();
final AudioPlayer _bgmPlayer = AudioPlayer();
void toggleMute() { AppThemeType _currentTheme = AppThemeType.doodle;
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
isMuted = prefs.getBool('isMuted') ?? false;
await _bgmPlayer.setReleaseMode(ReleaseMode.loop);
}
void toggleMute() async {
isMuted = !isMuted; isMuted = !isMuted;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isMuted', isMuted);
if (isMuted) {
// Se abbiamo appena silenziato, FERMA TUTTO immediatamente.
await _bgmPlayer.pause();
await _sfxPlayer.stop();
} else {
// Se riaccendiamo, fai ripartire la canzone
playBgm(_currentTheme);
}
notifyListeners(); notifyListeners();
} }
Future<void> playBgm(AppThemeType theme) async {
_currentTheme = theme;
await _bgmPlayer.stop();
if (isMuted) return;
String audioPath = '';
switch (theme) {
case AppThemeType.cyberpunk:
audioPath = 'audio/bgm/Cyber_Dystopia.mp3';
break;
case AppThemeType.doodle:
audioPath = 'audio/bgm/Quad_Dreams.mp3';
break;
case AppThemeType.wood:
audioPath = 'audio/bgm/Legno_Canopy.mp3';
break;
case AppThemeType.arcade:
audioPath = 'audio/bgm/8-bit_Prowler.mp3';
break;
case AppThemeType.grimorio:
audioPath = 'audio/bgm/Grimorio_Astral.mp3';
break;
case AppThemeType.music:
audioPath = 'audio/bgm/Music_Loop.mp3'; // <-- DEVI INSERIRE QUESTO FILE IN ASSETS
break;
}
if (audioPath.isNotEmpty) {
try {
await _bgmPlayer.play(AssetSource(audioPath), volume: 0.15);
} catch (e) {
debugPrint("Errore riproduzione BGM: $e");
}
}
}
Future<void> stopBgm() async {
await _bgmPlayer.stop();
}
void playLineSfx(AppThemeType theme) async { void playLineSfx(AppThemeType theme) async {
if (isMuted) return; if (isMuted) return;
String file = ''; String file = '';
switch (theme) { switch (theme) {
case AppThemeType.minimal: case AppThemeType.arcade:
case AppThemeType.arcade: // Suono secco per l'arcade case AppThemeType.music: // Usiamo l'effetto arcade o cyber per la musica
file = 'minimal_line.wav'; break; file = 'minimal_line.wav'; break;
case AppThemeType.doodle: case AppThemeType.doodle:
case AppThemeType.wood: case AppThemeType.wood:
file = 'doodle_line.wav'; break; file = 'doodle_line.wav'; break;
case AppThemeType.cyberpunk: case AppThemeType.cyberpunk:
case AppThemeType.grimorio: // Suono etereo per la magia case AppThemeType.grimorio:
file = 'cyber_line.wav'; break; file = 'cyber_line.wav'; break;
} }
await _sfxPlayer.play(AssetSource('audio/sfx/$file'));
if (file.isNotEmpty) {
try {
await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0);
} catch (e) {
debugPrint("Errore SFX Linea: $file");
}
}
} }
void playBoxSfx(AppThemeType theme) async { void playBoxSfx(AppThemeType theme) async {
if (isMuted) return; if (isMuted) return;
String file = ''; String file = '';
switch (theme) { switch (theme) {
case AppThemeType.minimal:
case AppThemeType.arcade: case AppThemeType.arcade:
case AppThemeType.music:
file = 'minimal_box.wav'; break; file = 'minimal_box.wav'; break;
case AppThemeType.doodle: case AppThemeType.doodle:
case AppThemeType.wood: case AppThemeType.wood:
@ -49,16 +119,27 @@ class AudioService extends ChangeNotifier {
case AppThemeType.grimorio: case AppThemeType.grimorio:
file = 'cyber_box.wav'; break; file = 'cyber_box.wav'; break;
} }
await _sfxPlayer.play(AssetSource('audio/sfx/$file'));
if (file.isNotEmpty) {
try {
await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0);
} catch (e) {
debugPrint("Errore SFX Box: $file");
}
}
} }
void playBonusSfx() async { void playBonusSfx() async {
if (isMuted) return; if (isMuted) return;
await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav')); try {
await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav'), volume: 1.0);
} catch(e) {}
} }
void playBombSfx() async { void playBombSfx() async {
if (isMuted) return; if (isMuted) return;
await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav')); try {
await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav'), volume: 1.0);
} catch(e) {}
} }
} }

View file

@ -4,15 +4,17 @@
import 'dart:math'; import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
class MultiplayerService { class MultiplayerService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
CollectionReference get _gamesCollection => _firestore.collection('games'); CollectionReference get _gamesCollection => _firestore.collection('games');
Future<String> createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode) async { Future<String> createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode, {bool isPublic = true}) async {
String roomCode = _generateRoomCode(); String roomCode = _generateRoomCode();
int randomSeed = Random().nextInt(1000000); int randomSeed = Random().nextInt(1000000);
@ -25,10 +27,11 @@ class MultiplayerService {
'moves': [], 'moves': [],
'seed': randomSeed, 'seed': randomSeed,
'hostName': hostName, 'hostName': hostName,
'hostUid': _auth.currentUser?.uid, // NUOVO: Salviamo l'ID univoco del creatore
'guestName': '', 'guestName': '',
'shape': shapeName, 'shape': shapeName,
'timeMode': isTimeMode, 'timeMode': isTimeMode,
// Nuovi campi per Emojis e Rivincita 'isPublic': isPublic,
'p1_reaction': null, 'p1_reaction': null,
'p2_reaction': null, 'p2_reaction': null,
'p1_rematch': false, 'p1_rematch': false,
@ -52,8 +55,18 @@ class MultiplayerService {
return null; return null;
} }
Stream<QuerySnapshot> getPublicRooms() {
return _gamesCollection
.where('status', isEqualTo: 'waiting')
.where('isPublic', isEqualTo: true)
.snapshots();
}
void shareInviteLink(String roomCode) { 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); Share.share(message);
} }
@ -69,7 +82,6 @@ class MultiplayerService {
)); ));
} }
// --- NUOVI METODI PER REAZIONI E RIVINCITA ---
Future<void> sendReaction(String roomCode, bool isHost, String reaction) async { Future<void> sendReaction(String roomCode, bool isHost, String reaction) async {
try { try {
String prefix = isHost ? 'p1' : 'p2'; String prefix = isHost ? 'p1' : 'p2';

View file

@ -6,6 +6,8 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../core/app_colors.dart'; import '../core/app_colors.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
class StorageService { class StorageService {
static final StorageService instance = StorageService._internal(); static final StorageService instance = StorageService._internal();
@ -15,10 +17,11 @@ class StorageService {
Future<void> init() async { Future<void> init() async {
_prefs = await SharedPreferences.getInstance(); _prefs = await SharedPreferences.getInstance();
_checkDailyQuests(); // All'avvio controlliamo se ci sono nuove sfide _checkDailyQuests();
} }
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.minimal.index; // Doodle è il nuovo tema di partenza (index 0)
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index;
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index); Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
int get savedRadius => _prefs.getInt('radius') ?? 2; int get savedRadius => _prefs.getInt('radius') ?? 2;
@ -29,7 +32,6 @@ class StorageService {
int get totalXP => _prefs.getInt('totalXP') ?? 0; int get totalXP => _prefs.getInt('totalXP') ?? 0;
// Modificato per sincronizzare automaticamente la classifica su Firebase
Future<void> addXP(int xp) async { Future<void> addXP(int xp) async {
await _prefs.setInt('totalXP', totalXP + xp); await _prefs.setInt('totalXP', totalXP + xp);
syncLeaderboard(); syncLeaderboard();
@ -52,46 +54,44 @@ class StorageService {
String get playerName => _prefs.getString('playerName') ?? ''; String get playerName => _prefs.getString('playerName') ?? '';
Future<void> savePlayerName(String name) async { Future<void> savePlayerName(String name) async {
await _prefs.setString('playerName', name); await _prefs.setString('playerName', name);
syncLeaderboard(); // Aggiorna il nome in classifica syncLeaderboard();
} }
// --- NUOVO: SINCRONIZZAZIONE CLASSIFICA ONLINE ---
Future<void> syncLeaderboard() async { Future<void> syncLeaderboard() async {
if (playerName.isNotEmpty) { if (playerName.isNotEmpty) {
try { try {
await FirebaseFirestore.instance.collection('leaderboard').doc(playerName).set({ final user = FirebaseAuth.instance.currentUser;
if (user != null) {
await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({
'name': playerName, 'name': playerName,
'xp': totalXP, 'xp': totalXP,
'level': playerLevel, 'level': playerLevel,
'wins': wins, 'wins': wins,
'lastActive': FieldValue.serverTimestamp(), 'lastActive': FieldValue.serverTimestamp(),
}, SetOptions(merge: true)); }, SetOptions(merge: true));
}
} catch(e) { } catch(e) {
// Ignoriamo gli errori se manca la rete, si sincronizzerà dopo debugPrint("Errore sinc. classifica: $e");
} }
} }
} }
// --- NUOVO: GESTIONE SFIDE GIORNALIERE ---
void _checkDailyQuests() { void _checkDailyQuests() {
String today = DateTime.now().toIso8601String().substring(0, 10); String today = DateTime.now().toIso8601String().substring(0, 10);
String lastDate = _prefs.getString('quest_date') ?? ''; String lastDate = _prefs.getString('quest_date') ?? '';
if (today != lastDate) { if (today != lastDate) {
// Nuovo giorno, nuove sfide!
_prefs.setString('quest_date', today); _prefs.setString('quest_date', today);
// Sfida 1: Gioca partite online
_prefs.setInt('q1_type', 0); _prefs.setInt('q1_type', 0);
_prefs.setInt('q1_prog', 0); _prefs.setInt('q1_prog', 0);
_prefs.setInt('q1_target', 3); _prefs.setInt('q1_target', 3);
// Sfida 2: Vinci contro la CPU
_prefs.setInt('q2_type', 1); _prefs.setInt('q2_type', 1);
_prefs.setInt('q2_prog', 0); _prefs.setInt('q2_prog', 0);
_prefs.setInt('q2_target', 2); _prefs.setInt('q2_target', 2);
// Sfida 3: Partite con forme speciali (Croce, Caos, ecc)
_prefs.setInt('q3_type', 2); _prefs.setInt('q3_type', 2);
_prefs.setInt('q3_prog', 0); _prefs.setInt('q3_prog', 0);
_prefs.setInt('q3_target', 2); _prefs.setInt('q3_target', 2);
@ -110,7 +110,6 @@ class StorageService {
} }
} }
// --- STORICO PARTITE ---
List<Map<String, dynamic>> get matchHistory { List<Map<String, dynamic>> get matchHistory {
List<String> history = _prefs.getStringList('matchHistory') ?? []; List<String> history = _prefs.getStringList('matchHistory') ?? [];
return history.map((e) => jsonDecode(e) as Map<String, dynamic>).toList(); return history.map((e) => jsonDecode(e) as Map<String, dynamic>).toList();

BIN
lib/ui/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -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<ThemeManager>().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<QuerySnapshot>(
// 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, dynamic>;
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)),
],
),
],
)
],
),
),
);
},
);
}
),
);
}
}

View file

@ -54,6 +54,29 @@ class BoardPainter extends CustomPainter {
double offset = spacing / 2; double offset = spacing / 2;
Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset); Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset);
// --- NUOVO SFONDO LUMINOSO SAGOMATO (Solo per tema Musica) ---
if (themeType == AppThemeType.music) {
Path arenaShape = Path();
// Uniamo la forma di ogni box giocabile per creare il tappeto
for (var box in board.boxes) {
if (box.type != BoxType.invisible) { // Ignora i buchi
Offset p1 = getScreenPos(box.x, box.y);
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
arenaShape.addRect(Rect.fromPoints(p1, p2));
}
}
// Disegniamo lo sfondo unito, chiaro e con un po' di blur
canvas.drawPath(
arenaShape,
Paint()
..color = Colors.white.withOpacity(0.08) // Colore chiarissimo
..style = PaintingStyle.fill
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0), // Effetto stacco/glow
);
}
// -----------------------------------------------------------
for (var box in board.boxes) { for (var box in board.boxes) {
Offset p1 = getScreenPos(box.x, box.y); Offset p1 = getScreenPos(box.x, box.y);
Offset p2 = getScreenPos(box.x + 1, box.y + 1); Offset p2 = getScreenPos(box.x + 1, box.y + 1);
@ -111,7 +134,7 @@ class BoardPainter extends CustomPainter {
if (box.type == BoxType.gold) { if (box.type == BoxType.gold) {
_drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber); _drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber);
} else if (box.type == BoxType.bomb) { } else if (box.type == BoxType.bomb) {
_drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple); _drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.greenAccent : Colors.deepPurple);
} else if (box.type == BoxType.swap) { } else if (box.type == BoxType.swap) {
_drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent); _drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent);
} else if (box.type == BoxType.ice) { } else if (box.type == BoxType.ice) {
@ -160,6 +183,10 @@ class BoardPainter extends CustomPainter {
_drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue); _drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
} else if (themeType == AppThemeType.grimorio) { } else if (themeType == AppThemeType.grimorio) {
_drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue); _drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
} else if (themeType == AppThemeType.music) {
// Linee nere per la base nel tema musica
if (line.owner == Player.none) lineColor = Colors.black.withOpacity(0.4);
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
} else { } else {
if (isLastMove && line.owner != Player.none) lineColor = Color.lerp(lineColor, Colors.white, blinkValue * 0.5) ?? lineColor; if (isLastMove && line.owner != Player.none) lineColor = Color.lerp(lineColor, Colors.white, blinkValue * 0.5) ?? lineColor;
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round); canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
@ -190,6 +217,9 @@ class BoardPainter extends CustomPainter {
canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0)); canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0));
Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close(); Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close();
canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8)); canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8));
} else if (themeType == AppThemeType.music) {
// Pallini (dots) neri per staccare dal fondo chiaro
canvas.drawCircle(pos, 4.5, dotPaint..color = Colors.black87);
} else { } else {
canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6)); canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6));
} }

View file

@ -10,9 +10,11 @@ import 'package:provider/provider.dart';
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../core/theme_manager.dart'; import '../../core/theme_manager.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../models/game_board.dart';
import 'board_painter.dart'; import 'board_painter.dart';
import 'score_board.dart'; import 'score_board.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../../services/storage_service.dart';
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
if (themeType == AppThemeType.doodle) { if (themeType == AppThemeType.doodle) {
@ -24,6 +26,8 @@ TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
)); ));
} else if (themeType == AppThemeType.grimorio) { } else if (themeType == AppThemeType.grimorio) {
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold)); return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
} else if (themeType == AppThemeType.music) {
return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5));
} }
return baseStyle; return baseStyle;
} }
@ -40,7 +44,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
bool _gameOverDialogShown = false; bool _gameOverDialogShown = false;
bool _opponentLeftDialogShown = false; bool _opponentLeftDialogShown = false;
// Variabili per coprire il posizionamento del Jolly in Locale
bool _hideJokerMessage = false; bool _hideJokerMessage = false;
bool _wasSetupPhase = false; bool _wasSetupPhase = false;
Player _lastJokerTurn = Player.red; Player _lastJokerTurn = Player.red;
@ -74,8 +77,12 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
int red = controller.board.scoreRed; int blue = controller.board.scoreBlue; int red = controller.board.scoreRed; int blue = controller.board.scoreBlue;
bool playerBeatCPU = controller.isVsCPU && red > blue; bool playerBeatCPU = controller.isVsCPU && red > blue;
String nameRed = controller.isOnline ? controller.onlineHostName.toUpperCase() : "TU";
String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"); String myName = StorageService.instance.playerName.toUpperCase();
if (myName.isEmpty) myName = "TU";
String nameRed = controller.isOnline ? controller.onlineHostName.toUpperCase() : myName;
String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? "VERDE" : "BLU");
if (controller.isVsCPU) nameBlue = "CPU"; if (controller.isVsCPU) nameBlue = "CPU";
String winnerText = ""; Color winnerColor = theme.text; String winnerText = ""; Color winnerColor = theme.text;
@ -95,6 +102,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)), decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -104,7 +113,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
], ],
), ),
), ),
),
if (controller.lastMatchXP > 0) ...[ if (controller.lastMatchXP > 0) ...[
const SizedBox(height: 15), const SizedBox(height: 15),
Container( Container(
@ -113,7 +122,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
color: Colors.green.withOpacity(0.15), color: Colors.green.withOpacity(0.15),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.greenAccent, width: 1.5), border: Border.all(color: Colors.greenAccent, width: 1.5),
boxShadow: themeType == AppThemeType.cyberpunk ? [const BoxShadow(color: Colors.greenAccent, blurRadius: 10, spreadRadius: -5)] : [], boxShadow: (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) ? [const BoxShadow(color: Colors.greenAccent, blurRadius: 10, spreadRadius: -5)] : [],
), ),
child: Text("+ ${controller.lastMatchXP} XP", style: _getTextStyle(themeType, const TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 1.5))), child: Text("+ ${controller.lastMatchXP} XP", style: _getTextStyle(themeType, const TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 1.5))),
), ),
@ -188,8 +197,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
titleText = "Nascondi il tuo Jolly!"; titleText = "Nascondi il tuo Jolly!";
subtitleText = "(Tocca qui per nascondere)"; subtitleText = "(Tocca qui per nascondere)";
} else { } else {
// --- TESTI MODALITÀ LOCALE --- String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? "VERDE" : "BLU");
String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU");
titleText = "TURNO GIOCATORE $pName"; titleText = "TURNO GIOCATORE $pName";
subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)"; subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)";
} }
@ -199,7 +207,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.yellowAccent : theme.playerBlue, size: 50), Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.yellowAccent : theme.playerBlue, size: 50),
const SizedBox(height: 15), const SizedBox(height: 15),
Text( Text(
titleText, titleText,
@ -224,8 +232,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
), ),
); );
if (themeType == AppThemeType.cyberpunk) { if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
return Container(decoration: BoxDecoration(color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.yellowAccent, width: 2), boxShadow: [const BoxShadow(color: Colors.yellowAccent, blurRadius: 15, spreadRadius: 0)]), child: content); return Container(decoration: BoxDecoration(color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.purpleAccent, width: 2), boxShadow: [BoxShadow(color: Colors.purpleAccent.withOpacity(0.6), blurRadius: 15, spreadRadius: 0)]), child: content);
} else if (themeType == AppThemeType.doodle) { } else if (themeType == AppThemeType.doodle) {
return Container(decoration: BoxDecoration(color: const Color(0xFFF9F9F9), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 3), boxShadow: const [BoxShadow(color: Colors.black26, offset: Offset(6, 6))]), child: content); return Container(decoration: BoxDecoration(color: const Color(0xFFF9F9F9), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 3), boxShadow: const [BoxShadow(color: Colors.black26, offset: Offset(6, 6))]), child: content);
} else if (themeType == AppThemeType.wood) { } else if (themeType == AppThemeType.wood) {
@ -235,7 +243,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
} else if (themeType == AppThemeType.grimorio) { } else if (themeType == AppThemeType.grimorio) {
return Container(decoration: BoxDecoration(color: const Color(0xFF2C1E3D), borderRadius: BorderRadius.circular(30), border: Border.all(color: const Color(0xFFBCAAA4), width: 3), boxShadow: [BoxShadow(color: Colors.deepPurpleAccent.withOpacity(0.5), blurRadius: 20, spreadRadius: 5)]), child: content); return Container(decoration: BoxDecoration(color: const Color(0xFF2C1E3D), borderRadius: BorderRadius.circular(30), border: Border.all(color: const Color(0xFFBCAAA4), width: 3), boxShadow: [BoxShadow(color: Colors.deepPurpleAccent.withOpacity(0.5), blurRadius: 20, spreadRadius: 5)]), child: content);
} else { } else {
return Container(decoration: BoxDecoration(color: theme.background, borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine, width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 10))]), child: content); return Container(decoration: BoxDecoration(color: theme.background.withOpacity(0.95), borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine.withOpacity(0.5), width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10))]), child: content);
} }
} }
@ -246,13 +254,10 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
final theme = themeManager.currentColors; final theme = themeManager.currentColors;
final gameController = context.watch<GameController>(); final gameController = context.watch<GameController>();
// --- LOGICA CAMBIO TURNO E SCHERMATA JOLLY ---
if (gameController.isSetupPhase && !_wasSetupPhase) { if (gameController.isSetupPhase && !_wasSetupPhase) {
// È appena iniziata una nuova partita
_hideJokerMessage = false; _hideJokerMessage = false;
_lastJokerTurn = Player.red; _lastJokerTurn = Player.red;
} else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) { } else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) {
// È cambiato il turno durante il setup (in modalità locale), rifacciamo apparire la copertura
_hideJokerMessage = false; _hideJokerMessage = false;
_lastJokerTurn = gameController.jokerTurn; _lastJokerTurn = gameController.jokerTurn;
} }
@ -286,9 +291,11 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
String? bgImage; String? bgImage;
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg'; if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; // Questo è lo sfondo carta
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.white : Colors.black; Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.white : Colors.black;
Widget emojiBar = const SizedBox(); Widget emojiBar = const SizedBox();
if (gameController.isOnline && !gameController.isGameOver) { if (gameController.isOnline && !gameController.isGameOver) {
@ -296,9 +303,9 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
emojiBar = Container( emojiBar = Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.black12, width: 2), border: Border.all(color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -332,7 +339,23 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
return SizedBox( return SizedBox(
width: actualWidth, height: actualHeight, width: actualWidth, height: actualHeight,
child: GestureDetector( child: Stack(
children: [
// --- IL VERO SFONDO SFOCATO SAGOMATO ---
if (themeType == AppThemeType.music)
Positioned.fill(
child: ClipPath(
clipper: _ArenaClipper(gameController.board),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
color: Colors.white.withOpacity(0.08),
),
),
),
),
GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType), onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
child: AnimatedBuilder( child: AnimatedBuilder(
@ -350,6 +373,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
} }
), ),
), ),
],
),
); );
} }
), ),
@ -404,28 +429,60 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
canPop: true, canPop: true,
onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); }, onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); },
child: Scaffold( child: Scaffold(
backgroundColor: bgImage != null ? Colors.transparent : theme.background, backgroundColor: Colors.transparent, // Assicuriamo che la scaffold base sia trasparente
body: CustomPaint( body: Stack(
painter: themeType == AppThemeType.minimal ? FullScreenGridPainter(Colors.black.withOpacity(0.06)) : null,
child: Container(
decoration: bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null,
child: Stack(
children: [ children: [
// 1. Sfondo base a tinta unita (In caso non ci sia l'immagine)
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
// 2. Immagine di Sfondo per tutti i temi che la supportano
if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
// 3. Griglia a righe incrociate per il doodle (Sopra l'immagine di carta)
if (themeType == AppThemeType.doodle)
Positioned.fill(
child: CustomPaint(
painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)),
),
),
// 4. Patina scura (Cyberpunk e Music) per far risaltare il neon
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music))
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter, end: Alignment.bottomCenter,
colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)]
)
),
),
),
// 5. Effetto "Furia" o Timeout
if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase) if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase)
Positioned.fill(child: BlitzBackgroundEffect(timeLeft: gameController.timeLeft, color: theme.playerRed, themeType: themeType)), Positioned.fill(child: BlitzBackgroundEffect(timeLeft: gameController.timeLeft, color: theme.playerRed, themeType: themeType)),
// 6. Testo degli Eventi
if (gameController.effectText.isNotEmpty) if (gameController.effectText.isNotEmpty)
Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)), Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)),
// 7. Il Gioco Vero e Proprio
Positioned.fill(child: gameContent), Positioned.fill(child: gameContent),
// --- SCHERMATA COPRENTE PER IL PASSAGGIO DEL TELEFONO IN LOCALE --- // 8. Schermata Passaggio Dispositivo
if (gameController.isSetupPhase && !_hideJokerMessage) if (gameController.isSetupPhase && !_hideJokerMessage)
Positioned.fill( Positioned.fill(
child: Container( child: Container(
// Il colore di sfondo riempie tutto lo schermo per non far sbirciare la griglia color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black.withOpacity(0.98)
? Colors.black
: theme.background.withOpacity(0.98), : theme.background.withOpacity(0.98),
child: Center( child: Center(
child: Padding( child: Padding(
@ -439,13 +496,12 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
), ),
), ),
// 9. Effetti di Vittoria
if (gameController.isGameOver && gameController.board.scoreRed != gameController.board.scoreBlue) if (gameController.isGameOver && gameController.board.scoreRed != gameController.board.scoreBlue)
Positioned.fill(child: IgnorePointer(child: WinnerVFXOverlay(winnerColor: gameController.board.scoreRed > gameController.board.scoreBlue ? theme.playerRed : theme.playerBlue, themeType: themeType))), Positioned.fill(child: IgnorePointer(child: WinnerVFXOverlay(winnerColor: gameController.board.scoreRed > gameController.board.scoreBlue ? theme.playerRed : theme.playerBlue, themeType: themeType))),
], ],
), ),
), ),
),
),
); );
} }
@ -479,6 +535,37 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
} }
} }
// ===========================================================================
// CLIPPER MAGICO: Ritaglia l'effetto sfocatura sull'esatta forma dell'arena
// ===========================================================================
class _ArenaClipper extends CustomClipper<Path> {
final GameBoard board;
_ArenaClipper(this.board);
@override
Path getClip(Size size) {
int cols = board.columns + 1;
double spacing = size.width / cols;
double offset = spacing / 2;
Path path = Path();
for (var box in board.boxes) {
if (box.type != BoxType.invisible) {
path.addRect(Rect.fromLTWH(
box.x * spacing + offset,
box.y * spacing + offset,
spacing,
spacing
));
}
}
return path;
}
@override
bool shouldReclip(covariant _ArenaClipper oldClipper) => true;
}
class _Particle { class _Particle {
double x, y, vx, vy, size, angle, spin; double x, y, vx, vy, size, angle, spin;
Color color; int type; Color color; int type;
@ -510,7 +597,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
} }
void _initParticles(Size screenSize) { void _initParticles(Size screenSize) {
int particleCount = widget.themeType == AppThemeType.cyberpunk ? 150 : 100; int particleCount = widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.music ? 150 : 100;
if (widget.themeType == AppThemeType.arcade) particleCount = 80; if (widget.themeType == AppThemeType.arcade) particleCount = 80;
if (widget.themeType == AppThemeType.grimorio) particleCount = 120; if (widget.themeType == AppThemeType.grimorio) particleCount = 120;
@ -520,6 +607,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
else if (widget.themeType == AppThemeType.wood) { palette = [Colors.orangeAccent, Colors.yellow, Colors.red, Colors.white]; } else if (widget.themeType == AppThemeType.wood) { palette = [Colors.orangeAccent, Colors.yellow, Colors.red, Colors.white]; }
else if (widget.themeType == AppThemeType.arcade) { palette = [widget.winnerColor, Colors.white, Colors.greenAccent]; } else if (widget.themeType == AppThemeType.arcade) { palette = [widget.winnerColor, Colors.white, Colors.greenAccent]; }
else if (widget.themeType == AppThemeType.grimorio) { palette = [widget.winnerColor, Colors.deepPurpleAccent, Colors.white]; } else if (widget.themeType == AppThemeType.grimorio) { palette = [widget.winnerColor, Colors.deepPurpleAccent, Colors.white]; }
else if (widget.themeType == AppThemeType.music) { palette.add(Colors.pinkAccent); palette.add(Colors.cyanAccent); }
for (int i = 0; i < particleCount; i++) { for (int i = 0; i < particleCount; i++) {
double speed = _rand.nextDouble() * 20 + 5; double speed = _rand.nextDouble() * 20 + 5;
@ -532,7 +620,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
setState(() { setState(() {
for (var p in _particles) { for (var p in _particles) {
p.x += p.vx; p.y += p.vy; p.x += p.vx; p.y += p.vy;
if (widget.themeType == AppThemeType.cyberpunk) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; } if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.music) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; }
else if (widget.themeType == AppThemeType.wood) { p.vy -= 0.2; p.x += math.sin(p.y * 0.05) * 2; } else if (widget.themeType == AppThemeType.wood) { p.vy -= 0.2; p.x += math.sin(p.y * 0.05) * 2; }
else if (widget.themeType == AppThemeType.arcade) { p.vy += 0.3; p.spin = 0; p.angle = 0; } else if (widget.themeType == AppThemeType.arcade) { p.vy += 0.3; p.spin = 0; p.angle = 0; }
else if (widget.themeType == AppThemeType.grimorio) { p.vy -= 0.1; p.x += math.sin(p.y * 0.02) * 1.5; p.size *= 0.995; } else if (widget.themeType == AppThemeType.grimorio) { p.vy -= 0.1; p.x += math.sin(p.y * 0.02) * 1.5; p.size *= 0.995; }
@ -555,7 +643,7 @@ class _VFXPainter extends CustomPainter {
for (var p in particles) { for (var p in particles) {
if (p.size < 0.5) continue; if (p.size < 0.5) continue;
final paint = Paint()..color = p.color..style = PaintingStyle.fill; final paint = Paint()..color = p.color..style = PaintingStyle.fill;
if (themeType == AppThemeType.cyberpunk) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); } if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); }
canvas.save(); canvas.translate(p.x, p.y); canvas.rotate(p.angle); canvas.save(); canvas.translate(p.x, p.y); canvas.rotate(p.angle);
if (themeType == AppThemeType.doodle) { if (themeType == AppThemeType.doodle) {

View file

@ -4,12 +4,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// Import separati e puliti import 'package:google_fonts/google_fonts.dart';
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../models/game_board.dart'; import '../../models/game_board.dart';
import '../../core/theme_manager.dart'; import '../../core/theme_manager.dart';
import '../../services/audio_service.dart'; import '../../services/audio_service.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../services/storage_service.dart';
import '../home/dialog.dart'; // <--- IMPORTANTE: Importa il TutorialDialog
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
if (themeType == AppThemeType.doodle) {
return GoogleFonts.permanentMarker(textStyle: baseStyle);
} else if (themeType == AppThemeType.arcade) {
return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(
fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null,
letterSpacing: 0.5,
));
} else if (themeType == AppThemeType.grimorio) {
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
}
return baseStyle;
}
class ScoreBoard extends StatefulWidget { class ScoreBoard extends StatefulWidget {
const ScoreBoard({super.key}); const ScoreBoard({super.key});
@ -32,21 +48,24 @@ class _ScoreBoardState extends State<ScoreBoard> {
bool isRedTurn = controller.board.currentPlayer == Player.red; bool isRedTurn = controller.board.currentPlayer == Player.red;
bool isMuted = AudioService.instance.isMuted; bool isMuted = AudioService.instance.isMuted;
String nameRed = "ROSSO"; String myName = StorageService.instance.playerName.toUpperCase();
String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU"; if (myName.isEmpty) myName = "TU";
String nameRed = myName;
String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU";
if (controller.isOnline) { if (controller.isOnline) {
nameRed = controller.onlineHostName.toUpperCase(); nameRed = controller.onlineHostName.toUpperCase();
nameBlue = controller.onlineGuestName.toUpperCase(); nameBlue = controller.onlineGuestName.toUpperCase();
} else if (controller.isVsCPU) { } else if (controller.isVsCPU) {
nameRed = "TU"; nameRed = myName;
nameBlue = "CPU"; nameBlue = "CPU";
} }
return Container( return Container(
padding: const EdgeInsets.only(top: 10, bottom: 20, left: 20, right: 20), padding: const EdgeInsets.only(top: 10, bottom: 20, left: 20, right: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.background.withOpacity(0.95), color: themeType == AppThemeType.doodle ? theme.background : theme.background.withOpacity(0.95),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.3), color: Colors.black.withOpacity(0.3),
@ -62,33 +81,84 @@ class _ScoreBoardState extends State<ScoreBoard> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_PlayerScore(color: theme.playerRed, score: redScore, isTurn: isRedTurn, textColor: theme.text, title: nameRed), _PlayerScore(color: theme.playerRed, score: redScore, isTurn: isRedTurn, textColor: theme.text, title: nameRed, themeType: themeType),
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
"TETRAQ", "TETRAQ",
style: TextStyle( style: _getTextStyle(themeType, TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
color: theme.text, color: theme.text,
letterSpacing: 4, letterSpacing: 4,
shadows: [Shadow(color: Colors.black.withOpacity(0.3), offset: const Offset(1, 2), blurRadius: 2)] shadows: themeType == AppThemeType.doodle
) ? [
// EFFETTO RILIEVO (Luce in alto a sx, ombra in basso a dx)
const Shadow(color: Colors.white, offset: Offset(-1.5, -1.5), blurRadius: 1),
Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(1.5, 1.5), blurRadius: 2),
]
: [Shadow(color: Colors.black.withOpacity(0.3), offset: const Offset(1, 2), blurRadius: 2)]
))
), ),
IconButton( const SizedBox(height: 8),
icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: theme.text.withOpacity(0.7)),
onPressed: () { // --- ROW DEI PULSANTI AGGIORNATA ---
Row(
mainAxisSize: MainAxisSize.min,
children: [
// TASTO AUDIO CON CONTORNO
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() { setState(() {
AudioService.instance.toggleMute(); AudioService.instance.toggleMute();
}); });
}, },
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: themeType == AppThemeType.doodle ? Colors.transparent : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: themeType == AppThemeType.doodle ? const Color(0xFF111122) : theme.text.withOpacity(0.3), width: 1.5),
),
child: Icon(
isMuted ? Icons.volume_off : Icons.volume_up,
color: themeType == AppThemeType.doodle ? const Color(0xFF111122) : theme.text.withOpacity(0.8),
size: 16
),
),
),
const SizedBox(width: 10),
// TASTO INFORMAZIONI (TUTORIAL) CON CONTORNO
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
showDialog(context: context, builder: (ctx) => const TutorialDialog());
},
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: themeType == AppThemeType.doodle ? Colors.transparent : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: themeType == AppThemeType.doodle ? const Color(0xFF111122) : theme.text.withOpacity(0.3), width: 1.5),
),
child: Icon(
Icons.info_outline,
color: themeType == AppThemeType.doodle ? const Color(0xFF111122) : theme.text.withOpacity(0.8),
size: 16
),
),
),
],
), ),
], ],
), ),
_PlayerScore(color: theme.playerBlue, score: blueScore, isTurn: !isRedTurn, textColor: theme.text, title: nameBlue), _PlayerScore(color: theme.playerBlue, score: blueScore, isTurn: !isRedTurn, textColor: theme.text, title: nameBlue, themeType: themeType),
], ],
), ),
); );
@ -101,15 +171,16 @@ class _PlayerScore extends StatelessWidget {
final bool isTurn; final bool isTurn;
final Color textColor; final Color textColor;
final String title; final String title;
final AppThemeType themeType;
const _PlayerScore({required this.color, required this.score, required this.isTurn, required this.textColor, required this.title}); const _PlayerScore({required this.color, required this.score, required this.isTurn, required this.textColor, required this.title, required this.themeType});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: isTurn ? color : textColor.withOpacity(0.5), fontSize: 12)), Text(title, style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: isTurn ? color : textColor.withOpacity(0.5), fontSize: 12))),
const SizedBox(height: 5), const SizedBox(height: 5),
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@ -122,7 +193,7 @@ class _PlayerScore extends StatelessWidget {
BoxShadow(color: color.withOpacity(0.5), offset: const Offset(0, 4), blurRadius: 6) BoxShadow(color: color.withOpacity(0.5), offset: const Offset(0, 4), blurRadius: 6)
] : [], ] : [],
), ),
child: Text('$score', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: isTurn ? Colors.white : textColor.withOpacity(0.5))), child: Text('$score', style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: isTurn ? Colors.white : textColor.withOpacity(0.5)))),
), ),
], ],
); );

375
lib/ui/home/dialog.dart Normal file
View file

@ -0,0 +1,375 @@
// ===========================================================================
// FILE: lib/ui/home/dialogs/dialog.dart
// ===========================================================================
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../../../core/theme_manager.dart';
import '../../../core/app_colors.dart';
import '../../../l10n/app_localizations.dart';
import '../../../widgets/painters.dart';
import '../../../widgets/cyber_border.dart';
// ===========================================================================
// 1. DIALOGO MISSIONI (QUESTS)
// ===========================================================================
class QuestsDialog extends StatelessWidget {
const QuestsDialog({super.key});
@override
Widget build(BuildContext context) {
final themeManager = context.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
final loc = AppLocalizations.of(context)!;
return FutureBuilder<SharedPreferences>(
future: SharedPreferences.getInstance(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
final prefs = snapshot.data!;
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.all(20),
child: Container(
padding: const EdgeInsets.all(25.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2),
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)]
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.assignment_turned_in, size: 50, color: theme.playerBlue),
const SizedBox(height: 10),
Text(loc.questsTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
const SizedBox(height: 25),
...List.generate(3, (index) {
int i = index + 1;
int type = prefs.getInt('q${i}_type') ?? 0;
int prog = prefs.getInt('q${i}_prog') ?? 0;
int target = prefs.getInt('q${i}_target') ?? 1;
String title = "";
IconData icon = Icons.star;
if (type == 0) { title = "Vinci partite Online"; icon = Icons.public; }
else if (type == 1) { title = "Vinci contro la CPU"; icon = Icons.smart_toy; }
else { title = "Gioca in Arene Speciali"; icon = Icons.extension; }
bool completed = prog >= target;
double percent = (prog / target).clamp(0.0, 1.0);
return Container(
margin: const EdgeInsets.only(bottom: 15),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: completed ? Colors.green.withOpacity(0.1) : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: completed ? Colors.green : theme.gridLine.withOpacity(0.3)),
),
child: Row(
children: [
Icon(icon, color: completed ? Colors.green : theme.text.withOpacity(0.6), size: 30),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: completed ? Colors.green : theme.text))),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(value: percent, backgroundColor: theme.gridLine.withOpacity(0.2), color: completed ? Colors.green : theme.playerBlue, minHeight: 8),
)
],
),
),
const SizedBox(width: 10),
Text("$prog / $target", style: getSharedTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6)))),
],
),
);
}),
const SizedBox(height: 15),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(context),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
),
);
}
);
}
}
// ===========================================================================
// 2. DIALOGO CLASSIFICA (LEADERBOARD)
// ===========================================================================
class LeaderboardDialog extends StatelessWidget {
const LeaderboardDialog({super.key});
@override
Widget build(BuildContext context) {
final themeManager = context.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
final loc = AppLocalizations.of(context)!;
Widget content = Container(
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
border: Border.all(color: Colors.amber.withOpacity(0.8), width: 2),
boxShadow: [BoxShadow(color: Colors.amber.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)]
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.emoji_events, size: 50, color: Colors.amber),
const SizedBox(height: 10),
Text(loc.leaderboardTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
const SizedBox(height: 20),
SizedBox(
height: 350,
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('xp', descending: true).limit(50).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("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
}
final docs = snapshot.data!.docs;
return ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: docs.length,
itemBuilder: (context, index) {
var data = docs[index].data() as Map<String, dynamic>;
String? myUid = FirebaseAuth.instance.currentUser?.uid;
bool isMe = docs[index].id == myUid;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(10),
border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null
),
child: Row(
children: [
Text("#${index + 1}", style: getSharedTextStyle(themeType, TextStyle(fontWeight: FontWeight.w900, color: index == 0 ? Colors.amber : (index == 1 ? Colors.grey.shade400 : (index == 2 ? Colors.brown.shade300 : theme.text.withOpacity(0.5)))))),
const SizedBox(width: 15),
Expanded(child: Text(data['name'] ?? 'Unknown', style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text("Lv. ${data['level'] ?? 1}", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 12)),
Text("${data['xp'] ?? 0} XP", style: TextStyle(color: theme.text.withOpacity(0.6), fontSize: 10)),
],
)
],
),
);
}
);
}
),
),
const SizedBox(height: 15),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.amber.shade700, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(context),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
);
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
content = AnimatedCyberBorder(child: content);
}
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content);
}
}
// ===========================================================================
// 3. DIALOGO TUTORIAL
// ===========================================================================
class TutorialDialog extends StatelessWidget {
const TutorialDialog({super.key});
@override
Widget build(BuildContext context) {
final themeManager = context.watch<ThemeManager>();
final theme = themeManager.currentColors;
final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122);
String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:";
String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:";
String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:";
Widget dialogContent = themeType == AppThemeType.doodle
? Transform.rotate(
angle: -0.01,
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade50, strokeColor: inkColor, seed: 400),
child: Padding(
padding: const EdgeInsets.all(25.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Text("COME GIOCARE", style: getSharedTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))),
const SizedBox(height: 20),
TutorialStep(icon: Icons.line_axis, text: "Chiudi i 4 lati di un quadrato per conquistare 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! Ogni quadrato nasconde un'insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
const Divider(color: Colors.black26, thickness: 2),
const SizedBox(height: 10),
Center(child: Text("GLOSSARIO ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor)))),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber.shade700, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: Colors.green.shade600, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 25),
Center(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.red.shade200, strokeColor: inkColor, seed: 401),
child: Container(
height: 50, width: 150, alignment: Alignment.center,
child: Text("HO CAPITO!", style: getSharedTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor))),
),
),
),
)
],
),
),
),
)
: Container(
padding: const EdgeInsets.all(25.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]),
borderRadius: BorderRadius.circular(25),
border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5),
boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))],
),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Text("COME GIOCARE", style: getSharedTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2)))),
const SizedBox(height: 20),
TutorialStep(icon: Icons.grid_4x4, text: "Chiudi i 4 lati di un quadrato per conquistare 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! Ogni quadrato nasconde un'insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 15),
const Divider(color: Colors.white24, thickness: 1.5),
const SizedBox(height: 10),
Center(child: Text("GLOSSARIO ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.7), letterSpacing: 1.5)))),
const SizedBox(height: 15),
TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: theme.playerBlue, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10),
TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 30),
SizedBox(
width: double.infinity, height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => Navigator.pop(context),
child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)),
),
)
],
),
),
);
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
dialogContent = AnimatedCyberBorder(child: dialogContent);
}
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: dialogContent);
}
}
class TutorialStep extends StatelessWidget {
final IconData icon;
final Color? iconColor;
final String text;
final AppThemeType themeType;
final Color inkColor;
final ThemeColors theme;
const TutorialStep({super.key, required this.icon, this.iconColor, required this.text, required this.themeType, required this.inkColor, required this.theme});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: iconColor ?? (themeType == AppThemeType.doodle ? inkColor : theme.playerBlue), size: 28),
const SizedBox(width: 15),
Expanded(
child: Text(text, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, color: themeType == AppThemeType.doodle ? inkColor : theme.text.withOpacity(0.8), height: 1.3))),
),
],
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,12 @@
// ===========================================================================
// FILE: lib/ui/multiplayer/lobby_screen.dart
// ===========================================================================
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../models/game_board.dart'; import '../../models/game_board.dart';
@ -240,14 +245,14 @@ class _NeonTimeSwitch extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 24), Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20),
const SizedBox(width: 12), const SizedBox(width: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 14, letterSpacing: 2.0))), Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 11, fontWeight: FontWeight.bold))), Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
], ],
), ),
], ],
@ -284,14 +289,107 @@ class _NeonTimeSwitch extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 24), Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20),
const SizedBox(width: 12), const SizedBox(width: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 13, letterSpacing: 1.5))), Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 10, fontWeight: FontWeight.bold))), Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold))),
],
),
],
),
),
);
}
}
class _NeonPrivacySwitch extends StatelessWidget {
final bool isPublic;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
const _NeonPrivacySwitch({required this.isPublic, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600;
return Transform.rotate(
angle: 0.015,
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0),
decoration: BoxDecoration(
color: isPublic ? doodleColor : Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topRight: Radius.circular(8),
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15),
),
border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
],
),
],
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isPublic
? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)]
: [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)],
),
border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1),
boxShadow: isPublic
? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
: [
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))),
], ],
), ),
], ],
@ -313,7 +411,7 @@ class _NeonActionButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) { if (themeType == AppThemeType.doodle) {
double tilt = (label == "UNISCITI") ? -0.015 : 0.02; double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
return Transform.rotate( return Transform.rotate(
angle: tilt, angle: tilt,
child: GestureDetector( child: GestureDetector(
@ -330,10 +428,16 @@ class _NeonActionButton extends StatelessWidget {
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)], boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
), ),
child: Center( child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))), child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
), ),
), ),
), ),
),
),
); );
} }
@ -351,9 +455,15 @@ class _NeonActionButton extends StatelessWidget {
], ],
), ),
child: Center( child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))), child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
), ),
), ),
),
),
); );
} }
} }
@ -423,7 +533,7 @@ class LobbyScreen extends StatefulWidget {
State<LobbyScreen> createState() => _LobbyScreenState(); State<LobbyScreen> createState() => _LobbyScreenState();
} }
class _LobbyScreenState extends State<LobbyScreen> { class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
final MultiplayerService _multiplayerService = MultiplayerService(); final MultiplayerService _multiplayerService = MultiplayerService();
late TextEditingController _codeController; late TextEditingController _codeController;
@ -431,13 +541,20 @@ class _LobbyScreenState extends State<LobbyScreen> {
String? _myRoomCode; String? _myRoomCode;
String _playerName = ''; String _playerName = '';
// Variabile per gestire l'effetto "sipario"
bool _isCreatingRoom = false;
int _selectedRadius = 4; int _selectedRadius = 4;
ArenaShape _selectedShape = ArenaShape.classic; ArenaShape _selectedShape = ArenaShape.classic;
bool _isTimeMode = true; bool _isTimeMode = true;
bool _isPublicRoom = true;
bool _roomStarted = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
_codeController = TextEditingController(); _codeController = TextEditingController();
_playerName = StorageService.instance.playerName; _playerName = StorageService.instance.playerName;
@ -449,30 +566,53 @@ class _LobbyScreenState extends State<LobbyScreen> {
} }
@override @override
void dispose() { _codeController.dispose(); super.dispose(); } void dispose() {
WidgetsBinding.instance.removeObserver(this);
_cleanupGhostRoom();
_codeController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) {
_cleanupGhostRoom();
}
}
void _cleanupGhostRoom() {
if (_myRoomCode != null && !_roomStarted) {
FirebaseFirestore.instance.collection('games').doc(_myRoomCode).delete();
_myRoomCode = null;
}
}
Future<void> _createRoom() async { Future<void> _createRoom() async {
if (_isLoading) return; if (_isLoading) return;
setState(() => _isLoading = true); setState(() => _isLoading = true);
try { try {
String code = await _multiplayerService.createGameRoom(_selectedRadius, _playerName, _selectedShape.name, _isTimeMode); String code = await _multiplayerService.createGameRoom(
_selectedRadius, _playerName, _selectedShape.name, _isTimeMode, isPublic: _isPublicRoom
);
if (!mounted) return; if (!mounted) return;
setState(() { _myRoomCode = code; _isLoading = false; }); setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; });
if (!_isPublicRoom) {
_multiplayerService.shareInviteLink(code); _multiplayerService.shareInviteLink(code);
}
_showWaitingDialog(code); _showWaitingDialog(code);
} catch (e) { } catch (e) {
if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); } if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); }
} }
} }
Future<void> _joinRoom() async { Future<void> _joinRoomByCode(String code) async {
if (_isLoading) return; if (_isLoading) return;
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
String code = _codeController.text.trim().toUpperCase(); code = code.trim().toUpperCase();
if (code.isEmpty || code.length != 5) { _showError("Inserisci un codice valido di 5 caratteri."); return; } if (code.isEmpty || code.length != 5) { _showError("Inserisci un codice valido di 5 caratteri."); return; }
setState(() => _isLoading = true); setState(() => _isLoading = true);
@ -532,10 +672,10 @@ class _LobbyScreenState extends State<LobbyScreen> {
), ),
child: Column( child: Column(
children: [ children: [
Icon(Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12), Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12),
Text("Invita un amico", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), Text(_isPublicRoom ? "Sei in Bacheca!" : "Invita un amico", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))),
], ],
), ),
), ),
@ -564,6 +704,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
if (snapshot.hasData && snapshot.data!.exists) { if (snapshot.hasData && snapshot.data!.exists) {
var data = snapshot.data!.data() as Map<String, dynamic>; var data = snapshot.data!.data() as Map<String, dynamic>;
if (data['status'] == 'playing') { if (data['status'] == 'playing') {
_roomStarted = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context); Navigator.pop(context);
context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode); context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode);
@ -572,7 +713,14 @@ class _LobbyScreenState extends State<LobbyScreen> {
} }
} }
return Dialog( return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) return;
_cleanupGhostRoom();
Navigator.pop(context);
},
child: Dialog(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -581,11 +729,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
dialogContent, dialogContent,
const SizedBox(height: 20), const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () { FirebaseFirestore.instance.collection('games').doc(code).delete(); Navigator.pop(context); }, onPressed: () {
_cleanupGhostRoom();
Navigator.pop(context);
},
child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))),
), ),
], ],
), ),
),
); );
}, },
); );
@ -605,9 +757,9 @@ class _LobbyScreenState extends State<LobbyScreen> {
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
bool isChaosUnlocked = true; bool isChaosUnlocked = true;
Color doodlePenColor = const Color(0xFF00008B); Color doodlePenColor = const Color(0xFF00008B);
// --- PANNELLO IMPOSTAZIONI STANZA ---
Widget hostPanel = Transform.rotate( Widget hostPanel = Transform.rotate(
angle: themeType == AppThemeType.doodle ? 0.01 : 0, angle: themeType == AppThemeType.doodle ? 0.01 : 0,
child: Container( child: Container(
@ -625,7 +777,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Center(child: Text("IMPOSTAZIONI GRIGLIA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))),
const SizedBox(height: 10), const SizedBox(height: 10),
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
@ -634,11 +786,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic)), Expanded(child: _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic))),
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross)), const SizedBox(width: 4),
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut)), Expanded(child: _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross))),
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass)), const SizedBox(width: 4),
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos)), Expanded(child: _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut))),
const SizedBox(width: 4),
Expanded(child: _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass))),
const SizedBox(width: 4),
Expanded(child: _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos))),
], ],
), ),
@ -662,9 +818,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5), Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5),
const SizedBox(height: 12), const SizedBox(height: 12),
Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), Text("REGOLE E VISIBILITÀ", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
const SizedBox(height: 8), const SizedBox(height: 8),
_NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode)), Row(
children: [
Expanded(child: _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))),
const SizedBox(width: 8),
Expanded(child: _NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))),
],
)
], ],
), ),
), ),
@ -676,7 +838,9 @@ class _LobbyScreenState extends State<LobbyScreen> {
Widget uiContent = SafeArea( Widget uiContent = SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), physics: const BouncingScrollPhysics(),
// Padding inferiore aumentato a 60 per evitare il taglio dei pulsanti
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 10.0, bottom: MediaQuery.of(context).padding.bottom + 60.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@ -703,12 +867,36 @@ class _LobbyScreenState extends State<LobbyScreen> {
), ),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// --- L'EFFETTO SIPARIO CON ANIMATED SIZE ---
AnimatedSize(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
alignment: Alignment.topCenter,
child: _isCreatingRoom
? Column( // MENU CREAZIONE (Aperto)
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
hostPanel, hostPanel,
const SizedBox(height: 15), const SizedBox(height: 15),
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType), Row(
children: [
Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
child: _NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
),
const SizedBox(width: 10),
Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
child: _NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType),
),
],
),
],
)
: Column( // MENU BASE (Chiuso)
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType),
const SizedBox(height: 20), const SizedBox(height: 20),
Row( Row(
children: [ children: [
@ -745,9 +933,132 @@ class _LobbyScreenState extends State<LobbyScreen> {
), ),
), ),
const SizedBox(height: 15), const SizedBox(height: 15),
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: _joinRoom, theme: theme, themeType: themeType), _NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
],
),
),
const SizedBox(height: 10), const SizedBox(height: 25),
Row(
children: [
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
],
),
const SizedBox(height: 15),
// --- LA VERA E PROPRIA BACHECA PUBBLICA ---
StreamBuilder<QuerySnapshot>(
stream: _multiplayerService.getPublicRooms(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Padding(padding: const EdgeInsets.all(20), child: Center(child: CircularProgressIndicator(color: theme.playerBlue)));
}
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))),
);
}
DateTime now = DateTime.now();
String? myUid = FirebaseAuth.instance.currentUser?.uid;
var docs = snapshot.data!.docs.where((doc) {
var data = doc.data() as Map<String, dynamic>;
if (data['isPublic'] != true) return false;
if (data['hostUid'] != null && data['hostUid'] == myUid) return false;
Timestamp? createdAt = data['createdAt'] as Timestamp?;
if (createdAt != null) {
int ageInMinutes = now.difference(createdAt.toDate()).inMinutes;
if (ageInMinutes > 15) {
FirebaseFirestore.instance.collection('games').doc(doc.id).delete();
return false;
}
}
return true;
}).toList();
if (docs.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))),
);
}
docs.sort((a, b) {
Timestamp? tA = (a.data() as Map<String, dynamic>)['createdAt'] as Timestamp?;
Timestamp? tB = (b.data() as Map<String, dynamic>)['createdAt'] as Timestamp?;
if (tA == null || tB == null) return 0;
return tB.compareTo(tA);
});
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: docs.length,
itemBuilder: (context, index) {
var doc = docs[index];
var data = doc.data() as Map<String, dynamic>;
String host = data['hostName'] ?? 'Sconosciuto';
int r = data['radius'] ?? 4;
String shapeStr = data['shape'] ?? 'classic';
bool time = data['timeMode'] ?? true;
String prettyShape = "Rombo";
if (shapeStr == 'cross') prettyShape = "Croce";
else if (shapeStr == 'donut') prettyShape = "Buco";
else if (shapeStr == 'hourglass') prettyShape = "Clessidra";
else if (shapeStr == 'chaos') prettyShape = "Caos";
return Transform.rotate(
angle: themeType == AppThemeType.doodle ? (index % 2 == 0 ? 0.01 : -0.01) : 0,
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: themeType == AppThemeType.doodle ? theme.text : theme.playerBlue.withOpacity(0.3), width: themeType == AppThemeType.doodle ? 2 : 1),
boxShadow: themeType == AppThemeType.doodle ? [BoxShadow(color: theme.text.withOpacity(0.6), offset: const Offset(3, 4))] : [],
),
child: Row(
children: [
CircleAvatar(backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Stanza di $host", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 16))),
const SizedBox(height: 4),
Text("Raggio: $r$prettyShape${time ? 'A Tempo' : 'Relax'}", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11))),
],
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: theme.playerBlue, foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: themeType == AppThemeType.doodle ? 0 : 2,
side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none,
),
onPressed: () => _joinRoomByCode(doc.id),
child: Text("ENTRA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))),
)
],
),
),
);
}
);
}
),
const SizedBox(height: 20),
], ],
), ),
), ),

View file

@ -35,10 +35,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
children: [ children: [
_ThemeCard( _ThemeCard(
title: "Minimal", title: "Quaderno (Doodle)",
subtitle: "Linee pulite, sfondo chiaro", subtitle: "Sfondo a quadretti, tratto a penna",
type: AppThemeType.minimal, type: AppThemeType.doodle,
previewColors: AppColors.minimal, previewColors: AppColors.doodle,
requiredLevel: 1, requiredLevel: 1,
currentLevel: playerLevel, currentLevel: playerLevel,
), ),
@ -52,15 +52,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
currentLevel: playerLevel, currentLevel: playerLevel,
), ),
const SizedBox(height: 15), const SizedBox(height: 15),
_ThemeCard(
title: "Quaderno (Doodle)",
subtitle: "Sfondo a quadretti, tratto a penna",
type: AppThemeType.doodle,
previewColors: AppColors.doodle,
requiredLevel: 5,
currentLevel: playerLevel,
),
const SizedBox(height: 15),
_ThemeCard( _ThemeCard(
title: "Cyberpunk", title: "Cyberpunk",
subtitle: "Nero profondo, luci al neon", subtitle: "Nero profondo, luci al neon",
@ -87,6 +78,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
requiredLevel: 15, requiredLevel: 15,
currentLevel: playerLevel, currentLevel: playerLevel,
), ),
const SizedBox(height: 15),
_ThemeCard(
title: "Musica",
subtitle: "Vinili, cassette e vibrazioni sonore",
type: AppThemeType.music,
previewColors: AppColors.music,
requiredLevel: 20, // Tema Esclusivo di Livello 20!
currentLevel: playerLevel,
),
], ],
), ),
); );
@ -164,9 +164,9 @@ class _ThemeCard extends StatelessWidget {
], ],
), ),
), ),
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle)), Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerRed.withOpacity(0.5), blurRadius: 4)])),
const SizedBox(width: 10), const SizedBox(width: 10),
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle)), Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerBlue.withOpacity(0.5), blurRadius: 4)])),
], ],
), ),
), ),

View file

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../core/theme_manager.dart'; // Import aggiornato
import 'dart:math' as math;
class AnimatedCyberBorder extends StatefulWidget {
final Widget child;
const AnimatedCyberBorder({super.key, required this.child});
@override
State<AnimatedCyberBorder> createState() => _AnimatedCyberBorderState();
}
class _AnimatedCyberBorderState extends State<AnimatedCyberBorder> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); }
@override
void dispose() { _controller.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
final theme = context.watch<ThemeManager>().currentColors;
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed),
child: Container(
decoration: BoxDecoration(color: theme.background.withOpacity(0.9), borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 25, spreadRadius: 2)]),
padding: const EdgeInsets.all(3),
child: widget.child,
),
);
},
child: widget.child,
);
}
}
class CyberBorderPainter extends CustomPainter {
final double animationValue;
final Color color1;
final Color color2;
CyberBorderPainter({required this.animationValue, required this.color1, required this.color2});
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(15));
final Paint paint = Paint()
..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect)
..style = PaintingStyle.stroke
..strokeWidth = 4.0
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 4);
canvas.drawRRect(rrect, paint);
}
@override bool shouldRepaint(covariant CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
}

View file

@ -1,8 +1,13 @@
// ===========================================================================
// FILE: lib/widgets/game_over_dialog.dart
// ===========================================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../logic/game_controller.dart'; import '../logic/game_controller.dart';
import '../core/theme_manager.dart'; import '../core/theme_manager.dart';
import '../core/app_colors.dart'; import '../core/app_colors.dart';
import '../services/storage_service.dart';
class GameOverDialog extends StatelessWidget { class GameOverDialog extends StatelessWidget {
const GameOverDialog({super.key}); const GameOverDialog({super.key});
@ -19,15 +24,18 @@ class GameOverDialog extends StatelessWidget {
bool playerBeatCPU = game.isVsCPU && red > blue; bool playerBeatCPU = game.isVsCPU && red > blue;
String myName = StorageService.instance.playerName.toUpperCase();
if (myName.isEmpty) myName = "TU";
// --- LOGICA NOMI --- // --- LOGICA NOMI ---
String nameRed = "ROSSO"; String nameRed = myName;
String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU"; String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU";
if (game.isOnline) { if (game.isOnline) {
nameRed = game.onlineHostName.toUpperCase(); nameRed = game.onlineHostName.toUpperCase();
nameBlue = game.onlineGuestName.toUpperCase(); nameBlue = game.onlineGuestName.toUpperCase();
} else if (game.isVsCPU) { } else if (game.isVsCPU) {
nameRed = "TU"; nameRed = myName;
nameBlue = "CPU"; nameBlue = "CPU";
} }

View file

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import '../core/app_colors.dart'; // Import aggiornato
import 'painters.dart';
class FeatureCard extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
final bool isFeatured;
final bool compact;
const FeatureCard({super.key, required this.title, required this.subtitle, required this.icon, required this.color, required this.theme, required this.themeType, required this.onTap, this.isFeatured = false, this.compact = false});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
double tilt = (title.length % 2 == 0) ? -0.015 : 0.02;
Color inkColor = const Color(0xFF111122);
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: onTap,
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: color, strokeColor: inkColor, seed: title.length * 5),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 22.0, vertical: compact ? 12.0 : 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, color: inkColor, size: compact ? 24 : 32),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: compact ? 16 : 24, fontWeight: FontWeight.w900)))),
if (!compact) ...[ const SizedBox(height: 2), Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.bold))) ]
],
),
),
if (!compact) Icon(Icons.chevron_right_rounded, color: inkColor.withOpacity(0.6), size: 32),
],
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 20.0, vertical: compact ? 10.0 : 14.0),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isFeatured ? [color.withOpacity(0.9), color.withOpacity(0.6)] : [color.withOpacity(0.25), color.withOpacity(0.05)]),
borderRadius: BorderRadius.circular(15), border: Border.all(color: color.withOpacity(isFeatured ? 0.5 : 0.2), width: 1.5),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(0, 8), blurRadius: 15), BoxShadow(color: color.withOpacity(isFeatured ? 0.3 : 0.05), offset: const Offset(-1, -1), blurRadius: 5)]
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(compact ? 6 : 10),
decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.05)]), shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.2)), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 5, offset: const Offset(2, 4))]),
child: Icon(icon, color: isFeatured ? Colors.white : color, size: compact ? 20 : 26),
),
SizedBox(width: compact ? 10 : 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white : theme.text, fontSize: compact ? 14 : 18, fontWeight: FontWeight.w900, shadows: [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)])))),
if (!compact) ...[ const SizedBox(height: 2), Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white.withOpacity(0.8) : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: FontWeight.bold))) ]
],
),
),
if (!compact) Icon(Icons.chevron_right_rounded, color: isFeatured ? Colors.white.withOpacity(0.7) : color.withOpacity(0.5), size: 30),
],
),
),
);
}
}

View file

@ -0,0 +1,129 @@
// ===========================================================================
// FILE: lib/widgets/music_theme_widgets.dart
// ===========================================================================
import 'package:flutter/material.dart';
import '../core/app_colors.dart';
import 'painters.dart';
class MusicCassetteCard extends StatelessWidget {
final String title;
final String subtitle;
final Color neonColor;
final double angle;
final IconData leftIcon;
final IconData rightIcon;
final VoidCallback onTap;
final AppThemeType themeType;
const MusicCassetteCard({super.key, required this.title, required this.subtitle, required this.neonColor, required this.angle, required this.leftIcon, required this.rightIcon, required this.onTap, required this.themeType});
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: angle,
child: GestureDetector(
onTap: onTap,
child: Container(
// Aumentato leggermente l'altezza a 125 per evitare l'overflow
height: 125, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF22222A), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 2),
boxShadow: [ BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2), const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15) ]
),
child: Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(color: neonColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5)),
child: Row(
children: [
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// Aggiunto minAxisSize per far stare il contenuto stretto
mainAxisSize: MainAxisSize.min,
children: [
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)]))))),
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(subtitle, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold))))),
],
),
),
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)),
],
),
),
),
const SizedBox(height: 10),
Container(
height: 35, width: 180, decoration: BoxDecoration(color: const Color(0xFF0D0D12), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white24, width: 1)),
child: Stack(
alignment: Alignment.center,
children: [
Container(height: 2, width: 120, color: const Color(0xFF333333)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildSpool(), _buildSpool() ]),
],
),
),
],
),
),
),
);
}
Widget _buildSpool() {
return Container(
width: 26, height: 26, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white70, border: Border.all(color: Colors.black87, width: 5)),
child: Center(child: Container(width: 6, height: 6, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black))),
);
}
}
class MusicKnobCard extends StatelessWidget {
final String title;
final IconData icon;
final VoidCallback onTap;
final AppThemeType themeType;
final Color? iconColor;
const MusicKnobCard({super.key, required this.title, required this.icon, required this.onTap, required this.themeType, this.iconColor});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 65, height: 65,
decoration: BoxDecoration(
shape: BoxShape.circle, color: const Color(0xFF222222), border: Border.all(color: const Color(0xFF111111), width: 2),
boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)), BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1))],
),
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle, border: Border.all(color: Colors.black54, width: 1),
gradient: const SweepGradient(colors: [Color(0xFF555555), Color(0xFFAAAAAA), Color(0xFF555555), Color(0xFF222222), Color(0xFF555555)]),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xFF1A1A1A)),
child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20)),
),
),
),
),
),
const SizedBox(height: 10),
FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0)))),
],
),
);
}
}

73
lib/widgets/painters.dart Normal file
View file

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'dart:math' as math;
import '../core/app_colors.dart'; // Import aggiornato
TextStyle getSharedTextStyle(AppThemeType themeType, TextStyle baseStyle) {
if (themeType == AppThemeType.doodle) {
return GoogleFonts.permanentMarker(textStyle: baseStyle);
} else if (themeType == AppThemeType.arcade) {
return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null, letterSpacing: 0.5));
} else if (themeType == AppThemeType.grimorio) {
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
} else if (themeType == AppThemeType.music) {
return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5));
}
return baseStyle;
}
class DoodleBackgroundPainter extends CustomPainter {
final Color fillColor; final Color strokeColor; final int seed; final bool isCircle;
DoodleBackgroundPainter({required this.fillColor, required this.strokeColor, required this.seed, this.isCircle = false});
@override
void paint(Canvas canvas, Size size) {
final math.Random random = math.Random(seed);
double wobble() => random.nextDouble() * 6 - 3;
final Paint fillPaint = Paint()..color = fillColor..style = PaintingStyle.fill;
final Paint strokePaint = Paint()..color = strokeColor..strokeWidth = 2.5..style = PaintingStyle.stroke..strokeCap = StrokeCap.round..strokeJoin = StrokeJoin.round;
if (isCircle) {
final Rect rect = Rect.fromLTWH(wobble(), wobble(), size.width + wobble(), size.height + wobble());
canvas.save(); canvas.translate(wobble(), wobble()); canvas.drawOval(rect, fillPaint); canvas.restore();
canvas.drawOval(rect, strokePaint);
canvas.save(); canvas.translate(random.nextDouble() * 4 - 2, random.nextDouble() * 4 - 2); canvas.drawOval(rect, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); canvas.restore();
} else {
final Path path = Path()..moveTo(wobble(), wobble())..lineTo(size.width + wobble(), wobble())..lineTo(size.width + wobble(), size.height + wobble())..lineTo(wobble(), size.height + wobble())..close();
final Path fillPath = Path()..moveTo(wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, size.height + wobble() * 1.5)..lineTo(wobble() * 1.5, size.height + wobble() * 1.5)..close();
canvas.drawPath(fillPath, fillPaint);
canvas.drawPath(path, strokePaint);
canvas.save(); canvas.translate(random.nextDouble() * 3 - 1.5, random.nextDouble() * 3 - 1.5); canvas.drawPath(path, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); canvas.restore();
}
}
@override bool shouldRepaint(covariant DoodleBackgroundPainter oldDelegate) => oldDelegate.fillColor != fillColor || oldDelegate.strokeColor != strokeColor;
}
class AudioCablesPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = const Color(0xFF151515)..style = PaintingStyle.stroke..strokeWidth = 8.0..strokeCap = StrokeCap.round;
final highlight = Paint()..color = const Color(0xFF3A3A3A)..style = PaintingStyle.stroke..strokeWidth = 2.0..strokeCap = StrokeCap.round;
void drawCable(Path path) { canvas.drawPath(path, paint); canvas.drawPath(path, highlight); }
Path c1 = Path()..moveTo(-20, size.height * 0.2)..quadraticBezierTo(100, size.height * 0.25, 50, size.height * 0.4)..quadraticBezierTo(0, size.height * 0.5, -20, size.height * 0.55); drawCable(c1);
Path c2 = Path()..moveTo(size.width + 20, size.height * 0.4)..quadraticBezierTo(size.width - 100, size.height * 0.5, size.width - 50, size.height * 0.7)..quadraticBezierTo(size.width, size.height * 0.8, size.width + 20, size.height * 0.85); drawCable(c2);
Path c3 = Path()..moveTo(size.width * 0.2, size.height + 20)..quadraticBezierTo(size.width * 0.3, size.height - 80, size.width * 0.5, size.height - 60)..quadraticBezierTo(size.width * 0.7, size.height - 40, size.width * 0.8, size.height + 20); drawCable(c3);
_drawJack(canvas, Offset(80, size.height * 0.38), -0.5);
_drawJack(canvas, Offset(size.width - 60, size.height * 0.68), 0.8);
}
void _drawJack(Canvas canvas, Offset pos, double angle) {
canvas.save(); canvas.translate(pos.dx, pos.dy); canvas.rotate(angle);
canvas.drawRect(const Rect.fromLTWH(-15, -4, 15, 8), Paint()..color = const Color(0xFF151515));
canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(0, -6, 25, 12), const Radius.circular(2)), Paint()..color = const Color(0xFF222222));
canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(2, -4, 21, 8), const Radius.circular(2)), Paint()..color = const Color(0xFF444444));
canvas.drawRect(const Rect.fromLTWH(25, -2, 15, 4), Paint()..color = const Color(0xFFCCCCCC));
canvas.drawRect(const Rect.fromLTWH(40, -1.5, 5, 3), Paint()..color = const Color(0xFFAAAAAA));
canvas.drawLine(const Offset(30, -2), const Offset(30, 2), Paint()..color = Colors.black..strokeWidth = 1.5);
canvas.drawLine(const Offset(35, -2), const Offset(35, 2), Paint()..color = Colors.black..strokeWidth = 1.5);
canvas.restore();
}
@override bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

BIN
macos/.DS_Store vendored

Binary file not shown.

View file

@ -8,6 +8,8 @@ import Foundation
import app_links import app_links
import audioplayers_darwin import audioplayers_darwin
import cloud_firestore import cloud_firestore
import firebase_app_check
import firebase_auth
import firebase_core import firebase_core
import share_plus import share_plus
import shared_preferences_foundation import shared_preferences_foundation
@ -16,6 +18,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View file

@ -1190,6 +1190,10 @@ PODS:
- abseil/xcprivacy (1.20240722.0) - abseil/xcprivacy (1.20240722.0)
- app_links (6.4.1): - app_links (6.4.1):
- FlutterMacOS - FlutterMacOS
- AppCheckCore (11.2.0):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- audioplayers_darwin (0.0.1): - audioplayers_darwin (0.0.1):
- FlutterMacOS - FlutterMacOS
- BoringSSL-GRPC (0.0.37): - BoringSSL-GRPC (0.0.37):
@ -1199,33 +1203,65 @@ PODS:
- BoringSSL-GRPC/Interface (= 0.0.37) - BoringSSL-GRPC/Interface (= 0.0.37)
- BoringSSL-GRPC/Interface (0.0.37) - BoringSSL-GRPC/Interface (0.0.37)
- cloud_firestore (6.1.2): - cloud_firestore (6.1.2):
- Firebase/CoreOnly (~> 12.8.0) - Firebase/CoreOnly (~> 12.9.0)
- Firebase/Firestore (~> 12.8.0) - Firebase/Firestore (~> 12.9.0)
- firebase_core - firebase_core
- FlutterMacOS - FlutterMacOS
- Firebase/CoreOnly (12.8.0): - Firebase/AppCheck (12.9.0):
- FirebaseCore (~> 12.8.0)
- Firebase/Firestore (12.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseFirestore (~> 12.8.0) - FirebaseAppCheck (~> 12.9.0)
- firebase_core (4.4.0): - Firebase/Auth (12.9.0):
- Firebase/CoreOnly (~> 12.8.0) - Firebase/CoreOnly
- FirebaseAuth (~> 12.9.0)
- Firebase/CoreOnly (12.9.0):
- FirebaseCore (~> 12.9.0)
- Firebase/Firestore (12.9.0):
- Firebase/CoreOnly
- FirebaseFirestore (~> 12.9.0)
- firebase_app_check (0.4.1-5):
- Firebase/AppCheck (~> 12.9.0)
- Firebase/CoreOnly (~> 12.9.0)
- firebase_core
- FlutterMacOS - FlutterMacOS
- FirebaseAppCheckInterop (12.8.0) - firebase_auth (6.1.4):
- FirebaseCore (12.8.0): - Firebase/Auth (~> 12.9.0)
- FirebaseCoreInternal (~> 12.8.0) - Firebase/CoreOnly (~> 12.9.0)
- firebase_core
- FlutterMacOS
- firebase_core (4.5.0):
- Firebase/CoreOnly (~> 12.9.0)
- FlutterMacOS
- FirebaseAppCheck (12.9.0):
- AppCheckCore (~> 11.0)
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- FirebaseAppCheckInterop (12.9.0)
- FirebaseAuth (12.9.0):
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseAuthInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.9.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1)
- GTMSessionFetcher/Core (< 6.0, >= 3.4)
- RecaptchaInterop (~> 101.0)
- FirebaseAuthInterop (12.9.0)
- FirebaseCore (12.9.0):
- FirebaseCoreInternal (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1) - GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreExtension (12.8.0): - FirebaseCoreExtension (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreInternal (12.8.0): - FirebaseCoreInternal (12.9.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)" - "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseFirestore (12.8.0): - FirebaseFirestore (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.8.0) - FirebaseCoreExtension (~> 12.9.0)
- FirebaseFirestoreInternal (~> 12.8.0) - FirebaseFirestoreInternal (~> 12.9.0)
- FirebaseSharedSwift (~> 12.8.0) - FirebaseSharedSwift (~> 12.9.0)
- FirebaseFirestoreInternal (12.8.0): - FirebaseFirestoreInternal (12.9.0):
- abseil/algorithm (~> 1.20240722.0) - abseil/algorithm (~> 1.20240722.0)
- abseil/base (~> 1.20240722.0) - abseil/base (~> 1.20240722.0)
- abseil/container/flat_hash_map (~> 1.20240722.0) - abseil/container/flat_hash_map (~> 1.20240722.0)
@ -1234,22 +1270,38 @@ PODS:
- abseil/strings/strings (~> 1.20240722.0) - abseil/strings/strings (~> 1.20240722.0)
- abseil/time (~> 1.20240722.0) - abseil/time (~> 1.20240722.0)
- abseil/types (~> 1.20240722.0) - abseil/types (~> 1.20240722.0)
- FirebaseAppCheckInterop (~> 12.8.0) - FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- "gRPC-C++ (~> 1.69.0)" - "gRPC-C++ (~> 1.69.0)"
- gRPC-Core (~> 1.69.0) - gRPC-Core (~> 1.69.0)
- leveldb-library (~> 1.22) - leveldb-library (~> 1.22)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseSharedSwift (12.8.0) - FirebaseSharedSwift (12.9.0)
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0): - GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0): - GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)": - "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0) - GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- "gRPC-C++ (1.69.0)": - "gRPC-C++ (1.69.0)":
- "gRPC-C++/Implementation (= 1.69.0)" - "gRPC-C++/Implementation (= 1.69.0)"
- "gRPC-C++/Interface (= 1.69.0)" - "gRPC-C++/Interface (= 1.69.0)"
@ -1342,12 +1394,14 @@ PODS:
- gRPC-Core/Privacy (= 1.69.0) - gRPC-Core/Privacy (= 1.69.0)
- gRPC-Core/Interface (1.69.0) - gRPC-Core/Interface (1.69.0)
- gRPC-Core/Privacy (1.69.0) - gRPC-Core/Privacy (1.69.0)
- GTMSessionFetcher/Core (5.1.0)
- leveldb-library (1.22.6) - leveldb-library (1.22.6)
- nanopb (3.30910.0): - nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0) - nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0) - nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0) - nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0) - nanopb/encode (3.30910.0)
- PromisesObjC (2.4.0)
- share_plus (0.0.1): - share_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@ -1358,6 +1412,8 @@ DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`)
- cloud_firestore (from `Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos`) - cloud_firestore (from `Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos`)
- firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`)
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
@ -1366,9 +1422,13 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- abseil - abseil
- AppCheckCore
- BoringSSL-GRPC - BoringSSL-GRPC
- Firebase - Firebase
- FirebaseAppCheck
- FirebaseAppCheckInterop - FirebaseAppCheckInterop
- FirebaseAuth
- FirebaseAuthInterop
- FirebaseCore - FirebaseCore
- FirebaseCoreExtension - FirebaseCoreExtension
- FirebaseCoreInternal - FirebaseCoreInternal
@ -1378,8 +1438,10 @@ SPEC REPOS:
- GoogleUtilities - GoogleUtilities
- "gRPC-C++" - "gRPC-C++"
- gRPC-Core - gRPC-Core
- GTMSessionFetcher
- leveldb-library - leveldb-library
- nanopb - nanopb
- PromisesObjC
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: app_links:
@ -1388,6 +1450,10 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos
cloud_firestore: cloud_firestore:
:path: Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos :path: Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos
firebase_app_check:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos
firebase_auth:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos
firebase_core: firebase_core:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
FlutterMacOS: FlutterMacOS:
@ -1400,24 +1466,32 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
abseil: a05cc83bf02079535e17169a73c5be5ba47f714b abseil: a05cc83bf02079535e17169a73c5be5ba47f714b
app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
audioplayers_darwin: 761f2948df701d05b5db603220c384fb55720012 audioplayers_darwin: 761f2948df701d05b5db603220c384fb55720012
BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508
cloud_firestore: 71947b640bd24f6f849d9d185e5d0a619fa6b93b cloud_firestore: a2a9382e6cc4dd07345748b904b3b194ea46be44
Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
firebase_core: b1697fb64ff2b9ca16baaa821205f8b0c058e5d2 firebase_app_check: 1ea404b52b0910bf632b1ea2e00ceb8d1730cb44
FirebaseAppCheckInterop: ba3dc604a89815379e61ec2365101608d365cf7d firebase_auth: 8db6796451d9aa44d4cc49b3e757865c65ce170f
FirebaseCore: 0dbad74bda10b8fb9ca34ad8f375fb9dd3ebef7c firebase_core: c74b220e9288decea6bed17399c249734a7e76d2
FirebaseCoreExtension: 6605938d51f765d8b18bfcafd2085276a252bee2 FirebaseAppCheck: 94dae4d9bb682bdef85a778b0c1024a4613f1e89
FirebaseCoreInternal: fe5fa466aeb314787093a7dce9f0beeaad5a2a21 FirebaseAppCheckInterop: 4bade10286cc977e516f75d2d8312cbdfa534789
FirebaseFirestore: 67f23000ca238ccbab79127ed59636a9a2689e74 FirebaseAuth: 3a39f6436c21ebfd7919b698228b4f89ff94c23b
FirebaseFirestoreInternal: a0e7382af3d208898dcd1d4d52d8a7870632e881 FirebaseAuthInterop: f8f6ff72dc24621906497fbe5cf3c42ee815e59c
FirebaseSharedSwift: f57ed48f4542b2d7eb4738f4f23ba443f78b3780 FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
FirebaseFirestore: d8b76ca1feb4ca0b0f078c45f7d1bd8014a49ef1
FirebaseFirestoreInternal: 02341a9ba87f6309227b04685022a5e16307bbf7
FirebaseSharedSwift: 9d2fa84a46676302b89dbd5e6e62bce2fe376909
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
"gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8
gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb

View file

@ -10,5 +10,7 @@
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>keychain-access-groups</key>
<array/>
</dict> </dict>
</plist> </plist>

View file

@ -8,5 +8,7 @@
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>keychain-access-groups</key>
<array/>
</dict> </dict>
</plist> </plist>

33
public/404.html Normal file
View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Not Found</title>
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px 16px; border-radius: 3px; }
#message h3 { color: #888; font-weight: normal; font-size: 16px; margin: 16px 0 12px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
@media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>404</h2>
<h1>Page Not Found</h1>
<p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>
<h3>Why am I seeing this?</h3>
<p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured <code>public</code> directory.</p>
</div>
</body>
</html>

89
public/index.html Normal file
View file

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to Firebase Hosting</title>
<!-- update the version number as needed -->
<script defer src="/__/firebase/12.10.0/firebase-app-compat.js"></script>
<!-- include only the Firebase features as you need -->
<script defer src="/__/firebase/12.10.0/firebase-auth-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-database-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-firestore-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-functions-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-messaging-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-storage-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-analytics-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-remote-config-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-performance-compat.js"></script>
<!--
initialize the SDK after all desired features are loaded, set useEmulator to false
to avoid connecting the SDK to running emulators.
-->
<script defer src="/__/firebase/init.js?useEmulator=true"></script>
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
@media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>Welcome</h2>
<h1>Firebase Hosting Setup Complete</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
</div>
<p id="load">Firebase SDK Loading&hellip;</p>
<script>
document.addEventListener('DOMContentLoaded', function() {
const loadEl = document.querySelector('#load');
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// // The Firebase SDK is initialized and available here!
//
// firebase.auth().onAuthStateChanged(user => { });
// firebase.database().ref('/path/to/ref').on('value', snapshot => { });
// firebase.firestore().doc('/foo/bar').get().then(() => { });
// firebase.functions().httpsCallable('yourFunction')().then(() => { });
// firebase.messaging().requestPermission().then(() => { });
// firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
// firebase.analytics(); // call to activate
// firebase.analytics().logEvent('tutorial_completed');
// firebase.performance(); // call to activate
//
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
try {
let app = firebase.app();
let features = [
'auth',
'database',
'firestore',
'functions',
'messaging',
'storage',
'analytics',
'remoteConfig',
'performance',
].filter(feature => typeof app[feature] === 'function');
loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;
} catch (e) {
console.error(e);
loadEl.textContent = 'Error loading the Firebase SDK, check the console.';
}
});
</script>
</body>
</html>

View file

@ -5,10 +5,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: cd83f7d6bd4e4c0b0b4fef802e8796784032e1cc23d7b0e982cf5d05d9bbe182 sha256: afe15ce18a287d2f89da95566e62892df339b1936bbe9b83587df45b944ee72a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.66" version: "1.3.67"
app_links: app_links:
dependency: "direct main" dependency: "direct main"
description: description:
@ -129,6 +129,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
change_app_package_name:
dependency: "direct dev"
description:
name: change_app_package_name
sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -249,14 +257,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
firebase_app_check:
dependency: "direct main"
description:
name: firebase_app_check
sha256: cc0bfeb003c5747e963f4f378e72c8c5c5f91a243f2e85b7da29ff42f4709996
url: "https://pub.dev"
source: hosted
version: "0.4.1+5"
firebase_app_check_platform_interface:
dependency: transitive
description:
name: firebase_app_check_platform_interface
sha256: "54166319f9121bd94b2374864e84dd54507438095fc2d42ca49f23957b16b0d1"
url: "https://pub.dev"
source: hosted
version: "0.2.1+5"
firebase_app_check_web:
dependency: transitive
description:
name: firebase_app_check_web
sha256: f8dddba09bd7786e017b58e74455886106ac7ca9e798769e2cac3e988fc2d146
url: "https://pub.dev"
source: hosted
version: "0.2.2+3"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: b20d1540460814c5984474c1e9dd833bdbcff6ecd8d6ad86cc9da8cfd581c172
url: "https://pub.dev"
source: hosted
version: "6.1.4"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: fd0225320b6bbc92460c86352d16b60aea15f9ef88292774cca97b0522ea9f72
url: "https://pub.dev"
source: hosted
version: "8.1.6"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: be7dccb263b89fbda2a564de9d8193118196e8481ffb937222a025cdfdf82c40
url: "https://pub.dev"
source: hosted
version: "6.1.2"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: "923085c881663ef685269b013e241b428e1fb03cdd0ebde265d9b40ff18abf80" sha256: f0997fee80fbb6d2c658c5b88ae87ba1f9506b5b37126db64fc2e75d8e977fbb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.5.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -269,10 +325,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: "83e7356c704131ca4d8d8dd57e360d8acecbca38b1a3705c7ae46cc34c708084" sha256: "856ca92bf2d75a63761286ab8e791bda3a85184c2b641764433b619647acfca6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.0" version: "3.5.0"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:

View file

@ -1,7 +1,7 @@
name: tetraq name: tetraq
description: A new Flutter project. description: A new Flutter project.
publish_to: 'none' publish_to: 'none'
version: 1.0.2+4 version: 1.1.4+6
environment: environment:
sdk: ^3.10.7 sdk: ^3.10.7
@ -18,17 +18,20 @@ dependencies:
flutter_localizations: # Il sistema multilingua ufficiale flutter_localizations: # Il sistema multilingua ufficiale
sdk: flutter sdk: flutter
firebase_core: ^4.4.0 firebase_core: ^4.4.0
firebase_auth: ^6.1.4 # <--- NUOVO: LA CORAZZA DI SICUREZZA!
cloud_firestore: ^6.1.2 cloud_firestore: ^6.1.2
share_plus: ^12.0.1 share_plus: ^12.0.1
app_links: ^7.0.0 app_links: ^7.0.0
google_fonts: ^8.0.2 google_fonts: ^8.0.2
font_awesome_flutter: ^10.12.0 font_awesome_flutter: ^10.12.0
firebase_app_check: ^0.4.1+5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
flutter_launcher_icons: ^0.13.1 flutter_launcher_icons: ^0.13.1
change_app_package_name: ^1.5.0
flutter: flutter:
uses-material-design: true uses-material-design: true
@ -41,6 +44,7 @@ flutter:
- assets/images/ - assets/images/
- assets/audio/bgm/ - assets/audio/bgm/
- assets/audio/sfx/ - assets/audio/sfx/
- assets/audio/
flutter_icons: flutter_icons:

View file

@ -0,0 +1,11 @@
{
"applinks": {
"apps": [],
"details": [
{
"appID": "2BX6QRR7GG.com.sanza.tetraq",
"paths": [ "/join" ]
}
]
}
}

View file

@ -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"]
}
}]