mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
fixing compression issue
This commit is contained in:
parent
e9502c7ce9
commit
59528bf082
11 changed files with 140 additions and 143 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ Future<File?> getVideoPath(int mediaId) async {
|
|||
Future<Uint8List?> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,17 +200,25 @@ Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
|
|||
Future<Uint8List> 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<Uint8List> 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(
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
_contacts = contacts.where((x) => !x.pinned).toList();
|
||||
_pinnedContacts = contacts.where((x) => x.pinned).toList();
|
||||
});
|
||||
;
|
||||
});
|
||||
|
||||
tutorial = Timer(Duration(seconds: 1), () async {
|
||||
|
|
|
|||
|
|
@ -170,9 +170,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
.reversed
|
||||
.toList();
|
||||
final items = await MemoryItem.convertFromMessages(filteredMediaFiles);
|
||||
setState(() {
|
||||
galleryItems = items.values.toList();
|
||||
});
|
||||
galleryItems = items.values.toList();
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -52,59 +52,58 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<InChatMediaViewer> {
|
||||
File? image;
|
||||
File? video;
|
||||
bool isMounted = true;
|
||||
bool mirrorVideo = false;
|
||||
VideoPlayerController? videoController;
|
||||
int? galleryItemIndex;
|
||||
StreamSubscription<Message?>? 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<InChatMediaViewer> {
|
|||
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<InChatMediaViewer> {
|
|||
|
||||
@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<InChatMediaViewer> {
|
|||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ class _MemoriesItemThumbnailState extends State<MemoriesItemThumbnail> {
|
|||
children: [
|
||||
Image.file(widget.galleryItem.thumbnailPath),
|
||||
if (widget.galleryItem.videoPath != null)
|
||||
Center(
|
||||
child: FaIcon(FontAwesomeIcons.circlePlay),
|
||||
)
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: FaIcon(FontAwesomeIcons.circlePlay),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue