Compare commits
21 commits
c90f75cf66
...
3c28c1f0c2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c28c1f0c2 | |||
| bbcd80a6a9 | |||
| e1861031a8 | |||
| 321810a6b4 | |||
| 518cb22ec4 | |||
| da52ad9d80 | |||
| 7c7bf0ef2d | |||
| 99ae270818 | |||
| a84c0b51b0 | |||
| 384a9d7bd7 | |||
| 50f67320a5 | |||
| 5aa831f750 | |||
| 37e59d5652 | |||
| 18b0965aae | |||
| b2b94a5e72 | |||
| 434ea96444 | |||
| a711b3dec2 | |||
| 3acfb76fac | |||
| c83cc6b9ae | |||
| 37d638816a | |||
| a3a30ebf1e |
76 changed files with 4282 additions and 1468 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
2
.firebase/hosting.cHVibGlj.cache
Normal file
2
.firebase/hosting.cHVibGlj.cache
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
index.html,1773344753424,0d5d4b835a7d632ad11d249230a15561286f2bfcd1da8305c6fb294d37e5da09
|
||||||
|
404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3
|
||||||
5
.firebaserc
Normal file
5
.firebaserc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"default": "tetraq-32a4a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
BIN
assets/.DS_Store
vendored
Binary file not shown.
BIN
assets/audio/.DS_Store
vendored
Normal file
BIN
assets/audio/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/audio/bgm/8-bit_Prowler.mp3
Normal file
BIN
assets/audio/bgm/8-bit_Prowler.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/Cyber_Dystopia.mp3
Normal file
BIN
assets/audio/bgm/Cyber_Dystopia.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/Grimorio_Astral.mp3
Normal file
BIN
assets/audio/bgm/Grimorio_Astral.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/Legno_Canopy.mp3
Normal file
BIN
assets/audio/bgm/Legno_Canopy.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/Music_Loop.mp3
Normal file
BIN
assets/audio/bgm/Music_Loop.mp3
Normal file
Binary file not shown.
BIN
assets/audio/bgm/Quad_Dreams.mp3
Normal file
BIN
assets/audio/bgm/Quad_Dreams.mp3
Normal file
Binary file not shown.
BIN
assets/images/egizi_bg.jpg
Normal file
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
BIN
assets/images/music_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
|
|
@ -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
27
genera_lingue.dart
Normal 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
BIN
ios/.DS_Store
vendored
Binary file not shown.
137
ios/Podfile.lock
137
ios/Podfile.lock
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
10
ios/Runner/Runner.entitlements
Normal file
10
ios/Runner/Runner.entitlements
Normal 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
4
l10n.yaml
Normal 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
BIN
lib/.DS_Store
vendored
Binary file not shown.
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
23
lib/l10n/app_de.arb
Normal 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"
|
||||||
|
}
|
||||||
|
|
@ -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
23
lib/l10n/app_es.arb
Normal 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
23
lib/l10n/app_fr.arb
Normal 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"
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
286
lib/l10n/app_localizations.dart
Normal file
286
lib/l10n/app_localizations.dart
Normal 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, you’ll need to edit this
|
||||||
|
/// file.
|
||||||
|
///
|
||||||
|
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||||
|
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||||
|
/// project’s 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.',
|
||||||
|
);
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_de.dart
Normal file
70
lib/l10n/app_localizations_de.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_en.dart
Normal file
70
lib/l10n/app_localizations_en.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_es.dart
Normal file
70
lib/l10n/app_localizations_es.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_fr.dart
Normal file
70
lib/l10n/app_localizations_fr.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_it.dart
Normal file
70
lib/l10n/app_localizations_it.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_pt.dart
Normal file
70
lib/l10n/app_localizations_pt.dart
Normal 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';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_ru.dart
Normal file
70
lib/l10n/app_localizations_ru.dart
Normal 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 => 'ВЫХОД';
|
||||||
|
}
|
||||||
70
lib/l10n/app_localizations_zh.dart
Normal file
70
lib/l10n/app_localizations_zh.dart
Normal 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
23
lib/l10n/app_pt.arb
Normal 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
23
lib/l10n/app_ru.arb
Normal 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
23
lib/l10n/app_zh.arb
Normal 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": "退出"
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
handleLineTap(randomMove, _activeTheme, forced: true);
|
|
||||||
} else if (isVsCPU && board.currentPlayer == Player.red) {
|
// 1. Raccogliamo TUTTE le linee ancora libere e giocabili
|
||||||
Line randomMove = AIEngine.getBestMove(board, cpuLevel);
|
List<Line> availableLines = board.lines.where((l) => l.owner == Player.none && l.isPlayable).toList();
|
||||||
handleLineTap(randomMove, _activeTheme, forced: true);
|
|
||||||
} else if (!isVsCPU) {
|
// Sicurezza: se non ci sono mosse, non facciamo nulla
|
||||||
Line randomMove = AIEngine.getBestMove(board, 5);
|
if (availableLines.isEmpty) return;
|
||||||
handleLineTap(randomMove, _activeTheme, forced: true);
|
|
||||||
}
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
'name': playerName,
|
|
||||||
'xp': totalXP,
|
if (user != null) {
|
||||||
'level': playerLevel,
|
await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({
|
||||||
'wins': wins,
|
'name': playerName,
|
||||||
'lastActive': FieldValue.serverTimestamp(),
|
'xp': totalXP,
|
||||||
}, SetOptions(merge: true));
|
'level': playerLevel,
|
||||||
|
'wins': wins,
|
||||||
|
'lastActive': FieldValue.serverTimestamp(),
|
||||||
|
}, 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
BIN
lib/ui/.DS_Store
vendored
Normal file
Binary file not shown.
128
lib/ui/admin/admin_screen.dart
Normal file
128
lib/ui/admin/admin_screen.dart
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,16 +102,18 @@ 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: Row(
|
child: FittedBox(
|
||||||
mainAxisSize: MainAxisSize.min,
|
fit: BoxFit.scaleDown,
|
||||||
children: [
|
child: Row(
|
||||||
Text("$nameRed: $red", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerRed))),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(" - ", style: _getTextStyle(themeType, TextStyle(fontSize: 18, color: theme.text))),
|
children: [
|
||||||
Text("$nameBlue: $blue", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerBlue))),
|
Text("$nameRed: $red", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerRed))),
|
||||||
],
|
Text(" - ", style: _getTextStyle(themeType, TextStyle(fontSize: 18, color: theme.text))),
|
||||||
|
Text("$nameBlue: $blue", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerBlue))),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
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,23 +339,41 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: actualWidth, height: actualHeight,
|
width: actualWidth, height: actualHeight,
|
||||||
child: GestureDetector(
|
child: Stack(
|
||||||
behavior: HitTestBehavior.opaque,
|
children: [
|
||||||
onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
|
// --- IL VERO SFONDO SFOCATO SAGOMATO ---
|
||||||
child: AnimatedBuilder(
|
if (themeType == AppThemeType.music)
|
||||||
animation: _blinkController,
|
Positioned.fill(
|
||||||
builder: (context, child) {
|
child: ClipPath(
|
||||||
return CustomPaint(
|
clipper: _ArenaClipper(gameController.board),
|
||||||
size: Size(actualWidth, actualHeight),
|
child: BackdropFilter(
|
||||||
painter: BoardPainter(
|
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
|
||||||
board: gameController.board, theme: theme, themeType: themeType,
|
child: Container(
|
||||||
blinkValue: _blinkController.value, isOnline: gameController.isOnline,
|
color: Colors.white.withOpacity(0.08),
|
||||||
isVsCPU: gameController.isVsCPU, isSetupPhase: gameController.isSetupPhase,
|
),
|
||||||
myPlayer: gameController.myPlayer, jokerTurn: gameController.jokerTurn,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
),
|
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: _blinkController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
size: Size(actualWidth, actualHeight),
|
||||||
|
painter: BoardPainter(
|
||||||
|
board: gameController.board, theme: theme, themeType: themeType,
|
||||||
|
blinkValue: _blinkController.value, isOnline: gameController.isOnline,
|
||||||
|
isVsCPU: gameController.isVsCPU, isSetupPhase: gameController.isSetupPhase,
|
||||||
|
myPlayer: gameController.myPlayer, jokerTurn: gameController.jokerTurn,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -404,46 +429,77 @@ 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,
|
children: [
|
||||||
child: Container(
|
// 1. Sfondo base a tinta unita (In caso non ci sia l'immagine)
|
||||||
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,
|
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
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)),
|
|
||||||
|
|
||||||
if (gameController.effectText.isNotEmpty)
|
// 2. Immagine di Sfondo per tutti i temi che la supportano
|
||||||
Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)),
|
if (bgImage != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Image.asset(
|
||||||
|
bgImage,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
Positioned.fill(child: gameContent),
|
// 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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// --- SCHERMATA COPRENTE PER IL PASSAGGIO DEL TELEFONO IN LOCALE ---
|
// 4. Patina scura (Cyberpunk e Music) per far risaltare il neon
|
||||||
if (gameController.isSetupPhase && !_hideJokerMessage)
|
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music))
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
// Il colore di sfondo riempie tutto lo schermo per non far sbirciare la griglia
|
decoration: BoxDecoration(
|
||||||
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade
|
gradient: LinearGradient(
|
||||||
? Colors.black
|
begin: Alignment.topCenter, end: Alignment.bottomCenter,
|
||||||
: theme.background.withOpacity(0.98),
|
colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)]
|
||||||
child: Center(
|
)
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30.0),
|
),
|
||||||
child: GestureDetector(
|
),
|
||||||
onTap: () { setState(() { _hideJokerMessage = true; }); },
|
|
||||||
child: Material(color: Colors.transparent, child: _buildThemedJokerMessage(theme, themeType, gameController)),
|
// 5. Effetto "Furia" o Timeout
|
||||||
),
|
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)),
|
||||||
|
|
||||||
|
// 6. Testo degli Eventi
|
||||||
|
if (gameController.effectText.isNotEmpty)
|
||||||
|
Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)),
|
||||||
|
|
||||||
|
// 7. Il Gioco Vero e Proprio
|
||||||
|
Positioned.fill(child: gameContent),
|
||||||
|
|
||||||
|
// 8. Schermata Passaggio Dispositivo
|
||||||
|
if (gameController.isSetupPhase && !_hideJokerMessage)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music
|
||||||
|
? Colors.black.withOpacity(0.98)
|
||||||
|
: theme.background.withOpacity(0.98),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 30.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () { setState(() { _hideJokerMessage = true; }); },
|
||||||
|
child: Material(color: Colors.transparent, child: _buildThemedJokerMessage(theme, themeType, gameController)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
if (gameController.isGameOver && gameController.board.scoreRed != gameController.board.scoreBlue)
|
// 9. Effetti di Vittoria
|
||||||
Positioned.fill(child: IgnorePointer(child: WinnerVFXOverlay(winnerColor: gameController.board.scoreRed > gameController.board.scoreBlue ? theme.playerRed : theme.playerBlue, themeType: themeType))),
|
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))),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 ---
|
||||||
setState(() {
|
Row(
|
||||||
AudioService.instance.toggleMute();
|
mainAxisSize: MainAxisSize.min,
|
||||||
});
|
children: [
|
||||||
},
|
// TASTO AUDIO CON CONTORNO
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
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
375
lib/ui/home/dialog.dart
Normal 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
|
|
@ -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,7 +428,13 @@ 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: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
|
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))),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -351,7 +455,13 @@ class _NeonActionButton extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
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: 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))]))),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -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; });
|
||||||
|
|
||||||
_multiplayerService.shareInviteLink(code);
|
if (!_isPublicRoom) {
|
||||||
|
_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,19 +713,30 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Dialog(
|
return PopScope(
|
||||||
backgroundColor: Colors.transparent,
|
canPop: false,
|
||||||
insetPadding: const EdgeInsets.all(20),
|
onPopInvoked: (didPop) {
|
||||||
child: Column(
|
if (didPop) return;
|
||||||
mainAxisSize: MainAxisSize.min,
|
_cleanupGhostRoom();
|
||||||
children: [
|
Navigator.pop(context);
|
||||||
dialogContent,
|
},
|
||||||
const SizedBox(height: 20),
|
child: Dialog(
|
||||||
TextButton(
|
backgroundColor: Colors.transparent,
|
||||||
onPressed: () { FirebaseFirestore.instance.collection('games').doc(code).delete(); Navigator.pop(context); },
|
insetPadding: const EdgeInsets.all(20),
|
||||||
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: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
],
|
children: [
|
||||||
|
dialogContent,
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
TextButton(
|
||||||
|
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)]))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -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,51 +867,198 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
hostPanel,
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
|
|
||||||
|
|
||||||
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,
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
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),
|
||||||
|
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("OPPURE", 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: 20),
|
||||||
|
|
||||||
|
Transform.rotate(
|
||||||
|
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
|
||||||
|
child: Container(
|
||||||
|
decoration: themeType == AppThemeType.doodle ? BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), bottomRight: Radius.circular(20), topRight: Radius.circular(5), bottomLeft: Radius.circular(5)),
|
||||||
|
border: Border.all(color: theme.text, width: 2.5),
|
||||||
|
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(5, 5), blurRadius: 0)],
|
||||||
|
) : BoxDecoration(
|
||||||
|
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 15, spreadRadius: 1)]
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
|
||||||
|
style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "",
|
||||||
|
filled: themeType != AppThemeType.doodle,
|
||||||
|
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05),
|
||||||
|
enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)),
|
||||||
|
focusedBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3.0), borderRadius: BorderRadius.circular(15)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 25),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
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("OPPURE", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
|
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)),
|
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
Transform.rotate(
|
|
||||||
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
|
|
||||||
child: Container(
|
|
||||||
decoration: themeType == AppThemeType.doodle ? BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), bottomRight: Radius.circular(20), topRight: Radius.circular(5), bottomLeft: Radius.circular(5)),
|
|
||||||
border: Border.all(color: theme.text, width: 2.5),
|
|
||||||
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(5, 5), blurRadius: 0)],
|
|
||||||
) : BoxDecoration(
|
|
||||||
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 15, spreadRadius: 1)]
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
|
|
||||||
style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "",
|
|
||||||
filled: themeType != AppThemeType.doodle,
|
|
||||||
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05),
|
|
||||||
enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)),
|
|
||||||
focusedBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3.0), borderRadius: BorderRadius.circular(15)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: _joinRoom, theme: theme, themeType: themeType),
|
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
// --- 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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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)])),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
57
lib/widgets/cyber_border.dart
Normal file
57
lib/widgets/cyber_border.dart
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
90
lib/widgets/home_buttons.dart
Normal file
90
lib/widgets/home_buttons.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
lib/widgets/music_theme_widgets.dart
Normal file
129
lib/widgets/music_theme_widgets.dart
Normal 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
73
lib/widgets/painters.dart
Normal 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
BIN
macos/.DS_Store
vendored
Binary file not shown.
|
|
@ -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"))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@
|
||||||
<!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>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<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>
|
||||||
|
|
@ -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
33
public/404.html
Normal 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
89
public/index.html
Normal 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…</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>
|
||||||
68
pubspec.lock
68
pubspec.lock
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
11
tetraq_website/.well-known/apple-app-site-association
Normal file
11
tetraq_website/.well-known/apple-app-site-association
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"applinks": {
|
||||||
|
"apps": [],
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"appID": "2BX6QRR7GG.com.sanza.tetraq",
|
||||||
|
"paths": [ "/join" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tetraq_website/.well-known/assetlinks.json
Normal file
8
tetraq_website/.well-known/assetlinks.json
Normal 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"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
Loading…
Reference in a new issue