basic support for XEP-0308: Last Message Correction. fixes #864
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
@ -167,7 +172,8 @@ public class Message extends AbstractEntity {
|
||||
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,
|
||||
|
@ -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"};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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())) {
|
||||
if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) {
|
||||
if (saveInDb) {
|
||||
databaseBackend.createMessage(message);
|
||||
} else if (message.edited()) {
|
||||
databaseBackend.updateMessage(message, message.getEditedId());
|
||||
}
|
||||
}
|
||||
updateConversationUi();
|
||||
}
|
||||
|
@ -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) {
|
||||
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,13 +335,22 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||
if (body.length() == 0 || this.conversation == null) {
|
||||
return;
|
||||
}
|
||||
Message message = new Message(conversation, body, conversation.getNextEncryption());
|
||||
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:
|
||||
sendOtrMessage(message);
|
||||
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,16 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
.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<Message> {
|
||||
.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<Message> {
|
||||
protected TextView status_message;
|
||||
protected TextView encryption;
|
||||
public Button load_more_messages;
|
||||
public ImageView edit_indicator;
|
||||
}
|
||||
|
||||
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
|
||||
|
BIN
src/main/res/drawable-hdpi/ic_lock_black_18dp.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
src/main/res/drawable-hdpi/ic_lock_white_18dp.png
Normal file
After Width: | Height: | Size: 371 B |
BIN
src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png
Normal file
After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 322 B |
BIN
src/main/res/drawable-mdpi/ic_lock_black_18dp.png
Normal file
After Width: | Height: | Size: 293 B |
BIN
src/main/res/drawable-mdpi/ic_lock_white_18dp.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png
Normal file
After Width: | Height: | Size: 229 B |
BIN
src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png
Normal file
After Width: | Height: | Size: 249 B |
Before Width: | Height: | Size: 295 B |
Before Width: | Height: | Size: 306 B |
BIN
src/main/res/drawable-xhdpi/ic_lock_black_18dp.png
Normal file
After Width: | Height: | Size: 397 B |
BIN
src/main/res/drawable-xhdpi/ic_lock_white_18dp.png
Normal file
After Width: | Height: | Size: 399 B |
BIN
src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png
Normal file
After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 410 B |
Before Width: | Height: | Size: 434 B |
BIN
src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png
Normal file
After Width: | Height: | Size: 559 B |
BIN
src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png
Normal file
After Width: | Height: | Size: 558 B |
BIN
src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png
Normal file
After Width: | Height: | Size: 336 B |
BIN
src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png
Normal file
After Width: | Height: | Size: 436 B |
Before Width: | Height: | Size: 380 B |
Before Width: | Height: | Size: 441 B |
BIN
src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png
Normal file
After Width: | Height: | Size: 636 B |
BIN
src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png
Normal file
After Width: | Height: | Size: 760 B |
BIN
src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png
Normal file
After Width: | Height: | Size: 366 B |
BIN
src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png
Normal file
After Width: | Height: | Size: 490 B |
@ -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" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/edit_indicator"
|
||||
android:layout_width="?attr/TextSizeInfo"
|
||||
android:layout_height="?attr/TextSizeInfo"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="4sp"
|
||||
android:alpha="0.70"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_mode_edit_white_18dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_time"
|
||||
|
@ -91,7 +91,17 @@
|
||||
android:layout_marginLeft="4sp"
|
||||
android:alpha="0.54"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_secure_indicator" />
|
||||
android:src="@drawable/ic_lock_black_18dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/edit_indicator"
|
||||
android:layout_width="?attr/TextSizeInfo"
|
||||
android:layout_height="?attr/TextSizeInfo"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="4sp"
|
||||
android:alpha="0.54"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_mode_edit_black_18dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/indicator_received"
|
||||
|
@ -5,6 +5,10 @@
|
||||
android:id="@+id/copy_text"
|
||||
android:title="@string/copy_text"
|
||||
android:visible="false"/>
|
||||
<item
|
||||
android:id="@+id/correct_message"
|
||||
android:title="@string/correct_message"
|
||||
android:visible="false"/>
|
||||
<item
|
||||
android:id="@+id/share_with"
|
||||
android:title="@string/share_with"
|
||||
|
@ -593,4 +593,6 @@
|
||||
<string name="selection_too_large">The selected area is too large</string>
|
||||
<string name="no_accounts">(No activated accounts)</string>
|
||||
<string name="this_field_is_required">This field is required</string>
|
||||
<string name="correct_message">Correct message</string>
|
||||
<string name="send_corrected_message">Send corrected message</string>
|
||||
</resources>
|
||||
|