Reaction.java

  1package eu.siacs.conversations.entities;
  2
  3import android.util.Log;
  4
  5import androidx.annotation.NonNull;
  6
  7import com.google.common.base.MoreObjects;
  8import com.google.common.base.Strings;
  9import com.google.common.collect.Collections2;
 10import com.google.common.collect.ImmutableList;
 11import com.google.common.collect.ImmutableSet;
 12import com.google.common.collect.Maps;
 13import com.google.common.collect.Multimaps;
 14import com.google.common.collect.Ordering;
 15import com.google.gson.Gson;
 16import com.google.gson.GsonBuilder;
 17import com.google.gson.JsonSyntaxException;
 18import com.google.gson.TypeAdapter;
 19import com.google.gson.reflect.TypeToken;
 20import com.google.gson.stream.JsonReader;
 21import com.google.gson.stream.JsonToken;
 22import com.google.gson.stream.JsonWriter;
 23
 24import eu.siacs.conversations.Config;
 25import eu.siacs.conversations.utils.Emoticons;
 26import eu.siacs.conversations.xmpp.Jid;
 27
 28import java.io.IOException;
 29import java.util.Arrays;
 30import java.util.Collection;
 31import java.util.Collections;
 32import java.util.Comparator;
 33import java.util.List;
 34import java.util.Map;
 35import java.util.Set;
 36
 37public class Reaction {
 38
 39    public static final List<String> SUGGESTIONS =
 40            Arrays.asList(
 41                    "\u2764\uFE0F",
 42                    "\uD83D\uDC4D",
 43                    "\uD83D\uDC4E",
 44                    "\uD83D\uDE02",
 45                    "\uD83D\uDE2E",
 46                    "\uD83D\uDE22");
 47
 48    private static final Gson GSON;
 49
 50    static {
 51        GSON = new GsonBuilder().registerTypeAdapter(Jid.class, new JidTypeAdapter()).create();
 52    }
 53
 54    public final String reaction;
 55    public final boolean received;
 56    public final Jid from;
 57    public final Jid trueJid;
 58    public final String occupantId;
 59
 60    public Reaction(
 61            final String reaction,
 62            boolean received,
 63            final Jid from,
 64            final Jid trueJid,
 65            final String occupantId) {
 66        this.reaction = reaction;
 67        this.received = received;
 68        this.from = from;
 69        this.trueJid = trueJid;
 70        this.occupantId = occupantId;
 71    }
 72
 73    public String normalizedReaction() {
 74        return Emoticons.normalizeToVS16(this.reaction);
 75    }
 76
 77    public static String toString(final Collection<Reaction> reactions) {
 78        return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions);
 79    }
 80
 81    public static Collection<Reaction> fromString(final String asString) {
 82        if (Strings.isNullOrEmpty(asString)) {
 83            return Collections.emptyList();
 84        }
 85        try {
 86            return GSON.fromJson(asString, new TypeToken<List<Reaction>>() {}.getType());
 87        } catch (final IllegalArgumentException | JsonSyntaxException e) {
 88            Log.e(Config.LOGTAG, "could not restore reactions", e);
 89            return Collections.emptyList();
 90        }
 91    }
 92
 93    public static Collection<Reaction> withOccupantId(
 94            final Collection<Reaction> existing,
 95            final Collection<String> reactions,
 96            final boolean received,
 97            final Jid from,
 98            final Jid trueJid,
 99            final String occupantId) {
100        final ImmutableList.Builder<Reaction> builder = new ImmutableList.Builder<>();
101        builder.addAll(Collections2.filter(existing, e -> !occupantId.equals(e.occupantId)));
102        builder.addAll(
103                Collections2.transform(
104                        reactions, r -> new Reaction(r, received, from, trueJid, occupantId)));
105        return builder.build();
106    }
107
108    @NonNull
109    @Override
110    public String toString() {
111        return MoreObjects.toStringHelper(this)
112                .add("reaction", reaction)
113                .add("received", received)
114                .add("from", from)
115                .add("trueJid", trueJid)
116                .add("occupantId", occupantId)
117                .toString();
118    }
119
120    public static Collection<Reaction> withFrom(
121            final Collection<Reaction> existing,
122            final Collection<String> reactions,
123            final boolean received,
124            final Jid from) {
125        final ImmutableList.Builder<Reaction> builder = new ImmutableList.Builder<>();
126        builder.addAll(
127                Collections2.filter(existing, e -> !from.asBareJid().equals(e.from.asBareJid())));
128        builder.addAll(
129                Collections2.transform(
130                        reactions, r -> new Reaction(r, received, from, null, null)));
131        return builder.build();
132    }
133
134    private static class JidTypeAdapter extends TypeAdapter<Jid> {
135        @Override
136        public void write(final JsonWriter out, final Jid value) throws IOException {
137            if (value == null) {
138                out.nullValue();
139            } else {
140                out.value(value.toEscapedString());
141            }
142        }
143
144        @Override
145        public Jid read(final JsonReader in) throws IOException {
146            if (in.peek() == JsonToken.NULL) {
147                in.nextNull();
148                return null;
149            } else if (in.peek() == JsonToken.STRING) {
150                final String value = in.nextString();
151                return Jid.ofEscaped(value);
152            }
153            throw new IOException("Unexpected token");
154        }
155    }
156
157    public static Aggregated aggregated(final Collection<Reaction> reactions) {
158        final Map<String, Integer> aggregatedReactions =
159                Maps.transformValues(
160                        Multimaps.index(reactions, Reaction::normalizedReaction).asMap(),
161                        Collection::size);
162        final List<Map.Entry<String, Integer>> sortedList =
163                Ordering.from(
164                                Comparator.comparingInt(
165                                        (Map.Entry<String, Integer> o) -> o.getValue()))
166                        .reverse()
167                        .immutableSortedCopy(aggregatedReactions.entrySet());
168        return new Aggregated(
169                sortedList,
170                ImmutableSet.copyOf(
171                        Collections2.transform(
172                                Collections2.filter(reactions, r -> !r.received),
173                                Reaction::normalizedReaction)));
174    }
175
176    public static final class Aggregated {
177
178        public final List<Map.Entry<String, Integer>> reactions;
179        public final Set<String> ourReactions;
180
181        private Aggregated(
182                final List<Map.Entry<String, Integer>> reactions, Set<String> ourReactions) {
183            this.reactions = reactions;
184            this.ourReactions = ourReactions;
185        }
186    }
187}