2015-05-06 13:53:55 -07:00
|
|
|
package org.thoughtcrime.securesms.components.emoji;
|
|
|
|
|
2015-05-21 18:27:31 -07:00
|
|
|
import android.annotation.TargetApi;
|
2015-05-06 13:53:55 -07:00
|
|
|
import android.content.Context;
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
import android.graphics.Canvas;
|
|
|
|
import android.graphics.ColorFilter;
|
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.graphics.PixelFormat;
|
|
|
|
import android.graphics.Rect;
|
|
|
|
import android.graphics.drawable.Drawable;
|
2015-05-21 18:27:31 -07:00
|
|
|
import android.os.Build.VERSION;
|
|
|
|
import android.os.Build.VERSION_CODES;
|
2015-08-06 10:35:51 -07:00
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
import android.support.annotation.Nullable;
|
2015-05-06 13:53:55 -07:00
|
|
|
import android.text.Spannable;
|
|
|
|
import android.text.SpannableStringBuilder;
|
|
|
|
import android.util.Log;
|
2015-07-10 14:58:05 -07:00
|
|
|
import android.widget.TextView;
|
2015-05-06 13:53:55 -07:00
|
|
|
|
|
|
|
import org.thoughtcrime.securesms.R;
|
2016-12-10 16:53:10 -08:00
|
|
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
|
|
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiPageBitmap;
|
|
|
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
|
|
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree;
|
2015-05-14 21:08:37 -07:00
|
|
|
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
2015-05-06 13:53:55 -07:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
|
|
|
2016-11-09 09:37:40 -08:00
|
|
|
import java.util.concurrent.ExecutionException;
|
2016-12-10 16:53:10 -08:00
|
|
|
|
|
|
|
class EmojiProvider {
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2015-05-14 21:08:37 -07:00
|
|
|
private static final String TAG = EmojiProvider.class.getSimpleName();
|
|
|
|
private static volatile EmojiProvider instance = null;
|
2015-05-21 18:27:31 -07:00
|
|
|
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
private final EmojiTree emojiTree = new EmojiTree();
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
private static final int EMOJI_RAW_HEIGHT = 64;
|
|
|
|
private static final int EMOJI_RAW_WIDTH = 64;
|
|
|
|
private static final int EMOJI_VERT_PAD = 0;
|
|
|
|
private static final int EMOJI_PER_ROW = 32;
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
private final float decodeScale;
|
|
|
|
private final float verticalPad;
|
2015-05-06 13:53:55 -07:00
|
|
|
|
|
|
|
public static EmojiProvider getInstance(Context context) {
|
|
|
|
if (instance == null) {
|
|
|
|
synchronized (EmojiProvider.class) {
|
|
|
|
if (instance == null) {
|
|
|
|
instance = new EmojiProvider(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
private EmojiProvider(Context context) {
|
2015-07-10 14:58:05 -07:00
|
|
|
this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
|
|
|
|
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
2016-12-10 16:53:10 -08:00
|
|
|
|
2015-05-21 17:29:23 -07:00
|
|
|
for (EmojiPageModel page : EmojiPages.PAGES) {
|
|
|
|
if (page.hasSpriteMap()) {
|
2016-12-10 16:53:10 -08:00
|
|
|
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
|
|
|
|
|
|
|
|
for (int i=0;i<page.getEmoji().length;i++) {
|
|
|
|
emojiTree.add(page.getEmoji()[i], new EmojiDrawInfo(pageBitmap, i));
|
2015-05-21 17:29:23 -07:00
|
|
|
}
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-25 19:09:35 -05:00
|
|
|
@Nullable EmojiParser.CandidateList getCandidates(@Nullable CharSequence text) {
|
2015-08-06 10:35:51 -07:00
|
|
|
if (text == null) return null;
|
2017-04-25 19:09:35 -05:00
|
|
|
return new EmojiParser(emojiTree).findCandidates(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
|
|
|
|
return emojify(getCandidates(text), text, tv);
|
|
|
|
}
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2017-04-25 19:09:35 -05:00
|
|
|
@Nullable Spannable emojify(@Nullable EmojiParser.CandidateList matches,
|
|
|
|
@Nullable CharSequence text,
|
|
|
|
@NonNull TextView tv) {
|
|
|
|
if (matches == null || text == null) return null;
|
2016-12-10 16:53:10 -08:00
|
|
|
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
|
|
|
|
|
|
|
for (EmojiParser.Candidate candidate : matches) {
|
|
|
|
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo());
|
|
|
|
|
2015-05-06 13:53:55 -07:00
|
|
|
if (drawable != null) {
|
2016-12-10 16:53:10 -08:00
|
|
|
builder.setSpan(new EmojiSpan(drawable, tv), candidate.getStartIndex(), candidate.getEndIndex(),
|
2015-05-06 13:53:55 -07:00
|
|
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder;
|
|
|
|
}
|
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
@Nullable Drawable getEmojiDrawable(CharSequence emoji) {
|
|
|
|
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
|
|
|
|
return getEmojiDrawable(drawInfo);
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
private @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo) {
|
2015-05-21 17:29:23 -07:00
|
|
|
if (drawInfo == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2015-05-14 21:08:37 -07:00
|
|
|
|
2015-07-10 14:58:05 -07:00
|
|
|
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
|
2016-12-10 16:53:10 -08:00
|
|
|
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
2015-05-14 21:08:37 -07:00
|
|
|
@Override public void onSuccess(final Bitmap result) {
|
2015-05-21 18:27:31 -07:00
|
|
|
Util.runOnMain(new Runnable() {
|
2015-05-14 21:08:37 -07:00
|
|
|
@Override public void run() {
|
|
|
|
drawable.setBitmap(result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-11-09 09:37:40 -08:00
|
|
|
@Override public void onFailure(ExecutionException error) {
|
2015-05-14 21:08:37 -07:00
|
|
|
Log.w(TAG, error);
|
|
|
|
}
|
|
|
|
});
|
2015-05-06 13:53:55 -07:00
|
|
|
return drawable;
|
|
|
|
}
|
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
class EmojiDrawable extends Drawable {
|
|
|
|
private final EmojiDrawInfo info;
|
|
|
|
private Bitmap bmp;
|
|
|
|
private float intrinsicWidth;
|
|
|
|
private float intrinsicHeight;
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
@Override
|
|
|
|
public int getIntrinsicWidth() {
|
2015-07-10 14:58:05 -07:00
|
|
|
return (int)intrinsicWidth;
|
2015-05-14 21:08:37 -07:00
|
|
|
}
|
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
@Override
|
|
|
|
public int getIntrinsicHeight() {
|
2015-07-10 14:58:05 -07:00
|
|
|
return (int)intrinsicHeight;
|
2015-05-14 21:08:37 -07:00
|
|
|
}
|
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
EmojiDrawable(EmojiDrawInfo info, float decodeScale) {
|
2015-07-10 14:58:05 -07:00
|
|
|
this.info = info;
|
|
|
|
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
|
|
|
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2016-12-10 16:53:10 -08:00
|
|
|
public void draw(@NonNull Canvas canvas) {
|
2015-05-21 18:27:31 -07:00
|
|
|
if (bmp == null) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-06 13:53:55 -07:00
|
|
|
|
2016-12-10 16:53:10 -08:00
|
|
|
final int row = info.getIndex() / EMOJI_PER_ROW;
|
|
|
|
final int row_index = info.getIndex() % EMOJI_PER_ROW;
|
2015-05-06 13:53:55 -07:00
|
|
|
|
|
|
|
canvas.drawBitmap(bmp,
|
2015-07-10 14:58:05 -07:00
|
|
|
new Rect((int)(row_index * intrinsicWidth),
|
|
|
|
(int)(row * intrinsicHeight + row * verticalPad),
|
|
|
|
(int)((row_index + 1) * intrinsicWidth),
|
|
|
|
(int)((row + 1) * intrinsicHeight + row * verticalPad)),
|
2015-05-21 18:27:31 -07:00
|
|
|
getBounds(),
|
2015-05-06 13:53:55 -07:00
|
|
|
paint);
|
|
|
|
}
|
|
|
|
|
2015-05-21 18:27:31 -07:00
|
|
|
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
|
2015-05-14 21:08:37 -07:00
|
|
|
public void setBitmap(Bitmap bitmap) {
|
|
|
|
Util.assertMainThread();
|
2015-05-21 18:27:31 -07:00
|
|
|
if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) {
|
|
|
|
bmp = bitmap;
|
|
|
|
invalidateSelf();
|
|
|
|
}
|
2015-05-14 21:08:37 -07:00
|
|
|
}
|
|
|
|
|
2015-05-06 13:53:55 -07:00
|
|
|
@Override
|
|
|
|
public int getOpacity() {
|
|
|
|
return PixelFormat.TRANSLUCENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setAlpha(int alpha) { }
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setColorFilter(ColorFilter cf) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|