import 'package:flutter/material.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/widgets/image_viewer_widget.dart'; import 'package:flux/core/widgets/pdf_viewer_widget.dart'; import 'package:flux/core/widgets/qr_upload_dialog.dart'; import 'package:flux/features/attachments/models/attachment_model.dart'; import 'package:flux/features/customers/blocs/customer_files_bloc.dart'; import 'package:flux/features/customers/models/customer_model.dart'; class CustomerDetailScreen extends StatefulWidget { final CustomerModel customer; const CustomerDetailScreen({super.key, required this.customer}); @override State createState() => _CustomerDetailScreenState(); } class _CustomerDetailScreenState extends State { @override void initState() { super.initState(); _loadFiles(); } void _loadFiles() { context.read().add(LoadCustomerFilesEvent()); } Future _pickAndUpload() async { CustomerFilesBloc customerFilesBloc = context.read(); // Chiamata statica pulita FilePickerResult? result = await FilePicker.pickFiles( allowMultiple: true, type: FileType.any, withData: true, // Fondamentale per avere i bytes pronti se servono ); if (result != null) { for (var pickedFile in result.files) { try { customerFilesBloc.add( UploadCustomerFileEvent(pickedFile: pickedFile), ); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Errore upload ${pickedFile.name}: $e")), ); } } } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: context.background, appBar: AppBar( title: Text( widget.customer.name, style: const TextStyle(fontWeight: FontWeight.bold), ), backgroundColor: context.background, elevation: 0, ), body: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // COLONNA SINISTRA: ANAGRAFICA Expanded( flex: 1, child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: _buildInfoSection(), ), ), // DIVISORE VERTICALE VerticalDivider( width: 1, color: context.accent.withValues(alpha: 0.1), ), // COLONNA DESTRA: DOCUMENTI Expanded( flex: 2, child: Padding( padding: const EdgeInsets.all(24), child: _buildDocumentSection(), ), ), ], ), ); } Widget _buildInfoSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _infoTile(Icons.phone_android, "Telefono", widget.customer.phoneNumber), _infoTile( Icons.email_outlined, "Email", widget.customer.email.isEmpty ? "Non fornita" : widget.customer.email, ), _infoTile( Icons.notes_outlined, "Note", widget.customer.note.isEmpty ? "Nessun appunto" : widget.customer.note, ), const SizedBox(height: 20), if (widget.customer.doNotDisturb) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: const Row( children: [ Icon(Icons.privacy_tip, color: Colors.red, size: 20), SizedBox(width: 10), Text( "PRIVACY: Non disturbare", style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, ), ), ], ), ), ], ); } Widget _buildDocumentSection() { return BlocBuilder( builder: (context, state) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "DOCUMENTI", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: context.accent, ), ), // ZONA BOTTONI: Li mettiamo in una Row Row( children: [ // Bottone classico: c'รจ sempre (carica da disco locale) ElevatedButton.icon( onPressed: _pickAndUpload, icon: const Icon(Icons.add_circle_outline), label: const Text("CARICA FILE"), ), const SizedBox(width: 12), ElevatedButton.icon( onPressed: state.selectedFiles.isEmpty ? null : () => _showDeleteConfirmationDialog( context: context, files: state.selectedFiles, ), icon: const Icon(Icons.delete_outline), label: const Text("ELIMINA FILE"), ), // Controlliamo se siamo su Desktop/Web per mostrare il QR if (!context.read().state.isMobileDevice) ...[ const SizedBox( width: 12, ), // Un po' di respiro tra i bottoni ElevatedButton.icon( onPressed: () { showDialog( context: context, builder: (context) => QrUploadDialog( deepLinkUrl: 'fluxapp://customer/${widget.customer.id}/upload?name=${Uri.encodeComponent(widget.customer.name)}', title: 'Scatta per ${widget.customer.name}', ), ); }, icon: const Icon(Icons.qr_code), label: const Text("GENERA QR"), style: ElevatedButton.styleFrom( // Lo facciamo di un colore leggermente diverso per distinguerlo backgroundColor: context.accent.withValues( alpha: 0.1, ), foregroundColor: context.accent, elevation: 0, ), ), ], ], ), ], ), const SizedBox(height: 20), if (state.status == CustomerFilesStatus.loading) const Center(child: CircularProgressIndicator()) else if (state.customerFiles.isEmpty) const Center(child: Text("Nessun documento presente")) else Expanded( child: GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, mainAxisSpacing: 16, crossAxisSpacing: 16, childAspectRatio: 1.2, ), itemCount: state.customerFiles.length, itemBuilder: (context, index) => _FileCard(file: state.customerFiles[index], state: state), ), ), ], ); }, ); } Widget _infoTile(IconData icon, String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 18, color: context.accent), const SizedBox(width: 8), Text(label, style: const TextStyle(fontWeight: FontWeight.w600)), ], ), const SizedBox(height: 4), Text( value, style: TextStyle(color: context.secondaryText, fontSize: 16), ), ], ), ); } void _showDeleteConfirmationDialog({ required BuildContext context, required List files, }) {} } class _FileCard extends StatelessWidget { final AttachmentModel file; final CustomerFilesState state; const _FileCard({required this.file, required this.state}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => context.read().add( ToggleCustomerFileSelectionEvent(file), ), onDoubleTap: () => _handleDoubleClickOnFile(context, file), child: Stack( children: [ Container( decoration: BoxDecoration( color: context.background, borderRadius: BorderRadius.circular(12), border: Border.all(color: context.accent.withValues(alpha: 0.1)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( _getFileIcon(file.extension), size: 48, color: context.accent, ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( file.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ], ), ), if (state.selectedFiles.contains(file)) Positioned( top: 10, left: 10, child: Icon(Icons.check_circle, color: context.accent, size: 24), ), ], ), ); } IconData _getFileIcon(String ext) { switch (ext.toLowerCase()) { case 'pdf': return Icons.picture_as_pdf; case 'jpg': case 'jpeg': case 'png': return Icons.image; default: return Icons.insert_drive_file; } } void _handleDoubleClickOnFile(BuildContext context, AttachmentModel file) { showDialog( context: context, barrierDismissible: true, builder: (ctx) => Dialog( insetPadding: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: SizedBox( width: double.infinity, height: MediaQuery.of(context).size.height * 0.8, child: file.isPdf ? PdfViewerWidget(storagePath: file.storagePath) : ImageViewerWidget(storagePath: file.storagePath), ), ), ), ); } }