import 'package:flutter/material.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter_dropzone/flutter_dropzone.dart'; import 'package:path/path.dart' as p; class DropzoneUploadWidget extends StatefulWidget { final List> uploadedFiles; final ValueChanged>> onFilesChanged; final bool isLoading; final String? error; final String? success; final double width; final double height; const DropzoneUploadWidget({ super.key, required this.uploadedFiles, required this.onFilesChanged, this.isLoading = false, this.error, this.success, this.width = 400, this.height = 200, }); @override State createState() => _DropzoneUploadWidgetState(); } class _DropzoneUploadWidgetState extends State { DropzoneViewController? _dropzoneController; bool _isDropzoneHighlighted = false; bool _isLoading = false; String? _error; String? _success; @override void initState() { super.initState(); _isLoading = widget.isLoading; _error = widget.error; _success = widget.success; } Future _handleFiles(List files) async { setState(() => _isLoading = true); try { List> newFiles = List.from(widget.uploadedFiles); for (final file in files) { final name = await _dropzoneController!.getFilename(file); final bytes = await _dropzoneController!.getFileData(file); if (bytes != null) { final ref = FirebaseStorage.instance.ref().child( 'events/temp/${DateTime.now().millisecondsSinceEpoch}_$name'); final uploadTask = await ref.putData(bytes); final url = await uploadTask.ref.getDownloadURL(); if (!newFiles.any((f) => f['name'] == name && f['url'] == url)) { newFiles.add({'name': name, 'url': url}); } } } widget.onFilesChanged(newFiles); setState(() { _success = "Fichier(s) ajouté(s) !"; _error = null; }); } catch (e) { setState(() { _error = 'Erreur lors de l\'upload : $e'; _success = null; }); } finally { setState(() { _isLoading = false; _isDropzoneHighlighted = false; }); } } @override Widget build(BuildContext context) { return SizedBox( width: widget.width, height: widget.height, child: Container( decoration: BoxDecoration( border: Border.all( color: _isDropzoneHighlighted ? Colors.blue : Colors.grey, width: 2, ), borderRadius: BorderRadius.circular(8), color: _isDropzoneHighlighted ? Colors.blue.withOpacity(0.1) : null, ), child: Stack( children: [ DropzoneView( onCreated: (controller) => _dropzoneController = controller, onDropFiles: (files) async { if (files == null) return; await _handleFiles(files); }, onHover: () => setState(() => _isDropzoneHighlighted = true), onLeave: () => setState(() => _isDropzoneHighlighted = false), ), Positioned.fill( child: IgnorePointer( child: widget.uploadedFiles.isEmpty ? Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Icons.cloud_upload, size: 48, color: Colors.grey), const SizedBox(height: 12), const Text( 'Glissez-déposez des fichiers ici ou cliquez sur "Ajouter"', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey, fontSize: 16), ), const SizedBox(height: 16), ], ) : null, ), ), if (widget.uploadedFiles.isNotEmpty) Padding( padding: const EdgeInsets.all(8.0), child: _buildFileListAndButton(), ), if (widget.uploadedFiles.isEmpty) Positioned( bottom: 16, left: 0, right: 0, child: Center( child: SizedBox( width: 160, child: ElevatedButton.icon( icon: const Icon(Icons.attach_file, size: 18), label: const Text('Ajouter'), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 8), minimumSize: const Size(80, 36), textStyle: const TextStyle(fontSize: 14), ), onPressed: _isLoading ? null : () async { final files = await _dropzoneController?.pickFiles(); if (files != null) { await _handleFiles(files); } }, ), ), ), ), if (_isLoading) const Positioned.fill( child: ColoredBox( color: Color.fromARGB(80, 255, 255, 255), child: Center( child: SizedBox( width: 32, height: 32, child: CircularProgressIndicator()), ), ), ), if (_error != null) Positioned( bottom: 0, left: 0, right: 0, child: Padding( padding: EdgeInsets.only(top: 8.0), child: Text(_error!, style: const TextStyle(color: Colors.red)), ), ), if (_success != null) Positioned( bottom: 0, left: 0, right: 0, child: Padding( padding: const EdgeInsets.only(top: 32.0), child: Text(_success!, style: const TextStyle(color: Colors.green)), ), ), ], ), ), ); } Widget _buildFileListAndButton() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...widget.uploadedFiles.map((file) { final fileName = file['name']!; final ext = p.extension(fileName).toLowerCase(); IconData icon; if ([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"] .contains(ext)) { icon = Icons.image; } else if (ext == ".pdf") { icon = Icons.picture_as_pdf; } else if ([".txt", ".md", ".csv", ".json", ".xml"].contains(ext)) { icon = Icons.description; } else { icon = Icons.attach_file; } return ListTile( leading: Icon(icon, color: Colors.blueGrey), title: Text(fileName, overflow: TextOverflow.ellipsis), trailing: IconButton( icon: const Icon(Icons.close), onPressed: _isLoading ? null : () { final newFiles = List>.from(widget.uploadedFiles) ..remove(file); widget.onFilesChanged(newFiles); }, ), contentPadding: EdgeInsets.zero, dense: true, ); }).toList(), SizedBox( width: 160, child: ElevatedButton.icon( icon: const Icon(Icons.attach_file, size: 18), label: const Text('Ajouter'), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), minimumSize: const Size(80, 36), textStyle: const TextStyle(fontSize: 14), ), onPressed: _isLoading ? null : () async { final files = await _dropzoneController?.pickFiles(); if (files != null) { await _handleFiles(files); } }, ), ), ], ); } }