Reactions UX polish.

This commit is contained in:
Alex Hart
2020-07-29 13:44:23 -03:00
committed by Greyson Parrelli
parent 0950235ccd
commit 9d3764c5d9
32 changed files with 489 additions and 284 deletions

View File

@@ -2,15 +2,25 @@ package org.thoughtcrime.securesms.reactions;
import androidx.annotation.NonNull;
final class EmojiCount {
private final String baseEmoji;
private final String displayEmoji;
private final int count;
import java.util.List;
EmojiCount(@NonNull String baseEmoji, @NonNull String emoji, int count) {
final class EmojiCount {
static EmojiCount all(@NonNull List<ReactionDetails> reactions) {
return new EmojiCount("", "", reactions);
}
private final String baseEmoji;
private final String displayEmoji;
private final List<ReactionDetails> reactions;
EmojiCount(@NonNull String baseEmoji,
@NonNull String emoji,
@NonNull List<ReactionDetails> reactions)
{
this.baseEmoji = baseEmoji;
this.displayEmoji = emoji;
this.count = count;
this.reactions = reactions;
}
public @NonNull String getBaseEmoji() {
@@ -22,6 +32,10 @@ final class EmojiCount {
}
public int getCount() {
return count;
return reactions.size();
}
public @NonNull List<ReactionDetails> getReactions() {
return reactions;
}
}

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.reactions;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.recipients.Recipient;
class ReactionDetails {
private final Recipient sender;
private final String baseEmoji;
private final String displayEmoji;
private final long timestamp;
ReactionDetails(@NonNull Recipient sender, @NonNull String baseEmoji, @NonNull String displayEmoji, long timestamp) {
this.sender = sender;
this.baseEmoji = baseEmoji;
this.displayEmoji = displayEmoji;
this.timestamp = timestamp;
}
public @NonNull Recipient getSender() {
return sender;
}
public @NonNull String getBaseEmoji() {
return baseEmoji;
}
public @NonNull String getDisplayEmoji() {
return displayEmoji;
}
public long getTimestamp() {
return timestamp;
}
}

View File

@@ -1,130 +0,0 @@
package org.thoughtcrime.securesms.reactions;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.Collections;
import java.util.List;
final class ReactionEmojiCountAdapter extends RecyclerView.Adapter<ReactionEmojiCountAdapter.ViewHolder> {
private List<EmojiCount> emojiCountList = Collections.emptyList();
private int totalCount = 0;
private int selectedPosition = -1;
private final OnEmojiCountSelectedListener onEmojiCountSelectedListener;
ReactionEmojiCountAdapter(@NonNull OnEmojiCountSelectedListener onEmojiCountSelectedListener) {
this.onEmojiCountSelectedListener = onEmojiCountSelectedListener;
}
void updateData(@NonNull List<EmojiCount> newEmojiCount) {
if (selectedPosition != -1 && selectedPosition != 0) {
int emojiPosition = selectedPosition - 1;
EmojiCount oldSelection = emojiCountList.get(emojiPosition);
int newPosition = -1;
for (int i = 0; i < newEmojiCount.size(); i++) {
if (newEmojiCount.get(i).getBaseEmoji().equals(oldSelection.getBaseEmoji())) {
newPosition = i;
break;
}
}
if (newPosition == -1 && !newEmojiCount.isEmpty()) {
selectedPosition = 0;
onEmojiCountSelectedListener.onSelected(null);
} else {
selectedPosition = newPosition + 1;
}
} else if (!newEmojiCount.isEmpty()) {
selectedPosition = 0;
onEmojiCountSelectedListener.onSelected(null);
}
this.emojiCountList = newEmojiCount;
this.totalCount = Stream.of(emojiCountList).reduce(0, (sum, e) -> sum + e.getCount());
notifyDataSetChanged();
}
@Override
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 -> {
if (position != -1 && position != selectedPosition) {
onEmojiCountSelectedListener.onSelected(position == 0 ? null : emojiCountList.get(position - 1).getBaseEmoji());
int oldPosition = selectedPosition;
selectedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(position);
}
});
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (position == 0) {
holder.bind(null, totalCount, selectedPosition == position);
} else {
EmojiCount item = emojiCountList.get(position - 1);
holder.bind(item.getDisplayEmoji(), item.getCount(), selectedPosition == position);
}
}
@Override
public int getItemCount() {
return 1 + emojiCountList.size();
}
static final class ViewHolder extends RecyclerView.ViewHolder {
private final Drawable selectedBackground;
private final EmojiTextView emojiView;
private final TextView countView;
ViewHolder(@NonNull View itemView, @NonNull OnViewHolderClickListener onClickListener) {
super(itemView);
emojiView = itemView.findViewById(R.id.reactions_bottom_view_emoji_item_emoji);
countView = itemView.findViewById(R.id.reactions_bottom_view_emoji_item_text );
selectedBackground = ThemeUtil.getThemedDrawable(itemView.getContext(), R.attr.reactions_bottom_dialog_fragment_emoji_selected);
itemView.setOnClickListener(v -> onClickListener.onClick(getAdapterPosition()));
}
void bind(@Nullable String emoji, int count, boolean selected) {
if (emoji != null) {
emojiView.setVisibility(View.VISIBLE);
emojiView.setText(emoji);
countView.setText(String.valueOf(count));
} else {
emojiView.setVisibility(View.GONE);
countView.setText(itemView.getContext().getString(R.string.ReactionsBottomSheetDialogFragment_all, count));
}
itemView.setBackground(selected ? selectedBackground : null);
}
}
interface OnViewHolderClickListener {
void onClick(int position);
}
interface OnEmojiCountSelectedListener {
void onSelected(@Nullable String emoji);
}
}

View File

@@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.reactions.ReactionsLoader.Reaction;
import org.thoughtcrime.securesms.util.AvatarUtil;
import java.util.Collections;
@@ -19,9 +18,9 @@ import java.util.List;
final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecipientsAdapter.ViewHolder> {
private List<Reaction> data = Collections.emptyList();
private List<ReactionDetails> data = Collections.emptyList();
public void updateData(List<Reaction> newData) {
public void updateData(List<ReactionDetails> newData) {
data = newData;
notifyDataSetChanged();
}
@@ -44,7 +43,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
return data.size();
}
final class ViewHolder extends RecyclerView.ViewHolder {
static final class ViewHolder extends RecyclerView.ViewHolder {
private final AvatarImageView avatar;
private final TextView recipient;
@@ -58,7 +57,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
emoji = itemView.findViewById(R.id.reactions_bottom_view_recipient_emoji);
}
void bind(@NonNull Reaction reaction) {
void bind(@NonNull ReactionDetails reaction) {
this.emoji.setText(reaction.getDisplayEmoji());
if (reaction.getSender().isLocalNumber()) {

View File

@@ -0,0 +1,91 @@
package org.thoughtcrime.securesms.reactions;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.adapter.AlwaysChangedDiffUtil;
import java.util.List;
/**
* ReactionViewPagerAdapter provides pages to a ViewPager2 which contains the reactions on a given message.
*/
class ReactionViewPagerAdapter extends ListAdapter<EmojiCount, ReactionViewPagerAdapter.ViewHolder> {
private int selectedPosition = 0;
protected ReactionViewPagerAdapter() {
super(new AlwaysChangedDiffUtil<>());
}
@NonNull EmojiCount getEmojiCount(int position) {
return getItem(position);
}
void enableNestedScrollingForPosition(int position) {
selectedPosition = position;
notifyItemRangeChanged(0, getItemCount(), new Object());
}
@Override
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.reactions_bottom_sheet_dialog_fragment_recycler, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
holder.setSelected(selectedPosition);
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.onBind(getItem(position));
holder.setSelected(selectedPosition);
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.setNestedScrollingEnabled(false);
ViewGroup.LayoutParams params = recyclerView.getLayoutParams();
params.height = (int) (recyclerView.getResources().getDisplayMetrics().heightPixels * 0.80);
recyclerView.setLayoutParams(params);
recyclerView.setHasFixedSize(true);
}
static class ViewHolder extends RecyclerView.ViewHolder {
private final RecyclerView recycler;
private final ReactionRecipientsAdapter adapter = new ReactionRecipientsAdapter();
public ViewHolder(@NonNull View itemView) {
super(itemView);
recycler = (RecyclerView) itemView;
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
recycler.setLayoutParams(params);
recycler.setAdapter(adapter);
}
public void onBind(@NonNull EmojiCount emojiCount) {
adapter.updateData(emojiCount.getReactions());
}
public void setSelected(int position) {
recycler.setNestedScrollingEnabled(getAdapterPosition() == position);
}
}
}

View File

@@ -1,21 +1,32 @@
package org.thoughtcrime.securesms.reactions;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProviders;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogFragment {
@@ -23,12 +34,11 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
private static final String ARGS_IS_MMS = "reactions.args.is.mms";
private long messageId;
private RecyclerView recipientRecyclerView;
private RecyclerView emojiRecyclerView;
private ViewPager2 recipientPagerView;
private ReactionsLoader reactionsLoader;
private ReactionRecipientsAdapter recipientsAdapter;
private ReactionEmojiCountAdapter emojiCountAdapter;
private ReactionViewPagerAdapter recipientsAdapter;
private ReactionsViewModel viewModel;
private Callback callback;
public static DialogFragment create(long messageId, boolean isMms) {
Bundle args = new Bundle();
@@ -42,13 +52,20 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
return fragment;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
callback = (Callback) context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (ThemeUtil.isDarkTheme(requireContext())) {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed);
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed_ReactWithAny);
} else {
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed);
setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny);
}
super.onCreate(savedInstanceState);
@@ -63,16 +80,56 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
recipientRecyclerView = view.findViewById(R.id.reactions_bottom_view_recipient_recycler);
emojiRecyclerView = view.findViewById(R.id.reactions_bottom_view_emoji_recycler);
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
emojiRecyclerView.setNestedScrollingEnabled(false);
messageId = getArguments().getLong(ARGS_MESSAGE_ID);
if (savedInstanceState == null) {
FrameLayout container = requireDialog().findViewById(R.id.container);
LayoutInflater layoutInflater = LayoutInflater.from(requireContext());
View statusBarShader = layoutInflater.inflate(R.layout.react_with_any_emoji_status_fade, container, false);
TabLayout emojiTabs = (TabLayout) layoutInflater.inflate(R.layout.reactions_bottom_sheet_dialog_fragment_tabs, container, false);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtil.getStatusBarHeight(container));
statusBarShader.setLayoutParams(params);
container.addView(statusBarShader, 0);
container.addView(emojiTabs);
ViewCompat.setOnApplyWindowInsetsListener(container, (v, insets) -> insets.consumeSystemWindowInsets());
new TabLayoutMediator(emojiTabs, recipientPagerView, (tab, position) -> {
tab.setCustomView(R.layout.reactions_bottom_sheet_dialog_fragment_emoji_item);
View customView = Objects.requireNonNull(tab.getCustomView());
EmojiImageView emoji = customView.findViewById(R.id.reactions_bottom_view_emoji_item_emoji);
TextView text = customView.findViewById(R.id.reactions_bottom_view_emoji_item_text);
EmojiCount emojiCount = recipientsAdapter.getEmojiCount(position);
if (position != 0) {
emoji.setVisibility(View.VISIBLE);
emoji.setImageEmoji(emojiCount.getDisplayEmoji());
text.setText(String.valueOf(emojiCount.getCount()));
} else {
emoji.setVisibility(View.GONE);
text.setText(requireContext().getString(R.string.ReactionsBottomSheetDialogFragment_all, emojiCount.getCount()));
}
}).attach();
}
setUpViewModel();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
recipientPagerView = view.findViewById(R.id.reactions_bottom_view_recipient_pager);
messageId = requireArguments().getLong(ARGS_MESSAGE_ID);
setUpRecipientsRecyclerView();
setUpEmojiRecyclerView();
setUpViewModel();
reactionsLoader = new ReactionsLoader(requireContext(),
requireArguments().getLong(ARGS_MESSAGE_ID),
requireArguments().getBoolean(ARGS_IS_MMS));
LoaderManager.getInstance(requireActivity()).initLoader((int) messageId, null, reactionsLoader);
}
@@ -83,34 +140,48 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
super.onDestroyView();
}
private void setUpRecipientsRecyclerView() {
recipientsAdapter = new ReactionRecipientsAdapter();
recipientRecyclerView.setAdapter(recipientsAdapter);
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
callback.onReactionsDialogDismissed();
}
private void setUpEmojiRecyclerView() {
emojiCountAdapter = new ReactionEmojiCountAdapter((emoji -> viewModel.setFilterEmoji(emoji)));
emojiRecyclerView.setAdapter(emojiCountAdapter);
private void setUpRecipientsRecyclerView() {
recipientsAdapter = new ReactionViewPagerAdapter();
recipientPagerView.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
recipientPagerView.post(() -> {
recipientsAdapter.enableNestedScrollingForPosition(position);
});
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager2.SCROLL_STATE_IDLE) {
recipientPagerView.requestLayout();
}
}
});
recipientPagerView.setAdapter(recipientsAdapter);
}
private void setUpViewModel() {
reactionsLoader = new ReactionsLoader(requireContext(),
getArguments().getLong(ARGS_MESSAGE_ID),
getArguments().getBoolean(ARGS_IS_MMS));
ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(reactionsLoader);
viewModel = ViewModelProviders.of(this, factory).get(ReactionsViewModel.class);
viewModel.getRecipients().observe(getViewLifecycleOwner(), reactions -> {
if (reactions.size() == 0) dismiss();
recipientsAdapter.updateData(reactions);
});
viewModel.getEmojiCounts().observe(getViewLifecycleOwner(), emojiCounts -> {
if (emojiCounts.size() == 0) dismiss();
if (emojiCounts.size() <= 1) dismiss();
emojiCountAdapter.updateData(emojiCounts);
recipientsAdapter.submitList(emojiCounts);
});
}
public interface Callback {
void onReactionsDialogDismissed();
}
}

