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 |
78 changed files with 4953 additions and 1828 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.metadata
16
.metadata
|
|
@ -4,7 +4,7 @@
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "ff37bef603469fb030f2b72995ab929ccfc227f0"
|
revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
@ -13,11 +13,17 @@ project_type: app
|
||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
- platform: android
|
||||||
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
|
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,15 +47,28 @@ 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -114,12 +155,11 @@ class GameController extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- AGGIUNTA LA COORDINATA Z PER IL 3D ---
|
void placeJoker(int bx, int by) {
|
||||||
void placeJoker(int bx, int by, {int bz = 0}) {
|
|
||||||
if (!isSetupPhase) return;
|
if (!isSetupPhase) return;
|
||||||
|
|
||||||
Box? target;
|
Box? target;
|
||||||
try { target = board.boxes.firstWhere((b) => b.x == bx && b.y == by && b.z == bz); } catch(e) {}
|
try { target = board.boxes.firstWhere((b) => b.x == bx && b.y == by); } catch(e) {}
|
||||||
|
|
||||||
if (target == null || target.type == BoxType.invisible || target.hiddenJokerOwner != null) return;
|
if (target == null || target.type == BoxType.invisible || target.hiddenJokerOwner != null) return;
|
||||||
|
|
||||||
|
|
@ -132,7 +172,7 @@ class GameController extends ChangeNotifier {
|
||||||
|
|
||||||
String prefix = isHost ? 'p1' : 'p2';
|
String prefix = isHost ? 'p1' : 'p2';
|
||||||
FirebaseFirestore.instance.collection('games').doc(roomCode).update({
|
FirebaseFirestore.instance.collection('games').doc(roomCode).update({
|
||||||
'${prefix}_joker': {'x': bx, 'y': by, 'z': bz}
|
'${prefix}_joker': {'x': bx, 'y': by}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
target.hiddenJokerOwner = jokerTurn;
|
target.hiddenJokerOwner = jokerTurn;
|
||||||
|
|
@ -286,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() {
|
||||||
|
|
@ -361,15 +406,14 @@ class GameController extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSetupPhase) {
|
if (isSetupPhase) {
|
||||||
// --- SINCRONIZZAZIONE JOLLY IN 3D ---
|
|
||||||
if (!isHost && data['p1_joker'] != null && !oppJokerPlaced) {
|
if (!isHost && data['p1_joker'] != null && !oppJokerPlaced) {
|
||||||
int jx = data['p1_joker']['x']; int jy = data['p1_joker']['y']; int jz = data['p1_joker']['z'] ?? 0;
|
int jx = data['p1_joker']['x']; int jy = data['p1_joker']['y'];
|
||||||
board.boxes.firstWhere((b) => b.x == jx && b.y == jy && b.z == jz).hiddenJokerOwner = Player.red;
|
board.boxes.firstWhere((b) => b.x == jx && b.y == jy).hiddenJokerOwner = Player.red;
|
||||||
oppJokerPlaced = true; _checkSetupComplete();
|
oppJokerPlaced = true; _checkSetupComplete();
|
||||||
}
|
}
|
||||||
if (isHost && data['p2_joker'] != null && !oppJokerPlaced) {
|
if (isHost && data['p2_joker'] != null && !oppJokerPlaced) {
|
||||||
int jx = data['p2_joker']['x']; int jy = data['p2_joker']['y']; int jz = data['p2_joker']['z'] ?? 0;
|
int jx = data['p2_joker']['x']; int jy = data['p2_joker']['y'];
|
||||||
board.boxes.firstWhere((b) => b.x == jx && b.y == jy && b.z == jz).hiddenJokerOwner = Player.blue;
|
board.boxes.firstWhere((b) => b.x == jx && b.y == jy).hiddenJokerOwner = Player.blue;
|
||||||
oppJokerPlaced = true; _checkSetupComplete();
|
oppJokerPlaced = true; _checkSetupComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -505,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;
|
||||||
|
|
@ -514,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);
|
||||||
|
|
@ -521,7 +581,9 @@ class GameController extends ChangeNotifier {
|
||||||
int myScore = isHost ? board.scoreRed : board.scoreBlue;
|
int myScore = isHost ? board.scoreRed : board.scoreBlue;
|
||||||
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);
|
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;
|
||||||
bool isWin = myScore > cpuScore;
|
bool isWin = myScore > cpuScore;
|
||||||
|
|
@ -543,7 +605,17 @@ class GameController extends ChangeNotifier {
|
||||||
StorageService.instance.updateQuestProgress(2, 1);
|
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,20 +5,18 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
enum Player { red, blue, none }
|
enum Player { red, blue, none }
|
||||||
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier }
|
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier } // Aggiunti ice e multiplier
|
||||||
// --- AGGIUNTA LA FORMA 3D ---
|
enum ArenaShape { classic, cross, donut, hourglass, chaos }
|
||||||
enum ArenaShape { classic, cross, donut, hourglass, chaos, test, pyramid3D }
|
|
||||||
|
|
||||||
class Dot {
|
class Dot {
|
||||||
final int x;
|
final int x;
|
||||||
final int y;
|
final int y;
|
||||||
final int z; // --- NUOVO: ALTEZZA 3D ---
|
Dot(this.x, this.y);
|
||||||
Dot(this.x, this.y, {this.z = 0});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y && z == other.z;
|
bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y;
|
||||||
@override
|
@override
|
||||||
int get hashCode => x.hashCode ^ y.hashCode ^ z.hashCode;
|
int get hashCode => x.hashCode ^ y.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Line {
|
class Line {
|
||||||
|
|
@ -26,7 +24,7 @@ class Line {
|
||||||
final Dot p2;
|
final Dot p2;
|
||||||
Player owner = Player.none;
|
Player owner = Player.none;
|
||||||
bool isPlayable = false;
|
bool isPlayable = false;
|
||||||
bool isIceCracked = false;
|
bool isIceCracked = false; // NUOVO: Stato per il blocco di ghiaccio
|
||||||
|
|
||||||
Line(this.p1, this.p2);
|
Line(this.p1, this.p2);
|
||||||
|
|
||||||
|
|
@ -36,7 +34,6 @@ class Line {
|
||||||
class Box {
|
class Box {
|
||||||
final int x;
|
final int x;
|
||||||
final int y;
|
final int y;
|
||||||
final int z; // --- NUOVO: PIANO DELLA SCATOLA ---
|
|
||||||
Player owner = Player.none;
|
Player owner = Player.none;
|
||||||
late Line top, bottom, left, right;
|
late Line top, bottom, left, right;
|
||||||
BoxType type = BoxType.normal;
|
BoxType type = BoxType.normal;
|
||||||
|
|
@ -45,7 +42,7 @@ class Box {
|
||||||
Player? hiddenJokerOwner;
|
Player? hiddenJokerOwner;
|
||||||
bool isJokerRevealed = false;
|
bool isJokerRevealed = false;
|
||||||
|
|
||||||
Box(this.x, this.y, {this.z = 0});
|
Box(this.x, this.y);
|
||||||
|
|
||||||
bool isClosed() {
|
bool isClosed() {
|
||||||
if (type == BoxType.invisible) return false;
|
if (type == BoxType.invisible) return false;
|
||||||
|
|
@ -53,13 +50,13 @@ class Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
int getCalculatedValue(Player closer) {
|
int getCalculatedValue(Player closer) {
|
||||||
if (hiddenJokerOwner != null) return (closer == hiddenJokerOwner) ? 2 : -1;
|
if (hiddenJokerOwner != null) {
|
||||||
|
return (closer == hiddenJokerOwner) ? 2 : -1;
|
||||||
|
}
|
||||||
if (type == BoxType.gold) return 2;
|
if (type == BoxType.gold) return 2;
|
||||||
if (type == BoxType.bomb) return -1;
|
if (type == BoxType.bomb) return -1;
|
||||||
if (type == BoxType.swap || type == BoxType.ice || type == BoxType.multiplier) return 0;
|
if (type == BoxType.swap || type == BoxType.ice || type == BoxType.multiplier) return 0; // Il moltiplicatore e il ghiaccio non danno punti base
|
||||||
|
return 1;
|
||||||
// --- NUOVO: NELLA PIRAMIDE I PIANI ALTI VALGONO DI PIÙ! ---
|
|
||||||
return 1 + z;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +77,10 @@ class GameBoard {
|
||||||
int scoreRed = 0;
|
int scoreRed = 0;
|
||||||
int scoreBlue = 0;
|
int scoreBlue = 0;
|
||||||
bool isGameOver = false;
|
bool isGameOver = false;
|
||||||
|
|
||||||
Line? lastMove;
|
Line? lastMove;
|
||||||
|
|
||||||
|
// Variabili per il Moltiplicatore
|
||||||
bool redHasMultiplier = false;
|
bool redHasMultiplier = false;
|
||||||
bool blueHasMultiplier = false;
|
bool blueHasMultiplier = false;
|
||||||
|
|
||||||
|
|
@ -89,111 +89,205 @@ class GameBoard {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _generateBoard() {
|
void _generateBoard() {
|
||||||
dots.clear(); lines.clear(); boxes.clear(); lastMove = null;
|
final random = seed != null ? Random(seed) : Random();
|
||||||
|
int chaosAlgorithm = random.nextInt(5);
|
||||||
|
|
||||||
if (shape == ArenaShape.pyramid3D) {
|
if (shape == ArenaShape.chaos) {
|
||||||
// --- LOGICA GENERAZIONE PIRAMIDE 3D ---
|
columns = radius * 2 + 1;
|
||||||
columns = 4; rows = 4; // Base 4x4
|
rows = (radius * 3) + 2;
|
||||||
int maxZ = 4; // 4 Piani (0, 1, 2, 3)
|
|
||||||
|
|
||||||
for (int z = 0; z < maxZ; z++) {
|
|
||||||
int currentLayerSize = columns - z; // Il piano si restringe man mano che sale
|
|
||||||
for (int y = 0; y < currentLayerSize; y++) {
|
|
||||||
for (int x = 0; x < currentLayerSize; x++) {
|
|
||||||
var box = Box(x, y, z: z);
|
|
||||||
// Più sali, più possibilità ci sono di trovare Oro o Bombe
|
|
||||||
if (z > 0 && Random().nextDouble() > 0.8) box.type = BoxType.gold;
|
|
||||||
if (z > 1 && Random().nextDouble() > 0.85) box.type = BoxType.bomb;
|
|
||||||
boxes.add(box);
|
|
||||||
|
|
||||||
Dot tl = _getOrAddDot(x, y, z);
|
|
||||||
Dot tr = _getOrAddDot(x + 1, y, z);
|
|
||||||
Dot bl = _getOrAddDot(x, y + 1, z);
|
|
||||||
Dot br = _getOrAddDot(x + 1, y + 1, z);
|
|
||||||
|
|
||||||
box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
|
|
||||||
box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_updatePyramidPlayability(); // Blocchiamo i piani superiori!
|
|
||||||
} else {
|
} else {
|
||||||
// (Qui c'è il codice standard 2D che abbiamo lasciato invariato per non rompere nulla)
|
columns = radius * 2 + 1;
|
||||||
columns = shape == ArenaShape.chaos ? radius * 2 + 1 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);
|
rows = radius * 2 + 1;
|
||||||
rows = shape == ArenaShape.chaos ? (radius * 3) + 2 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);
|
|
||||||
|
|
||||||
for (int y = 0; y < rows; y++) {
|
|
||||||
for (int x = 0; x < columns; x++) {
|
|
||||||
var box = Box(x, y);
|
|
||||||
boxes.add(box);
|
|
||||||
Dot tl = _getOrAddDot(x, y, 0); Dot tr = _getOrAddDot(x + 1, y, 0);
|
|
||||||
Dot bl = _getOrAddDot(x, y + 1, 0); Dot br = _getOrAddDot(x + 1, y + 1, 0);
|
|
||||||
box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
|
|
||||||
box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
|
|
||||||
box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Sblocca i piani superiori solo se il "pavimento" è solido
|
dots.clear();
|
||||||
void _updatePyramidPlayability() {
|
lines.clear();
|
||||||
if (shape != ArenaShape.pyramid3D) return;
|
boxes.clear();
|
||||||
for (var box in boxes) {
|
lastMove = null;
|
||||||
if (box.z == 0) {
|
|
||||||
if (box.owner == Player.none) {
|
for (int y = 0; y < rows; y++) {
|
||||||
box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
|
for (int x = 0; x < columns; x++) {
|
||||||
}
|
var box = Box(x, y);
|
||||||
} else {
|
bool isVisible = true;
|
||||||
// Cerca i 4 quadrati del piano di sotto che lo sorreggono
|
|
||||||
bool isSupported = true;
|
if (shape != ArenaShape.chaos) {
|
||||||
for (int dy = 0; dy <= 1; dy++) {
|
int dx = (x - radius).abs();
|
||||||
for (int dx = 0; dx <= 1; dx++) {
|
int dy = (y - radius).abs();
|
||||||
var supportBox = boxes.where((b) => b.z == box.z - 1 && b.x == box.x + dx && b.y == box.y + dy).firstOrNull;
|
isVisible = (dx + dy) <= radius;
|
||||||
if (supportBox == null || !supportBox.isClosed()) isSupported = false;
|
|
||||||
|
if (isVisible) {
|
||||||
|
switch (shape) {
|
||||||
|
case ArenaShape.cross:
|
||||||
|
int spessoreBraccio = radius > 3 ? 1 : 0;
|
||||||
|
if (dx > spessoreBraccio && dy > spessoreBraccio) isVisible = false; break;
|
||||||
|
case ArenaShape.donut:
|
||||||
|
int dimensioneBuco = radius > 3 ? 2 : 1;
|
||||||
|
if ((dx + dy) <= dimensioneBuco) isVisible = false; break;
|
||||||
|
case ArenaShape.hourglass:
|
||||||
|
if (dx > dy) isVisible = false;
|
||||||
|
if (x == radius && y == radius) isVisible = true; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
double percentY = y / rows;
|
||||||
|
if (chaosAlgorithm == 0) {
|
||||||
|
isVisible = (x % 2 == 0) && (random.nextDouble() > 0.15);
|
||||||
|
} else if (chaosAlgorithm == 1) {
|
||||||
|
double chance = 0.2 + (percentY * 0.7);
|
||||||
|
isVisible = random.nextDouble() < chance;
|
||||||
|
} else if (chaosAlgorithm == 2) {
|
||||||
|
int midY = rows ~/ 2;
|
||||||
|
int distFromCenterY = (y - midY).abs();
|
||||||
|
int allowedWidth = (distFromCenterY / midY * radius).ceil() + 1;
|
||||||
|
int dx = (x - radius).abs();
|
||||||
|
isVisible = dx <= allowedWidth && random.nextDouble() > 0.1;
|
||||||
|
} else if (chaosAlgorithm == 3) {
|
||||||
|
isVisible = (y % 2 == 0) ? (x < columns - 1) : (x > 0);
|
||||||
|
if (random.nextDouble() > 0.8) isVisible = false;
|
||||||
|
} else if (chaosAlgorithm == 4) {
|
||||||
|
isVisible = random.nextDouble() > 0.45;
|
||||||
|
}
|
||||||
|
if (x == radius && y == rows ~/ 2) isVisible = true;
|
||||||
}
|
}
|
||||||
if (box.owner == Player.none) {
|
|
||||||
box.top.isPlayable = isSupported; box.bottom.isPlayable = isSupported;
|
if (!isVisible) {
|
||||||
box.left.isPlayable = isSupported; box.right.isPlayable = isSupported;
|
box.type = BoxType.invisible;
|
||||||
|
} else if (level > 1) {
|
||||||
|
double chance = random.nextDouble();
|
||||||
|
if (chance < 0.08) box.type = BoxType.gold;
|
||||||
|
else if (chance > 0.92) box.type = BoxType.bomb;
|
||||||
|
else if (level >= 5 && chance > 0.88 && chance <= 0.92) box.type = BoxType.swap;
|
||||||
|
else if (level >= 10 && chance > 0.83 && chance <= 0.88) box.type = BoxType.ice; // Nuova Scatola Ghiaccio
|
||||||
|
else if (level >= 15 && chance > 0.78 && chance <= 0.83) box.type = BoxType.multiplier; // Nuova Scatola x2
|
||||||
}
|
}
|
||||||
|
boxes.add(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var box in boxes) {
|
||||||
|
Dot tl = _getOrAddDot(box.x, box.y);
|
||||||
|
Dot tr = _getOrAddDot(box.x + 1, box.y);
|
||||||
|
Dot bl = _getOrAddDot(box.x, box.y + 1);
|
||||||
|
Dot br = _getOrAddDot(box.x + 1, box.y + 1);
|
||||||
|
|
||||||
|
box.top = _getOrAddLine(tl, tr);
|
||||||
|
box.bottom = _getOrAddLine(bl, br);
|
||||||
|
box.left = _getOrAddLine(tl, bl);
|
||||||
|
box.right = _getOrAddLine(tr, br);
|
||||||
|
|
||||||
|
if (box.type != BoxType.invisible) {
|
||||||
|
box.top.isPlayable = true; box.bottom.isPlayable = true;
|
||||||
|
box.left.isPlayable = true; box.right.isPlayable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dot _getOrAddDot(int x, int y, int z) {
|
Dot _getOrAddDot(int x, int y) {
|
||||||
for (var dot in dots) { if (dot.x == x && dot.y == y && dot.z == z) return dot; }
|
for (var dot in dots) { if (dot.x == x && dot.y == y) return dot; }
|
||||||
var newDot = Dot(x, y, z: z); dots.add(newDot); return newDot;
|
var newDot = Dot(x, y);
|
||||||
|
dots.add(newDot); return newDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
Line _getOrAddLine(Dot a, Dot b) {
|
Line _getOrAddLine(Dot a, Dot b) {
|
||||||
for (var line in lines) { if (line.connects(a, b)) return line; }
|
for (var line in lines) { if (line.connects(a, b)) return line; }
|
||||||
var newLine = Line(a, b); lines.add(newLine); return newLine;
|
var newLine = Line(a, b);
|
||||||
|
lines.add(newLine); return newLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool playMove(Line lineToPlay, {Player? forcedPlayer}) {
|
bool playMove(Line lineToPlay, {Player? forcedPlayer}) {
|
||||||
if (isGameOver) return false;
|
if (isGameOver) return false;
|
||||||
|
|
||||||
Player playerMakingMove = forcedPlayer ?? currentPlayer;
|
Player playerMakingMove = forcedPlayer ?? currentPlayer;
|
||||||
Line? actualLine;
|
Line? actualLine;
|
||||||
for (var l in lines) { if (l.connects(lineToPlay.p1, lineToPlay.p2)) { actualLine = l; break; } }
|
for (var l in lines) {
|
||||||
|
if (l.connects(lineToPlay.p1, lineToPlay.p2)) { actualLine = l; break; }
|
||||||
|
}
|
||||||
|
|
||||||
if (actualLine == null || actualLine.owner != Player.none || !actualLine.isPlayable) return false;
|
if (actualLine == null || actualLine.owner != Player.none || !actualLine.isPlayable) return false;
|
||||||
|
|
||||||
actualLine.owner = playerMakingMove;
|
// --- LOGICA BLOCCO DI GHIACCIO ---
|
||||||
lastMove = actualLine;
|
bool closesIce = false;
|
||||||
bool scoredPoint = false;
|
|
||||||
|
|
||||||
for (var box in boxes) {
|
for (var box in boxes) {
|
||||||
if (box.owner == Player.none && box.isClosed()) {
|
if (box.type == BoxType.ice && box.owner == Player.none) {
|
||||||
box.owner = playerMakingMove; scoredPoint = true;
|
int linesCount = 0;
|
||||||
int points = box.getCalculatedValue(playerMakingMove);
|
if (box.top.owner != Player.none || box.top == actualLine) linesCount++;
|
||||||
if (playerMakingMove == Player.red) scoreRed += points; else scoreBlue += points;
|
if (box.bottom.owner != Player.none || box.bottom == actualLine) linesCount++;
|
||||||
|
if (box.left.owner != Player.none || box.left == actualLine) linesCount++;
|
||||||
|
if (box.right.owner != Player.none || box.right == actualLine) linesCount++;
|
||||||
|
if (linesCount == 4) closesIce = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shape == ArenaShape.pyramid3D) _updatePyramidPlayability(); // Ricalcola chi può giocare sopra!
|
if (closesIce && !actualLine.isIceCracked) {
|
||||||
|
actualLine.isIceCracked = true; // Si incrina ma non si chiude!
|
||||||
|
lastMove = actualLine;
|
||||||
|
if (forcedPlayer == null) currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red;
|
||||||
|
else currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red;
|
||||||
|
return true; // Mossa valida, ma turno finito.
|
||||||
|
}
|
||||||
|
|
||||||
if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) isGameOver = true;
|
// Mossa normale o secondo colpo al ghiaccio
|
||||||
if (!scoredPoint && !isGameOver) currentPlayer = (playerMakingMove == Player.red) ? Player.blue : Player.red;
|
actualLine.isIceCracked = false;
|
||||||
|
actualLine.owner = playerMakingMove;
|
||||||
|
lastMove = actualLine;
|
||||||
|
|
||||||
|
bool scoredPoint = false;
|
||||||
|
bool triggeredSwap = false;
|
||||||
|
|
||||||
|
for (var box in boxes) {
|
||||||
|
if (box.owner == Player.none && box.isClosed()) {
|
||||||
|
box.owner = playerMakingMove;
|
||||||
|
scoredPoint = true;
|
||||||
|
|
||||||
|
if (box.hiddenJokerOwner != null) box.isJokerRevealed = true;
|
||||||
|
|
||||||
|
int points = box.getCalculatedValue(playerMakingMove);
|
||||||
|
|
||||||
|
// --- LOGICA MOLTIPLICATORE x2 ---
|
||||||
|
if (box.type == BoxType.multiplier) {
|
||||||
|
if (playerMakingMove == Player.red) redHasMultiplier = true;
|
||||||
|
else blueHasMultiplier = true;
|
||||||
|
} else if (points != 0) {
|
||||||
|
// Se la scatola chiusa dà punti e il giocatore ha un x2 attivo...
|
||||||
|
if (playerMakingMove == Player.red && redHasMultiplier) {
|
||||||
|
points *= 2;
|
||||||
|
redHasMultiplier = false; // Si consuma
|
||||||
|
} else if (playerMakingMove == Player.blue && blueHasMultiplier) {
|
||||||
|
points *= 2;
|
||||||
|
blueHasMultiplier = false; // Si consuma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerMakingMove == Player.red) { scoreRed += points; }
|
||||||
|
else { scoreBlue += points; }
|
||||||
|
|
||||||
|
if (box.type == BoxType.swap && box.hiddenJokerOwner == null) {
|
||||||
|
triggeredSwap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.type == BoxType.invisible && !box.isRevealed) {
|
||||||
|
if (box.top.owner != Player.none && box.bottom.owner != Player.none &&
|
||||||
|
box.left.owner != Player.none && box.right.owner != Player.none) {
|
||||||
|
box.isRevealed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggeredSwap) {
|
||||||
|
int temp = scoreRed; scoreRed = scoreBlue; scoreBlue = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) { isGameOver = true; }
|
||||||
|
|
||||||
|
if (forcedPlayer == null) {
|
||||||
|
if (!scoredPoint && !isGameOver) { currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red; }
|
||||||
|
else if (scoredPoint && !isGameOver) { currentPlayer = playerMakingMove; }
|
||||||
|
} else {
|
||||||
|
if (!scoredPoint && !isGameOver) { currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red; }
|
||||||
|
else { currentPlayer = forcedPlayer; }
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
|
|
||||||
import '../../models/game_board.dart';
|
import '../../models/game_board.dart';
|
||||||
import '../../core/app_colors.dart';
|
import '../../core/app_colors.dart';
|
||||||
|
|
@ -21,8 +20,6 @@ class BoardPainter extends CustomPainter {
|
||||||
final Player myPlayer;
|
final Player myPlayer;
|
||||||
final Player jokerTurn;
|
final Player jokerTurn;
|
||||||
|
|
||||||
final double cameraAngle; // Angolazione della telecamera a 360 gradi!
|
|
||||||
|
|
||||||
BoardPainter({
|
BoardPainter({
|
||||||
required this.board,
|
required this.board,
|
||||||
required this.theme,
|
required this.theme,
|
||||||
|
|
@ -32,231 +29,348 @@ class BoardPainter extends CustomPainter {
|
||||||
required this.isSetupPhase,
|
required this.isSetupPhase,
|
||||||
required this.myPlayer,
|
required this.myPlayer,
|
||||||
required this.jokerTurn,
|
required this.jokerTurn,
|
||||||
this.blinkValue = 0.0,
|
this.blinkValue = 0.0
|
||||||
this.cameraAngle = 0.0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Color _darken(Color c, [double amount = .1]) {
|
|
||||||
assert(amount >= 0 && amount <= 1);
|
|
||||||
final hsl = HSLColor.fromColor(c);
|
|
||||||
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
|
|
||||||
return hslDark.toColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
// LA MAGIA: Proiezione Isometrica Ruotabile
|
|
||||||
Offset projectLogical(double x, double y, double z, Size size) {
|
|
||||||
if (board.shape != ArenaShape.pyramid3D) {
|
|
||||||
int gridPoints = board.columns + 1;
|
|
||||||
double spacing = size.width / gridPoints;
|
|
||||||
return Offset(x * spacing + (spacing / 2), y * spacing + (spacing / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
double tileW = size.width / 3.8;
|
|
||||||
double tileH = tileW * 0.55;
|
|
||||||
double zHeight = tileW * 0.45; // L'altezza fisica del blocco 3D
|
|
||||||
|
|
||||||
// Calcoliamo la posizione tenendo conto del restringimento della piramide (+ z * 0.5)
|
|
||||||
double actualX = x + z * 0.5 - board.columns / 2.0;
|
|
||||||
double actualY = y + z * 0.5 - board.rows / 2.0;
|
|
||||||
|
|
||||||
// Matrice di rotazione della telecamera (Z-Axis)
|
|
||||||
double rx = actualX * cos(cameraAngle) - actualY * sin(cameraAngle);
|
|
||||||
double ry = actualX * sin(cameraAngle) + actualY * cos(cameraAngle);
|
|
||||||
|
|
||||||
// Proiezione Isometrica 2D
|
|
||||||
double sx = (rx - ry) * tileW;
|
|
||||||
// Il SEGRETO: -z sposta fisicamente in ALTO la faccia del cubo rispetto allo schermo
|
|
||||||
double sy = (rx + ry) * tileH - (z * zHeight);
|
|
||||||
|
|
||||||
return Offset(size.width / 2 + sx, size.height * 0.70 + sy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calcola la distanza dalla telecamera per lo Z-Buffering
|
|
||||||
double getDepth(Box b) {
|
|
||||||
double actualX = b.x + b.z * 0.5 - board.columns / 2.0;
|
|
||||||
double actualY = b.y + b.z * 0.5 - board.rows / 2.0;
|
|
||||||
double rx = actualX * cos(cameraAngle) - actualY * sin(cameraAngle);
|
|
||||||
double ry = actualX * sin(cameraAngle) + actualY * cos(cameraAngle);
|
|
||||||
return rx + ry; // Ordina dal più lontano al più vicino
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
if (board.shape == ArenaShape.pyramid3D) _paint3D(canvas, size);
|
if (themeType == AppThemeType.doodle) {
|
||||||
else _paint2D(canvas, size);
|
final Paint paperGridPaint = Paint()
|
||||||
}
|
..color = Colors.grey.withOpacity(0.3)
|
||||||
|
..strokeWidth = 1.0
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
void _paint3D(Canvas canvas, Size size) {
|
double paperStep = 20.0;
|
||||||
double visualZHeight = (size.width / 3.8) * 0.45;
|
for (double i = 0; i <= size.width; i += paperStep) {
|
||||||
int maxZ = 4;
|
canvas.drawLine(Offset(i, 0), Offset(i, size.height), paperGridPaint);
|
||||||
Set<Line> drawnLines = {};
|
}
|
||||||
|
for (double i = 0; i <= size.height; i += paperStep) {
|
||||||
// Costruiamo la piramide piano per piano, partendo dal basso
|
canvas.drawLine(Offset(0, i), Offset(size.width, i), paperGridPaint);
|
||||||
for (int currentZ = 0; currentZ < maxZ; currentZ++) {
|
|
||||||
var currentLevelBoxes = board.boxes.where((b) => b.z == currentZ && b.type != BoxType.invisible).toList();
|
|
||||||
|
|
||||||
// Ordiniamo dal fondo allo schermo
|
|
||||||
currentLevelBoxes.sort((a, b) => getDepth(a).compareTo(getDepth(b)));
|
|
||||||
|
|
||||||
for (var box in currentLevelBoxes) {
|
|
||||||
bool isPlayable = box.top.isPlayable;
|
|
||||||
bool isOwned = box.owner != Player.none;
|
|
||||||
if (!isOwned && !isPlayable) continue;
|
|
||||||
|
|
||||||
// Disegniamo prima le LINEE DELLA GRIGLIA relative a questo cubo
|
|
||||||
void drawLine(Line l, double lx1, double ly1, double lx2, double ly2) {
|
|
||||||
if (drawnLines.contains(l)) return;
|
|
||||||
drawnLines.add(l);
|
|
||||||
if (!l.isPlayable && l.owner == Player.none) return;
|
|
||||||
|
|
||||||
Offset pt1 = projectLogical(lx1, ly1, currentZ.toDouble(), size);
|
|
||||||
Offset pt2 = projectLogical(lx2, ly2, currentZ.toDouble(), size);
|
|
||||||
|
|
||||||
if (l.isIceCracked) { _drawCrackedIceLine(canvas, pt1, pt2, blinkValue); return; }
|
|
||||||
Color lineColor = l.owner == Player.none ? theme.gridLine.withOpacity(0.5) : (l.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
|
||||||
if (l == board.lastMove && l.owner != Player.none) canvas.drawLine(pt1, pt2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.8)..strokeWidth = 10.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
|
|
||||||
canvas.drawLine(pt1, pt2, Paint()..color = lineColor..strokeWidth = 4.0..strokeCap = StrokeCap.round);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawLine(box.top, box.x.toDouble(), box.y.toDouble(), box.x + 1.0, box.y.toDouble());
|
|
||||||
drawLine(box.right, box.x + 1.0, box.y.toDouble(), box.x + 1.0, box.y + 1.0);
|
|
||||||
drawLine(box.bottom, box.x.toDouble(), box.y + 1.0, box.x + 1.0, box.y + 1.0);
|
|
||||||
drawLine(box.left, box.x.toDouble(), box.y.toDouble(), box.x.toDouble(), box.y + 1.0);
|
|
||||||
|
|
||||||
// Disegniamo i PALLINI
|
|
||||||
void drawDot(Dot d) {
|
|
||||||
if (board.lines.any((l) => (l.p1 == d || l.p2 == d) && l.isPlayable)) {
|
|
||||||
canvas.drawCircle(projectLogical(d.x.toDouble(), d.y.toDouble(), currentZ.toDouble(), size), 5.0, Paint()..color = theme.text.withOpacity(0.8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drawDot(box.top.p1); drawDot(box.top.p2); drawDot(box.bottom.p2); drawDot(box.bottom.p1);
|
|
||||||
|
|
||||||
// --- IL CUBO SOLIDO ---
|
|
||||||
if (isOwned) {
|
|
||||||
// Calcoliamo i 4 vertici del TETTO (Z + 1)
|
|
||||||
List<Offset> topCorners = [
|
|
||||||
projectLogical(box.x.toDouble(), box.y.toDouble(), currentZ + 1.0, size),
|
|
||||||
projectLogical(box.x + 1.0, box.y.toDouble(), currentZ + 1.0, size),
|
|
||||||
projectLogical(box.x + 1.0, box.y + 1.0, currentZ + 1.0, size),
|
|
||||||
projectLogical(box.x.toDouble(), box.y + 1.0, currentZ + 1.0, size),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Algoritmo Geometrico Infallibile: troviamo la silhouette
|
|
||||||
topCorners.sort((a, b) => a.dy.compareTo(b.dy));
|
|
||||||
Offset screenTop = topCorners[0]; // Il punto più alto sullo schermo
|
|
||||||
Offset screenBottom = topCorners[3]; // Il punto più basso sullo schermo
|
|
||||||
Offset screenLeft = topCorners[1].dx < topCorners[2].dx ? topCorners[1] : topCorners[2];
|
|
||||||
Offset screenRight = topCorners[1].dx > topCorners[2].dx ? topCorners[1] : topCorners[2];
|
|
||||||
|
|
||||||
Color baseColor = box.owner == Player.red ? theme.playerRed : theme.playerBlue;
|
|
||||||
|
|
||||||
// PARETE SINISTRA: Dal tetto scende verso il basso
|
|
||||||
Path leftWall = Path()
|
|
||||||
..moveTo(screenLeft.dx, screenLeft.dy)
|
|
||||||
..lineTo(screenBottom.dx, screenBottom.dy)
|
|
||||||
..lineTo(screenBottom.dx, screenBottom.dy + visualZHeight)
|
|
||||||
..lineTo(screenLeft.dx, screenLeft.dy + visualZHeight)
|
|
||||||
..close();
|
|
||||||
canvas.drawPath(leftWall, Paint()..color = _darken(baseColor, 0.15)..style = PaintingStyle.fill);
|
|
||||||
canvas.drawPath(leftWall, Paint()..color = Colors.black.withOpacity(0.3)..style = PaintingStyle.stroke..strokeWidth = 1.0);
|
|
||||||
|
|
||||||
// PARETE DESTRA: Dal tetto scende verso il basso
|
|
||||||
Path rightWall = Path()
|
|
||||||
..moveTo(screenBottom.dx, screenBottom.dy)
|
|
||||||
..lineTo(screenRight.dx, screenRight.dy)
|
|
||||||
..lineTo(screenRight.dx, screenRight.dy + visualZHeight)
|
|
||||||
..lineTo(screenBottom.dx, screenBottom.dy + visualZHeight)
|
|
||||||
..close();
|
|
||||||
canvas.drawPath(rightWall, Paint()..color = _darken(baseColor, 0.35)..style = PaintingStyle.fill);
|
|
||||||
canvas.drawPath(rightWall, Paint()..color = Colors.black.withOpacity(0.3)..style = PaintingStyle.stroke..strokeWidth = 1.0);
|
|
||||||
|
|
||||||
// IL TETTO: Disegnato per ultimo, copre i muri ed è solido
|
|
||||||
Path roof = Path()..moveTo(screenTop.dx, screenTop.dy)..lineTo(screenRight.dx, screenRight.dy)..lineTo(screenBottom.dx, screenBottom.dy)..lineTo(screenLeft.dx, screenLeft.dy)..close();
|
|
||||||
canvas.drawPath(roof, Paint()..color = baseColor..style = PaintingStyle.fill);
|
|
||||||
canvas.drawPath(roof, Paint()..color = Colors.white.withOpacity(0.5)..style = PaintingStyle.stroke..strokeWidth = 2.0);
|
|
||||||
|
|
||||||
_drawBoxIcon(canvas, roof, box);
|
|
||||||
|
|
||||||
} else if (isPlayable) {
|
|
||||||
// PAVIMENTO VUOTO
|
|
||||||
Offset f0 = projectLogical(box.x.toDouble(), box.y.toDouble(), currentZ.toDouble(), size);
|
|
||||||
Offset f1 = projectLogical(box.x + 1.0, box.y.toDouble(), currentZ.toDouble(), size);
|
|
||||||
Offset f2 = projectLogical(box.x + 1.0, box.y + 1.0, currentZ.toDouble(), size);
|
|
||||||
Offset f3 = projectLogical(box.x.toDouble(), box.y + 1.0, currentZ.toDouble(), size);
|
|
||||||
Path floor = Path()..moveTo(f0.dx, f0.dy)..lineTo(f1.dx, f1.dy)..lineTo(f2.dx, f2.dy)..lineTo(f3.dx, f3.dy)..close();
|
|
||||||
|
|
||||||
canvas.drawPath(floor, Paint()..color = Colors.white.withOpacity(0.08)..style = PaintingStyle.fill);
|
|
||||||
_drawBoxIcon(canvas, floor, box);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _paint2D(Canvas canvas, Size size) {
|
int gridPoints = board.columns + 1;
|
||||||
|
double spacing = size.width / gridPoints;
|
||||||
|
double offset = spacing / 2;
|
||||||
|
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) {
|
||||||
if (box.type == BoxType.invisible) continue;
|
Offset p1 = getScreenPos(box.x, box.y);
|
||||||
Offset p1 = projectLogical(box.top.p1.x.toDouble(), box.top.p1.y.toDouble(), 0, size);
|
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
|
||||||
Offset p2 = projectLogical(box.top.p2.x.toDouble(), box.top.p2.y.toDouble(), 0, size);
|
Rect rect = Rect.fromPoints(p1, p2);
|
||||||
Offset p3 = projectLogical(box.bottom.p2.x.toDouble(), box.bottom.p2.y.toDouble(), 0, size);
|
|
||||||
Offset p4 = projectLogical(box.bottom.p1.x.toDouble(), box.bottom.p1.y.toDouble(), 0, size);
|
if (box.type == BoxType.invisible) {
|
||||||
Path poly = Path()..moveTo(p1.dx, p1.dy)..lineTo(p2.dx, p2.dy)..lineTo(p3.dx, p3.dy)..lineTo(p4.dx, p4.dy)..close();
|
if (box.isRevealed) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.block(themeType), Colors.grey.shade500);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sfondo azzurrino se è di ghiaccio (anche prima di chiuderla)
|
||||||
|
if (box.type == BoxType.ice && box.owner == Player.none) {
|
||||||
|
canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill);
|
||||||
|
}
|
||||||
|
|
||||||
if (box.owner != Player.none) {
|
if (box.owner != Player.none) {
|
||||||
Color c = box.owner == Player.red ? theme.playerRed : theme.playerBlue;
|
final boxPaint = Paint()
|
||||||
canvas.drawPath(poly, Paint()..color = c.withOpacity(0.85)..style = PaintingStyle.fill);
|
..style = PaintingStyle.fill
|
||||||
|
..color = box.owner == Player.red ? theme.playerRed.withOpacity(0.6) : theme.playerBlue.withOpacity(0.6);
|
||||||
|
|
||||||
|
if (themeType == AppThemeType.wood) {
|
||||||
|
_drawFlameBox(canvas, rect, box.owner == Player.red);
|
||||||
|
} else if (themeType == AppThemeType.doodle) {
|
||||||
|
Color penColor = box.owner == Player.red ? Colors.redAccent.shade700 : Colors.blueAccent.shade700;
|
||||||
|
_drawScribbleBox(canvas, rect, penColor);
|
||||||
|
} else if (themeType == AppThemeType.arcade) {
|
||||||
|
_drawArcadeBox(canvas, rect, box.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
||||||
|
} else if (themeType == AppThemeType.grimorio) {
|
||||||
|
_drawGrimorioBox(canvas, rect, box.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
||||||
|
} else {
|
||||||
|
canvas.drawRect(rect, boxPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.hiddenJokerOwner != null) {
|
||||||
|
Color jokerColor = box.hiddenJokerOwner == Player.red ? theme.playerRed : theme.playerBlue;
|
||||||
|
|
||||||
|
if (box.isJokerRevealed) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.joker(themeType), jokerColor);
|
||||||
|
} else {
|
||||||
|
bool canSee = false;
|
||||||
|
if (isOnline || isVsCPU) {
|
||||||
|
canSee = box.hiddenJokerOwner == myPlayer;
|
||||||
|
} else {
|
||||||
|
canSee = false;
|
||||||
|
}
|
||||||
|
if (canSee) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.joker(themeType), jokerColor.withOpacity(0.3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.type == BoxType.gold) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber);
|
||||||
|
} else if (box.type == BoxType.bomb) {
|
||||||
|
_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) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent);
|
||||||
|
} else if (box.type == BoxType.ice) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.ice(themeType), Colors.cyanAccent);
|
||||||
|
} else if (box.type == BoxType.multiplier) {
|
||||||
|
_drawIconInBox(canvas, rect, ThemeIcons.multiplier(themeType), Colors.yellowAccent);
|
||||||
}
|
}
|
||||||
_drawBoxIcon(canvas, poly, box);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var line in board.lines) {
|
for (var line in board.lines) {
|
||||||
if (!line.isPlayable && line.owner == Player.none) continue;
|
if (!line.isPlayable) continue;
|
||||||
Offset p1 = projectLogical(line.p1.x.toDouble(), line.p1.y.toDouble(), 0, size);
|
|
||||||
Offset p2 = projectLogical(line.p2.x.toDouble(), line.p2.y.toDouble(), 0, size);
|
|
||||||
|
|
||||||
if (line.isIceCracked) { _drawCrackedIceLine(canvas, p1, p2, blinkValue); continue; }
|
Offset p1 = getScreenPos(line.p1.x, line.p1.y);
|
||||||
Color lineColor = line.owner == Player.none ? theme.gridLine.withOpacity(0.4) : (line.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
Offset p2 = getScreenPos(line.p2.x, line.p2.y);
|
||||||
if (line == board.lastMove && line.owner != Player.none) canvas.drawLine(p1, p2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.8)..strokeWidth = 12.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
|
|
||||||
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = (line.owner == Player.none ? 3.0 : 6.0)..strokeCap = StrokeCap.round);
|
// --- DISEGNO DELLA LINEA "INCRINATA" DAL GHIACCIO ---
|
||||||
|
if (line.isIceCracked) {
|
||||||
|
_drawCrackedIceLine(canvas, p1, p2, blinkValue);
|
||||||
|
continue; // Non ha ancora un proprietario, passiamo alla prossima!
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLastMove = (line == board.lastMove);
|
||||||
|
Color lineColor = line.owner == Player.none
|
||||||
|
? theme.gridLine.withOpacity(0.4)
|
||||||
|
: (line.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
||||||
|
|
||||||
|
if (isLastMove && line.owner != Player.none && themeType != AppThemeType.wood && themeType != AppThemeType.cyberpunk && themeType != AppThemeType.arcade && themeType != AppThemeType.grimorio) {
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.5)..strokeWidth = 16.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeType == AppThemeType.wood) {
|
||||||
|
if (line.owner == Player.none) {
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = const Color(0xFF3E2723).withOpacity(0.3)..strokeWidth = 4.5..strokeCap = StrokeCap.round);
|
||||||
|
} else {
|
||||||
|
Color headColor = lineColor;
|
||||||
|
if (isLastMove) headColor = Color.lerp(headColor, Colors.yellow, blinkValue * 0.8) ?? headColor;
|
||||||
|
_drawRealisticMatch(canvas, p1, p2, headColor, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
|
}
|
||||||
|
} else if (themeType == AppThemeType.cyberpunk) {
|
||||||
|
_drawNeonLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
|
} else if (themeType == AppThemeType.doodle) {
|
||||||
|
Color doodleColor = line.owner == Player.none ? Colors.black.withOpacity(0.05) : lineColor;
|
||||||
|
if (isLastMove && line.owner != Player.none) doodleColor = Color.lerp(doodleColor, Colors.black, blinkValue * 0.4) ?? doodleColor;
|
||||||
|
_drawWobblyLine(canvas, p1, p2, doodleColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
|
} else if (themeType == AppThemeType.arcade) {
|
||||||
|
_drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||||
|
} else if (themeType == AppThemeType.grimorio) {
|
||||||
|
_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 {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var dot in board.dots) {
|
final dotPaint = Paint()..style = PaintingStyle.fill;
|
||||||
bool isVisible = board.lines.any((l) => (l.p1 == dot || l.p2 == dot) && l.isPlayable);
|
Set<Dot> activeDots = {};
|
||||||
if (isVisible) canvas.drawCircle(projectLogical(dot.x.toDouble(), dot.y.toDouble(), 0, size), 4.0, Paint()..color = theme.text.withOpacity(0.8));
|
for (var line in board.lines) {
|
||||||
|
if (line.isPlayable) {
|
||||||
|
activeDots.add(line.p1); activeDots.add(line.p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var dot in activeDots) {
|
||||||
|
Offset pos = getScreenPos(dot.x, dot.y);
|
||||||
|
if (themeType == AppThemeType.wood) {
|
||||||
|
canvas.drawCircle(pos, 3.5, dotPaint..color = const Color(0xFF3E2723).withOpacity(0.2));
|
||||||
|
} else if (themeType == AppThemeType.cyberpunk) {
|
||||||
|
canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3));
|
||||||
|
canvas.drawCircle(pos, 3.0, Paint()..color = Colors.white.withOpacity(0.5));
|
||||||
|
} else if (themeType == AppThemeType.doodle) {
|
||||||
|
canvas.drawRect(Rect.fromCenter(center: pos, width: 4, height: 4), dotPaint..color = Colors.black.withOpacity(0.25));
|
||||||
|
} else if (themeType == AppThemeType.arcade) {
|
||||||
|
canvas.drawRect(Rect.fromCenter(center: pos, width: 8, height: 8), dotPaint..color = theme.gridLine.withOpacity(0.9));
|
||||||
|
canvas.drawRect(Rect.fromCenter(center: pos, width: 4, height: 4), dotPaint..color = theme.background);
|
||||||
|
} else if (themeType == AppThemeType.grimorio) {
|
||||||
|
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();
|
||||||
|
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 {
|
||||||
|
canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawBoxIcon(Canvas canvas, Path face, Box box) {
|
void _drawIconInBox(Canvas canvas, Rect rect, IconData icon, Color color) {
|
||||||
if (box.type == BoxType.gold) _drawIconOnPath(canvas, face, FontAwesomeIcons.crown, Colors.amber);
|
TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||||
else if (box.type == BoxType.bomb) _drawIconOnPath(canvas, face, FontAwesomeIcons.skull, Colors.redAccent);
|
textPainter.text = TextSpan(
|
||||||
else if (box.type == BoxType.swap) _drawIconOnPath(canvas, face, FontAwesomeIcons.arrowsRotate, Colors.purpleAccent);
|
text: String.fromCharCode(icon.codePoint),
|
||||||
else if (box.type == BoxType.multiplier) _drawIconOnPath(canvas, face, FontAwesomeIcons.bolt, Colors.yellowAccent);
|
style: TextStyle(
|
||||||
else if (box.type == BoxType.ice && box.owner == Player.none) {
|
color: themeType == AppThemeType.arcade ? color : color.withOpacity(0.7),
|
||||||
canvas.drawPath(face, Paint()..color = Colors.cyanAccent.withOpacity(0.15)..style = PaintingStyle.fill);
|
fontSize: rect.width * 0.45,
|
||||||
_drawIconOnPath(canvas, face, FontAwesomeIcons.snowflake, Colors.cyanAccent);
|
fontFamily: icon.fontFamily,
|
||||||
}
|
package: icon.fontPackage,
|
||||||
}
|
shadows: themeType == AppThemeType.arcade ? [] : [Shadow(color: color.withOpacity(0.6), blurRadius: 10, offset: const Offset(0, 0))]
|
||||||
|
),
|
||||||
void _drawIconOnPath(Canvas canvas, Path path, IconData icon, Color color) {
|
);
|
||||||
Rect bounds = path.getBounds();
|
textPainter.layout();
|
||||||
TextPainter tp = TextPainter(text: TextSpan(text: String.fromCharCode(icon.codePoint), style: TextStyle(color: color, fontSize: 16, fontFamily: icon.fontFamily, package: icon.fontPackage)), textDirection: TextDirection.ltr)..layout();
|
textPainter.paint(canvas, Offset(rect.center.dx - textPainter.width / 2, rect.center.dy - textPainter.height / 2));
|
||||||
tp.paint(canvas, Offset(bounds.center.dx - tp.width / 2, bounds.center.dy - tp.height / 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawCrackedIceLine(Canvas canvas, Offset p1, Offset p2, double blink) {
|
void _drawCrackedIceLine(Canvas canvas, Offset p1, Offset p2, double blink) {
|
||||||
Paint crackPaint = Paint()..color = Colors.cyanAccent.withOpacity(0.6 + (0.4 * blink))..strokeWidth = 3.0..style = PaintingStyle.stroke..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
|
Paint crackPaint = Paint()
|
||||||
|
..color = Colors.cyanAccent.withOpacity(0.6 + (0.4 * blink))
|
||||||
|
..strokeWidth = 3.0
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeCap = StrokeCap.round
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
|
||||||
|
|
||||||
|
// Effetto linea frammentata
|
||||||
canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
|
canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
|
||||||
double dx = p2.dx - p1.dx; double dy = p2.dy - p1.dy;
|
|
||||||
double len = sqrt(dx * dx + dy * dy);
|
Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy);
|
||||||
if (len == 0) return;
|
double len = dir.length; Vector2 ndir = dir.normalized(); Vector2 perp = Vector2(-ndir.y, ndir.x);
|
||||||
double ndx = dx / len; double ndy = dy / len;
|
|
||||||
Path crack = Path()..moveTo(p1.dx, p1.dy);
|
Path crack = Path()..moveTo(p1.dx, p1.dy);
|
||||||
for (int i = 1; i < 6; i++) {
|
int zigzags = 6;
|
||||||
|
for (int i=1; i<zigzags; i++) {
|
||||||
|
double d = len * (i / zigzags);
|
||||||
|
Offset basePt = Offset(p1.dx + ndir.x * d, p1.dy + ndir.y * d);
|
||||||
double offset = (i % 2 == 0 ? 3.0 : -3.0);
|
double offset = (i % 2 == 0 ? 3.0 : -3.0);
|
||||||
crack.lineTo(p1.dx + ndx * (len * (i / 6)) + (-ndy) * offset, p1.dy + ndy * (len * (i / 6)) + ndx * offset);
|
crack.lineTo(basePt.dx + perp.x * offset, basePt.dy + perp.y * offset);
|
||||||
}
|
}
|
||||||
crack.lineTo(p2.dx, p2.dy);
|
crack.lineTo(p2.dx, p2.dy);
|
||||||
canvas.drawPath(crack, crackPaint);
|
canvas.drawPath(crack, crackPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _drawArcadeBox(Canvas canvas, Rect rect, Color color) {
|
||||||
|
double pixelSize = 4.0; Paint paint = Paint()..color = color.withOpacity(0.9)..style = PaintingStyle.fill;
|
||||||
|
for (double y = rect.top; y < rect.bottom; y += pixelSize) {
|
||||||
|
for (double x = rect.left; x < rect.right; x += pixelSize) {
|
||||||
|
int xi = ((x - rect.left) / pixelSize).floor(); int yi = ((y - rect.top) / pixelSize).floor();
|
||||||
|
if ((xi + yi) % 2 == 0) canvas.drawRect(Rect.fromLTWH(x, y, pixelSize, pixelSize), paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.white.withOpacity(0.4)..style = PaintingStyle.stroke..strokeWidth = 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawGrimorioBox(Canvas canvas, Rect rect, Color color) {
|
||||||
|
canvas.drawRect(rect, Paint()..color = color.withOpacity(0.15)..style=PaintingStyle.fill);
|
||||||
|
Offset c = rect.center; double r = rect.width * 0.35;
|
||||||
|
Paint linePaint = Paint()..color = color.withOpacity(0.8)..style = PaintingStyle.stroke..strokeWidth = 1.5..maskFilter = const MaskFilter.blur(BlurStyle.solid, 1.0);
|
||||||
|
canvas.drawCircle(c, r, linePaint); canvas.drawCircle(c, r * 0.8, linePaint..strokeWidth = 0.5);
|
||||||
|
Path p = Path();
|
||||||
|
for(int i=0; i<3; i++) {
|
||||||
|
double a = -pi/2 + i * 2*pi/3; Offset pt = Offset(c.dx + r*cos(a), c.dy + r*sin(a));
|
||||||
|
if(i==0) p.moveTo(pt.dx, pt.dy); else p.lineTo(pt.dx, pt.dy);
|
||||||
|
}
|
||||||
|
p.close(); canvas.drawPath(p, linePaint..strokeWidth = 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawArcadeLine(Canvas canvas, Offset p1, Offset p2, Color color, bool isConquered, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||||
|
double pixelSize = 6.0; Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy); double len = dir.length; Vector2 ndir = dir.normalized();
|
||||||
|
Paint paint = Paint()..color = isConquered ? color : color.withOpacity(0.15)..style = PaintingStyle.fill;
|
||||||
|
Paint highlight = Paint()..color = Colors.white.withOpacity(0.6)..style = PaintingStyle.fill;
|
||||||
|
for(double d = 0; d <= len; d += pixelSize + 1.0) {
|
||||||
|
Offset pt = Offset(p1.dx + ndir.x * d, p1.dy + ndir.y * d);
|
||||||
|
canvas.drawRect(Rect.fromCenter(center: pt, width: pixelSize, height: pixelSize), paint);
|
||||||
|
if (isConquered && (d / (pixelSize+1.0)).floor() % 3 == 0) canvas.drawRect(Rect.fromCenter(center: pt - const Offset(1,1), width: pixelSize*0.4, height: pixelSize*0.4), highlight);
|
||||||
|
}
|
||||||
|
if (isLastMove && isConquered) canvas.drawRect(Rect.fromPoints(p1, p2).inflate(4.0), Paint()..color = Colors.white.withOpacity(blinkValue*0.4)..style=PaintingStyle.stroke..strokeWidth=2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawGrimorioLine(Canvas canvas, Offset p1, Offset p2, Color color, bool isConquered, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||||
|
if (!isConquered) { canvas.drawLine(p1, p2, Paint()..color = color.withOpacity(0.15)..strokeWidth = 2.0..strokeCap = StrokeCap.round); return; }
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = color.withOpacity(0.6)..strokeWidth = 5.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = Colors.white.withOpacity(0.7)..strokeWidth = 1.5..strokeCap = StrokeCap.round);
|
||||||
|
int seed = (p1.dx * 1000 + p1.dy).toInt(); Random rand = Random(seed);
|
||||||
|
Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy); double len = dir.length; Vector2 ndir = dir.normalized(); Vector2 perp = Vector2(-ndir.y, ndir.x);
|
||||||
|
Path thread1 = Path(); Path thread2 = Path(); int segments = 15; double step = len / segments;
|
||||||
|
double phaseOffset = (isLastMove ? blinkValue * pi * 4 : 0) + rand.nextDouble()*pi;
|
||||||
|
for(int i = 0; i <= segments; i++) {
|
||||||
|
double d = i * step; Offset basePt = Offset(p1.dx + ndir.x * d, p1.dy + ndir.y * d);
|
||||||
|
double amplitude = 3.5; double wave1 = sin(d * 0.15 + phaseOffset) * amplitude; double wave2 = cos(d * 0.15 + phaseOffset) * amplitude;
|
||||||
|
Offset pt1 = basePt + Offset(perp.x * wave1, perp.y * wave1); Offset pt2 = basePt + Offset(perp.x * wave2, perp.y * wave2);
|
||||||
|
if (i == 0) { thread1.moveTo(pt1.dx, pt1.dy); thread2.moveTo(pt2.dx, pt2.dy); } else { thread1.lineTo(pt1.dx, pt1.dy); thread2.lineTo(pt2.dx, pt2.dy); }
|
||||||
|
}
|
||||||
|
Paint threadPaint = Paint()..color = color.withOpacity(0.9)..style = PaintingStyle.stroke..strokeWidth = 1.5..maskFilter = const MaskFilter.blur(BlurStyle.solid, 1.0);
|
||||||
|
canvas.drawPath(thread1, threadPaint); canvas.drawPath(thread2, threadPaint..color = Colors.white.withOpacity(0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawFlameBox(Canvas canvas, Rect baseRect, bool isRed) {
|
||||||
|
final rand = Random((baseRect.left + baseRect.top).toInt());
|
||||||
|
Offset center = baseRect.center; double w = baseRect.width * 0.35; double h = baseRect.height * 0.55; Offset bottomCenter = Offset(center.dx, center.dy + h * 0.5);
|
||||||
|
Color outerColor = isRed ? Colors.red.shade600.withOpacity(0.85) : Colors.blue.shade700.withOpacity(0.85); Color midColor = isRed ? Colors.orangeAccent : Colors.lightBlueAccent; Color coreColor = isRed ? Colors.yellowAccent : Colors.white;
|
||||||
|
canvas.drawOval(Rect.fromCenter(center: bottomCenter, width: w * 1.5, height: w * 0.5), Paint()..color = Colors.black.withOpacity(0.4)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
|
||||||
|
void drawFlameLayer(double scale, Color color, double tipOffsetX) {
|
||||||
|
Path path = Path(); double fw = w * scale; double fh = h * scale;
|
||||||
|
path.moveTo(bottomCenter.dx, bottomCenter.dy); path.cubicTo(bottomCenter.dx + fw, bottomCenter.dy, bottomCenter.dx + fw * 0.8, bottomCenter.dy - fh * 0.6, bottomCenter.dx + tipOffsetX, bottomCenter.dy - fh); path.cubicTo(bottomCenter.dx - fw * 0.8, bottomCenter.dy - fh * 0.6, bottomCenter.dx - fw, bottomCenter.dy, bottomCenter.dx, bottomCenter.dy);
|
||||||
|
canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.fill..maskFilter = const MaskFilter.blur(BlurStyle.normal, 1.5));
|
||||||
|
}
|
||||||
|
double randomTipX = (rand.nextDouble() - 0.5) * w * 0.8; drawFlameLayer(1.0, outerColor, randomTipX); drawFlameLayer(0.65, midColor.withOpacity(0.9), randomTipX * 0.6); drawFlameLayer(0.35, coreColor.withOpacity(0.9), randomTipX * 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawScribbleBox(Canvas canvas, Rect baseRect, Color color) {
|
||||||
|
final rand = Random((baseRect.left + baseRect.top).toInt());
|
||||||
|
final paint = Paint()..color = color.withOpacity(0.85)..style = PaintingStyle.stroke..strokeWidth = 3.5..strokeCap = StrokeCap.round..strokeJoin = StrokeJoin.round;
|
||||||
|
final path = Path(); Rect rect = baseRect.deflate(4.0); int numZigs = 15 + rand.nextInt(6); double stepY = rect.height / numZigs;
|
||||||
|
path.moveTo(rect.left + rand.nextDouble() * 5, rect.top + rand.nextDouble() * 5);
|
||||||
|
for (int i = 1; i <= numZigs; i++) { double targetX = (i % 2 != 0) ? rect.right + (rand.nextDouble() * 4 - 2) : rect.left + (rand.nextDouble() * 4 - 2); double targetY = rect.top + stepY * i + (rand.nextDouble() - 0.5) * 3; double ctrlX = rect.center.dx + (rand.nextDouble() - 0.5) * 20; double ctrlY = targetY - stepY / 2; path.quadraticBezierTo(ctrlX, ctrlY, targetX, targetY); }
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawRealisticMatch(Canvas canvas, Offset p1, Offset p2, Color headColor, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||||
|
int seed = (p1.dx * 1000 + p1.dy).toInt(); Random rand = Random(seed); Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy).normalized(); double shrink = 8.0; Offset start = Offset(p1.dx + dir.x * shrink, p1.dy + dir.y * shrink); Offset end = Offset(p2.dx - dir.x * shrink, p2.dy - dir.y * shrink); start += Offset(rand.nextDouble() * 4 - 2, rand.nextDouble() * 4 - 2); end += Offset(rand.nextDouble() * 4 - 2, rand.nextDouble() * 4 - 2); bool headAtEnd = rand.nextBool(); Offset headPos = headAtEnd ? end : start; Offset tailPos = headAtEnd ? start : end; Vector2 matchDir = Vector2(headPos.dx - tailPos.dx, headPos.dy - tailPos.dy).normalized();
|
||||||
|
canvas.drawLine(tailPos + const Offset(4, 4), headPos + const Offset(4, 4), Paint()..color = Colors.black.withOpacity(0.6)..strokeWidth = 7.0..strokeCap = StrokeCap.round);
|
||||||
|
if (isLastMove) { canvas.drawCircle(headPos, 8.0 + (blinkValue * 6.0), Paint()..color = Colors.orangeAccent.withOpacity(0.6 * blinkValue)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6.0)); }
|
||||||
|
canvas.drawLine(tailPos, headPos, Paint()..color = const Color(0xFF6D4C41)..strokeWidth = 7.0..strokeCap = StrokeCap.round); canvas.drawLine(tailPos, headPos, Paint()..color = const Color(0xFFEDC498)..strokeWidth = 4.0..strokeCap = StrokeCap.round); Offset burnPos = Offset(headPos.dx - matchDir.x * 8, headPos.dy - matchDir.y * 8); canvas.drawLine(burnPos, headPos, Paint()..color = const Color(0xFF2E1A14)..strokeWidth = 6.0..strokeCap = StrokeCap.round);
|
||||||
|
canvas.save(); canvas.translate(headPos.dx, headPos.dy); double angle = atan2(matchDir.y, matchDir.x); canvas.rotate(angle); Rect headOval = Rect.fromCenter(center: Offset.zero, width: 18.0, height: 13.0); canvas.drawOval(headOval.shift(const Offset(1, 2)), Paint()..color = Colors.black.withOpacity(0.6)); canvas.drawOval(headOval, Paint()..color = headColor); canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawNeonLine(Canvas canvas, Offset p1, Offset p2, Color color, bool isConquered, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||||
|
double mainWidth = isConquered ? (isLastMove ? 6.0 + (blinkValue * 3.0) : 6.0) : 3.0; Color coreColor = isConquered ? (isLastMove ? Color.lerp(Colors.white, color, 1.0 - blinkValue)! : Colors.white.withOpacity(0.9)) : color.withOpacity(0.6);
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = color.withOpacity(isConquered ? (isLastMove ? 0.4 + (0.4 * blinkValue) : 0.4) : 0.2)..strokeWidth = mainWidth * 4..strokeCap = StrokeCap.round..maskFilter = MaskFilter.blur(BlurStyle.normal, isConquered ? 12.0 : 6.0));
|
||||||
|
if (isConquered) { canvas.drawLine(p1, p2, Paint()..color = color.withOpacity(isLastMove ? 0.7 + (0.3 * blinkValue) : 0.7)..strokeWidth = mainWidth * 2..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6.0)); }
|
||||||
|
canvas.drawLine(p1, p2, Paint()..color = coreColor..strokeWidth = mainWidth..strokeCap = StrokeCap.round);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawWobblyLine(Canvas canvas, Offset p1, Offset p2, Color color, bool isConquered, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||||
|
final random = Random((p1.dx + p1.dy + p2.dx + p2.dy).toInt()); final dx = p2.dx - p1.dx; final dy = p2.dy - p1.dy;
|
||||||
|
double strokeW = isConquered ? (isLastMove ? 4.5 + (2.0 * blinkValue) : 4.5) : 2.0;
|
||||||
|
final basePaint = Paint()..color = color..strokeWidth = strokeW..style = PaintingStyle.stroke..strokeCap = StrokeCap.round;
|
||||||
|
final mid1 = Offset(p1.dx + dx / 2 + (random.nextDouble() - 0.5) * 8, p1.dy + dy / 2 + (random.nextDouble() - 0.5) * 8); canvas.drawPath(Path()..moveTo(p1.dx, p1.dy)..quadraticBezierTo(mid1.dx, mid1.dy, p2.dx, p2.dy), basePaint);
|
||||||
|
final mid2 = Offset(p1.dx + dx / 2 + (random.nextDouble() - 0.5) * 6, p1.dy + dy / 2 + (random.nextDouble() - 0.5) * 6); canvas.drawPath(Path()..moveTo(p1.dx, p1.dy)..quadraticBezierTo(mid2.dx, mid2.dy, p2.dx, p2.dy), basePaint..strokeWidth = strokeW * 0.5..color = color.withOpacity(0.8));
|
||||||
|
}
|
||||||
|
|
||||||
@override bool shouldRepaint(covariant BoardPainter oldDelegate) => true;
|
@override bool shouldRepaint(covariant BoardPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vector2 {
|
||||||
|
final double x, y; Vector2(this.x, this.y); double get length => sqrt(x * x + y * y);
|
||||||
|
Vector2 normalized() { double l = length; return l == 0 ? Vector2(0, 0) : Vector2(x / l, y / l); }
|
||||||
}
|
}
|
||||||
|
|
@ -5,15 +5,16 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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 '../../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) {
|
||||||
|
|
@ -25,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;
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +48,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
bool _wasSetupPhase = false;
|
bool _wasSetupPhase = false;
|
||||||
Player _lastJokerTurn = Player.red;
|
Player _lastJokerTurn = Player.red;
|
||||||
|
|
||||||
double _cameraAngle = 0.0; // La nostra telecamera fluida a 360 gradi
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -58,18 +59,30 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
|
|
||||||
void _showGameOverDialog(BuildContext context, GameController game, ThemeColors theme, AppThemeType themeType) {
|
void _showGameOverDialog(BuildContext context, GameController game, ThemeColors theme, AppThemeType themeType) {
|
||||||
_gameOverDialogShown = true;
|
_gameOverDialogShown = true;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
barrierDismissible: false, context: context,
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
builder: (dialogContext) => Consumer<GameController>(
|
builder: (dialogContext) => Consumer<GameController>(
|
||||||
builder: (context, controller, child) {
|
builder: (context, controller, child) {
|
||||||
if (!controller.isGameOver) {
|
if (!controller.isGameOver) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) { if (_gameOverDialogShown) { _gameOverDialogShown = false; if (Navigator.canPop(dialogContext)) Navigator.pop(dialogContext); } });
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_gameOverDialogShown) {
|
||||||
|
_gameOverDialogShown = false;
|
||||||
|
if (Navigator.canPop(dialogContext)) Navigator.pop(dialogContext);
|
||||||
|
}
|
||||||
|
});
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -78,7 +91,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
else { winnerText = "PAREGGIO!"; winnerColor = theme.text; }
|
else { winnerText = "PAREGGIO!"; winnerColor = theme.text; }
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: theme.background, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: winnerColor.withOpacity(0.5), width: 2)),
|
backgroundColor: theme.background,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: winnerColor.withOpacity(0.5), width: 2)),
|
||||||
title: Text("FINE PARTITA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 22))),
|
title: Text("FINE PARTITA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 22))),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -86,49 +100,83 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
Text(winnerText, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: winnerColor))),
|
Text(winnerText, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: winnerColor))),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
child: Row(
|
decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: FittedBox(
|
||||||
children: [
|
fit: BoxFit.scaleDown,
|
||||||
Text("$nameRed: $red", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerRed))),
|
child: Row(
|
||||||
Text(" - ", style: _getTextStyle(themeType, TextStyle(fontSize: 18, color: theme.text))),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text("$nameBlue: $blue", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.playerBlue))),
|
children: [
|
||||||
],
|
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(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||||
decoration: BoxDecoration(color: Colors.green.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.greenAccent, width: 1.5), boxShadow: themeType == AppThemeType.cyberpunk ? [const BoxShadow(color: Colors.greenAccent, blurRadius: 10, spreadRadius: -5)] : []),
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.green.withOpacity(0.15),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.greenAccent, width: 1.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))),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
if (controller.isVsCPU) ...[
|
if (controller.isVsCPU) ...[
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
Text("Difficoltà CPU: Livello ${controller.cpuLevel}", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: theme.text.withOpacity(0.7)))),
|
Text("Difficoltà CPU: Livello ${controller.cpuLevel}", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: theme.text.withOpacity(0.7)))),
|
||||||
],
|
],
|
||||||
if (controller.isOnline) ...[
|
if (controller.isOnline) ...[
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (controller.rematchRequested && !controller.opponentWantsRematch) Text("In attesa di $nameBlue...", style: _getTextStyle(themeType, const TextStyle(color: Colors.amber, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic))),
|
if (controller.rematchRequested && !controller.opponentWantsRematch)
|
||||||
if (controller.opponentWantsRematch && !controller.rematchRequested) Text("$nameBlue vuole la rivincita!", style: _getTextStyle(themeType, const TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.bold))),
|
Text("In attesa di $nameBlue...", style: _getTextStyle(themeType, const TextStyle(color: Colors.amber, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic))),
|
||||||
if (controller.rematchRequested && controller.opponentWantsRematch) Text("Avvio nuova partita...", style: _getTextStyle(themeType, const TextStyle(color: Colors.green, fontWeight: FontWeight.bold))),
|
if (controller.opponentWantsRematch && !controller.rematchRequested)
|
||||||
|
Text("$nameBlue vuole la rivincita!", style: _getTextStyle(themeType, const TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.bold))),
|
||||||
|
if (controller.rematchRequested && controller.opponentWantsRematch)
|
||||||
|
Text("Avvio nuova partita...", style: _getTextStyle(themeType, const TextStyle(color: Colors.green, fontWeight: FontWeight.bold))),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actionsPadding: const EdgeInsets.only(left: 20, right: 20, bottom: 20, top: 10), actionsAlignment: MainAxisAlignment.center,
|
actionsPadding: const EdgeInsets.only(left: 20, right: 20, bottom: 20, top: 10),
|
||||||
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
actions: [
|
actions: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
if (playerBeatCPU)
|
if (playerBeatCPU)
|
||||||
ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: winnerColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5), onPressed: () { controller.increaseLevelAndRestart(); }, child: Text("PROSSIMO LIVELLO ➔", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16))))
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: winnerColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5),
|
||||||
|
onPressed: () { controller.increaseLevelAndRestart(); },
|
||||||
|
child: Text("PROSSIMO LIVELLO ➔", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16))),
|
||||||
|
)
|
||||||
else if (controller.isOnline)
|
else if (controller.isOnline)
|
||||||
ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: controller.rematchRequested ? Colors.grey : (winnerColor == theme.text ? theme.playerBlue : winnerColor), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5), onPressed: controller.rematchRequested ? null : () { controller.requestRematch(); }, child: Text(controller.opponentWantsRematch ? "ACCETTA RIVINCITA" : "CHIEDI RIVINCITA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 1.0))))
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: controller.rematchRequested ? Colors.grey : (winnerColor == theme.text ? theme.playerBlue : winnerColor), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5),
|
||||||
|
onPressed: controller.rematchRequested ? null : () { controller.requestRematch(); },
|
||||||
|
child: Text(controller.opponentWantsRematch ? "ACCETTA RIVINCITA" : "CHIEDI RIVINCITA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 1.0))),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: winnerColor == theme.text ? theme.playerBlue : winnerColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5), onPressed: () { controller.startNewGame(controller.board.radius, vsCPU: controller.isVsCPU, shape: controller.board.shape, timeMode: controller.isTimeMode); }, child: Text("RIGIOCA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 2)))),
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: winnerColor == theme.text ? theme.playerBlue : winnerColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), elevation: 5),
|
||||||
|
onPressed: () { controller.startNewGame(controller.board.radius, vsCPU: controller.isVsCPU, shape: controller.board.shape, timeMode: controller.isTimeMode); },
|
||||||
|
child: Text("RIGIOCA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, letterSpacing: 2))),
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
OutlinedButton(style: OutlinedButton.styleFrom(foregroundColor: theme.text, side: BorderSide(color: theme.text.withOpacity(0.3), width: 2), padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), onPressed: () { if (controller.isOnline) controller.disconnectOnlineGame(); _gameOverDialogShown = false; Navigator.pop(dialogContext); Navigator.pop(context); }, child: Text("TORNA AL MENU", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text, fontSize: 14, letterSpacing: 1.5)))),
|
OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(foregroundColor: theme.text, side: BorderSide(color: theme.text.withOpacity(0.3), width: 2), padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
|
||||||
|
onPressed: () {
|
||||||
|
if (controller.isOnline) controller.disconnectOnlineGame();
|
||||||
|
_gameOverDialogShown = false;
|
||||||
|
Navigator.pop(dialogContext); Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text("TORNA AL MENU", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text, fontSize: 14, letterSpacing: 1.5))),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -139,19 +187,64 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildThemedJokerMessage(ThemeColors theme, AppThemeType themeType, GameController gameController) {
|
Widget _buildThemedJokerMessage(ThemeColors theme, AppThemeType themeType, GameController gameController) {
|
||||||
String titleText = ""; String subtitleText = "";
|
String titleText = "";
|
||||||
if (gameController.isOnline) { titleText = gameController.myJokerPlaced ? "In attesa dell'avversario..." : "Nascondi il tuo Jolly!"; subtitleText = gameController.myJokerPlaced ? "" : "(Tocca qui per nascondere)"; }
|
String subtitleText = "";
|
||||||
else if (gameController.isVsCPU) { titleText = "Nascondi il tuo Jolly!"; subtitleText = "(Tocca qui per nascondere)"; }
|
|
||||||
else { String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"); titleText = "TURNO GIOCATORE $pName"; subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)"; }
|
|
||||||
|
|
||||||
Widget content = Padding(padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 25), child: Column(mainAxisSize: MainAxisSize.min, children: [Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.yellowAccent : theme.playerBlue, size: 50), const SizedBox(height: 15), Text(titleText, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? Colors.black87 : theme.text, fontSize: 20, fontWeight: FontWeight.bold))), const SizedBox(height: 25), Text(subtitleText, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? Colors.black54 : theme.text.withOpacity(0.6), fontSize: 12, height: 1.5)))]));
|
if (gameController.isOnline) {
|
||||||
|
titleText = gameController.myJokerPlaced ? "In attesa dell'avversario..." : "Nascondi il tuo Jolly!";
|
||||||
|
subtitleText = gameController.myJokerPlaced ? "" : "(Tocca qui per nascondere)";
|
||||||
|
} else if (gameController.isVsCPU) {
|
||||||
|
titleText = "Nascondi il tuo Jolly!";
|
||||||
|
subtitleText = "(Tocca qui per nascondere)";
|
||||||
|
} else {
|
||||||
|
String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? "VERDE" : "BLU");
|
||||||
|
titleText = "TURNO GIOCATORE $pName";
|
||||||
|
subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)";
|
||||||
|
}
|
||||||
|
|
||||||
if (themeType == AppThemeType.cyberpunk) 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);
|
Widget content = Padding(
|
||||||
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);
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 25),
|
||||||
else if (themeType == AppThemeType.wood) return Container(decoration: BoxDecoration(color: const Color(0xFF5D4037), borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xFF3E2723), width: 4), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), blurRadius: 15, offset: const Offset(0, 8))]), child: content);
|
child: Column(
|
||||||
else if (themeType == AppThemeType.arcade) return Container(decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.zero, border: Border.all(color: Colors.greenAccent, width: 4)), child: content);
|
mainAxisSize: MainAxisSize.min,
|
||||||
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);
|
children: [
|
||||||
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);
|
Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.yellowAccent : theme.playerBlue, size: 50),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
Text(
|
||||||
|
titleText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: _getTextStyle(themeType, TextStyle(
|
||||||
|
color: themeType == AppThemeType.doodle ? Colors.black87 : theme.text,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 25),
|
||||||
|
Text(
|
||||||
|
subtitleText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: _getTextStyle(themeType, TextStyle(
|
||||||
|
color: themeType == AppThemeType.doodle ? Colors.black54 : theme.text.withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
height: 1.5
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
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.purpleAccent, width: 2), boxShadow: [BoxShadow(color: Colors.purpleAccent.withOpacity(0.6), blurRadius: 15, spreadRadius: 0)]), child: content);
|
||||||
|
} 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);
|
||||||
|
} else if (themeType == AppThemeType.wood) {
|
||||||
|
return Container(decoration: BoxDecoration(color: const Color(0xFF5D4037), borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xFF3E2723), width: 4), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), blurRadius: 15, offset: const Offset(0, 8))]), child: content);
|
||||||
|
} else if (themeType == AppThemeType.arcade) {
|
||||||
|
return Container(decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.zero, border: Border.all(color: Colors.greenAccent, width: 4)), child: content);
|
||||||
|
} 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);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -161,14 +254,36 @@ 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>();
|
||||||
|
|
||||||
if (gameController.isSetupPhase && !_wasSetupPhase) { _hideJokerMessage = false; _lastJokerTurn = Player.red; }
|
if (gameController.isSetupPhase && !_wasSetupPhase) {
|
||||||
else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) { _hideJokerMessage = false; _lastJokerTurn = gameController.jokerTurn; }
|
_hideJokerMessage = false;
|
||||||
|
_lastJokerTurn = Player.red;
|
||||||
|
} else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) {
|
||||||
|
_hideJokerMessage = false;
|
||||||
|
_lastJokerTurn = gameController.jokerTurn;
|
||||||
|
}
|
||||||
_wasSetupPhase = gameController.isSetupPhase;
|
_wasSetupPhase = gameController.isSetupPhase;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (gameController.opponentLeft && !_opponentLeftDialogShown) {
|
if (gameController.opponentLeft && !_opponentLeftDialogShown) {
|
||||||
_opponentLeftDialogShown = true;
|
_opponentLeftDialogShown = true;
|
||||||
showDialog(barrierDismissible: false, context: context, builder: (dialogContext) => AlertDialog(backgroundColor: theme.background, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Text("VITTORIA A TAVOLINO!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold))), content: Text("L'avversario ha abbandonato la stanza.\nSei il vincitore incontestato!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 16))), actionsAlignment: MainAxisAlignment.center, actions: [ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(dialogContext); Navigator.pop(context); }, child: Text("MENU PRINCIPALE", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold))))]));
|
showDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AlertDialog(
|
||||||
|
backgroundColor: theme.background,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
title: Text("VITTORIA A TAVOLINO!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold))),
|
||||||
|
content: Text("L'avversario ha abbandonato la stanza.\nSei il vincitore incontestato!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 16))),
|
||||||
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
|
||||||
|
onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(dialogContext); Navigator.pop(context); },
|
||||||
|
child: Text("MENU PRINCIPALE", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.bold))),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
} else if (gameController.board.isGameOver && !_gameOverDialogShown) {
|
} else if (gameController.board.isGameOver && !_gameOverDialogShown) {
|
||||||
_showGameOverDialog(context, gameController, theme, themeType);
|
_showGameOverDialog(context, gameController, theme, themeType);
|
||||||
}
|
}
|
||||||
|
|
@ -176,14 +291,30 @@ 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) {
|
||||||
final List<String> emojis = ['😂', '😡', '😱', '🥳', '👀'];
|
final List<String> emojis = ['😂', '😡', '😱', '🥳', '👀'];
|
||||||
emojiBar = Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration(color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(30), border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.black12, width: 2)), child: Row(mainAxisSize: MainAxisSize.min, children: emojis.map((e) => GestureDetector(onTap: () => gameController.sendReaction(e), child: Padding(padding: const EdgeInsets.symmetric(horizontal: 6), child: Text(e, style: const TextStyle(fontSize: 22))))).toList()));
|
emojiBar = Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8),
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
border: Border.all(color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: emojis.map((e) => GestureDetector(
|
||||||
|
onTap: () => gameController.sendReaction(e),
|
||||||
|
child: Padding(padding: const EdgeInsets.symmetric(horizontal: 6), child: Text(e, style: const TextStyle(fontSize: 22))),
|
||||||
|
)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget gameContent = SafeArea(
|
Widget gameContent = SafeArea(
|
||||||
|
|
@ -208,31 +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: [
|
||||||
onTapUp: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType),
|
// --- IL VERO SFONDO SFOCATO SAGOMATO ---
|
||||||
onPanUpdate: (details) {
|
if (themeType == AppThemeType.music)
|
||||||
if (gameController.board.shape == ArenaShape.pyramid3D) {
|
Positioned.fill(
|
||||||
setState(() {
|
child: ClipPath(
|
||||||
_cameraAngle -= details.delta.dx * 0.015;
|
clipper: _ArenaClipper(gameController.board),
|
||||||
});
|
child: BackdropFilter(
|
||||||
}
|
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
|
||||||
},
|
child: Container(
|
||||||
child: AnimatedBuilder(
|
color: Colors.white.withOpacity(0.08),
|
||||||
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,
|
|
||||||
cameraAngle: _cameraAngle,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
),
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -247,44 +388,118 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
if (gameController.isVsCPU)
|
if (gameController.isVsCPU)
|
||||||
Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: indicatorColor.withOpacity(0.1), borderRadius: BorderRadius.circular(20), border: Border.all(color: indicatorColor.withOpacity(0.3))), child: Row(mainAxisSize: MainAxisSize.min, children: [Icon(Icons.smart_toy_rounded, size: 16, color: indicatorColor), const SizedBox(width: 8), Text("LIVELLO CPU: ${gameController.cpuLevel}", style: _getTextStyle(themeType, TextStyle(color: indicatorColor, fontWeight: FontWeight.bold, fontSize: 11, letterSpacing: 1.0)))]))
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(color: indicatorColor.withOpacity(0.1), borderRadius: BorderRadius.circular(20), border: Border.all(color: indicatorColor.withOpacity(0.3))),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.smart_toy_rounded, size: 16, color: indicatorColor), const SizedBox(width: 8),
|
||||||
|
Text("LIVELLO CPU: ${gameController.cpuLevel}", style: _getTextStyle(themeType, TextStyle(color: indicatorColor, fontWeight: FontWeight.bold, fontSize: 11, letterSpacing: 1.0))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
emojiBar,
|
emojiBar,
|
||||||
|
|
||||||
Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), offset: const Offset(0, 4), blurRadius: 5)]), child: TextButton.icon(style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))), icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20), onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); }, label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))))),
|
Container(
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), offset: const Offset(0, 4), blurRadius: 5)]),
|
||||||
|
child: TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))),
|
||||||
|
icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20),
|
||||||
|
onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); },
|
||||||
|
label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
if (gameController.board.shape == ArenaShape.pyramid3D)
|
if (gameController.myReaction != null)
|
||||||
Positioned(top: 80, left: 0, right: 0, child: Center(child: Text("Scorri in orizzontale per ruotare", style: TextStyle(color: Colors.white.withOpacity(0.5), fontStyle: FontStyle.italic, fontWeight: FontWeight.bold, letterSpacing: 1.5)))),
|
Positioned(top: 80, left: gameController.isHost ? 30 : null, right: gameController.isHost ? null : 30, child: _BouncingEmoji(emoji: gameController.myReaction!)),
|
||||||
|
if (gameController.opponentReaction != null)
|
||||||
if (gameController.myReaction != null) Positioned(top: 80, left: gameController.isHost ? 30 : null, right: gameController.isHost ? null : 30, child: _BouncingEmoji(emoji: gameController.myReaction!)),
|
Positioned(top: 80, left: !gameController.isHost ? 30 : null, right: !gameController.isHost ? null : 30, child: _BouncingEmoji(emoji: gameController.opponentReaction!)),
|
||||||
if (gameController.opponentReaction != null) Positioned(top: 80, left: !gameController.isHost ? 30 : null, right: !gameController.isHost ? null : 30, child: _BouncingEmoji(emoji: gameController.opponentReaction!)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: true, onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); },
|
canPop: true,
|
||||||
|
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: [
|
// 2. Immagine di Sfondo per tutti i temi che la supportano
|
||||||
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 (bgImage != null)
|
||||||
if (gameController.effectText.isNotEmpty) Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)),
|
Positioned.fill(
|
||||||
Positioned.fill(child: gameContent),
|
child: Image.asset(
|
||||||
if (gameController.isSetupPhase && !_hideJokerMessage) Positioned.fill(child: Container(color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black : 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))))))),
|
bgImage,
|
||||||
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))),
|
fit: BoxFit.cover,
|
||||||
],
|
alignment: Alignment.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// 3. Griglia a righe incrociate per il doodle (Sopra l'immagine di carta)
|
||||||
|
if (themeType == AppThemeType.doodle)
|
||||||
|
Positioned.fill(
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 4. Patina scura (Cyberpunk e Music) per far risaltare il neon
|
||||||
|
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music))
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter, end: Alignment.bottomCenter,
|
||||||
|
colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 5. Effetto "Furia" o Timeout
|
||||||
|
if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase)
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 9. Effetti di Vittoria
|
||||||
|
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))),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -293,48 +508,21 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
||||||
void _handleTap(Offset tapPos, double width, double height, GameController controller, AppThemeType themeType) {
|
void _handleTap(Offset tapPos, double width, double height, GameController controller, AppThemeType themeType) {
|
||||||
final board = controller.board;
|
final board = controller.board;
|
||||||
if (board.isGameOver) return;
|
if (board.isGameOver) return;
|
||||||
|
int cols = board.columns + 1; double spacing = width / cols; double offset = spacing / 2;
|
||||||
BoardPainter dummyPainter = BoardPainter(
|
|
||||||
board: board, theme: AppColors.minimal, themeType: AppThemeType.minimal,
|
|
||||||
isOnline: false, isVsCPU: false, isSetupPhase: false,
|
|
||||||
myPlayer: Player.red, jokerTurn: Player.red,
|
|
||||||
cameraAngle: _cameraAngle,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (controller.isSetupPhase) {
|
if (controller.isSetupPhase) {
|
||||||
var sortedBoxes = List<Box>.from(board.boxes);
|
int bx = ((tapPos.dx - offset) / spacing).floor(); int by = ((tapPos.dy - offset) / spacing).floor();
|
||||||
sortedBoxes.sort((a,b) => dummyPainter.getDepth(b).compareTo(dummyPainter.getDepth(a)));
|
controller.placeJoker(bx, by); return;
|
||||||
|
|
||||||
for (var box in sortedBoxes) {
|
|
||||||
if (box.type == BoxType.invisible) continue;
|
|
||||||
Offset p0 = dummyPainter.projectLogical(box.x.toDouble(), box.y.toDouble(), box.z.toDouble(), Size(width, height));
|
|
||||||
Offset p1 = dummyPainter.projectLogical(box.x + 1.0, box.y.toDouble(), box.z.toDouble(), Size(width, height));
|
|
||||||
Offset p2 = dummyPainter.projectLogical(box.x + 1.0, box.y + 1.0, box.z.toDouble(), Size(width, height));
|
|
||||||
Offset p3 = dummyPainter.projectLogical(box.x.toDouble(), box.y + 1.0, box.z.toDouble(), Size(width, height));
|
|
||||||
|
|
||||||
Path poly = Path()..moveTo(p0.dx, p0.dy)..lineTo(p1.dx, p1.dy)..lineTo(p2.dx, p2.dy)..lineTo(p3.dx, p3.dy)..close();
|
|
||||||
if (poly.contains(tapPos)) {
|
|
||||||
controller.placeJoker(box.x, box.y, bz: box.z);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Line? closestLine;
|
Line? closestLine; double minDistance = double.infinity; double maxTouchDistance = spacing * 0.4;
|
||||||
double minDistance = double.infinity;
|
|
||||||
double maxTouchDistance = 40.0;
|
|
||||||
|
|
||||||
for (var line in board.lines) {
|
for (var line in board.lines) {
|
||||||
if (line.owner != Player.none || !line.isPlayable) continue;
|
if (line.owner != Player.none || !line.isPlayable) continue;
|
||||||
|
Offset screenP1 = Offset(line.p1.x * spacing + offset, line.p1.y * spacing + offset);
|
||||||
Offset screenP1 = dummyPainter.projectLogical(line.p1.x.toDouble(), line.p1.y.toDouble(), line.p1.z.toDouble(), Size(width, height));
|
Offset screenP2 = Offset(line.p2.x * spacing + offset, line.p2.y * spacing + offset);
|
||||||
Offset screenP2 = dummyPainter.projectLogical(line.p2.x.toDouble(), line.p2.y.toDouble(), line.p2.z.toDouble(), Size(width, height));
|
|
||||||
|
|
||||||
double dist = _distanceToSegment(tapPos, screenP1, screenP2);
|
double dist = _distanceToSegment(tapPos, screenP1, screenP2);
|
||||||
if (dist < minDistance && dist < maxTouchDistance) { minDistance = dist; closestLine = line; }
|
if (dist < minDistance && dist < maxTouchDistance) { minDistance = dist; closestLine = line; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closestLine != null) { controller.handleLineTap(closestLine, themeType); }
|
if (closestLine != null) { controller.handleLineTap(closestLine, themeType); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,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;
|
||||||
|
|
@ -378,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;
|
||||||
|
|
||||||
|
|
@ -388,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;
|
||||||
|
|
@ -400,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; }
|
||||||
|
|
@ -423,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>
|
||||||
84
pubspec.lock
84
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,14 +129,22 @@ 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:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
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:
|
||||||
|
|
@ -449,18 +505,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -734,10 +790,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.7"
|
||||||
typed_data:
|
typed_data:
|
||||||
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