CustomEmojiHelper: rewrite to Kotlin (#1787)
* CustomEmojiHelper: rewrite to Kotlin * CustomEmojiHelper: PR fixes
This commit is contained in:
parent
e72bdcaf42
commit
2fc7ad13bb
18 changed files with 151 additions and 190 deletions
|
@ -1,146 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.SpannedString;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.View;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class CustomEmojiHelper {
|
||||
|
||||
/**
|
||||
* replaces emoji shortcodes in a text with EmojiSpans
|
||||
* @param text the text containing custom emojis
|
||||
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances)
|
||||
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable)
|
||||
* @return the text with the shortcodes replaced by EmojiSpans
|
||||
*/
|
||||
@NonNull
|
||||
public static Spanned emojifyText(@NonNull Spanned text, @Nullable List<Emoji> emojis, @NonNull final View view) {
|
||||
|
||||
if (emojis != null && !emojis.isEmpty()) {
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
for (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(view);
|
||||
builder.setSpan(span, matcher.start(), matcher.end(), 0);
|
||||
Glide.with(view)
|
||||
.asBitmap()
|
||||
.load(emoji.getUrl())
|
||||
.into(span.getTarget());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Spanned emojifyString(@NonNull String string, @Nullable List<Emoji> emojis, @NonNull final View ciew) {
|
||||
return emojifyText(new SpannedString(string), emojis, ciew);
|
||||
}
|
||||
|
||||
|
||||
public static class EmojiSpan extends ReplacementSpan {
|
||||
|
||||
@Nullable
|
||||
private Drawable imageDrawable;
|
||||
private WeakReference<View> viewWeakReference;
|
||||
|
||||
EmojiSpan(View view) {
|
||||
this.viewWeakReference = new WeakReference<>(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
|
||||
@Nullable Paint.FontMetricsInt fm) {
|
||||
|
||||
/* update FontMetricsInt or otherwise span does not get drawn when
|
||||
it covers the whole text */
|
||||
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
if (fm != null) {
|
||||
fm.top = metrics.top;
|
||||
fm.ascent = metrics.ascent;
|
||||
fm.descent = metrics.descent;
|
||||
fm.bottom = metrics.bottom;
|
||||
}
|
||||
|
||||
return (int) (paint.getTextSize()*1.2);
|
||||
}
|
||||
|
||||
@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 emojiSize = (int) (paint.getTextSize() * 1.1);
|
||||
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
|
||||
|
||||
int transY = bottom - imageDrawable.getBounds().bottom;
|
||||
transY -= paint.getFontMetricsInt().descent/2;
|
||||
canvas.translate(x, transY);
|
||||
imageDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
Target<Bitmap> getTarget(){
|
||||
return new CustomTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||
View view = viewWeakReference.get();
|
||||
if (view != null) {
|
||||
imageDrawable = new BitmapDrawable(view.getContext().getResources(), resource);
|
||||
view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
//Do nothing on load cleared
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/* Copyright 2020 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
@file:JvmName("CustomEmojiHelper")
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ReplacementSpan
|
||||
import android.view.View
|
||||
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* replaces emoji shortcodes in a text with EmojiSpans
|
||||
* @param text the text containing custom emojis
|
||||
* @param emojis a list of the custom emojis (nullable for backward compatibility with old mastodon instances)
|
||||
* @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable)
|
||||
* @return the text with the shortcodes replaced by EmojiSpans
|
||||
*/
|
||||
fun CharSequence.emojify(emojis: List<Emoji>?, view: View) : CharSequence {
|
||||
if(emojis.isNullOrEmpty())
|
||||
return this
|
||||
|
||||
val builder = SpannableStringBuilder.valueOf(this)
|
||||
|
||||
emojis.forEach { (shortcode, url) ->
|
||||
val matcher = Pattern.compile(":$shortcode:", Pattern.LITERAL)
|
||||
.matcher(this)
|
||||
|
||||
while(matcher.find()) {
|
||||
val span = EmojiSpan(WeakReference(view))
|
||||
|
||||
builder.setSpan(span, matcher.start(), matcher.end(), 0);
|
||||
Glide.with(view)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.into(span.getTarget())
|
||||
}
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
class EmojiSpan(val viewWeakReference: WeakReference<View>) : ReplacementSpan() {
|
||||
var imageDrawable: Drawable? = null
|
||||
|
||||
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?) : Int {
|
||||
if (fm != null) {
|
||||
/* update FontMetricsInt or otherwise span does not get drawn when
|
||||
* it covers the whole text */
|
||||
val metrics = paint.fontMetricsInt
|
||||
fm.top = metrics.top
|
||||
fm.ascent = metrics.ascent
|
||||
fm.descent = metrics.descent
|
||||
fm.bottom = metrics.bottom
|
||||
}
|
||||
|
||||
return (paint.textSize * 1.2).toInt()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
|
||||
imageDrawable?.let { drawable ->
|
||||
canvas.save()
|
||||
|
||||
val emojiSize = (paint.textSize * 1.1).toInt()
|
||||
drawable.setBounds(0, 0, emojiSize, emojiSize)
|
||||
|
||||
var transY = bottom - drawable.bounds.bottom
|
||||
transY -= paint.fontMetricsInt.descent / 2;
|
||||
|
||||
canvas.translate(x, transY.toFloat())
|
||||
drawable.draw(canvas)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTarget(): Target<Bitmap> {
|
||||
return object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
viewWeakReference.get()?.let { view ->
|
||||
imageDrawable = BitmapDrawable(view.context.resources, resource)
|
||||
view.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,10 +68,10 @@ public class LinkHelper {
|
|||
* @param mentions any '@' mentions which are known to be in the content
|
||||
* @param listener to notify about particular spans that are clicked
|
||||
*/
|
||||
public static void setClickableText(TextView view, Spanned content,
|
||||
public static void setClickableText(TextView view, CharSequence content,
|
||||
@Nullable Status.Mention[] mentions, final LinkListener listener) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||
SpannableStringBuilder builder = SpannableStringBuilder.valueOf(content);
|
||||
URLSpan[] urlSpans = builder.getSpans(0, content.length(), URLSpan.class);
|
||||
for (URLSpan span : urlSpans) {
|
||||
int start = builder.getSpanStart(span);
|
||||
int end = builder.getSpanEnd(span);
|
||||
|
|
|
@ -302,7 +302,7 @@ class StatusViewHelper(private val itemView: View) {
|
|||
val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount)
|
||||
|
||||
val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context)
|
||||
pollResults[i].text = CustomEmojiHelper.emojifyText(pollOptionText, emojis, pollResults[i])
|
||||
pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i])
|
||||
pollResults[i].visibility = View.VISIBLE
|
||||
|
||||
val level = percent * 100
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue