support gifs and allow videos to pick from the gallery

This commit is contained in:
otsmr 2025-11-02 23:01:53 +01:00
parent e1232f45b5
commit 9356a1fc70
7 changed files with 114 additions and 66 deletions

View file

@ -161,13 +161,12 @@ class MediaFileService {
return; return;
} }
switch (mediaFile.type) { switch (mediaFile.type) {
case MediaType.gif:
case MediaType.image: case MediaType.image:
// all images are already compress.. // all images are already compress..
break; break;
case MediaType.video: case MediaType.video:
await createThumbnailsForVideo(storedPath, thumbnailPath); await createThumbnailsForVideo(storedPath, thumbnailPath);
case MediaType.gif:
Log.error('Thumbnail for .gif is not implemented yet');
} }
} }
@ -183,8 +182,7 @@ class MediaFileService {
case MediaType.video: case MediaType.video:
await compressAndOverlayVideo(this); await compressAndOverlayVideo(this);
case MediaType.gif: case MediaType.gif:
originalPath.renameSync(tempPath.path); originalPath.copySync(tempPath.path);
Log.error('Compression for .gif is not implemented yet.');
} }
} }

View file

@ -45,7 +45,8 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
_imageSaving = true; _imageSaving = true;
}); });
if (widget.mediaService.mediaFile.type == MediaType.image) { if (widget.mediaService.mediaFile.type == MediaType.image ||
widget.mediaService.mediaFile.type == MediaType.gif) {
await widget.storeImageAsOriginal(); await widget.storeImageAsOriginal();
} }

View file

