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>
|
@ -1,7 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:enterFadeDuration="200"
|
android:enterFadeDuration="200"
|
||||||
android:exitFadeDuration="300">
|
android:exitFadeDuration="300">
|
||||||
<item android:state_selected="true" android:drawable="@drawable/ic_emoji_objects_activated_dark" />
|
<item android:state_selected="true">
|
||||||
<item android:drawable="@drawable/ic_emoji_objects_normal_dark" />
|
<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>
|
</selector>
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:enterFadeDuration="200"
|
android:enterFadeDuration="200"
|
||||||
android:exitFadeDuration="300">
|
android:exitFadeDuration="300">
|
||||||
<item android:state_selected="true" android:drawable="@drawable/ic_emoji_objects_activated_light" />
|
<item android:state_selected="true">
|
||||||
<item android:drawable="@drawable/ic_emoji_objects_normal_light" />
|
<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>
|
</selector>
|
||||||
|
@ -52,10 +52,13 @@
|
|||||||
|
|
||||||
<attr name="emoji_category_recent" format="reference"/>
|
<attr name="emoji_category_recent" format="reference"/>
|
||||||
<attr name="emoji_category_people" 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_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_places" format="reference"/>
|
||||||
|
<attr name="emoji_category_objects" format="reference"/>
|
||||||
<attr name="emoji_category_symbol" 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="emoji_category_emoticons" format="reference"/>
|
||||||
<attr name="quick_camera_icon" format="reference"/>
|
<attr name="quick_camera_icon" format="reference"/>
|
||||||
<attr name="quick_mic_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_recent">@drawable/emoji_category_recent_light</item>
|
||||||
<item name="emoji_category_people">@drawable/emoji_category_people_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_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_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_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>
|
<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_recent">@drawable/emoji_category_recent_dark</item>
|
||||||
<item name="emoji_category_people">@drawable/emoji_category_people_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_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_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_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="emoji_category_emoticons">@drawable/emoji_category_emoticons_dark</item>
|
||||||
|
|
||||||
<item name="quick_camera_icon">@drawable/quick_camera_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);
|
RepeatableImageKey backspace = (RepeatableImageKey)v.findViewById(R.id.backspace);
|
||||||
backspace.setOnKeyEventListener(new KeyEventListener() {
|
backspace.setOnKeyEventListener(new KeyEventListener() {
|
||||||
@Override public void onKeyEvent() {
|
@Override
|
||||||
|
public void onKeyEvent() {
|
||||||
if (listener != null) listener.onKeyEvent(DELETE_KEY_EVENT);
|
if (listener != null) listener.onKeyEvent(DELETE_KEY_EVENT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -101,7 +102,8 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
|||||||
pager.setAdapter(new EmojiPagerAdapter(getContext(),
|
pager.setAdapter(new EmojiPagerAdapter(getContext(),
|
||||||
models,
|
models,
|
||||||
new EmojiSelectionListener() {
|
new EmojiSelectionListener() {
|
||||||
@Override public void onEmojiSelected(String emoji) {
|
@Override
|
||||||
|
public void onEmojiSelected(String emoji) {
|
||||||
Log.w("EmojiDrawer", "onEmojiSelected()");
|
Log.w("EmojiDrawer", "onEmojiSelected()");
|
||||||
recentModel.onCodePointSelected(emoji);
|
recentModel.onCodePointSelected(emoji);
|
||||||
if (listener != null) listener.onEmojiSelected(emoji);
|
if (listener != null) listener.onEmojiSelected(emoji);
|
||||||
@ -143,7 +145,8 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
|||||||
return pages.size();
|
return pages.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Object instantiateItem(ViewGroup container, int position) {
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
EmojiPageView page = new EmojiPageView(context);
|
EmojiPageView page = new EmojiPageView(context);
|
||||||
page.setModel(pages.get(position));
|
page.setModel(pages.get(position));
|
||||||
page.setEmojiSelectedListener(listener);
|
page.setEmojiSelectedListener(listener);
|
||||||
@ -151,22 +154,26 @@ public class EmojiDrawer extends LinearLayout implements InputView {
|
|||||||
return page;
|
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);
|
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;
|
EmojiPageView current = (EmojiPageView) object;
|
||||||
current.onSelected();
|
current.onSelected();
|
||||||
super.setPrimaryItem(container, position, object);
|
super.setPrimaryItem(container, position, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean isViewFromObject(View view, Object object) {
|
@Override
|
||||||
|
public boolean isViewFromObject(View view, Object object) {
|
||||||
return view == object;
|
return view == object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public View getCustomTabView(ViewGroup viewGroup, int i) {
|
@Override
|
||||||
ImageView image = new ImageView(context);
|
public View getCustomTabView(ViewGroup viewGroup, int i) {
|
||||||
|
ImageView image = new ImageView(context);
|
||||||
image.setScaleType(ScaleType.CENTER_INSIDE);
|
image.setScaleType(ScaleType.CENTER_INSIDE);
|
||||||
image.setImageResource(ResUtil.getDrawableRes(context, pages.get(i).getIconAttr()));
|
image.setImageResource(ResUtil.getDrawableRes(context, pages.get(i).getIconAttr()));
|
||||||
return image;
|
return image;
|
||||||
|
@ -13,15 +13,18 @@ public class EmojiFilter implements InputFilter {
|
|||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public CharSequence filter(CharSequence source, int start, int end,
|
@Override
|
||||||
Spanned dest, int dstart, int dend)
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
|
||||||
{
|
{
|
||||||
char[] v = new char[end - start];
|
char[] v = new char[end - start];
|
||||||
TextUtils.getChars(source, start, end, v, 0);
|
TextUtils.getChars(source, start, end, v, 0);
|
||||||
|
|
||||||
Spannable emojified = EmojiProvider.getInstance(view.getContext()).emojify(new String(v), view);
|
Spannable emojified = EmojiProvider.getInstance(view.getContext()).emojify(new String(v), view);
|
||||||
|
|
||||||
if (source instanceof Spanned && emojified != null) {
|
if (source instanceof Spanned && emojified != null) {
|
||||||
TextUtils.copySpansFrom((Spanned) source, start, end, null, emojified, 0);
|
TextUtils.copySpansFrom((Spanned) source, start, end, null, emojified, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emojified;
|
return emojified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@ -17,42 +16,33 @@ import android.support.annotation.Nullable;
|
|||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
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.FutureTaskListener;
|
||||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.List;
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.util.concurrent.Callable;
|
class EmojiProvider {
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class EmojiProvider {
|
|
||||||
private static final String TAG = EmojiProvider.class.getSimpleName();
|
private static final String TAG = EmojiProvider.class.getSimpleName();
|
||||||
private static volatile EmojiProvider instance = null;
|
private static volatile EmojiProvider instance = null;
|
||||||
private static final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
|
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")
|
private static final int EMOJI_RAW_HEIGHT = 64;
|
||||||
// 0x203c,0x2049 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
|
private static final int EMOJI_RAW_WIDTH = 64;
|
||||||
// |== !!, ?! ==||==== misc ====||======== emoticons ========||========= flags ==========|
|
private static final int EMOJI_VERT_PAD = 0;
|
||||||
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_PER_ROW = 32;
|
||||||
|
|
||||||
public static final int EMOJI_RAW_HEIGHT = 64;
|
private final float decodeScale;
|
||||||
public static final int EMOJI_RAW_WIDTH = 64;
|
private final float verticalPad;
|
||||||
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;
|
|
||||||
|
|
||||||
public static EmojiProvider getInstance(Context context) {
|
public static EmojiProvider getInstance(Context context) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@ -66,29 +56,31 @@ public class EmojiProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EmojiProvider(Context context) {
|
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.decodeScale = Math.min(1f, context.getResources().getDimension(R.dimen.emoji_drawer_size) / EMOJI_RAW_HEIGHT);
|
||||||
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
this.verticalPad = EMOJI_VERT_PAD * this.decodeScale;
|
||||||
|
|
||||||
for (EmojiPageModel page : EmojiPages.PAGES) {
|
for (EmojiPageModel page : EmojiPages.PAGES) {
|
||||||
if (page.hasSpriteMap()) {
|
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));
|
for (int i=0;i<page.getEmoji().length;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;
|
if (text == null) return null;
|
||||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
|
||||||
|
|
||||||
while (matches.find()) {
|
List<EmojiParser.Candidate> matches = new EmojiParser(emojiTree).findCandidates(text);
|
||||||
int codePoint = matches.group().codePointAt(0);
|
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||||
Drawable drawable = getEmojiDrawable(codePoint);
|
|
||||||
|
for (EmojiParser.Candidate candidate : matches) {
|
||||||
|
Drawable drawable = getEmojiDrawable(candidate.getDrawInfo());
|
||||||
|
|
||||||
if (drawable != null) {
|
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);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,17 +88,18 @@ public class EmojiProvider {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable getEmojiDrawable(int emojiCode) {
|
@Nullable Drawable getEmojiDrawable(CharSequence emoji) {
|
||||||
return getEmojiDrawable(offsets.get(emojiCode));
|
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) {
|
if (drawInfo == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale);
|
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) {
|
@Override public void onSuccess(final Bitmap result) {
|
||||||
Util.runOnMain(new Runnable() {
|
Util.runOnMain(new Runnable() {
|
||||||
@Override public void run() {
|
@Override public void run() {
|
||||||
@ -122,34 +115,36 @@ public class EmojiProvider {
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EmojiDrawable extends Drawable {
|
class EmojiDrawable extends Drawable {
|
||||||
private final DrawInfo info;
|
private final EmojiDrawInfo info;
|
||||||
private Bitmap bmp;
|
private Bitmap bmp;
|
||||||
private float intrinsicWidth;
|
private float intrinsicWidth;
|
||||||
private float intrinsicHeight;
|
private float intrinsicHeight;
|
||||||
|
|
||||||
@Override public int getIntrinsicWidth() {
|
@Override
|
||||||
|
public int getIntrinsicWidth() {
|
||||||
return (int)intrinsicWidth;
|
return (int)intrinsicWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int getIntrinsicHeight() {
|
@Override
|
||||||
|
public int getIntrinsicHeight() {
|
||||||
return (int)intrinsicHeight;
|
return (int)intrinsicHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmojiDrawable(DrawInfo info, float decodeScale) {
|
EmojiDrawable(EmojiDrawInfo info, float decodeScale) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
this.intrinsicWidth = EMOJI_RAW_WIDTH * decodeScale;
|
||||||
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
this.intrinsicHeight = EMOJI_RAW_HEIGHT * decodeScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
if (bmp == null) {
|
if (bmp == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int row = info.index / EMOJI_PER_ROW;
|
final int row = info.getIndex() / EMOJI_PER_ROW;
|
||||||
final int row_index = info.index % EMOJI_PER_ROW;
|
final int row_index = info.getIndex() % EMOJI_PER_ROW;
|
||||||
|
|
||||||
canvas.drawBitmap(bmp,
|
canvas.drawBitmap(bmp,
|
||||||
new Rect((int)(row_index * intrinsicWidth),
|
new Rect((int)(row_index * intrinsicWidth),
|
||||||
@ -181,85 +176,4 @@ public class EmojiProvider {
|
|||||||
public void setColorFilter(ColorFilter cf) { }
|
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) {
|
public void setEmoji(String emoji) {
|
||||||
this.emoji = emoji;
|
this.emoji = emoji;
|
||||||
this.drawable = EmojiProvider.getInstance(getContext())
|
this.drawable = EmojiProvider.getInstance(getContext())
|
||||||
.getEmojiDrawable(Character.codePointAt(emoji, 0));
|
.getEmojiDrawable(emoji);
|
||||||
postInvalidate();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|