diff --git a/src/cheogram/res/drawable/subject.xml b/src/cheogram/res/drawable/subject.xml new file mode 100644 index 0000000000000000000000000000000000000000..39474655b2152ec779cc07bfd2855e74d0a0e3ca --- /dev/null +++ b/src/cheogram/res/drawable/subject.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 540ae834e7f2924a2df449d857d93f7aae9e1557..655b19f4f68ec367a89e5b772a79cf2cdf3ac08e 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -518,7 +518,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } private Pair bodyMinusFallbacks(String... fallbackNames) { - StringBuilder body = new StringBuilder(this.body); + StringBuilder body = new StringBuilder(this.body == null ? "" : this.body); List fallbacks = getFallbacks(fallbackNames); List> spans = new ArrayList<>(); @@ -541,6 +541,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public String getBody() { + if (body == null) return ""; + Pair result = bodyMinusFallbacks("http://jabber.org/protocol/address", Namespace.OOB); StringBuilder body = result.first; @@ -568,8 +570,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public synchronized void setBody(Spanned span) { - setBody(span.toString()); - if (SpannedToXHTML.isPlainText(span)) { + setBody(span == null ? null : span.toString()); + if (span == null || SpannedToXHTML.isPlainText(span)) { this.payloads.remove(getHtml(true)); } else { final Element body = getOrMakeHtml(); diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 0c24a462ee1aa0fa4e3268a8da8cf1f4346ed6d5..ab63a5c362a2db4e873bd2dbcf780ada82cd86b3 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -64,6 +64,7 @@ public class MessageGenerator extends AbstractGenerator { packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat()); } if (!legacyEncryption) { + if (message.getSubject() != null && message.getSubject().length() > 0) packet.addChild("subject").setContent(message.getSubject()); // Legacy encryption can't handle advanced payloads for (Element el : message.getPayloads()) { packet.addChild(el); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 84a9985dc147610ee246ebe7d559576c29f8b192..7bb1b0121e892ca5960d8d1dda0c7ea347053fdc 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -658,12 +658,13 @@ public class XmppConnectionService extends Service { return this.mAvatarService; } - public void attachLocationToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { + public void attachLocationToConversation(final Conversation conversation, final Uri uri, final String subject, final UiCallback callback) { int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { encryption = Message.ENCRYPTION_DECRYPTED; } Message message = new Message(conversation, uri.toString(), encryption); + if (subject != null && subject.length() > 0) message.setSubject(subject); message.setThread(conversation.getThread()); Message.configurePrivateMessage(message); if (encryption == Message.ENCRYPTION_DECRYPTED) { @@ -674,7 +675,7 @@ public class XmppConnectionService extends Service { } } - public void attachFileToConversation(final Conversation conversation, final Uri uri, final String type, final UiCallback callback) { + public void attachFileToConversation(final Conversation conversation, final Uri uri, final String type, final String subject, final UiCallback callback) { final Message message; if (conversation.getReplyTo() == null) { message = new Message(conversation, "", conversation.getNextEncryption()); @@ -685,6 +686,8 @@ public class XmppConnectionService extends Service { if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message.setEncryption(Message.ENCRYPTION_DECRYPTED); } + if (subject.length() > 0) message.setSubject(subject); + if (subject != null && subject.length() > 0) message.setSubject(subject); message.setThread(conversation.getThread()); if (!Message.configurePrivateFileMessage(message)) { message.setCounterpart(conversation.getNextCounterpart()); @@ -700,7 +703,7 @@ public class XmppConnectionService extends Service { } } - public void attachImageToConversation(final Conversation conversation, final Uri uri, final String type, final UiCallback callback) { + public void attachImageToConversation(final Conversation conversation, final Uri uri, final String type, final String subject, final UiCallback callback) { final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(this, uri, type); final String compressPictures = getCompressPicturesPreference(); @@ -709,7 +712,7 @@ public class XmppConnectionService extends Service { || (mimeType != null && mimeType.endsWith("/gif")) || getFileBackend().unusualBounds(uri)) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": not compressing picture. sending as file"); - attachFileToConversation(conversation, uri, mimeType, callback); + attachFileToConversation(conversation, uri, mimeType, subject, callback); return; } final Message message; @@ -723,6 +726,7 @@ public class XmppConnectionService extends Service { if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message.setEncryption(Message.ENCRYPTION_DECRYPTED); } + if (subject != null && subject.length() > 0) message.setSubject(subject); message.setThread(conversation.getThread()); if (!Message.configurePrivateFileMessage(message)) { message.setCounterpart(conversation.getNextCounterpart()); @@ -734,7 +738,7 @@ public class XmppConnectionService extends Service { getFileBackend().copyImageToPrivateStorage(message, uri); } catch (FileBackend.ImageCompressionException e) { Log.d(Config.LOGTAG, "unable to compress image. fall back to file transfer", e); - attachFileToConversation(conversation, uri, mimeType, callback); + attachFileToConversation(conversation, uri, mimeType, subject, callback); return; } catch (final FileBackend.FileCopyException e) { callback.error(e.getResId(), message); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index afc11fc5ec45f64914cedab9cc6ed7b82c5c37b3..7604505c7df9534f47676ea519179182160fe30c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -618,6 +618,8 @@ public class ConversationFragment extends XmppFragment } else { binding.textinput.setText(""); } + binding.textinputSubject.setText(""); + binding.textinputSubject.setVisibility(View.GONE); updateChatMsgHint(); updateSendButton(); updateEditablity(); @@ -817,13 +819,17 @@ public class ConversationFragment extends XmppFragment if (conversation == null) { return; } + final String subject = binding.textinputSubject.getText().toString(); activity.xmppConnectionService.attachLocationToConversation( conversation, uri, + subject, new UiCallback() { @Override - public void success(Message message) {} + public void success(Message message) { + messageSent(); + } @Override public void error(int errorCode, Message object) { @@ -839,6 +845,7 @@ public class ConversationFragment extends XmppFragment if (conversation == null) { return; } + final String subject = binding.textinputSubject.getText().toString(); if (type == "application/xdc+zip") newSubThread(); final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_file), Toast.LENGTH_LONG); @@ -848,6 +855,7 @@ public class ConversationFragment extends XmppFragment conversation, uri, type, + subject, new UiInformableCallback() { @Override public void inform(final String text) { @@ -859,7 +867,7 @@ public class ConversationFragment extends XmppFragment public void success(Message message) { runOnUiThread(() -> { activity.hideToast(); - setupReply(null); + messageSent(); }); hidePrepareFileToast(prepareFileToast); } @@ -887,6 +895,7 @@ public class ConversationFragment extends XmppFragment if (conversation == null) { return; } + final String subject = binding.textinputSubject.getText().toString(); final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); prepareFileToast.show(); @@ -895,6 +904,7 @@ public class ConversationFragment extends XmppFragment conversation, uri, type, + subject, new UiCallback() { @Override @@ -905,7 +915,7 @@ public class ConversationFragment extends XmppFragment @Override public void success(Message message) { hidePrepareFileToast(prepareFileToast); - runOnUiThread(() -> setupReply(null)); + runOnUiThread(() -> messageSent()); } @Override @@ -934,7 +944,8 @@ public class ConversationFragment extends XmppFragment Editable body = this.binding.textinput.getText(); if (body == null) body = new SpannableStringBuilder(""); final Conversation conversation = this.conversation; - if (body.length() == 0 || conversation == null) { + final boolean hasSubject = binding.textinputSubject.getText().length() > 0; + if (conversation == null || body.length() == 0) { // (conversation.getThread() == null || !hasSubject))) https://issues.prosody.im/1838 binding.textSendButton.showContextMenu(0, 0); return; } @@ -959,7 +970,7 @@ public class ConversationFragment extends XmppFragment message.setEncryption(conversation.getNextEncryption()); } else { message = new Message(conversation, body.toString(), conversation.getNextEncryption()); - message.setBody(body); + message.setBody(hasSubject && body.length() == 0 ? null : body); if (message.bodyIsOnlyEmojis()) { SpannableStringBuilder spannable = message.getSpannableBody(null, null); ImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), ImageSpan.class); @@ -981,6 +992,7 @@ public class ConversationFragment extends XmppFragment } } } + if (hasSubject) message.setSubject(binding.textinputSubject.getText().toString()); message.setThread(conversation.getThread()); if (attention) { message.addPayload(new Element("attention", "urn:xmpp:attention:0")); @@ -988,7 +1000,8 @@ public class ConversationFragment extends XmppFragment Message.configurePrivateMessage(message); } else { message = conversation.getCorrectingMessage(); - message.setBody(body); + message.setBody(hasSubject && body.length() == 0 ? null : body); + if (hasSubject) message.setSubject(binding.textinputSubject.getText().toString()); message.setThread(conversation.getThread()); message.putEdited(message.getUuid(), message.getServerMsgId()); message.setServerMsgId(null); @@ -1809,6 +1822,7 @@ public class ConversationFragment extends XmppFragment } } message.setBody(" "); + message.setSubject(null); message.putEdited(message.getUuid(), message.getServerMsgId()); message.setServerMsgId(null); message.setUuid(UUID.randomUUID().toString()); @@ -1923,6 +1937,9 @@ public class ConversationFragment extends XmppFragment case R.id.attach_location: handleAttachmentSelection(item); break; + case R.id.attach_subject: + binding.textinputSubject.setVisibility(binding.textinputSubject.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); + break; case R.id.action_search: startSearch(); break; @@ -2856,6 +2873,10 @@ public class ConversationFragment extends XmppFragment this.conversation.setDraftMessage(editable.toString()); this.binding.textinput.setText(""); this.binding.textinput.append(message.getBody()); + if (message.getSubject() != null && message.getSubject().length() > 0) { + this.binding.textinputSubject.setText(message.getSubject()); + this.binding.textinputSubject.setVisibility(View.VISIBLE); + } } private void highlightInConference(String nick) { @@ -3072,6 +3093,7 @@ public class ConversationFragment extends XmppFragment this.binding.textSendButton.setContentDescription( activity.getString(R.string.send_message_to_x, conversation.getName())); this.binding.textinput.setKeyboardListener(null); + this.binding.textinputSubject.setKeyboardListener(null); final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); @@ -3082,6 +3104,7 @@ public class ConversationFragment extends XmppFragment this.binding.textinput.setText(MessageUtils.EMPTY_STRING); } this.binding.textinput.setKeyboardListener(this); + this.binding.textinputSubject.setKeyboardListener(this); messageListAdapter.updatePreferences(); refresh(false); activity.invalidateOptionsMenu(); @@ -3556,6 +3579,8 @@ public class ConversationFragment extends XmppFragment } protected void messageSent() { + binding.textinputSubject.setText(""); + binding.textinputSubject.setVisibility(View.GONE); setThread(null); conversation.setUserSelectedThread(false); mSendingPgpMessage.set(false); @@ -3636,7 +3661,7 @@ public class ConversationFragment extends XmppFragment if (hasAttachments) { action = SendButtonAction.TEXT; } else { - action = SendButtonTool.getAction(getActivity(), c, text); + action = SendButtonTool.getAction(getActivity(), c, text, binding.textinputSubject.getText().toString()); } if (c.getAccount().getStatus() == Account.State.ONLINE) { if (activity != null @@ -3658,7 +3683,7 @@ public class ConversationFragment extends XmppFragment final Activity activity = getActivity(); if (activity != null) { this.binding.textSendButton.setImageDrawable( - SendButtonTool.getSendButtonImageResource(activity, action, status, text.length() > 0 || hasAttachments)); + SendButtonTool.getSendButtonImageResource(activity, action, status, text.length() > 0 || hasAttachments)); // || (c.getThread() != null && binding.textinputSubject.getText().length() > 0))); https://issues.prosody.im/1838 } ViewGroup.LayoutParams params = binding.threadIdenticonLayout.getLayoutParams(); diff --git a/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java b/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java index a9aa947a097d076faad41a6c4c9bdd97e636a43a..747eac9c040a07a605c1c7bb9d03eff86d135233 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java +++ b/src/main/java/eu/siacs/conversations/ui/util/SendButtonTool.java @@ -44,14 +44,14 @@ import eu.siacs.conversations.utils.UIHelper; public class SendButtonTool { - public static SendButtonAction getAction(final Activity activity, final Conversation c, final String text) { + public static SendButtonAction getAction(final Activity activity, final Conversation c, final String text, final String subject) { if (activity == null) { return SendButtonAction.TEXT; } final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); final boolean empty = text.length() == 0; final boolean conference = c.getMode() == Conversation.MODE_MULTI; - if (c.getCorrectingMessage() != null && (empty || (text.equals(c.getCorrectingMessage().getBody()) && (c.getThread() == c.getCorrectingMessage().getThread() || (c.getThread() != null && c.getThread().equals(c.getCorrectingMessage().getThread())))))) { + if (c.getCorrectingMessage() != null && (empty || (text.equals(c.getCorrectingMessage().getBody()) && (subject.equals(c.getCorrectingMessage().getSubject())) && (c.getThread() == c.getCorrectingMessage().getThread() || (c.getThread() != null && c.getThread().equals(c.getCorrectingMessage().getThread())))))) { return SendButtonAction.CANCEL; } else if (conference && !c.getAccount().httpUploadAvailable()) { if (empty && c.getNextCounterpart() != null) { @@ -60,7 +60,7 @@ public class SendButtonTool { return SendButtonAction.TEXT; } } else { - if (empty) { + if (empty && (c.getThread() == null || subject.length() == 0)) { if (conference && c.getNextCounterpart() != null) { return SendButtonAction.CANCEL; } else { diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index a86f813e8ad235226df37b5b8d57a0f4578ab231..66d8233743d14f4a0aa8fdf0a4e6c655bdc28f80 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -105,17 +105,6 @@ android:layout_toEndOf="@+id/thread_identicon_layout" android:layout_toRightOf="@+id/thread_identicon_layout" android:orientation="vertical"> - - + + + + + +