@ -285,9 +285,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<Uint8List?>? imageBytes, Future<Uint8List?>? imageBytes,
File? videoFilePath, { File? videoFilePath, {
bool sharedFromGallery = false, bool sharedFromGallery = false,
MediaType? mediaType,
}) async { }) async {
final type = mediaType ??
((videoFilePath != null) ? MediaType.video : MediaType.image);
final mediaFileService = await initializeMediaUpload( final mediaFileService = await initializeMediaUpload(
(videoFilePath != null) ? MediaType.video : MediaType.image, type,
gUser.defaultShowTime, gUser.defaultShowTime,
); );
if (!mounted) return true; if (!mounted) return true;
@ -377,14 +380,42 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
_sharePreviewIsShown = true; _sharePreviewIsShown = true;
}); });
final picker = ImagePicker(); final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery); final pickedFile = await picker.pickMedia();
if (pickedFile != null) { if (pickedFile != null) {
final imageFile = File(pickedFile.path); final imageExtensions = [
'.png',
'.jpg',
'.jpeg',
'.gif',
'.webp',
'.heic',
'.heif',
'.avif',
];
Log.info('Picket from gallery: ${pickedFile.path}');
File? videoFilePath;
Future<Uint8List>? imageBytes;
MediaType? mediaType;
final isImage =
imageExtensions.any((ext) => pickedFile.name.contains(ext));
if (isImage) {
if (pickedFile.name.contains('.gif')) {
mediaType = MediaType.gif;
}
imageBytes = pickedFile.readAsBytes();
} else {
videoFilePath = File(pickedFile.path);
}
await pushMediaEditor( await pushMediaEditor(
imageFile.readAsBytes(), imageBytes,
null, videoFilePath,
sharedFromGallery: true, sharedFromGallery: true,
mediaType: mediaType,
); );
} }
setState(() { setState(() {

View file

@ -55,6 +55,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
double widthRatio = 1; double widthRatio = 1;
double heightRatio = 1; double heightRatio = 1;
double pixelRatio = 1; double pixelRatio = 1;
Uint8List? imageBytes;
VideoPlayerController? videoController; VideoPlayerController? videoController;
ImageItem currentImage = ImageItem(); ImageItem currentImage = ImageItem();
ScreenshotController screenshotController = ScreenshotController(); ScreenshotController screenshotController = ScreenshotController();
@ -66,13 +67,16 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
void initState() { void initState() {
super.initState(); super.initState();
layers.add(FilterLayerData()); if (media.type != MediaType.gif) {
layers.add(FilterLayerData());
}
if (widget.sendToGroup != null) { if (widget.sendToGroup != null) {
selectedGroupIds.add(widget.sendToGroup!.groupId); selectedGroupIds.add(widget.sendToGroup!.groupId);
} }
if (widget.mediaFileService.mediaFile.type == MediaType.image) { if (widget.mediaFileService.mediaFile.type == MediaType.video ||
widget.mediaFileService.mediaFile.type == MediaType.gif) {
if (widget.imageBytesFuture != null) { if (widget.imageBytesFuture != null) {
loadImage(widget.imageBytesFuture!); loadImage(widget.imageBytesFuture!);
} else { } else {
@ -124,52 +128,55 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
return []; return [];
} }
return <Widget>[ return <Widget>[
ActionButton( if (media.type != MediaType.gif)
Icons.text_fields_rounded, ActionButton(
tooltipText: context.lang.addTextItem, Icons.text_fields_rounded,
onPressed: () async { tooltipText: context.lang.addTextItem,
layers = layers.where((x) => !x.isDeleted).toList(); onPressed: () async {
if (layers.any((x) => x.isEditing)) return; layers = layers.where((x) => !x.isDeleted).toList();
undoLayers.clear(); if (layers.any((x) => x.isEditing)) return;
removedLayers.clear(); undoLayers.clear();
layers.add( removedLayers.clear();
TextLayerData( layers.add(
textLayersBefore: layers.whereType<TextLayerData>().length, TextLayerData(
), textLayersBefore: layers.whereType<TextLayerData>().length,
); ),
setState(() {}); );
}, setState(() {});
), },
),
const SizedBox(height: 8), const SizedBox(height: 8),
ActionButton( if (media.type != MediaType.gif)
Icons.draw_rounded, ActionButton(
tooltipText: context.lang.addDrawing, Icons.draw_rounded,
onPressed: () async { tooltipText: context.lang.addDrawing,
undoLayers.clear(); onPressed: () async {
removedLayers.clear(); undoLayers.clear();
layers.add(DrawLayerData()); removedLayers.clear();
setState(() {}); layers.add(DrawLayerData());
}, setState(() {});
), },
),
const SizedBox(height: 8), const SizedBox(height: 8),
ActionButton( if (media.type != MediaType.gif)
Icons.add_reaction_outlined, ActionButton(
tooltipText: context.lang.addEmoji, Icons.add_reaction_outlined,
onPressed: () async { tooltipText: context.lang.addEmoji,
final layer = await showModalBottomSheet( onPressed: () async {
context: context, final layer = await showModalBottomSheet(
backgroundColor: Colors.black, context: context,
builder: (BuildContext context) { backgroundColor: Colors.black,
return const Emojis(); builder: (BuildContext context) {
}, return const Emojis();
) as Layer?; },
if (layer == null) return; ) as Layer?;
undoLayers.clear(); if (layer == null) return;
removedLayers.clear(); undoLayers.clear();
layers.add(layer); removedLayers.clear();
setState(() {}); layers.add(layer);
}, setState(() {});
), },
),
const SizedBox(height: 8), const SizedBox(height: 8),
NotificationBadge( NotificationBadge(
count: (media.type == MediaType.video) count: (media.type == MediaType.video)
@ -356,12 +363,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
if (mediaService.tempPath.existsSync()) { if (mediaService.tempPath.existsSync()) {
mediaService.tempPath.deleteSync(); mediaService.tempPath.deleteSync();
} }
final imageBytes = await getEditedImageBytes(); if (media.type == MediaType.gif) {
if (imageBytes == null) return false; mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
if (media.type == MediaType.image) {
mediaService.originalPath.writeAsBytesSync(imageBytes);
} else { } else {
mediaService.overlayImagePath.writeAsBytesSync(imageBytes); final imageBytes = await getEditedImageBytes();
if (imageBytes == null) return false;
if (media.type == MediaType.image || media.type == MediaType.gif) {
mediaService.originalPath.writeAsBytesSync(imageBytes);
} else if (media.type == MediaType.video) {
mediaService.overlayImagePath.writeAsBytesSync(imageBytes);
} else {
Log.error('MediaType not supported: ${media.type}');
}
} }
// In case the image was already stored, then rename the stored image. // In case the image was already stored, then rename the stored image.
@ -374,7 +387,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
} }
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async { Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
await currentImage.load(await imageBytesFuture); imageBytes = await imageBytesFuture;
await currentImage.load(imageBytes);
if (isDisposed) return; if (isDisposed) return;
if (!context.mounted) return; if (!context.mounted) return;

View file

@ -298,7 +298,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (gUser.storeMediaFilesInGallery) { if (gUser.storeMediaFilesInGallery) {
if (currentMedia!.mediaFile.type == MediaType.video) { if (currentMedia!.mediaFile.type == MediaType.video) {
await saveVideoToGallery(currentMedia!.storedPath.path); await saveVideoToGallery(currentMedia!.storedPath.path);
} else if (currentMedia!.mediaFile.type == MediaType.image) { } else if (currentMedia!.mediaFile.type == MediaType.image ||
currentMedia!.mediaFile.type == MediaType.gif) {
final imageBytes = await currentMedia!.storedPath.readAsBytes(); final imageBytes = await currentMedia!.storedPath.readAsBytes();
await saveImageToGallery(imageBytes); await saveImageToGallery(imageBytes);
} }
@ -466,7 +467,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
child: VideoPlayer(videoController!), child: VideoPlayer(videoController!),
), ),
if (currentMedia != null && if (currentMedia != null &&
currentMedia!.mediaFile.type == MediaType.image) currentMedia!.mediaFile.type == MediaType.image ||
currentMedia!.mediaFile.type == MediaType.gif)
Positioned.fill( Positioned.fill(
child: Image.file( child: Image.file(
currentMedia!.tempPath, currentMedia!.tempPath,

View file

@ -57,7 +57,8 @@ class _MemoriesItemThumbnailState extends State<MemoriesItemThumbnail> {
if (media.thumbnailPath.existsSync()) if (media.thumbnailPath.existsSync())
Image.file(media.thumbnailPath) Image.file(media.thumbnailPath)
else if (media.storedPath.existsSync() && else if (media.storedPath.existsSync() &&
media.mediaFile.type == MediaType.image) media.mediaFile.type == MediaType.image ||
media.mediaFile.type == MediaType.gif)
Image.file(media.storedPath) Image.file(media.storedPath)
else else
const Text('Media file removed.'), const Text('Media file removed.'),

View file

@ -75,7 +75,8 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
try { try {
if (item.mediaFile.type == MediaType.video) { if (item.mediaFile.type == MediaType.video) {
await saveVideoToGallery(item.storedPath.path); await saveVideoToGallery(item.storedPath.path);
} else if (item.mediaFile.type == MediaType.image) { } else if (item.mediaFile.type == MediaType.image ||
item.mediaFile.type == MediaType.gif) {
final imageBytes = await item.storedPath.readAsBytes(); final imageBytes = await item.storedPath.readAsBytes();
await saveImageToGallery(imageBytes); await saveImageToGallery(imageBytes);
} }