show web link previews in chat

fixes #113
This commit is contained in:
Christian Schneppe 2019-02-09 14:34:49 +01:00
parent 42c3129b66
commit 7aa4999842
12 changed files with 329 additions and 16 deletions

View file

@ -69,7 +69,6 @@ dependencies {
implementation 'com.android.support:exifinterface:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.github.rtoshiro.fullscreenvideoview:fullscreenvideoview:1.1.3'
implementation 'pub.devrel:easypermissions:2.0.0'
@ -81,7 +80,9 @@ dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' // last working version for minSDK 16
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'org.osmdroid:osmdroid-android:6.0.3'
implementation "com.leinardi.android:speed-dial:2.0.1"
implementation 'com.leinardi.android:speed-dial:2.0.1'
implementation 'io.github.ponnamkarthik:richlinkpreview:1.0.9'
implementation 'com.squareup.picasso:picasso:2.71828'
}
ext {

View file

@ -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;
}

View file

@ -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;

View file

@ -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<Message> implements CopyTextView.CopyHandler {
@ -356,6 +359,7 @@ public class MessageAdapter extends ArrayAdapter<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> 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<Message> implements CopyTextVie
protected Button resend_button;
protected ImageView image;
protected GifImageView gifImage;
protected RichLinkView richlinkview;
protected ImageView indicator;
protected ImageView indicatorReceived;
protected ImageView indicatorRead;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rich_link_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/rich_link_image"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="2dp">
<TextView
android:id="@+id/rich_link_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.Conversations.Body2"
tools:text="Data" />
<TextView
android:id="@+id/rich_link_desp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="4"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.Conversations.Status"
tools:text="Data" />
<TextView
android:id="@+id/rich_link_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.Conversations.Body1.Secondary"
android:visibility="gone"
tools:text="Data" />
</LinearLayout>
</LinearLayout>

View file

@ -28,6 +28,11 @@
android:scaleType="centerCrop"
app:riv_corner_radius="@dimen/rounded_image_border" />
<de.pixart.messenger.ui.widget.RichLinkView
android:id="@+id/richLinkView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<de.pixart.messenger.ui.widget.CopyTextView
android:id="@+id/message_body"
android:layout_width="wrap_content"

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="prefer_xmpp_avatar">true</bool>
<bool name="show_links_inside">true</bool>
</resources>

View file

@ -823,8 +823,10 @@
<string name="enter_your_name_instructions">Please enter your nickname which will be visible to your contacts.</string>
<string name="no_name_set_instructions">No nickname set.</string>
<string name="autojoin_groupchat">Automatically join this group chat</string>
<string name="pref_show_links_inside">Show previews of web links in chat</string>
<string name="pref_show_links_inside_summary">Show previews of web links directly inside the chat view.</string>
<string name="pref_play_gif_inside">Play GIF files in chat</string>
<string name="pref_play_gif_inside_summary">Setting this to true plays GIF files directly inside the chat view.</string>
<string name="pref_play_gif_inside_summary">Play GIF files directly inside the chat view.</string>
<string name="open_with">Open with…</string>
<string name="server_info_adhoc_invite">XEP-0050: Ad-Hoc Commands: user invite</string>
<string name="choose_account">Choose account</string>

View file

@ -76,6 +76,11 @@
android:key="play_gif_inside"
android:summary="@string/pref_play_gif_inside_summary"
android:title="@string/pref_play_gif_inside" />
<CheckBoxPreference
android:defaultValue="@bool/show_links_inside"
android:key="show_links_inside"
android:summary="@string/pref_show_links_inside_summary"
android:title="@string/pref_show_links_inside" />
<CheckBoxPreference
android:defaultValue="@bool/prefer_xmpp_avatar"
android:key="prefer_xmpp_avatar"