Allow saving inline images as sticker by long press

Stephen Paul Weber created

Change summary

src/main/java/eu/siacs/conversations/entities/Message.java          | 18 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java   | 15 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 32 
3 files changed, 63 insertions(+), 2 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -7,9 +7,13 @@ import android.graphics.Color;
 import android.os.Build;
 import android.text.Html;
 import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ImageSpan;
+import android.text.style.ClickableSpan;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
+import android.view.View;
 
 import com.cheogram.android.BobTransfer;
 import com.cheogram.android.GetThumbnailForCid;
@@ -854,6 +858,20 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 (opening, tag, output, xmlReader) -> {}
             ));
 
+            // Make images clickable and long-clickable with BetterLinkMovementMethod
+            ImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), ImageSpan.class);
+            for (ImageSpan span : imageSpans) {
+                final int start = spannable.getSpanStart(span);
+                final int end = spannable.getSpanEnd(span);
+
+                ClickableSpan click_span = new ClickableSpan() {
+                    @Override
+                    public void onClick(View widget) { }
+                };
+
+                spannable.setSpan(click_span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+
             // https://stackoverflow.com/a/10187511/8611
             int i = spannable.length();
             while(--i >= 0 && Character.isWhitespace(spannable.charAt(i))) { }

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -75,6 +75,8 @@ import com.google.common.collect.ImmutableList;
 
 import org.jetbrains.annotations.NotNull;
 
+import io.ipfs.cid.Cid;
+
 import java.io.File;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -159,7 +161,8 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 public class ConversationFragment extends XmppFragment
         implements EditMessage.KeyboardListener,
                 MessageAdapter.OnContactPictureLongClicked,
-                MessageAdapter.OnContactPictureClicked {
+                MessageAdapter.OnContactPictureClicked,
+                MessageAdapter.OnInlineImageLongClicked {
 
     public static final int REQUEST_SEND_MESSAGE = 0x0201;
     public static final int REQUEST_DECRYPT_PGP = 0x0202;
@@ -1282,6 +1285,7 @@ public class ConversationFragment extends XmppFragment
         messageListAdapter = new MessageAdapter((XmppActivity) getActivity(), this.messageList);
         messageListAdapter.setOnContactPictureClicked(this);
         messageListAdapter.setOnContactPictureLongClicked(this);
+        messageListAdapter.setOnInlineImageLongClicked(this);
         binding.messagesView.setAdapter(messageListAdapter);
 
         registerForContextMenu(binding.messagesView);
@@ -1340,6 +1344,7 @@ public class ConversationFragment extends XmppFragment
         Log.d(Config.LOGTAG, "ConversationFragment.onDestroyView()");
         messageListAdapter.setOnContactPictureClicked(null);
         messageListAdapter.setOnContactPictureLongClicked(null);
+        messageListAdapter.setOnInlineImageLongClicked(null);
         if (conversation != null) conversation.setupViewPager(null, null);
     }
 
@@ -2326,6 +2331,14 @@ public class ConversationFragment extends XmppFragment
         builder.create().show();
     }
 
+    public boolean onInlineImageLongClicked(Cid cid) {
+        DownloadableFile f = activity.xmppConnectionService.getFileForCid(cid);
+        if (f == null) return false;
+
+        saveAsSticker(f, null);
+        return true;
+    }
+
     private void saveAsSticker(final Message m) {
         String existingName = m.getFileParams() != null && m.getFileParams().getName() != null ? m.getFileParams().getName() : "";
         existingName = existingName.lastIndexOf(".") == -1 ? existingName : existingName.substring(0, existingName.lastIndexOf("."));

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -14,6 +14,8 @@ import android.preference.PreferenceManager;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+import android.text.style.ClickableSpan;
 import android.text.format.DateUtils;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.RelativeSizeSpan;
@@ -111,6 +113,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
     private OnContactPictureClicked mOnContactPictureClickedListener;
     private OnContactPictureClicked mOnMessageBoxClickedListener;
     private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
+    private OnInlineImageLongClicked mOnInlineImageLongClickedListener;
     private boolean mUseGreenBackground = false;
     private final boolean mForceNames;
 
@@ -162,6 +165,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         this.mOnContactPictureLongClickedListener = listener;
     }
 
+    public void setOnInlineImageLongClicked(OnInlineImageLongClicked listener) {
+        this.mOnInlineImageLongClickedListener = listener;
+    }
+
     @Override
     public int getViewTypeCount() {
         return 5;
@@ -577,7 +584,26 @@ public class MessageAdapter extends ArrayAdapter<Message> {
             MyLinkify.addLinks(body, message.getConversation().getAccount());
             viewHolder.messageBody.setAutoLinkMask(0);
             viewHolder.messageBody.setText(body);
-            BetterLinkMovementMethod method = BetterLinkMovementMethod.newInstance();
+            BetterLinkMovementMethod method = new BetterLinkMovementMethod() {
+                @Override
+                protected void dispatchUrlLongClick(TextView tv, ClickableSpan span) {
+                    if (span instanceof URLSpan || mOnInlineImageLongClickedListener == null) {
+                        super.dispatchUrlLongClick(tv, span);
+                        return;
+                    }
+
+                    Spannable body = (Spannable) tv.getText();
+                    ImageSpan[] imageSpans = body.getSpans(body.getSpanStart(span), body.getSpanEnd(span), ImageSpan.class);
+                    if (imageSpans.length > 0) {
+                        Uri uri = Uri.parse(imageSpans[0].getSource());
+                        Cid cid = BobTransfer.cid(uri);
+                        if (cid == null) return;
+                        if (mOnInlineImageLongClickedListener.onInlineImageLongClicked(cid)) {
+                            tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
+                        }
+                    }
+                }
+            };
             method.setOnLinkLongClickListener((tv, url) -> {
                 tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
                 ShareUtil.copyLinkToClipboard(activity, url);
@@ -1096,6 +1122,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
         void onContactPictureLongClicked(View v, Message message);
     }
 
+    public interface OnInlineImageLongClicked {
+        boolean onInlineImageLongClicked(Cid cid);
+    }
+
     private static class ViewHolder {
 
         public Button load_more_messages;