import 'dart:async'; 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 { // Riportiamo lo stato su initial per far girare lo spinner se stiamo riprovando emit(state.copyWith(status: SessionStatus.initial, errorMessage: null)); // WRAP DELLA LOGICA IN UN BLOCCO PROTETTO DA TIMEOUT (10 Secondi) await Future(() async { StaffMemberModel? staff = await _repository.getStaffMemberByUserId( user.id, ); CompanyModel? company; if (staff != null) { if (staff.hasJoined == false) { await _repository.updateStaffMember(staff.id!, { 'has_joined': true, }); staff = staff.copyWith(hasJoined: true); } company = await _repository.getCompanyById(staff.companyId); } else { 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)); } 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)); } if (staff == null) { return emit( state.copyWith( status: SessionStatus.onboardingRequired, user: user, company: company, onboardingStep: OnboardingStep.staff, ), ); } final lastStoreId = _prefs.getString(_lastStoreKey); final activeStore = stores.firstWhereOrNull((s) => s.id == lastStoreId) ?? stores.first; if (lastStoreId != activeStore.id && activeStore.id != null) { await _prefs.setString(_lastStoreKey, activeStore.id!); } setIsSingleUserMode(_prefs.getBool('isSingleUserMode') ?? false); emit( state.copyWith( status: SessionStatus.authenticated, user: user, company: company, currentStore: activeStore, currentStaffMember: staff, onboardingStep: OnboardingStep.none, ), ); // FCM è fuori dall'await principale, quindi va bene così _registerFcmToken(companyId: company.id!, staffId: staff.id!); }).timeout( const Duration(seconds: 10), // Tempo massimo concesso al server onTimeout: () { throw TimeoutException( 'Il server di FLUX non risponde. Controlla la connessione.', ); }, ); } on TimeoutException catch (e) { // 🎯 BINGO! IL TIMEOUT È SCATTATO debugPrint("Timeout Inizializzazione: ${e.message}"); emit( state.copyWith(status: SessionStatus.error, errorMessage: e.message), ); } catch (e) { // Altri errori generici del DB o di rete debugPrint("Errore Inizializzazione: $e"); emit( state.copyWith( status: SessionStatus.error, errorMessage: "Si è verificato un errore di connessione imprevisto.", ), ); } } 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) { String? fcmToken; if (kIsWeb) { fcmToken = await messaging.getToken( vapidKey: 'BLMUr7crlRghEW6iWtRZ7Y0a74OPAMG9Oh37ewhVP3_5YD9e5RHUeO79sDys6P-7KjOz6I6HiaPqNndmatQlu3g', ); } else { 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)); } void updateCurrentStoreLocally(StoreModel updatedStore) { // Verifichiamo che l'utente stia effettivamente lavorando nel negozio appena modificato if (state.currentStore != null && state.currentStore!.id == updatedStore.id) { // Emettiamo il nuovo stato sovrascrivendo solo il negozio corrente emit(state.copyWith(currentStore: updatedStore)); } } }