125
lib/features/services/blocs/service_files_bloc.dart
Normal file
125
lib/features/services/blocs/service_files_bloc.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
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/string_extensions.dart';
|
||||
import 'package:flux/features/services/data/services_repository.dart';
|
||||
import 'package:flux/features/services/models/service_file_model.dart';
|
||||
import 'package:flux/features/services/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<ServiceFilesEvent, ServiceFilesState> {
|
||||
final _repository = GetIt.I.get<ServicesRepository>();
|
||||
final String? serviceId;
|
||||
|
||||
ServiceFilesBloc({this.serviceId})
|
||||
: super(
|
||||
ServiceFilesState(
|
||||
status: ServiceFilesStatus.initial,
|
||||
serviceId: serviceId,
|
||||
),
|
||||
) {
|
||||
on<ServiceSavedEvent>(_onServiceSaved);
|
||||
on<LoadServiceFilesEvent>(_onLoadServiceFiles);
|
||||
on<AddServiceFilesEvent>(_onAddServiceFiles);
|
||||
on<UploadServiceFilesEvent>(_onUploadServiceFiles);
|
||||
on<DeleteServiceFilesEvent>(_onDeleteServiceFiles);
|
||||
on<ToggleServiceFileSelectionEvent>(_onToggleServiceFileSelection);
|
||||
}
|
||||
|
||||
FutureOr<void> _onServiceSaved(
|
||||
ServiceSavedEvent event,
|
||||
Emitter<ServiceFilesState> emit,
|
||||
) {
|
||||
emit(state.copyWith(serviceId: event.serviceId));
|
||||
}
|
||||
|
||||
FutureOr<void> _onLoadServiceFiles(
|
||||
LoadServiceFilesEvent event,
|
||||
Emitter<ServiceFilesState> emit,
|
||||
) async {
|
||||
if (serviceId != null) {
|
||||
emit(state.copyWith(status: ServiceFilesStatus.loading));
|
||||
await emit.forEach(
|
||||
_repository.getServiceFilesStream(serviceId!),
|
||||
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<ServiceFilesState> emit,
|
||||
) async {
|
||||
// BIVIO 1: PRATICA NUOVA (Nessun ID)
|
||||
if (serviceId == 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<ServiceFileModel> 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<void> _onUploadServiceFiles(
|
||||
UploadServiceFilesEvent event,
|
||||
Emitter<ServiceFilesState> emit,
|
||||
) {}
|
||||
|
||||
FutureOr<void> _onDeleteServiceFiles(
|
||||
DeleteServiceFilesEvent event,
|
||||
Emitter<ServiceFilesState> emit,
|
||||
) {}
|
||||
|
||||
FutureOr<void> _onToggleServiceFileSelection(
|
||||
ToggleServiceFileSelectionEvent event,
|
||||
Emitter<ServiceFilesState> emit,
|
||||
) {}
|
||||
}
|
||||
49
lib/features/services/blocs/service_files_events.dart
Normal file
49
lib/features/services/blocs/service_files_events.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
part of 'service_files_bloc.dart';
|
||||
|
||||
abstract class ServiceFilesEvent extends Equatable {
|
||||
const ServiceFilesEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ServiceSavedEvent extends ServiceFilesEvent {
|
||||
final String serviceId;
|
||||
const ServiceSavedEvent(this.serviceId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [serviceId];
|
||||
}
|
||||
|
||||
class LoadServiceFilesEvent extends ServiceFilesEvent {
|
||||
final String? serviceId;
|
||||
final ServiceModel? service;
|
||||
const LoadServiceFilesEvent({this.serviceId, this.service});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [serviceId, service];
|
||||
}
|
||||
|
||||
class AddServiceFilesEvent extends ServiceFilesEvent {
|
||||
final List<PlatformFile> files;
|
||||
const AddServiceFilesEvent(this.files);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [files];
|
||||
}
|
||||
|
||||
class UploadServiceFilesEvent extends ServiceFilesEvent {
|
||||
final PlatformFile? pickedFile;
|
||||
final File? photo;
|
||||
const UploadServiceFilesEvent({this.pickedFile, this.photo});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [pickedFile, photo];
|
||||
}
|
||||
|
||||
class DeleteServiceFilesEvent extends ServiceFilesEvent {}
|
||||
|
||||
class ToggleServiceFileSelectionEvent extends ServiceFilesEvent {
|
||||
final ServiceFileModel file;
|
||||
const ToggleServiceFileSelectionEvent(this.file);
|
||||
}
|
||||
52
lib/features/services/blocs/service_files_state.dart
Normal file
52
lib/features/services/blocs/service_files_state.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
part of 'service_files_bloc.dart';
|
||||
|
||||
enum ServiceFilesStatus { initial, loading, uploading, success, failure }
|
||||
|
||||
class ServiceFilesState extends Equatable {
|
||||
const ServiceFilesState({
|
||||
this.serviceId,
|
||||
required this.status,
|
||||
this.error,
|
||||
this.localFiles = const [],
|
||||
this.remoteFiles = const [],
|
||||
this.selectedFiles = const [],
|
||||
});
|
||||
|
||||
final String? serviceId;
|
||||
final ServiceFilesStatus status;
|
||||
final String? error;
|
||||
final List<ServiceFileModel> localFiles;
|
||||
final List<ServiceFileModel> remoteFiles;
|
||||
|
||||
final List<ServiceFileModel> selectedFiles;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
serviceId,
|
||||
status,
|
||||
error,
|
||||
localFiles,
|
||||
remoteFiles,
|
||||
selectedFiles,
|
||||
];
|
||||
|
||||
List<ServiceFileModel> get allFiles => [...remoteFiles, ...localFiles];
|
||||
|
||||
ServiceFilesState copyWith({
|
||||
String? serviceId,
|
||||
ServiceFilesStatus? status,
|
||||
String? error,
|
||||
List<ServiceFileModel>? localFiles,
|
||||
List<ServiceFileModel>? remoteFiles,
|
||||
List<ServiceFileModel>? selectedFiles,
|
||||
}) {
|
||||
return ServiceFilesState(
|
||||
serviceId: serviceId ?? this.serviceId,
|
||||
status: status ?? this.status,
|
||||
error: error,
|
||||
localFiles: localFiles ?? this.localFiles,
|
||||
remoteFiles: remoteFiles ?? this.remoteFiles,
|
||||
selectedFiles: selectedFiles ?? this.selectedFiles,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import 'package:flux/features/services/models/service_file_model.dart';
|
||||
import 'package:flux/features/services/models/service_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
part 'services_state.dart';
|
||||
|
||||
class ServicesCubit extends Cubit<ServicesState> {
|
||||
@@ -273,49 +274,62 @@ class ServicesCubit extends Cubit<ServicesState> {
|
||||
);
|
||||
}
|
||||
|
||||
void saveAndCopyFileToCustomer(ServiceFileModel file) async {
|
||||
void saveAndCopyFileToCustomer(List<ServiceFileModel> selectedFiles) async {
|
||||
final currentService = state.currentService;
|
||||
|
||||
// 1. Check di sicurezza: se non c'è il cliente, non sappiamo dove copiare
|
||||
if (currentService == null || currentService.customerId == null) {
|
||||
// Magari mostra un errore: non posso copiare al cliente se non c'è un cliente!
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ServicesStatus.failure,
|
||||
errorMessage:
|
||||
"Impossibile copiare: nessun cliente associato alla pratica.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: ServicesStatus.loading));
|
||||
|
||||
try {
|
||||
// 1. Salviamo la pratica (Bozza o definitiva che sia)
|
||||
// Questo assicura che il file sia stato caricato su Storage e censito su DB
|
||||
await saveCurrentService(isBozza: currentService.isBozza);
|
||||
// 2. SALVATAGGIO CORAZZATO
|
||||
// Chiamiamo il repo e otteniamo la pratica con TUTTI i file ora dotati di ID e storagePath
|
||||
final updatedService = await _repository.saveFullService(currentService);
|
||||
|
||||
// 2. Recuperiamo il file "aggiornato"
|
||||
// Dopo il saveCurrentService, il file che prima era "locale" ora ha un URL.
|
||||
// Lo cerchiamo nella lista aggiornata per nome o estensione.
|
||||
final savedFile = state.currentService!.files.firstWhere(
|
||||
(f) => f.name == file.name && f.extension == file.extension,
|
||||
orElse: () => file,
|
||||
);
|
||||
// 3. COPIA RELAZIONALE
|
||||
// Per ogni file che l'utente ha selezionato nella UI, cerchiamo la sua versione
|
||||
// "ufficiale" (quella con lo storagePath) nel modello appena tornato dal DB.
|
||||
for (var selectedFile in selectedFiles) {
|
||||
// Cerchiamo il match nel modello aggiornato
|
||||
final persistedFile = updatedService.files.firstWhere(
|
||||
(f) =>
|
||||
f.name == selectedFile.name &&
|
||||
f.extension == selectedFile.extension,
|
||||
orElse: () => throw Exception(
|
||||
"File ${selectedFile.name} non trovato dopo il salvataggio.",
|
||||
),
|
||||
);
|
||||
|
||||
if (savedFile.storagePath.isEmpty) {
|
||||
throw Exception(
|
||||
"Errore: URL del file non trovato dopo il salvataggio.",
|
||||
// Creiamo il link nel database del cliente
|
||||
await _repository.copyFileToCustomer(
|
||||
file: persistedFile,
|
||||
customerId: currentService.customerId!,
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Chiamiamo il repository per la copia fisica nel database del cliente
|
||||
// Passiamo l'URL del file e l'ID del cliente
|
||||
await _repository.copyFileToCustomer(
|
||||
file: savedFile,
|
||||
customerId: currentService.customerId!,
|
||||
// 4. AGGIORNAMENTO STATO
|
||||
// Aggiorniamo il Cubit con il servizio salvato così la UI mostra i file come "Remoti"
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ServicesStatus.success,
|
||||
currentService: updatedService,
|
||||
),
|
||||
);
|
||||
|
||||
// 4. Feedback all'utente
|
||||
// Potresti emettere un successo o mostrare un toast
|
||||
emit(state.copyWith(status: ServicesStatus.success));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ServicesStatus.failure,
|
||||
errorMessage: "Errore durante la copia del file: $e",
|
||||
errorMessage: "Errore durante il salvataggio e copia: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user