Dropzone OK et refactor page event_add

This commit is contained in:
2025-05-27 20:08:39 +02:00
parent 49dffff1bf
commit 9489183b68
8 changed files with 627 additions and 590 deletions

View File

@ -0,0 +1,260 @@
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<Map<String, String>> uploadedFiles;
final ValueChanged<List<Map<String, String>>> 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<DropzoneUploadWidget> createState() => _DropzoneUploadWidgetState();
}
class _DropzoneUploadWidgetState extends State<DropzoneUploadWidget> {
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<void> _handleFiles(List<dynamic> files) async {
setState(() => _isLoading = true);
try {
List<Map<String, String>> 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<Map<String, String>>.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);
}
},
),
),
],
);
}
}