Last active
May 9, 2025 09:51
-
-
Save PlugFox/c857c181a5a90eec465cadb60df97ee4 to your computer and use it in GitHub Desktop.
Flutter file picker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:typed_data'; | |
import 'package:crypto/crypto.dart' as crypto; | |
import 'package:meta/meta.dart'; | |
@internal | |
abstract final class BytesUtil { | |
/// Extract hash from a [Uint8List] and convert it to a hex string. | |
static String sha256(Uint8List bytes) { | |
if (bytes.isEmpty) return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; | |
crypto.Digest digest = crypto.sha256.convert(bytes); | |
return digest.toString(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:file_picker/file_picker.dart' as file_picker; | |
/// Pick a file to attach to the next message. | |
void _pickFile() => runZonedGuarded<void>( | |
() async { | |
const allowedExtensions = <String>{ | |
'txt', | |
'pdf', | |
'doc', | |
'docx', | |
'png', | |
'jpg', | |
'jpeg', | |
'gif', | |
'bpm', | |
//'webp', | |
'heic', | |
'heics', | |
'heif', | |
'heifs', | |
'hif', | |
}; | |
const maxFileSize = 20 * 1024 * 1024; // 20 MB | |
const maxFileCount = 15; // 15 files max can be attached at once | |
final file_picker.FilePickerResult? result; | |
try { | |
final filePicker = file_picker.FilePicker.platform; | |
result = await filePicker.pickFiles( | |
dialogTitle: _localization.chatInputTooltipAttachFile, | |
allowMultiple: false, // allow multiple files to be selected | |
withData: true, // bytes for web/mobile preview | |
type: file_picker.FileType.custom, | |
readSequential: false, | |
withReadStream: false, | |
allowedExtensions: allowedExtensions.toList(growable: false), | |
); | |
} on Object catch (e, s) { | |
l.w('File picker not available: $e', s); | |
if (_disposed) return; | |
// ignore: use_build_context_synchronously | |
ErrorUtil.displayErrorSnackBar(context, 'Can\'t pick files', s); | |
return; | |
} | |
if (_disposed) return; | |
if (_chatController.state case ChatState$Processing(blockInput: true)) return; | |
if (result == null || result.count < 1 || result.files.isEmpty) return; | |
final attachedAt = DateTime.now().toUtc(); | |
final stopwatch = Stopwatch()..start(); | |
final newFiles = | |
await Stream<file_picker.PlatformFile>.fromIterable(result.files) | |
.take(maxFileCount) | |
.asyncMap<AttachmentFile>((e) async { | |
if (stopwatch.elapsed > Duration(milliseconds: 8)) { | |
// Releave the event loop | |
await Future<void>.delayed(Duration.zero); | |
stopwatch.reset(); | |
} | |
final name = path.basename(e.name); | |
var extension = e.extension?.toLowerCase() ?? path.extension(name).toLowerCase(); | |
if (extension.startsWith('.')) extension = extension.substring(1); | |
if (extension.isEmpty) extension = 'bin'; | |
final type = AttachmentFile.extensionToType(extension); | |
var bytes = e.bytes ?? Uint8List(0); | |
if (bytes.length > maxFileSize) bytes = Uint8List(0); | |
return AttachmentFile( | |
hash: BytesUtil.sha256(bytes), | |
name: name, | |
extension: extension, | |
type: type, | |
bytes: bytes, | |
attachedAt: attachedAt, | |
); | |
}) | |
.where( | |
(f) => | |
f.name.isNotEmpty && | |
f.extension.isNotEmpty && | |
f.bytes.isNotEmpty && | |
f.size > 0 && | |
f.size <= maxFileSize, | |
) | |
.toList(); | |
stopwatch.stop(); | |
if (_disposed || newFiles.isEmpty) return; | |
// All previous attachments that are not in the new list | |
// To avoid duplicates | |
final attachmentsMap = <String, AttachmentFile>{ | |
// Previous attachments | |
for (final f in _attachments.value) f.hash: f, | |
// New attachments | |
for (final f in newFiles) f.hash: f, | |
}; | |
var attachments = attachmentsMap.values.toList(growable: false) | |
..sort((a, b) => b.attachedAt.compareTo(a.attachedAt)); | |
if (_disposed) return; | |
if (attachments.length > maxFileCount) attachments = attachments.take(maxFileCount).toList(growable: false); | |
_attachments.value = attachments; | |
}, | |
(e, s) { | |
l.e('Error while picking file: $e', s); | |
if (_disposed) return; | |
// ignore: use_build_context_synchronously | |
ErrorUtil.displayErrorSnackBar(context, e, s); | |
}, | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment