1package com.cheogram.android;
2import android.util.Log;
3
4import android.content.Context;
5import android.graphics.drawable.Drawable;
6import android.text.Spannable;
7import android.text.SpannableStringBuilder;
8import android.view.LayoutInflater;
9import android.view.View;
10import android.view.ViewGroup;
11import android.widget.ArrayAdapter;
12
13import androidx.databinding.DataBindingUtil;
14
15import com.google.common.collect.Lists;
16import com.google.common.io.CharStreams;
17
18import java.io.IOException;
19import java.io.InputStreamReader;
20import java.lang.Comparable;
21import java.util.Arrays;
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.List;
25import java.util.Set;
26import java.util.TreeSet;
27
28import me.xdrop.fuzzywuzzy.FuzzySearch;
29import me.xdrop.fuzzywuzzy.algorithms.WeightedRatio;
30import me.xdrop.fuzzywuzzy.model.BoundExtractedResult;
31
32import org.json.JSONArray;
33import org.json.JSONException;
34import org.json.JSONObject;
35
36import eu.siacs.conversations.R;
37import eu.siacs.conversations.databinding.EmojiSearchRowBinding;
38
39public class EmojiSearch {
40 protected final Set<Emoji> emoji = new TreeSet<>();
41
42 public EmojiSearch(Context context) {
43 try {
44 final JSONArray data = new JSONArray(CharStreams.toString(new InputStreamReader(context.getResources().openRawResource(R.raw.emoji), "UTF-8")));
45 for (int i = 0; i < data.length(); i++) {
46 emoji.add(new Emoji(data.getJSONObject(i)));
47 }
48 } catch (final JSONException | IOException e) {
49 throw new IllegalStateException("emoji.json invalid: " + e);
50 }
51 }
52
53 public synchronized void addEmoji(final Emoji one) {
54 emoji.add(one);
55 }
56
57 public synchronized List<Emoji> find(final String q) {
58 final Set<Emoji> emoticon = new TreeSet<>();
59 for (Emoji e : emoji) {
60 if (e.emoticonMatch(q)) {
61 emoticon.add(e);
62 }
63 }
64
65 WeightedRatio wr = new WeightedRatio();
66 List<BoundExtractedResult<Emoji>> result = FuzzySearch.extractTop(
67 q,
68 emoji,
69 (e) -> e.fuzzyFind,
70 (query, s) -> {
71 int score = 0;
72 String[] kinds = s.split(">");
73 for (int i = 0; i < kinds.length; i++) {
74 int nscore = Collections.max(Lists.transform(Arrays.asList(kinds[i].split("~")), (x) -> wr.apply(query, x))) - (i * 2);
75 if (nscore > score) score = nscore;
76 }
77 return score;
78 },
79 10
80 );
81
82 List<Emoji> lst = new ArrayList<>(emoticon);
83 lst.addAll(Lists.transform(result, (r) -> r.getReferent()));
84
85 List<Emoji> scanList = new ArrayList<>(lst);
86 int inserted = 0;
87 for (int i = 0; i < scanList.size(); i++) {
88 for (Emoji e : emoji) {
89 if (e.shortcodeMatch(scanList.get(i).uniquePart())) {
90 inserted ++;
91 lst.add(i + inserted, e);
92 }
93 }
94 }
95 return lst;
96 }
97
98 public EmojiSearchAdapter makeAdapter(Context context) {
99 return new EmojiSearchAdapter(context);
100 }
101
102 public static class Emoji implements Comparable<Emoji> {
103 protected final String unicode;
104 protected final int order;
105 protected final List<String> tags = new ArrayList<>();
106 protected final List<String> emoticon = new ArrayList<>();
107 protected final List<String> shortcodes = new ArrayList<>();
108 protected final String fuzzyFind;
109
110 public Emoji(final String unicode, final int order, final String fuzzyFind) {
111 this.unicode = unicode;
112 this.order = order;
113 this.fuzzyFind = fuzzyFind;
114 }
115
116 public Emoji(JSONObject o) throws JSONException {
117 unicode = o.getString("unicode");
118 order = o.getInt("order");
119 final JSONArray rawTags = o.getJSONArray("tags");
120 for (int i = 0; i < rawTags.length(); i++) {
121 tags.add(rawTags.getString(i));
122 }
123 final JSONArray rawEmoticon = o.getJSONArray("emoticon");
124 for (int i = 0; i < rawEmoticon.length(); i++) {
125 emoticon.add(rawEmoticon.getString(i));
126 }
127 final JSONArray rawShortcodes = o.getJSONArray("shortcodes");
128 for (int i = 0; i < rawShortcodes.length(); i++) {
129 shortcodes.add(rawShortcodes.getString(i));
130 }
131 fuzzyFind = String.join("~", shortcodes) + ">" + String.join("~", tags);
132 }
133
134 public boolean emoticonMatch(final String q) {
135 for (final String emote : emoticon) {
136 if (emote.equals(q) || emote.equals(":" + q)) return true;
137 }
138
139 return false;
140 }
141
142 public boolean shortcodeMatch(final String q) {
143 for (final String shortcode : shortcodes) {
144 if (shortcode.equals(q)) return true;
145 }
146
147 return false;
148 }
149
150 public SpannableStringBuilder toInsert() {
151 return new SpannableStringBuilder(unicode);
152 }
153
154 public String uniquePart() {
155 return unicode;
156 }
157
158 @Override
159 public int compareTo(Emoji o) {
160 if (equals(o)) return 0;
161 if (order == o.order) return uniquePart().compareTo(o.uniquePart());
162 return order - o.order;
163 }
164
165 @Override
166 public boolean equals(Object o) {
167 if (!(o instanceof Emoji)) return false;
168
169 return uniquePart().equals(((Emoji) o).uniquePart());
170 }
171 }
172
173 public static class CustomEmoji extends Emoji {
174 protected final String source;
175 protected final Drawable icon;
176
177 public CustomEmoji(final String shortcode, final String source, final Drawable icon) {
178 super(null, 10, shortcode);
179 shortcodes.add(shortcode);
180 this.source = source;
181 this.icon = icon;
182 if (icon == null) {
183 throw new IllegalArgumentException("icon must not be null");
184 }
185 }
186
187 public SpannableStringBuilder toInsert() {
188 SpannableStringBuilder builder = new SpannableStringBuilder(":" + shortcodes.get(0) + ":");
189 builder.setSpan(new InlineImageSpan(icon, source), 0, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
190 return builder;
191 }
192
193 @Override
194 public String uniquePart() {
195 return source;
196 }
197 }
198
199 public class EmojiSearchAdapter extends ArrayAdapter<Emoji> {
200 public EmojiSearchAdapter(Context context) {
201 super(context, 0);
202 }
203
204 @Override
205 public View getView(int position, View view, ViewGroup parent) {
206 EmojiSearchRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.emoji_search_row, parent, false);
207 if (getItem(position) instanceof CustomEmoji) {
208 binding.nonunicode.setText(getItem(position).toInsert());
209 binding.nonunicode.setVisibility(View.VISIBLE);
210 binding.unicode.setVisibility(View.GONE);
211 } else {
212 binding.unicode.setText(getItem(position).toInsert());
213 binding.unicode.setVisibility(View.VISIBLE);
214 binding.nonunicode.setVisibility(View.GONE);
215 }
216 binding.shortcode.setText(getItem(position).shortcodes.get(0));
217 return binding.getRoot();
218 }
219
220 public void search(final String q) {
221 clear();
222 addAll(find(q));
223 notifyDataSetChanged();
224 }
225 }
226}