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 // Importa lo state con l'Enum e il CoreRepository... part 'session_state.dart'; class SessionCubit extends Cubit { final CoreRepository _repository; final SharedPreferences _prefs; // Iniettato via GetIt final SupabaseClient _supabase = Supabase.instance.client; static const String _lastStoreKey = 'last_selected_store_id'; SessionCubit(this._repository, this._prefs) : super(const SessionState(status: SessionStatus.initial)) { initializeSession(); // Possiamo metterci in ascolto dei cambiamenti di Auth (Login/Logout) _supabase.auth.onAuthStateChange.listen((data) { final AuthChangeEvent event = data.event; if (event == AuthChangeEvent.signedIn) { initializeSession(); } else if (event == AuthChangeEvent.signedOut) { emit(const SessionState(status: SessionStatus.unauthenticated)); } }); } Future initializeSession() async { final user = _supabase.auth.currentUser; if (user == null) { return emit(state.copyWith(status: SessionStatus.unauthenticated)); } try { // 1. CHI È QUESTO UTENTE? (Vediamo se ha un profilo staff, che sia Invitato o Admin) StaffMemberModel? staff = await _repository.getStaffMemberByUserId( user.id, ); CompanyModel? company; if (staff != null) { // --- LA MAGIA DEL SENSORE --- if (staff.hasJoined == false) { // È la primissima volta che entra! Aggiorniamo il DB. await _repository.updateStaffMember(staff.id!, {'has_joined': true}); // Aggiorniamo anche il nostro modello in memoria per questa sessione staff = staff.copyWith(hasJoined: true); } company = await _repository.getCompanyById(staff.companyId); } else { // È l'Admin in onboarding company = await _repository.getCompanyByOwnerId(user.id); } // 1. Controllo Azienda if (staff != null) { // L'utente esiste già nel sistema! Carichiamo l'azienda per cui lavora company = await _repository.getCompanyById(staff.companyId); } else { // L'utente non ha profilo. Probabilmente è l'Admin che ha appena // fatto Sign Up e sta iniziando l'Onboarding company = await _repository.getCompanyByOwnerId(user.id); } if (company == null) { return emit( state.copyWith( status: SessionStatus.onboardingRequired, user: user, onboardingStep: OnboardingStep.company, ), ); } else { emit(state.copyWith(company: company)); } // 2. Controllo Negozi final stores = await _repository.getStoresByCompanyId(company.id!); if (stores.isEmpty) { return emit( state.copyWith( status: SessionStatus.onboardingRequired, user: user, company: company, onboardingStep: OnboardingStep.store, ), ); } else { emit(state.copyWith(currentStore: stores.first)); } // 3. Controllo Staff (Paziente Zero) if (staff == null) { return emit( state.copyWith( status: SessionStatus.onboardingRequired, user: user, company: company, onboardingStep: OnboardingStep.staff, ), ); } // --- TUTTO COMPLETATO: LOGICA DEL NEGOZIO DI DEFAULT --- // Leggiamo l'ultimo negozio dalle SharedPreferences final lastStoreId = _prefs.getString(_lastStoreKey); // Cerchiamo quel negozio nella lista. Se non c'è (magari è stato eliminato), prendiamo il primo. final activeStore = stores.firstWhereOrNull((s) => s.id == lastStoreId) ?? stores.first; // Se non avevamo il lastStoreId salvato, salviamolo ora if (lastStoreId != activeStore.id && activeStore.id != null) { await _prefs.setString(_lastStoreKey, activeStore.id!); } setIsSingleUserMode(_prefs.getBool('isSingleUserMode') ?? false); // 4. BENVENUTO A BORDO emit( state.copyWith( status: SessionStatus.authenticated, user: user, company: company, currentStore: activeStore, currentStaffMember: staff, 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( state.copyWith( status: SessionStatus .unauthenticated, // O un nuovo stato SessionStatus.error ), ); } } Future _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(); // 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)); } // --- FUNZIONE EXTRA: CAMBIO NEGOZIO DALLA DASHBOARD --- Future changeStore(StoreModel newStore) async { if (newStore.id != null) { await _prefs.setString(_lastStoreKey, newStore.id!); emit(state.copyWith(currentStore: newStore)); } } // --- LOGOUT --- Future signOut() async { await _supabase.auth.signOut(); // Non serve emettere stato qui, ci pensa il listener nel costruttore! } void setIsMobileDevice(bool isMobile) { emit(state.copyWith(isMobileDevice: isMobile)); } void setIsSingleUserMode(bool isSingleUser) { emit(state.copyWith(isSingleUserMode: isSingleUser)); } }