From fa22aa63457c7bad05d47c24e753ae26b5826bf4 Mon Sep 17 00:00:00 2001 From: Sagar Date: Tue, 14 Jan 2025 00:43:22 +0530 Subject: [PATCH] Added "Tap to remove" emoji in ReactionsBottomSheet --- .../reactions/ReactionRecipientsAdapter.java | 25 ++++++++--- .../reactions/ReactionViewPagerAdapter.java | 16 +++++-- .../ReactionsBottomSheetDialogFragment.java | 20 +++++++-- .../reactions/ReactionsRepository.kt | 19 ++++++++ .../reactions/ReactionsViewModel.java | 7 +++ ...m_sheet_dialog_fragment_recipient_item.xml | 45 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 7 files changed, 107 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java index d0ba563a2cd..6f737d8f9ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -20,7 +20,12 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter { - private List data = Collections.emptyList(); + private ReactionViewPagerAdapter.EventListener listener = null; + private List data = Collections.emptyList(); + + void addListener(ReactionViewPagerAdapter.EventListener listener) { + this.listener = listener; + } public void updateData(List newData) { data = newData; @@ -37,7 +42,7 @@ public void updateData(List newData) { @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 @@ static final class ViewHolder extends RecyclerView.ViewHolder { 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 @@ void bind(@NonNull ReactionDetails reaction) { 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); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionViewPagerAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionViewPagerAdapter.java index ea8e62f5a40..e30795f1353 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionViewPagerAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionViewPagerAdapter.java @@ -18,12 +18,17 @@ */ class ReactionViewPagerAdapter extends ListAdapter { - private int selectedPosition = 0; + private int selectedPosition = 0; + private EventListener listener = null; protected ReactionViewPagerAdapter() { super(new AlwaysChangedDiffUtil<>()); } + void addListener(EventListener listener) { + this.listener = listener; + } + @NonNull EmojiCount getEmojiCount(int position) { return getItem(position); } @@ -50,7 +55,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - holder.onBind(getItem(position)); + holder.onBind(getItem(position),listener); holder.setSelected(selectedPosition); } @@ -80,12 +85,17 @@ public ViewHolder(@NonNull View itemView) { recycler.setAdapter(adapter); } - public void onBind(@NonNull EmojiCount emojiCount) { + public void onBind(@NonNull EmojiCount emojiCount, EventListener listener) { adapter.updateData(emojiCount.getReactions()); + adapter.addListener(listener); } public void setSelected(int position) { recycler.setNestedScrollingEnabled(getAdapterPosition() == position); } } + + interface EventListener { + void onClick(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java index 2102391db61..fc945bce8aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java @@ -29,15 +29,17 @@ 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(); @@ -173,6 +175,16 @@ private void setUpViewModel(@NonNull MessageId messageId) { recipientsAdapter.submitList(emojiCounts); })); + + recipientsAdapter.addListener( + () -> disposables.add( + viewModel.removeReactionEmoji(getContext()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + it -> recipientsAdapter.notifyItemRemoved(0) + ) + ) + ); } public interface Callback { diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt index 2ca2f35787f..ad9018929b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsRepository.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.reactions +import android.content.Context +import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.ObservableEmitter import io.reactivex.rxjava3.schedulers.Schedulers @@ -10,6 +12,8 @@ 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 +import java.util.NoSuchElementException class ReactionsRepository { @@ -45,4 +49,19 @@ class ReactionsRepository { ) } } + + fun sendReactionRemoval(context: Context, messageId: MessageId): Completable { + val oldReactionRecord = oldReactionRecord(messageId) ?: return Completable.error(NoSuchElementException("Removing invalid emoji!")) + return Completable.fromAction { + MessageSender.sendReactionRemoval( + context.applicationContext, + MessageId(messageId.id), + oldReactionRecord + ) + }.subscribeOn(Schedulers.io()) + } + + private fun oldReactionRecord(messageId: MessageId): ReactionRecord? { + return SignalDatabase.reactions.getReactions(messageId).firstOrNull { it.author == Recipient.self().id } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java index 249c63251a7..73794f47766 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.reactions; +import android.content.Context; + import androidx.annotation.NonNull; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; @@ -13,6 +15,7 @@ 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 { @@ -70,6 +73,10 @@ private long getLatestTimestamp(List reactions) { return reactions.get(reactions.size() - 1).getDisplayEmoji(); } + Maybe removeReactionEmoji(Context context) { + return repository.sendReactionRemoval(context, messageId).toMaybe(); + } + static final class Factory implements ViewModelProvider.Factory { private final MessageId messageId; diff --git a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_recipient_item.xml b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_recipient_item.xml index 3bf0b92a84f..f624ae21f43 100644 --- a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_recipient_item.xml +++ b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_recipient_item.xml @@ -1,10 +1,10 @@ + android:layout_height="52dp" + tools:viewBindingIgnore="true"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + + All · %1$d + Tap to remove +%1$d