mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-27 13:13:43 +00:00
Add "Tap to remove" option for emoji in ReactionsBottomSheet.
This commit is contained in:
@@ -20,7 +20,12 @@ import java.util.List;
|
||||
|
||||
final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecipientsAdapter.ViewHolder> {
|
||||
|
||||
private List<ReactionDetails> data = Collections.emptyList();
|
||||
private ReactionViewPagerAdapter.EventListener listener = null;
|
||||
private List<ReactionDetails> data = Collections.emptyList();
|
||||
|
||||
void setListener(ReactionViewPagerAdapter.EventListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void updateData(List<ReactionDetails> newData) {
|
||||
data = newData;
|
||||
@@ -37,7 +42,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
holder.bind(data.get(position), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,17 +56,19 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
private final BadgeImageView badge;
|
||||
private final TextView recipient;
|
||||
private final TextView emoji;
|
||||
private final TextView tapToRemoveText;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
avatar = itemView.findViewById(R.id.reactions_bottom_view_recipient_avatar);
|
||||
badge = itemView.findViewById(R.id.reactions_bottom_view_recipient_badge);
|
||||
recipient = itemView.findViewById(R.id.reactions_bottom_view_recipient_name);
|
||||
emoji = itemView.findViewById(R.id.reactions_bottom_view_recipient_emoji);
|
||||
avatar = itemView.findViewById(R.id.reactions_bottom_view_recipient_avatar);
|
||||
badge = itemView.findViewById(R.id.reactions_bottom_view_recipient_badge);
|
||||
recipient = itemView.findViewById(R.id.reactions_bottom_view_recipient_name);
|
||||
emoji = itemView.findViewById(R.id.reactions_bottom_view_recipient_emoji);
|
||||
tapToRemoveText = itemView.findViewById(R.id.reactions_bottom_view_recipient_tap_to_remove_action_text);
|
||||
}
|
||||
|
||||
void bind(@NonNull ReactionDetails reaction) {
|
||||
void bind(@NonNull ReactionDetails reaction, ReactionViewPagerAdapter.EventListener listener) {
|
||||
this.emoji.setText(reaction.getDisplayEmoji());
|
||||
|
||||
if (reaction.getSender().isSelf()) {
|
||||
@@ -69,10 +76,14 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter<ReactionRecip
|
||||
this.avatar.setAvatar(Glide.with(avatar), null, false);
|
||||
this.badge.setBadge(null);
|
||||
AvatarUtil.loadIconIntoImageView(reaction.getSender(), avatar);
|
||||
itemView.setOnClickListener((view) -> listener.onClick());
|
||||
tapToRemoveText.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
this.recipient.setText(reaction.getSender().getDisplayName(itemView.getContext()));
|
||||
this.avatar.setAvatar(Glide.with(avatar), reaction.getSender(), false);
|
||||
this.badge.setBadgeFromRecipient(reaction.getSender());
|
||||
itemView.setOnClickListener(null);
|
||||
tapToRemoveText.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,12 @@ import java.util.List;
|
||||
*/
|
||||
class ReactionViewPagerAdapter extends ListAdapter<EmojiCount, ReactionViewPagerAdapter.ViewHolder> {
|
||||
|
||||
private int selectedPosition = 0;
|
||||
private int selectedPosition = 0;
|
||||
private final EventListener listener;
|
||||
|
||||
protected ReactionViewPagerAdapter() {
|
||||
protected ReactionViewPagerAdapter(@NonNull EventListener listener) {
|
||||
super(new AlwaysChangedDiffUtil<>());
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull EmojiCount getEmojiCount(int position) {
|
||||
@@ -50,7 +52,7 @@ class ReactionViewPagerAdapter extends ListAdapter<EmojiCount, ReactionViewPager
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.onBind(getItem(position));
|
||||
holder.onBind(getItem(position),listener);
|
||||
holder.setSelected(selectedPosition);
|
||||
}
|
||||
|
||||
@@ -80,12 +82,17 @@ class ReactionViewPagerAdapter extends ListAdapter<EmojiCount, ReactionViewPager
|
||||
recycler.setAdapter(adapter);
|
||||
}
|
||||
|
||||
public void onBind(@NonNull EmojiCount emojiCount) {
|
||||
public void onBind(@NonNull EmojiCount emojiCount, EventListener listener) {
|
||||
adapter.updateData(emojiCount.getReactions());
|
||||
adapter.setListener(listener);
|
||||
}
|
||||
|
||||
public void setSelected(int position) {
|
||||
recycler.setNestedScrollingEnabled(getAdapterPosition() == position);
|
||||
}
|
||||
}
|
||||
|
||||
interface EventListener {
|
||||
void onClick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,17 @@ import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogFragment {
|
||||
|
||||
private static final String ARGS_MESSAGE_ID = "reactions.args.message.id";
|
||||
private static final String ARGS_IS_MMS = "reactions.args.is.mms";
|
||||
|
||||
private ViewPager2 recipientPagerView;
|
||||
private ReactionViewPagerAdapter recipientsAdapter;
|
||||
private ReactionsViewModel viewModel;
|
||||
private Callback callback;
|
||||
private ViewPager2 recipientPagerView;
|
||||
private ReactionViewPagerAdapter recipientsAdapter;
|
||||
private ReactionsViewModel viewModel;
|
||||
private Callback callback;
|
||||
|
||||
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
||||
|
||||
@@ -97,11 +99,11 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
|
||||
disposables.bindTo(getViewLifecycleOwner());
|
||||
|
||||
setUpRecipientsRecyclerView();
|
||||
setUpTabMediator(view, savedInstanceState);
|
||||
|
||||
MessageId messageId = new MessageId(requireArguments().getLong(ARGS_MESSAGE_ID));
|
||||
setUpViewModel(messageId);
|
||||
|
||||
setUpRecipientsRecyclerView();
|
||||
setUpTabMediator(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,14 +144,12 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
}
|
||||
|
||||
private void setUpRecipientsRecyclerView() {
|
||||
recipientsAdapter = new ReactionViewPagerAdapter();
|
||||
recipientsAdapter = new ReactionViewPagerAdapter(() -> viewModel.removeReactionEmoji());
|
||||
|
||||
recipientPagerView.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
recipientPagerView.post(() -> {
|
||||
recipientsAdapter.enableNestedScrollingForPosition(position);
|
||||
});
|
||||
recipientPagerView.post(() -> recipientsAdapter.enableNestedScrollingForPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +164,7 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
|
||||
}
|
||||
|
||||
private void setUpViewModel(@NonNull MessageId messageId) {
|
||||
ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(messageId);
|
||||
ReactionsViewModel.Factory factory = new ReactionsViewModel.Factory(new ReactionsRepository(), messageId);
|
||||
|
||||
viewModel = new ViewModelProvider(this, factory).get(ReactionsViewModel.class);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.reactions
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiUtil
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
@@ -10,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
|
||||
class ReactionsRepository {
|
||||
|
||||
@@ -45,4 +47,15 @@ class ReactionsRepository {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendReactionRemoval(messageId: MessageId) {
|
||||
val oldReactionRecord = SignalDatabase.reactions.getReactions(messageId).firstOrNull { it.author == Recipient.self().id } ?: return
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
MessageSender.sendReactionRemoval(
|
||||
AppDependencies.application.applicationContext,
|
||||
MessageId(messageId.id),
|
||||
oldReactionRecord
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Maybe;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
|
||||
public class ReactionsViewModel extends ViewModel {
|
||||
@@ -20,9 +21,9 @@ public class ReactionsViewModel extends ViewModel {
|
||||
private final MessageId messageId;
|
||||
private final ReactionsRepository repository;
|
||||
|
||||
public ReactionsViewModel(@NonNull MessageId messageId) {
|
||||
public ReactionsViewModel(@NonNull ReactionsRepository reactionRepository, @NonNull MessageId messageId) {
|
||||
this.messageId = messageId;
|
||||
this.repository = new ReactionsRepository();
|
||||
this.repository = reactionRepository;
|
||||
}
|
||||
|
||||
public @NonNull Observable<List<EmojiCount>> getEmojiCounts() {
|
||||
@@ -70,17 +71,23 @@ public class ReactionsViewModel extends ViewModel {
|
||||
return reactions.get(reactions.size() - 1).getDisplayEmoji();
|
||||
}
|
||||
|
||||
void removeReactionEmoji() {
|
||||
repository.sendReactionRemoval(messageId);
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final ReactionsRepository reactionsRepository;
|
||||
private final MessageId messageId;
|
||||
|
||||
Factory(@NonNull MessageId messageId) {
|
||||
this.messageId = messageId;
|
||||
Factory(@NonNull ReactionsRepository reactionsRepository, @NonNull MessageId messageId) {
|
||||
this.reactionsRepository = reactionsRepository;
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return modelClass.cast(new ReactionsViewModel(messageId));
|
||||
return modelClass.cast(new ReactionsViewModel(reactionsRepository, messageId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
|
||||
@@ -34,8 +35,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPageCategoryIconMappingModel;
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoriesAdapter;
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsRepository;
|
||||
import org.thoughtcrime.securesms.reactions.edit.EditReactionsActivity;
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel;
|
||||
@@ -48,7 +49,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
|
||||
EmojiPageViewGridAdapter.VariationSelectorListener
|
||||
{
|
||||
|
||||
public static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
|
||||
public static final String REACTION_STORAGE_KEY = "reactions_recent_emoji";
|
||||
private static final String ABOUT_STORAGE_KEY = TextSecurePreferences.RECENT_STORAGE_KEY;
|
||||
|
||||
private static final String ARG_MESSAGE_ID = "arg_message_id";
|
||||
@@ -126,7 +127,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
|
||||
|
||||
public static ReactWithAnyEmojiBottomSheetDialogFragment createForCallingReactions() {
|
||||
ReactWithAnyEmojiBottomSheetDialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putLong(ARG_MESSAGE_ID, -1);
|
||||
args.putBoolean(ARG_IS_MMS, false);
|
||||
@@ -254,9 +255,10 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
Bundle args = requireArguments();
|
||||
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY, REACTION_STORAGE_KEY));
|
||||
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
|
||||
Bundle args = requireArguments();
|
||||
ReactionsRepository reactionsRepository = new ReactionsRepository();
|
||||
ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext(), args.getString(ARG_RECENT_KEY, REACTION_STORAGE_KEY));
|
||||
ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(reactionsRepository, repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS));
|
||||
|
||||
viewModel = new ViewModelProvider(this, factory).get(ReactWithAnyEmojiViewModel.class);
|
||||
}
|
||||
@@ -273,7 +275,7 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVariationSelectorStateChanged(boolean open) { }
|
||||
public void onVariationSelectorStateChanged(boolean open) {}
|
||||
|
||||
public interface Callback {
|
||||
void onReactWithAnyEmojiDialogDismissed();
|
||||
@@ -336,9 +338,9 @@ public final class ReactWithAnyEmojiBottomSheetDialogFragment extends FixedRound
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClicked() { }
|
||||
public void onClicked() {}
|
||||
|
||||
@Override
|
||||
public void onFocusLost() { }
|
||||
public void onFocusLost() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
|
||||
private final BehaviorSubject<EmojiSearchResult> searchResults;
|
||||
private final BehaviorSubject<String> selectedKey;
|
||||
|
||||
private ReactWithAnyEmojiViewModel(@NonNull ReactWithAnyEmojiRepository repository,
|
||||
long messageId,
|
||||
boolean isMms,
|
||||
@NonNull EmojiSearchRepository emojiSearchRepository)
|
||||
private ReactWithAnyEmojiViewModel(
|
||||
@NonNull ReactionsRepository reactionsRepository,
|
||||
@NonNull ReactWithAnyEmojiRepository repository,
|
||||
long messageId,
|
||||
boolean isMms,
|
||||
@NonNull EmojiSearchRepository emojiSearchRepository)
|
||||
{
|
||||
this.repository = repository;
|
||||
this.messageId = messageId;
|
||||
@@ -54,8 +56,8 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
|
||||
this.searchResults = BehaviorSubject.createDefault(new EmojiSearchResult());
|
||||
this.selectedKey = BehaviorSubject.createDefault(getStartingKey());
|
||||
|
||||
Observable<List<ReactWithAnyEmojiPage>> emojiPages = new ReactionsRepository().getReactions(new MessageId(messageId))
|
||||
.map(repository::getEmojiPageModels);
|
||||
Observable<List<ReactWithAnyEmojiPage>> emojiPages = reactionsRepository.getReactions(new MessageId(messageId))
|
||||
.map(repository::getEmojiPageModels);
|
||||
|
||||
Observable<MappingModelList> emojiList = emojiPages.map(pages -> {
|
||||
MappingModelList list = new MappingModelList();
|
||||
@@ -154,20 +156,22 @@ public final class ReactWithAnyEmojiViewModel extends ViewModel {
|
||||
|
||||
static class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final ReactionsRepository reactionsRepository;
|
||||
private final ReactWithAnyEmojiRepository repository;
|
||||
private final long messageId;
|
||||
private final boolean isMms;
|
||||
|
||||
Factory(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
|
||||
this.repository = repository;
|
||||
this.messageId = messageId;
|
||||
this.isMms = isMms;
|
||||
Factory(@NonNull ReactionsRepository reactionsRepository, @NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) {
|
||||
this.reactionsRepository = reactionsRepository;
|
||||
this.repository = repository;
|
||||
this.messageId = messageId;
|
||||
this.isMms = isMms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection ConstantConditions
|
||||
return modelClass.cast(new ReactWithAnyEmojiViewModel(repository, messageId, isMms, new EmojiSearchRepository(AppDependencies.getApplication())));
|
||||
return modelClass.cast(new ReactWithAnyEmojiViewModel(reactionsRepository, repository, messageId, isMms, new EmojiSearchRepository(AppDependencies.getApplication())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user