243 lines
7.5 KiB
Dart
243 lines
7.5 KiB
Dart
|
|
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<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<UploadMultipleOperationFilesEvent>(_onUploadMultipleOperationFiles);
|
||
|
|
on<DeleteOperationFilesEvent>(_onDeleteOperationFiles);
|
||
|
|
on<ToggleOperationFileSelectionEvent>(_onToggleOperationFileSelection);
|
||
|
|
// 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,
|
||
|
|
) {
|
||
|
|
// 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<void> _onLoadOperationFiles(
|
||
|
|
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(
|
||
|
|
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)
|
||
|
|
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<OperationFileModel> 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<void> _onUploadOperationFiles(
|
||
|
|
UploadOperationFilesEvent event,
|
||
|
|
Emitter<OperationFilesState> 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<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,
|
||
|
|
) 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,
|
||
|
|
) {
|
||
|
|
List<OperationFileModel> selectedFiles = List.from(state.selectedFiles);
|
||
|
|
if (selectedFiles.contains(event.file)) {
|
||
|
|
selectedFiles.remove(event.file);
|
||
|
|
} else {
|
||
|
|
selectedFiles.add(event.file);
|
||
|
|
}
|
||
|
|
emit(state.copyWith(selectedFiles: selectedFiles));
|
||
|
|
}
|
||
|
|
}
|