Allow the selection of fitzpatrick emoji.

This commit is contained in:
Greyson Parrelli 2018-10-23 23:37:09 -07:00
parent 1999d09901
commit 48ff9673b9
20 changed files with 486 additions and 240 deletions

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="@color/core_white" />
</shape>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M24,0 L0,24 L24,24 Z"
android:strokeColor="#00000000"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:background="?selectableItemBackground">
<ImageView
android:id="@+id/emoji_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingLeft="6dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
<org.thoughtcrime.securesms.components.emoji.AsciiEmojiView
android:id="@+id/emoji_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingLeft="6dp"
android:visibility="gone"/>
<ImageView
android:id="@+id/emoji_variation_hint"
android:layout_width="7dp"
android:layout_height="7dp"
android:layout_gravity="bottom|right|end"
app:srcCompat="@drawable/triangle_bottom_right_corner"
android:tint="@color/core_grey_25"/>
</FrameLayout>

View File

@ -36,7 +36,7 @@
</LinearLayout> </LinearLayout>
<android.support.v4.view.ViewPager <org.thoughtcrime.securesms.components.ControllableViewPager
android:id="@+id/emoji_pager" android:id="@+id/emoji_pager"
android:visibility="visible" android:visibility="visible"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -4,15 +4,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<GridView android:id="@+id/emoji" <android.support.v7.widget.RecyclerView
android:visibility="visible" android:id="@+id/emoji"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:stretchMode="columnWidth" android:clipToPadding="false"
android:columnWidth="@dimen/emoji_drawer_size" android:paddingBottom="8dp"/>
android:horizontalSpacing="0dp"
android:verticalSpacing="0dp"
android:numColumns="auto_fit"
android:gravity="center"/>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/emoji_variation_selector_background_light">
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="6dp">
</ImageView>

View File

@ -8,6 +8,7 @@
<dimen name="emoji_drawer_item_padding">5dp</dimen> <dimen name="emoji_drawer_item_padding">5dp</dimen>
<dimen name="emoji_drawer_indicator_height">1.5dp</dimen> <dimen name="emoji_drawer_indicator_height">1.5dp</dimen>
<dimen name="emoji_drawer_left_right_padding">5dp</dimen> <dimen name="emoji_drawer_left_right_padding">5dp</dimen>
<dimen name="emoji_drawer_item_width">48dp</dimen>
<dimen name="conversation_item_body_text_size">16sp</dimen> <dimen name="conversation_item_body_text_size">16sp</dimen>
<dimen name="conversation_item_date_text_size">12sp</dimen> <dimen name="conversation_item_date_text_size">12sp</dimen>
<dimen name="transport_selection_popup_width">200sp</dimen> <dimen name="transport_selection_popup_width">200sp</dimen>

View File

