migration #13

Closed
brontomark wants to merge 2 commits from migration into main
14 changed files with 443 additions and 1 deletions
Showing only changes of commit 1115d2cb87 - Show all commits

View File

@@ -1,5 +1,8 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
// END: FlutterFire Configuration
id("kotlin-android") id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")

View File

@@ -0,0 +1,86 @@
{
"project_info": {
"project_number": "872447580790",
"project_id": "assistenza-catelli",
"storage_bucket": "assistenza-catelli.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:872447580790:android:193235afcc2920ce5d9d57",
"android_client_info": {
"package_name": "com.catelli.scans2"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBSxpdLDlPnN0xjejlX_5JL19BDeSzKOr8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:872447580790:android:9c6172d77b1d2cae5d9d57",
"android_client_info": {
"package_name": "com.catellisrl.assistenza"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBSxpdLDlPnN0xjejlX_5JL19BDeSzKOr8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:872447580790:android:425d21710d7682005d9d57",
"android_client_info": {
"package_name": "com.catellisrl.catelli_energy_comparator"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBSxpdLDlPnN0xjejlX_5JL19BDeSzKOr8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:872447580790:android:a1d8d57960451f935d9d57",
"android_client_info": {
"package_name": "com.catellisrl.flux"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBSxpdLDlPnN0xjejlX_5JL19BDeSzKOr8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://operations.gradle.org/distributions/gradle-8.14-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@@ -20,6 +20,9 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false id("com.android.application") version "8.11.1" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.15") apply false
// END: FlutterFire Configuration
id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false
} }

1
firebase.json Normal file
View File

@@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"assistenza-catelli","appId":"1:872447580790:android:a1d8d57960451f935d9d57","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"assistenza-catelli","configurations":{"android":"1:872447580790:android:a1d8d57960451f935d9d57","ios":"1:872447580790:ios:a87d56c718aa61e05d9d57","macos":"1:872447580790:ios:a87d56c718aa61e05d9d57","web":"1:872447580790:web:10745e7f9afb447d5d9d57","windows":"1:872447580790:web:3b1623eda6abdac75d9d57"}}}}}}

View File

@@ -5,6 +5,7 @@ import 'package:flux/core/theme/theme.dart';
import 'package:flux/features/customers/blocs/customers_cubit.dart'; import 'package:flux/features/customers/blocs/customers_cubit.dart';
import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/models/customer_model.dart';
import 'package:flux/features/customers/ui/customer_form.dart'; import 'package:flux/features/customers/ui/customer_form.dart';
import 'package:flux/temp/migration_tools.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
class CustomersContent extends StatefulWidget { class CustomersContent extends StatefulWidget {
@@ -84,6 +85,12 @@ class _CustomersContentState extends State<CustomersContent> {
), ),
), ),
//TODO cancella quando import finito
ElevatedButton(
onPressed: () => migrateModelsToSupabase(),
child: const Text('migra clienti'),
),
// LISTA CLIENTI // LISTA CLIENTI
Expanded( Expanded(
child: BlocBuilder<CustomersCubit, CustomersState>( child: BlocBuilder<CustomersCubit, CustomersState>(

88
lib/firebase_options.dart Normal file
View File

@@ -0,0 +1,88 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
return windows;
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyA8vQbyEt81DoAuRVDc_3W_VIKY-9F-XTw',
appId: '1:872447580790:web:10745e7f9afb447d5d9d57',
messagingSenderId: '872447580790',
projectId: 'assistenza-catelli',
authDomain: 'assistenza-catelli.firebaseapp.com',
storageBucket: 'assistenza-catelli.firebasestorage.app',
measurementId: 'G-HTSSNQJ15P',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyBSxpdLDlPnN0xjejlX_5JL19BDeSzKOr8',
appId: '1:872447580790:android:a1d8d57960451f935d9d57',
messagingSenderId: '872447580790',
projectId: 'assistenza-catelli',
storageBucket: 'assistenza-catelli.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyCkjOTW6BlckKIxQdp5TPnHuRfXFoVC3bY',
appId: '1:872447580790:ios:a87d56c718aa61e05d9d57',
messagingSenderId: '872447580790',
projectId: 'assistenza-catelli',
storageBucket: 'assistenza-catelli.firebasestorage.app',
iosBundleId: 'com.catellisrl.flux',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyCkjOTW6BlckKIxQdp5TPnHuRfXFoVC3bY',
appId: '1:872447580790:ios:a87d56c718aa61e05d9d57',
messagingSenderId: '872447580790',
projectId: 'assistenza-catelli',
storageBucket: 'assistenza-catelli.firebasestorage.app',
iosBundleId: 'com.catellisrl.flux',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyA5uJhb8ksqKqdEWbMD5ra6JYXIGoaIdIM',
appId: '1:872447580790:web:3b1623eda6abdac75d9d57',
messagingSenderId: '872447580790',
projectId: 'assistenza-catelli',
authDomain: 'assistenza-catelli.firebaseapp.com',
storageBucket: 'assistenza-catelli.firebasestorage.app',
measurementId: 'G-J8LZTQ9NHB',
);
}

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -7,6 +8,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flux/features/attachments/data/attachments_repository.dart'; import 'package:flux/features/attachments/data/attachments_repository.dart';
import 'package:flux/features/auth/bloc/auth_cubit.dart'; import 'package:flux/features/auth/bloc/auth_cubit.dart';
import 'package:flux/features/operations/data/operations_repository.dart'; import 'package:flux/features/operations/data/operations_repository.dart';
import 'package:flux/firebase_options.dart';
import 'package:flux/l10n/app_localizations.dart'; import 'package:flux/l10n/app_localizations.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -102,6 +104,9 @@ Future<void> setupLocator() async {
getIt.registerSingleton<SessionCubit>( getIt.registerSingleton<SessionCubit>(
SessionCubit(getIt<CoreRepository>(), getIt<SharedPreferences>()), SessionCubit(getIt<CoreRepository>(), getIt<SharedPreferences>()),
); );
//TODO rimuovere dopo gli import
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
} }
class FluxApp extends StatefulWidget { class FluxApp extends StatefulWidget {

View File

@@ -0,0 +1,148 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> migrateCustomersToSupabase() async {
// 1. IL TUO COMPANY ID REALE SU SUPABASE
// Vai nel database Supabase, copia l'UUID della tua azienda e incollalo qui
final String myRealCompanyId = '6c4b2323-2a60-4d33-bf21-c5a8eb6b4a5b';
try {
print("Inizio download modello da Firebase...");
// 2. Scarichiamo TUTTI i clienti da Firebase
final snapshot = await FirebaseFirestore.instance.collection('marca').get();
if (snapshot.docs.isEmpty) {
print("Nessun marca trovato su Firebase!");
return;
}
// Questa lista conterrà i dati formattati pronti per Supabase
List<Map<String, dynamic>> supabaseBrands = [];
// 3. Cicliamo i documenti di Firebase e li trasformiamo
for (var doc in snapshot.docs) {
final data = doc.data();
// Creiamo la riga per Supabase
supabaseBrands.add({
'legacy_id': doc.id, // L'ID vecchio di Firebase
//'company_id': myRealCompanyId, // ECCO IL TUO COMPANY ID!
// Mappa i campi (attento a far combaciare i nomi esatti delle colonne Supabase!)
'name': (data['nome'] as String).trim().toLowerCase(),
'company_id': myRealCompanyId,
// Se avevi una data di creazione su Firebase, convertila, altrimenti ignorala
// e Supabase userà il suo 'default now()'
// 'created_at': (data['createdAt'] as Timestamp?)?.toDate().toIso8601String(),
});
}
print("Sto per inviare ${supabaseBrands.length} brand a Supabase...");
// 4. Invio a Supabase con UPSERT
await Supabase.instance.client
.from('brand')
.upsert(
supabaseBrands,
onConflict:
'legacy_id', // Se il legacy_id c'è già, aggiorna invece di duplicare
);
print("BOOM! Migrazione brand completata con successo! 🚀");
} catch (e) {
print("Porca miseria, errore durante la migrazione: $e");
}
}
Future<void> migrateModelsToSupabase() async {
final String myRealCompanyId = '6c4b2323-2a60-4d33-bf21-c5a8eb6b4a5b';
try {
print("Inizio migrazione Modelli...");
// ==========================================================
// FASE 1: CREAZIONE DEL DIZIONARIO DI TRADUZIONE (LA MAGIA)
// ==========================================================
print("Scarico i Brand da Supabase per tradurre gli ID...");
// Chiediamo a Supabase solo 2 colonne: il nuovo UUID e il vecchio ID di Firebase
final List<dynamic> brandResponse = await Supabase.instance.client
.from('brand')
.select('id, legacy_id');
// Creiamo la mappa: la chiave è il vecchio ID, il valore è il nuovo UUID
Map<String, String> brandTranslationMap = {};
for (var b in brandResponse) {
if (b['legacy_firestore_id'] != null) {
brandTranslationMap[b['legacy_id']] = b['id'];
}
}
print("Dizionario pronto! Trovati ${brandTranslationMap.length} Brand.");
// ==========================================================
// FASE 2: SCARICAMENTO E TRADUZIONE DEI MODELLI
// ==========================================================
final snapshot = await FirebaseFirestore.instance
.collection('modello')
.get(); // Controlla il nome esatto della collection!
if (snapshot.docs.isEmpty) {
print("Nessun modello trovato su Firebase!");
return;
}
List<Map<String, dynamic>> supabaseModels = [];
for (var doc in snapshot.docs) {
final data = doc.data();
// 1. Prendiamo il vecchio ID del brand salvato su Firebase
String? oldFirebaseBrandId = data['idMarca'];
// 2. TRADUZIONE ISTANTANEA! Cerchiamo il nuovo UUID nel nostro dizionario
String? newSupabaseBrandUuid;
if (oldFirebaseBrandId != null) {
newSupabaseBrandUuid = brandTranslationMap[oldFirebaseBrandId];
}
// 3. Controllo di sicurezza: se il brand non esiste su Supabase, saltiamo il record o mettiamo null?
// Se nella tua tabella 'model' il 'brand_id' NON PUÒ essere null, devi per forza avere un match!
if (newSupabaseBrandUuid == null && oldFirebaseBrandId != null) {
print(
"ATTENZIONE: Il modello ${data['nome']} ha un brand_id ($oldFirebaseBrandId) che non esiste su Supabase. Salto o metto null.",
);
continue; // Decommenta questo se vuoi saltare i modelli orfani
}
// Creiamo la riga per Supabase
supabaseModels.add({
'legacy_id': doc.id,
// ECCO LA CHIAVE ESTERNA TRADOTTA!
'brand_id': newSupabaseBrandUuid,
// Mappa gli altri campi
'name': (data['name'] as String).trim().toLowerCase(),
'name_with_brand': (data['nomeConMarca'] as String)
.toLowerCase()
.trim(),
});
}
// ==========================================================
// FASE 3: INVIO A SUPABASE
// ==========================================================
print("Sto per inviare ${supabaseModels.length} modelli a Supabase...");
await Supabase.instance.client
.from('model')
.upsert(supabaseModels, onConflict: 'legacy_id');
print("BOOM! Migrazione modelli completata con successo! 🚀");
} catch (e) {
print("Errore durante la migrazione dei modelli: $e");
}
}

View File

@@ -6,16 +6,22 @@ import FlutterMacOS
import Foundation import Foundation
import app_links import app_links
import cloud_firestore
import file_picker import file_picker
import file_selector_macos import file_selector_macos
import firebase_auth
import firebase_core
import pdfx import pdfx
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
PdfxPlugin.register(with: registry.registrar(forPlugin: "PdfxPlugin")) PdfxPlugin.register(with: registry.registrar(forPlugin: "PdfxPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: bda3b7b55958bfd867addc40d067b4b11f7b8846d57671f5b5a6e7f9a56fe3ad
url: "https://pub.dev"
source: hosted
version: "1.3.69"
app_links: app_links:
dependency: transitive dependency: transitive
description: description:
@@ -105,6 +113,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
sha256: "3ac242332166ae5037bd87bc343744bb96d88d7b13f791492b00958ce5cc6c63"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "1bd08b736e1015e8bf5448f5ef67b2087a2380c2c1c7972f8403c1c7b41f5359"
url: "https://pub.dev"
source: hosted
version: "7.2.0"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: "18617275ffa2331d3ea058c515ef218bcce2ae13a14bee922563ca6ae2507c26"
url: "https://pub.dev"
source: hosted
version: "5.3.0"
code_assets: code_assets:
dependency: transitive dependency: transitive
description: description:
@@ -241,6 +273,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+5" version: "0.9.3+5"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: b12cb1e2e87797d27e0041100b73ebf890dbafcff2e7e991d4593f5e8e309808
url: "https://pub.dev"
source: hosted
version: "6.4.0"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: c71517b3c78480be42789b05316a7692d69296c17848bd6a9e798300abae1ec7
url: "https://pub.dev"
source: hosted
version: "8.1.9"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: "52b0224eb46b09f387e99710707be2d3f48da67c74fe14202e4b942cbe8ce9fd"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: d5a94b884dcb1e6d3430298e94bfe002238094cdfd5e29202d536ee2120f9158
url: "https://pub.dev"
source: hosted
version: "4.7.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: dc5096257cd67292d34d78ceeb90836f02a4be921b5f3934311a02bb2376118c
url: "https://pub.dev"
source: hosted
version: "3.6.0"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:

View File

@@ -31,6 +31,9 @@ dependencies:
uuid: ^4.5.3 uuid: ^4.5.3
pdf: ^3.12.0 pdf: ^3.12.0
universal_io: ^2.3.1 universal_io: ^2.3.1
firebase_core: ^4.7.0
firebase_auth: ^6.4.0
cloud_firestore: ^6.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -7,7 +7,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_auth/firebase_auth_plugin_c_api.h>
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <pdfx/pdfx_plugin.h> #include <pdfx/pdfx_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@@ -15,8 +18,14 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar( AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
CloudFirestorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
FirebaseAuthPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
PdfxPluginRegisterWithRegistrar( PdfxPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PdfxPlugin")); registry->GetRegistrarForPlugin("PdfxPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@@ -4,7 +4,10 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
cloud_firestore
file_selector_windows file_selector_windows
firebase_auth
firebase_core
pdfx pdfx
permission_handler_windows permission_handler_windows
url_launcher_windows url_launcher_windows