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