@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
import androidx.annotation.Nullable;
public class AsciiEmojiView extends View {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private String emoji;
public AsciiEmojiView(Context context) {
super(context);
}
public AsciiEmojiView(Context context, @Nullable @android.support.annotation.Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
}
@Override
protected void onDraw(Canvas canvas) {
if (TextUtils.isEmpty(emoji)) {
return;
}
float targetFontSize = 0.75f * getHeight() - getPaddingTop() - getPaddingBottom();
paint.setTextSize(targetFontSize);
paint.setColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color));
paint.setTextAlign(Paint.Align.CENTER);
int xPos = (getWidth() / 2);
int yPos = (int) ((getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
float overflow = paint.measureText(emoji) / (getWidth() - getPaddingLeft() - getPaddingRight());
if (overflow > 1f) {
paint.setTextSize(targetFontSize / overflow);
yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
}
canvas.drawText(emoji, xPos, yPos, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}

View File

@ -0,0 +1,21 @@
package org.thoughtcrime.securesms.components.emoji;
import java.util.Arrays;
import java.util.List;
public class Emoji {
private final List<String> variations;
public Emoji(String... variations) {
this.variations = Arrays.asList(variations);
}
public String getValue() {
return variations.get(0);
}
public List<String> getVariations() {
return variations;
}
}

View File

@ -2,10 +2,11 @@ package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.util.AttributeSet; import android.util.AttributeSet;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -27,7 +28,7 @@ import org.thoughtcrime.securesms.util.ResUtil;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class EmojiDrawer extends LinearLayout implements InputView { public class EmojiDrawer extends LinearLayout implements InputView, EmojiSelectionListener, VariationSelectorListener {
private static final String TAG = EmojiDrawer.class.getSimpleName(); private static final String TAG = EmojiDrawer.class.getSimpleName();
@ -101,19 +102,23 @@ public class EmojiDrawer extends LinearLayout implements InputView {
Log.i(TAG, "hide()"); Log.i(TAG, "hide()");
} }
private void initializeEmojiGrid() { @Override
pager.setAdapter(new EmojiPagerAdapter(getContext(), public void onEmojiSelected(String emoji) {
models, recentModel.onCodePointSelected(emoji);
new EmojiSelectionListener() { if (listener != null) {
@Override listener.onEmojiSelected(emoji);
public void onEmojiSelected(String emoji) { }
Log.i(TAG, "onEmojiSelected()"); }
recentModel.onCodePointSelected(emoji);
if (listener != null) listener.onEmojiSelected(emoji);
}
}));
if (recentModel.getEmoji().length == 0) { @Override
public void onVariationSelectorStateChanged(boolean open) {
pager.setEnabled(!open);
}
private void initializeEmojiGrid() {
pager.setAdapter(new EmojiPagerAdapter(getContext(), models, this, this));
if (recentModel.getDisplayEmoji().size() == 0) {
pager.setCurrentItem(1); pager.setCurrentItem(1);
} }
strip.setViewPager(pager); strip.setViewPager(pager);
@ -129,18 +134,21 @@ public class EmojiDrawer extends LinearLayout implements InputView {
public static class EmojiPagerAdapter extends PagerAdapter public static class EmojiPagerAdapter extends PagerAdapter
implements PagerSlidingTabStrip.CustomTabProvider implements PagerSlidingTabStrip.CustomTabProvider
{ {
private Context context; private Context context;
private List<EmojiPageModel> pages; private List<EmojiPageModel> pages;
private EmojiSelectionListener listener; private EmojiSelectionListener emojiSelectionListener;
private VariationSelectorListener variationSelectorListener;
public EmojiPagerAdapter(@NonNull Context context, public EmojiPagerAdapter(@NonNull Context context,
@NonNull List<EmojiPageModel> pages, @NonNull List<EmojiPageModel> pages,
@Nullable EmojiSelectionListener listener) @NonNull EmojiSelectionListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener)
{ {
super(); super();
this.context = context; this.context = context;
this.pages = pages; this.pages = pages;
this.listener = listener; this.emojiSelectionListener = emojiSelectionListener;
this.variationSelectorListener = variationSelectorListener;
} }
@Override @Override
@ -149,10 +157,9 @@ public class EmojiDrawer extends LinearLayout implements InputView {
} }
@Override @Override
public Object instantiateItem(ViewGroup container, int position) { public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context); EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener);
page.setModel(pages.get(position)); page.setModel(pages.get(position));
page.setEmojiSelectedListener(listener);
container.addView(page); container.addView(page);
return page; return page;
} }

View File

@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import java.util.List;
public interface EmojiPageModel { public interface EmojiPageModel {
int getIconAttr(); int getIconAttr();
String[] getEmoji(); List<String> getEmoji();
String[] getDisplayEmoji(); List<Emoji> getDisplayEmoji();
boolean hasSpriteMap(); boolean hasSpriteMap();
String getSprite(); String getSprite();
boolean isDynamic(); boolean isDynamic();

View File

@ -1,106 +1,92 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.GridView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
public class EmojiPageView extends FrameLayout { public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
private static final String TAG = EmojiPageView.class.getSimpleName(); private static final String TAG = EmojiPageView.class.getSimpleName();
private EmojiPageModel model; private EmojiPageModel model;
private EmojiSelectionListener listener; private EmojiPageViewGridAdapter adapter;
private GridView grid; private RecyclerView recyclerView;
private GridLayoutManager layoutManager;
private RecyclerView.OnItemTouchListener scrollDisabler;
private VariationSelectorListener variationSelectorListener;
public EmojiPageView(Context context) { public EmojiPageView(@NonNull Context context,
this(context, null); @NonNull EmojiSelectionListener emojiSelectionListener,
} @NonNull VariationSelectorListener variationSelectorListener)
{
public EmojiPageView(Context context, AttributeSet attrs) { super(context);
this(context, attrs, 0);
}
public EmojiPageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true); final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
grid = (GridView) view.findViewById(R.id.emoji);
grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding)); this.variationSelectorListener = variationSelectorListener;
grid.setOnItemClickListener(new OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { recyclerView = view.findViewById(R.id.emoji);
if (listener != null) listener.onEmojiSelected(((EmojiView)view).getEmoji()); layoutManager = new GridLayoutManager(context, 8);
} scrollDisabler = new ScrollDisabler();
}); adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context),
new EmojiVariationSelectorPopup(context, emojiSelectionListener),
emojiSelectionListener,
this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
} }
public void onSelected() { public void onSelected() {
if (model.isDynamic() && grid != null && grid.getAdapter() != null) { if (model.isDynamic() && adapter != null) {
((EmojiGridAdapter)grid.getAdapter()).notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
} }
public void setModel(EmojiPageModel model) { public void setModel(EmojiPageModel model) {
this.model = model; this.model = model;
grid.setAdapter(new EmojiGridAdapter(getContext(), model)); adapter.setEmoji(model.getDisplayEmoji());
} }
public void setEmojiSelectedListener(EmojiSelectionListener listener) { @Override
this.listener = listener; protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
layoutManager.setSpanCount(Math.max(w / idealWidth, 1));
} }
private static class EmojiGridAdapter extends BaseAdapter { @Override
public void onVariationSelectorStateChanged(boolean open) {
protected final Context context; if (open) {
private final int emojiSize; recyclerView.addOnItemTouchListener(scrollDisabler);
private final EmojiPageModel model; } else {
post(() -> recyclerView.removeOnItemTouchListener(scrollDisabler));
public EmojiGridAdapter(Context context, EmojiPageModel model) {
this.context = context;
this.emojiSize = (int) context.getResources().getDimension(R.dimen.emoji_drawer_size);
this.model = model;
} }
@Override public int getCount() { if (variationSelectorListener != null) {
return model.getDisplayEmoji().length; variationSelectorListener.onVariationSelectorStateChanged(open);
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final EmojiView view;
final int pad = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
if (convertView != null && convertView instanceof EmojiView) {
view = (EmojiView)convertView;
} else {
final EmojiView emojiView = new EmojiView(context);
emojiView.setPadding(pad, pad, pad, pad);
emojiView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
view = emojiView;
}
view.setEmoji(model.getDisplayEmoji()[position]);
return view;
} }
} }
public interface EmojiSelectionListener { public interface EmojiSelectionListener {
void onEmojiSelected(String emoji); void onEmojiSelected(String emoji);
} }
private static class ScrollDisabler implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
return true;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
}
} }

