From c0b3a3ff0c32c8025174ebb92fbcf4a7fc67f497 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Feb 2016 23:15:04 +0100 Subject: basic support for XEP-0308: Last Message Correction. fixes #864 --- .../siacs/conversations/entities/Conversation.java | 20 ++++++ .../eu/siacs/conversations/entities/Message.java | 38 +++++++++-- .../conversations/generator/AbstractGenerator.java | 1 + .../conversations/generator/MessageGenerator.java | 3 + .../siacs/conversations/parser/MessageParser.java | 61 +++++++++++++---- .../conversations/persistance/DatabaseBackend.java | 14 +++- .../services/XmppConnectionService.java | 13 ++-- .../conversations/ui/ConversationFragment.java | 72 +++++++++++++++++---- .../eu/siacs/conversations/ui/EditMessage.java | 2 + .../conversations/ui/adapter/MessageAdapter.java | 15 ++++- src/main/res/drawable-hdpi/ic_lock_black_18dp.png | Bin 0 -> 368 bytes src/main/res/drawable-hdpi/ic_lock_white_18dp.png | Bin 0 -> 371 bytes .../res/drawable-hdpi/ic_mode_edit_black_18dp.png | Bin 0 -> 264 bytes .../res/drawable-hdpi/ic_mode_edit_white_18dp.png | Bin 0 -> 299 bytes src/main/res/drawable-hdpi/ic_secure_indicator.png | Bin 294 -> 0 bytes .../drawable-hdpi/ic_secure_indicator_white.png | Bin 322 -> 0 bytes src/main/res/drawable-mdpi/ic_lock_black_18dp.png | Bin 0 -> 293 bytes src/main/res/drawable-mdpi/ic_lock_white_18dp.png | Bin 0 -> 298 bytes .../res/drawable-mdpi/ic_mode_edit_black_18dp.png | Bin 0 -> 229 bytes .../res/drawable-mdpi/ic_mode_edit_white_18dp.png | Bin 0 -> 249 bytes src/main/res/drawable-mdpi/ic_secure_indicator.png | Bin 295 -> 0 bytes .../drawable-mdpi/ic_secure_indicator_white.png | Bin 306 -> 0 bytes src/main/res/drawable-xhdpi/ic_lock_black_18dp.png | Bin 0 -> 397 bytes src/main/res/drawable-xhdpi/ic_lock_white_18dp.png | Bin 0 -> 399 bytes .../res/drawable-xhdpi/ic_mode_edit_black_18dp.png | Bin 0 -> 291 bytes .../res/drawable-xhdpi/ic_mode_edit_white_18dp.png | Bin 0 -> 351 bytes .../res/drawable-xhdpi/ic_secure_indicator.png | Bin 410 -> 0 bytes .../drawable-xhdpi/ic_secure_indicator_white.png | Bin 434 -> 0 bytes .../res/drawable-xxhdpi/ic_lock_black_18dp.png | Bin 0 -> 559 bytes .../res/drawable-xxhdpi/ic_lock_white_18dp.png | Bin 0 -> 558 bytes .../drawable-xxhdpi/ic_mode_edit_black_18dp.png | Bin 0 -> 336 bytes .../drawable-xxhdpi/ic_mode_edit_white_18dp.png | Bin 0 -> 436 bytes .../res/drawable-xxhdpi/ic_secure_indicator.png | Bin 380 -> 0 bytes .../drawable-xxhdpi/ic_secure_indicator_white.png | Bin 441 -> 0 bytes .../res/drawable-xxxhdpi/ic_lock_black_18dp.png | Bin 0 -> 636 bytes .../res/drawable-xxxhdpi/ic_lock_white_18dp.png | Bin 0 -> 760 bytes .../drawable-xxxhdpi/ic_mode_edit_black_18dp.png | Bin 0 -> 366 bytes .../drawable-xxxhdpi/ic_mode_edit_white_18dp.png | Bin 0 -> 490 bytes src/main/res/layout/message_received.xml | 12 +++- src/main/res/layout/message_sent.xml | 12 +++- src/main/res/menu/message_context.xml | 4 ++ src/main/res/values/strings.xml | 2 + 42 files changed, 228 insertions(+), 41 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_lock_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_lock_white_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png create mode 100644 src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png delete mode 100644 src/main/res/drawable-hdpi/ic_secure_indicator.png delete mode 100644 src/main/res/drawable-hdpi/ic_secure_indicator_white.png create mode 100644 src/main/res/drawable-mdpi/ic_lock_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_lock_white_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png create mode 100644 src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png delete mode 100644 src/main/res/drawable-mdpi/ic_secure_indicator.png delete mode 100644 src/main/res/drawable-mdpi/ic_secure_indicator_white.png create mode 100644 src/main/res/drawable-xhdpi/ic_lock_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_lock_white_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png delete mode 100644 src/main/res/drawable-xhdpi/ic_secure_indicator.png delete mode 100644 src/main/res/drawable-xhdpi/ic_secure_indicator_white.png create mode 100644 src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png delete mode 100644 src/main/res/drawable-xxhdpi/ic_secure_indicator.png delete mode 100644 src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 53bd19a5..a0600d13 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -82,6 +82,7 @@ public class Conversation extends AbstractEntity implements Blockable { private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; private String mLastReceivedOtrMessageId = null; private String mFirstMamReference = null; + private Message correctingMessage; public boolean hasMessagesLeftOnServer() { return messagesLeftOnServer; @@ -226,6 +227,17 @@ public class Conversation extends AbstractEntity implements Blockable { return null; } + public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart) { + synchronized (this.messages) { + for(Message message : this.messages) { + if(id.equals(message.getRemoteMsgId()) && counterpart.equals(message.getCounterpart())) { + return message; + } + } + } + return null; + } + public Message findSentMessageWithUuid(String id) { synchronized (this.messages) { for (Message message : this.messages) { @@ -294,6 +306,14 @@ public class Conversation extends AbstractEntity implements Blockable { return getLongAttribute("last_clear_history", 0); } + public void setCorrectingMessage(Message correctingMessage) { + this.correctingMessage = correctingMessage; + } + + public Message getCorrectingMessage() { + return this.correctingMessage; + } + public interface OnMessageFound { void onMessageFound(final Message message); } diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index f37ae427..1f9212fd 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -52,6 +52,7 @@ public class Message extends AbstractEntity { public static final String STATUS = "status"; public static final String TYPE = "type"; public static final String CARBON = "carbon"; + public static final String EDITED = "edited"; public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; @@ -71,6 +72,7 @@ public class Message extends AbstractEntity { protected int status; protected int type; protected boolean carbon = false; + protected String edited = null; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -104,7 +106,8 @@ public class Message extends AbstractEntity { null, null, null, - true); + true, + null); this.conversation = conversation; } @@ -112,7 +115,8 @@ public class Message extends AbstractEntity { final Jid trueCounterpart, final String body, final long timeSent, final int encryption, final int status, final int type, final boolean carbon, final String remoteMsgId, final String relativeFilePath, - final String serverMsgId, final String fingerprint, final boolean read) { + final String serverMsgId, final String fingerprint, final boolean read, + final String edited) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -128,6 +132,7 @@ public class Message extends AbstractEntity { this.serverMsgId = serverMsgId; this.axolotlFingerprint = fingerprint; this.read = read; + this.edited = edited; } public static Message fromCursor(Cursor cursor) { @@ -162,12 +167,13 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), - cursor.getInt(cursor.getColumnIndex(CARBON))>0, + cursor.getInt(cursor.getColumnIndex(CARBON)) > 0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), cursor.getString(cursor.getColumnIndex(FINGERPRINT)), - cursor.getInt(cursor.getColumnIndex(READ)) > 0); + cursor.getInt(cursor.getColumnIndex(READ)) > 0, + cursor.getString(cursor.getColumnIndex(EDITED))); } public static Message createStatusMessage(Conversation conversation, String body) { @@ -211,7 +217,8 @@ public class Message extends AbstractEntity { values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); values.put(FINGERPRINT, axolotlFingerprint); - values.put(READ,read); + values.put(READ,read ? 1 : 0); + values.put(EDITED, edited); return values; } @@ -340,10 +347,22 @@ public class Message extends AbstractEntity { this.carbon = carbon; } + public void setEdited(String edited) { + this.edited = edited; + } + + public boolean edited() { + return this.edited != null; + } + public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } + public Jid getTrueCounterpart() { + return this.trueCounterpart; + } + public Transferable getTransferable() { return this.transferable; } @@ -421,6 +440,7 @@ public class Message extends AbstractEntity { this.getEncryption() == message.getEncryption() && this.getCounterpart() != null && this.getCounterpart().equals(message.getCounterpart()) && + this.edited() == message.edited() && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !GeoHelper.isGeoUri(message.getBody()) && !GeoHelper.isGeoUri(this.body) && @@ -510,6 +530,14 @@ public class Message extends AbstractEntity { } } + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getEditedId() { + return edited; + } + public enum Decision { MUST, SHOULD, diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index d5059605..d223ab84 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -31,6 +31,7 @@ public abstract class AbstractGenerator { "urn:xmpp:avatar:metadata+notify", "http://jabber.org/protocol/nick+notify", "urn:xmpp:ping", + "urn:xmpp:message-correct:0", "jabber:iq:version", "http://jabber.org/protocol/chatstates", AxolotlService.PEP_DEVICE_LIST+"+notify"}; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index b849f56f..0e7a8ce6 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -47,6 +47,9 @@ public class MessageGenerator extends AbstractGenerator { } packet.setFrom(account.getJid()); packet.setId(message.getUuid()); + if (message.edited()) { + packet.addChild("replace","urn:xmpp:message-correct:0").setAttribute("id",message.getEditedId()); + } return packet; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3bf48831..18edfdeb 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -297,6 +297,8 @@ public class MessageParser extends AbstractParser implements final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + final Element replaceElement = packet.findChild("replace","urn:xmpp:message-correct:0"); + final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id"); final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; @@ -390,6 +392,33 @@ public class MessageParser extends AbstractParser implements } else { updateLastseen(timestamp, account, packet.getFrom(), true); } + + if (replacementId != null) { + Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId, counterpart); + if (replacedMessage != null) { + final boolean fingerprintsMatch = replacedMessage.getAxolotlFingerprint() == null + || replacedMessage.getAxolotlFingerprint().equals(message.getAxolotlFingerprint()); + final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null + && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart()); + if (fingerprintsMatch && (trueCountersMatch || conversation.getMode() == Conversation.MODE_SINGLE)) { + Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'"); + replacedMessage.setBody(message.getBody()); + replacedMessage.setEdited(replacedMessage.getRemoteMsgId()); + replacedMessage.setRemoteMsgId(remoteMsgId); + if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) { + replacedMessage.markUnread(); + } + mXmppConnectionService.updateMessage(replacedMessage); + if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { + sendMessageReceipts(account, packet); + } + return; + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received message correction but verification didn't check out"); + } + } + } + boolean checkForDuplicates = query != null || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay")) || message.getType() == Message.TYPE_PRIVATE; @@ -420,20 +449,7 @@ public class MessageParser extends AbstractParser implements } if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { - ArrayList receiptsNamespaces = new ArrayList<>(); - if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { - receiptsNamespaces.add("urn:xmpp:chat-markers:0"); - } - if (packet.hasChild("request", "urn:xmpp:receipts")) { - receiptsNamespaces.add("urn:xmpp:receipts"); - } - if (receiptsNamespaces.size() > 0) { - MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, - packet, - receiptsNamespaces, - packet.getType()); - mXmppConnectionService.sendMessagePacket(account, receipt); - } + sendMessageReceipts(account, packet); } if (message.getStatus() == Message.STATUS_RECEIVED @@ -524,4 +540,21 @@ public class MessageParser extends AbstractParser implements contact.setPresenceName(nick); } } + + private void sendMessageReceipts(Account account, MessagePacket packet) { + ArrayList receiptsNamespaces = new ArrayList<>(); + if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { + receiptsNamespaces.add("urn:xmpp:chat-markers:0"); + } + if (packet.hasChild("request", "urn:xmpp:receipts")) { + receiptsNamespaces.add("urn:xmpp:receipts"); + } + if (receiptsNamespaces.size() > 0) { + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + receiptsNamespaces, + packet.getType()); + mXmppConnectionService.sendMessagePacket(account, receipt); + } + } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 2f28a30f..dcba4f74 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -51,7 +51,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 23; + private static final int DATABASE_VERSION = 24; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -161,6 +161,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.SERVER_MSG_ID + " TEXT, " + Message.FINGERPRINT + " TEXT, " + Message.CARBON + " INTEGER, " + + Message.EDITED + " TEXT, " + Message.READ + " NUMBER DEFAULT 1, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " @@ -370,6 +371,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 23 && newVersion >= 23) { db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); } + + if (oldVersion < 24 && newVersion >= 24) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -586,6 +591,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { + "=?", args); } + public void updateMessage(Message message, String uuid) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {uuid}; + db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + + "=?", args); + } + public void readRoster(Roster roster) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 16d7f139..10f6b5ef 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -841,8 +841,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); MessagePacket packet = null; - final boolean addToConversation = conversation.getMode() != Conversation.MODE_MULTI - || account.getServerIdentity() != XmppConnection.Identity.SLACK; + final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI + || account.getServerIdentity() != XmppConnection.Identity.SLACK) + && !message.edited(); boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); @@ -966,8 +967,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (addToConversation) { conversation.add(message); } - if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages())) { - databaseBackend.createMessage(message); + if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { + if (saveInDb) { + databaseBackend.createMessage(message); + } else if (message.edited()) { + databaseBackend.updateMessage(message, message.getEditedId()); + } } updateConversationUi(); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c555ead0..6ead9962 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -8,7 +8,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.support.annotation.Nullable; @@ -40,6 +39,7 @@ import net.java.otr4j.session.SessionStatus; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -51,7 +51,6 @@ import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presence; -import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; import eu.siacs.conversations.services.XmppConnectionService; @@ -294,8 +293,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE); break; case CANCEL: - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - conversation.setNextCounterpart(null); + if (conversation != null) { + if (conversation.getCorrectingMessage() != null) { + conversation.setCorrectingMessage(null); + mEditMessage.getEditableText().clear(); + } + if (conversation.getMode() == Conversation.MODE_MULTI) { + conversation.setNextCounterpart(null); + } updateChatMsgHint(); updateSendButton(); } @@ -330,12 +335,21 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (body.length() == 0 || this.conversation == null) { return; } - Message message = new Message(conversation, body, conversation.getNextEncryption()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (conversation.getNextCounterpart() != null) { - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_PRIVATE); + final Message message; + if (conversation.getCorrectingMessage() == null) { + message = new Message(conversation, body, conversation.getNextEncryption()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (conversation.getNextCounterpart() != null) { + message.setCounterpart(conversation.getNextCounterpart()); + message.setType(Message.TYPE_PRIVATE); + } } + } else { + message = conversation.getCorrectingMessage(); + message.setBody(body); + message.setEdited(message.getUuid()); + message.setUuid(UUID.randomUUID().toString()); + conversation.setCorrectingMessage(null); } switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_OTR: @@ -356,7 +370,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void updateChatMsgHint() { final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; - if (multi && conversation.getNextCounterpart() != null) { + if (conversation.getCorrectingMessage() != null) { + this.mEditMessage.setHint(R.string.send_corrected_message); + } else if (multi && conversation.getNextCounterpart() != null) { this.mEditMessage.setHint(getString( R.string.send_private_message_to, conversation.getNextCounterpart().getResourcepart())); @@ -487,8 +503,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { synchronized (this.messageList) { super.onCreateContextMenu(menu, v, menuInfo); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; @@ -503,6 +518,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.getMenuInflater().inflate(R.menu.message_context, menu); menu.setHeaderTitle(R.string.message_options); MenuItem copyText = menu.findItem(R.id.copy_text); + MenuItem correctMessage = menu.findItem(R.id.correct_message); MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); @@ -514,6 +530,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && m.treatAsDownloadable() != Message.Decision.MUST) { copyText.setVisible(true); } + if (m.getType() == Message.TYPE_TEXT + && m.getStatus() != Message.STATUS_RECEIVED + && !m.isCarbon()) { + correctMessage.setVisible(true); + } if ((m.getType() != Message.TYPE_TEXT && m.getType() != Message.TYPE_PRIVATE && m.getTransferable() == null) @@ -550,6 +571,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case R.id.copy_text: copyText(selectedMessage); return true; + case R.id.correct_message: + correctMessage(selectedMessage); + return true; case R.id.send_again: resendMessage(selectedMessage); return true; @@ -652,6 +676,16 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateSendButton(); } + private void correctMessage(Message message) { + while(message.mergeable(message.next())) { + message = message.next(); + } + this.conversation.setCorrectingMessage(message); + this.mEditMessage.getEditableText().clear(); + this.mEditMessage.getEditableText().append(message.getBody()); + + } + protected void highlightInConference(String nick) { String oldString = mEditMessage.getText().toString().trim(); if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { @@ -958,9 +992,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final Conversation c = this.conversation; final SendButtonAction action; final Presence.Status status; - final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0; + final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); + final boolean empty = text.length() == 0; final boolean conference = c.getMode() == Conversation.MODE_MULTI; - if (conference && !c.getAccount().httpUploadAvailable()) { + if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { + action = SendButtonAction.CANCEL; + } else if (conference && !c.getAccount().httpUploadAvailable()) { if (empty && c.getNextCounterpart() != null) { action = SendButtonAction.CANCEL; } else { @@ -1238,6 +1275,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateSendButton(); } + @Override + public void onTextChanged() { + if (conversation != null && conversation.getCorrectingMessage() != null) { + updateSendButton(); + } + } + private int completionIndex = 0; private int lastCompletionLength = 0; private String incomplete; diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index fc655b0c..e3841d1d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -69,6 +69,7 @@ public class EditMessage extends EditText { this.isUserTyping = false; this.keyboardListener.onTextDeleted(); } + this.keyboardListener.onTextChanged(); } } @@ -84,6 +85,7 @@ public class EditMessage extends EditText { void onTypingStarted(); void onTypingStopped(); void onTextDeleted(); + void onTextChanged(); boolean onTabPressed(boolean repeated); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index fc2bd2ab..f6496b21 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -123,6 +123,16 @@ public class MessageAdapter extends ArrayAdapter { if (viewHolder.indicatorReceived != null) { viewHolder.indicatorReceived.setVisibility(View.GONE); } + + if (viewHolder.edit_indicator != null) { + if (message.edited()) { + viewHolder.edit_indicator.setVisibility(View.VISIBLE); + viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp); + viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f); + } else { + viewHolder.edit_indicator.setVisibility(View.GONE); + } + } boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI && message.getMergedStatus() <= Message.STATUS_RECEIVED; if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) { @@ -179,7 +189,7 @@ public class MessageAdapter extends ArrayAdapter { if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); } else { - viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_secure_indicator_white : R.drawable.ic_secure_indicator); + viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { XmppAxolotlSession.Trust trust = message.getConversation() @@ -463,6 +473,7 @@ public class MessageAdapter extends ArrayAdapter { .findViewById(R.id.download_button); viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); + viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); viewHolder.messageBody = (TextView) view @@ -483,6 +494,7 @@ public class MessageAdapter extends ArrayAdapter { .findViewById(R.id.download_button); viewHolder.indicator = (ImageView) view .findViewById(R.id.security_indicator); + viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); viewHolder.messageBody = (TextView) view @@ -701,6 +713,7 @@ public class MessageAdapter extends ArrayAdapter { protected TextView status_message; protected TextView encryption; public Button load_more_messages; + public ImageView edit_indicator; } class BitmapWorkerTask extends AsyncTask { diff --git a/src/main/res/drawable-hdpi/ic_lock_black_18dp.png b/src/main/res/drawable-hdpi/ic_lock_black_18dp.png new file mode 100644 index 00000000..4c7a7c59 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_lock_black_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_lock_white_18dp.png b/src/main/res/drawable-hdpi/ic_lock_white_18dp.png new file mode 100644 index 00000000..29e8bfd3 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_lock_white_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png new file mode 100644 index 00000000..00e7d074 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png new file mode 100644 index 00000000..558f0ea3 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_secure_indicator.png b/src/main/res/drawable-hdpi/ic_secure_indicator.png deleted file mode 100644 index 220463fc..00000000 Binary files a/src/main/res/drawable-hdpi/ic_secure_indicator.png and /dev/null differ diff --git a/src/main/res/drawable-hdpi/ic_secure_indicator_white.png b/src/main/res/drawable-hdpi/ic_secure_indicator_white.png deleted file mode 100644 index 46eb1195..00000000 Binary files a/src/main/res/drawable-hdpi/ic_secure_indicator_white.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_lock_black_18dp.png b/src/main/res/drawable-mdpi/ic_lock_black_18dp.png new file mode 100644 index 00000000..c8b6fe71 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_lock_black_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_lock_white_18dp.png b/src/main/res/drawable-mdpi/ic_lock_white_18dp.png new file mode 100644 index 00000000..1265e98e Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_lock_white_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png new file mode 100644 index 00000000..ebd96073 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png new file mode 100644 index 00000000..e23c42db Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_secure_indicator.png b/src/main/res/drawable-mdpi/ic_secure_indicator.png deleted file mode 100644 index 690d4d03..00000000 Binary files a/src/main/res/drawable-mdpi/ic_secure_indicator.png and /dev/null differ diff --git a/src/main/res/drawable-mdpi/ic_secure_indicator_white.png b/src/main/res/drawable-mdpi/ic_secure_indicator_white.png deleted file mode 100644 index e2f894ef..00000000 Binary files a/src/main/res/drawable-mdpi/ic_secure_indicator_white.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png new file mode 100644 index 00000000..0888c617 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png new file mode 100644 index 00000000..b94735ec Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png new file mode 100644 index 00000000..b33c964d Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png new file mode 100644 index 00000000..3ee3e172 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_secure_indicator.png b/src/main/res/drawable-xhdpi/ic_secure_indicator.png deleted file mode 100644 index cd0d1391..00000000 Binary files a/src/main/res/drawable-xhdpi/ic_secure_indicator.png and /dev/null differ diff --git a/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png b/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png deleted file mode 100644 index b624a8ce..00000000 Binary files a/src/main/res/drawable-xhdpi/ic_secure_indicator_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png new file mode 100644 index 00000000..dbcf3f33 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png new file mode 100644 index 00000000..895aabbf Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png new file mode 100644 index 00000000..66d25296 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png new file mode 100644 index 00000000..9d7f2ff9 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png b/src/main/res/drawable-xxhdpi/ic_secure_indicator.png deleted file mode 100644 index 6a74ccbe..00000000 Binary files a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png and /dev/null differ diff --git a/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png b/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png deleted file mode 100644 index 4945c959..00000000 Binary files a/src/main/res/drawable-xxhdpi/ic_secure_indicator_white.png and /dev/null differ diff --git a/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png new file mode 100644 index 00000000..c49d420e Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png new file mode 100644 index 00000000..0dcada81 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png new file mode 100644 index 00000000..827b6848 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png new file mode 100644 index 00000000..34ec7092 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png differ diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index a998bf37..466dd045 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -91,7 +91,17 @@ android:layout_marginRight="4sp" android:alpha="0.70" android:gravity="center_vertical" - android:src="@drawable/ic_secure_indicator_white" /> + android:src="@drawable/ic_lock_white_18dp" /> + + + android:src="@drawable/ic_lock_black_18dp" /> + + + The selected area is too large (No activated accounts) This field is required + Correct message + Send corrected message -- cgit v1.2.3