aaaaaaaaaaaa

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-02 10:22:47 +02:00
parent ac97e47771
commit 1721b2ff89
32 changed files with 454 additions and 1031 deletions

View File

@@ -1,14 +1,13 @@
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/blocs/session/session_cubit.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/attachments/models/attachment_model.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';
import 'package:image_picker/image_picker.dart';
part 'operation_files_events.dart';
part 'operation_files_state.dart';
@@ -29,9 +28,10 @@ class OperationFilesBloc
on<LoadOperationFilesEvent>(_onLoadOperationFiles);
on<AddOperationFilesEvent>(_onAddOperationFiles);
on<UploadOperationFilesEvent>(_onUploadOperationFiles);
on<UploadMultipleOperationFilesEvent>(_onUploadMultipleOperationFiles);
on<DeleteOperationFilesEvent>(_onDeleteOperationFiles);
on<ToggleOperationFileSelectionEvent>(_onToggleOperationFileSelection);
on<LinkFilesToCustomerEvent>(_onLinkFilesToCustomer);
// Se il BLoC nasce con un ID, accendiamo subito lo stream!
if (operationId != null) {
add(LoadOperationFilesEvent(operationId: operationId));
@@ -41,18 +41,53 @@ class OperationFilesBloc
FutureOr<void> _onOperationsaved(
OperationsavedEvent event,
Emitter<OperationFilesState> 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.
) async {
// 1. Aggiorniamo l'ID e mettiamo in loading
emit(
state.copyWith(
operationId: event.operationId,
localFiles: [], // <-- LA MAGIA ANTI-DUPLICATI
status: OperationFilesStatus.uploading,
),
);
// Lanciamo il caricamento
// 2. RECUPERO E UPLOAD DEI FILE "PARCHEGGIATI" (Pratica Nuova)
if (state.localFiles.isNotEmpty) {
try {
final List<Future<void>> uploadTasks = [];
for (var file in state.localFiles) {
// Ricreiamo il PlatformFile dal nostro AttachmentModel
// così il repository lo accetta senza fare storie!
final fakePlatformFile = PlatformFile(
name: '${file.name}.${file.extension}',
size: file.fileSize,
bytes: file.localBytes,
);
uploadTasks.add(
_repository.uploadAndRegisterOperationFile(
operationId: event.operationId, // L'ID APPENA NATO!
pickedFile: fakePlatformFile,
),
);
}
// Lanciamo tutti gli upload in parallelo
await Future.wait(uploadTasks);
} catch (e) {
emit(
state.copyWith(
status: OperationFilesStatus.failure,
error: "Errore upload post-salvataggio: $e",
),
);
return; // Ci fermiamo qui se esplode qualcosa
}
}
// 3. FINE DEI GIOCHI! Svuotiamo i locali, passiamo a success e accendiamo lo Stream
emit(state.copyWith(localFiles: [], status: OperationFilesStatus.success));
add(LoadOperationFilesEvent(operationId: event.operationId));
}
@@ -60,17 +95,14 @@ class OperationFilesBloc
LoadOperationFilesEvent event,
Emitter<OperationFilesState> 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(
_repository.getOperationFilesStream(currentId),
onData: (List<AttachmentModel> data) => state.copyWith(
status: OperationFilesStatus.success,
remoteFiles: data,
),
@@ -87,13 +119,15 @@ class OperationFilesBloc
Emitter<OperationFilesState> emit,
) async {
final currentId = state.operationId;
// BIVIO 1: PRATICA NUOVA (Nessun ID)
// BIVIO 1: PRATICA NUOVA (Nessun ID - salvataggio locale)
if (currentId == null) {
// Mettiamo i file nel "parcheggio" locale dello State
final companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
final newLocalFiles = event.files.map((file) {
return OperationFileModel(
return AttachmentModel(
id: null,
operationId: operationId ?? '',
companyId: companyId,
operationId: '', // Sarà riempito al salvataggio
name: file.name.fileNameWithoutExtension(),
extension: file.name.fileExtension(),
storagePath: '',
@@ -101,29 +135,29 @@ class OperationFilesBloc
localBytes: file.bytes,
);
}).toList();
final List<OperationFileModel> updatedLocalFiles = [
...state.localFiles,
...newLocalFiles,
];
emit(
state.copyWith(
localFiles: updatedLocalFiles,
localFiles: [...state.localFiles, ...newLocalFiles],
status: OperationFilesStatus.success,
),
);
return;
}
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID)
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID - Upload immediato)
emit(state.copyWith(status: OperationFilesStatus.uploading));
try {
// Logica identica a quella che abbiamo fatto per i clienti
final List<Future<void>> uploadTasks = [];
for (var file in event.files) {
await _repository.uploadAndRegisterOperationFile(
operationId: operationId!,
pickedFile: file,
uploadTasks.add(
_repository.uploadAndRegisterOperationFile(
operationId: currentId,
pickedFile: file,
),
);
}
await Future.wait(uploadTasks);
emit(state.copyWith(status: OperationFilesStatus.success));
} catch (e) {
emit(
@@ -139,21 +173,55 @@ class OperationFilesBloc
UploadOperationFilesEvent event,
Emitter<OperationFilesState> emit,
) async {
if (event.pickedFiles == null && event.photos == null) return;
if (event.pickedFiles!.isEmpty && event.photos!.isEmpty) return;
if ((event.pickedFiles == null || event.pickedFiles!.isEmpty) &&
(event.photos == null || event.photos!.isEmpty)) {
return;
}
if (state.operationId == null) 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) {
final List<Future<void>> uploadTasks = [];
// 1. Gestione Documenti normali (PlatformFile)
if (event.pickedFiles != null) {
for (var file in event.pickedFiles!) {
await _repository.uploadAndRegisterOperationFile(
operationId: state.operationId!,
pickedFile: file,
uploadTasks.add(
_repository.uploadAndRegisterOperationFile(
operationId: state.operationId!,
pickedFile: file,
),
);
}
}
// 2. Gestione Foto Fotocamera (XFile)
if (event.photos != null) {
for (var photo in event.photos!) {
// Leggiamo i byte asincronamente
final bytes = await photo.readAsBytes();
final fileSize = await photo.length();
// Lo travestiamo da PlatformFile per passarlo al Repository!
final fakePlatformFile = PlatformFile(
name: photo.name,
size: fileSize,
bytes: bytes,
path: photo.path,
);
uploadTasks.add(
_repository.uploadAndRegisterOperationFile(
operationId: state.operationId!,
pickedFile: fakePlatformFile,
),
);
}
}
// Esecuzione parallela di tutti i documenti e foto
await Future.wait(uploadTasks);
emit(state.copyWith(status: OperationFilesStatus.success));
} catch (e) {
emit(
@@ -165,48 +233,6 @@ class OperationFilesBloc
}
}
FutureOr<void> _onUploadMultipleOperationFiles(
UploadMultipleOperationFilesEvent event,
Emitter<OperationFilesState> 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<Future<void>> 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<void> _onDeleteOperationFiles(
DeleteOperationFilesEvent event,
Emitter<OperationFilesState> emit,
@@ -231,7 +257,7 @@ class OperationFilesBloc
ToggleOperationFileSelectionEvent event,
Emitter<OperationFilesState> emit,
) {
List<OperationFileModel> selectedFiles = List.from(state.selectedFiles);
final selectedFiles = List<AttachmentModel>.from(state.selectedFiles);
if (selectedFiles.contains(event.file)) {
selectedFiles.remove(event.file);
} else {
@@ -239,4 +265,62 @@ class OperationFilesBloc
}
emit(state.copyWith(selectedFiles: selectedFiles));
}
FutureOr<void> _onLinkFilesToCustomer(
LinkFilesToCustomerEvent event,
Emitter<OperationFilesState> emit,
) async {
if (state.selectedFiles.isEmpty) return;
// BIVIO 1: PRATICA NUOVA (Modalità Locale)
if (state.operationId == null) {
// Mappiamo i file locali: se sono tra quelli selezionati, iniettiamo il customerId
final updatedLocalFiles = state.localFiles.map((file) {
if (state.selectedFiles.contains(file)) {
return file.copyWith(customerId: event.customerId);
}
return file;
}).toList();
emit(
state.copyWith(
localFiles: updatedLocalFiles,
selectedFiles: [], // Svuotiamo la selezione dopo averli associati
status: OperationFilesStatus.success, // o un toast di feedback
),
);
return;
}
// BIVIO 2: PRATICA ESISTENTE (Modalità Remota su DB)
emit(state.copyWith(status: OperationFilesStatus.loading));
try {
final List<Future<void>> linkTasks = [];
for (var file in state.selectedFiles) {
linkTasks.add(
_repository.copyFileToCustomer(
file: file,
customerId: event.customerId,
),
);
}
await Future.wait(linkTasks);
// Svuotiamo la selezione.
// NON serve aggiornare la lista a mano, perché il DB si aggiorna
// e lo Stream di Supabase spingerà automaticamente in UI i file aggiornati!
emit(
state.copyWith(status: OperationFilesStatus.success, selectedFiles: []),
);
} catch (e) {
emit(
state.copyWith(
status: OperationFilesStatus.failure,
error: "Errore associazione: $e",
),
);
}
}
}