Group together skin tone variations of the same reaction.

This commit is contained in:
Greyson Parrelli 2020-07-19 12:31:20 -04:00
parent 6e75d42a92
commit 9a566e5559
6 changed files with 66 additions and 35 deletions

View File

@ -3,16 +3,22 @@ package org.thoughtcrime.securesms.reactions;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
final class EmojiCount { final class EmojiCount {
private final String emoji; private final String baseEmoji;
private final int count; private final String displayEmoji;
private final int count;
EmojiCount(@NonNull String emoji, int count) { EmojiCount(@NonNull String baseEmoji, @NonNull String emoji, int count) {
this.emoji = emoji; this.baseEmoji = baseEmoji;
this.count = count; this.displayEmoji = emoji;
this.count = count;
} }
public @NonNull String getEmoji() { public @NonNull String getBaseEmoji() {
return emoji; return baseEmoji;
}
public @NonNull String getDisplayEmoji() {
return displayEmoji;
} }
public int getCount() { public int getCount() {

View File

@ -38,7 +38,7 @@ final class ReactionEmojiCountAdapter extends RecyclerView.Adapter<ReactionEmoji
int newPosition = -1; int newPosition = -1;
for (int i = 0; i < newEmojiCount.size(); i++) { for (int i = 0; i < newEmojiCount.size(); i++) {
if (newEmojiCount.get(i).getEmoji().equals(oldSelection.getEmoji())) { if (newEmojiCount.get(i).getBaseEmoji().equals(oldSelection.getBaseEmoji())) {
newPosition = i; newPosition = i;
break; break;
} }
@ -66,7 +66,7 @@ final class ReactionEmojiCountAdapter extends RecyclerView.Adapter<ReactionEmoji
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.reactions_bottom_sheet_dialog_fragment_emoji_item, parent, false), position -> { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.reactions_bottom_sheet_dialog_fragment_emoji_item, parent, false), position -> {
if (position != -1 && position != selectedPosition) { if (position != -1 && position != selectedPosition) {
onEmojiCountSelectedListener.onSelected(position == 0 ? null : emojiCountList.get(position - 1).getEmoji()); onEmojiCountSelectedListener.onSelected(position == 0 ? null : emojiCountList.get(position - 1).getBaseEmoji());
int oldPosition = selectedPosition; int oldPosition = selectedPosition;
selectedPosition = position; selectedPosition = position;
@ -83,7 +83,7 @@ final class ReactionEmojiCountAdapter extends RecyclerView.Adapter<ReactionEmoji
holder.bind(null, totalCount, selectedPosition == position); holder.bind(null, totalCount, selectedPosition == position);
} else { } else {
EmojiCount item = emojiCountList.get(position - 1); EmojiCount item = emojiCountList.get(position - 1);
holder.bind(item.getEmoji(), item.getCount(), selectedPosition == position); holder.bind(item.getDisplayEmoji(), item.getCount(), selectedPosition == position);
} }
} }

View File

@ -59,7 +59,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
} }
void bind(@NonNull Reaction reaction) { void bind(@NonNull Reaction reaction) {
this.emoji.setText(reaction.getEmoji()); this.emoji.setText(reaction.getDisplayEmoji());
if (reaction.getSender().isLocalNumber()) { if (reaction.getSender().isLocalNumber()) {
this.recipient.setText(R.string.ReactionsRecipientAdapter_you); this.recipient.setText(R.string.ReactionsRecipientAdapter_you);

View File

@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.model.ReactionRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@ -107,15 +108,16 @@ public class ReactionsConversationView extends LinearLayout {
RecipientId selfId = Recipient.self().getId(); RecipientId selfId = Recipient.self().getId();
for (ReactionRecord record : records) { for (ReactionRecord record : records) {
Reaction info = counters.get(record.getEmoji()); String baseEmoji = EmojiUtil.getCanonicalRepresentation(record.getEmoji());
Reaction info = counters.get(baseEmoji);
if (info == null) { if (info == null) {
info = new Reaction(record.getEmoji(), 1, record.getDateReceived(), selfId.equals(record.getAuthor())); info = new Reaction(baseEmoji, record.getEmoji(), 1, record.getDateReceived(), selfId.equals(record.getAuthor()));
} else { } else {
info.update(record.getDateReceived(), selfId.equals(record.getAuthor())); info.update(record.getEmoji(), record.getDateReceived(), selfId.equals(record.getAuthor()));
} }
counters.put(record.getEmoji(), info); counters.put(baseEmoji, info);
} }
List<Reaction> reactions = new ArrayList<>(counters.values()); List<Reaction> reactions = new ArrayList<>(counters.values());
@ -126,7 +128,7 @@ public class ReactionsConversationView extends LinearLayout {
List<Reaction> shortened = new ArrayList<>(3); List<Reaction> shortened = new ArrayList<>(3);
shortened.add(reactions.get(0)); shortened.add(reactions.get(0));
shortened.add(reactions.get(1)); shortened.add(reactions.get(1));
shortened.add(Stream.of(reactions).skip(2).reduce(new Reaction(null, 0, 0, false), Reaction::merge)); shortened.add(Stream.of(reactions).skip(2).reduce(new Reaction(null, null, 0, 0, false), Reaction::merge));
return shortened; return shortened;
} else { } else {
@ -140,8 +142,8 @@ public class ReactionsConversationView extends LinearLayout {
TextView countView = root.findViewById(R.id.reactions_pill_count); TextView countView = root.findViewById(R.id.reactions_pill_count);
View spacer = root.findViewById(R.id.reactions_pill_spacer); View spacer = root.findViewById(R.id.reactions_pill_spacer);
if (reaction.emoji != null) { if (reaction.displayEmoji != null) {
emojiView.setText(reaction.emoji); emojiView.setText(reaction.displayEmoji);
if (reaction.count > 1) { if (reaction.count > 1) {
countView.setText(String.valueOf(reaction.count)); countView.setText(String.valueOf(reaction.count));
@ -166,19 +168,27 @@ public class ReactionsConversationView extends LinearLayout {
} }
private static class Reaction implements Comparable<Reaction> { private static class Reaction implements Comparable<Reaction> {
private String emoji; private String baseEmoji;
private String displayEmoji;
private int count; private int count;
private long lastSeen; private long lastSeen;
private boolean userWasSender; private boolean userWasSender;
Reaction(@Nullable String emoji, int count, long lastSeen, boolean userWasSender) { Reaction(@Nullable String baseEmoji, @Nullable String displayEmoji, int count, long lastSeen, boolean userWasSender) {
this.emoji = emoji; this.baseEmoji = baseEmoji;
this.displayEmoji = displayEmoji;
this.count = count; this.count = count;
this.lastSeen = lastSeen; this.lastSeen = lastSeen;
this.userWasSender = userWasSender; this.userWasSender = userWasSender;
} }
void update(long lastSeen, boolean userWasSender) { void update(@NonNull String displayEmoji, long lastSeen, boolean userWasSender) {
if (!this.userWasSender) {
if (userWasSender || lastSeen > this.lastSeen) {
this.displayEmoji = displayEmoji;
}
}
this.count = this.count + 1; this.count = this.count + 1;
this.lastSeen = Math.max(this.lastSeen, lastSeen); this.lastSeen = Math.max(this.lastSeen, lastSeen);
this.userWasSender = this.userWasSender || userWasSender; this.userWasSender = this.userWasSender || userWasSender;

View File

@ -6,18 +6,16 @@ import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AbstractCursorLoader; import org.thoughtcrime.securesms.util.AbstractCursorLoader;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
@ -57,6 +55,7 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
} else { } else {
internalLiveData.postValue(Stream.of(record.getReactions()) internalLiveData.postValue(Stream.of(record.getReactions())
.map(reactionRecord -> new Reaction(Recipient.resolved(reactionRecord.getAuthor()), .map(reactionRecord -> new Reaction(Recipient.resolved(reactionRecord.getAuthor()),
EmojiUtil.getCanonicalRepresentation(reactionRecord.getEmoji()),
reactionRecord.getEmoji(), reactionRecord.getEmoji(),
reactionRecord.getDateReceived())) reactionRecord.getDateReceived()))
.toList()); .toList());
@ -106,21 +105,27 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
static class Reaction { static class Reaction {
private final Recipient sender; private final Recipient sender;
private final String emoji; private final String baseEmoji;
private final String displayEmoji;
private final long timestamp; private final long timestamp;
private Reaction(@NonNull Recipient sender, @NonNull String emoji, long timestamp) { private Reaction(@NonNull Recipient sender, @NonNull String baseEmoji, @NonNull String displayEmoji, long timestamp) {
this.sender = sender; this.sender = sender;
this.emoji = emoji; this.baseEmoji = baseEmoji;
this.timestamp = timestamp; this.displayEmoji = displayEmoji;
this.timestamp = timestamp;
} }
public @NonNull Recipient getSender() { public @NonNull Recipient getSender() {
return sender; return sender;
} }
public @NonNull String getEmoji() { public @NonNull String getBaseEmoji() {
return emoji; return baseEmoji;
}
public @NonNull String getDisplayEmoji() {
return displayEmoji;
} }
public long getTimestamp() { public long getTimestamp() {

View File

@ -27,16 +27,16 @@ public class ReactionsViewModel extends ViewModel {
return Transformations.switchMap(filterEmoji, return Transformations.switchMap(filterEmoji,
emoji -> Transformations.map(repository.getReactions(), emoji -> Transformations.map(repository.getReactions(),
reactions -> Stream.of(reactions) reactions -> Stream.of(reactions)
.filter(reaction -> emoji == null || reaction.getEmoji().equals(emoji)) .filter(reaction -> emoji == null || reaction.getBaseEmoji().equals(emoji))
.toList())); .toList()));
} }
public @NonNull LiveData<List<EmojiCount>> getEmojiCounts() { public @NonNull LiveData<List<EmojiCount>> getEmojiCounts() {
return Transformations.map(repository.getReactions(), return Transformations.map(repository.getReactions(),
reactionList -> Stream.of(reactionList) reactionList -> Stream.of(reactionList)
.groupBy(Reaction::getEmoji) .groupBy(Reaction::getBaseEmoji)
.sorted(this::compareReactions) .sorted(this::compareReactions)
.map(entry -> new EmojiCount(entry.getKey(), entry.getValue().size())) .map(entry -> new EmojiCount(entry.getKey(), getCountDisplayEmoji(entry.getValue()), entry.getValue().size()))
.toList()); .toList());
} }
@ -61,6 +61,16 @@ public class ReactionsViewModel extends ViewModel {
.orElse(-1L); .orElse(-1L);
} }
private @NonNull String getCountDisplayEmoji(@NonNull List<Reaction> reactions) {
for (Reaction reaction : reactions) {
if (reaction.getSender().isLocalNumber()) {
return reaction.getDisplayEmoji();
}
}
return reactions.get(reactions.size() - 1).getDisplayEmoji();
}
interface Repository { interface Repository {
LiveData<List<Reaction>> getReactions(); LiveData<List<Reaction>> getReactions();
} }