Load custom emoji in statuses (#400)

This commit is contained in:
Ivan Kupalov 2017-10-19 17:25:04 +04:00 committed by Konrad Pozniak
commit 3adef27bbb
6 changed files with 199 additions and 82 deletions

View file

@ -2,6 +2,11 @@ package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
@ -9,7 +14,9 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ReplacementSpan;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageButton;
@ -25,11 +32,17 @@ import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.view.RoundedTransformation;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import com.varunest.sparkbutton.SparkButton;
import com.varunest.sparkbutton.SparkEventListener;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private View container;
@ -80,10 +93,8 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
videoIndicator = itemView.findViewById(R.id.status_video_indicator);
mediaLabel = itemView.findViewById(R.id.status_media_label);
contentWarningBar = itemView.findViewById(R.id.status_content_warning_bar);
contentWarningDescription =
itemView.findViewById(R.id.status_content_warning_description);
contentWarningButton =
itemView.findViewById(R.id.status_content_warning_button);
contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
}
private void setDisplayName(String name) {
@ -97,14 +108,43 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
username.setText(usernameText);
}
private void setContent(Spanned content, Status.Mention[] mentions,
private Callback spanCallback = new Callback() {
@Override
public void onSuccess() {
content.invalidate();
}
@Override
public void onError() {
}
};
private void setContent(Spanned content, Status.Mention[] mentions, List<Status.Emoji> emojis,
StatusActionListener listener) {
SpannableStringBuilder builder = new SpannableStringBuilder(content);
if (!emojis.isEmpty()) {
CharSequence text = builder.subSequence(0, builder.length());
for (Status.Emoji emoji : emojis) {
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':');
Matcher matcher = Pattern.compile(pattern.toString()).matcher(text);
while (matcher.find()) {
EmojiSpan span = new EmojiSpan();
span.setCallback(spanCallback);
builder.setSpan(span, matcher.start(), matcher.end(), 0);
Picasso.with(container.getContext())
.load(emoji.getUrl())
.into(span);
}
}
}
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
* account pages. */
Context context = this.content.getContext();
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("customTabs", true);
LinkHelper.setClickableText(this.content, content, mentions, useCustomTabs, listener);
boolean useCustomTabs =
PreferenceManager.getDefaultSharedPreferences(context).getBoolean("customTabs", true);
LinkHelper.setClickableText(this.content, builder, mentions, useCustomTabs, listener);
}
void setAvatar(String url, @Nullable String rebloggedUrl) {
@ -177,15 +217,13 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private void setMediaPreviews(final Status.MediaAttachment[] attachments, boolean sensitive,
final StatusActionListener listener, boolean showingSensitive) {
final ImageView[] previews = {
mediaPreview0,
mediaPreview1,
mediaPreview2,
mediaPreview3
mediaPreview0, mediaPreview1, mediaPreview2, mediaPreview3
};
Context context = mediaPreview0.getContext();
int mediaPreviewUnloadedId = ThemeUtils.getDrawableId(itemView.getContext(),
R.attr.media_preview_unloaded_drawable, android.R.color.black);
int mediaPreviewUnloadedId =
ThemeUtils.getDrawableId(itemView.getContext(), R.attr.media_preview_unloaded_drawable,
android.R.color.black);
final int n = Math.min(attachments.length, Status.MAX_MEDIA_ATTACHMENTS);
@ -200,9 +238,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
previews[i].setVisibility(View.VISIBLE);
if (previewUrl == null || previewUrl.isEmpty()) {
Picasso.with(context)
.load(mediaPreviewUnloadedId)
.into(previews[i]);
Picasso.with(context).load(mediaPreviewUnloadedId).into(previews[i]);
} else {
Picasso.with(context)
.load(previewUrl)
@ -211,8 +247,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
final Status.MediaAttachment.Type type = attachments[i].type;
if (type == Status.MediaAttachment.Type.VIDEO
| type == Status.MediaAttachment.Type.GIFV) {
if (type == Status.MediaAttachment.Type.VIDEO | type == Status.MediaAttachment.Type.GIFV) {
videoIndicator.setVisibility(View.VISIBLE);
}
@ -233,7 +268,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (sensitive && (!isAlwayShowSensitive)) {
sensitiveMediaWarning.setVisibility(showingSensitive ? View.GONE : View.VISIBLE);
sensitiveMediaShow.setVisibility(showingSensitive ? View.VISIBLE : View.GONE);
sensitiveMediaShow.setOnClickListener(new View.OnClickListener(){
sensitiveMediaShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setVisibility(View.GONE);
@ -330,20 +365,19 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
contentWarningDescription.setText(spoilerText);
contentWarningBar.setVisibility(View.VISIBLE);
contentWarningButton.setChecked(expanded);
contentWarningButton.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onExpandedChange(isChecked, getAdapterPosition());
}
if (isChecked) {
content.setVisibility(View.VISIBLE);
} else {
content.setVisibility(View.GONE);
}
}
});
contentWarningButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onExpandedChange(isChecked, getAdapterPosition());
}
if (isChecked) {
content.setVisibility(View.VISIBLE);
} else {
content.setVisibility(View.GONE);
}
}
});
if (expanded) {
content.setVisibility(View.VISIBLE);
} else {
@ -441,7 +475,7 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
setDisplayName(status.getUserFullName());
setUsername(status.getNickname());
setCreatedAt(status.getCreatedAt());
setContent(status.getContent(), status.getMentions(), listener);
setContent(status.getContent(), status.getMentions(), status.getEmojis(), listener);
setAvatar(status.getAvatar(), status.getRebloggedAvatar());
setReblogged(status.isReblogged());
setFavourited(status.isFavourited());
@ -478,4 +512,58 @@ class StatusBaseViewHolder extends RecyclerView.ViewHolder {
setSpoilerText(status.getSpoilerText(), status.isExpanded(), listener);
}
}
static class EmojiSpan extends ReplacementSpan implements Target {
private @Nullable
Drawable imageDrawable;
private WeakReference<Callback> callbackWeakReference;
public void setImageDrawable(@Nullable Drawable imageDrawable) {
this.imageDrawable = imageDrawable;
}
public void setCallback(Callback callback) {
this.callbackWeakReference = new WeakReference<Callback>(callback);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
@Nullable Paint.FontMetricsInt fm) {
if (imageDrawable == null) return 0;
Rect sizeRect = imageDrawable.getBounds();
return sizeRect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable == null) return;
canvas.save();
int transY = bottom - imageDrawable.getBounds().bottom;
transY -= paint.getFontMetricsInt().descent;
canvas.translate(x, transY);
imageDrawable.draw(canvas);
canvas.restore();
}
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
imageDrawable = new BitmapDrawable(bitmap);
imageDrawable.setBounds(0, 0, imageDrawable.getIntrinsicWidth() + 10,
imageDrawable.getIntrinsicHeight() + 10);
if (callbackWeakReference != null) {
Callback cb = callbackWeakReference.get();
if (cb != null) cb.onSuccess();
}
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
}
}