Updated emoji set
// FREEBIE
Before Width: | Height: | Size: 922 KiB |
Before Width: | Height: | Size: 681 KiB |
Before Width: | Height: | Size: 612 KiB |
Before Width: | Height: | Size: 511 KiB |
Before Width: | Height: | Size: 546 KiB |
BIN
assets/emoji/Activity.png
Normal file
After Width: | Height: | Size: 286 KiB |
BIN
assets/emoji/Flags.png
Normal file
After Width: | Height: | Size: 777 KiB |
BIN
assets/emoji/Foods.png
Normal file
After Width: | Height: | Size: 372 KiB |
BIN
assets/emoji/Nature.png
Normal file
After Width: | Height: | Size: 781 KiB |
BIN
assets/emoji/Objects.png
Normal file
After Width: | Height: | Size: 783 KiB |
BIN
assets/emoji/People.png
Normal file
After Width: | Height: | Size: 846 KiB |
BIN
assets/emoji/Places.png
Normal file
After Width: | Height: | Size: 540 KiB |
BIN
assets/emoji/Symbols.png
Normal file
After Width: | Height: | Size: 737 KiB |
BIN
res/drawable-hdpi/ic_directions_bike_white_24dp.png
Normal file
After Width: | Height: | Size: 494 B |
BIN
res/drawable-hdpi/ic_flag_white_24dp.png
Normal file
After Width: | Height: | Size: 135 B |
BIN
res/drawable-hdpi/ic_lightbulb_outline_white_24dp.png
Normal file
After Width: | Height: | Size: 362 B |
BIN
res/drawable-hdpi/ic_restaurant_white_24dp.png
Normal file
After Width: | Height: | Size: 228 B |
BIN
res/drawable-mdpi/ic_directions_bike_white_24dp.png
Normal file
After Width: | Height: | Size: 328 B |
BIN
res/drawable-mdpi/ic_flag_white_24dp.png
Normal file
After Width: | Height: | Size: 100 B |
BIN
res/drawable-mdpi/ic_lightbulb_outline_white_24dp.png
Normal file
After Width: | Height: | Size: 246 B |
BIN
res/drawable-mdpi/ic_restaurant_white_24dp.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
res/drawable-xhdpi/ic_directions_bike_white_24dp.png
Normal file
After Width: | Height: | Size: 625 B |
BIN
res/drawable-xhdpi/ic_flag_white_24dp.png
Normal file
After Width: | Height: | Size: 134 B |
BIN
res/drawable-xhdpi/ic_lightbulb_outline_white_24dp.png
Normal file
After Width: | Height: | Size: 437 B |
BIN
res/drawable-xhdpi/ic_restaurant_white_24dp.png
Normal file
After Width: | Height: | Size: 244 B |
BIN
res/drawable-xxhdpi/ic_directions_bike_white_24dp.png
Normal file
After Width: | Height: | Size: 952 B |
BIN
res/drawable-xxhdpi/ic_flag_white_24dp.png
Normal file
After Width: | Height: | Size: 168 B |
BIN
res/drawable-xxhdpi/ic_lightbulb_outline_white_24dp.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
res/drawable-xxhdpi/ic_restaurant_white_24dp.png
Normal file
After Width: | Height: | Size: 336 B |
BIN
res/drawable-xxxhdpi/ic_directions_bike_white_24dp.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xxxhdpi/ic_flag_white_24dp.png
Normal file
After Width: | Height: | Size: 200 B |
BIN
res/drawable-xxxhdpi/ic_lightbulb_outline_white_24dp.png
Normal file
After Width: | Height: | Size: 854 B |
BIN
res/drawable-xxxhdpi/ic_restaurant_white_24dp.png
Normal file
After Width: | Height: | Size: 413 B |
14
res/drawable/emoji_category_activity_dark.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_directions_bike_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_directions_bike_white_24dp"
|
||||
android:tint="@color/grey_700"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
14
res/drawable/emoji_category_activity_light.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_directions_bike_white_24dp"
|
||||
android:tint="@color/grey_800"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_directions_bike_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
14
res/drawable/emoji_category_flags_dark.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_flag_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_flag_white_24dp"
|
||||
android:tint="@color/grey_700"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
14
res/drawable/emoji_category_flags_light.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_flag_white_24dp"
|
||||
android:tint="@color/grey_800"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_flag_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
14
res/drawable/emoji_category_foods_dark.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_restaurant_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_restaurant_white_24dp"
|
||||
android:tint="@color/grey_700"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
14
res/drawable/emoji_category_foods_light.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_restaurant_white_24dp"
|
||||
android:tint="@color/grey_800"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_restaurant_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
@ -2,6 +2,13 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true" android:drawable="@drawable/ic_emoji_objects_activated_dark" />
|
||||
<item android:drawable="@drawable/ic_emoji_objects_normal_dark" />
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_lightbulb_outline_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_lightbulb_outline_white_24dp"
|
||||
android:tint="@color/grey_700"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
|
@ -2,6 +2,13 @@
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="200"
|
||||
android:exitFadeDuration="300">
|
||||
<item android:state_selected="true" android:drawable="@drawable/ic_emoji_objects_activated_light" />
|
||||
<item android:drawable="@drawable/ic_emoji_objects_normal_light" />
|
||||
<item android:state_selected="true">
|
||||
<bitmap android:src="@drawable/ic_lightbulb_outline_white_24dp"
|
||||
android:tint="@color/grey_800"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:src="@drawable/ic_lightbulb_outline_white_24dp"
|
||||
android:tint="@color/grey_400"/>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
|
@ -52,10 +52,13 @@
|
||||
|
||||
<attr name="emoji_category_recent" format="reference"/>
|
||||
<attr name="emoji_category_people" format="reference"/>
|
||||
<attr name="emoji_category_objects" format="reference"/>
|
||||
<attr name="emoji_category_nature" format="reference"/>
|
||||
<attr name="emoji_category_foods" format="reference"/>
|
||||
<attr name="emoji_category_activity" format="reference"/>
|
||||
<attr name="emoji_category_places" format="reference"/>
|
||||
<attr name="emoji_category_objects" format="reference"/>
|
||||
<attr name="emoji_category_symbol" format="reference"/>
|
||||
<attr name="emoji_category_flags" format="reference"/>
|
||||
<attr name="emoji_category_emoticons" format="reference"/>
|
||||
<attr name="quick_camera_icon" format="reference"/>
|
||||
<attr name="quick_mic_icon" format="reference"/>
|
||||
|
@ -137,10 +137,13 @@
|
||||
|
||||
<item name="emoji_category_recent">@drawable/emoji_category_recent_light</item>
|
||||
<item name="emoji_category_people">@drawable/emoji_category_people_light</item>
|
||||
<item name="emoji_category_objects">@drawable/emoji_category_objects_light</item>
|
||||
<item name="emoji_category_nature">@drawable/emoji_category_nature_light</item>
|
||||
<item name="emoji_category_foods">@drawable/emoji_category_foods_light</item>
|
||||
<item name="emoji_category_activity">@drawable/emoji_category_activity_light</item>
|
||||
<item name="emoji_category_places">@drawable/emoji_category_places_light</item>
|
||||
<item name="emoji_category_objects">@drawable/emoji_category_objects_light</item>
|
||||
<item name="emoji_category_symbol">@drawable/emoji_category_symbol_light</item>
|
||||
<item name="emoji_category_flags">@drawable/emoji_category_flags_light</item>
|
||||
<item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_light</item>
|
||||
|
||||
|
||||
@ -280,10 +283,13 @@
|
||||
|
||||
<item name="emoji_category_recent">@drawable/emoji_category_recent_dark</item>
|
||||
<item name="emoji_category_people">@drawable/emoji_category_people_dark</item>
|
||||
<item name="emoji_category_objects">@drawable/emoji_category_objects_dark</item>
|
||||
<item name="emoji_category_nature">@drawable/emoji_category_nature_dark</item>
|
||||
<item name="emoji_category_foods">@drawable/emoji_category_foods_dark</item>
|
||||
<item name="emoji_category_activity">@drawable/emoji_category_activity_dark</item>
|
||||
<item name="emoji_category_places">@drawable/emoji_category_places_dark</item>
|
||||
<item name="emoji_category_objects">@drawable/emoji_category_objects_dark</item>
|
||||
<item name="emoji_category_symbol">@drawable/emoji_category_symbol_dark</item>
|
||||
<item name="emoji_category_flags">@drawable/emoji_category_flags_dark</item>
|
||||
<item name="emoji_category_emoticons">@drawable/emoji_category_emoticons_dark</item>
|
||||
|
||||
<item name="quick_camera_icon">@drawable/quick_camera_dark</item>
|
||||
|
@ -68,7 +68,8 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
||||
|
||||
RepeatableImageKey backspace = (RepeatableImageKey)v.findViewById(R.id.backspace);
|
||||
backspace.setOnKeyEventListener(new KeyEventListener() {
|
||||
@Override public void onKeyEvent() {
|
||||
@Override
|
||||
public void onKeyEvent() {
|
||||
if (listener != null) listener.onKeyEvent(DELETE_KEY_EVENT);
|
||||
}
|
||||
});
|
||||
@ -101,7 +102,8 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
||||
pager.setAdapter(new EmojiPagerAdapter(getContext(),
|
||||
models,
|
||||
new EmojiSelectionListener() {
|
||||
@Override public void onEmojiSelected(String emoji) {
|
||||
@Override
|
||||
public void onEmojiSelected(String emoji) {
|
||||
Log.w("EmojiDrawer", "onEmojiSelected()");
|
||||
recentModel.onCodePointSelected(emoji);
|
||||
if (listener != null) listener.onEmojiSelected(emoji);
|
||||
@ -143,7 +145,8 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
||||
return pages.size();
|
||||
}
|
||||
|
||||
@Override public Object instantiateItem(ViewGroup container, int position) {
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
EmojiPageView page = new EmojiPageView(context);
|
||||
page.setModel(pages.get(position));
|
||||
page.setEmojiSelectedListener(listener);
|
||||
@ -151,21 +154,25 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
container.removeView((View)object);
|
||||
}
|
||||
|
||||
@Override public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
EmojiPageView current = (EmojiPageView) object;
|
||||
current.onSelected();
|
||||
super.setPrimaryItem(container, position, object);
|
||||
}
|
||||
|
||||
@Override public boolean isViewFromObject(View view, Object object) {
|
||||
@Override
|
||||
public boolean isViewFromObject(View view, Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
@Override public View getCustomTabView(ViewGroup viewGroup, int i) {
|
||||
@Override
|
||||
public View getCustomTabView(ViewGroup viewGroup, int i) {
|
||||
ImageView image = new ImageView(context);
|
||||
image.setScaleType(ScaleType.CENTER_INSIDE);
|
||||
image.setImageResource(ResUtil.getDrawableRes(context, pages.get(i).getIconAttr()));
|
||||
|
@ -13,15 +13,18 @@ public class EmojiFilter implements InputFilter {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override public CharSequence filter(CharSequence source, int start, int end,
|
||||
Spanned dest, int dstart, int dend)
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
|
||||
{
|
||||
char[] v = new char[end - start];
|
||||
TextUtils.getChars(source, start, end, v, 0);
|
||||
|
||||
Spannable emojified = EmojiProvider.getInstance(view.getContext()).emojify(new String(v), view);
|
||||
|
||||
if (source instanceof Spanned && emojified != null) {
|
||||
TextUtils.copySpansFrom((Spanned) source, start, end, null, emojified, 0);
|
||||
}
|
||||
|
||||
return emojified;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -17,40 +16,31 @@ import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
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;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.List;
|
||||
|
||||
class EmojiProvider {
|
||||
|
||||
public class EmojiProvider {
|
||||
private static final String TAG = EmojiProvider.class.getSimpleName();
|
||||
private static volatile EmojiProvider instance = null;
|
||||
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final SparseArray<DrawInfo> offsets = new SparseArray<>();
|
||||
private final EmojiTree emojiTree = new EmojiTree();
|
||||
|
||||
@SuppressWarnings("MalformedRegex")
|
||||
// 0x203c,0x2049 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
|
||||
// |== !!, ?! ==||==== misc ====||======== emoticons ========||========= flags ==========|
|
||||
private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u203c\\u2049\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
|
||||
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;
|
||||
|
||||
public static final int EMOJI_RAW_HEIGHT = 64;
|
||||
public static final int EMOJI_RAW_WIDTH = 64;
|
||||
public static final int EMOJI_VERT_PAD = 0;
|
||||
public static final int EMOJI_PER_ROW = 32;
|
||||
|
||||
private final Context context;
|
||||
private final float decodeScale;
|
||||
private final float verticalPad;
|
||||
|
||||
@ -66,29 +56,31 @@ public class EmojiProvider {
|
||||
}
|
||||
|
||||
private EmojiProvider(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
|
||||
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
||||
|
||||
for (EmojiPageModel page : EmojiPages.PAGES) {
|
||||
if (page.hasSpriteMap()) {
|
||||
final EmojiPageBitmap pageBitmap = new EmojiPageBitmap(page);
|
||||
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
|
||||
|
||||
for (int i=0;i<page.getEmoji().length;i++) {
|
||||
offsets.put(Character.codePointAt(page.getEmoji()[i], 0), new DrawInfo(pageBitmap, i));
|
||||
emojiTree.add(page.getEmoji()[i], new EmojiDrawInfo(pageBitmap, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
|
||||
@Nullable Spannable emojify(@Nullable CharSequence text, @NonNull TextView tv) {
|
||||
if (text == null) return null;
|
||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
||||
|
||||
List<EmojiParser.Candidate> matches = new EmojiParser(emojiTree).findCandidates(text);
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
|
||||
while (matches.find()) {
|
||||
int codePoint = matches.group().codePointAt(0);
|
||||
Drawable drawable = getEmojiDrawable(codePoint);
|
||||
for (EmojiParser.Candidate candidate : matches) {
|
||||
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo());
|
||||
|
||||
if (drawable != null) {
|
||||
builder.setSpan(new EmojiSpan(drawable, tv), matches.start(), matches.end(),
|
||||
builder.setSpan(new EmojiSpan(drawable, tv), candidate.getStartIndex(), candidate.getEndIndex(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
@ -96,17 +88,18 @@ public class EmojiProvider {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public Drawable getEmojiDrawable(int emojiCode) {
|
||||
return getEmojiDrawable(offsets.get(emojiCode));
|
||||
@Nullable Drawable getEmojiDrawable(CharSequence emoji) {
|
||||
EmojiDrawInfo drawInfo = emojiTree.getEmoji(emoji, 0, emoji.length());
|
||||
return getEmojiDrawable(drawInfo);
|
||||
}
|
||||
|
||||
private Drawable getEmojiDrawable(DrawInfo drawInfo) {
|
||||
private @Nullable Drawable getEmojiDrawable(@Nullable EmojiDrawInfo drawInfo) {
|
||||
if (drawInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
|
||||
drawInfo.page.get().addListener(new FutureTaskListener<Bitmap>() {
|
||||
drawInfo.getPage().get().addListener(new FutureTaskListener<Bitmap>() {
|
||||
@Override public void onSuccess(final Bitmap result) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override public void run() {
|
||||
@ -122,34 +115,36 @@ public class EmojiProvider {
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public class EmojiDrawable extends Drawable {
|
||||
private final DrawInfo info;
|
||||
class EmojiDrawable extends Drawable {
|
||||
private final EmojiDrawInfo info;
|
||||
private Bitmap bmp;
|
||||
private float intrinsicWidth;
|
||||
private float intrinsicHeight;
|
||||
|
||||
@Override public int getIntrinsicWidth() {
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return (int)intrinsicWidth;
|
||||
}
|
||||
|
||||
@Override public int getIntrinsicHeight() {
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return (int)intrinsicHeight;
|
||||
}
|
||||
|
||||
public EmojiDrawable(DrawInfo info, float decodeScale) {
|
||||
EmojiDrawable(EmojiDrawInfo info, float decodeScale) {
|
||||
this.info = info;
|
||||
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
||||
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (bmp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int row = info.index / EMOJI_PER_ROW;
|
||||
final int row_index = info.index % EMOJI_PER_ROW;
|
||||
final int row = info.getIndex() / EMOJI_PER_ROW;
|
||||
final int row_index = info.getIndex() % EMOJI_PER_ROW;
|
||||
|
||||
canvas.drawBitmap(bmp,
|
||||
new Rect((int)(row_index * intrinsicWidth),
|
||||
@ -181,85 +176,4 @@ public class EmojiProvider {
|
||||
public void setColorFilter(ColorFilter cf) { }
|
||||
}
|
||||
|
||||
private static class DrawInfo {
|
||||
EmojiPageBitmap page;
|
||||
int index;
|
||||
|
||||
public DrawInfo(final EmojiPageBitmap page, final int index) {
|
||||
this.page = page;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DrawInfo{" +
|
||||
"page=" + page +
|
||||
", index=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiPageBitmap {
|
||||
private EmojiPageModel model;
|
||||
private SoftReference<Bitmap> bitmapReference;
|
||||
private ListenableFutureTask<Bitmap> task;
|
||||
|
||||
public EmojiPageBitmap(EmojiPageModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
private ListenableFutureTask<Bitmap> get() {
|
||||
Util.assertMainThread();
|
||||
|
||||
if (bitmapReference != null && bitmapReference.get() != null) {
|
||||
return new ListenableFutureTask<>(bitmapReference.get());
|
||||
} else if (task != null) {
|
||||
return task;
|
||||
} else {
|
||||
Callable<Bitmap> callable = new Callable<Bitmap>() {
|
||||
@Override public Bitmap call() throws Exception {
|
||||
try {
|
||||
Log.w(TAG, "loading page " + model.getSprite());
|
||||
return loadPage();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override protected Void doInBackground(Void... params) {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void onPostExecute(Void aVoid) {
|
||||
task = null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private Bitmap loadPage() throws IOException {
|
||||
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
|
||||
|
||||
try {
|
||||
final Bitmap bitmap = BitmapUtil.createScaledBitmap(context,
|
||||
"file:///android_asset/" + model.getSprite(),
|
||||
decodeScale);
|
||||
bitmapReference = new SoftReference<>(bitmap);
|
||||
Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")");
|
||||
return bitmap;
|
||||
} catch (BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return model.getSprite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public class EmojiView extends View implements Drawable.Callback {
|
||||
public void setEmoji(String emoji) {
|
||||
this.emoji = emoji;
|
||||
this.drawable = EmojiProvider.getInstance(getContext())
|
||||
.getEmojiDrawable(Character.codePointAt(emoji, 0));
|
||||
.getEmojiDrawable(emoji);
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,31 @@
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class EmojiDrawInfo {
|
||||
|
||||
private final EmojiPageBitmap page;
|
||||
private final int index;
|
||||
|
||||
public EmojiDrawInfo(final @NonNull EmojiPageBitmap page, final int index) {
|
||||
this.page = page;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public @NonNull EmojiPageBitmap getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DrawInfo{" +
|
||||
"page=" + page +
|
||||
", index=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class EmojiPageBitmap {
|
||||
|
||||
private static final String TAG = EmojiPageBitmap.class.getName();
|
||||
|
||||
private final Context context;
|
||||
private final EmojiPageModel model;
|
||||
private final float decodeScale;
|
||||
|
||||
private SoftReference<Bitmap> bitmapReference;
|
||||
private ListenableFutureTask<Bitmap> task;
|
||||
|
||||
public EmojiPageBitmap(@NonNull Context context, @NonNull EmojiPageModel model, float decodeScale) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.model = model;
|
||||
this.decodeScale = decodeScale;
|
||||
}
|
||||
|
||||
public ListenableFutureTask<Bitmap> get() {
|
||||
Util.assertMainThread();
|
||||
|
||||
if (bitmapReference != null && bitmapReference.get() != null) {
|
||||
return new ListenableFutureTask<>(bitmapReference.get());
|
||||
} else if (task != null) {
|
||||
return task;
|
||||
} else {
|
||||
Callable<Bitmap> callable = new Callable<Bitmap>() {
|
||||
@Override public Bitmap call() throws Exception {
|
||||
try {
|
||||
Log.w(TAG, "loading page " + model.getSprite());
|
||||
return loadPage();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override protected Void doInBackground(Void... params) {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void onPostExecute(Void aVoid) {
|
||||
task = null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private Bitmap loadPage() throws IOException {
|
||||
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
|
||||
|
||||
try {
|
||||
final Bitmap bitmap = BitmapUtil.createScaledBitmap(context,
|
||||
"file:///android_asset/" + model.getSprite(),
|
||||
decodeScale);
|
||||
bitmapReference = new SoftReference<>(bitmap);
|
||||
Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")");
|
||||
return bitmap;
|
||||
} catch (BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return model.getSprite();
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2014-present Vincent DURMONT vdurmont@gmail.com
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Based in part on code from emoji-java
|
||||
*/
|
||||
public class EmojiParser {
|
||||
|
||||
private final EmojiTree emojiTree;
|
||||
|
||||
public EmojiParser(EmojiTree emojiTree) {
|
||||
this.emojiTree = emojiTree;
|
||||
}
|
||||
|
||||
public @NonNull List<Candidate> findCandidates(@Nullable CharSequence text) {
|
||||
List<Candidate> results = new LinkedList<>();
|
||||
|
||||
if (text == null) return results;
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
int emojiEnd = getEmojiEndPos(text, i);
|
||||
|
||||
if (emojiEnd != -1) {
|
||||
EmojiDrawInfo drawInfo = emojiTree.getEmoji(text, i, emojiEnd);
|
||||
|
||||
if (emojiEnd + 2 <= text.length()) {
|
||||
if (Fitzpatrick.fitzpatrickFromUnicode(text, emojiEnd) != null) {
|
||||
emojiEnd += 2;
|
||||
}
|
||||
}
|
||||
|
||||
results.add(new Candidate(i, emojiEnd, drawInfo));
|
||||
|
||||
i = emojiEnd - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private int getEmojiEndPos(CharSequence text, int startPos) {
|
||||
int best = -1;
|
||||
|
||||
for (int j = startPos + 1; j <= text.length(); j++) {
|
||||
EmojiTree.Matches status = emojiTree.isEmoji(text, startPos, j);
|
||||
|
||||
if (status.exactMatch()) {
|
||||
best = j;
|
||||
} else if (status.impossibleMatch()) {
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
public class Candidate {
|
||||
|
||||
private final int startIndex;
|
||||
private final int endIndex;
|
||||
private final EmojiDrawInfo drawInfo;
|
||||
|
||||
Candidate(int startIndex, int endIndex, EmojiDrawInfo drawInfo) {
|
||||
this.startIndex = startIndex;
|
||||
this.endIndex = endIndex;
|
||||
this.drawInfo = drawInfo;
|
||||
}
|
||||
|
||||
public EmojiDrawInfo getDrawInfo() {
|
||||
return drawInfo;
|
||||
}
|
||||
|
||||
public int getEndIndex() {
|
||||
return endIndex;
|
||||
}
|
||||
|
||||
public int getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2014-present Vincent DURMONT vdurmont@gmail.com
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Based in part on code from emoji-java
|
||||
*/
|
||||
public class EmojiTree {
|
||||
|
||||
private final EmojiTreeNode root = new EmojiTreeNode();
|
||||
|
||||
public void add(String emojiEncoding, EmojiDrawInfo emoji) {
|
||||
EmojiTreeNode tree = root;
|
||||
|
||||
for (char c: emojiEncoding.toCharArray()) {
|
||||
if (!tree.hasChild(c)) {
|
||||
tree.addChild(c);
|
||||
}
|
||||
|
||||
tree = tree.getChild(c);
|
||||
}
|
||||
|
||||
tree.setEmoji(emoji);
|
||||
}
|
||||
|
||||
public Matches isEmoji(CharSequence sequence, int startPosition, int endPosition) {
|
||||
if (sequence == null) {
|
||||
return Matches.POSSIBLY;
|
||||
}
|
||||
|
||||
EmojiTreeNode tree = root;
|
||||
|
||||
for (int i=startPosition; i<endPosition; i++) {
|
||||
char character = sequence.charAt(i);
|
||||
|
||||
if (!tree.hasChild(character)) {
|
||||
return Matches.IMPOSSIBLE;
|
||||
}
|
||||
|
||||
tree = tree.getChild(character);
|
||||
}
|
||||
|
||||
return tree.isEndOfEmoji() ? Matches.EXACTLY : Matches.POSSIBLY;
|
||||
}
|
||||
|
||||
public @Nullable EmojiDrawInfo getEmoji(CharSequence unicode, int startPosition, int endPostiion) {
|
||||
EmojiTreeNode tree = root;
|
||||
|
||||
for (int i=startPosition; i<endPostiion; i++) {
|
||||
char character = unicode.charAt(i);
|
||||
|
||||
if (!tree.hasChild(character)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
tree = tree.getChild(character);
|
||||
}
|
||||
|
||||
return tree.getEmoji();
|
||||
}
|
||||
|
||||
|
||||
private static class EmojiTreeNode {
|
||||
|
||||
private Map<Character, EmojiTreeNode> children = new HashMap<>();
|
||||
private EmojiDrawInfo emoji;
|
||||
|
||||
public void setEmoji(EmojiDrawInfo emoji) {
|
||||
this.emoji = emoji;
|
||||
}
|
||||
|
||||
public @Nullable EmojiDrawInfo getEmoji() {
|
||||
return emoji;
|
||||
}
|
||||
|
||||
boolean hasChild(char child) {
|
||||
return children.containsKey(child);
|
||||
}
|
||||
|
||||
void addChild(char child) {
|
||||
children.put(child, new EmojiTreeNode());
|
||||
}
|
||||
|
||||
EmojiTreeNode getChild(char child) {
|
||||
return children.get(child);
|
||||
}
|
||||
|
||||
boolean isEndOfEmoji() {
|
||||
return emoji != null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Matches {
|
||||
EXACTLY, POSSIBLY, IMPOSSIBLE;
|
||||
|
||||
public boolean exactMatch() {
|
||||
return this == EXACTLY;
|
||||
}
|
||||
|
||||
public boolean impossibleMatch() {
|
||||
return this == IMPOSSIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.thoughtcrime.securesms.components.emoji.parsing;
|
||||
|
||||
|
||||
public enum Fitzpatrick {
|
||||
/**
|
||||
* Fitzpatrick modifier of type 1/2 (pale white/white)
|
||||
*/
|
||||
TYPE_1_2("\uD83C\uDFFB"),
|
||||
|
||||
/**
|
||||
* Fitzpatrick modifier of type 3 (cream white)
|
||||
*/
|
||||
TYPE_3("\uD83C\uDFFC"),
|
||||
|
||||
/**
|
||||
* Fitzpatrick modifier of type 4 (moderate brown)
|
||||
*/
|
||||
TYPE_4("\uD83C\uDFFD"),
|
||||
|
||||
/**
|
||||
* Fitzpatrick modifier of type 5 (dark brown)
|
||||
*/
|
||||
TYPE_5("\uD83C\uDFFE"),
|
||||
|
||||
/**
|
||||
* Fitzpatrick modifier of type 6 (black)
|
||||
*/
|
||||
TYPE_6("\uD83C\uDFFF");
|
||||
|
||||
/**
|
||||
* The unicode representation of the Fitzpatrick modifier
|
||||
*/
|
||||
public final String unicode;
|
||||
|
||||
Fitzpatrick(String unicode) {
|
||||
this.unicode = unicode;
|
||||
}
|
||||
|
||||
|
||||
public static Fitzpatrick fitzpatrickFromUnicode(CharSequence unicode, int index) {
|
||||
for (Fitzpatrick v : values()) {
|
||||
boolean match = true;
|
||||
|
||||
for (int i=0;i<v.unicode.toCharArray().length;i++) {
|
||||
if (v.unicode.toCharArray()[i] != unicode.charAt(index + i)) {
|
||||
match = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) return v;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Fitzpatrick fitzpatrickFromType(String type) {
|
||||
try {
|
||||
return Fitzpatrick.valueOf(type.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|