View File

@ -0,0 +1,116 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener;
import java.util.ArrayList;
import java.util.List;
public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageViewGridAdapter.EmojiViewHolder> implements PopupWindow.OnDismissListener {
private final List<Emoji> emojiList;
private final EmojiProvider emojiProvider;
private final EmojiVariationSelectorPopup popup;
private final VariationSelectorListener variationSelectorListener;
private final EmojiSelectionListener emojiSelectionListener;
public EmojiPageViewGridAdapter(@NonNull EmojiProvider emojiProvider,
@NonNull EmojiVariationSelectorPopup popup,
@NonNull EmojiSelectionListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener)
{
this.emojiList = new ArrayList<>();
this.emojiProvider = emojiProvider;
this.popup = popup;
this.emojiSelectionListener = emojiSelectionListener;
this.variationSelectorListener = variationSelectorListener;
popup.setOnDismissListener(this);
}
@NonNull
@Override
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new EmojiViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.emoji_display_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull EmojiViewHolder viewHolder, int i) {
Emoji emoji = emojiList.get(i);
Drawable drawable = emojiProvider.getEmojiDrawable(emoji.getValue());
if (drawable != null) {
viewHolder.textView.setVisibility(View.GONE);
viewHolder.imageView.setVisibility(View.VISIBLE);
viewHolder.imageView.setImageDrawable(drawable);
} else {
viewHolder.textView.setVisibility(View.VISIBLE);
viewHolder.imageView.setVisibility(View.GONE);
viewHolder.textView.setEmoji(emoji.getValue());
}
viewHolder.itemView.setOnClickListener(v -> {
emojiSelectionListener.onEmojiSelected(emoji.getValue());
});
if (emoji.getVariations().size() > 1) {
viewHolder.itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(emoji.getVariations());
popup.showAsDropDown(viewHolder.itemView, 0, -(2 * viewHolder.itemView.getHeight()));
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
viewHolder.hintCorner.setVisibility(View.VISIBLE);
} else {
viewHolder.itemView.setOnLongClickListener(null);
viewHolder.hintCorner.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return emojiList.size();
}
public void setEmoji(@NonNull List<Emoji> emojiList) {
this.emojiList.clear();
this.emojiList.addAll(emojiList);
notifyDataSetChanged();
}
@Override
public void onDismiss() {
variationSelectorListener.onVariationSelectorStateChanged(false);
}
static class EmojiViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final AsciiEmojiView textView;
private final ImageView hintCorner;
public EmojiViewHolder(@NonNull View itemView) {
super(itemView);
this.imageView = itemView.findViewById(R.id.emoji_image);
this.textView = itemView.findViewById(R.id.emoji_text);
this.hintCorner = itemView.findViewById(R.id.emoji_variation_hint);
}
}
interface VariationSelectorListener {
void onVariationSelectorStateChanged(boolean open);
}
}

