first commit
This commit is contained in:
378
lib/flutterlib/upload_data.dart
Normal file
378
lib/flutterlib/upload_data.dart
Normal file
@@ -0,0 +1,378 @@
|
||||
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_theme.dart';
|
||||
import 'flutter_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 {
|
||||
final 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: 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 != null ? storageFolderPath : null,
|
||||
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: EdgeInsetsDirectional.only(end: 10.0),
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: Theme.of(context).brightness == Brightness.dark
|
||||
? AlwaysStoppedAnimation<Color>(
|
||||
FlutterTheme.of(context).accent4)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
Text(message),
|
||||
],
|
||||
),
|
||||
duration: showLoading ? Duration(days: 1) : Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String? _removeTrailingSlash(String? path) => path != null && path.endsWith('/')
|
||||
? path.substring(0, path.length - 1)
|
||||
: path;
|
||||
Reference in New Issue
Block a user