flutter-freaccess-hub/lib/flutter_flow/upload_data.dart

377 lines
11 KiB
Dart

import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime_type/mime_type.dart';
import 'package:video_player/video_player.dart';
import 'flutter_flow_theme.dart';
import 'flutter_flow_util.dart';
const allowedFormats = {'image/png', 'image/jpeg', 'video/mp4', 'image/gif'};
class SelectedFile {
const SelectedFile({
this.storagePath = '',
this.filePath,
required this.bytes,
this.dimensions,
this.blurHash,
});
final String storagePath;
final String? filePath;
final Uint8List bytes;
final MediaDimensions? dimensions;
final String? blurHash;
}
class MediaDimensions {
const MediaDimensions({
this.height,
this.width,
});
final double? height;
final double? width;
}
enum MediaSource {
photoGallery,
videoGallery,
camera,
}
Future<List<SelectedFile>?> selectMediaWithSourceBottomSheet({
required BuildContext context,
String? storageFolderPath,
double? maxWidth,
double? maxHeight,
int? imageQuality,
required bool allowPhoto,
bool allowVideo = false,
String pickerFontFamily = 'Roboto',
Color textColor = const Color(0xFF111417),
Color backgroundColor = const Color(0xFFF5F5F5),
bool includeDimensions = false,
bool includeBlurHash = false,
}) async {
createUploadMediaListTile(String label, MediaSource mediaSource) => ListTile(
title: Text(
label,
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: textColor,
fontWeight: FontWeight.w600,
fontSize: 20,
),
),
tileColor: backgroundColor,
dense: false,
onTap: () => Navigator.pop(
context,
mediaSource,
),
);
final mediaSource = await showModalBottomSheet<MediaSource>(
context: context,
backgroundColor: backgroundColor,
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!kIsWeb) ...[
Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: ListTile(
title: Text(
'Choose Source',
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: textColor.withOpacity(0.65),
fontWeight: FontWeight.w500,
fontSize: 20,
),
),
tileColor: backgroundColor,
dense: false,
),
),
const Divider(),
],
if (allowPhoto && allowVideo) ...[
createUploadMediaListTile(
'Gallery (Photo)',
MediaSource.photoGallery,
),
const Divider(),
createUploadMediaListTile(
'Gallery (Video)',
MediaSource.videoGallery,
),
] else if (allowPhoto)
createUploadMediaListTile(
'Gallery',
MediaSource.photoGallery,
)
else
createUploadMediaListTile(
'Gallery',
MediaSource.videoGallery,
),
if (!kIsWeb) ...[
const Divider(),
createUploadMediaListTile('Camera', MediaSource.camera),
const Divider(),
],
const SizedBox(height: 10),
],
);
});
if (mediaSource == null) {
return null;
}
return selectMedia(
storageFolderPath: storageFolderPath,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
isVideo: mediaSource == MediaSource.videoGallery ||
(mediaSource == MediaSource.camera && allowVideo && !allowPhoto),
mediaSource: mediaSource,
includeDimensions: includeDimensions,
includeBlurHash: includeBlurHash,
);
}
Future<List<SelectedFile>?> selectMedia({
String? storageFolderPath,
double? maxWidth,
double? maxHeight,
int? imageQuality,
bool isVideo = false,
MediaSource mediaSource = MediaSource.camera,
bool multiImage = false,
bool includeDimensions = false,
bool includeBlurHash = false,
}) async {
final picker = ImagePicker();
if (multiImage) {
final pickedMediaFuture = picker.pickMultiImage(
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
);
final pickedMedia = await pickedMediaFuture;
if (pickedMedia.isEmpty) {
return null;
}
return Future.wait(pickedMedia.asMap().entries.map((e) async {
final index = e.key;
final media = e.value;
final mediaBytes = await media.readAsBytes();
final path = _getStoragePath(storageFolderPath, media.name, false, index);
final dimensions = includeDimensions
? isVideo
? _getVideoDimensions(media.path)
: _getImageDimensions(mediaBytes)
: null;
return SelectedFile(
storagePath: path,
filePath: media.path,
bytes: mediaBytes,
dimensions: await dimensions,
);
}));
}
final source = mediaSource == MediaSource.camera
? ImageSource.camera
: ImageSource.gallery;
final pickedMediaFuture = isVideo
? picker.pickVideo(source: source)
: picker.pickImage(
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
source: source,
);
final pickedMedia = await pickedMediaFuture;
final mediaBytes = await pickedMedia?.readAsBytes();
if (mediaBytes == null) {
return null;
}
final path = _getStoragePath(storageFolderPath, pickedMedia!.name, isVideo);
final dimensions = includeDimensions
? isVideo
? _getVideoDimensions(pickedMedia.path)
: _getImageDimensions(mediaBytes)
: null;
return [
SelectedFile(
storagePath: path,
filePath: pickedMedia.path,
bytes: mediaBytes,
dimensions: await dimensions,
),
];
}
bool validateFileFormat(String filePath, BuildContext context) {
if (allowedFormats.contains(mime(filePath))) {
return true;
}
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text('Invalid file format: ${mime(filePath)}'),
));
return false;
}
Future<SelectedFile?> selectFile({
String? storageFolderPath,
List<String>? allowedExtensions,
}) =>
selectFiles(
storageFolderPath: storageFolderPath,
allowedExtensions: allowedExtensions,
multiFile: false,
).then((value) => value?.first);
Future<List<SelectedFile>?> selectFiles({
String? storageFolderPath,
List<String>? allowedExtensions,
bool multiFile = false,
}) async {
final pickedFiles = await FilePicker.platform.pickFiles(
type: allowedExtensions != null ? FileType.custom : FileType.any,
allowedExtensions: allowedExtensions,
withData: true,
allowMultiple: multiFile,
);
if (pickedFiles == null || pickedFiles.files.isEmpty) {
return null;
}
if (multiFile) {
return Future.wait(pickedFiles.files.asMap().entries.map((e) async {
final index = e.key;
final file = e.value;
final storagePath =
_getStoragePath(storageFolderPath, file.name, false, index);
return SelectedFile(
storagePath: storagePath,
filePath: isWeb ? null : file.path,
bytes: file.bytes!,
);
}));
}
final file = pickedFiles.files.first;
if (file.bytes == null) {
return null;
}
final storagePath = _getStoragePath(storageFolderPath, file.name, false);
return [
SelectedFile(
storagePath: storagePath,
filePath: isWeb ? null : file.path,
bytes: file.bytes!,
)
];
}
List<SelectedFile> selectedFilesFromUploadedFiles(
List<FFUploadedFile> uploadedFiles, {
String? storageFolderPath,
bool isMultiData = false,
}) =>
uploadedFiles.asMap().entries.map(
(entry) {
final index = entry.key;
final file = entry.value;
return SelectedFile(
storagePath: _getStoragePath(
storageFolderPath,
file.name!,
false,
isMultiData ? index : null,
),
bytes: file.bytes!);
},
).toList();
Future<MediaDimensions> _getImageDimensions(Uint8List mediaBytes) async {
final image = await decodeImageFromList(mediaBytes);
return MediaDimensions(
width: image.width.toDouble(),
height: image.height.toDouble(),
);
}
Future<MediaDimensions> _getVideoDimensions(String path) async {
final VideoPlayerController videoPlayerController =
VideoPlayerController.asset(path);
await videoPlayerController.initialize();
final size = videoPlayerController.value.size;
return MediaDimensions(width: size.width, height: size.height);
}
String _getStoragePath(
String? pathPrefix,
String filePath,
bool isVideo, [
int? index,
]) {
pathPrefix = _removeTrailingSlash(pathPrefix);
final timestamp = DateTime.now().microsecondsSinceEpoch;
// Workaround fixed by https://github.com/flutter/plugins/pull/3685
// (not yet in stable).
final ext = isVideo ? 'mp4' : filePath.split('.').last;
final indexStr = index != null ? '_$index' : '';
return '$pathPrefix/$timestamp$indexStr.$ext';
}
String getSignatureStoragePath([String? pathPrefix]) {
pathPrefix = _removeTrailingSlash(pathPrefix);
final timestamp = DateTime.now().microsecondsSinceEpoch;
return '$pathPrefix/signature_$timestamp.png';
}
void showUploadMessage(
BuildContext context,
String message, {
bool showLoading = false,
}) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Row(
children: [
if (showLoading)
Padding(
padding: const EdgeInsetsDirectional.only(end: 10.0),
child: CircularProgressIndicator(
valueColor: Theme.of(context).brightness == Brightness.dark
? AlwaysStoppedAnimation<Color>(
FlutterFlowTheme.of(context).accent4)
: null,
),
),
Text(message),
],
),
duration: showLoading ? const Duration(days: 1) : const Duration(seconds: 4),
),
);
}
String? _removeTrailingSlash(String? path) => path != null && path.endsWith('/')
? path.substring(0, path.length - 1)
: path;