fcm
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deno.enable": true
|
||||
}
|
||||
@@ -7,6 +7,10 @@ analyzer:
|
||||
- "lib/l10n/*.dart"
|
||||
- "**/*.g.dart" # Già che ci siamo escludiamo tutti i file generati (tipo quelli di JsonSerializable)
|
||||
- "**/*.freezed.dart"
|
||||
- "build/**"
|
||||
- "ios/**"
|
||||
- "macos/**"
|
||||
- ".dart_tool/**"
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services")
|
||||
// END: FlutterFire Configuration
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
|
||||
29
android/app/google-services.json
Normal file
29
android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "249756116297",
|
||||
"project_id": "flux-87e49",
|
||||
"storage_bucket": "flux-87e49.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:249756116297:android:a2c3d37323752069cf2698",
|
||||
"android_client_info": {
|
||||
"package_name": "com.catellisrl.flux"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyA6-uX6504B3yofeo7YQwfQaS0cCDoZnvY"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -20,6 +20,9 @@ pluginManagement {
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
1
firebase.json
Normal file
1
firebase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"flux-87e49","appId":"1:249756116297:android:a2c3d37323752069cf2698","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"flux-87e49","appId":"1:249756116297:ios:fe9dadca7150da16cf2698","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"flux-87e49","appId":"1:249756116297:ios:fe9dadca7150da16cf2698","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"flux-87e49","configurations":{"android":"1:249756116297:android:a2c3d37323752069cf2698","ios":"1:249756116297:ios:fe9dadca7150da16cf2698","macos":"1:249756116297:ios:fe9dadca7150da16cf2698","web":"1:249756116297:web:7c652e51004414b7cf2698","windows":"1:249756116297:web:b094277c2fedb425cf2698"}}}}}}
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
101B9A4BF8F30D998DDC11D4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D4B70082D3146D8C2B7AFA02 /* GoogleService-Info.plist */; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
@@ -67,6 +68,7 @@
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
AB44F93458B7D70EE383A3A9 /* 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>"; };
|
||||
BDDDA09E437D9C0E7B65B3B1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D4B70082D3146D8C2B7AFA02 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -126,6 +128,7 @@
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
F5D002C3092D87755D552D32 /* Pods */,
|
||||
6A991A28CCED9666CA172E00 /* Frameworks */,
|
||||
D4B70082D3146D8C2B7AFA02 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -267,6 +270,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
101B9A4BF8F30D998DDC11D4 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
30
ios/Runner/GoogleService-Info.plist
Normal file
30
ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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>API_KEY</key>
|
||||
<string>AIzaSyAllwaoNyqHsZtqfMMo9DxVS6-q7yBwWow</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>249756116297</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.catellisrl.flux</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>flux-87e49</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>flux-87e49.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:249756116297:ios:fe9dadca7150da16cf2698</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,9 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/data/core_repository.dart';
|
||||
import 'package:flux/features/company/models/company_model.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:collection/collection.dart'; // Per firstWhereOrNull
|
||||
@@ -134,6 +139,11 @@ class SessionCubit extends Cubit<SessionState> {
|
||||
onboardingStep: OnboardingStep.none, // Svuotiamo l'onboarding
|
||||
),
|
||||
);
|
||||
// --- REGISTRAZIONE DISPOSITIVO PER NOTIFICHE PUSH ---
|
||||
// Lo chiamiamo SENZA 'await' in modo che il caricamento dell'app non si blocchi.
|
||||
// L'utente entrerà subito nell'app e poi vedrà comparire il popup di sistema
|
||||
// per accettare i permessi delle notifiche.
|
||||
_registerFcmToken(companyId: company.id!, staffId: staff.id!);
|
||||
} catch (e) {
|
||||
// Se esplode il database, non lasciamo l'app freezata in 'initial'
|
||||
emit(
|
||||
@@ -145,6 +155,68 @@ class SessionCubit extends Cubit<SessionState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _registerFcmToken({
|
||||
required String companyId,
|
||||
required String staffId,
|
||||
}) async {
|
||||
// Scudo anti-crash per lo sviluppo su Linux / Windows
|
||||
if (!kIsWeb &&
|
||||
!Platform.isAndroid &&
|
||||
!Platform.isIOS &&
|
||||
!Platform.isMacOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
|
||||
// 1. Richiesta permessi di notifica
|
||||
final settings = await messaging.requestPermission(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
|
||||
// 2. Cattura del token dal device
|
||||
final String? fcmToken = await messaging.getToken();
|
||||
|
||||
if (fcmToken != null) {
|
||||
final supabase = GetIt.I.get<SupabaseClient>();
|
||||
|
||||
// Determiniamo la piattaforma in modo sicuro per Linux
|
||||
String osPlatform = 'web';
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isAndroid) osPlatform = 'android';
|
||||
if (Platform.isIOS) osPlatform = 'ios';
|
||||
if (Platform.isMacOS) osPlatform = 'macos';
|
||||
}
|
||||
|
||||
// 3. UPSERT su Supabase condizionato dal vincolo 'fcm_token'
|
||||
await supabase.from('staff_devices').upsert(
|
||||
{
|
||||
'company_id': companyId,
|
||||
'staff_id': staffId,
|
||||
'fcm_token': fcmToken,
|
||||
'os_platform': osPlatform,
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
},
|
||||
onConflict:
|
||||
'fcm_token', // Se il token esiste già, aggiorna questa riga!
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
'Dispositivo registrato con successo su FLUX Cloud. Platform: $osPlatform',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debugPrint('Permesso push negato dall\'utente.');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Errore durante la registrazione del dispositivo: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void updateCurrentCompany(CompanyModel newCompany) {
|
||||
emit(state.copyWith(company: newCompany));
|
||||
}
|
||||
|
||||
@@ -545,16 +545,21 @@ class AppRouter {
|
||||
realTaskId = pathId;
|
||||
}
|
||||
|
||||
final allStaffList = context.read<StaffCubit>().state.allStaff;
|
||||
List<StaffMemberModel>? preloadedStaff;
|
||||
try {
|
||||
preloadedStaff = context.read<StaffCubit>().state.allStaff;
|
||||
} catch (_) {
|
||||
preloadedStaff = null; // Fallback se la rotta è isolata
|
||||
}
|
||||
|
||||
// Creiamo il BLoC "al volo" solo per questa schermata
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<TaskFormCubit>(
|
||||
create: (context) => TaskFormCubit(
|
||||
globalStaff: allStaffList,
|
||||
existingTask: task,
|
||||
initialTaskId: realTaskId,
|
||||
allStaff: preloadedStaff,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -232,7 +232,7 @@ class HomeScreen extends StatelessWidget {
|
||||
QuickActionButton(
|
||||
icon: Icons.task_alt,
|
||||
label: context.l10n.commonTask,
|
||||
color: Colors.teal,
|
||||
color: Colors.orange,
|
||||
onTap: () {
|
||||
context.pushNamed(Routes.taskForm, pathParameters: {'id': 'new'});
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||
@@ -10,8 +11,12 @@ class StaffRepository {
|
||||
// --- ANAGRAFICA PURA ---
|
||||
|
||||
// Prende tutto lo staff della Company (per l'Hub Anagrafiche)
|
||||
Future<List<StaffMemberModel>> getStaffMembers(String companyId) async {
|
||||
final response = await _supabase
|
||||
Future<List<StaffMemberModel>> getStaffMembers(
|
||||
String companyId, {
|
||||
String? storeId,
|
||||
}) async {
|
||||
try {
|
||||
var filterBuilder = _supabase
|
||||
.from(Tables.staffMembers)
|
||||
.select('''
|
||||
*,
|
||||
@@ -19,10 +24,25 @@ class StaffRepository {
|
||||
${Tables.stores}(*)
|
||||
)
|
||||
''')
|
||||
.eq('company_id', companyId)
|
||||
.order('name', ascending: true);
|
||||
.eq('company_id', companyId);
|
||||
|
||||
return (response as List).map((s) => StaffMemberModel.fromMap(s)).toList();
|
||||
if (storeId != null) {
|
||||
filterBuilder = filterBuilder.or(
|
||||
'store_id.eq.$storeId,store_id.is.null',
|
||||
);
|
||||
}
|
||||
|
||||
var transformBuilder = filterBuilder.order('name', ascending: true);
|
||||
|
||||
final response = await transformBuilder;
|
||||
|
||||
return (response as List)
|
||||
.map((s) => StaffMemberModel.fromMap(s))
|
||||
.toList();
|
||||
} on Exception catch (e) {
|
||||
debugPrint('Errore nel recupero della lista di staff: $e');
|
||||
throw Exception('Errore nel recupero della lista di staff: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<StaffMemberModel?> getStaffMemberById(String staffId) async {
|
||||
|
||||
@@ -18,6 +18,7 @@ class _ReminderSettingsScreenState extends State<ReminderSettingsScreen> {
|
||||
}
|
||||
|
||||
void _showAddReminderBottomSheet(BuildContext context) {
|
||||
final cubit = context.read<ReminderDefaultsCubit>();
|
||||
// Valori preselezionati
|
||||
int selectedMinutes = 15;
|
||||
String selectedChannel = 'push';
|
||||
@@ -73,8 +74,9 @@ class _ReminderSettingsScreenState extends State<ReminderSettingsScreen> {
|
||||
),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null)
|
||||
if (val != null) {
|
||||
setModalState(() => selectedMinutes = val);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -113,8 +115,9 @@ class _ReminderSettingsScreenState extends State<ReminderSettingsScreen> {
|
||||
),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null)
|
||||
if (val != null) {
|
||||
setModalState(() => selectedChannel = val);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
@@ -125,7 +128,7 @@ class _ReminderSettingsScreenState extends State<ReminderSettingsScreen> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<ReminderDefaultsCubit>().addReminder(
|
||||
cubit.addReminder(
|
||||
minutesBefore: selectedMinutes,
|
||||
channel: selectedChannel,
|
||||
);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/master_data/staff/data/staff_repository.dart';
|
||||
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/settings/data/settings_repository.dart';
|
||||
import 'package:flux/features/tasks/data/task_repository.dart';
|
||||
import 'package:flux/features/tasks/models/task_model.dart';
|
||||
import 'package:flux/features/tasks/models/task_reminder_config.dart';
|
||||
import 'package:flux/features/tasks/models/task_status.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
part 'task_form_state.dart';
|
||||
|
||||
@@ -14,12 +15,16 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
||||
final TasksRepository _repository = GetIt.I.get<TasksRepository>();
|
||||
final SettingsRepository _settingsRepository = GetIt.I
|
||||
.get<SettingsRepository>();
|
||||
final _staffRepository = GetIt.I.get<StaffRepository>();
|
||||
final SessionCubit _sessionCubit = GetIt.I.get<SessionCubit>();
|
||||
final List<StaffMemberModel>? _preloadedStaff;
|
||||
|
||||
TaskFormCubit({
|
||||
String? initialTaskId, // <-- RIPRISTINATO PER DEEP LINK
|
||||
TaskModel? existingTask,
|
||||
}) : super(const TaskFormState()) {
|
||||
List<StaffMemberModel>? allStaff,
|
||||
}) : _preloadedStaff = allStaff,
|
||||
super(const TaskFormState()) {
|
||||
// Avviamo l'inizializzazione centralizzata (gestisce sia mem, sia deep link, sia nuovo)
|
||||
initForm(initialTaskId: initialTaskId, existingTask: existingTask);
|
||||
}
|
||||
@@ -77,19 +82,35 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
||||
|
||||
// --- LOGICA GESTIONE STAFF (GLOBAL STAFF / STORE STAFF) ---
|
||||
Future<void> _loadAndGroupStaff() async {
|
||||
// Se isGlobal è true, passiamo null come storeId al repo per tirare giù tutta l'azienda
|
||||
final List<StaffMemberModel> staffList = await _repository
|
||||
.fetchAvailableStaff(
|
||||
companyId: _companyId,
|
||||
storeId: state.isGlobal ? null : _currentStoreId,
|
||||
);
|
||||
final List<StaffMemberModel> staffList;
|
||||
|
||||
// SE C'È LO STAFF PASCIUTO DALL'APP USA QUELLO, ALTRIMENTI CHIAMA IL REPO
|
||||
if (_preloadedStaff != null && _preloadedStaff.isNotEmpty) {
|
||||
staffList = _preloadedStaff;
|
||||
} else {
|
||||
staffList = await _staffRepository.getStaffMembers(_companyId);
|
||||
}
|
||||
|
||||
// Raggruppamento per nome del negozio (Mappa { "Nome Negozio": [Membri] })
|
||||
final Map<String, List<StaffMemberModel>> grouped = {};
|
||||
|
||||
for (var staff in staffList) {
|
||||
final storeName = staff.storeName ?? 'Senza Sede';
|
||||
if (!state.isGlobal) {
|
||||
final belongsToCurrentStore = staff.assignedStores.any(
|
||||
(store) => store.id == _currentStoreId,
|
||||
);
|
||||
if (!belongsToCurrentStore) continue;
|
||||
}
|
||||
|
||||
if (staff.assignedStores.isEmpty) {
|
||||
grouped.putIfAbsent('Direzione / Senza Sede', () => []).add(staff);
|
||||
} else {
|
||||
for (var store in staff.assignedStores) {
|
||||
if (!state.isGlobal && store.id != _currentStoreId) continue;
|
||||
final storeName = store.name;
|
||||
grouped.putIfAbsent(storeName, () => []).add(staff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit(state.copyWith(groupedAvailableStaff: grouped));
|
||||
}
|
||||
@@ -138,7 +159,7 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
||||
);
|
||||
emit(state.copyWith(reminders: existingConfigs));
|
||||
} catch (e) {
|
||||
print('Errore caricamento reminder: $e');
|
||||
debugPrint('Errore caricamento reminder: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class TasksRepository {
|
||||
)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
debugPrint('Errore fetch personal reminders: $e');
|
||||
throw Exception('Errore fetch personal reminders: $e');
|
||||
}
|
||||
}
|
||||
@@ -98,6 +99,26 @@ class TasksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<TaskModel?> fetchTaskById(String taskId) async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(Tables.tasks)
|
||||
.select('''
|
||||
*,
|
||||
task_assignments:${Tables.taskAssignments} (
|
||||
${Tables.staffMembers} (*)
|
||||
)
|
||||
''')
|
||||
.eq('id', taskId)
|
||||
.single();
|
||||
|
||||
return TaskModel.fromMap(response);
|
||||
} catch (e) {
|
||||
debugPrint('Errore fetch task by id: $e');
|
||||
throw Exception('Errore fetch task by id: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// REALTIME STREAM (La sentinella per la bacheca)
|
||||
// =========================================================================
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/tasks/blocs/task_form_cubit.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class TaskFormScreen extends StatefulWidget {
|
||||
@@ -136,15 +134,7 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
|
||||
)
|
||||
else
|
||||
TextButton.icon(
|
||||
onPressed: state.isFormValid
|
||||
? () => cubit.saveTask(
|
||||
currentUserId: GetIt.I
|
||||
.get<SessionCubit>()
|
||||
.state
|
||||
.currentStaffMember!
|
||||
.id!,
|
||||
)
|
||||
: null,
|
||||
onPressed: state.isFormValid ? () => cubit.saveTask() : null,
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Salva'),
|
||||
style: TextButton.styleFrom(
|
||||
|
||||
89
lib/firebase_options.dart
Normal file
89
lib/firebase_options.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
// 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: 'AIzaSyACOLz2mY8fHd5RWfJmDvN53LCd5_TxI6o',
|
||||
appId: '1:249756116297:web:7c652e51004414b7cf2698',
|
||||
messagingSenderId: '249756116297',
|
||||
projectId: 'flux-87e49',
|
||||
authDomain: 'flux-87e49.firebaseapp.com',
|
||||
storageBucket: 'flux-87e49.firebasestorage.app',
|
||||
measurementId: 'G-6V4VN8GWWZ',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyA6-uX6504B3yofeo7YQwfQaS0cCDoZnvY',
|
||||
appId: '1:249756116297:android:a2c3d37323752069cf2698',
|
||||
messagingSenderId: '249756116297',
|
||||
projectId: 'flux-87e49',
|
||||
storageBucket: 'flux-87e49.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAllwaoNyqHsZtqfMMo9DxVS6-q7yBwWow',
|
||||
appId: '1:249756116297:ios:fe9dadca7150da16cf2698',
|
||||
messagingSenderId: '249756116297',
|
||||
projectId: 'flux-87e49',
|
||||
storageBucket: 'flux-87e49.firebasestorage.app',
|
||||
iosBundleId: 'com.catellisrl.flux',
|
||||
);
|
||||
|
||||
static const FirebaseOptions macos = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAllwaoNyqHsZtqfMMo9DxVS6-q7yBwWow',
|
||||
appId: '1:249756116297:ios:fe9dadca7150da16cf2698',
|
||||
messagingSenderId: '249756116297',
|
||||
projectId: 'flux-87e49',
|
||||
storageBucket: 'flux-87e49.firebasestorage.app',
|
||||
iosBundleId: 'com.catellisrl.flux',
|
||||
);
|
||||
|
||||
static const FirebaseOptions windows = FirebaseOptions(
|
||||
apiKey: 'AIzaSyACOLz2mY8fHd5RWfJmDvN53LCd5_TxI6o',
|
||||
appId: '1:249756116297:web:b094277c2fedb425cf2698',
|
||||
messagingSenderId: '249756116297',
|
||||
projectId: 'flux-87e49',
|
||||
authDomain: 'flux-87e49.firebaseapp.com',
|
||||
storageBucket: 'flux-87e49.firebasestorage.app',
|
||||
measurementId: 'G-8E29KT6RWX',
|
||||
);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -22,6 +23,7 @@ import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
|
||||
import 'package:flux/features/tickets/data/ticket_repository.dart';
|
||||
import 'package:flux/features/tracking/blocs/tracking_cubit.dart';
|
||||
import 'package:flux/features/tracking/data/tracking_repository.dart';
|
||||
import 'package:flux/firebase_options.dart';
|
||||
import 'package:flux/l10n/app_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -53,6 +55,16 @@ void main() async {
|
||||
await setupLocator();
|
||||
// RIMUOVE IL CARATTERE # DAGLI URL WEB!
|
||||
usePathUrlStrategy();
|
||||
// Lo Scudo Ninja: Inizializziamo Firebase SOLO sulle piattaforme supportate
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
|
||||
try {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Errore inizializzazione Firebase: $e');
|
||||
}
|
||||
}
|
||||
runApp(
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
@@ -138,7 +150,7 @@ Future<void> setupLocator() async {
|
||||
() => TicketsShippingRepository(),
|
||||
);
|
||||
getIt.registerLazySingleton<NotesRepository>(() => NotesRepository());
|
||||
getIt.registerLazySingleton<TaskRepository>(() => TaskRepository());
|
||||
getIt.registerLazySingleton<TasksRepository>(() => TasksRepository());
|
||||
getIt.registerLazySingleton<SettingsRepository>(() => SettingsRepository());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import Foundation
|
||||
import app_links
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import firebase_core
|
||||
import firebase_messaging
|
||||
import package_info_plus
|
||||
import pdfx
|
||||
import printing
|
||||
@@ -18,6 +20,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PdfxPlugin.register(with: registry.registrar(forPlugin: "PdfxPlugin"))
|
||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||
|
||||
@@ -28,8 +28,9 @@
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
47B861EC08643C31319819EE /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AD6F0633B38D7C51DB0A44A /* Pods_RunnerTests.framework */; };
|
||||
BC7B14BF366111D5491A16DE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C1847336C291D2358A2A03 /* Pods_Runner.framework */; };
|
||||
654626D9777B906635ABD770 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5541A1B1FDEB6E0619C1BD7E /* GoogleService-Info.plist */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
BC7B14BF366111D5491A16DE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C1847336C291D2358A2A03 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -79,7 +80,9 @@
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
5541A1B1FDEB6E0619C1BD7E /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
64CEA5375FF158D0C9BAC5E3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
7B4E4499661A94FDCB94A882 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -89,7 +92,6 @@
|
||||
EF12DE99A4F7A47E54D9243F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F673AA005B814BAB5FA49C69 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F9C1847336C291D2358A2A03 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -141,6 +143,7 @@
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
BABCD4273B81B107DD58605D /* Pods */,
|
||||
5541A1B1FDEB6E0619C1BD7E /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -235,9 +238,6 @@
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
@@ -255,6 +255,9 @@
|
||||
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
productName = Runner;
|
||||
productReference = 33CC10ED2044A3C60003C045 /* flux.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -263,9 +266,6 @@
|
||||
|
||||
/* Begin PBXProject section */
|
||||
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
@@ -302,6 +302,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 33CC10E42044A3C60003C045;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -327,6 +330,7 @@
|
||||
files = (
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||
654626D9777B906635ABD770 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -806,12 +810,14 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
|
||||
30
macos/Runner/GoogleService-Info.plist
Normal file
30
macos/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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>API_KEY</key>
|
||||
<string>AIzaSyAllwaoNyqHsZtqfMMo9DxVS6-q7yBwWow</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>249756116297</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.catellisrl.flux</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>flux-87e49</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>flux-87e49.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:249756116297:ios:fe9dadca7150da16cf2698</string>
|
||||
</dict>
|
||||
</plist>
|
||||
56
pubspec.lock
56
pubspec.lock
@@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: "8f89e371e2883de35cdc78f648e725fa4da5f3b6c927269f00fa68f1ea92b598"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.71"
|
||||
app_links:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -257,6 +265,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "93a5bde9775fd5adcc937f39dfa04ae0bc89c4d79bea6abc49de3f7b049d9ff6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: "4a120366dbf7d5a8ee9438978530b664b855728fb8dcc3a201017660817e555b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "7c98f10b8c8e5adedc0b810b66a877120696675e2c22d9ca9caca092da0d9e57"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "8d0dc81a31cd030170508dc3e89bfd14355b20a1b991340af5f018e37daab5d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.2.2"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: "37abb0b0535c5497605ee94c12470e1ebbbe47e71a22d0c20bffcc912311f8cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.11"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "54e22b43e2c26a2728a3f68c188de0f9011993ae19ae959a06d476dad935c776"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.7"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -39,6 +39,8 @@ dependencies:
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
package_info_plus: ^9.0.1
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
firebase_core: ^4.9.0
|
||||
firebase_messaging: ^16.2.2
|
||||
|
||||
dependency_overrides:
|
||||
pdfx:
|
||||
|
||||
8
supabase/.gitignore
vendored
Normal file
8
supabase/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
|
||||
# dotenvx
|
||||
.env.keys
|
||||
.env.local
|
||||
.env.*.local
|
||||
424
supabase/config.toml
Normal file
424
supabase/config.toml
Normal file
@@ -0,0 +1,424 @@
|
||||
# For detailed configuration reference documentation, visit:
|
||||
# https://supabase.com/docs/guides/local-development/cli/config
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "flux"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. `public` and `graphql_public` schemas are included by default.
|
||||
schemas = ["public", "graphql_public"]
|
||||
# Extra schemas to add to the search_path of every request.
|
||||
extra_search_path = ["public", "extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
# Controls whether new tables, views, sequences and functions created in the `public` schema by
|
||||
# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`)
|
||||
# without explicit GRANTs. Leave unset today to preserve local behaviour. The implicit default
|
||||
# flips to `false` on 2026-05-30 to match the new cloud default, and the field is removed in
|
||||
# 2026-10-30 once the always-revoked behaviour is permanent. Set to `false` to opt in early.
|
||||
# auto_expose_new_tables = false
|
||||
|
||||
[api.tls]
|
||||
# Enable HTTPS endpoints locally using a self-signed certificate.
|
||||
enabled = false
|
||||
# Paths to self-signed certificate pair.
|
||||
# cert_path = "../certs/my-cert.pem"
|
||||
# key_path = "../certs/my-key.pem"
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
# Maximum amount of time to wait for health check when starting the local database.
|
||||
health_timeout = "2m"
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 17
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
# How many server connections to allow per user/database pair.
|
||||
default_pool_size = 20
|
||||
# Maximum number of client connections allowed.
|
||||
max_client_conn = 100
|
||||
|
||||
# [db.vault]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[db.migrations]
|
||||
# If disabled, migrations will be skipped during a db push or reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of schema files that describe your database.
|
||||
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
||||
schema_paths = []
|
||||
|
||||
[db.seed]
|
||||
# If enabled, seeds the database after migrations during a db reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of seed files to load during db reset.
|
||||
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
|
||||
sql_paths = ["./seed.sql"]
|
||||
|
||||
[db.network_restrictions]
|
||||
# Enable management of network restrictions.
|
||||
enabled = false
|
||||
# List of IPv4 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv4 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs = ["0.0.0.0/0"]
|
||||
# List of IPv6 CIDR blocks allowed to connect to the database.
|
||||
# Defaults to allow all IPv6 connections. Set empty array to block all IPs.
|
||||
allowed_cidrs_v6 = ["::/0"]
|
||||
|
||||
# Uncomment to reject non-secure connections to the database.
|
||||
# [db.ssl_enforcement]
|
||||
# enabled = true
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
||||
# ip_version = "IPv6"
|
||||
# The maximum length in bytes of HTTP request headers. (default: 4096)
|
||||
# max_header_length = 4096
|
||||
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
||||
openai_api_key = "env(OPENAI_API_KEY)"
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
[storage]
|
||||
enabled = true
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
# Uncomment to configure local storage buckets
|
||||
# [storage.buckets.images]
|
||||
# public = false
|
||||
# file_size_limit = "50MiB"
|
||||
# allowed_mime_types = ["image/png", "image/jpeg"]
|
||||
# objects_path = "./images"
|
||||
|
||||
# Allow connections via S3 compatible clients
|
||||
[storage.s3_protocol]
|
||||
enabled = true
|
||||
|
||||
# Image transformation API is available to Supabase Pro plan.
|
||||
# [storage.image_transformation]
|
||||
# enabled = true
|
||||
|
||||
# Store analytical data in S3 for running ETL jobs over Iceberg Catalog
|
||||
# This feature is only available on the hosted platform.
|
||||
[storage.analytics]
|
||||
enabled = false
|
||||
max_namespaces = 5
|
||||
max_tables = 10
|
||||
max_catalogs = 2
|
||||
|
||||
# Analytics Buckets is available to Supabase Pro plan.
|
||||
# [storage.analytics.buckets.my-warehouse]
|
||||
|
||||
# Store vector embeddings in S3 for large and durable datasets
|
||||
[storage.vector]
|
||||
enabled = false
|
||||
max_buckets = 10
|
||||
max_indexes = 5
|
||||
|
||||
# Vector Buckets is available to Supabase Pro plan.
|
||||
# [storage.vector.buckets.documents-openai]
|
||||
|
||||
[auth]
|
||||
enabled = true
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://127.0.0.1:3000"
|
||||
# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended.
|
||||
# external_url = ""
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# JWT issuer URL. If not set, defaults to auth.external_url.
|
||||
# jwt_issuer = ""
|
||||
# Path to JWT signing key. DO NOT commit your signing keys file to git.
|
||||
# signing_keys_path = "./signing_keys.json"
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
# Requires enable_refresh_token_rotation = true.
|
||||
refresh_token_reuse_interval = 10
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
# Allow/disallow anonymous sign-ins to your project.
|
||||
enable_anonymous_sign_ins = false
|
||||
# Allow/disallow testing manual linking of accounts
|
||||
enable_manual_linking = false
|
||||
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
|
||||
minimum_password_length = 6
|
||||
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
|
||||
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
|
||||
password_requirements = ""
|
||||
|
||||
# Configure passkey sign-ins.
|
||||
# [auth.passkey]
|
||||
# enabled = false
|
||||
|
||||
# Configure WebAuthn relying party settings (required when passkey is enabled).
|
||||
# [auth.webauthn]
|
||||
# rp_display_name = "Supabase"
|
||||
# rp_id = "localhost"
|
||||
# rp_origins = ["http://127.0.0.1:3000"]
|
||||
|
||||
[auth.rate_limit]
|
||||
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
||||
email_sent = 2
|
||||
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
||||
sms_sent = 30
|
||||
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
||||
anonymous_users = 30
|
||||
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
|
||||
token_refresh = 150
|
||||
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
|
||||
sign_in_sign_ups = 30
|
||||
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
||||
token_verifications = 30
|
||||
# Number of Web3 logins that can be made in a 5 minute interval per IP address.
|
||||
web3 = 30
|
||||
|
||||
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
|
||||
# [auth.captcha]
|
||||
# enabled = true
|
||||
# provider = "hcaptcha"
|
||||
# secret = ""
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
||||
secure_password_change = false
|
||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||
max_frequency = "1s"
|
||||
# Number of characters used in the email OTP.
|
||||
otp_length = 6
|
||||
# Number of seconds before the email OTP expires (defaults to 1 hour).
|
||||
otp_expiry = 3600
|
||||
|
||||
# Use a production-ready SMTP server
|
||||
# [auth.email.smtp]
|
||||
# enabled = true
|
||||
# host = "smtp.sendgrid.net"
|
||||
# port = 587
|
||||
# user = "apikey"
|
||||
# pass = "env(SENDGRID_API_KEY)"
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
# Uncomment to customize email template
|
||||
# [auth.email.template.invite]
|
||||
# subject = "You have been invited"
|
||||
# content_path = "./supabase/templates/invite.html"
|
||||
|
||||
# Uncomment to customize notification email template
|
||||
# [auth.email.notification.password_changed]
|
||||
# enabled = true
|
||||
# subject = "Your password has been changed"
|
||||
# content_path = "./templates/password_changed_notification.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = false
|
||||
# If enabled, users need to confirm their phone number before signing in.
|
||||
enable_confirmations = false
|
||||
# Template for sending OTP to users
|
||||
template = "Your code is {{ .Code }}"
|
||||
# Controls the minimum amount of time that must pass before sending another sms otp.
|
||||
max_frequency = "5s"
|
||||
|
||||
# Use pre-defined map of phone number to OTP for testing.
|
||||
# [auth.sms.test_otp]
|
||||
# 4152127777 = "123456"
|
||||
|
||||
# Configure logged in session timeouts.
|
||||
# [auth.sessions]
|
||||
# Force log out after the specified duration.
|
||||
# timebox = "24h"
|
||||
# Force log out if the user has been inactive longer than the specified duration.
|
||||
# inactivity_timeout = "8h"
|
||||
|
||||
# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object.
|
||||
# [auth.hook.before_user_created]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://postgres/auth/before-user-created-hook"
|
||||
|
||||
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
||||
# [auth.hook.custom_access_token]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://<database>/<schema>/<hook_name>"
|
||||
|
||||
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
||||
[auth.sms.twilio]
|
||||
enabled = false
|
||||
account_sid = ""
|
||||
message_service_sid = ""
|
||||
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
||||
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
||||
|
||||
# Multi-factor-authentication is available to Supabase Pro plan.
|
||||
[auth.mfa]
|
||||
# Control how many MFA factors can be enrolled at once per user.
|
||||
max_enrolled_factors = 10
|
||||
|
||||
# Control MFA via App Authenticator (TOTP)
|
||||
[auth.mfa.totp]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
|
||||
# Configure MFA via Phone Messaging
|
||||
[auth.mfa.phone]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
otp_length = 6
|
||||
template = "Your code is {{ .Code }}"
|
||||
max_frequency = "5s"
|
||||
|
||||
# Configure MFA via WebAuthn
|
||||
# [auth.mfa.web_authn]
|
||||
# enroll_enabled = true
|
||||
# verify_enabled = true
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
|
||||
# `twitter`, `x`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
|
||||
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
|
||||
# Overrides the default auth callback URL derived from auth.external_url.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
||||
skip_nonce_check = false
|
||||
# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address.
|
||||
email_optional = false
|
||||
|
||||
# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard.
|
||||
# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting.
|
||||
[auth.web3.solana]
|
||||
enabled = false
|
||||
|
||||
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.firebase]
|
||||
enabled = false
|
||||
# project_id = "my-firebase-project"
|
||||
|
||||
# Use Auth0 as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.auth0]
|
||||
enabled = false
|
||||
# tenant = "my-auth0-tenant"
|
||||
# tenant_region = "us"
|
||||
|
||||
# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.aws_cognito]
|
||||
enabled = false
|
||||
# user_pool_id = "my-user-pool-id"
|
||||
# user_pool_region = "us-east-1"
|
||||
|
||||
# Use Clerk as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.clerk]
|
||||
enabled = false
|
||||
# Obtain from https://clerk.com/setup/supabase
|
||||
# domain = "example.clerk.accounts.dev"
|
||||
|
||||
# OAuth server configuration
|
||||
[auth.oauth_server]
|
||||
# Enable OAuth server functionality
|
||||
enabled = false
|
||||
# Path for OAuth consent flow UI
|
||||
authorization_url_path = "/oauth/consent"
|
||||
# Allow dynamic client registration
|
||||
allow_dynamic_registration = false
|
||||
|
||||
[edge_runtime]
|
||||
enabled = true
|
||||
# Supported request policies: `oneshot`, `per_worker`.
|
||||
# `per_worker` (default) — enables hot reload during local development.
|
||||
# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks).
|
||||
policy = "per_worker"
|
||||
# Port to attach the Chrome inspector for debugging edge functions.
|
||||
inspector_port = 8083
|
||||
# The Deno major version to use.
|
||||
deno_version = 2
|
||||
|
||||
# [edge_runtime.secrets]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[analytics]
|
||||
enabled = true
|
||||
port = 54327
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
# Experimental features may be deprecated any time
|
||||
[experimental]
|
||||
# Configures Postgres storage engine to use OrioleDB (S3)
|
||||
orioledb_version = ""
|
||||
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
|
||||
s3_host = "env(S3_HOST)"
|
||||
# Configures S3 bucket region, eg. us-east-1
|
||||
s3_region = "env(S3_REGION)"
|
||||
# Configures AWS_ACCESS_KEY_ID for S3 bucket
|
||||
s3_access_key = "env(S3_ACCESS_KEY)"
|
||||
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
||||
s3_secret_key = "env(S3_SECRET_KEY)"
|
||||
|
||||
# [experimental.pgdelta]
|
||||
# When enabled, pg-delta becomes the active engine for supported schema flows.
|
||||
# enabled = false
|
||||
# Directory under `supabase/` where declarative files are written.
|
||||
# declarative_schema_path = "./database"
|
||||
# JSON string passed through to pg-delta SQL formatting.
|
||||
# format_options = "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}"
|
||||
|
||||
[functions.send-reminders]
|
||||
enabled = true
|
||||
verify_jwt = false
|
||||
import_map = "./functions/send-reminders/deno.json"
|
||||
# Uncomment to specify a custom file path to the entrypoint.
|
||||
# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
|
||||
entrypoint = "./functions/send-reminders/index.ts"
|
||||
# Specifies static files to be bundled with the function. Supports glob patterns.
|
||||
# For example, if you want to serve static HTML pages in your function:
|
||||
# static_files = [ "./functions/send-reminders/*.html" ]
|
||||
3
supabase/functions/send-reminders/.npmrc
Normal file
3
supabase/functions/send-reminders/.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
# Configuration for private npm package dependencies
|
||||
# For more information on using private registries with Edge Functions, see:
|
||||
# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries
|
||||
6
supabase/functions/send-reminders/deno.json
Normal file
6
supabase/functions/send-reminders/deno.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"imports": {
|
||||
"@supabase/functions-js": "jsr:@supabase/functions-js@^2",
|
||||
"@supabase/server": "npm:@supabase/server@^1"
|
||||
}
|
||||
}
|
||||
116
supabase/functions/send-reminders/index.ts
Normal file
116
supabase/functions/send-reminders/index.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
|
||||
import { JWT } from "https://esm.sh/google-auth-library@8.9.0"
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apiKey, content-type',
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// Gestione CORS per sicurezza
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Inizializziamo il client Supabase con i poteri di Service Role (per scavalcare le RLS)
|
||||
const supabaseClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
||||
)
|
||||
|
||||
// 2. Recuperiamo la chiave di Firebase dai Secrets
|
||||
const firebaseSecret = Deno.env.get('FIREBASE_SERVICE_ACCOUNT')
|
||||
if (!firebaseSecret) throw new Error('Missing FIREBASE_SERVICE_ACCOUNT secret')
|
||||
const credentials = JSON.parse(firebaseSecret)
|
||||
|
||||
// 3. Prepariamo l'autenticazione OAuth2 per Firebase HTTP v1
|
||||
const jwtClient = new JWT({
|
||||
email: credentials.client_email,
|
||||
key: credentials.private_key,
|
||||
scopes: ['https://www.googleapis.com/auth/firebase.messaging'],
|
||||
})
|
||||
const tokens = await jwtClient.getAccessToken()
|
||||
const fcmAccessToken = tokens.token
|
||||
|
||||
// 4. Selezioniamo i reminder da inviare (trigger_at passato e non ancora inviati)
|
||||
const { data: reminders, error: fetchError } = await supabaseClient
|
||||
.from('task_reminders')
|
||||
.select('id, task_id, staff_id, channel, tasks(title, description)')
|
||||
.eq('channel', 'push')
|
||||
.eq('is_sent', false)
|
||||
.lte('trigger_at', new Date().toISOString())
|
||||
|
||||
if (fetchError) throw fetchError
|
||||
|
||||
if (!reminders || reminders.length === 0) {
|
||||
return new Response(JSON.stringify({ message: 'Nessun promemoria push da inviare.' }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
|
||||
// 5. Ciclo sui promemoria per raccogliere i token ed inviare
|
||||
for (const reminder of reminders) {
|
||||
const taskTitle = reminder.tasks?.title || 'Nuovo Task!';
|
||||
const taskBody = reminder.tasks?.description || 'Hai un task da completare.';
|
||||
|
||||
// Preleviamo tutti i dispositivi registrati per questo specifico membro dello staff
|
||||
const { data: devices } = await supabaseClient
|
||||
.from('staff_devices')
|
||||
.select('fcm_token')
|
||||
.eq('staff_id', reminder.staff_id)
|
||||
|
||||
if (devices && devices.length > 0) {
|
||||
// Spediamo la notifica a OGNI dispositivo associato all'utente
|
||||
for (const device of devices) {
|
||||
try {
|
||||
await fetch(
|
||||
`https://fcm.googleapis.com/v1/projects/${credentials.project_id}/messages:send`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${fcmAccessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: {
|
||||
token: device.fcm_token,
|
||||
notification: {
|
||||
title: taskTitle,
|
||||
body: taskBody,
|
||||
},
|
||||
data: {
|
||||
click_action: 'FLUTTER_NOTIFICATION_CLICK',
|
||||
taskId: reminder.task_id, // Fondamentale per far scattare il Deep Link all'apertura!
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
} catch (pushErr) {
|
||||
console.error(`Errore invio push al token ${device.fcm_token}:`, pushErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Segnamo il reminder come inviato per non riprocessarlo al prossimo giro
|
||||
await supabaseClient
|
||||
.from('task_reminders')
|
||||
.update({ is_sent: true, updated_at: new Date().toISOString() })
|
||||
.eq('id', reminder.id)
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true, processed: reminders.length }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 500,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <app_links/app_links_plugin_c_api.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <pdfx/pdfx_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
@@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
PdfxPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PdfxPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
app_links
|
||||
file_selector_windows
|
||||
firebase_core
|
||||
pdfx
|
||||
permission_handler_windows
|
||||
printing
|
||||
|
||||
Reference in New Issue
Block a user