import 'dart:async'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/operations/data/services_repository.dart'; import 'package:flux/features/operations/models/service_file_model.dart'; import 'package:flux/features/operations/models/service_model.dart'; import 'package:get_it/get_it.dart'; part 'service_files_events.dart'; part 'service_files_state.dart'; class ServiceFilesBloc extends Bloc { final _repository = GetIt.I.get(); final String? serviceId; ServiceFilesBloc({this.serviceId}) : super( ServiceFilesState( status: ServiceFilesStatus.initial, serviceId: serviceId, ), ) { on(_onServiceSaved); on(_onLoadServiceFiles); on(_onAddServiceFiles); on(_onUploadServiceFiles); on(_onUploadMultipleServiceFiles); on(_onDeleteServiceFiles); on(_onToggleServiceFileSelection); // Se il BLoC nasce con un ID, accendiamo subito lo stream! if (serviceId != null) { add(LoadServiceFilesEvent(serviceId: serviceId)); } } FutureOr _onServiceSaved( ServiceSavedEvent event, Emitter emit, ) { // 1. Aggiorniamo l'ID nello stato // 2. PIALLIAMO i file locali: ormai sono partiti per Supabase! // Così la UI si pulisce all'istante e aspetta quelli remoti. emit( state.copyWith( serviceId: event.serviceId, localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI ), ); // Lanciamo il caricamento add(LoadServiceFilesEvent(serviceId: event.serviceId)); } FutureOr _onLoadServiceFiles( LoadServiceFilesEvent event, Emitter emit, ) async { // Usiamo l'ID dell'evento, e se non c'è usiamo quello dello stato final currentId = event.serviceId ?? state.serviceId; if (currentId != null) { emit(state.copyWith(status: ServiceFilesStatus.loading)); await emit.forEach( _repository.getServiceFilesStream( currentId, ), // <-- Usiamo l'ID corretto! onData: (data) => state.copyWith( status: ServiceFilesStatus.success, remoteFiles: data, ), onError: (error, stackTrace) => state.copyWith( status: ServiceFilesStatus.failure, error: error.toString(), ), ); } } void _onAddServiceFiles( AddServiceFilesEvent event, Emitter emit, ) async { final currentId = state.serviceId; // BIVIO 1: PRATICA NUOVA (Nessun ID) if (currentId == null) { // Mettiamo i file nel "parcheggio" locale dello State final newLocalFiles = event.files.map((file) { return ServiceFileModel( id: null, serviceId: serviceId ?? '', name: file.name.fileNameWithoutExtension(), extension: file.name.fileExtension(), storagePath: '', fileSize: file.size, localBytes: file.bytes, ); }).toList(); final List updatedLocalFiles = [ ...state.localFiles, ...newLocalFiles, ]; emit( state.copyWith( localFiles: updatedLocalFiles, status: ServiceFilesStatus.success, ), ); return; } // BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID) emit(state.copyWith(status: ServiceFilesStatus.uploading)); try { // Logica identica a quella che abbiamo fatto per i clienti for (var file in event.files) { await _repository.uploadAndRegisterServiceFile( serviceId: serviceId!, pickedFile: file, ); } emit(state.copyWith(status: ServiceFilesStatus.success)); } catch (e) { emit( state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()), ); } } FutureOr _onUploadServiceFiles( UploadServiceFilesEvent event, Emitter emit, ) async { if (event.pickedFiles == null && event.photos == null) return; if (event.pickedFiles!.isEmpty && event.photos!.isEmpty) return; // BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID emit(state.copyWith(status: ServiceFilesStatus.uploading)); try { // Logica identica a quella che abbiamo fatto per i clienti if (event.pickedFiles != null && event.pickedFiles!.isNotEmpty) { for (var file in event.pickedFiles!) { await _repository.uploadAndRegisterServiceFile( serviceId: state.serviceId!, pickedFile: file, ); } } emit(state.copyWith(status: ServiceFilesStatus.success)); } catch (e) { emit( state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()), ); } } FutureOr _onUploadMultipleServiceFiles( UploadMultipleServiceFilesEvent event, Emitter emit, ) async { if (event.files.isEmpty) { emit( state.copyWith( status: ServiceFilesStatus.failure, error: "Nessun file selezionato", ), ); return; } emit(state.copyWith(status: ServiceFilesStatus.uploading, error: null)); try { // 2. Creiamo una lista di "Promesse" (Futures) per il repository final List> uploadTasks = []; for (var file in event.files) { // Aggiungiamo il task alla lista, ma NON usiamo await qui dentro! uploadTasks.add( _repository.uploadAndRegisterServiceFile( serviceId: state.serviceId!, pickedFile: file, ), ); } // 3. ESECUZIONE PARALLELA! // Aspettiamo che tutti i file siano caricati contemporaneamente. await Future.wait(uploadTasks); // 4. GRAN FINALE: Tutto caricato, emettiamo il success! emit(state.copyWith(status: ServiceFilesStatus.success)); } catch (e) { // Se anche un solo file fallisce, catturiamo l'errore emit( state.copyWith( status: ServiceFilesStatus.failure, error: "Errore durante l'upload multiplo: $e", ), ); } } FutureOr _onDeleteServiceFiles( DeleteServiceFilesEvent event, Emitter emit, ) async { emit(state.copyWith(status: ServiceFilesStatus.loading)); try { await _repository.deleteServiceFiles(state.selectedFiles); emit( state.copyWith(status: ServiceFilesStatus.success, selectedFiles: []), ); } catch (e) { emit( state.copyWith(status: ServiceFilesStatus.failure, error: e.toString()), ); } } FutureOr _onToggleServiceFileSelection( ToggleServiceFileSelectionEvent event, Emitter emit, ) { List selectedFiles = List.from(state.selectedFiles); if (selectedFiles.contains(event.file)) { selectedFiles.remove(event.file); } else { selectedFiles.add(event.file); } emit(state.copyWith(selectedFiles: selectedFiles)); } }