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/operations_repository.dart'; import 'package:flux/features/operations/models/operation_file_model.dart'; import 'package:flux/features/operations/models/operation_model.dart'; import 'package:get_it/get_it.dart'; part 'operation_files_events.dart'; part 'operation_files_state.dart'; class OperationFilesBloc extends Bloc { final _repository = GetIt.I.get(); final String? operationId; OperationFilesBloc({this.operationId}) : super( OperationFilesState( status: OperationFilesStatus.initial, operationId: operationId, ), ) { on(_onOperationsaved); on(_onLoadOperationFiles); on(_onAddOperationFiles); on(_onUploadOperationFiles); on(_onUploadMultipleOperationFiles); on(_onDeleteOperationFiles); on(_onToggleOperationFileSelection); // Se il BLoC nasce con un ID, accendiamo subito lo stream! if (operationId != null) { add(LoadOperationFilesEvent(operationId: operationId)); } } FutureOr _onOperationsaved( OperationsavedEvent 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( operationId: event.operationId, localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI ), ); // Lanciamo il caricamento add(LoadOperationFilesEvent(operationId: event.operationId)); } FutureOr _onLoadOperationFiles( LoadOperationFilesEvent event, Emitter emit, ) async { // Usiamo l'ID dell'evento, e se non c'è usiamo quello dello stato final currentId = event.operationId ?? state.operationId; if (currentId != null) { emit(state.copyWith(status: OperationFilesStatus.loading)); await emit.forEach( _repository.getOperationFilesStream( currentId, ), // <-- Usiamo l'ID corretto! onData: (data) => state.copyWith( status: OperationFilesStatus.success, remoteFiles: data, ), onError: (error, stackTrace) => state.copyWith( status: OperationFilesStatus.failure, error: error.toString(), ), ); } } void _onAddOperationFiles( AddOperationFilesEvent event, Emitter emit, ) async { final currentId = state.operationId; // BIVIO 1: PRATICA NUOVA (Nessun ID) if (currentId == null) { // Mettiamo i file nel "parcheggio" locale dello State final newLocalFiles = event.files.map((file) { return OperationFileModel( id: null, operationId: operationId ?? '', 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: OperationFilesStatus.success, ), ); return; } // BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID) emit(state.copyWith(status: OperationFilesStatus.uploading)); try { // Logica identica a quella che abbiamo fatto per i clienti for (var file in event.files) { await _repository.uploadAndRegisterOperationFile( operationId: operationId!, pickedFile: file, ); } emit(state.copyWith(status: OperationFilesStatus.success)); } catch (e) { emit( state.copyWith( status: OperationFilesStatus.failure, error: e.toString(), ), ); } } FutureOr _onUploadOperationFiles( UploadOperationFilesEvent 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: OperationFilesStatus.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.uploadAndRegisterOperationFile( operationId: state.operationId!, pickedFile: file, ); } } emit(state.copyWith(status: OperationFilesStatus.success)); } catch (e) { emit( state.copyWith( status: OperationFilesStatus.failure, error: e.toString(), ), ); } } FutureOr _onUploadMultipleOperationFiles( UploadMultipleOperationFilesEvent event, Emitter emit, ) async { if (event.files.isEmpty) { emit( state.copyWith( status: OperationFilesStatus.failure, error: "Nessun file selezionato", ), ); return; } emit(state.copyWith(status: OperationFilesStatus.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.uploadAndRegisterOperationFile( operationId: state.operationId!, 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: OperationFilesStatus.success)); } catch (e) { // Se anche un solo file fallisce, catturiamo l'errore emit( state.copyWith( status: OperationFilesStatus.failure, error: "Errore durante l'upload multiplo: $e", ), ); } } FutureOr _onDeleteOperationFiles( DeleteOperationFilesEvent event, Emitter emit, ) async { emit(state.copyWith(status: OperationFilesStatus.loading)); try { await _repository.deleteOperationFiles(state.selectedFiles); emit( state.copyWith(status: OperationFilesStatus.success, selectedFiles: []), ); } catch (e) { emit( state.copyWith( status: OperationFilesStatus.failure, error: e.toString(), ), ); } } FutureOr _onToggleOperationFileSelection( ToggleOperationFileSelectionEvent 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)); } }