File diff suppressed because one or more lines are too long

View File

@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
class EmojiProvider { class EmojiProvider {
@ -64,8 +65,9 @@ class EmojiProvider {
if (page.hasSpriteMap()) { if (page.hasSpriteMap()) {
EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale); EmojiPageBitmap pageBitmap = new EmojiPageBitmap(context, page, decodeScale);
for (int i=0;i<page.getEmoji().length;i++) { List<String> emojis = page.getEmoji();
emojiTree.add(page.getEmoji()[i], new EmojiDrawInfo(pageBitmap, i)); for (int i = 0; i < emojis.size(); i++) {
emojiTree.add(emojis.get(i), new EmojiDrawInfo(pageBitmap, i));
} }
} }
} }

View File

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener;
import java.util.List;
public class EmojiVariationSelectorPopup extends PopupWindow {
private final Context context;
private final ViewGroup list;
private final EmojiSelectionListener listener;
public EmojiVariationSelectorPopup(@NonNull Context context, @NonNull EmojiSelectionListener listener) {
super(context);
this.context = context;
this.listener = listener;
this.list = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector, null);
setContentView(list);
setBackgroundDrawable(null);
setOutsideTouchable(true);
if (Build.VERSION.SDK_INT >= 21) {
setElevation(20);
}
}
public void setVariations(List<String> variations) {
list.removeAllViews();
for (String variation : variations) {
ImageView imageView = (ImageView) LayoutInflater.from(context).inflate(R.layout.emoji_variation_selector_item, list, false);
imageView.setImageDrawable(EmojiProvider.getInstance(context).getEmojiDrawable(variation));
imageView.setOnClickListener(v -> {
listener.onEmojiSelected(variation);
dismiss();
});
list.addView(imageView);
}
}
}

View File

