From a57a8051efd5bd3ae86e6775e09304463fff396e Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 20 May 2025 20:35:21 +0200 Subject: [PATCH] maybe fix for #180 --- lib/src/providers/api/api.dart | 95 ++++++++++++------- lib/src/providers/api/media_send.dart | 2 +- lib/src/providers/api/server_messages.dart | 2 +- lib/src/services/flame_service.dart | 2 +- lib/src/services/notification_service.dart | 2 +- .../views/chats/chat_item_details_view.dart | 24 ++++- .../chats/components/chat_list_entry.dart | 34 +++---- lib/src/views/chats/media_viewer_view.dart | 5 +- lib/src/views/chats/search_username_view.dart | 6 +- 9 files changed, 109 insertions(+), 63 deletions(-) diff --git a/lib/src/providers/api/api.dart b/lib/src/providers/api/api.dart index 9f3146d..85358e1 100644 --- a/lib/src/providers/api/api.dart +++ b/lib/src/providers/api/api.dart @@ -9,7 +9,6 @@ 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/json/userdata.dart'; -import 'package:twonly/src/model/protobuf/api/error.pb.dart'; import 'package:twonly/src/providers/api/api_utils.dart'; import 'package:twonly/src/providers/hive.dart'; import 'package:twonly/src/services/notification_service.dart'; @@ -29,9 +28,12 @@ Future tryTransmitMessages() async { Map failed = {}; - for (String key in retransmit.keys) { + // List> sortedList = retransmit.entries.toList() + // ..sort((a, b) => int.parse(a.key).compareTo(int.parse(b.key))); + + for (final element in retransmit.entries) { RetransmitMessage msg = - RetransmitMessage.fromJson(jsonDecode(retransmit[key])); + RetransmitMessage.fromJson(jsonDecode(element.value)); Result resp = await apiProvider.sendTextMessage( msg.userId, @@ -49,7 +51,7 @@ Future tryTransmitMessages() async { ); } } else { - failed[key] = retransmit[key]; + failed[element.key] = element.value; } } Box box = await getMediaStorage(); @@ -98,21 +100,49 @@ Future> getAllMessagesForRetransmitting() async { Map retransmit = {}; if (retransmitJson != null) { - retransmit = jsonDecode(retransmitJson); + try { + retransmit = jsonDecode(retransmitJson); + } catch (e) { + Logger("api.dart").shout("Could not decode the retransmit messages: $e"); + await box.delete("messages-to-retransmit"); + } } return retransmit; } +Future sendRetransmitMessage( + String stateId, RetransmitMessage msg) async { + Result resp = + await apiProvider.sendTextMessage(msg.userId, msg.bytes, msg.pushData); + + if (resp.isSuccess) { + { + var retransmit = await getAllMessagesForRetransmitting(); + retransmit.remove(stateId); + Box box = await getMediaStorage(); + box.put("messages-to-retransmit", jsonEncode(retransmit)); + } + if (msg.messageId != null) { + await twonlyDatabase.messagesDao.updateMessageByMessageId( + msg.messageId!, + MessagesCompanion(acknowledgeByServer: Value(true)), + ); + } + } + return resp; +} + // this functions ensures that the message is received by the server and in case of errors will try again later -Future encryptAndSendMessage( +Future<(String, RetransmitMessage)?> encryptMessage( int? messageId, int userId, MessageJson msg, {PushKind? pushKind}) async { - return await lockSendingMessages.protect(() async { + return await lockSendingMessages + .protect<(String, RetransmitMessage)?>(() async { Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId); if (bytes == null) { Logger("api.dart").shout("Error encryption message!"); - return Result.error(ErrorCode.InternalError); + return null; } String stateId = @@ -124,39 +154,36 @@ Future encryptAndSendMessage( pushData = await getPushData(userId, pushKind); } + RetransmitMessage encryptedMessage = RetransmitMessage( + messageId: messageId, + userId: userId, + bytes: bytes, + pushData: pushData, + ); + { var retransmit = await getAllMessagesForRetransmitting(); - retransmit[stateId] = jsonEncode(RetransmitMessage( - messageId: messageId, - userId: userId, - bytes: bytes, - pushData: pushData, - ).toJson()); + retransmit[stateId] = jsonEncode(encryptedMessage.toJson()); box.put("messages-to-retransmit", jsonEncode(retransmit)); } - Result resp = await apiProvider.sendTextMessage(userId, bytes, pushData); - - if (resp.isSuccess) { - { - var retransmit = await getAllMessagesForRetransmitting(); - retransmit.remove(stateId); - box.put("messages-to-retransmit", jsonEncode(retransmit)); - } - if (messageId != null) { - await twonlyDatabase.messagesDao.updateMessageByMessageId( - messageId, - MessagesCompanion(acknowledgeByServer: Value(true)), - ); - } - } - - return resp; + return (stateId, encryptedMessage); }); } +// encrypts and stores the message and then sends it in the background +Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg, + {PushKind? pushKind}) async { + (String, RetransmitMessage)? stateData = + await encryptMessage(messageId, userId, msg); + if (stateData != null) { + final (stateId, message) = stateData; + sendRetransmitMessage(stateId, message); + } +} + Future sendTextMessage( int target, TextMessageContent content, PushKind? pushKind) async { DateTime messageSendAt = DateTime.now(); @@ -184,13 +211,13 @@ Future sendTextMessage( timestamp: messageSendAt, ); - encryptAndSendMessage(messageId, target, msg, pushKind: pushKind); + await encryptAndSendMessageAsync(messageId, target, msg, pushKind: pushKind); } Future notifyContactAboutOpeningMessage( int fromUserId, List messageOtherIds) async { for (final messageOtherId in messageOtherIds) { - await encryptAndSendMessage( + await encryptAndSendMessageAsync( null, fromUserId, MessageJson( @@ -216,7 +243,7 @@ Future notifyContactsAboutProfileChange() async { if (contact.myAvatarCounter < user.avatarCounter!) { twonlyDatabase.contactsDao.updateContact(contact.userId, ContactsCompanion(myAvatarCounter: Value(user.avatarCounter!))); - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, contact.userId, MessageJson( diff --git a/lib/src/providers/api/media_send.dart b/lib/src/providers/api/media_send.dart index 08c789d..d43cefc 100644 --- a/lib/src/providers/api/media_send.dart +++ b/lib/src/providers/api/media_send.dart @@ -451,7 +451,7 @@ Future handleNotifyReceiver(MediaUpload media) async { ); // Ensures the retransmit of the message - await encryptAndSendMessage( + await encryptAndSendMessageAsync( messageId, message.contactId, MessageJson( diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index 9c09265..8383cd7 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -226,7 +226,7 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { } } - encryptAndSendMessage( + await encryptAndSendMessageAsync( message.messageId!, fromUserId, MessageJson( diff --git a/lib/src/services/flame_service.dart b/lib/src/services/flame_service.dart index a80a877..b69dce2 100644 --- a/lib/src/services/flame_service.dart +++ b/lib/src/services/flame_service.dart @@ -36,7 +36,7 @@ Future syncFlameCounters() async { // only sync when flame counter is higher than three days if (flameCounter < 1 && bestFriend.userId != contact.userId) continue; - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, contact.userId, my.MessageJson( diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart index 1840ec5..9100c99 100644 --- a/lib/src/services/notification_service.dart +++ b/lib/src/services/notification_service.dart @@ -124,7 +124,7 @@ Future setupNotificationWithUsers({bool force = false}) async { } Future sendNewPushKey(int userId, PushKeyMeta pushKey) async { - await encryptAndSendMessage( + await encryptAndSendMessageAsync( null, userId, my.MessageJson( diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index 9826d39..004a43a 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -89,6 +89,17 @@ class _ChatItemDetailsViewState extends State { Map> tmpEmojiReactionsToMessageId = {}; List openedMessageOtherIds = []; + + Map messageOtherMessageIdToMyMessageId = {}; + + /// there is probably a better way... + for (Message msg in msgs) { + if (msg.messageOtherId != null) { + messageOtherMessageIdToMyMessageId[msg.messageOtherId!] = + msg.messageId; + } + } + for (Message msg in msgs) { if (msg.kind == MessageKind.textMessage && msg.messageOtherId != null && @@ -96,8 +107,9 @@ class _ChatItemDetailsViewState extends State { openedMessageOtherIds.add(msg.messageOtherId!); } - int? responseId = - msg.responseToMessageId ?? msg.responseToOtherMessageId; + int? responseId = msg.responseToMessageId ?? + messageOtherMessageIdToMyMessageId[msg.responseToOtherMessageId]; + if (responseId != null) { bool added = false; MessageContent? content = @@ -243,9 +255,11 @@ class _ChatItemDetailsViewState extends State { children: [ Expanded( child: ListView.builder( - itemCount: messages.length, + itemCount: messages.length + 1, reverse: true, itemExtentBuilder: (index, dimensions) { + if (index == 0) return 10; // empty padding + index -= 1; double size = 44; if (messages[index].kind == MessageKind.textMessage) { MessageContent? content = MessageContent.fromJson( @@ -277,6 +291,10 @@ class _ChatItemDetailsViewState extends State { return size; }, itemBuilder: (context, i) { + if (i == 0) { + return Container(); // just a padding + } + i -= 1; return ChatListEntry( key: Key(messages[i].messageId.toString()), messages[i], diff --git a/lib/src/views/chats/components/chat_list_entry.dart b/lib/src/views/chats/components/chat_list_entry.dart index 0c02e17..5ebfba6 100644 --- a/lib/src/views/chats/components/chat_list_entry.dart +++ b/lib/src/views/chats/components/chat_list_entry.dart @@ -204,7 +204,7 @@ class ChatListEntry extends StatelessWidget { return; } if (await received.existsMediaFile(message.messageId, "png")) { - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, contact.userId, MessageJson( @@ -291,22 +291,22 @@ class ChatListEntry extends StatelessWidget { crossAxisAlignment: right ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - Stack( - alignment: right ? Alignment.centerRight : Alignment.centerLeft, - children: [ - SlidingResponse( - child: child, - onResponseTriggered: () { - onResponseTriggered(message); - }, - ), - Positioned( - bottom: 5, - left: 5, - right: 5, - child: getReactionRow(), - ), - ], + SlidingResponse( + child: Stack( + alignment: right ? Alignment.centerRight : Alignment.centerLeft, + children: [ + child, + Positioned( + bottom: 5, + left: 5, + right: 5, + child: getReactionRow(), + ), + ], + ), + onResponseTriggered: () { + onResponseTriggered(message); + }, ), getTextResponseColumns(context, !right) ], diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index e79c0e1..11e09d8 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -132,8 +132,8 @@ class _MediaViewerViewState extends State { Future loadCurrentMediaFile({bool showTwonly = false}) async { if (!isMounted) return; - await _noScreenshot.screenshotOff(); if (!context.mounted || allMediaFiles.isEmpty) return nextMediaOrExit(); + await _noScreenshot.screenshotOff(); setState(() { videoController = null; @@ -230,6 +230,7 @@ class _MediaViewerViewState extends State { }); } } + imageBytes = await getImageBytes(current.messageId); if ((imageBytes == null && !content.isVideo) || @@ -300,7 +301,7 @@ class _MediaViewerViewState extends State { allMediaFiles.first.messageId, MessagesCompanion(mediaStored: Value(true)), ); - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, widget.contact.userId, MessageJson( diff --git a/lib/src/views/chats/search_username_view.dart b/lib/src/views/chats/search_username_view.dart index f0f9363..fc0f504 100644 --- a/lib/src/views/chats/search_username_view.dart +++ b/lib/src/views/chats/search_username_view.dart @@ -95,7 +95,7 @@ class _SearchUsernameView extends State { if (await SignalHelper.addNewContact(res.value.userdata)) { // before notifying the other party, add await setupNotificationWithUsers(); - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, res.value.userdata.userId.toInt(), MessageJson( @@ -267,7 +267,7 @@ class _ContactsListViewState extends State { onPressed: () async { await twonlyDatabase.contactsDao .deleteContactByUserId(contact.userId); - encryptAndSendMessage( + await encryptAndSendMessageAsync( null, contact.userId, MessageJson( @@ -285,7 +285,7 @@ class _ContactsListViewState extends State { final update = ContactsCompanion(accepted: Value(true)); await twonlyDatabase.contactsDao .updateContact(contact.userId, update); - await encryptAndSendMessage( + await encryptAndSendMessageAsync( null, contact.userId, MessageJson(