261 lines
8.8 KiB
Dart
261 lines
8.8 KiB
Dart
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);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|