From 59528bf08255c75de24b3c8c29c5214bb7c451d8 Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 11 Jul 2025 01:16:02 +0200 Subject: [PATCH] fixing compression issue --- lib/src/services/api.service.dart | 2 +- lib/src/services/api/media_download.dart | 2 +- lib/src/services/api/media_upload.dart | 34 +++++- lib/src/services/thumbnail.service.dart | 9 +- lib/src/views/chats/chat_list.view.dart | 1 - lib/src/views/chats/chat_messages.view.dart | 5 +- .../chat_media_entry.dart | 8 +- .../chat_message_entry.dart | 97 ++++++++-------- .../in_chat_media_viewer.dart | 109 ++++++------------ lib/src/views/memories/memories.view.dart | 8 +- .../memories/memories_item_thumbnail.dart | 8 +- 11 files changed, 140 insertions(+), 143 deletions(-) diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 2030357..9d9c90a 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -45,7 +45,7 @@ final lockRetransStore = Mutex(); /// It handles errors and does automatically tries to reconnect on /// errors or network changes. class ApiService { - final String apiHost = (kDebugMode) ? "192.168.178.89:3030" : "api.twonly.eu"; + final String apiHost = (kDebugMode) ? "10.99.0.140:3030" : "api.twonly.eu"; final String apiSecure = (kDebugMode) ? "" : "s"; bool appIsOutdated = false; diff --git a/lib/src/services/api/media_download.dart b/lib/src/services/api/media_download.dart index bcade48..0ba9174 100644 --- a/lib/src/services/api/media_download.dart +++ b/lib/src/services/api/media_download.dart @@ -299,7 +299,7 @@ Future getVideoPath(int mediaId) async { Future readMediaFile(int mediaId, String type) async { String basePath = await getMediaFilePath(mediaId, "received"); File file = File("$basePath.$type"); - Log.info("Reading: ${file}"); + Log.info("Reading: $file"); if (!await file.exists()) { return null; } diff --git a/lib/src/services/api/media_upload.dart b/lib/src/services/api/media_upload.dart index e16fdcd..d7a740f 100644 --- a/lib/src/services/api/media_upload.dart +++ b/lib/src/services/api/media_upload.dart @@ -200,17 +200,25 @@ Future addVideoToUpload(int mediaUploadId, File videoFilePath) async { Future addOrModifyImageToUpload( int mediaUploadId, Uint8List imageBytes) async { Uint8List imageBytesCompressed; + + Stopwatch stopwatch = Stopwatch(); + stopwatch.start(); + + Log.info("Raw images size in bytes: ${imageBytes.length}"); + try { imageBytesCompressed = await FlutterImageCompress.compressWithList( - format: CompressFormat.png, + format: CompressFormat.webp, + // minHeight: 0, + // minWidth: 0, imageBytes, quality: 90, ); - if (imageBytesCompressed.length >= 2 * 1000 * 1000) { + if (imageBytesCompressed.length >= 1 * 1000 * 1000) { // if the media file is over 2MB compress it with 60% imageBytesCompressed = await FlutterImageCompress.compressWithList( - format: CompressFormat.png, + format: CompressFormat.webp, imageBytes, quality: 60, ); @@ -223,6 +231,26 @@ Future addOrModifyImageToUpload( imageBytesCompressed = imageBytes; } + stopwatch.stop(); + + Log.info( + 'Compression the image took: ${stopwatch.elapsedMilliseconds} milliseconds'); + Log.info("Raw images size in bytes: ${imageBytesCompressed.length}"); + + // stopwatch.reset(); + // stopwatch.start(); + + // // var helper = MediaUploadHelper(); + // try { + // final webpBytes = + // await convertAndCompressImage(pngRawImageBytes: imageBytes); + // Log.info( + // 'Compression the image in rust took: ${stopwatch.elapsedMilliseconds} milliseconds'); + // Log.info("Raw images size in bytes using webp: ${webpBytes.length}"); + // } catch (e) { + // Log.error("$e"); + // } + /// in case the media file was already encrypted of even uploaded /// remove the data so it will be done again. await twonlyDB.mediaUploadsDao.updateMediaUpload( diff --git a/lib/src/services/thumbnail.service.dart b/lib/src/services/thumbnail.service.dart index 227b20e..4b989b1 100644 --- a/lib/src/services/thumbnail.service.dart +++ b/lib/src/services/thumbnail.service.dart @@ -75,14 +75,13 @@ Future createThumbnailsForVideo(File file) async { } try { - String? thumbnailFile = await VideoThumbnail.thumbnailFile( + await VideoThumbnail.thumbnailFile( video: file.path, imageFormat: ImageFormat.PNG, + thumbnailPath: getThumbnailPath(file).path, maxWidth: 450, quality: 75, ); - - File(thumbnailFile!).rename(getThumbnailPath(file).path); } catch (e) { Log.error("Could not create the video thumbnail: $e"); } @@ -92,6 +91,10 @@ File getThumbnailPath(File file) { String originalFileName = file.uri.pathSegments.last; String fileNameWithoutExtension = originalFileName.split('.').first; String fileExtension = originalFileName.split('.').last; + if (fileExtension == "mp4") { + fileExtension = "png"; + } String newFileName = '$fileNameWithoutExtension.thumbnail.$fileExtension'; + Directory(file.parent.path).createSync(); return File(join(file.parent.path, newFileName)); } diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index 46e0389..400b250 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -54,7 +54,6 @@ class _ChatListViewState extends State { _contacts = contacts.where((x) => !x.pinned).toList(); _pinnedContacts = contacts.where((x) => x.pinned).toList(); }); - ; }); tutorial = Timer(Duration(seconds: 1), () async { diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index be5eaa3..bbbc1e2 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -170,9 +170,8 @@ class _ChatMessagesViewState extends State { .reversed .toList(); final items = await MemoryItem.convertFromMessages(filteredMediaFiles); - setState(() { - galleryItems = items.values.toList(); - }); + galleryItems = items.values.toList(); + setState(() {}); }); } diff --git a/lib/src/views/chats/chat_messages_components/chat_media_entry.dart b/lib/src/views/chats/chat_messages_components/chat_media_entry.dart index 95e9477..e9ec71c 100644 --- a/lib/src/views/chats/chat_messages_components/chat_media_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_media_entry.dart @@ -48,9 +48,11 @@ class _ChatMediaEntryState extends State { return; } if (await received.existsMediaFile(widget.message.messageId, "png")) { - setState(() { - canBeReopened = true; - }); + if (mounted) { + setState(() { + canBeReopened = true; + }); + } Future.delayed(Duration(seconds: 1), () { if (!mounted) return; showReopenMediaFilesTutorial(context, reopenMediaFile); diff --git a/lib/src/views/chats/chat_messages_components/chat_message_entry.dart b/lib/src/views/chats/chat_messages_components/chat_message_entry.dart index 91d22d6..a28f955 100644 --- a/lib/src/views/chats/chat_messages_components/chat_message_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_message_entry.dart @@ -52,59 +52,58 @@ class _ChatListEntryState extends State { if (content == null) return Container(); bool right = widget.message.messageOtherId == null; - return Hero( - tag: "${widget.message.mediaUploadId ?? widget.message.messageId}", + return Container( + // tag: "${widget.message.mediaUploadId ?? widget.message.messageId}", child: Align( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: widget.lastMessageFromSameUser - ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) - : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), - child: Column( - mainAxisAlignment: - right ? MainAxisAlignment.end : MainAxisAlignment.start, - crossAxisAlignment: - right ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - MessageActions( - message: widget.message, - child: Stack( - alignment: - right ? Alignment.centerRight : Alignment.centerLeft, - children: [ - (textMessage != null) - ? ChatTextEntry( - message: widget.message, - text: textMessage!, - ) - : ChatMediaEntry( - message: widget.message, - contact: widget.contact, - galleryItems: widget.galleryItems, - content: content!, - ), - Positioned( - bottom: 5, - left: 5, - right: 5, - child: ReactionRow( - otherReactions: widget.otherReactions, + alignment: right ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: widget.lastMessageFromSameUser + ? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10) + : EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10), + child: Column( + mainAxisAlignment: + right ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: + right ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + MessageActions( + message: widget.message, + child: Stack( + alignment: right ? Alignment.centerRight : Alignment.centerLeft, + children: [ + (textMessage != null) + ? ChatTextEntry( message: widget.message, + text: textMessage!, + ) + : ChatMediaEntry( + message: widget.message, + contact: widget.contact, + galleryItems: widget.galleryItems, + content: content!, ), - ), - ], + Positioned( + bottom: 5, + left: 5, + right: 5, + child: ReactionRow( + otherReactions: widget.otherReactions, + message: widget.message, + ), ), - onResponseTriggered: () { - widget.onResponseTriggered(widget.message); - }, - ), - ChatTextResponseColumns( - textReactions: widget.textReactions, - right: right, - ) - ], + ], + ), + onResponseTriggered: () { + widget.onResponseTriggered(widget.message); + }, ), - ), - )); + ChatTextResponseColumns( + textReactions: widget.textReactions, + right: right, + ) + ], + ), + ), + )); } } diff --git a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart index 47c8d74..6fb6bec 100644 --- a/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart +++ b/lib/src/views/chats/chat_messages_components/in_chat_media_viewer.dart @@ -1,17 +1,11 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/services/api/media_upload.dart' as send; -import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/views/components/message_send_state_icon.dart'; import 'package:twonly/src/database/twonly_database.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; -import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/model/memory_item.model.dart'; +import 'package:twonly/src/views/memories/memories_item_thumbnail.dart'; import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; -import 'package:video_player/video_player.dart'; class InChatMediaViewer extends StatefulWidget { const InChatMediaViewer({ @@ -34,26 +28,48 @@ class InChatMediaViewer extends StatefulWidget { } class _InChatMediaViewerState extends State { - File? image; - File? video; - bool isMounted = true; bool mirrorVideo = false; - VideoPlayerController? videoController; + int? galleryItemIndex; StreamSubscription? messageStream; + Timer? _timer; @override void initState() { super.initState(); - initAsync(widget.message); + loadIndexAsync(); initStream(); } + Future loadIndexAsync() async { + if (!widget.message.mediaStored) return; + _timer = Timer.periodic(Duration(milliseconds: 10), (timer) { + /// when the galleryItems are updated this widget is not reloaded + /// so using this timer as a workaround + if (loadIndex()) { + timer.cancel(); + setState(() {}); + } + }); + } + + bool loadIndex() { + if (widget.message.mediaStored) { + final index = widget.galleryItems.indexWhere((x) => + x.id == (widget.message.mediaUploadId ?? widget.message.messageId)); + if (index != -1) { + galleryItemIndex = index; + return true; + } + } + return false; + } + @override void dispose() { super.dispose(); - isMounted = false; messageStream?.cancel(); - videoController?.dispose(); + _timer?.cancel(); + // videoController?.dispose(); } Future initStream() async { @@ -70,60 +86,20 @@ class _InChatMediaViewerState extends State { if (updated != null) { if (updated.mediaStored) { messageStream?.cancel(); - initAsync(updated); + loadIndexAsync(); } } }); } - Future initAsync(Message message) async { - if (!message.mediaStored) return; - bool isSend = message.messageOtherId == null; - final basePath = await send.getMediaFilePath( - isSend ? message.mediaUploadId! : message.messageId, - isSend ? "send" : "received", - ); - if (!isMounted) return; - final videoPath = File("$basePath.mp4"); - final imagePath = File("$basePath.png"); - if (videoPath.existsSync() && message.contentJson != null) { - MessageContent? content = MessageContent.fromJson( - MessageKind.media, jsonDecode(message.contentJson!)); - if (content is MediaMessageContent) { - mirrorVideo = content.mirrorVideo; - } - videoController = VideoPlayerController.file( - videoPath, - videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), - ); - videoController?.initialize().then((_) { - videoController!.setVolume(0); - videoController!.play(); - videoController!.setLooping(true); - }); - - setState(() { - image = imagePath; - }); - } - if (imagePath.existsSync()) { - setState(() { - image = imagePath; - }); - } else { - Log.error("file not found: $imagePath"); - } - } - Future onTap() async { + if (galleryItemIndex == null) return; Navigator.push( context, MaterialPageRoute( builder: (context) => MemoriesPhotoSliderView( galleryItems: widget.galleryItems, - initialIndex: widget.galleryItems.indexWhere((x) => - x.id == - (widget.message.mediaUploadId ?? widget.message.messageId)), + initialIndex: galleryItemIndex!, scrollDirection: Axis.horizontal, ), ), @@ -132,7 +108,7 @@ class _InChatMediaViewerState extends State { @override Widget build(BuildContext context) { - if (image == null && video == null) { + if (galleryItemIndex == null) { return Container( constraints: BoxConstraints( minHeight: 39, @@ -164,20 +140,9 @@ class _InChatMediaViewerState extends State { color: Colors.transparent, borderRadius: BorderRadius.circular(12.0), ), - child: GestureDetector( - onTap: ((image == null && videoController == null)) ? null : onTap, - child: Stack( - children: [ - if (image != null) Image.file(image!), - if (videoController != null) - Positioned.fill( - child: Transform.flip( - flipX: mirrorVideo, - child: VideoPlayer(videoController!), - ), - ), - ], - ), + child: MemoriesItemThumbnail( + galleryItem: widget.galleryItems[galleryItemIndex!], + onTap: onTap, ), ); } diff --git a/lib/src/views/memories/memories.view.dart b/lib/src/views/memories/memories.view.dart index f403858..03fe6e3 100644 --- a/lib/src/views/memories/memories.view.dart +++ b/lib/src/views/memories/memories.view.dart @@ -56,10 +56,10 @@ class MemoriesViewState extends State { } if (fileName.contains(".png")) { imagePath = file; - thumbnailFile = getThumbnailPath(imagePath); - if (!await thumbnailFile.exists()) { - await createThumbnailsForImage(imagePath); - } + thumbnailFile = file; + // if (!await thumbnailFile.exists()) { + // await createThumbnailsForImage(imagePath); + // } } else if (fileName.contains(".mp4")) { videoPath = file; thumbnailFile = getThumbnailPath(videoPath); diff --git a/lib/src/views/memories/memories_item_thumbnail.dart b/lib/src/views/memories/memories_item_thumbnail.dart index 6e948fd..9c8f0ea 100644 --- a/lib/src/views/memories/memories_item_thumbnail.dart +++ b/lib/src/views/memories/memories_item_thumbnail.dart @@ -44,9 +44,11 @@ class _MemoriesItemThumbnailState extends State { children: [ Image.file(widget.galleryItem.thumbnailPath), if (widget.galleryItem.videoPath != null) - Center( - child: FaIcon(FontAwesomeIcons.circlePlay), - ) + Positioned.fill( + child: Center( + child: FaIcon(FontAwesomeIcons.circlePlay), + ), + ), ], ), ),