fix recent emoji pane
1) Make recent list properly update and invalidate. 2) Show most-recently-used first. 3) Refactoring Closes #3171 // FREEBIE
This commit is contained in:
parent
5ec9197912
commit
cf420de65f
11 changed files with 210 additions and 172 deletions
|
@ -16,7 +16,6 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.astuetz.PagerSlidingTabStrip;
|
||||
|
@ -38,6 +37,7 @@ public class EmojiDrawer extends Fragment {
|
|||
private KeyboardAwareLinearLayout container;
|
||||
private ViewPager pager;
|
||||
private PagerSlidingTabStrip strip;
|
||||
private RecentEmojiPageModel recentModel;
|
||||
|
||||
public static EmojiDrawer newInstance(@ArrayRes int categories, @ArrayRes int icons) {
|
||||
final EmojiDrawer fragment = new EmojiDrawer();
|
||||
|
@ -104,6 +104,7 @@ public class EmojiDrawer extends Fragment {
|
|||
getArguments().getInt("icons")),
|
||||
new EmojiSelectionListener() {
|
||||
@Override public void onEmojiSelected(int emojiCode) {
|
||||
recentModel.onCodePointSelected(emojiCode);
|
||||
composeText.insertEmoji(emojiCode);
|
||||
}
|
||||
}));
|
||||
|
@ -114,7 +115,8 @@ public class EmojiDrawer extends Fragment {
|
|||
final int[] icons = ResUtil.getResourceIds(getActivity(), iconsRes);
|
||||
final int[] pages = ResUtil.getResourceIds(getActivity(), pagesRes);
|
||||
final List<EmojiPageModel> models = new LinkedList<>();
|
||||
models.add(new RecentEmojiPageModel(getActivity()));
|
||||
recentModel = new RecentEmojiPageModel(getActivity());
|
||||
models.add(recentModel);
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
models.add(new StaticEmojiPageModel(icons[i], getResources().getIntArray(pages[i])));
|
||||
}
|
||||
|
|
|
@ -3,29 +3,26 @@ package org.thoughtcrime.securesms.components.emoji;
|
|||
import android.content.Context;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
|
||||
|
||||
public class EmojiEditText extends AppCompatEditText {
|
||||
public EmojiEditText(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public EmojiEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public EmojiEditText(Context context, AttributeSet attrs,
|
||||
int defStyleAttr)
|
||||
{
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
@Override public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(EmojiProvider.getInstance(getContext()).emojify(text, EmojiProvider.EMOJI_SMALL, new PostInvalidateCallback(this)),
|
||||
BufferType.SPANNABLE);
|
||||
}
|
||||
|
||||
public void insertEmoji(int codePoint) {
|
||||
|
@ -34,7 +31,7 @@ public class EmojiEditText extends AppCompatEditText {
|
|||
final char[] chars = Character.toChars(codePoint);
|
||||
final CharSequence text = EmojiProvider.getInstance(getContext()).emojify(new String(chars),
|
||||
EmojiProvider.EMOJI_SMALL,
|
||||
new InvalidatingPageLoadedListener(this));
|
||||
new PostInvalidateCallback(this));
|
||||
|
||||
getText().replace(Math.min(start, end), Math.max(start, end), text);
|
||||
setSelection(end + chars.length);
|
||||
|
|
|
@ -17,7 +17,7 @@ import android.widget.GridView;
|
|||
import android.widget.ImageView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel.OnModelChangedListener;
|
||||
|
||||
public class EmojiPageFragment extends Fragment {
|
||||
private static final String TAG = EmojiPageFragment.class.getSimpleName();
|
||||
|
@ -42,11 +42,16 @@ public class EmojiPageFragment extends Fragment {
|
|||
grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
|
||||
grid.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
model.onCodePointSelected((Integer)view.getTag());
|
||||
if (listener != null) listener.onEmojiSelected((Integer)view.getTag());
|
||||
}
|
||||
});
|
||||
grid.setAdapter(new EmojiGridAdapter(getActivity(), model));
|
||||
model.setOnModelChangedListener(new OnModelChangedListener() {
|
||||
@Override public void onModelChanged() {
|
||||
((EmojiGridAdapter)grid.getAdapter()).notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -99,9 +104,7 @@ public class EmojiPageFragment extends Fragment {
|
|||
|
||||
final Integer unicodeTag = model.getCodePoints()[position];
|
||||
final EmojiProvider provider = EmojiProvider.getInstance(context);
|
||||
final Drawable drawable = provider.getEmojiDrawable(unicodeTag,
|
||||
EmojiProvider.EMOJI_HUGE,
|
||||
new InvalidatingPageLoadedListener(view));
|
||||
final Drawable drawable = provider.getEmojiDrawable(unicodeTag, EmojiProvider.EMOJI_HUGE);
|
||||
|
||||
view.setImageDrawable(drawable);
|
||||
view.setPadding(pad, pad, pad, pad);
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
public interface EmojiPageModel {
|
||||
int getIconRes();
|
||||
int[] getCodePoints();
|
||||
void onCodePointSelected(int codePoint);
|
||||
public abstract class EmojiPageModel {
|
||||
protected OnModelChangedListener listener;
|
||||
|
||||
public abstract int getIconRes();
|
||||
public abstract int[] getCodePoints();
|
||||
|
||||
public void setOnModelChangedListener(OnModelChangedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
interface OnModelChangedListener {
|
||||
void onModelChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@ import android.graphics.Paint;
|
|||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.Callback;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.ResUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
|
@ -27,17 +29,14 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class EmojiProvider {
|
||||
private static final String TAG = EmojiProvider.class.getSimpleName();
|
||||
private static final ExecutorService executor = Util.newSingleThreadedLifoExecutor();
|
||||
private static volatile EmojiProvider instance = null;
|
||||
private static final SparseArray<SoftReference<Bitmap>> bitmaps = new SparseArray<>();
|
||||
private static final Paint paint = new Paint();
|
||||
private static final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private static final String TAG = EmojiProvider.class.getSimpleName();
|
||||
private static volatile EmojiProvider instance = null;
|
||||
private static final Paint paint = new Paint();
|
||||
static { paint.setFilterBitmap(true); }
|
||||
|
||||
private final SparseArray<DrawInfo> offsets = new SparseArray<>();
|
||||
|
@ -55,7 +54,7 @@ public class EmojiProvider {
|
|||
|
||||
private final Context context;
|
||||
private final int bigDrawSize;
|
||||
private final int[] pages;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
public static EmojiProvider getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
|
@ -69,72 +68,28 @@ public class EmojiProvider {
|
|||
}
|
||||
|
||||
private EmojiProvider(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
int[] pages = ResUtil.getResourceIds(context, R.array.emoji_categories);
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
|
||||
this.pages = ResUtil.getResourceIds(context, R.array.emoji_categories);
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
final int[] page = context.getResources().getIntArray(pages[i]);
|
||||
for (int j = 0; j < page.length; j++) {
|
||||
offsets.put(page[j], new DrawInfo(i, j));
|
||||
final EmojiPageBitmap page = new EmojiPageBitmap(i);
|
||||
final int[] codePoints = context.getResources().getIntArray(pages[i]);
|
||||
for (int j = 0; j < codePoints.length; j++) {
|
||||
offsets.put(codePoints[j], new DrawInfo(page, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void preloadPage(final int page, final PageLoadedListener pageLoadListener) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
loadPage(page);
|
||||
if (pageLoadListener != null) {
|
||||
pageLoadListener.onPageLoaded();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPage(int page) throws IOException {
|
||||
if (page < 0 || page >= pages.length) {
|
||||
throw new IndexOutOfBoundsException("can't load page that doesn't exist");
|
||||
}
|
||||
|
||||
if (bitmaps.get(page) != null && bitmaps.get(page).get() != null) return;
|
||||
|
||||
try {
|
||||
final String file = "emoji_" + page + "_wrapped.png";
|
||||
final InputStream measureStream = context.getAssets().open(file);
|
||||
final InputStream bitmapStream = context.getAssets().open(file);
|
||||
final Bitmap bitmap = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
|
||||
bitmaps.put(page, new SoftReference<>(bitmap));
|
||||
Log.w(TAG, "onPageLoaded(" + page + ")");
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
throw ioe;
|
||||
} catch (BitmapDecodingException bde) {
|
||||
Log.w(TAG, bde);
|
||||
throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence emojify(CharSequence text, PageLoadedListener pageLoadedListener) {
|
||||
return emojify(text, EMOJI_LARGE, pageLoadedListener);
|
||||
}
|
||||
|
||||
public CharSequence emojify(CharSequence text, double size, PageLoadedListener pageLoadedListener) {
|
||||
public CharSequence emojify(CharSequence text, double size, Callback callback) {
|
||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
|
||||
while (matches.find()) {
|
||||
int codePoint = matches.group().codePointAt(0);
|
||||
Drawable drawable = getEmojiDrawable(codePoint, size, pageLoadedListener);
|
||||
Drawable drawable = getEmojiDrawable(codePoint, size);
|
||||
if (drawable != null) {
|
||||
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
|
||||
char[] chars = new char[matches.end() - matches.start()];
|
||||
Arrays.fill(chars, ' ');
|
||||
builder.setSpan(imageSpan, matches.start(), matches.end(),
|
||||
builder.setSpan(new InvalidatingDrawableSpan(drawable, callback), matches.start(), matches.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
@ -142,51 +97,52 @@ public class EmojiProvider {
|
|||
return builder;
|
||||
}
|
||||
|
||||
public Drawable getEmojiDrawable(int emojiCode, double size, PageLoadedListener pageLoadedListener) {
|
||||
return getEmojiDrawable(offsets.get(emojiCode), size, pageLoadedListener);
|
||||
public Drawable getEmojiDrawable(int emojiCode, double size) {
|
||||
return getEmojiDrawable(offsets.get(emojiCode), size);
|
||||
}
|
||||
|
||||
private Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
|
||||
if (drawInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
|
||||
private Drawable getEmojiDrawable(DrawInfo drawInfo, double size) {
|
||||
if (drawInfo == null) return null;
|
||||
|
||||
final EmojiDrawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
|
||||
drawable.setBounds(0, 0, (int)((double)bigDrawSize * size), (int)((double)bigDrawSize * size));
|
||||
if (bitmaps.get(drawInfo.page) == null || bitmaps.get(drawInfo.page).get() == null) {
|
||||
preloadPage(drawInfo.page, pageLoadedListener);
|
||||
}
|
||||
drawInfo.page.get().addListener(new FutureTaskListener<Bitmap>() {
|
||||
@Override public void onSuccess(final Bitmap result) {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
drawable.setBitmap(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void onFailure(Throwable error) {
|
||||
Log.w(TAG, error);
|
||||
}
|
||||
});
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public class EmojiDrawable extends Drawable {
|
||||
private final int index;
|
||||
private final int page;
|
||||
private final int emojiSize;
|
||||
private Bitmap bmp;
|
||||
|
||||
@Override public int getIntrinsicWidth() {
|
||||
return emojiSize;
|
||||
}
|
||||
|
||||
@Override public int getIntrinsicHeight() {
|
||||
return emojiSize;
|
||||
}
|
||||
|
||||
public EmojiDrawable(DrawInfo info, int emojiSize) {
|
||||
this.index = info.index;
|
||||
this.page = info.page;
|
||||
this.emojiSize = emojiSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (bitmaps.get(page) == null || bitmaps.get(page).get() == null) {
|
||||
preloadPage(page, new PageLoadedListener() {
|
||||
@Override public void onPageLoaded() {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
invalidateSelf();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (bmp == null) {
|
||||
bmp = bitmaps.get(page).get();
|
||||
}
|
||||
if (bmp == null) return;
|
||||
|
||||
Rect b = copyBounds();
|
||||
|
||||
|
@ -202,6 +158,12 @@ public class EmojiProvider {
|
|||
paint);
|
||||
}
|
||||
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
Util.assertMainThread();
|
||||
bmp = bitmap;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
|
@ -212,39 +174,13 @@ public class EmojiProvider {
|
|||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) { }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EmojiDrawable{" +
|
||||
"page=" + page +
|
||||
", index=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidatingPageLoadedListener implements PageLoadedListener {
|
||||
private final View view;
|
||||
|
||||
public InvalidatingPageLoadedListener(final View view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageLoaded() {
|
||||
view.postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InvalidatingPageLoadedListener{}";
|
||||
}
|
||||
}
|
||||
|
||||
class DrawInfo {
|
||||
int page;
|
||||
int index;
|
||||
EmojiPageBitmap page;
|
||||
int index;
|
||||
|
||||
public DrawInfo(final int page, final int index) {
|
||||
public DrawInfo(final EmojiPageBitmap page, final int index) {
|
||||
this.page = page;
|
||||
this.index = index;
|
||||
}
|
||||
|
@ -258,7 +194,67 @@ public class EmojiProvider {
|
|||
}
|
||||
}
|
||||
|
||||
interface PageLoadedListener {
|
||||
void onPageLoaded();
|
||||
private class EmojiPageBitmap {
|
||||
private int page;
|
||||
private SoftReference<Bitmap> bitmapReference;
|
||||
private ListenableFutureTask<Bitmap> task;
|
||||
|
||||
public EmojiPageBitmap(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
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 " + page);
|
||||
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 String file = "emoji_" + page + "_wrapped.png";
|
||||
final InputStream measureStream = context.getAssets().open(file);
|
||||
final InputStream bitmapStream = context.getAssets().open(file);
|
||||
final Bitmap bitmap = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float)bigDrawSize / (float)EMOJI_RAW_SIZE);
|
||||
bitmapReference = new SoftReference<>(bitmap);
|
||||
Log.w(TAG, "onPageLoaded(" + page + ")");
|
||||
return bitmap;
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
throw ioe;
|
||||
} catch (BitmapDecodingException bde) {
|
||||
Log.w(TAG, bde);
|
||||
throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,26 @@
|
|||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.text.method.TransformationMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.InvalidatingPageLoadedListener;
|
||||
|
||||
public class EmojiTextView extends AppCompatTextView {
|
||||
public EmojiTextView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public EmojiTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setTransformationMethod(new EmojiTransformationMethod());
|
||||
}
|
||||
|
||||
private static class EmojiTransformationMethod implements TransformationMethod {
|
||||
|
||||
@Override public CharSequence getTransformation(CharSequence source, View view) {
|
||||
return EmojiProvider.getInstance(view.getContext()).emojify(source,
|
||||
@Override public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(EmojiProvider.getInstance(getContext()).emojify(text,
|
||||
EmojiProvider.EMOJI_SMALL,
|
||||
new InvalidatingPageLoadedListener(view));
|
||||
}
|
||||
|
||||
@Override public void onFocusChanged(View view, CharSequence sourceText, boolean focused,
|
||||
int direction, Rect previouslyFocusedRect) { }
|
||||
new PostInvalidateCallback(this)),
|
||||
BufferType.SPANNABLE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.Callback;
|
||||
import android.text.style.ImageSpan;
|
||||
|
||||
public class InvalidatingDrawableSpan extends ImageSpan {
|
||||
public InvalidatingDrawableSpan(Drawable drawable, Callback callback) {
|
||||
super(drawable, ALIGN_BOTTOM);
|
||||
drawable.setCallback(callback);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.thoughtcrime.securesms.components.emoji;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.Callback;
|
||||
import android.view.View;
|
||||
|
||||
public class PostInvalidateCallback implements Callback {
|
||||
private final View view;
|
||||
|
||||
public PostInvalidateCallback(View view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override public void invalidateDrawable(Drawable who) {
|
||||
view.postInvalidate();
|
||||
}
|
||||
|
||||
@Override public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
|
||||
}
|
||||
|
||||
@Override public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
|
||||
}
|
||||
}
|
|
@ -18,13 +18,14 @@ import java.io.IOException;
|
|||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
public class RecentEmojiPageModel implements EmojiPageModel {
|
||||
public class RecentEmojiPageModel extends EmojiPageModel {
|
||||
private static final String TAG = RecentEmojiPageModel.class.getSimpleName();
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_recent_emoji";
|
||||
private static final int EMOJI_LRU_SIZE = 50;
|
||||
|
||||
private final SharedPreferences prefs;
|
||||
private final LinkedHashSet<Integer> recentlyUsed;
|
||||
private OnModelChangedListener listener;
|
||||
|
||||
public RecentEmojiPageModel(Context context) {
|
||||
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
@ -50,10 +51,11 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
|||
}
|
||||
|
||||
@Override public int[] getCodePoints() {
|
||||
return toPrimitiveArray(recentlyUsed);
|
||||
return toReversePrimitiveArray(recentlyUsed);
|
||||
}
|
||||
|
||||
@Override public void onCodePointSelected(int codePoint) {
|
||||
public void onCodePointSelected(int codePoint) {
|
||||
Log.w(TAG, "onCodePointSelected(" + codePoint + ")");
|
||||
recentlyUsed.remove(codePoint);
|
||||
recentlyUsed.add(codePoint);
|
||||
|
||||
|
@ -80,6 +82,12 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
|||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
if (listener != null) listener.onModelChanged();
|
||||
}
|
||||
|
||||
@Override public void setOnModelChangedListener(OnModelChangedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private LinkedHashSet<Integer> fromHexString(@Nullable LinkedHashSet<String> stringSet) {
|
||||
|
@ -100,11 +108,11 @@ public class RecentEmojiPageModel implements EmojiPageModel {
|
|||
return stringSet;
|
||||
}
|
||||
|
||||
private int[] toPrimitiveArray(@NonNull LinkedHashSet<Integer> integerSet) {
|
||||
private int[] toReversePrimitiveArray(@NonNull LinkedHashSet<Integer> integerSet) {
|
||||
int[] ints = new int[integerSet.size()];
|
||||
int i = 0;
|
||||
int i = integerSet.size() - 1;
|
||||
for (Integer integer : integerSet) {
|
||||
ints[i++] = integer;
|
||||
ints[i--] = integer;
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.components.emoji;
|
|||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class StaticEmojiPageModel implements EmojiPageModel {
|
||||
public class StaticEmojiPageModel extends EmojiPageModel {
|
||||
@DrawableRes private final int icon;
|
||||
@NonNull private final int[] codePoints;
|
||||
|
||||
|
@ -19,6 +19,4 @@ public class StaticEmojiPageModel implements EmojiPageModel {
|
|||
@NonNull public int[] getCodePoints() {
|
||||
return codePoints;
|
||||
}
|
||||
|
||||
@Override public void onCodePointSelected(int codePoint) { }
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.graphics.Typeface;
|
|||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Looper;
|
||||
import android.provider.Telephony;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.Spannable;
|
||||
|
@ -296,4 +297,10 @@ public class Util {
|
|||
public static boolean isMmsCapable(Context context) {
|
||||
return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context);
|
||||
}
|
||||
|
||||
public static void assertMainThread() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
throw new AssertionError("Main-thread assertion failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue