mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
support gifs and allow videos to pick from the gallery
This commit is contained in:
parent
e1232f45b5
commit
9356a1fc70
7 changed files with 114 additions and 66 deletions
|
|
@ -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.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(() {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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.'),
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue