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

605 lines
19 KiB
Dart

import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:hub/flutter_flow/nav/nav.dart';
import 'package:hub/shared/utils/dialog_util.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime_type/mime_type.dart';
import 'flutter_flow_theme.dart';
import 'flutter_flow_util.dart';
const allowedFormats = {'image/png', 'image/jpeg'};
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>?> selectMediaWithSourceBottomSheetandFaceDetection({
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 faceDetector = FaceDetector(
options: FaceDetectorOptions(
enableContours: true,
enableLandmarks: true,
),
);
createUploadMediaListTile(
String label, MediaSource mediaSource, IconData icon) =>
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: FlutterFlowTheme.of(context).primaryText),
const SizedBox(width: 5),
Text(
label,
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: FlutterFlowTheme.of(context).primaryText,
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
],
),
tileColor: FlutterFlowTheme.of(context).primaryBackground,
dense: false,
onTap: () => context.pop(mediaSource),
);
final mediaSource = await showModalBottomSheet<MediaSource>(
context: context,
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!kIsWeb) ...[
Container(
margin: const EdgeInsets.only(top: 10),
width: 40,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: ListTile(
title: Text(
FFLocalizations.of(context).getVariableText(
ptText: "Escolha uma das opções",
enText: "Choose one of the options"),
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: FlutterFlowTheme.of(context)
.primaryText
.withOpacity(0.65),
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
tileColor: FlutterFlowTheme.of(context).primaryBackground,
dense: true,
),
),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (allowPhoto && allowVideo) ...[
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria (Foto)",
enText: "Gallery (Photo)"),
MediaSource.photoGallery,
Icons.camera),
),
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria (Video)",
enText: "Gallery (Video)"),
MediaSource.videoGallery,
Icons.videocam),
)
] else if (allowPhoto)
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria", enText: "Gallery"),
MediaSource.photoGallery,
Icons.photo))
else
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria", enText: "Gallery"),
MediaSource.videoGallery,
Icons.videocam),
),
if (!kIsWeb) ...[
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Camera", enText: "Camera"),
MediaSource.camera,
Icons.camera_alt),
)
],
],
),
const SizedBox(height: 15),
],
);
});
if (mediaSource == null) {
return null;
}
final selectedMedia = await selectMedia(
storageFolderPath: storageFolderPath,
maxWidth: maxWidth,
maxHeight: maxHeight,
imageQuality: imageQuality,
isVideo: mediaSource == MediaSource.videoGallery ||
(mediaSource == MediaSource.camera && allowVideo && !allowPhoto),
mediaSource: mediaSource,
includeDimensions: includeDimensions,
includeBlurHash: includeBlurHash,
);
if (selectedMedia == null || selectedMedia.isEmpty) {
return null;
}
// Verifique se há faces na imagem
for (final media in selectedMedia) {
final inputImage = InputImage.fromFilePath(media.filePath!);
final faces = await faceDetector.processImage(inputImage);
if (faces.isEmpty) {
final String message = FFLocalizations.of(context).getVariableText(
ptText: "Nenhuma face detectada na imagem",
enText: "No face detected in the image");
DialogUtil.error(context, message);
return null;
}
if (faces.length > 1) {
final String message = FFLocalizations.of(context).getVariableText(
ptText: "Mais de uma face detectada na imagem",
enText: "More than one face detected in the image");
DialogUtil.error(context, message);
return null;
}
}
return selectedMedia;
}
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, IconData icon) =>
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: FlutterFlowTheme.of(context).primaryText),
const SizedBox(width: 5),
Text(
label,
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: FlutterFlowTheme.of(context).primaryText,
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
],
),
tileColor: FlutterFlowTheme.of(context).primaryBackground,
dense: false,
onTap: () => context.pop(mediaSource),
// Navigator.pop(context,mediaSource,),
);
final mediaSource = await showModalBottomSheet<MediaSource>(
context: context,
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!kIsWeb) ...[
Container(
margin: const EdgeInsets.only(top: 10),
width: 40,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: ListTile(
title: Text(
FFLocalizations.of(context).getVariableText(
ptText: "Escolha uma das opções",
enText: "Choose one of the options"),
textAlign: TextAlign.center,
style: GoogleFonts.getFont(
pickerFontFamily,
color: FlutterFlowTheme.of(context)
.primaryText
.withOpacity(0.65),
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
tileColor: FlutterFlowTheme.of(context).primaryBackground,
dense: true,
),
),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (allowPhoto && allowVideo) ...[
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria (Foto)",
enText: "Gallery (Photo)"),
MediaSource.photoGallery,
Icons.camera),
),
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria (Video)",
enText: "Gallery (Video)"),
MediaSource.videoGallery,
Icons.videocam),
)
] else if (allowPhoto)
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria", enText: "Gallery"),
MediaSource.photoGallery,
Icons.photo))
else
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Galeria", enText: "Gallery"),
MediaSource.videoGallery,
Icons.videocam),
),
if (!kIsWeb) ...[
Expanded(
child: createUploadMediaListTile(
FFLocalizations.of(context).getVariableText(
ptText: "Camera", enText: "Camera"),
MediaSource.camera,
Icons.camera_alt),
)
],
],
),
const SizedBox(height: 15),
],
);
});
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;
//mock
final size = const Size(1920, 1080);
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).info)
: null,
),
),
Text(message, style: FlutterFlowTheme.of(context).bodyMedium),
],
),
backgroundColor: FlutterFlowTheme.of(context).primary,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
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;