Reaction.java

  1package eu.siacs.conversations.entities;
  2
  3import androidx.annotation.NonNull;
  4
  5import com.cheogram.android.EmojiSearch;
  6import com.cheogram.android.GetThumbnailForCid;
  7
  8import com.google.common.base.MoreObjects;
  9import com.google.common.base.Strings;
 10import com.google.common.collect.Collections2;
 11import com.google.common.collect.ImmutableList;
 12import com.google.common.collect.ImmutableSet;
 13import com.google.common.collect.Maps;
 14import com.google.common.collect.Multimaps;
 15import com.google.common.collect.Ordering;
 16import com.google.gson.reflect.TypeToken;
 17import com.google.gson.Gson;
 18import com.google.gson.GsonBuilder;
 19import com.google.gson.JsonSyntaxException;
 20import com.google.gson.TypeAdapter;
 21import com.google.gson.stream.JsonReader;
 22import com.google.gson.stream.JsonToken;
 23import com.google.gson.stream.JsonWriter;
 24
 25import io.ipfs.cid.Cid;
 26
 27import eu.siacs.conversations.xmpp.Jid;
 28
 29import java.io.IOException;
 30import java.util.ArrayList;
 31import java.util.Arrays;
 32import java.util.Collection;
 33import java.util.Collections;
 34import java.util.Comparator;
 35import java.util.List;
 36import java.util.Map;
 37import java.util.Set;
 38import java.util.function.Function;
 39
 40public class Reaction {
 41
 42    public static final List<String> SUGGESTIONS =
 43            Arrays.asList(
 44                    "\u2764\uFE0F",
 45                    "\uD83D\uDC4D",
 46                    "\uD83D\uDC4E",
 47                    "\uD83D\uDE02",
 48                    "\u2757",
 49                    "\u2753");
 50
 51    private static final Gson GSON;
 52
 53    static {
 54        GSON = new GsonBuilder().registerTypeAdapter(Jid.class, new JidTypeAdapter()).registerTypeAdapter(Cid.class, new CidTypeAdapter()).create();
 55    }
 56
 57    public final String reaction;
 58    public final boolean received;
 59    public final Jid from;
 60    public final Jid trueJid;
 61    public final String occupantId;
 62    public final Cid cid;
 63    public final String envelopeId;
 64
 65    public Reaction(
 66            final String reaction,
 67            final Cid cid,
 68            boolean received,
 69            final Jid from,
 70            final Jid trueJid,
 71            final String occupantId,
 72            final String envelopeId) {
 73        this.reaction = reaction;
 74        this.cid = cid;
 75        this.received = received;
 76        this.from = from;
 77        this.trueJid = trueJid;
 78        this.occupantId = occupantId;
 79        this.envelopeId = envelopeId;
 80    }
 81
 82    public static String toString(final Collection<Reaction> reactions) {
 83        return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions);
 84    }
 85
 86    public static Collection<Reaction> fromString(final String asString) {
 87        if (Strings.isNullOrEmpty(asString)) {
 88            return Collections.emptyList();
 89        }
 90        try {
 91            return GSON.fromJson(asString, new TypeToken<ArrayList<Reaction>>() {}.getType());
 92        } catch (final JsonSyntaxException e) {
 93            return Collections.emptyList();
 94        }
 95    }
 96
 97    public static Collection<Reaction> withMine(
 98            final Collection<Reaction> existing,
 99            final Collection<String> reactions,
100            final boolean received,
101            final Jid from,
102            final Jid trueJid,
103            final String occupantId,
104            final String envelopeId) {
105         final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
106        builder.addAll(Collections2.filter(existing, e -> e.received));
107        builder.addAll(
108                Collections2.transform(
109                        reactions, r -> new Reaction(r, null, received, from, trueJid, occupantId, envelopeId)));
110        return builder.build();
111    }
112
113    public static Collection<Reaction> withOccupantId(
114            final Collection<Reaction> existing,
115            final Collection<String> reactions,
116            final boolean received,
117            final Jid from,
118            final Jid trueJid,
119            final String occupantId,
120            final String envelopeId) {
121        final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
122        builder.addAll(Collections2.filter(existing, e -> !occupantId.equals(e.occupantId)));
123        builder.addAll(
124                Collections2.transform(
125                        reactions, r -> new Reaction(r, null, received, from, trueJid, occupantId, envelopeId)));
126        return builder.build();
127    }
128
129    @NonNull
130    @Override
131    public String toString() {
132        return MoreObjects.toStringHelper(this)
133                .add("reaction", cid == null ? reaction : null)
134                .add("cid", cid)
135                .add("received", received)
136                .add("from", from)
137                .add("trueJid", trueJid)
138                .add("occupantId", occupantId)
139                .toString();
140    }
141
142    public int hashCode() {
143        return toString().hashCode();
144    }
145
146    public boolean equals(Object o) {
147        if (o == null) return false;
148        if (!(o instanceof Reaction)) return false;
149        return toString().equals(o.toString());
150    }
151
152    public static Collection<Reaction> withFrom(
153            final Collection<Reaction> existing,
154            final Collection<String> reactions,
155            final boolean received,
156            final Jid from,
157            final String envelopeId) {
158        final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
159        builder.addAll(
160                Collections2.filter(existing, e -> !from.asBareJid().equals(e.from.asBareJid())));
161        builder.addAll(
162                Collections2.transform(
163                        reactions, r -> new Reaction(r, null, received, from, null, null, envelopeId)));
164        return builder.build();
165    }
166
167    private static class JidTypeAdapter extends TypeAdapter<Jid> {
168        @Override
169        public void write(final JsonWriter out, final Jid value) throws IOException {
170            if (value == null) {
171                out.nullValue();
172            } else {
173                out.value(value.toEscapedString());
174            }
175        }
176
177        @Override
178        public Jid read(final JsonReader in) throws IOException {
179            if (in.peek() == JsonToken.NULL) {
180                in.nextNull();
181                return null;
182            } else if (in.peek() == JsonToken.STRING) {
183                final String value = in.nextString();
184                return Jid.ofEscaped(value);
185            }
186            throw new IOException("Unexpected token");
187        }
188    }
189
190    private static class CidTypeAdapter extends TypeAdapter<Cid> {
191        @Override
192        public void write(final JsonWriter out, final Cid value) throws IOException {
193            if (value == null) {
194                out.nullValue();
195            } else {
196                out.value(value.toString());
197            }
198        }
199
200        @Override
201        public Cid read(final JsonReader in) throws IOException {
202            if (in.peek() == JsonToken.NULL) {
203                in.nextNull();
204                return null;
205            } else if (in.peek() == JsonToken.STRING) {
206                final String value = in.nextString();
207                return Cid.decode(value);
208            }
209            throw new IOException("Unexpected token");
210        }
211    }
212
213    public static Aggregated aggregated(final Collection<Reaction> reactions) {
214        return aggregated(reactions, (r) -> null);
215    }
216
217    public static Aggregated aggregated(final Collection<Reaction> reactions, Function<Reaction, GetThumbnailForCid> thumbnailer) {
218        final Map<EmojiSearch.Emoji, Collection<Reaction>> aggregatedReactions =
219                        Multimaps.index(reactions, r -> r.cid == null ? new EmojiSearch.Emoji(r.reaction, 0) : new EmojiSearch.CustomEmoji(r.reaction, r.cid.toString(), thumbnailer.apply(r).getThumbnail(r.cid), null)).asMap();
220        final List<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>> sortedList =
221                Ordering.from(
222                                Comparator.comparingInt(
223                                        (Map.Entry<EmojiSearch.Emoji, Collection<Reaction>> o) -> o.getValue().size()))
224                        .reverse()
225                        .immutableSortedCopy(aggregatedReactions.entrySet());
226        return new Aggregated(
227                sortedList,
228                ImmutableSet.copyOf(
229                        Collections2.transform(
230                                Collections2.filter(reactions, r -> r.cid == null && !r.received),
231                                r -> r.reaction)));
232    }
233
234    public static final class Aggregated {
235
236        public final List<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>> reactions;
237        public final Set<String> ourReactions;
238
239        private Aggregated(
240                final List<Map.Entry<EmojiSearch.Emoji, Collection<Reaction>>> reactions, Set<String> ourReactions) {
241            this.reactions = reactions;
242            this.ourReactions = ourReactions;
243        }
244    }
245}