From 65aa3c7de842ffcc7ff9f2181407e003a63f2eec Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Sat, 9 May 2026 09:50:20 +0200 Subject: [PATCH] fix mobile upload --- lib/core/routes/app_router.dart | 5 +- .../shared_forms/attachments_section.dart | 78 +++++++++++++++++-- .../shared_forms/mobile_upload_screen.dart | 17 +++- .../shared_forms/shared_files_section.dart | 14 +++- .../attachments/blocs/attachments_bloc.dart | 10 ++- .../attachments/blocs/attachments_events.dart | 7 +- .../attachments/blocs/attachments_state.dart | 1 + .../data/attachments_repository.dart | 4 +- .../customers/ui/customer_detail_screen.dart | 8 +- .../operations/ui/operation_form_screen.dart | 26 ++++--- .../ui/widgets/details_section.dart | 46 ++++++----- 11 files changed, 164 insertions(+), 52 deletions(-) diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index 1bbe5d4..af24795 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/data/core_repository.dart'; import 'package:flux/core/layout/app_shell.dart'; -import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/widgets/set_password_screen.dart'; import 'package:flux/core/widgets/shared_forms/mobile_upload_screen.dart'; import 'package:flux/core/widgets/shared_forms/upload_success_screen.dart'; @@ -285,6 +284,7 @@ class AppRouter { builder: (context, state) { final typeString = state.pathParameters['type']!; final id = state.pathParameters['id']!; + final companyId = state.uri.queryParameters['companyId']!; // Trasformiamo la stringa dell'URL nel nostro amato Enum! final parentType = AttachmentParentType.values.firstWhere( @@ -297,8 +297,9 @@ class AppRouter { return BlocProvider( create: (context) => AttachmentsBloc(parentId: id, parentType: parentType), - child: const SharedMobileUploadScreen( + child: SharedMobileUploadScreen( title: 'Caricamento Rapido', + companyId: companyId, ), ); }, diff --git a/lib/core/widgets/shared_forms/attachments_section.dart b/lib/core/widgets/shared_forms/attachments_section.dart index 474b51f..af7ec31 100644 --- a/lib/core/widgets/shared_forms/attachments_section.dart +++ b/lib/core/widgets/shared_forms/attachments_section.dart @@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flux/core/blocs/session/session_cubit.dart'; +import 'package:flux/core/widgets/qr_upload_dialog.dart'; import 'package:flux/features/attachments/data/attachments_repository.dart'; import 'package:flux/features/attachments/ui/attachment_viewer_screen.dart'; import 'package:flux/features/attachments/ui/quick_rename_dialog.dart'; @@ -30,14 +32,16 @@ class _ExportItem { class SharedAttachmentsSection extends StatefulWidget { final String? parentId; - final String customerDisplayName; + final String titleForUpload; final AttachmentParentType parentType; + final Future Function()? onGenerateIdForQr; const SharedAttachmentsSection({ super.key, this.parentId, - this.customerDisplayName = 'Cliente_sconosciuto', + this.titleForUpload = 'Cliente_sconosciuto', required this.parentType, + this.onGenerateIdForQr, }); @override @@ -65,7 +69,7 @@ class _SharedAttachmentsSectionState extends State { Future _selectExportDirectory() async { final String? selectedDirectory = await FilePicker.getDirectoryPath( - dialogTitle: 'Seleziona la cartella di esportazione per TIM/Citrix', + dialogTitle: 'Seleziona la cartella di esportazione', ); if (selectedDirectory != null) { @@ -189,7 +193,7 @@ class _SharedAttachmentsSectionState extends State { } else { // Se sono più file uniti - suggestedName = '${widget.customerDisplayName}_Unito'; + suggestedName = '${widget.titleForUpload}_Unito'; } if (!mounted) return; @@ -397,7 +401,6 @@ class _SharedAttachmentsSectionState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); - // USIAMO IL TUO BLOC! return BlocBuilder( builder: (context, state) { final allFiles = state.allFiles; @@ -421,7 +424,7 @@ class _SharedAttachmentsSectionState extends State { color: theme.colorScheme.primary, ), title: const Text( - 'Cartella Export (Es. Citrix TIM)', + 'Cartella Export (Es. TIM AttachmentRepository)', style: TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( @@ -451,6 +454,69 @@ class _SharedAttachmentsSectionState extends State { onPressed: state.status == AttachmentsStatus.uploading ? null : _pickFiles, + /* : () { + final bloc = context.read(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => BlocProvider.value( + value: bloc, + child: SharedMobileUploadScreen( + title: widget.titleForUpload, + ), + ), + ), + ); + }, */ + ), + const SizedBox(width: 8), + Tooltip( + message: 'Carica foto con lo smartphone', + child: IconButton( + icon: const Icon(Icons.qr_code_scanner), + color: theme.colorScheme.primary, // Sempre colorato! + onPressed: () async { + String? targetId = state.parentId; + + // SE L'ID NON C'È, CHIAMIAMO IL SALVATAGGIO IN BACKGROUND! + if (targetId == null) { + if (widget.onGenerateIdForQr != null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Salvataggio rapido scheda in corso... ⏳', + ), + duration: Duration(seconds: 1), + ), + ); + + // Aspettiamo che il TicketFormCubit faccia il suo lavoro + targetId = await widget.onGenerateIdForQr!(); + } + + // Se fallisce (es. validazione form non passata), ci fermiamo + if (targetId == null) return; + } + + // GENERAZIONE DEL DEEP LINK AGNOSTICO + final companyId = GetIt.I + .get() + .state + .company! + .id!; + final deepLink = + 'https://flux.catelli.it/upload/${state.parentType.name}/$targetId?companyId=$companyId'; + + if (context.mounted) { + showDialog( + context: context, + builder: (_) => QrUploadDialog( + deepLinkUrl: deepLink, + title: 'Carica File: ${widget.titleForUpload}', + ), + ); + } + }, + ), ), const SizedBox(width: 12), diff --git a/lib/core/widgets/shared_forms/mobile_upload_screen.dart b/lib/core/widgets/shared_forms/mobile_upload_screen.dart index b995cb6..9d15b0e 100644 --- a/lib/core/widgets/shared_forms/mobile_upload_screen.dart +++ b/lib/core/widgets/shared_forms/mobile_upload_screen.dart @@ -8,8 +8,13 @@ import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; class SharedMobileUploadScreen extends StatefulWidget { final String title; + final String companyId; - const SharedMobileUploadScreen({super.key, required this.title}); + const SharedMobileUploadScreen({ + super.key, + required this.title, + required this.companyId, + }); @override State createState() => @@ -290,7 +295,10 @@ class _SharedMobileUploadScreenState extends State { } Future _handleFilePicker() async { - final result = await FilePicker.pickFiles(allowMultiple: true); + final result = await FilePicker.pickFiles( + allowMultiple: true, + withData: true, + ); if (result != null) { setState(() { _stagedFiles.addAll(result.files); @@ -304,7 +312,10 @@ class _SharedMobileUploadScreenState extends State { // Lanciamo l'evento del nostro nuovo AttachmentsBloc Agnostico! context.read().add( - UploadAttachmentsEvent(pickedFiles: _stagedFiles), + UploadAttachmentsEvent( + pickedFiles: _stagedFiles, + companyId: widget.companyId, + ), ); } } diff --git a/lib/core/widgets/shared_forms/shared_files_section.dart b/lib/core/widgets/shared_forms/shared_files_section.dart index e1e2439..a993661 100644 --- a/lib/core/widgets/shared_forms/shared_files_section.dart +++ b/lib/core/widgets/shared_forms/shared_files_section.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/widgets/qr_upload_dialog.dart'; import 'package:flux/core/widgets/shared_forms/mobile_upload_screen.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; +import 'package:get_it/get_it.dart'; class SharedFilesSection extends StatelessWidget { final String titleNameForUpload; @@ -64,8 +66,13 @@ class SharedFilesSection extends StatelessWidget { } // GENERAZIONE DEL DEEP LINK AGNOSTICO + final companyId = GetIt.I + .get() + .state + .company! + .id!; final deepLink = - 'https://flux.catelli.it/upload/${state.parentType.name}/$targetId'; + 'https://flux.catelli.it/upload/${state.parentType.name}/$targetId?companyId=$companyId'; if (context.mounted) { showDialog( @@ -93,6 +100,11 @@ class SharedFilesSection extends StatelessWidget { value: bloc, child: SharedMobileUploadScreen( title: titleNameForUpload, + companyId: GetIt.I + .get() + .state + .company! + .id!, ), ), ), diff --git a/lib/features/attachments/blocs/attachments_bloc.dart b/lib/features/attachments/blocs/attachments_bloc.dart index 3c2b665..cf6b307 100644 --- a/lib/features/attachments/blocs/attachments_bloc.dart +++ b/lib/features/attachments/blocs/attachments_bloc.dart @@ -14,6 +14,7 @@ part 'attachments_state.dart'; class AttachmentsBloc extends Bloc { final _repository = GetIt.I.get(); + final String? companyId = GetIt.I.get().state.company?.id; AttachmentsBloc({String? parentId, required AttachmentParentType parentType}) : super( @@ -36,7 +37,7 @@ class AttachmentsBloc extends Bloc { on(_onClearAttachmentSelection); // Se il BLoC nasce già con un ID, carichiamo i file - if (parentId != null) { + if (parentId != null && companyId != null) { add(LoadAttachmentsEvent(parentId: parentId)); } } @@ -65,6 +66,7 @@ class AttachmentsBloc extends Bloc { parentId: event.newParentId, parentType: state.parentType, pickedFile: fakePlatformFile, + companyId: companyId!, ); }).toList(); @@ -118,12 +120,11 @@ class AttachmentsBloc extends Bloc { // BIVIO 1: PRATICA NUOVA (Salvataggio locale) if (currentId == null) { - final companyId = GetIt.I.get().state.company!.id!; final newLocalFiles = event.files.map((file) { // Assegniamo i campi dinamicamente in base al parentType! return AttachmentModel( id: null, - companyId: companyId, + companyId: companyId!, operationId: state.parentType == AttachmentParentType.operation ? '' : null, @@ -156,6 +157,7 @@ class AttachmentsBloc extends Bloc { parentId: currentId, parentType: state.parentType, pickedFile: file, + companyId: companyId!, ); }).toList(); @@ -191,6 +193,7 @@ class AttachmentsBloc extends Bloc { parentId: state.parentId!, parentType: state.parentType, pickedFile: file, + companyId: event.companyId, ), ); } @@ -216,6 +219,7 @@ class AttachmentsBloc extends Bloc { parentId: state.parentId!, parentType: state.parentType, pickedFile: fakePlatformFile, + companyId: companyId!, ), ); } diff --git a/lib/features/attachments/blocs/attachments_events.dart b/lib/features/attachments/blocs/attachments_events.dart index f968e63..6fe6826 100644 --- a/lib/features/attachments/blocs/attachments_events.dart +++ b/lib/features/attachments/blocs/attachments_events.dart @@ -29,7 +29,12 @@ class AddAttachmentsEvent extends AttachmentsEvent { class UploadAttachmentsEvent extends AttachmentsEvent { final List? pickedFiles; final List? photos; - const UploadAttachmentsEvent({this.pickedFiles, this.photos}); + final String companyId; + const UploadAttachmentsEvent({ + this.pickedFiles, + this.photos, + required this.companyId, + }); } class DeleteAttachmentsEvent extends AttachmentsEvent {} diff --git a/lib/features/attachments/blocs/attachments_state.dart b/lib/features/attachments/blocs/attachments_state.dart index 94de53c..827db86 100644 --- a/lib/features/attachments/blocs/attachments_state.dart +++ b/lib/features/attachments/blocs/attachments_state.dart @@ -16,6 +16,7 @@ class AttachmentsState extends Equatable { final AttachmentParentType parentType; final AttachmentsStatus status; final String? error; + final List localFiles; final List remoteFiles; final List selectedFiles; diff --git a/lib/features/attachments/data/attachments_repository.dart b/lib/features/attachments/data/attachments_repository.dart index 391a71a..9b86fdf 100644 --- a/lib/features/attachments/data/attachments_repository.dart +++ b/lib/features/attachments/data/attachments_repository.dart @@ -3,8 +3,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:flux/features/attachments/models/attachment_model.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; -import 'package:get_it/get_it.dart'; -import 'package:flux/core/blocs/session/session_cubit.dart'; class AttachmentsRepository { final _supabase = Supabase.instance.client; @@ -58,9 +56,9 @@ class AttachmentsRepository { required String parentId, required AttachmentParentType parentType, required PlatformFile pickedFile, + required String companyId, }) async { try { - final companyId = GetIt.I.get().state.company!.id!; final extension = pickedFile.extension ?? pickedFile.name.split('.').last; final cleanName = pickedFile.name .replaceAll(RegExp(r'[^\w\s\.-]'), '') diff --git a/lib/features/customers/ui/customer_detail_screen.dart b/lib/features/customers/ui/customer_detail_screen.dart index 30f90aa..0e2e949 100644 --- a/lib/features/customers/ui/customer_detail_screen.dart +++ b/lib/features/customers/ui/customer_detail_screen.dart @@ -9,6 +9,7 @@ import 'package:flux/core/widgets/qr_upload_dialog.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/attachments/models/attachment_model.dart'; import 'package:flux/features/customers/models/customer_model.dart'; +import 'package:get_it/get_it.dart'; class CustomerDetailScreen extends StatefulWidget { final CustomerModel customer; @@ -43,7 +44,12 @@ class _CustomerDetailScreenState extends State { if (result != null) { try { - attachmentsBloc.add(UploadAttachmentsEvent(pickedFiles: result.files)); + attachmentsBloc.add( + UploadAttachmentsEvent( + pickedFiles: result.files, + companyId: GetIt.I.get().state.company!.id!, + ), + ); } catch (e) { if (mounted) { ScaffoldMessenger.of( diff --git a/lib/features/operations/ui/operation_form_screen.dart b/lib/features/operations/ui/operation_form_screen.dart index fb694ce..321dbb7 100644 --- a/lib/features/operations/ui/operation_form_screen.dart +++ b/lib/features/operations/ui/operation_form_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/widgets/shared_forms/shared_files_section.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/operations/blocs/operation_form_cubit.dart'; import 'package:flux/features/operations/models/operation_model.dart'; @@ -246,10 +245,12 @@ class _OperationFormScreenState extends State { flex: 3, child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), - child: SharedFilesSection( - titleNameForUpload: + child: SharedAttachmentsSection( + parentType: AttachmentParentType.operation, + parentId: state.operation.id, + titleForUpload: state.operation.customerDisplayName ?? - 'Nuova operazione', + 'Nuova pratica', onGenerateIdForQr: _generateIdForQr, ), ), @@ -408,9 +409,9 @@ class _OperationFormScreenState extends State { color: _getStatusColor(displayStatus), ), items: OperationStatus.values - .where( + /* .where( (s) => s != OperationStatus.draft, - ) // Nascondiamo 'Bozza' dal menu + ) // Nascondiamo 'Bozza' dal menu */ .map( (status) => DropdownMenuItem( value: status, @@ -456,7 +457,7 @@ class _OperationFormScreenState extends State { ), const Divider(height: 32), - _buildSectionTitle('Cliente & Riferimento'), + //_buildSectionTitle('Cliente & Riferimento'), SharedCustomerSection( customerId: currentOp.customerId, customerName: currentOp.customerDisplayName, @@ -539,15 +540,18 @@ class _OperationFormScreenState extends State { const Divider(height: 32), if (showFiles) ...[ - /* SharedAttachmentsSection( + SharedAttachmentsSection( parentType: AttachmentParentType.operation, parentId: currentOp.id, - ), */ - SharedFilesSection( - titleNameForUpload: + titleForUpload: state.operation.customerDisplayName ?? 'Nuova pratica', onGenerateIdForQr: _generateIdForQr, ), + /* SharedFilesSection( + titleNameForUpload: + state.operation.customerDisplayName ?? 'Nuova pratica', + onGenerateIdForQr: _generateIdForQr, + ), */ ], ], ); diff --git a/lib/features/operations/ui/widgets/details_section.dart b/lib/features/operations/ui/widgets/details_section.dart index e26334d..b64f388 100644 --- a/lib/features/operations/ui/widgets/details_section.dart +++ b/lib/features/operations/ui/widgets/details_section.dart @@ -45,6 +45,7 @@ class DetailsSection extends StatelessWidget { } void _showProviderModal(BuildContext context, String operationType) { + final OperationFormCubit cubit = context.read(); showModalBottomSheet( context: context, isScrollControlled: true, @@ -103,28 +104,31 @@ class DetailsSection extends StatelessWidget { ); } - return ListView.builder( - controller: scrollController, - itemCount: filteredProviders.length, - itemBuilder: (context, index) { - final provider = filteredProviders[index]; - return ListTile( - leading: const Icon(Icons.business), - title: Text( - provider.name, - style: const TextStyle( - fontWeight: FontWeight.bold, + return BlocProvider.value( + value: cubit, + child: ListView.builder( + controller: scrollController, + itemCount: filteredProviders.length, + itemBuilder: (context, index) { + final provider = filteredProviders[index]; + return ListTile( + leading: const Icon(Icons.business), + title: Text( + provider.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), - ), - onTap: () { - context.read().updateFields( - providerId: provider.id, - providerDisplayName: provider.name, - ); - Navigator.pop(modalContext); - }, - ); - }, + onTap: () { + context.read().updateFields( + providerId: provider.id, + providerDisplayName: provider.name, + ); + Navigator.pop(modalContext); + }, + ); + }, + ), ); }, ),