From 7aa49998425b768244715b19c9779202c0145230 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sat, 9 Feb 2019 14:34:49 +0100 Subject: show web link previews in chat fixes #113 --- .../java/de/pixart/messenger/entities/Message.java | 16 +- .../de/pixart/messenger/ui/SettingsActivity.java | 1 + .../messenger/ui/adapter/MessageAdapter.java | 60 ++++++- .../de/pixart/messenger/ui/util/MyLinkify.java | 1 + .../pixart/messenger/ui/widget/RichLinkView.java | 184 +++++++++++++++++++++ .../java/de/pixart/messenger/utils/XmppUri.java | 8 +- src/main/res/layout/link_layout.xml | 55 ++++++ src/main/res/layout/message_content.xml | 5 + src/main/res/values/bools.xml | 1 + src/main/res/values/strings.xml | 4 +- src/main/res/xml/preferences.xml | 5 + 11 files changed, 326 insertions(+), 14 deletions(-) create mode 100644 src/main/java/de/pixart/messenger/ui/widget/RichLinkView.java create mode 100644 src/main/res/layout/link_layout.xml (limited to 'src') diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java index ba44a10ae..178a8e425 100644 --- a/src/main/java/de/pixart/messenger/entities/Message.java +++ b/src/main/java/de/pixart/messenger/entities/Message.java @@ -5,6 +5,7 @@ import android.database.Cursor; import android.graphics.Color; import android.text.SpannableStringBuilder; import android.util.Log; +import android.webkit.URLUtil; import java.lang.ref.WeakReference; import java.net.MalformedURLException; @@ -14,15 +15,18 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; import de.pixart.messenger.Config; import de.pixart.messenger.crypto.axolotl.FingerprintStatus; import de.pixart.messenger.services.AvatarService; +import de.pixart.messenger.ui.util.MyLinkify; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.Emoticons; import de.pixart.messenger.utils.GeoHelper; import de.pixart.messenger.utils.MessageUtils; import de.pixart.messenger.utils.MimeUtils; +import de.pixart.messenger.utils.Patterns; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.utils.XmppUri; import rocks.xmpp.addr.Jid; @@ -108,6 +112,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable private Boolean isGeoUri = null; private Boolean isXmppUri = null; + private Boolean isWebUri = null; private Boolean isEmojisOnly = null; private Boolean treatAsDownloadable = null; private FileParams fileParams = null; @@ -602,6 +607,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable this.getBody().length() + message.getBody().length() <= Config.MAX_DISPLAY_MESSAGE_CHARS && !message.isGeoUri()&& !this.isGeoUri() && + !this.isWebUri() && !message.treatAsDownloadable() && !this.treatAsDownloadable() && !message.getBody().startsWith(ME_COMMAND) && @@ -767,7 +773,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public synchronized boolean isXmppUri() { if (isXmppUri == null) { - isXmppUri = XmppUri.isXmppUri(body.trim()); + isXmppUri = XmppUri.XMPP_URI.matcher(body).matches(); } return isXmppUri; } @@ -779,6 +785,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return isGeoUri; } + public synchronized boolean isWebUri() { + String url = body.toLowerCase().trim(); + if (isWebUri == null) { + isWebUri = URLUtil.isValidUrl(url) && Patterns.WEB_URL.matcher(url).matches(); + } + return isWebUri; + } + public synchronized void resetFileParams() { this.fileParams = null; } diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java index ecae24588..a7076f4c5 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java @@ -60,6 +60,7 @@ public class SettingsActivity extends XmppActivity implements public static final String QUICK_SHARE_ATTACHMENT_CHOICE = "quick_share_attachment_choice"; public static final String NUMBER_OF_ACCOUNTS = "number_of_accounts"; public static final String PLAY_GIF_INSIDE = "play_gif_inside"; + public static final String SHOW_LINKS_INSIDE = "show_links_inside"; public static final String PREFER_XMPP_AVATAR = "prefer_xmpp_avatar"; public static final int REQUEST_CREATE_BACKUP = 0xbf8701; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 7e3d26728..897e37e85 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -10,6 +10,7 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; +import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -35,8 +36,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.squareup.picasso.Picasso; import java.io.UnsupportedEncodingException; import java.net.URL; @@ -70,6 +70,7 @@ import de.pixart.messenger.ui.util.ViewUtil; import de.pixart.messenger.ui.widget.ClickableMovementMethod; import de.pixart.messenger.ui.widget.CopyTextView; import de.pixart.messenger.ui.widget.ListSelectionManager; +import de.pixart.messenger.ui.widget.RichLinkView; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.Emoticons; @@ -77,9 +78,11 @@ import de.pixart.messenger.utils.GeoHelper; import de.pixart.messenger.utils.StylingHelper; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.mam.MamReference; +import io.github.ponnamkarthik.richlinkpreview.ViewListener; import pl.droidsonroids.gif.GifImageView; import static de.pixart.messenger.ui.SettingsActivity.PLAY_GIF_INSIDE; +import static de.pixart.messenger.ui.SettingsActivity.SHOW_LINKS_INSIDE; public class MessageAdapter extends ArrayAdapter implements CopyTextView.CopyHandler { @@ -356,6 +359,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); if (darkBackground) { @@ -371,6 +375,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); if (darkBackground) { viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji_OnDark); @@ -415,6 +420,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie }); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); } @@ -490,6 +496,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.download_button.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); if (darkBackground) { @@ -587,6 +594,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(text); @@ -598,6 +606,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); final String mimeType = message.getMimeType(); @@ -658,12 +667,45 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message) + VCardName)); } + private void displayRichLinkMessage(ViewHolder viewHolder, final Message message, boolean darkBackground) { + viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.image.setVisibility(View.GONE); + viewHolder.gifImage.setVisibility(View.GONE); + boolean showLinksInside = activity.getPreferences().getBoolean(SHOW_LINKS_INSIDE, activity.getResources().getBoolean(R.bool.show_links_inside)); + viewHolder.messageBody.setVisibility(View.VISIBLE); + Editable body = new SpannableStringBuilder(message.getBody().toLowerCase()); + if (darkBackground) { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_OnDark); + } else { + viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1); + } + MyLinkify.addLinks(body, false); + viewHolder.messageBody.setAutoLinkMask(0); + viewHolder.messageBody.setText(EmojiWrapper.transform(body)); + if (showLinksInside) { + viewHolder.richlinkview.setVisibility(View.VISIBLE); + viewHolder.richlinkview.setLink(body.toString(), new ViewListener() { + + @Override + public void onSuccess(boolean status) { + } + + @Override + public void onError(Exception e) { + } + }); + } else { + viewHolder.richlinkview.setVisibility(View.GONE); + } + } + private void displayLocationMessage(ViewHolder viewHolder, final Message message) { viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); String url = GeoHelper.MapPreviewUri(message); viewHolder.image.setVisibility(View.VISIBLE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); double target = metrics.density * 200; int scaledW; int scaledH; @@ -681,12 +723,9 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); viewHolder.image.setLayoutParams(layoutParams); viewHolder.image.setOnClickListener(v -> showLocation(message)); - Glide - .with(activity) + Picasso + .get() .load(Uri.parse(url)) - .asBitmap() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .fitCenter() .placeholder(R.drawable.ic_map_marker_grey600_48dp) .error(R.drawable.ic_map_marker_grey600_48dp) .into(viewHolder.image); @@ -702,6 +741,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) { viewHolder.image.setVisibility(View.GONE); viewHolder.gifImage.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.GONE); final RelativeLayout audioPlayer = viewHolder.audioPlayer; @@ -714,6 +754,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.download_button.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); viewHolder.audioPlayer.setVisibility(View.GONE); + viewHolder.richlinkview.setVisibility(View.GONE); DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); @@ -821,6 +862,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); viewHolder.image = view.findViewById(R.id.message_image); viewHolder.gifImage = view.findViewById(R.id.message_image_gif); + viewHolder.richlinkview = view.findViewById(R.id.richLinkView); viewHolder.messageBody = view.findViewById(R.id.message_body); viewHolder.time = view.findViewById(R.id.message_time); viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); @@ -836,6 +878,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator); viewHolder.image = view.findViewById(R.id.message_image); viewHolder.gifImage = view.findViewById(R.id.message_image_gif); + viewHolder.richlinkview = view.findViewById(R.id.richLinkView); viewHolder.messageBody = view.findViewById(R.id.message_body); viewHolder.time = view.findViewById(R.id.message_time); viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received); @@ -964,6 +1007,8 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } else { if (message.isGeoUri()) { displayLocationMessage(viewHolder, message); + } else if (message.isWebUri()) { + displayRichLinkMessage(viewHolder, message, darkBackground); } else if (message.bodyIsOnlyEmojis() && message.getType() != Message.TYPE_PRIVATE) { displayEmojiMessage(viewHolder, message.getBody().trim(), darkBackground); } else if (message.isXmppUri()) { @@ -1130,6 +1175,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie protected Button resend_button; protected ImageView image; protected GifImageView gifImage; + protected RichLinkView richlinkview; protected ImageView indicator; protected ImageView indicatorReceived; protected ImageView indicatorRead; diff --git a/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java b/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java index 4b276e392..59de9be6d 100644 --- a/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java +++ b/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java @@ -34,6 +34,7 @@ import android.text.Editable; import android.text.util.Linkify; import java.util.Locale; +import java.util.regex.Pattern; import de.pixart.messenger.ui.text.FixedURLSpan; import de.pixart.messenger.utils.GeoHelper; diff --git a/src/main/java/de/pixart/messenger/ui/widget/RichLinkView.java b/src/main/java/de/pixart/messenger/ui/widget/RichLinkView.java new file mode 100644 index 000000000..29f04970d --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/RichLinkView.java @@ -0,0 +1,184 @@ +package de.pixart.messenger.ui.widget; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; + +import de.pixart.messenger.R; +import io.github.ponnamkarthik.richlinkpreview.MetaData; +import io.github.ponnamkarthik.richlinkpreview.ResponseListener; +import io.github.ponnamkarthik.richlinkpreview.RichLinkListener; +import io.github.ponnamkarthik.richlinkpreview.RichPreview; +import io.github.ponnamkarthik.richlinkpreview.ViewListener; + +/** + * Created by ponna on 16-01-2018. + */ + +public class RichLinkView extends RelativeLayout { + + private View view; + Context context; + private MetaData meta; + + LinearLayout linearLayout; + ImageView imageView; + TextView textViewTitle; + TextView textViewDesp; + TextView textViewUrl; + + private String main_url; + + private boolean isDefaultClick = true; + + private RichLinkListener richLinkListener; + + + public RichLinkView(Context context) { + super(context); + this.context = context; + } + + public RichLinkView(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } + + public RichLinkView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.context = context; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public RichLinkView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + this.context = context; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + } + + + public void initView() { + if (findLinearLayoutChild() != null) { + this.view = findLinearLayoutChild(); + } else { + this.view = this; + inflate(context, R.layout.link_layout, this); + } + linearLayout = findViewById(R.id.rich_link_card); + imageView = findViewById(R.id.rich_link_image); + textViewTitle = findViewById(R.id.rich_link_title); + textViewDesp = findViewById(R.id.rich_link_desp); + textViewUrl = findViewById(R.id.rich_link_url); + + if (!meta.getImageurl().equals("") || !meta.getImageurl().isEmpty() + && !meta.getTitle().isEmpty() || !meta.getTitle().equals("") + && !meta.getUrl().isEmpty() || !meta.getUrl().equals("") + && !meta.getDescription().isEmpty() || !meta.getDescription().equals("")) { + linearLayout.setVisibility(VISIBLE); + } else { + linearLayout.setVisibility(GONE); + } + if (meta.getImageurl().equals("") || meta.getImageurl().isEmpty()) { + imageView.setVisibility(GONE); + } else { + imageView.setVisibility(VISIBLE); + Picasso.get() + .load(meta.getImageurl()) + .into(imageView); + } + if (meta.getTitle().isEmpty() || meta.getTitle().equals("")) { + textViewTitle.setVisibility(GONE); + } else { + textViewTitle.setVisibility(VISIBLE); + textViewTitle.setText(meta.getTitle()); + } + if (meta.getUrl().isEmpty() || meta.getUrl().equals("")) { + textViewUrl.setVisibility(GONE); + } else { + textViewUrl.setVisibility(GONE); + textViewUrl.setText(meta.getUrl()); + } + if (meta.getDescription().isEmpty() || meta.getDescription().equals("")) { + textViewDesp.setVisibility(GONE); + } else { + textViewDesp.setVisibility(VISIBLE); + textViewDesp.setText(meta.getDescription()); + } + + linearLayout.setOnClickListener(view -> { + if (isDefaultClick) { + richLinkClicked(); + } else { + if (richLinkListener != null) { + richLinkListener.onClicked(view, meta); + } else { + richLinkClicked(); + } + } + }); + } + + private void richLinkClicked() { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(main_url)); + context.startActivity(intent); + } + + public void setDefaultClickListener(boolean isDefault) { + isDefaultClick = isDefault; + } + + public void setClickListener(RichLinkListener richLinkListener1) { + richLinkListener = richLinkListener1; + } + + protected LinearLayout findLinearLayoutChild() { + if (getChildCount() > 0 && getChildAt(0) instanceof LinearLayout) { + return (LinearLayout) getChildAt(0); + } + return null; + } + + public void setLinkFromMeta(MetaData metaData) { + meta = metaData; + initView(); + } + + public MetaData getMetaData() { + return meta; + } + + public void setLink(String url, final ViewListener viewListener) { + main_url = url; + RichPreview richPreview = new RichPreview(new ResponseListener() { + @Override + public void onData(MetaData metaData) { + meta = metaData; + if (!meta.getTitle().isEmpty() || !meta.getTitle().equals("")) { + viewListener.onSuccess(true); + } + initView(); + } + + @Override + public void onError(Exception e) { + viewListener.onError(e); + } + }); + richPreview.getPreview(url); + } +} diff --git a/src/main/java/de/pixart/messenger/utils/XmppUri.java b/src/main/java/de/pixart/messenger/utils/XmppUri.java index 1ee90aa84..64f28cbab 100644 --- a/src/main/java/de/pixart/messenger/utils/XmppUri.java +++ b/src/main/java/de/pixart/messenger/utils/XmppUri.java @@ -8,6 +8,7 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.regex.Pattern; import de.pixart.messenger.Config; import rocks.xmpp.addr.Jid; @@ -27,6 +28,8 @@ public class XmppUri { public static final String ACTION_JOIN = "join"; public static final String ACTION_MESSAGE = "message"; + public static Pattern XMPP_URI = Patterns.XMPP_PATTERN; + public XmppUri(String uri) { try { parse(Uri.parse(uri)); @@ -43,11 +46,6 @@ public class XmppUri { parse(uri); } - public static boolean isXmppUri(String uri) { - String scheme = Uri.parse(uri).getScheme(); - return "xmpp".equalsIgnoreCase(scheme); - } - public XmppUri(Uri uri, boolean safeSource) { this.safeSource = safeSource; parse(uri); diff --git a/src/main/res/layout/link_layout.xml b/src/main/res/layout/link_layout.xml new file mode 100644 index 000000000..68e7a6cc7 --- /dev/null +++ b/src/main/res/layout/link_layout.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/message_content.xml b/src/main/res/layout/message_content.xml index f14f310c3..be2bbf3db 100644 --- a/src/main/res/layout/message_content.xml +++ b/src/main/res/layout/message_content.xml @@ -28,6 +28,11 @@ android:scaleType="centerCrop" app:riv_corner_radius="@dimen/rounded_image_border" /> + + true + true \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 5742775e0..3a2eb4ea7 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -823,8 +823,10 @@ Please enter your nickname which will be visible to your contacts. No nickname set. Automatically join this group chat + Show previews of web links in chat + Show previews of web links directly inside the chat view. Play GIF files in chat - Setting this to true plays GIF files directly inside the chat view. + Play GIF files directly inside the chat view. Open with… XEP-0050: Ad-Hoc Commands: user invite Choose account diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 52a957f98..15fe0fc34 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -76,6 +76,11 @@ android:key="play_gif_inside" android:summary="@string/pref_play_gif_inside_summary" android:title="@string/pref_play_gif_inside" /> +