Reaction.java

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