show real reply text instead of fallback

This commit is contained in:
kosyak 2024-01-20 00:56:22 +01:00
parent 869f92169d
commit 366e5aa389
8 changed files with 286 additions and 12 deletions

View file

@ -780,6 +780,45 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return unread;
}
@Nullable
public Message getMessageWithAnyMatchingId(String uuid) {
if (uuid == null) {
return null;
}
synchronized (this.messages) {
for (int i = 0; i < messages.size(); ++i) {
if (uuid.equals(messages.get(i).getServerMsgId())) {
return messages.get(i);
}
if (uuid.equals(messages.get(i).getRemoteMsgId())) {
return messages.get(i);
}
if (uuid.equals(messages.get(i).getUuid())) {
return messages.get(i);
}
}
for (int i = 0; i < historyPartMessages.size(); ++i) {
if (uuid.equals(historyPartMessages.get(i).getServerMsgId())) {
return historyPartMessages.get(i);
}
if (uuid.equals(historyPartMessages.get(i).getRemoteMsgId())) {
return historyPartMessages.get(i);
}
if (uuid.equals(historyPartMessages.get(i).getUuid())) {
return historyPartMessages.get(i);
}
}
}
return null;
}
public Message getLatestMessage() {
synchronized (this.messages) {
if (this.messages.size() == 0) {
@ -1190,12 +1229,14 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (!message.isPrivateMessage()) {
synchronized (this.messages) {
this.messages.add(message);
actualizeReplyMessages(this.messages, List.of(message));
}
}
} else {
if (message.isPrivateMessage() && Objects.equals(res1, res2)) {
synchronized (this.messages) {
this.messages.add(message);
actualizeReplyMessages(this.messages, List.of(message));
}
}
}
@ -1217,21 +1258,26 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (!message.isPrivateMessage()) {
synchronized (this.messages) {
properListToAdd.add(Math.min(offset, properListToAdd.size()), message);
actualizeReplyMessages(properListToAdd, List.of(message));
}
}
} else {
if (message.isPrivateMessage() && Objects.equals(res1, res2)) {
synchronized (this.messages) {
properListToAdd.add(Math.min(offset, properListToAdd.size()), message);
actualizeReplyMessages(properListToAdd, List.of(message));
}
}
}
synchronized (this.messages) {
if (!historyPartMessages.isEmpty() && hasDuplicateMessage(historyPartMessages.get(historyPartMessages.size() - 1))) {
messages.addAll(0, historyPartMessages);
actualizeReplyMessages(messages, List.of(message));
jumpToLatest();
}
}
}
public void addAll(int index, List<Message> messages, boolean fromPagination) {
if (messages.isEmpty()) return;
@ -1279,10 +1325,43 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} else {
properListToAdd.addAll(index, newM);
}
actualizeReplyMessages(properListToAdd, messages);
}
account.getPgpDecryptionService().decrypt(newM);
}
private void actualizeReplyMessages(List<Message> mainList, List<Message> messages) {
for (Message m : mainList) {
if (m.isReplyRestoredFromDb()) {
Element reply = m.getReply();
if (reply == null) {
continue;
}
String replyId = reply.getAttribute("id");
for (Message rep : messages) {
if (replyId.equals(rep.getServerMsgId())) {
m.setReplyMessage(rep, false);
break;
}
if (replyId.equals(rep.getRemoteMsgId())) {
m.setReplyMessage(rep, false);
break;
}
if (replyId.equals(rep.getUuid())) {
m.setReplyMessage(rep, false);
break;
}
}
}
}
}
public void expireOldMessages(long timestamp) {
synchronized (this.messages) {
for (ListIterator<Message> iterator = this.messages.listIterator(); iterator.hasNext(); ) {

View file

@ -4,7 +4,11 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.Color;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteSource;
@ -122,6 +126,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
protected Transferable transferable = null;
private Message mNextMessage = null;
private Message mPreviousMessage = null;
private Message replyMessage = null;
private boolean replyRestoredFromDb = false;
private String axolotlFingerprint = null;
private String errorMessage = null;
private Set<ReadByMarker> readByMarkers = new CopyOnWriteArraySet<>();
@ -411,6 +417,25 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return null;
}
@Nullable
public Message getReplyMessage() {
if (replyMessage != null && replyMessage.deleted) {
replyMessage = null;
replyRestoredFromDb = false;
}
return replyMessage;
}
public boolean isReplyRestoredFromDb() {
return replyRestoredFromDb;
}
public void setReplyMessage(Message replyMessage, boolean restoredFromDb) {
this.replyMessage = replyMessage;
this.replyRestoredFromDb = restoredFromDb;
}
public String getConversationUuid() {
return conversationUuid;
}
@ -815,8 +840,43 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public SpannableStringBuilder getBodyForDisplaying() {
if (replyMessage != null) {
try {
return new SpannableStringBuilder(MessageUtils.filterLtrRtl(">" + removeReplyFallback(replyMessage) + "\n" + removeReplyFallback(this)).trim());
} catch (IndexOutOfBoundsException e) {
return new SpannableStringBuilder(MessageUtils.filterLtrRtl(this.body).trim());
}
} else {
return new SpannableStringBuilder(MessageUtils.filterLtrRtl(this.body).trim());
}
}
public SpannableStringBuilder getBodyForReplyPreview() {
try {
return new SpannableStringBuilder(MessageUtils.filterLtrRtl(">" + removeReplyFallback(this)).trim());
} catch (IndexOutOfBoundsException e) {
return new SpannableStringBuilder(MessageUtils.filterLtrRtl(this.body).trim());
}
}
private StringBuilder removeReplyFallback(Message message) {
StringBuilder sb = new StringBuilder(message.body);
List<Element> replyFallback = message.getFallbacks("urn:xmpp:reply:0");
if (replyFallback.size() == 0) {
return sb;
}
Element bodyFallback = replyFallback.get(0).findChild("body");
int startCodePoint = Integer.parseInt(bodyFallback.getAttribute("start"));
int endCodePoint = Integer.parseInt(bodyFallback.getAttribute("end"));
if (startCodePoint < 0) return sb;
if (endCodePoint > sb.length()) return sb;
sb.replace(message.body.offsetByCodePoints(0, startCodePoint), message.body.offsetByCodePoints(0, endCodePoint), "");
return sb;
}
public boolean hasMeCommand() {
return this.body.trim().startsWith(ME_COMMAND);

View file

@ -758,6 +758,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
mXmppConnectionService.restoreReplyForMessage(conversation, message);
if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
conversation.prepend(query.getActualInThisQuery(), message);
} else {
@ -946,6 +947,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setServerMsgId(serverMsgId);
message.setTime(timestamp);
message.setBody(new RtpSessionStatus(false, 0).toString());
mXmppConnectionService.restoreReplyForMessage(conversation, message);
c.add(message);
mXmppConnectionService.databaseBackend.createMessage(message);
}
@ -988,6 +990,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setServerMsgId(serverMsgId);
message.setTime(timestamp);
message.setBody(new RtpSessionStatus(true, 0).toString());
mXmppConnectionService.restoreReplyForMessage(conversation, message);
if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
c.prepend(query.getActualInThisQuery(), message);
} else {

View file

@ -8,6 +8,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@ -840,6 +841,45 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return list;
}
public ArrayList<Message> getMessagesByIds(Conversation conversation, Set<String> ids) {
SQLiteDatabase db = this.getReadableDatabase();
List<String> parameters = new ArrayList<>();
for (String id : ids) {
parameters.add("?");
}
String parametersString = TextUtils.join(",", parameters);
String[] selectionArgs = new String[ids.size() * 3 + 1];
selectionArgs[0] = conversation.getUuid();
int ind = 1;
for (int i=0;i<3;i++) {
for (String id : ids) {
selectionArgs[ind] = id;
ind++;
}
}
Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ "=? and (" + Message.SERVER_MSG_ID + " in (" + parametersString + ") or " + Message.REMOTE_MSG_ID + " in (" + parametersString + ") or " + Message.UUID + " in (" + parametersString + "))", selectionArgs, null, null, Message.TIME_SENT
+ " DESC", String.valueOf(ids.size()));
CursorUtils.upgradeCursorWindowSize(cursor);
ArrayList<Message> list = new ArrayList<>();
while (cursor.moveToNext()) {
try {
Message m = Message.fromCursor(cursor, conversation);
list.add(m);
} catch (Exception e) {
Log.e(Config.LOGTAG, "unable to restore message");
}
}
return list;
}
public ArrayList<Message> getMessages(Conversation conversation, int limit, long timestamp, boolean isForward) {
ArrayList<Message> list = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();

View file

@ -1821,6 +1821,7 @@ public class XmppConnectionService extends Service {
}
} else {
if (addToConversation) {
restoreReplyForMessage(conversation, message);
conversation.add(message);
}
if (saveInDb) {
@ -2182,7 +2183,9 @@ public class XmppConnectionService extends Service {
}
private void restoreMessages(Conversation conversation) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE), false);
List<Message> messages = databaseBackend.getMessages(conversation, Config.PAGE_SIZE);
restoreRepliesForMessages(conversation, messages);
conversation.addAll(0, messages, false);
conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING));
conversation.findUnreadMessagesAndCalls(mNotificationService::pushFromBacklog);
}
@ -2310,6 +2313,7 @@ public class XmppConnectionService extends Service {
public void jumpToMessage(final Conversation conversation, final String uuid, JumpToMessageListener listener) {
final Runnable runnable = () -> {
List<Message> messages = databaseBackend.getMessagesNearUuid(conversation, 30, uuid);
restoreRepliesForMessages(conversation, messages);
if (messages != null && !messages.isEmpty()) {
conversation.jumpToHistoryPart(messages);
listener.onSuccess();
@ -2321,6 +2325,88 @@ public class XmppConnectionService extends Service {
mDatabaseReaderExecutor.execute(runnable);
}
public void restoreReplyForMessage(final Conversation conversation, Message message) {
restoreRepliesForMessages(conversation, List.of(message));
}
public void restoreRepliesForMessages(final Conversation conversation, List<Message> messages) {
Map<String, ArrayList<Message>> notFoundReplies = null;
for (Message m : messages) {
Element reply = m.getReply();
if (reply == null) {
continue;
}
String replyId = reply.getAttribute("id");
Message replyMessage = null;
for (Message rep : messages) {
if (replyId.equals(rep.getServerMsgId())) {
replyMessage = rep;
break;
}
if (replyId.equals(rep.getRemoteMsgId())) {
replyMessage = rep;
break;
}
if (replyId.equals(rep.getUuid())) {
replyMessage = rep;
break;
}
}
if (replyMessage == null) {
replyMessage = conversation.getMessageWithAnyMatchingId(replyId);
}
if (replyMessage != null) {
m.setReplyMessage(replyMessage, false);
} else {
if (notFoundReplies == null) {
notFoundReplies = new HashMap<>();
}
ArrayList<Message> list = notFoundReplies.computeIfAbsent(replyId, id -> new ArrayList<>());
list.add(m);
}
}
if (notFoundReplies != null) {
List<Message> restored = databaseBackend.getMessagesByIds(conversation, notFoundReplies.keySet());
for (String id : notFoundReplies.keySet()) {
for (Message m : restored) {
if (id.equals(m.getServerMsgId())) {
for (Message rm : notFoundReplies.get(id)) {
rm.setReplyMessage(m, true);
}
break;
}
if (id.equals(m.getRemoteMsgId())) {
for (Message rm : notFoundReplies.get(id)) {
rm.setReplyMessage(m, true);
}
break;
}
if (id.equals(m.getUuid())) {
for (Message rm : notFoundReplies.get(id)) {
rm.setReplyMessage(m, true);
}
break;
}
}
}
}
}
public void loadMoreMessages(final Conversation conversation, final long timestamp, boolean isForward, final OnMoreMessagesLoaded callback) {
if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) {
return;
@ -2339,11 +2425,14 @@ public class XmppConnectionService extends Service {
List<Message> messages = databaseBackend.getMessages(conversation, Config.PAGE_SIZE, timestamp, isForward);
if (messages.size() > 0) {
restoreRepliesForMessages(conversation, messages);
if (isForward) {
conversation.addAll(-1, messages, true);
} else {
conversation.addAll(0, messages, true);
}
callback.onMoreMessagesLoaded(messages.size(), conversation);
} else if (!isForward &&
conversation.hasMessagesLeftOnServer()
@ -2517,7 +2606,9 @@ public class XmppConnectionService extends Service {
final Conversation c = conversation;
final Runnable runnable = () -> {
if (loadMessagesFromDb) {
c.addAll(0, databaseBackend.getMessages(c, Config.PAGE_SIZE), false);
List<Message> messages = databaseBackend.getMessages(c, Config.PAGE_SIZE);
restoreRepliesForMessages(c, messages);
c.addAll(0, messages, false);
updateConversationUi();
c.messagesLoaded.set(true);
}

View file

@ -1554,9 +1554,9 @@ public class ConversationFragment extends XmppFragment
return;
}
SpannableStringBuilder body = message.getBodyForDisplaying();
SpannableStringBuilder body = message.getBodyForReplyPreview();
if (message.isFileOrImage() || message.isOOb()) body.append(" 🖼️");
messageListAdapter.handleTextQuotes(body, activity.isDarkTheme(), false, message);
messageListAdapter.handleTextQuotes(body, activity.isDarkTheme(), true, message);
binding.contextPreviewText.setText(body);
binding.contextPreviewAuthor.setText(message.getAvatarName());
binding.contextPreview.setVisibility(View.VISIBLE);

View file

@ -37,6 +37,7 @@ import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@ -462,7 +463,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
* Applies QuoteSpan to group of lines which starts with > or » characters.
* Appends likebreaks and applies DividerSpan to them to show a padding between quote and text.
*/
public boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground, boolean highlightReply, Message message) {
public void handleTextQuotes(SpannableStringBuilder body, boolean darkBackground, boolean highlightReply, Message message) {
boolean startsWithQuote = false;
int quoteDepth = 0;
while (QuoteHelper.bodyContainsQuoteStart(body) && quoteDepth <= Config.QUOTE_MAX_DEPTH) {
@ -546,7 +547,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
applyQuoteSpan(body, start, end, darkBackground, true, message);
}
return startsWithQuote;
}
private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) {
@ -586,10 +586,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
int start = body.getSpanStart(quote);
int end = body.getSpanEnd(quote);
body.removeSpan(quote);
applyQuoteSpan(body, start, end, darkBackground, message.getReply() != null, message);
applyQuoteSpan(body, start, end, darkBackground, message.getReplyMessage() != null, message);
}
boolean startsWithQuote = handleTextQuotes(body, darkBackground, message.getReply() != null, message);
handleTextQuotes(body, darkBackground, message.getReplyMessage() != null, message);
if (!message.isPrivateMessage()) {
if (hasMeCommand) {
body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),

View file

@ -508,6 +508,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
}
long size = parseLong(fileSize, 0);
message.setBody(Long.toString(size));
xmppConnectionService.restoreReplyForMessage(conversation, message);
conversation.add(message);
jingleConnectionManager.updateConversationUi(true);
this.file = this.xmppConnectionService.getFileBackend().getFile(message, false);