1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-22 21:09:21 +00:00
comunicmobile/lib/ui/dialogs/pick_file_dialog.dart

216 lines
6.0 KiB
Dart

import 'package:comunic/models/api_request.dart';
import 'package:comunic/ui/dialogs/record_audio_dialog.dart';
import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
/// Pick file dialog
///
/// @author Pierre Hubert
enum _FileChoices {
PICK_IMAGE,
TAKE_PICTURE,
PICK_VIDEO,
TAKE_VIDEO,
RECORD_AUDIO,
PICK_OTHER_FILE,
}
typedef _CanEnable = bool Function(List<String>);
typedef _OnOptionSelected = void Function(_FileChoices);
class _PickFileOption {
final _FileChoices value;
final String label;
final IconData icon;
final _CanEnable canEnable;
const _PickFileOption({
@required this.value,
@required this.label,
@required this.icon,
@required this.canEnable,
}) : assert(value != null),
assert(label != null),
assert(icon != null),
assert(canEnable != null);
}
List<_PickFileOption> get _optionsList => [
// Image
_PickFileOption(
value: _FileChoices.PICK_IMAGE,
label: tr("Choose an image"),
icon: Icons.image,
canEnable: (l) => l.any(isImage)),
_PickFileOption(
value: _FileChoices.TAKE_PICTURE,
label: tr("Take a picture"),
icon: Icons.camera_alt,
canEnable: (l) => l.any(isImage)),
// Video
_PickFileOption(
value: _FileChoices.PICK_VIDEO,
label: tr("Choose a video"),
icon: Icons.video_library,
canEnable: (l) => l.any(isVideo)),
_PickFileOption(
value: _FileChoices.TAKE_VIDEO,
label: tr("Take a video"),
icon: Icons.videocam,
canEnable: (l) => l.any(isVideo)),
// Audio
_PickFileOption(
value: _FileChoices.RECORD_AUDIO,
label: tr("Record audio"),
icon: Icons.mic,
canEnable: (l) => l.any(isAudio)),
// Other
_PickFileOption(
value: _FileChoices.PICK_OTHER_FILE,
label: tr("Browse files"),
icon: Icons.folder_open,
canEnable: (l) =>
l.any((el) => !isImage(el) && !isVideo(el) && !isAudio(el))),
];
Future<BytesFile> showPickFileDialog({
@required BuildContext context,
int maxFileSize,
List<String> allowedMimeTypes,
int imageMaxWidth,
int imageMaxHeight,
}) async {
assert(allowedMimeTypes != null);
// Get the list of allowed extension
final allowedExtensions = <String>[];
for (var mime in allowedExtensions) {
final ext = extensionFromMime(mime);
if (ext != mime) allowedExtensions.add(ext);
}
// Display bottom sheet
final choice = await showModalBottomSheet(
context: context,
builder: (c) => BottomSheet(
onClosing: () {},
builder: (c) => _BottomSheetPickOption(
options: _optionsList
.where((element) => element.canEnable(allowedMimeTypes))
.toList(),
onOptionSelected: (v) => Navigator.pop(c, v),
),
));
if (choice == null) return null;
BytesFile file;
switch (choice) {
// Pick an image
case _FileChoices.PICK_IMAGE:
case _FileChoices.TAKE_PICTURE:
final image = await ImagePicker().getImage(
source: choice == _FileChoices.PICK_IMAGE
? ImageSource.gallery
: ImageSource.camera,
maxWidth: imageMaxWidth.toDouble(),
maxHeight: imageMaxHeight.toDouble(),
);
if (image == null) return null;
file = BytesFile(image.path.split("/").last, await image.readAsBytes());
break;
// Pick an video
case _FileChoices.PICK_VIDEO:
case _FileChoices.TAKE_VIDEO:
final image = await ImagePicker().getVideo(
source: choice == _FileChoices.PICK_VIDEO
? ImageSource.gallery
: ImageSource.camera,
);
if (image == null) return null;
file = BytesFile(image.path.split("/").last, await image.readAsBytes());
break;
// Record audio file
case _FileChoices.RECORD_AUDIO:
final bytes = await showRecordAudioDialog(context);
if (bytes == null) return null;
file = BytesFile("record.mp3", bytes);
break;
// Pick other files
case _FileChoices.PICK_OTHER_FILE:
final pickedFile = await FilePicker.platform.pickFiles(
type: FileType.any,
allowedExtensions: allowedExtensions,
allowMultiple: false,
withData: true,
);
if (pickedFile == null || pickedFile.files.length == 0) return null;
file = BytesFile(pickedFile.files[0].name, pickedFile.files[0].bytes);
break;
}
if (file == null) return null;
// Check file size
if (maxFileSize != null && file.bytes.length > maxFileSize) {
showSimpleSnack(
context,
tr("This file could not be sent: it is too big! (Max allowed size: %1%)",
args: {"1": filesize(file.bytes.length)}));
return null;
}
return file;
}
class _BottomSheetPickOption extends StatelessWidget {
final List<_PickFileOption> options;
final _OnOptionSelected onOptionSelected;
const _BottomSheetPickOption(
{Key key, @required this.options, @required this.onOptionSelected})
: assert(options != null),
assert(onOptionSelected != null),
super(key: key);
@override
Widget build(BuildContext context) => Container(
height: 255,
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: ListView.builder(
itemCount: options.length,
itemBuilder: (c, i) => ListTile(
leading: Icon(options[i].icon),
title: Text(options[i].label),
onTap: () => onOptionSelected(options[i].value),
),
),
),
),
);
}