View File

@@ -29,7 +29,7 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
private final boolean isMms;
private final Context appContext;
private MutableLiveData<List<Reaction>> internalLiveData = new MutableLiveData<>();
private MutableLiveData<List<ReactionDetails>> internalLiveData = new MutableLiveData<>();
public ReactionsLoader(@NonNull Context context, long messageId, boolean isMms)
{
@@ -47,6 +47,8 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
SignalExecutors.BOUNDED.execute(() -> {
data.moveToPosition(-1);
MessageRecord record = isMms ? DatabaseFactory.getMmsDatabase(appContext).readerFor(data).getNext()
: DatabaseFactory.getSmsDatabase(appContext).readerFor(data).getNext();
@@ -54,10 +56,10 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
internalLiveData.postValue(Collections.emptyList());
} else {
internalLiveData.postValue(Stream.of(record.getReactions())
.map(reactionRecord -> new Reaction(Recipient.resolved(reactionRecord.getAuthor()),
EmojiUtil.getCanonicalRepresentation(reactionRecord.getEmoji()),
reactionRecord.getEmoji(),
reactionRecord.getDateReceived()))
.map(reactionRecord -> new ReactionDetails(Recipient.resolved(reactionRecord.getAuthor()),
EmojiUtil.getCanonicalRepresentation(reactionRecord.getEmoji()),
reactionRecord.getEmoji(),
reactionRecord.getDateReceived()))
.toList());
}
});
@@ -69,7 +71,7 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
}
@Override
public LiveData<List<Reaction>> getReactions() {
public LiveData<List<ReactionDetails>> getReactions() {
return internalLiveData;
}
@@ -103,33 +105,4 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan
}
}
static class Reaction {
private final Recipient sender;
private final String baseEmoji;
private final String displayEmoji;
private final long timestamp;
private Reaction(@NonNull Recipient sender, @NonNull String baseEmoji, @NonNull String displayEmoji, long timestamp) {
this.sender = sender;
this.baseEmoji = baseEmoji;
this.displayEmoji = displayEmoji;
this.timestamp = timestamp;
}
public @NonNull Recipient getSender() {
return sender;
}
public @NonNull String getBaseEmoji() {
return baseEmoji;
}
public @NonNull String getDisplayEmoji() {
return displayEmoji;
}
public long getTimestamp() {
return timestamp;
}
}
}

