From f0e48c8af5133a141e25580e1e79f78895f9633a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 29 Sep 2017 22:56:17 +0200 Subject: [PATCH] use own algorithm to count emoji instead of using emoji-java --- build.gradle | 3 - .../siacs/conversations/entities/Message.java | 5 +- .../ui/adapter/MessageAdapter.java | 5 +- .../siacs/conversations/utils/Emoticons.java | 150 ++++++++++++++++++ 4 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/utils/Emoticons.java diff --git a/build.gradle b/build.gradle index 767f37ae..bf8a6a20 100644 --- a/build.gradle +++ b/build.gradle @@ -51,9 +51,6 @@ dependencies { compile 'com.makeramen:roundedimageview:2.3.0' compile "com.wefika:flowlayout:0.4.1" compile 'net.ypresto.androidtranscoder:android-transcoder:0.2.0' - compile('com.vdurmont:emoji-java:3.3.0') { - exclude group: 'org.json', module: 'json' - } } ext { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index df877411..d01ec2dc 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -4,8 +4,6 @@ import android.content.ContentValues; import android.database.Cursor; import android.text.SpannableStringBuilder; -import com.vdurmont.emoji.EmojiManager; - import java.net.MalformedURLException; import java.net.URL; @@ -14,6 +12,7 @@ import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.http.AesGcmURLStreamHandler; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -684,7 +683,7 @@ public class Message extends AbstractEntity { public synchronized boolean bodyIsOnlyEmojis() { if (isEmojisOnly == null) { - isEmojisOnly = EmojiManager.isOnlyEmojis(body.replaceAll("\\s", "")); + isEmojisOnly = Emoticons.isOnlyEmoji(body.replaceAll("\\s","")); } return isEmojisOnly; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index a5823bbe..335ce167 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -37,8 +37,6 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; -import com.vdurmont.emoji.EmojiManager; - import java.lang.ref.WeakReference; import java.net.URL; import java.util.List; @@ -67,6 +65,7 @@ import eu.siacs.conversations.ui.widget.ClickableMovementMethod; import eu.siacs.conversations.ui.widget.CopyTextView; import eu.siacs.conversations.ui.widget.ListSelectionManager; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.UIHelper; @@ -332,7 +331,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); Spannable span = new SpannableString(body); - float size = EmojiManager.isEmoji(body) ? 3.0f : 2.0f; + float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f; span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } diff --git a/src/main/java/eu/siacs/conversations/utils/Emoticons.java b/src/main/java/eu/siacs/conversations/utils/Emoticons.java new file mode 100644 index 00000000..84760fe6 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/Emoticons.java @@ -0,0 +1,150 @@ +package eu.siacs.conversations.utils; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.Config; + +public class Emoticons { + + private static final UnicodeRange MISC_SYMBOLS_AND_PICTOGRAPHS = new UnicodeRange(0x1F300,0x1F5FF); + private static final UnicodeRange SUPPLEMENTAL_SYMBOLS = new UnicodeRange(0x1F900,0x1F9FF); + private static final UnicodeRange EMOTICONS = new UnicodeRange(0x1F600,0x1F64F); + private static final UnicodeRange TRANSPORT_SYMBOLS = new UnicodeRange(0x1F680,0x1F6FF); + private static final UnicodeRange MISC_SYMBOLS = new UnicodeRange(0x2600,0x26FF); + private static final UnicodeRange DINGBATS = new UnicodeRange(0x2700,0x27BF); + private static final UnicodeRange REGIONAL_INDICATORS = new UnicodeRange(0x1F1E6,0x1F1FF); + private static final UnicodeBlocks EMOJIS = new UnicodeBlocks(MISC_SYMBOLS_AND_PICTOGRAPHS,SUPPLEMENTAL_SYMBOLS,EMOTICONS,TRANSPORT_SYMBOLS,MISC_SYMBOLS,DINGBATS); + private static final int ZWJ = 0x200D; + private static final int VARIATION_16 = 0xFE0F; + private static final UnicodeRange FITZPATRICK = new UnicodeRange(0x1F3FB,0x1F3FF); + + private static List parse(String input) { + List symbols = new ArrayList<>(); + Builder builder = new Builder(); + boolean needsFinalBuild = false; + for (int cp, i = 0; i < input.length(); i += Character.charCount(cp)) { + cp = input.codePointAt(i); + if (builder.offer(cp)) { + needsFinalBuild = true; + } else { + symbols.add(builder.build()); + builder = new Builder(); + if (builder.offer(cp)) { + needsFinalBuild = true; + } + } + } + if (needsFinalBuild) { + symbols.add(builder.build()); + } + return symbols; + } + + public static boolean isEmoji(String input) { + List symbols = parse(input); + return symbols.size() == 1 && symbols.get(0) == Symbol.EMOJI; + } + + public static boolean isOnlyEmoji(String input) { + List symbols = parse(input); + for(Symbol symbol : symbols) { + if (symbol == Symbol.NON_EMOJI) { + return false; + } + } + return symbols.size() > 0; + } + + private enum Symbol { + EMOJI, NON_EMOJI + } + + + private static class Builder { + private final List codepoints = new ArrayList<>(); + + + public boolean offer(int codepoint) { + if (this.codepoints.size() == 0) { + if (REGIONAL_INDICATORS.contains(codepoint)) { + codepoints.add(codepoint); + return true; + } else if (EMOJIS.contains(codepoint) && !FITZPATRICK.contains(codepoint) && codepoint != ZWJ) { + codepoints.add(codepoint); + return true; + } + } else { + int previous = codepoints.get(codepoints.size() -1); + if (REGIONAL_INDICATORS.contains(previous) && REGIONAL_INDICATORS.contains(codepoint)) { + if (codepoints.size() == 1) { + codepoints.add(codepoint); + return true; + } + } else if (previous == VARIATION_16) { + if (isMerger(codepoint)) { + codepoints.add(codepoint); + return true; + } + } else if (isMerger(previous)) { + if (EMOJIS.contains(codepoint)) { + codepoints.add(codepoint); + return true; + } + } else { + if (isMerger(codepoint)) { + codepoints.add(codepoint); + return true; + } else if (codepoint == VARIATION_16 && EMOJIS.contains(previous)) { + codepoints.add(codepoint); + return true; + } + } + } + return false; + } + + public static boolean isMerger(int codepoint) { + return codepoint == ZWJ || FITZPATRICK.contains(codepoint); + } + + public Symbol build() { + return codepoints.size() == 0 ? Symbol.NON_EMOJI : Symbol.EMOJI; + } + } + + public static class UnicodeBlocks { + final UnicodeRange[] ranges; + + public UnicodeBlocks(UnicodeRange... ranges) { + this.ranges = ranges; + } + + public boolean contains(int codepoint) { + for(UnicodeRange range : ranges) { + if (range.contains(codepoint)) { + return true; + } + } + return false; + } + } + + + public static class UnicodeRange { + + private final int lower; + private final int upper; + + UnicodeRange(int lower, int upper) { + this.lower = lower; + this.upper = upper; + } + + public boolean contains(int codePoint) { + return codePoint >= lower && codePoint <= upper; + } + } +}