2015-05-06 13:53:55 -07:00
|
|
|
package org.thoughtcrime.securesms.components.emoji;
|
|
|
|
|
|
|
|
import android.content.Context;
|
2017-04-25 19:09:35 -05:00
|
|
|
import android.content.res.TypedArray;
|
2015-05-30 23:52:41 -07:00
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.support.annotation.NonNull;
|
2015-08-06 10:35:51 -07:00
|
|
|
import android.support.annotation.Nullable;
|
2018-04-20 13:53:23 -07:00
|
|
|
import android.support.v4.widget.TextViewCompat;
|
2015-05-14 15:54:07 -07:00
|
|
|
import android.support.v7.widget.AppCompatTextView;
|
2018-04-20 13:53:23 -07:00
|
|
|
import android.text.SpannableStringBuilder;
|
2015-07-28 15:38:38 -07:00
|
|
|
import android.text.TextUtils;
|
2015-05-06 13:53:55 -07:00
|
|
|
import android.util.AttributeSet;
|
2017-04-25 19:09:35 -05:00
|
|
|
import android.util.TypedValue;
|
2015-05-14 15:54:07 -07:00
|
|
|
|
2017-04-25 19:09:35 -05:00
|
|
|
import org.thoughtcrime.securesms.R;
|
2015-05-30 23:52:41 -07:00
|
|
|
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
2017-04-25 19:09:35 -05:00
|
|
|
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
2015-09-07 13:11:40 -10:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2018-05-24 13:30:28 -04:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2018-04-20 13:53:23 -07:00
|
|
|
|
2015-05-30 23:52:41 -07:00
|
|
|
|
2015-05-14 15:54:07 -07:00
|
|
|
public class EmojiTextView extends AppCompatTextView {
|
2018-05-24 13:30:28 -04:00
|
|
|
|
2017-04-25 19:09:35 -05:00
|
|
|
private final boolean scaleEmojis;
|
|
|
|
|
2018-06-06 07:57:03 -07:00
|
|
|
private CharSequence previousText;
|
|
|
|
private BufferType previousBufferType;
|
2017-04-28 16:38:58 -05:00
|
|
|
private float originalFontSize;
|
2018-05-28 17:20:28 -04:00
|
|
|
private boolean useSystemEmoji;
|
2018-06-06 07:57:03 -07:00
|
|
|
private boolean sizeChangeInProgress;
|
2015-05-26 15:10:45 -07:00
|
|
|
|
2015-05-06 13:53:55 -07:00
|
|
|
public EmojiTextView(Context context) {
|
2015-05-30 23:52:41 -07:00
|
|
|
this(context, null);
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public EmojiTextView(Context context, AttributeSet attrs) {
|
2015-05-30 23:52:41 -07:00
|
|
|
this(context, attrs, 0);
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
|
|
super(context, attrs, defStyleAttr);
|
2017-04-25 19:09:35 -05:00
|
|
|
|
|
|
|
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
|
|
|
|
scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false);
|
|
|
|
a.recycle();
|
|
|
|
|
|
|
|
a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textSize});
|
|
|
|
originalFontSize = a.getDimensionPixelSize(0, 0);
|
|
|
|
a.recycle();
|
2015-07-28 15:38:38 -07:00
|
|
|
}
|
|
|
|
|
2015-08-06 10:35:51 -07:00
|
|
|
@Override public void setText(@Nullable CharSequence text, BufferType type) {
|
2018-05-28 17:20:28 -04:00
|
|
|
EmojiProvider provider = EmojiProvider.getInstance(getContext());
|
2017-04-25 19:09:35 -05:00
|
|
|
EmojiParser.CandidateList candidates = provider.getCandidates(text);
|
|
|
|
|
|
|
|
if (scaleEmojis && candidates != null && candidates.allEmojis) {
|
2018-05-28 17:20:28 -04:00
|
|
|
int emojis = candidates.size();
|
|
|
|
float scale = 1.0f;
|
|
|
|
|
2017-04-25 19:09:35 -05:00
|
|
|
if (emojis <= 8) scale += 0.25f;
|
|
|
|
if (emojis <= 6) scale += 0.25f;
|
|
|
|
if (emojis <= 4) scale += 0.25f;
|
|
|
|
if (emojis <= 2) scale += 0.25f;
|
2018-05-28 17:20:28 -04:00
|
|
|
|
2017-04-28 16:38:58 -05:00
|
|
|
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize * scale);
|
2017-04-25 19:09:35 -05:00
|
|
|
} else if (scaleEmojis) {
|
2017-04-28 16:38:58 -05:00
|
|
|
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalFontSize);
|
2017-04-25 19:09:35 -05:00
|
|
|
}
|
|
|
|
|
2018-06-06 07:57:03 -07:00
|
|
|
if (unchanged(text, type)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
previousText = text;
|
|
|
|
previousBufferType = type;
|
|
|
|
useSystemEmoji = useSystemEmoji();
|
|
|
|
|
2018-05-28 17:20:28 -04:00
|
|
|
if (useSystemEmoji || candidates == null || candidates.size() == 0) {
|
2018-05-24 13:30:28 -04:00
|
|
|
super.setText(text, BufferType.NORMAL);
|
2015-09-07 13:11:40 -10:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-24 13:30:28 -04:00
|
|
|
CharSequence emojified = provider.emojify(candidates, text, this);
|
|
|
|
super.setText(emojified, BufferType.SPANNABLE);
|
2018-04-20 13:53:23 -07:00
|
|
|
|
|
|
|
// Android fails to ellipsize spannable strings. (https://issuetracker.google.com/issues/36991688)
|
|
|
|
// We ellipsize them ourselves by manually truncating the appropriate section.
|
|
|
|
if (getEllipsize() == TextUtils.TruncateAt.END) {
|
2018-05-24 13:30:28 -04:00
|
|
|
ellipsize();
|
2018-04-20 13:53:23 -07:00
|
|
|
}
|
2015-07-28 15:38:38 -07:00
|
|
|
}
|
|
|
|
|
2018-05-24 13:30:28 -04:00
|
|
|
private void ellipsize() {
|
|
|
|
post(() -> {
|
2018-05-28 17:20:28 -04:00
|
|
|
if (getLayout() == null) {
|
2018-05-24 13:30:28 -04:00
|
|
|
ellipsize();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int maxLines = TextViewCompat.getMaxLines(EmojiTextView.this);
|
|
|
|
if (maxLines <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int lineCount = getLineCount();
|
|
|
|
if (lineCount > maxLines) {
|
|
|
|
int overflowStart = getLayout().getLineStart(maxLines - 1);
|
|
|
|
CharSequence overflow = getText().subSequence(overflowStart, getText().length());
|
|
|
|
CharSequence ellipsized = TextUtils.ellipsize(overflow, getPaint(), getWidth(), TextUtils.TruncateAt.END);
|
|
|
|
|
|
|
|
SpannableStringBuilder newContent = new SpannableStringBuilder();
|
|
|
|
newContent.append(getText().subSequence(0, overflowStart))
|
|
|
|
.append(ellipsized.subSequence(0, ellipsized.length()));
|
|
|
|
|
|
|
|
EmojiParser.CandidateList newCandidates = EmojiProvider.getInstance(getContext()).getCandidates(newContent);
|
|
|
|
CharSequence emojified = EmojiProvider.getInstance(getContext()).emojify(newCandidates, newContent, this);
|
|
|
|
|
|
|
|
super.setText(emojified, BufferType.SPANNABLE);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-06-06 07:57:03 -07:00
|
|
|
private boolean unchanged(CharSequence text, BufferType bufferType) {
|
|
|
|
return Util.equals(previousText, text) &&
|
|
|
|
Util.equals(previousBufferType, bufferType) &&
|
|
|
|
useSystemEmoji == useSystemEmoji() &&
|
|
|
|
!sizeChangeInProgress;
|
|
|
|
}
|
|
|
|
|
2015-09-21 12:02:34 -07:00
|
|
|
private boolean useSystemEmoji() {
|
|
|
|
return TextSecurePreferences.isSystemEmojiPreferred(getContext());
|
|
|
|
}
|
|
|
|
|
2018-06-06 07:57:03 -07:00
|
|
|
@Override
|
|
|
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
|
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
|
|
|
|
|
|
if (!sizeChangeInProgress) {
|
|
|
|
sizeChangeInProgress = true;
|
|
|
|
setText(previousText, previousBufferType);
|
|
|
|
sizeChangeInProgress = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void invalidateDrawable(@NonNull Drawable drawable) {
|
2015-05-30 23:52:41 -07:00
|
|
|
if (drawable instanceof EmojiDrawable) invalidate();
|
|
|
|
else super.invalidateDrawable(drawable);
|
2015-05-14 15:54:07 -07:00
|
|
|
}
|
2015-07-28 15:38:38 -07:00
|
|
|
|
2017-04-28 16:38:58 -05:00
|
|
|
@Override
|
|
|
|
public void setTextSize(float size) {
|
|
|
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setTextSize(int unit, float size) {
|
|
|
|
this.originalFontSize = TypedValue.applyDimension(unit, size, getResources().getDisplayMetrics());
|
|
|
|
super.setTextSize(unit, size);
|
|
|
|
}
|
2015-05-06 13:53:55 -07:00
|
|
|
}
|