View File

@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.reactions;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@@ -12,39 +11,32 @@ import com.annimon.stream.Stream;
import java.util.List;
import java.util.Map;
import static org.thoughtcrime.securesms.reactions.ReactionsLoader.*;
public class ReactionsViewModel extends ViewModel {
private final Repository repository;
private final MutableLiveData<String> filterEmoji = new MutableLiveData<>();
public ReactionsViewModel(@NonNull Repository repository) {
this.repository = repository;
}
public @NonNull LiveData<List<Reaction>> getRecipients() {
return Transformations.switchMap(filterEmoji,
emoji -> Transformations.map(repository.getReactions(),
reactions -> Stream.of(reactions)
.filter(reaction -> emoji == null || reaction.getBaseEmoji().equals(emoji))
.toList()));
}
public @NonNull LiveData<List<EmojiCount>> getEmojiCounts() {
return Transformations.map(repository.getReactions(),
reactionList -> Stream.of(reactionList)
.groupBy(Reaction::getBaseEmoji)
.sorted(this::compareReactions)
.map(entry -> new EmojiCount(entry.getKey(), getCountDisplayEmoji(entry.getValue()), entry.getValue().size()))
.toList());
reactionList -> {
List<EmojiCount> emojiCounts = Stream.of(reactionList)
.groupBy(ReactionDetails::getBaseEmoji)
.sorted(this::compareReactions)
.map(entry -> new EmojiCount(entry.getKey(),
getCountDisplayEmoji(entry.getValue()),
entry.getValue()))
.toList();
emojiCounts.add(0, EmojiCount.all(reactionList));
return emojiCounts;
});
}
public void setFilterEmoji(String filterEmoji) {
this.filterEmoji.setValue(filterEmoji);
}
private int compareReactions(@NonNull Map.Entry<String, List<Reaction>> lhs, @NonNull Map.Entry<String, List<Reaction>> rhs) {
private int compareReactions(@NonNull Map.Entry<String, List<ReactionDetails>> lhs, @NonNull Map.Entry<String, List<ReactionDetails>> rhs) {
int lengthComparison = -Integer.compare(lhs.getValue().size(), rhs.getValue().size());
if (lengthComparison != 0) return lengthComparison;
@@ -54,15 +46,15 @@ public class ReactionsViewModel extends ViewModel {
return -Long.compare(latestTimestampLhs, latestTimestampRhs);
}
private long getLatestTimestamp(List<Reaction> reactions) {
private long getLatestTimestamp(List<ReactionDetails> reactions) {
return Stream.of(reactions)
.max((a, b) -> Long.compare(a.getTimestamp(), b.getTimestamp()))
.map(Reaction::getTimestamp)
.map(ReactionDetails::getTimestamp)
.orElse(-1L);
}
private @NonNull String getCountDisplayEmoji(@NonNull List<Reaction> reactions) {
for (Reaction reaction : reactions) {
private @NonNull String getCountDisplayEmoji(@NonNull List<ReactionDetails> reactions) {
for (ReactionDetails reaction : reactions) {
if (reaction.getSender().isLocalNumber()) {
return reaction.getDisplayEmoji();
}
@@ -72,7 +64,7 @@ public class ReactionsViewModel extends ViewModel {
}
interface Repository {
LiveData<List<Reaction>> getReactions();
LiveData<List<ReactionDetails>> getReactions();
}
static final class Factory implements ViewModelProvider.Factory {

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import java.util.List;
@@ -30,6 +31,7 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
}
void onEmojiSelected(@NonNull String emoji) {
SignalStore.emojiValues().setPreferredVariation(emoji);
repository.addEmojiToMessage(emoji, messageId, isMms);
}