mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-11 22:21:48 +00:00
committed by
Moxie Marlinspike
parent
bea3c33223
commit
530ad7bc86
@@ -45,8 +45,8 @@ import android.view.ContextThemeWrapper;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
@@ -188,7 +188,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
initializeReceivers();
|
||||
initializeResources();
|
||||
initializeDraft();
|
||||
initializeTitleBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -680,7 +679,8 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
|
||||
for (Draft draft : drafts) {
|
||||
if (draft.getType().equals(Draft.TEXT) && !nativeEmojiSupported) {
|
||||
composeText.setText(Emoji.getInstance(context).emojify(draft.getValue()),
|
||||
composeText.setText(Emoji.getInstance(context).emojify(draft.getValue(),
|
||||
new Emoji.InvalidatingPageLoadedListener(composeText)),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else if (draft.getType().equals(Draft.TEXT)) {
|
||||
composeText.setText(draft.getValue());
|
||||
@@ -781,6 +781,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
composeText.addTextChangedListener(composeKeyPressedListener);
|
||||
composeText.setOnEditorActionListener(sendButtonListener);
|
||||
composeText.setOnClickListener(composeKeyPressedListener);
|
||||
composeText.setOnFocusChangeListener(composeKeyPressedListener);
|
||||
emojiDrawer.setComposeEditText(composeText);
|
||||
emojiToggle.setOnClickListener(new EmojiToggleListener());
|
||||
|
||||
@@ -1137,12 +1138,13 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
public void onClick(View v) {
|
||||
InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
if (emojiDrawer.getVisibility() == View.VISIBLE) {
|
||||
if (emojiDrawer.isOpen()) {
|
||||
input.showSoftInput(composeText, 0);
|
||||
emojiDrawer.setVisibility(View.GONE);
|
||||
emojiDrawer.hide();
|
||||
} else {
|
||||
input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
|
||||
emojiDrawer.setVisibility(View.VISIBLE);
|
||||
|
||||
emojiDrawer.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1164,7 +1166,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
}
|
||||
}
|
||||
|
||||
private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher {
|
||||
private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
@@ -1194,6 +1196,13 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,int count) {}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus && emojiDrawer.isOpen()) {
|
||||
emojiToggle.performClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -228,13 +228,8 @@ public class ConversationItem extends LinearLayout {
|
||||
}
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), Emoji.EMOJI_LARGE),
|
||||
bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(), new Emoji.InvalidatingPageLoadedListener(bodyText)),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
bodyText.setText(messageRecord.getDisplayBody());
|
||||
}
|
||||
}
|
||||
|
||||
private void setContactPhoto(MessageRecord messageRecord) {
|
||||
|
||||
@@ -102,13 +102,10 @@ public class ConversationListItem extends RelativeLayout
|
||||
this.recipients.addListener(this);
|
||||
this.fromView.setText(formatFrom(recipients, count, read));
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
|
||||
Emoji.EMOJI_SMALL),
|
||||
Emoji.EMOJI_SMALL,
|
||||
new Emoji.InvalidatingPageLoadedListener(subjectView)),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
this.subjectView.setText(thread.getDisplayBody());
|
||||
}
|
||||
|
||||
if (thread.getDate() > 0)
|
||||
this.dateView.setText(DateUtils.getBetterRelativeTimeSpanString(getContext(), thread.getDate()));
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -19,34 +19,39 @@ import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.astuetz.PagerSlidingTabStrip;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.Emoji;
|
||||
|
||||
public class EmojiDrawer extends FrameLayout {
|
||||
public class EmojiDrawer extends KeyboardAwareLinearLayout {
|
||||
|
||||
private static final int RECENT_TYPE = 0;
|
||||
private static final int ALL_TYPE = 1;
|
||||
|
||||
private FrameLayout emojiGridLayout;
|
||||
private FrameLayout recentEmojiGridLayout;
|
||||
private EditText composeText;
|
||||
private Emoji emoji;
|
||||
private GridView emojiGrid;
|
||||
private GridView recentEmojiGrid;
|
||||
private ViewPager pager;
|
||||
private FrameLayout[] gridLayouts = new FrameLayout[Emoji.PAGES.length+1];
|
||||
private EditText composeText;
|
||||
private Emoji emoji;
|
||||
private ViewPager pager;
|
||||
private PagerSlidingTabStrip strip;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public EmojiDrawer(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public EmojiDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB)
|
||||
public EmojiDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
@@ -61,7 +66,7 @@ public class EmojiDrawer extends FrameLayout {
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.emoji_drawer, this, true);
|
||||
|
||||
initializeResources();
|
||||
@@ -69,26 +74,48 @@ public class EmojiDrawer extends FrameLayout {
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
this.pager = (ViewPager ) findViewById(R.id.emoji_pager);
|
||||
this.emojiGridLayout = (FrameLayout ) inflater.inflate(R.layout.emoji_grid_layout, null);
|
||||
this.recentEmojiGridLayout = (FrameLayout ) inflater.inflate(R.layout.emoji_grid_layout, null);
|
||||
this.emojiGrid = (GridView ) emojiGridLayout.findViewById(R.id.emoji);
|
||||
this.recentEmojiGrid = (GridView ) recentEmojiGridLayout.findViewById(R.id.emoji);
|
||||
this.emoji = Emoji.getInstance(getContext());
|
||||
this.pager = (ViewPager ) findViewById(R.id.emoji_pager);
|
||||
this.strip = (PagerSlidingTabStrip ) findViewById(R.id.tabs);
|
||||
this.emoji = Emoji.getInstance(getContext());
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
int keyboardHeight = getKeyboardHeight();
|
||||
Log.w("EmojiDrawer", "setting emoji drawer to height " + keyboardHeight);
|
||||
setLayoutParams(new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, keyboardHeight));
|
||||
requestLayout();
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void initializeEmojiGrid() {
|
||||
emojiGrid.setAdapter(new EmojiGridAdapter(ALL_TYPE));
|
||||
emojiGrid.setOnItemClickListener(new EmojiClickListener(ALL_TYPE));
|
||||
recentEmojiGrid.setAdapter(new EmojiGridAdapter(RECENT_TYPE));
|
||||
recentEmojiGrid.setOnItemClickListener(new EmojiClickListener(RECENT_TYPE));
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
for (int i = 0; i < gridLayouts.length; i++) {
|
||||
gridLayouts[i] = (FrameLayout) inflater.inflate(R.layout.emoji_grid_layout, pager, false);
|
||||
final GridView gridView = (GridView) gridLayouts[i].findViewById(R.id.emoji);
|
||||
gridLayouts[i].setTag(gridView);
|
||||
final int type = (i == 0 ? RECENT_TYPE : ALL_TYPE);
|
||||
gridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2*getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
|
||||
gridView.setAdapter(new EmojiGridAdapter(type, i-1));
|
||||
gridView.setOnItemClickListener(new EmojiClickListener(ALL_TYPE));
|
||||
}
|
||||
|
||||
pager.setAdapter(new EmojiPagerAdapter());
|
||||
|
||||
if (emoji.getRecentlyUsedAssetCount() <= 0) {
|
||||
pager.setCurrentItem(1);
|
||||
}
|
||||
strip.setTabPaddingLeftRight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_left_right_padding));
|
||||
strip.setAllCaps(false);
|
||||
strip.setShouldExpand(true);
|
||||
strip.setUnderlineColorResource(R.color.emoji_tab_underline);
|
||||
strip.setIndicatorColorResource(R.color.emoji_tab_indicator);
|
||||
strip.setIndicatorHeight(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_indicator_height));
|
||||
|
||||
strip.setViewPager(pager);
|
||||
}
|
||||
|
||||
private class EmojiClickListener implements AdapterView.OnItemClickListener {
|
||||
@@ -101,44 +128,43 @@ public class EmojiDrawer extends FrameLayout {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String characters;
|
||||
|
||||
if (type == ALL_TYPE ) characters = emoji.getEmojiUnicode(position);
|
||||
else characters = emoji.getRecentEmojiUnicode(position);
|
||||
|
||||
int start = composeText.getSelectionStart();
|
||||
int end = composeText.getSelectionEnd ();
|
||||
|
||||
composeText.getText().replace(Math.min(start, end), Math.max(start, end),
|
||||
characters, 0, characters.length());
|
||||
|
||||
composeText.setText(emoji.emojify(composeText.getText().toString()),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
|
||||
composeText.setSelection(end+2);
|
||||
|
||||
Integer unicodePoint = (Integer) view.getTag();
|
||||
insertEmoji(composeText, unicodePoint);
|
||||
if (type != RECENT_TYPE) {
|
||||
emoji.setRecentlyUsed(position);
|
||||
((BaseAdapter)recentEmojiGrid.getAdapter()).notifyDataSetChanged();
|
||||
emoji.setRecentlyUsed(Integer.toHexString(unicodePoint));
|
||||
((BaseAdapter)((GridView)gridLayouts[0].getTag()).getAdapter()).notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEmoji(EditText editText, Integer unicodePoint) {
|
||||
final char[] chars = Character.toChars(unicodePoint);
|
||||
String characters = new String(chars);
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
|
||||
CharSequence text = emoji.emojify(characters, new Emoji.InvalidatingPageLoadedListener(composeText));
|
||||
editText.getText().replace(Math.min(start, end), Math.max(start, end), text, 0, text.length());
|
||||
|
||||
editText.setSelection(end+chars.length);
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiGridAdapter extends BaseAdapter {
|
||||
|
||||
private final int type;
|
||||
private final int page;
|
||||
private final int emojiSize;
|
||||
|
||||
public EmojiGridAdapter(int type) {
|
||||
this.type = type;
|
||||
emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size);
|
||||
public EmojiGridAdapter(int type, int page) {
|
||||
this.type = type;
|
||||
this.page = page;
|
||||
emojiSize = (int) getResources().getDimension(R.dimen.emoji_drawer_size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (type == RECENT_TYPE) return emoji.getRecentlyUsedAssetCount();
|
||||
else return emoji.getEmojiAssetCount();
|
||||
|
||||
else return Emoji.PAGES[page].length;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,57 +174,49 @@ public class EmojiDrawer extends FrameLayout {
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return 0;
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
Drawable drawable;
|
||||
|
||||
if (type == RECENT_TYPE) drawable = emoji.getRecentlyUsed(position);
|
||||
else drawable = emoji.getEmojiDrawable(position);
|
||||
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
final ImageView view;
|
||||
final int pad = getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding);
|
||||
if (convertView != null && convertView instanceof ImageView) {
|
||||
((ImageView)convertView).setImageDrawable(drawable);
|
||||
return convertView;
|
||||
view = (ImageView) convertView;
|
||||
} else {
|
||||
ImageView imageView = new ImageView(getContext());
|
||||
imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize, emojiSize));
|
||||
imageView.setImageDrawable(drawable);
|
||||
return imageView;
|
||||
imageView.setPadding(pad, pad, pad, pad);
|
||||
imageView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2*pad, emojiSize + 2*pad));
|
||||
view = imageView;
|
||||
}
|
||||
|
||||
final Drawable drawable;
|
||||
final Integer unicodeTag;
|
||||
if (type == ALL_TYPE) {
|
||||
unicodeTag = Emoji.PAGES[page][position];
|
||||
drawable = emoji.getEmojiDrawable(new Emoji.DrawInfo(page, position),
|
||||
Emoji.EMOJI_HUGE,
|
||||
new Emoji.InvalidatingPageLoadedListener(view));
|
||||
} else {
|
||||
Pair<Integer, Drawable> recentlyUsed = emoji.getRecentlyUsed(position,
|
||||
Emoji.EMOJI_HUGE,
|
||||
new Emoji.InvalidatingPageLoadedListener(view));
|
||||
unicodeTag = recentlyUsed.first;
|
||||
drawable = recentlyUsed.second;
|
||||
}
|
||||
|
||||
view.setImageDrawable(drawable);
|
||||
view.setPadding(pad, pad, pad, pad);
|
||||
view.setTag(unicodeTag);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiPagerAdapter extends PagerAdapter {
|
||||
private class EmojiPagerAdapter extends PagerAdapter implements PagerSlidingTabStrip.IconTabProvider {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
SpannableString recent = new SpannableString(" Recent ");
|
||||
ImageSpan recentImage = new ImageSpan(getContext(), R.drawable.ic_emoji_recent_light,
|
||||
ImageSpan.ALIGN_BASELINE);
|
||||
|
||||
recent.setSpan(recentImage, 1, recent.length()-1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
return recent;
|
||||
case 1:
|
||||
SpannableString emoji = new SpannableString(" Emoji ");
|
||||
ImageSpan emojiImage = new ImageSpan(getContext(), R.drawable.ic_emoji_light,
|
||||
ImageSpan.ALIGN_BASELINE);
|
||||
|
||||
emoji.setSpan(emojiImage, 1, emoji.length()-1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
return emoji;
|
||||
default:
|
||||
throw new AssertionError("Bad position!");
|
||||
}
|
||||
return gridLayouts.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,17 +225,31 @@ public class EmojiDrawer extends FrameLayout {
|
||||
}
|
||||
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
View view;
|
||||
if (position < 0 || position >= gridLayouts.length)
|
||||
throw new AssertionError("position out of range!");
|
||||
|
||||
switch (position) {
|
||||
case 0: view = recentEmojiGridLayout; break;
|
||||
case 1: view = emojiGridLayout; break;
|
||||
default: throw new AssertionError("Too many positions!");
|
||||
container.addView(gridLayouts[position], 0);
|
||||
|
||||
return gridLayouts[position];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
Log.w("EmojiDrawer", "destroying item at " + position);
|
||||
container.removeView(gridLayouts[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPageIconResId(int i) {
|
||||
switch (i) {
|
||||
case 0: return R.drawable.emoji_category_recent;
|
||||
case 1: return R.drawable.emoji_category_smile;
|
||||
case 2: return R.drawable.emoji_category_flower;
|
||||
case 3: return R.drawable.emoji_category_bell;
|
||||
case 4: return R.drawable.emoji_category_car;
|
||||
case 5: return R.drawable.emoji_category_symbol;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
container.addView(view, 0);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* LinearLayout that, when a view container, will report back when it thinks a soft keyboard
|
||||
* has been opened and what its height would be.
|
||||
*/
|
||||
public class KeyboardAwareLinearLayout extends LinearLayout {
|
||||
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
|
||||
private static final Rect rect = new Rect();
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* inspired by http://stackoverflow.com/a/7104303
|
||||
* @param widthMeasureSpec width measure
|
||||
* @param heightMeasureSpec height measure
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int res = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
int statusBarHeight = res > 0 ? getResources().getDimensionPixelSize(res) : 0;
|
||||
|
||||
final int availableHeight = this.getRootView().getHeight() - statusBarHeight;
|
||||
getWindowVisibleDisplayFrame(rect);
|
||||
|
||||
final int keyboardHeight = availableHeight - (rect.bottom - rect.top);
|
||||
|
||||
if (keyboardHeight > getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height)) {
|
||||
onKeyboardShown(keyboardHeight);
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
protected void onKeyboardShown(int keyboardHeight) {
|
||||
Log.w(TAG, "keyboard shown, height " + keyboardHeight);
|
||||
|
||||
WindowManager wm = (WindowManager) getContext().getSystemService(Activity.WINDOW_SERVICE);
|
||||
if (wm == null || wm.getDefaultDisplay() == null) {
|
||||
return;
|
||||
}
|
||||
int rotation = wm.getDefaultDisplay().getRotation();
|
||||
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_270:
|
||||
case Surface.ROTATION_90:
|
||||
setKeyboardLandscapeHeight(keyboardHeight);
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
case Surface.ROTATION_180:
|
||||
setKeyboardPortraitHeight(keyboardHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public int getKeyboardHeight() {
|
||||
WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
if (wm == null || wm.getDefaultDisplay() == null) {
|
||||
throw new AssertionError("WindowManager was null or there is no default display");
|
||||
}
|
||||
|
||||
int rotation = wm.getDefaultDisplay().getRotation();
|
||||
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_270:
|
||||
case Surface.ROTATION_90:
|
||||
return getKeyboardLandscapeHeight();
|
||||
case Surface.ROTATION_0:
|
||||
case Surface.ROTATION_180:
|
||||
default:
|
||||
return getKeyboardPortraitHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private int getKeyboardLandscapeHeight() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.getInt("keyboard_height_landscape",
|
||||
getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
|
||||
}
|
||||
|
||||
private int getKeyboardPortraitHeight() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.getInt("keyboard_height_portrait",
|
||||
getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
|
||||
}
|
||||
|
||||
private void setKeyboardLandscapeHeight(int height) {
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.edit().putInt("keyboard_height_landscape", height).apply();
|
||||
}
|
||||
|
||||
private void setKeyboardPortraitHeight(int height) {
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.edit().putInt("keyboard_height_portrait", height).apply();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,33 +47,48 @@ public class BitmapUtil {
|
||||
else throw new IOException("Unable to scale image below: " + baos.size());
|
||||
}
|
||||
|
||||
public static Bitmap createScaledBitmap(InputStream measure, InputStream data,
|
||||
int maxWidth, int maxHeight)
|
||||
public static Bitmap createScaledBitmap(InputStream measure, InputStream data, float scale)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
BitmapFactory.Options options = getImageDimensions(measure);
|
||||
int imageWidth = options.outWidth;
|
||||
int imageHeight = options.outHeight;
|
||||
final BitmapFactory.Options options = getImageDimensions(measure);
|
||||
final int outWidth = (int)(options.outWidth * scale);
|
||||
final int outHeight = (int)(options.outHeight * scale);
|
||||
Log.w("BitmapUtil", "creating scaled bitmap with scale " + scale + " => " + outWidth + "x" + outHeight);
|
||||
return createScaledBitmap(data, outWidth, outHeight, options);
|
||||
}
|
||||
|
||||
public static Bitmap createScaledBitmap(InputStream measure, InputStream data,
|
||||
int maxWidth, int maxHeight)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
final BitmapFactory.Options options = getImageDimensions(measure);
|
||||
return createScaledBitmap(data, maxWidth, maxHeight, options);
|
||||
}
|
||||
|
||||
private static Bitmap createScaledBitmap(InputStream data,
|
||||
int maxWidth, int maxHeight, BitmapFactory.Options options)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
final int imageWidth = options.outWidth;
|
||||
final int imageHeight = options.outHeight;
|
||||
|
||||
int scaler = 1;
|
||||
|
||||
while ((imageWidth / scaler > maxWidth) && (imageHeight / scaler > maxHeight))
|
||||
while ((imageWidth / scaler / 2 >= maxWidth) && (imageHeight / scaler / 2 >= maxHeight))
|
||||
scaler *= 2;
|
||||
|
||||
if (scaler > 1)
|
||||
scaler /= 2;
|
||||
|
||||
options.inSampleSize = scaler;
|
||||
options.inJustDecodeBounds = false;
|
||||
|
||||
Bitmap roughThumbnail = BitmapFactory.decodeStream(new BufferedInputStream(data), null, options);
|
||||
|
||||
Log.w("BitmapUtil", "rough scale " + (imageWidth) + "x" + (imageHeight) +
|
||||
" => " + (options.outWidth) + "x" + (options.outHeight));
|
||||
if (roughThumbnail == null) {
|
||||
throw new BitmapDecodingException("Decoded stream was null.");
|
||||
}
|
||||
|
||||
if (roughThumbnail.getWidth() > maxWidth || roughThumbnail.getHeight() > maxHeight) {
|
||||
float aspectWidth, aspectHeight;
|
||||
if (options.outWidth > maxWidth || options.outHeight > maxHeight) {
|
||||
final float aspectWidth, aspectHeight;
|
||||
|
||||
if (imageWidth == 0 || imageHeight == 0) {
|
||||
aspectWidth = maxWidth;
|
||||
@@ -86,7 +101,8 @@ public class BitmapUtil {
|
||||
aspectWidth = (aspectHeight / options.outHeight) * options.outWidth;
|
||||
}
|
||||
|
||||
Log.w("BitmapUtil", "Scaling to max width and height: " + aspectWidth + "," + aspectHeight);
|
||||
Log.w("BitmapUtil", "fine scale " + options.outWidth + "x" + options.outHeight +
|
||||
" => " + aspectWidth + "x" + aspectHeight);
|
||||
Bitmap scaledThumbnail = Bitmap.createScaledBitmap(roughThumbnail, (int)aspectWidth, (int)aspectHeight, true);
|
||||
if (roughThumbnail != scaledThumbnail) roughThumbnail.recycle();
|
||||
return scaledThumbnail;
|
||||
|
||||
@@ -3,37 +3,173 @@ package org.thoughtcrime.securesms.util;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import com.google.thoughtcrimegson.reflect.TypeToken;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Emoji {
|
||||
|
||||
private static ExecutorService executor = Util.newSingleThreadedLifoExecutor();
|
||||
|
||||
public static final int[][] PAGES = {
|
||||
{
|
||||
0x263a, 0x1f60a, 0x1f600, 0x1f601, 0x1f602, 0x1f603, 0x1f604, 0x1f605,
|
||||
0x1f606, 0x1f607, 0x1f608, 0x1f609, 0x1f62f, 0x1f610, 0x1f611, 0x1f615,
|
||||
0x1f620, 0x1f62c, 0x1f621, 0x1f622, 0x1f634, 0x1f62e, 0x1f623, 0x1f624,
|
||||
0x1f625, 0x1f626, 0x1f627, 0x1f628, 0x1f629, 0x1f630, 0x1f61f, 0x1f631,
|
||||
0x1f632, 0x1f633, 0x1f635, 0x1f636, 0x1f637, 0x1f61e, 0x1f612, 0x1f60d,
|
||||
0x1f61b, 0x1f61c, 0x1f61d, 0x1f60b, 0x1f617, 0x1f619, 0x1f618, 0x1f61a,
|
||||
0x1f60e, 0x1f62d, 0x1f60c, 0x1f616, 0x1f614, 0x1f62a, 0x1f60f, 0x1f613,
|
||||
0x1f62b, 0x1f64b, 0x1f64c, 0x1f64d, 0x1f645, 0x1f646, 0x1f647, 0x1f64e,
|
||||
0x1f64f, 0x1f63a, 0x1f63c, 0x1f638, 0x1f639, 0x1f63b, 0x1f63d, 0x1f63f,
|
||||
0x1f63e, 0x1f640, 0x1f648, 0x1f649, 0x1f64a, 0x1f4a9, 0x1f476, 0x1f466,
|
||||
0x1f467, 0x1f468, 0x1f469, 0x1f474, 0x1f475, 0x1f48f, 0x1f491, 0x1f46a,
|
||||
0x1f46b, 0x1f46c, 0x1f46d, 0x1f464, 0x1f465, 0x1f46e, 0x1f477, 0x1f481,
|
||||
0x1f482, 0x1f46f, 0x1f470, 0x1f478, 0x1f385, 0x1f47c, 0x1f471, 0x1f472,
|
||||
0x1f473, 0x1f483, 0x1f486, 0x1f487, 0x1f485, 0x1f47b, 0x1f479, 0x1f47a,
|
||||
0x1f47d, 0x1f47e, 0x1f47f, 0x1f480, 0x1f4aa, 0x1f440, 0x1f442, 0x1f443,
|
||||
0x1f463, 0x1f444, 0x1f445, 0x1f48b, 0x2764, 0x1f499, 0x1f49a, 0x1f49b,
|
||||
0x1f49c, 0x1f493, 0x1f494, 0x1f495, 0x1f496, 0x1f497, 0x1f498, 0x1f49d,
|
||||
0x1f49e, 0x1f49f, 0x1f44d, 0x1f44e, 0x1f44c, 0x270a, 0x270c, 0x270b,
|
||||
0x1f44a, 0x261d, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44b, 0x1f44f,
|
||||
0x1f450
|
||||
},
|
||||
{
|
||||
0x1f530, 0x1f484, 0x1f45e, 0x1f45f, 0x1f451, 0x1f452, 0x1f3a9, 0x1f393,
|
||||
0x1f453, 0x231a, 0x1f454, 0x1f455, 0x1f456, 0x1f457, 0x1f458, 0x1f459,
|
||||
0x1f460, 0x1f461, 0x1f462, 0x1f45a, 0x1f45c, 0x1f4bc, 0x1f392, 0x1f45d,
|
||||
0x1f45b, 0x1f4b0, 0x1f4b3, 0x1f4b2, 0x1f4b5, 0x1f4b4, 0x1f4b6, 0x1f4b7,
|
||||
0x1f4b8, 0x1f4b1, 0x1f4b9, 0x1f52b, 0x1f52a, 0x1f4a3, 0x1f489, 0x1f48a,
|
||||
0x1f6ac, 0x1f514, 0x1f515, 0x1f6aa, 0x1f52c, 0x1f52d, 0x1f52e, 0x1f526,
|
||||
0x1f50b, 0x1f50c, 0x1f4dc, 0x1f4d7, 0x1f4d8, 0x1f4d9, 0x1f4da, 0x1f4d4,
|
||||
0x1f4d2, 0x1f4d1, 0x1f4d3, 0x1f4d5, 0x1f4d6, 0x1f4f0, 0x1f4db, 0x1f383,
|
||||
0x1f384, 0x1f380, 0x1f381, 0x1f382, 0x1f388, 0x1f386, 0x1f387, 0x1f389,
|
||||
0x1f38a, 0x1f38d, 0x1f38f, 0x1f38c, 0x1f390, 0x1f38b, 0x1f38e, 0x1f4f1,
|
||||
0x1f4f2, 0x1f4df, 0x260e, 0x1f4de, 0x1f4e0, 0x1f4e6, 0x2709, 0x1f4e8,
|
||||
0x1f4e9, 0x1f4ea, 0x1f4eb, 0x1f4ed, 0x1f4ec, 0x1f4ee, 0x1f4e4, 0x1f4e5,
|
||||
0x1f4ef, 0x1f4e2, 0x1f4e3, 0x1f4e1, 0x1f4ac, 0x1f4ad, 0x2712, 0x270f,
|
||||
0x1f4dd, 0x1f4cf, 0x1f4d0, 0x1f4cd, 0x1f4cc, 0x1f4ce, 0x2702, 0x1f4ba,
|
||||
0x1f4bb, 0x1f4bd, 0x1f4be, 0x1f4bf, 0x1f4c6, 0x1f4c5, 0x1f4c7, 0x1f4cb,
|
||||
0x1f4c1, 0x1f4c2, 0x1f4c3, 0x1f4c4, 0x1f4ca, 0x1f4c8, 0x1f4c9, 0x26fa,
|
||||
0x1f3a1, 0x1f3a2, 0x1f3a0, 0x1f3aa, 0x1f3a8, 0x1f3ac, 0x1f3a5, 0x1f4f7,
|
||||
0x1f4f9, 0x1f3a6, 0x1f3ad, 0x1f3ab, 0x1f3ae, 0x1f3b2, 0x1f3b0, 0x1f0cf,
|
||||
0x1f3b4, 0x1f004, 0x1f3af, 0x1f4fa, 0x1f4fb, 0x1f4c0, 0x1f4fc, 0x1f3a7,
|
||||
0x1f3a4, 0x1f3b5, 0x1f3b6, 0x1f3bc, 0x1f3bb, 0x1f3b9, 0x1f3b7, 0x1f3ba,
|
||||
0x1f3b8, 0x303d
|
||||
},
|
||||
{
|
||||
0x1f415, 0x1f436, 0x1f429, 0x1f408, 0x1f431, 0x1f400, 0x1f401, 0x1f42d,
|
||||
0x1f439, 0x1f422, 0x1f407, 0x1f430, 0x1f413, 0x1f414, 0x1f423, 0x1f424,
|
||||
0x1f425, 0x1f426, 0x1f40f, 0x1f411, 0x1f410, 0x1f43a, 0x1f403, 0x1f402,
|
||||
0x1f404, 0x1f42e, 0x1f434, 0x1f417, 0x1f416, 0x1f437, 0x1f43d, 0x1f438,
|
||||
0x1f40d, 0x1f43c, 0x1f427, 0x1f418, 0x1f428, 0x1f412, 0x1f435, 0x1f406,
|
||||
0x1f42f, 0x1f43b, 0x1f42b, 0x1f42a, 0x1f40a, 0x1f433, 0x1f40b, 0x1f41f,
|
||||
0x1f420, 0x1f421, 0x1f419, 0x1f41a, 0x1f42c, 0x1f40c, 0x1f41b, 0x1f41c,
|
||||
0x1f41d, 0x1f41e, 0x1f432, 0x1f409, 0x1f43e, 0x1f378, 0x1f37a, 0x1f37b,
|
||||
0x1f377, 0x1f379, 0x1f376, 0x2615, 0x1f375, 0x1f37c, 0x1f374, 0x1f368,
|
||||
0x1f367, 0x1f366, 0x1f369, 0x1f370, 0x1f36a, 0x1f36b, 0x1f36c, 0x1f36d,
|
||||
0x1f36e, 0x1f36f, 0x1f373, 0x1f354, 0x1f35f, 0x1f35d, 0x1f355, 0x1f356,
|
||||
0x1f357, 0x1f364, 0x1f363, 0x1f371, 0x1f35e, 0x1f35c, 0x1f359, 0x1f35a,
|
||||
0x1f35b, 0x1f372, 0x1f365, 0x1f362, 0x1f361, 0x1f358, 0x1f360, 0x1f34c,
|
||||
0x1f34e, 0x1f34f, 0x1f34a, 0x1f34b, 0x1f344, 0x1f345, 0x1f346, 0x1f347,
|
||||
0x1f348, 0x1f349, 0x1f350, 0x1f351, 0x1f352, 0x1f353, 0x1f34d, 0x1f330,
|
||||
0x1f331, 0x1f332, 0x1f333, 0x1f334, 0x1f335, 0x1f337, 0x1f338, 0x1f339,
|
||||
0x1f340, 0x1f341, 0x1f342, 0x1f343, 0x1f33a, 0x1f33b, 0x1f33c, 0x1f33d,
|
||||
0x1f33e, 0x1f33f, 0x2600, 0x1f308, 0x26c5, 0x2601, 0x1f301, 0x1f302,
|
||||
0x2614, 0x1f4a7, 0x26a1, 0x1f300, 0x2744, 0x26c4, 0x1f319, 0x1f31e,
|
||||
0x1f31d, 0x1f31a, 0x1f31b, 0x1f31c, 0x1f311, 0x1f312, 0x1f313, 0x1f314,
|
||||
0x1f315, 0x1f316, 0x1f317, 0x1f318, 0x1f391, 0x1f304, 0x1f305, 0x1f307,
|
||||
0x1f306, 0x1f303, 0x1f30c, 0x1f309, 0x1f30a, 0x1f30b, 0x1f30e, 0x1f30f,
|
||||
0x1f30d, 0x1f310
|
||||
},
|
||||
{
|
||||
0x1f3e0, 0x1f3e1, 0x1f3e2, 0x1f3e3, 0x1f3e4, 0x1f3e5, 0x1f3e6, 0x1f3e7,
|
||||
0x1f3e8, 0x1f3e9, 0x1f3ea, 0x1f3eb, 0x26ea, 0x26f2, 0x1f3ec, 0x1f3ef,
|
||||
0x1f3f0, 0x1f3ed, 0x1f5fb, 0x1f5fc, 0x1f5fd, 0x1f5fe, 0x1f5ff, 0x2693,
|
||||
0x1f3ee, 0x1f488, 0x1f527, 0x1f528, 0x1f529, 0x1f6bf, 0x1f6c1, 0x1f6c0,
|
||||
0x1f6bd, 0x1f6be, 0x1f3bd, 0x1f3a3, 0x1f3b1, 0x1f3b3, 0x26be, 0x26f3,
|
||||
0x1f3be, 0x26bd, 0x1f3bf, 0x1f3c0, 0x1f3c1, 0x1f3c2, 0x1f3c3, 0x1f3c4,
|
||||
0x1f3c6, 0x1f3c7, 0x1f40e, 0x1f3c8, 0x1f3c9, 0x1f3ca, 0x1f682, 0x1f683,
|
||||
0x1f684, 0x1f685, 0x1f686, 0x1f687, 0x24c2, 0x1f688, 0x1f68a, 0x1f68b,
|
||||
0x1f68c, 0x1f68d, 0x1f68e, 0x1f68f, 0x1f690, 0x1f691, 0x1f692, 0x1f693,
|
||||
0x1f694, 0x1f695, 0x1f696, 0x1f697, 0x1f698, 0x1f699, 0x1f69a, 0x1f69b,
|
||||
0x1f69c, 0x1f69d, 0x1f69e, 0x1f69f, 0x1f6a0, 0x1f6a1, 0x1f6a2, 0x1f6a3,
|
||||
0x1f681, 0x2708, 0x1f6c2, 0x1f6c3, 0x1f6c4, 0x1f6c5, 0x26f5, 0x1f6b2,
|
||||
0x1f6b3, 0x1f6b4, 0x1f6b5, 0x1f6b7, 0x1f6b8, 0x1f689, 0x1f680, 0x1f6a4,
|
||||
0x1f6b6, 0x26fd, 0x1f17f, 0x1f6a5, 0x1f6a6, 0x1f6a7, 0x1f6a8, 0x2668,
|
||||
0x1f48c, 0x1f48d, 0x1f48e, 0x1f490, 0x1f492, 0xfe4e5, 0xfe4e6, 0xfe4e7,
|
||||
0xfe4e8, 0xfe4e9, 0xfe4ea, 0xfe4eb, 0xfe4ec, 0xfe4ed, 0xfe4ee
|
||||
},
|
||||
{
|
||||
0x1f51d, 0x1f519, 0x1f51b, 0x1f51c, 0x1f51a, 0x23f3, 0x231b, 0x23f0,
|
||||
0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f,
|
||||
0x2650, 0x2651, 0x2652, 0x2653, 0x26ce, 0x1f531, 0x1f52f, 0x1f6bb,
|
||||
0x1f6ae, 0x1f6af, 0x1f6b0, 0x1f6b1, 0x1f170, 0x1f171, 0x1f18e, 0x1f17e,
|
||||
0x1f4ae, 0x1f4af, 0x1f520, 0x1f521, 0x1f522, 0x1f523, 0x1f524, 0x27bf,
|
||||
0x1f4f6, 0x1f4f3, 0x1f4f4, 0x1f4f5, 0x1f6b9, 0x1f6ba, 0x1f6bc, 0x267f,
|
||||
0x267b, 0x1f6ad, 0x1f6a9, 0x26a0, 0x1f201, 0x1f51e, 0x26d4, 0x1f192,
|
||||
0x1f197, 0x1f195, 0x1f198, 0x1f199, 0x1f193, 0x1f196, 0x1f19a, 0x1f232,
|
||||
0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f202,
|
||||
0x1f23a, 0x1f250, 0x1f251, 0x3299, 0x00ae, 0x00a9, 0x2122, 0x1f21a,
|
||||
0x1f22f, 0x3297, 0x2b55, 0x274c, 0x274e, 0x2139, 0x1f6ab, 0x2705,
|
||||
0x2714, 0x1f517, 0x2734, 0x2733, 0x2795, 0x2796, 0x2716, 0x2797,
|
||||
0x1f4a0, 0x1f4a1, 0x1f4a4, 0x1f4a2, 0x1f525, 0x1f4a5, 0x1f4a8, 0x1f4a6,
|
||||
0x1f4ab, 0x1f55b, 0x1f567, 0x1f550, 0x1f55c, 0x1f551, 0x1f55d, 0x1f552,
|
||||
0x1f55e, 0x1f553, 0x1f55f, 0x1f554, 0x1f560, 0x1f555, 0x1f561, 0x1f556,
|
||||
0x1f562, 0x1f557, 0x1f563, 0x1f558, 0x1f564, 0x1f559, 0x1f565, 0x1f55a,
|
||||
0x1f566, 0x2195, 0x2b06, 0x2197, 0x27a1, 0x2198, 0x2b07, 0x2199,
|
||||
0x2b05, 0x2196, 0x2194, 0x2934, 0x2935, 0x23ea, 0x23eb, 0x23ec,
|
||||
0x23e9, 0x25c0, 0x25b6, 0x1f53d, 0x1f53c, 0x2747, 0x2728, 0x1f534,
|
||||
0x1f535, 0x26aa, 0x26ab, 0x1f533, 0x1f532, 0x2b50, 0x1f31f, 0x1f320,
|
||||
0x25ab, 0x25aa, 0x25fd, 0x25fe, 0x25fb, 0x25fc, 0x2b1c, 0x2b1b,
|
||||
0x1f538, 0x1f539, 0x1f536, 0x1f537, 0x1f53a, 0x1f53b, 0x1f51f, /*0x20e3,*/
|
||||
0x2754, 0x2753, 0x2755, 0x2757, 0x203c, 0x2049, 0x3030, 0x27b0,
|
||||
0x2660, 0x2665, 0x2663, 0x2666, 0x1f194, 0x1f511, 0x21a9, 0x1f191,
|
||||
0x1f50d, 0x1f512, 0x1f513, 0x21aa, 0x1f510, 0x2611, 0x1f518, 0x1f50e,
|
||||
0x1f516, 0x1f50f, 0x1f503, 0x1f500, 0x1f501, 0x1f502, 0x1f504, 0x1f4e7,
|
||||
0x1f505, 0x1f506, 0x1f507, 0x1f508, 0x1f509, 0x1f50a
|
||||
}
|
||||
};
|
||||
|
||||
private static final SparseArray<DrawInfo> offsets;
|
||||
|
||||
static {
|
||||
offsets = new SparseArray<DrawInfo>();
|
||||
for (int i = 0; i < PAGES.length; i++) {
|
||||
for (int j = 0; j < PAGES[i].length; j++) {
|
||||
offsets.put(PAGES[i][j], new DrawInfo(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap[] bitmaps = new Bitmap[PAGES.length];
|
||||
|
||||
private static Emoji instance = null;
|
||||
|
||||
public synchronized static Emoji getInstance(Context context) {
|
||||
@@ -44,157 +180,287 @@ public class Emoji {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static final Pattern EMOJI_RANGE = Pattern.compile("[\ud83d\ude01-\ud83d\ude4f]");
|
||||
public static final double EMOJI_LARGE = 1;
|
||||
public static final double EMOJI_SMALL = 0.75;
|
||||
public static final int EMOJI_LARGE_SIZE = 22;
|
||||
@SuppressWarnings("MalformedRegex")
|
||||
// 0x20a0-0x32ff 0x1f00-0x1fff 0xfe4e5-0xfe4ee
|
||||
// |==== misc ====||======== emoticons ========||========= flags ==========|
|
||||
private static final Pattern EMOJI_RANGE = Pattern.compile("[\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee]");
|
||||
|
||||
private final Context context;
|
||||
private final String[] emojiAssets;
|
||||
private final Set<String> emojiAssetsSet;
|
||||
private final BitmapFactory.Options bitmapOptions;
|
||||
private final int bigDrawSize;
|
||||
public static final double EMOJI_HUGE = 1.00;
|
||||
public static final double EMOJI_LARGE = 0.75;
|
||||
public static final double EMOJI_SMALL = 0.50;
|
||||
public static final int EMOJI_RAW_SIZE = 128;
|
||||
public static final int EMOJI_PER_ROW = 16;
|
||||
|
||||
private final Context context;
|
||||
private final int bigDrawSize;
|
||||
|
||||
private Emoji(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.emojiAssets = initializeEmojiAssets();
|
||||
this.emojiAssetsSet = new HashSet<String>();
|
||||
this.bitmapOptions = initializeBitmapOptions();
|
||||
|
||||
this.bigDrawSize = scale(EMOJI_LARGE_SIZE);
|
||||
|
||||
Collections.addAll(this.emojiAssetsSet, emojiAssets);
|
||||
this.context = context.getApplicationContext();
|
||||
this.bigDrawSize = context.getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size);
|
||||
}
|
||||
|
||||
private int scale(float value) {
|
||||
return (int)(context.getResources().getDisplayMetrics().density * value);
|
||||
}
|
||||
|
||||
public int getEmojiAssetCount() {
|
||||
return emojiAssets.length;
|
||||
}
|
||||
|
||||
public String getEmojiUnicode(int position) {
|
||||
return getEmojiUnicodeFromAssetName(emojiAssets[position]);
|
||||
}
|
||||
|
||||
public String getRecentEmojiUnicode(int position) {
|
||||
return getEmojiUnicodeFromAssetName(EmojiLRU.getRecentlyUsed(context).get(position));
|
||||
}
|
||||
|
||||
public Drawable getEmojiDrawable(int position) {
|
||||
return getEmojiDrawable(emojiAssets[position]);
|
||||
}
|
||||
|
||||
public SpannableString emojify(String text) {
|
||||
return emojify(new SpannableString(text), EMOJI_LARGE);
|
||||
}
|
||||
|
||||
public SpannableString emojify(SpannableString text, double size) {
|
||||
if (text.toString().contains("\ud83d")) {
|
||||
|
||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
||||
|
||||
while (matches.find()) {
|
||||
String resource = Integer.toHexString(matches.group().codePointAt(0)) + ".png";
|
||||
|
||||
if (emojiAssetsSet.contains(resource)) {
|
||||
Drawable drawable = getEmojiDrawable(resource);
|
||||
drawable.setBounds(0, 0, (int)(bigDrawSize*size),
|
||||
(int)(bigDrawSize*size));
|
||||
|
||||
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
|
||||
text.setSpan(imageSpan, matches.start(), matches.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
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("Emoji", 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[page] != null) return;
|
||||
|
||||
try {
|
||||
final String file = "emoji_" + page + "_wrapped.png";
|
||||
final InputStream measureStream = context.getAssets().open(file);
|
||||
final InputStream bitmapStream = context.getAssets().open(file);
|
||||
|
||||
bitmaps[page] = BitmapUtil.createScaledBitmap(measureStream, bitmapStream, (float) bigDrawSize / (float) EMOJI_RAW_SIZE);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("Emoji", ioe);
|
||||
throw ioe;
|
||||
} catch (BitmapDecodingException bde) {
|
||||
Log.w("Emoji", bde);
|
||||
throw new AssertionError("emoji sprite asset is corrupted or android decoding is broken");
|
||||
}
|
||||
}
|
||||
|
||||
public SpannableString emojify(String text, PageLoadedListener pageLoadedListener) {
|
||||
return emojify(new SpannableString(text), pageLoadedListener);
|
||||
}
|
||||
|
||||
public SpannableString emojify(SpannableString text, PageLoadedListener pageLoadedListener) {
|
||||
return emojify(text, EMOJI_LARGE, pageLoadedListener);
|
||||
}
|
||||
|
||||
public SpannableString emojify(SpannableString text, double size, PageLoadedListener pageLoadedListener) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return text;
|
||||
|
||||
Matcher matches = EMOJI_RANGE.matcher(text);
|
||||
|
||||
while (matches.find()) {
|
||||
String resource = Integer.toHexString(matches.group().codePointAt(0));
|
||||
|
||||
Drawable drawable = getEmojiDrawable(resource, size, pageLoadedListener);
|
||||
if (drawable != null) {
|
||||
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
|
||||
text.setSpan(imageSpan, matches.start(), matches.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setRecentlyUsed(int position) {
|
||||
String assetName = emojiAssets[position];
|
||||
EmojiLRU.putRecentlyUsed(context, assetName);
|
||||
public Pair<Integer, Drawable> getRecentlyUsed(int position, double size, PageLoadedListener pageLoadedListener) {
|
||||
String code = EmojiLRU.getRecentlyUsed(context)[position];
|
||||
return new Pair<Integer, Drawable>(Integer.parseInt(code, 16), getEmojiDrawable(code, size, pageLoadedListener));
|
||||
}
|
||||
|
||||
public void setRecentlyUsed(String emojiCode) {
|
||||
EmojiLRU.putRecentlyUsed(context, emojiCode);
|
||||
}
|
||||
|
||||
public int getRecentlyUsedAssetCount() {
|
||||
return EmojiLRU.getRecentlyUsed(context).size();
|
||||
return EmojiLRU.getRecentlyUsedCount(context);
|
||||
}
|
||||
|
||||
public Drawable getRecentlyUsed(int position) {
|
||||
return getEmojiDrawable(EmojiLRU.getRecentlyUsed(context).get(position));
|
||||
public Drawable getEmojiDrawable(String emojiCode, double size, PageLoadedListener pageLoadedListener) {
|
||||
return getEmojiDrawable(offsets.get(Integer.parseInt(emojiCode, 16)), size, pageLoadedListener);
|
||||
}
|
||||
|
||||
private String getEmojiUnicodeFromAssetName(String assetName) {
|
||||
String hexString = assetName.split("\\.")[0];
|
||||
Integer unicodePoint = Integer.parseInt(hexString, 16);
|
||||
return new String(Character.toChars(unicodePoint));
|
||||
}
|
||||
|
||||
private Drawable getEmojiDrawable(String assetName) {
|
||||
try {
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(context.getAssets().open("emoji" + File.separator + assetName),
|
||||
null, bitmapOptions);
|
||||
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, 64, 64, true);
|
||||
|
||||
return new BitmapDrawable(context.getResources(), bitmap);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
public Drawable getEmojiDrawable(DrawInfo drawInfo, double size, PageLoadedListener pageLoadedListener) {
|
||||
if (drawInfo == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String[] initializeEmojiAssets() {
|
||||
try {
|
||||
return context.getAssets().list("emoji");
|
||||
} catch (IOException e) {
|
||||
Log.w("Emoji", e);
|
||||
return new String[0];
|
||||
final Drawable drawable = new EmojiDrawable(drawInfo, bigDrawSize);
|
||||
drawable.setBounds(0, 0, (int) ((double) bigDrawSize * size), (int) ((double) bigDrawSize * size));
|
||||
if (bitmaps[drawInfo.page] == null) {
|
||||
preloadPage(drawInfo.page, pageLoadedListener);
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapFactory.Options initializeBitmapOptions() {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
options.inScaled = true;
|
||||
options.inDensity = DisplayMetrics.DENSITY_MEDIUM;
|
||||
options.inTargetDensity = context.getResources().getDisplayMetrics().densityDpi;
|
||||
options.inSampleSize = 1;
|
||||
options.inJustDecodeBounds = false;
|
||||
|
||||
return options;
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private static class EmojiLRU {
|
||||
private static SharedPreferences prefs = null;
|
||||
private static LinkedHashSet<String> recentlyUsed = null;
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_popular_emoji";
|
||||
private static final int EMOJI_LRU_SIZE = 50;
|
||||
|
||||
private static final String EMOJI_LRU_PREFERENCE = "pref_popular_emoji";
|
||||
private static final int EMOJI_LRU_SIZE = 10;
|
||||
private static void initializeCache(Context context) {
|
||||
if (prefs == null) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static List<String> getRecentlyUsed(Context context) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String serialized = preferences.getString(EMOJI_LRU_PREFERENCE, "[]");
|
||||
Type type = new TypeToken<Collection<String>>(){}.getType();
|
||||
String serialized = prefs.getString(EMOJI_LRU_PREFERENCE, "[]");
|
||||
Type type = new TypeToken<LinkedHashSet<String>>() {
|
||||
}.getType();
|
||||
|
||||
return new Gson().fromJson(serialized, type);
|
||||
recentlyUsed = new Gson().fromJson(serialized, type);
|
||||
}
|
||||
|
||||
public static String[] getRecentlyUsed(Context context) {
|
||||
if (recentlyUsed == null) initializeCache(context);
|
||||
return recentlyUsed.toArray(new String[recentlyUsed.size()]);
|
||||
}
|
||||
|
||||
public static int getRecentlyUsedCount(Context context) {
|
||||
if (recentlyUsed == null) initializeCache(context);
|
||||
return recentlyUsed.size();
|
||||
}
|
||||
|
||||
public static void putRecentlyUsed(Context context, String asset) {
|
||||
LinkedHashSet<String> recentlyUsed = new LinkedHashSet<String>(getRecentlyUsed(context));
|
||||
if (recentlyUsed == null) initializeCache(context);
|
||||
if (prefs == null) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
recentlyUsed.add(asset);
|
||||
|
||||
if (recentlyUsed.size() > 10) {
|
||||
if (recentlyUsed.size() > EMOJI_LRU_SIZE) {
|
||||
Iterator<String> iterator = recentlyUsed.iterator();
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
String serialized = new Gson().toJson(recentlyUsed);
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
.putString(EMOJI_LRU_PREFERENCE, serialized)
|
||||
.apply();
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
String serialized = new Gson().toJson(recentlyUsed);
|
||||
prefs.edit()
|
||||
.putString(EMOJI_LRU_PREFERENCE, serialized)
|
||||
.apply();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmojiDrawable extends Drawable {
|
||||
private final int index;
|
||||
private final int page;
|
||||
private final int emojiSize;
|
||||
private static final Paint placeholderPaint;
|
||||
private static final Paint paint;
|
||||
private Bitmap bmp;
|
||||
|
||||
static {
|
||||
paint = new Paint();
|
||||
paint.setFilterBitmap(true);
|
||||
placeholderPaint = new Paint();
|
||||
placeholderPaint.setColor(0x55000000);
|
||||
}
|
||||
|
||||
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[page] == null) {
|
||||
Log.w("Emoji", "bitmap for this page was null");
|
||||
canvas.drawRect(getBounds(), placeholderPaint);
|
||||
return;
|
||||
}
|
||||
if (bmp == null) {
|
||||
bmp = bitmaps[page];
|
||||
}
|
||||
|
||||
Rect b = copyBounds();
|
||||
// int cX = b.centerX(), cY = b.centerY();
|
||||
// b.left = cX - emojiSize / 2;
|
||||
// b.right = cX + emojiSize / 2;
|
||||
// b.top = cY - emojiSize / 2;
|
||||
// b.bottom = cY + emojiSize / 2;
|
||||
|
||||
final int row = index / EMOJI_PER_ROW;
|
||||
final int row_index = index % EMOJI_PER_ROW;
|
||||
|
||||
canvas.drawBitmap(bmp,
|
||||
new Rect(row_index * emojiSize,
|
||||
row * emojiSize,
|
||||
(row_index + 1) * emojiSize,
|
||||
(row + 1) * emojiSize),
|
||||
b,
|
||||
paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) { }
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) { }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EmojiDrawable{" +
|
||||
"page=" + page +
|
||||
", index=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static interface PageLoadedListener {
|
||||
public void onPageLoaded();
|
||||
}
|
||||
|
||||
public static class InvalidatingPageLoadedListener implements PageLoadedListener {
|
||||
private final View view;
|
||||
|
||||
public InvalidatingPageLoadedListener(final View view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageLoaded() {
|
||||
view.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
view.invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InvalidatingPageLoadedListener{}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class DrawInfo {
|
||||
int page;
|
||||
int index;
|
||||
|
||||
public DrawInfo(final int page, final int index) {
|
||||
this.page = page;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DrawInfo{" +
|
||||
"page=" + page +
|
||||
", index=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,29 +161,4 @@ public class Util {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// public static Bitmap loadScaledBitmap(InputStream src, int targetWidth, int targetHeight) {
|
||||
// return BitmapFactory.decodeStream(src);
|
||||
//// BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
//// options.inJustDecodeBounds = true;
|
||||
//// BitmapFactory.decodeStream(src, null, options);
|
||||
////
|
||||
//// Log.w("Util", "Bitmap Origin Width: " + options.outWidth);
|
||||
//// Log.w("Util", "Bitmap Origin Height: " + options.outHeight);
|
||||
////
|
||||
//// boolean scaleByHeight =
|
||||
//// Math.abs(options.outHeight - targetHeight) >=
|
||||
//// Math.abs(options.outWidth - targetWidth);
|
||||
////
|
||||
//// if (options.outHeight * options.outWidth >= targetWidth * targetHeight * 2) {
|
||||
//// double sampleSize = scaleByHeight ? (double)options.outHeight / (double)targetHeight : (double)options.outWidth / (double)targetWidth;
|
||||
////// options.inSampleSize = (int)Math.pow(2d, Math.floor(Math.log(sampleSize) / Math.log(2d)));
|
||||
//// Log.w("Util", "Sampling by: " + options.inSampleSize);
|
||||
//// }
|
||||
////
|
||||
//// options.inJustDecodeBounds = false;
|
||||
////
|
||||
//// return BitmapFactory.decodeStream(src, null, options);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user