@ -1,77 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ResUtil;
public class EmojiView extends View implements Drawable.Callback {
private String emoji;
private Drawable drawable;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
public EmojiView(Context context) {
this(context, null);
}
public EmojiView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EmojiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
this.drawable = EmojiProvider.getInstance(getContext())
.getEmojiDrawable(emoji);
postInvalidate();
}
public String getEmoji() {
return emoji;
}
@Override protected void onDraw(Canvas canvas) {
if (drawable != null) {
drawable.setBounds(getPaddingLeft(),
getPaddingTop(),
getWidth() - getPaddingRight(),
getHeight() - getPaddingBottom());
drawable.setCallback(this);
drawable.draw(canvas);
} else {
float targetFontSize = 0.75f * getHeight() - getPaddingTop() - getPaddingBottom();
paint.setTextSize(targetFontSize);
paint.setColor(ResUtil.getColor(getContext(), R.attr.emoji_text_color));
paint.setTextAlign(Paint.Align.CENTER);
int xPos = (canvas.getWidth() / 2);
int yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
float overflow = paint.measureText(emoji) /
(getWidth() - getPaddingLeft() - getPaddingRight());
if (overflow > 1f) {
paint.setTextSize(targetFontSize / overflow);
yPos = (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2));
}
canvas.drawText(emoji, xPos, yPos, paint);
}
}
@Override public void invalidateDrawable(@NonNull Drawable drawable) {
super.invalidateDrawable(drawable);
postInvalidate();
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@ -6,6 +6,7 @@ import android.os.AsyncTask;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.annimon.stream.Stream;
import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.type.TypeFactory;
@ -14,8 +15,11 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
public class RecentEmojiPageModel implements EmojiPageModel { public class RecentEmojiPageModel implements EmojiPageModel {
private static final String TAG = RecentEmojiPageModel.class.getSimpleName(); private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
@ -46,12 +50,14 @@ public class RecentEmojiPageModel implements EmojiPageModel {
return R.attr.emoji_category_recent; return R.attr.emoji_category_recent;
} }
@Override public String[] getEmoji() { @Override public List<String> getEmoji() {
return toReversePrimitiveArray(recentlyUsed); List<String> emoji = new ArrayList<>(recentlyUsed);
Collections.reverse(emoji);
return emoji;
} }
@Override public String[] getDisplayEmoji() { @Override public List<Emoji> getDisplayEmoji() {
return getEmoji(); return Stream.of(getEmoji()).map(Emoji::new).toList();
} }
@Override public boolean hasSpriteMap() { @Override public boolean hasSpriteMap() {

View File

@ -4,20 +4,30 @@ import android.support.annotation.AttrRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
public class StaticEmojiPageModel implements EmojiPageModel { import java.util.ArrayList;
@AttrRes private final int iconAttr; import java.util.Arrays;
@NonNull private final String[] emoji; import java.util.LinkedList;
@NonNull private final String[] displayEmoji; import java.util.List;
@Nullable private final String sprite;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] emoji, @Nullable String sprite) { public class StaticEmojiPageModel implements EmojiPageModel {
this(iconAttr, emoji, emoji, sprite); @AttrRes private final int iconAttr;
@NonNull private final List<Emoji> emoji;
@Nullable private final String sprite;
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] strings, @Nullable String sprite) {
List<Emoji> emoji = new ArrayList<>(strings.length);
for (String s : strings) {
emoji.add(new Emoji(s));
}
this.iconAttr = iconAttr;
this.emoji = emoji;
this.sprite = sprite;
} }
public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull String[] emoji, @NonNull String[] displayEmoji, @Nullable String sprite) { public StaticEmojiPageModel(@AttrRes int iconAttr, @NonNull Emoji[] emoji, @Nullable String sprite) {
this.iconAttr = iconAttr; this.iconAttr = iconAttr;
this.emoji = emoji; this.emoji = Arrays.asList(emoji);
this.displayEmoji = displayEmoji;
this.sprite = sprite; this.sprite = sprite;
} }
@ -26,13 +36,17 @@ public class StaticEmojiPageModel implements EmojiPageModel {
} }
@Override @Override
public @NonNull String[] getEmoji() { public @NonNull List<String> getEmoji() {
return emoji; List<String> emojis = new LinkedList<>();
for (Emoji e : emoji) {
emojis.addAll(e.getVariations());
}
return emojis;
} }
@Override @Override
public @NonNull String[] getDisplayEmoji() { public @NonNull List<Emoji> getDisplayEmoji() {
return displayEmoji; return emoji;
} }
@Override @Override