390 lines
12 KiB
Dart
390 lines
12 KiB
Dart
|
|
import 'dart:async';
|
||
|
|
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:get_it/get_it.dart';
|
||
|
|
import 'package:image_picker/image_picker.dart';
|
||
|
|
|
||
|
|
part 'operation_files_events.dart';
|
||
|
|
part 'operation_files_state.dart';
|
||
|
|
|
||
|
|
class OperationFilesBloc
|
||
|
|
extends Bloc<OperationFilesEvent, OperationFilesState> {
|
||
|
|
final _repository = GetIt.I.get<OperationsRepository>();
|
||
|
|
final String? operationId;
|
||
|
|
|
||
|
|
OperationFilesBloc({this.operationId})
|
||
|
|
: super(
|
||
|
|
OperationFilesState(
|
||
|
|
status: OperationFilesStatus.initial,
|
||
|
|
operationId: operationId,
|
||
|
|
),
|
||
|
|
) {
|
||
|
|
on<OperationsavedEvent>(_onOperationsaved);
|
||
|
|
on<LoadOperationFilesEvent>(_onLoadOperationFiles);
|
||
|
|
on<AddOperationFilesEvent>(_onAddOperationFiles);
|
||
|
|
on<UploadOperationFilesEvent>(_onUploadOperationFiles);
|
||
|
|
on<DeleteOperationFilesEvent>(_onDeleteOperationFiles);
|
||
|
|
on<ToggleOperationFileSelectionEvent>(_onToggleOperationFileSelection);
|
||
|
|
on<LinkFilesToCustomerEvent>(_onLinkFilesToCustomer);
|
||
|
|
on<RenameOperationFileEvent>(_onRenameOperationFile);
|
||
|
|
on<DeleteSpecificOperationFileEvent>(_onDeleteSpecificOperationFiles);
|
||
|
|
on<SelectAllOperationFilesEvent>(_onSelectAllOperationFiles);
|
||
|
|
on<ClearOperationFileSelectionEvent>(_onClearOperationFileSelection);
|
||
|
|
|
||
|
|
// Se il BLoC nasce con un ID, accendiamo subito lo stream!
|
||
|
|
if (operationId != null) {
|
||
|
|
add(LoadOperationFilesEvent(operationId: operationId));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onOperationsaved(
|
||
|
|
OperationsavedEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) async {
|
||
|
|
// 1. Aggiorniamo l'ID e mettiamo in loading
|
||
|
|
emit(
|
||
|
|
state.copyWith(
|
||
|
|
operationId: event.operationId,
|
||
|
|
status: OperationFilesStatus.uploading,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
// 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));
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onLoadOperationFiles(
|
||
|
|
LoadOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) async {
|
||
|
|
final currentId = event.operationId ?? state.operationId;
|
||
|
|
|
||
|
|
if (currentId != null) {
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||
|
|
|
||
|
|
await emit.forEach(
|
||
|
|
_repository.getOperationFilesStream(currentId),
|
||
|
|
onData: (List<AttachmentModel> data) => state.copyWith(
|
||
|
|
status: OperationFilesStatus.success,
|
||
|
|
remoteFiles: data,
|
||
|
|
),
|
||
|
|
onError: (error, stackTrace) => state.copyWith(
|
||
|
|
status: OperationFilesStatus.failure,
|
||
|
|
error: error.toString(),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void _onAddOperationFiles(
|
||
|
|
AddOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) async {
|
||
|
|
final currentId = state.operationId;
|
||
|
|
|
||
|
|
// BIVIO 1: PRATICA NUOVA (Nessun ID - salvataggio locale)
|
||
|
|
if (currentId == null) {
|
||
|
|
final companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
|
||
|
|
final newLocalFiles = event.files.map((file) {
|
||
|
|
return AttachmentModel(
|
||
|
|
id: null,
|
||
|
|
companyId: companyId,
|
||
|
|
operationId: '', // Sarà riempito al salvataggio
|
||
|
|
name: file.name.fileNameWithoutExtension(),
|
||
|
|
extension: file.name.fileExtension(),
|
||
|
|
storagePath: '',
|
||
|
|
fileSize: file.size,
|
||
|
|
localBytes: file.bytes,
|
||
|
|
);
|
||
|
|
}).toList();
|
||
|
|
|
||
|
|
emit(
|
||
|
|
state.copyWith(
|
||
|
|
localFiles: [...state.localFiles, ...newLocalFiles],
|
||
|
|
status: OperationFilesStatus.success,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// BIVIO 2: PRATICA ESISTENTE (Abbiamo l'ID - Upload immediato)
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||
|
|
try {
|
||
|
|
final List<Future<void>> uploadTasks = [];
|
||
|
|
for (var file in event.files) {
|
||
|
|
uploadTasks.add(
|
||
|
|
_repository.uploadAndRegisterOperationFile(
|
||
|
|
operationId: currentId,
|
||
|
|
pickedFile: file,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
await Future.wait(uploadTasks);
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.success));
|
||
|
|
} catch (e) {
|
||
|
|
emit(
|
||
|
|
state.copyWith(
|
||
|
|
status: OperationFilesStatus.failure,
|
||
|
|
error: e.toString(),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onUploadOperationFiles(
|
||
|
|
UploadOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) async {
|
||
|
|
if ((event.pickedFiles == null || event.pickedFiles!.isEmpty) &&
|
||
|
|
(event.photos == null || event.photos!.isEmpty)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (state.operationId == null) return;
|
||
|
|
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.uploading));
|
||
|
|
try {
|
||
|
|
final List<Future<void>> uploadTasks = [];
|
||
|
|
|
||
|
|
// 1. Gestione Documenti normali (PlatformFile)
|
||
|
|
if (event.pickedFiles != null) {
|
||
|
|
for (var file in event.pickedFiles!) {
|
||
|
|
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(
|
||
|
|
state.copyWith(
|
||
|
|
status: OperationFilesStatus.failure,
|
||
|
|
error: e.toString(),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onDeleteOperationFiles(
|
||
|
|
DeleteOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> 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<void> _onToggleOperationFileSelection(
|
||
|
|
ToggleOperationFileSelectionEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) {
|
||
|
|
final selectedFiles = List<AttachmentModel>.from(state.selectedFiles);
|
||
|
|
if (selectedFiles.contains(event.file)) {
|
||
|
|
selectedFiles.remove(event.file);
|
||
|
|
} else {
|
||
|
|
selectedFiles.add(event.file);
|
||
|
|
}
|
||
|
|
emit(state.copyWith(selectedFiles: selectedFiles));
|
||
|
|
}
|
||
|
|
|
||
|
|
void _onSelectAllOperationFiles(
|
||
|
|
SelectAllOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) {
|
||
|
|
// Prendiamo TUTTI i file (locali e remoti) e li buttiamo nei selezionati
|
||
|
|
emit(state.copyWith(selectedFiles: state.allFiles));
|
||
|
|
}
|
||
|
|
|
||
|
|
void _onClearOperationFileSelection(
|
||
|
|
ClearOperationFileSelectionEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) {
|
||
|
|
// Svuotiamo brutalmente la lista
|
||
|
|
emit(state.copyWith(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",
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onRenameOperationFile(
|
||
|
|
RenameOperationFileEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) async {
|
||
|
|
// BIVIO 1: File Locale (Bozza)
|
||
|
|
if (event.file.localBytes != null) {
|
||
|
|
final updatedLocalFiles = state.localFiles.map((f) {
|
||
|
|
if (f == event.file) {
|
||
|
|
return f.copyWith(name: event.newName);
|
||
|
|
}
|
||
|
|
return f;
|
||
|
|
}).toList();
|
||
|
|
emit(state.copyWith(localFiles: updatedLocalFiles));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// BIVIO 2: File Remoto (Salvato su DB)
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.loading));
|
||
|
|
try {
|
||
|
|
await _repository.renameAttachment(event.file.id!, event.newName);
|
||
|
|
emit(state.copyWith(status: OperationFilesStatus.success));
|
||
|
|
} catch (e) {
|
||
|
|
emit(
|
||
|
|
state.copyWith(
|
||
|
|
status: OperationFilesStatus.failure,
|
||
|
|
error: "Errore rinomina: $e",
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FutureOr<void> _onDeleteSpecificOperationFiles(
|
||
|
|
DeleteSpecificOperationFileEvent event,
|
||
|
|
Emitter<OperationFilesState> emit,
|
||
|
|
) {
|
||
|
|
if (event.file.localBytes != null) {
|
||
|
|
final updatedLocalFiles = state.localFiles
|
||
|
|
.where((f) => f != event.file)
|
||
|
|
.toList();
|
||
|
|
emit(state.copyWith(localFiles: updatedLocalFiles));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|