Fix multiple chatcolors issues from beta feedback.

- Fix issue where custom color would come out as black
- Completely remove mask view in favour of using the item decoration.
- Fix issue where video gifs wouldn't "cut through" bubble.
- Fix issue where multiselect shade would only appear if bottom or top item was not visible
fork-5.53.8
Alex Hart 2021-09-29 13:38:34 -03:00 zatwierdzone przez Cody Henthorne
rodzic 705839068a
commit 7e91132e7e
15 zmienionych plików z 153 dodań i 337 usunięć

Wyświetl plik

@ -66,7 +66,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onAddToContactsClicked(@NonNull Contact contact); void onAddToContactsClicked(@NonNull Contact contact);
void onMessageSharedContactClicked(@NonNull List<Recipient> choices); void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
void onInviteSharedContactClicked(@NonNull List<Recipient> choices); void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms); void onReactionClicked(@NonNull MultiselectPart multiselectPart, long messageId, boolean isMms);
void onGroupMemberClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId); void onGroupMemberClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
void onMessageWithRecaptchaNeededClicked(@NonNull MessageRecord messageRecord); void onMessageWithRecaptchaNeededClicked(@NonNull MessageRecord messageRecord);

Wyświetl plik

@ -1,152 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MaskView extends View {
private MaskTarget maskTarget;
private ViewGroup activityContentView;
private Paint maskPaint;
private Rect drawingRect = new Rect();
private float targetParentTranslationY;
private final ViewTreeObserver.OnDrawListener onDrawListener = this::invalidate;
public MaskView(@NonNull Context context) {
super(context);
}
public MaskView(@NonNull Context context, @Nullable AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
setLayerType(LAYER_TYPE_HARDWARE, maskPaint);
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
activityContentView = getRootView().findViewById(android.R.id.content);
}
public void setTarget(@Nullable MaskTarget maskTarget) {
if (this.maskTarget != null) {
removeOnDrawListener(this.maskTarget, onDrawListener);
}
this.maskTarget = maskTarget;
if (this.maskTarget != null) {
addOnDrawListener(maskTarget, onDrawListener);
}
invalidate();
}
public void setTargetParentTranslationY(float targetParentTranslationY) {
this.targetParentTranslationY = targetParentTranslationY;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
if (nothingToMask(maskTarget)) {
return;
}
maskTarget.getPrimaryTarget().getDrawingRect(drawingRect);
activityContentView.offsetDescendantRectToMyCoords(maskTarget.getPrimaryTarget(), drawingRect);
drawingRect.top += targetParentTranslationY;
drawingRect.bottom += targetParentTranslationY;
Bitmap mask = Bitmap.createBitmap(maskTarget.getPrimaryTarget().getWidth(), drawingRect.height(), Bitmap.Config.ARGB_8888);
Canvas maskCanvas = new Canvas(mask);
maskTarget.draw(maskCanvas);
canvas.clipRect(drawingRect.left, Math.max(drawingRect.top, getTop() + getPaddingTop()), drawingRect.right, Math.min(drawingRect.bottom, getBottom() - getPaddingBottom()));
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) maskTarget.getPrimaryTarget().getLayoutParams();
canvas.drawBitmap(mask, params.leftMargin, drawingRect.top, maskPaint);
mask.recycle();
}
private static void removeOnDrawListener(@NonNull MaskTarget maskTarget, @NonNull ViewTreeObserver.OnDrawListener onDrawListener) {
for (View view : maskTarget.getAllTargets()) {
if (view != null) {
view.getViewTreeObserver().removeOnDrawListener(onDrawListener);
}
}
}
private static void addOnDrawListener(@NonNull MaskTarget maskTarget, @NonNull ViewTreeObserver.OnDrawListener onDrawListener) {
for (View view : maskTarget.getAllTargets()) {
if (view != null) {
view.getViewTreeObserver().addOnDrawListener(onDrawListener);
}
}
}
private static boolean nothingToMask(@Nullable MaskTarget maskTarget) {
if (maskTarget == null) {
return true;
}
for (View view : maskTarget.getAllTargets()) {
if (view == null || !view.isAttachedToWindow()) {
return true;
}
}
return false;
}
public static class MaskTarget {
private final View primaryTarget;
public MaskTarget(@NonNull View primaryTarget) {
this.primaryTarget = primaryTarget;
}
final @NonNull View getPrimaryTarget() {
return primaryTarget;
}
protected @NonNull List<View> getAllTargets() {
return Collections.singletonList(primaryTarget);
}
protected void draw(@NonNull Canvas canvas) {
primaryTarget.draw(canvas);
}
}
}

Wyświetl plik

@ -115,7 +115,6 @@ import org.thoughtcrime.securesms.components.HidingLinearLayout;
import org.thoughtcrime.securesms.components.InputAwareLayout; import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.InputPanel; import org.thoughtcrime.securesms.components.InputPanel;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.components.SendButton; import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
@ -158,8 +157,6 @@ import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft; import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.MentionUtil; import org.thoughtcrime.securesms.database.MentionUtil;
import org.thoughtcrime.securesms.database.MentionUtil.UpdatedBodyAndMentions; import org.thoughtcrime.securesms.database.MentionUtil.UpdatedBodyAndMentions;
@ -168,6 +165,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList; import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
@ -2430,11 +2428,12 @@ public class ConversationActivity extends PassphraseRequiredActivity
@Override @Override
public void onReactWithAnyEmojiDialogDismissed() { public void onReactWithAnyEmojiDialogDismissed() {
reactionDelegate.hideMask(); reactionDelegate.hide();
} }
@Override @Override
public void onReactWithAnyEmojiSelected(@NonNull String emoji) { public void onReactWithAnyEmojiSelected(@NonNull String emoji) {
reactionDelegate.hide();
} }
@Override @Override
@ -3334,7 +3333,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
@Override @Override
public void onReactionsDialogDismissed() { public void onReactionsDialogDismissed() {
reactionDelegate.hideMask(); fragment.clearFocusedItem();
} }
@Override @Override
@ -3669,19 +3668,13 @@ public class ConversationActivity extends PassphraseRequiredActivity
} }
@Override @Override
public void handleReaction(@NonNull MaskView.MaskTarget maskTarget, public void handleReaction(@NonNull ConversationMessage conversationMessage,
@NonNull ConversationMessage conversationMessage,
@NonNull Toolbar.OnMenuItemClickListener toolbarListener, @NonNull Toolbar.OnMenuItemClickListener toolbarListener,
@NonNull ConversationReactionOverlay.OnHideListener onHideListener) @NonNull ConversationReactionOverlay.OnHideListener onHideListener)
{ {
reactionDelegate.setOnToolbarItemClickedListener(toolbarListener); reactionDelegate.setOnToolbarItemClickedListener(toolbarListener);
reactionDelegate.setOnHideListener(onHideListener); reactionDelegate.setOnHideListener(onHideListener);
reactionDelegate.show(this, maskTarget, recipient.get(), conversationMessage, inputAreaHeight(), groupViewModel.isNonAdminInAnnouncementGroup()); reactionDelegate.show(this, recipient.get(), conversationMessage, groupViewModel.isNonAdminInAnnouncementGroup());
}
@Override
public void onListVerticalTranslationChanged(float translationY) {
reactionDelegate.setListVerticalTranslation(translationY);
} }
@Override @Override
@ -3699,11 +3692,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
} }
} }
@Override
public void handleReactionDetails(@NonNull MaskView.MaskTarget maskTarget) {
reactionDelegate.showMask(maskTarget, titleView.getMeasuredHeight(), inputAreaHeight());
}
@Override @Override
public void onVoiceNotePause(@NonNull Uri uri) { public void onVoiceNotePause(@NonNull Uri uri) {
voiceNoteMediaController.pausePlayback(uri); voiceNoteMediaController.pausePlayback(uri);

Wyświetl plik

@ -75,7 +75,6 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.VerifyIdentityActivity; import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.components.ConversationScrollToView; import org.thoughtcrime.securesms.components.ConversationScrollToView;
import org.thoughtcrime.securesms.components.ConversationTypingView; import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager; import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
@ -224,6 +223,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler; private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler;
private Colorizer colorizer; private Colorizer colorizer;
private ConversationUpdateTick conversationUpdateTick; private ConversationUpdateTick conversationUpdateTick;
private MultiselectItemDecoration multiselectItemDecoration;
public static void prepare(@NonNull Context context) { public static void prepare(@NonNull Context context) {
FrameLayout parent = new FrameLayout(context); FrameLayout parent = new FrameLayout(context);
@ -275,22 +275,21 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
return adapter.getSelectedItems().contains(multiselectPart); return adapter.getSelectedItems().contains(multiselectPart);
} }
}); });
MultiselectItemDecoration multiselectItemDecoration = new MultiselectItemDecoration(requireContext(), multiselectItemDecoration = new MultiselectItemDecoration(requireContext(),
() -> conversationViewModel.getWallpaper().getValue(), () -> conversationViewModel.getWallpaper().getValue(),
multiselectItemAnimator::getSelectedProgressForPart, multiselectItemAnimator::getSelectedProgressForPart,
multiselectItemAnimator::isInitialAnimation); multiselectItemAnimator::isInitialAnimation);
list.setHasFixedSize(false); list.setHasFixedSize(false);
list.setLayoutManager(layoutManager); list.setLayoutManager(layoutManager);
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
list.addItemDecoration(multiselectItemDecoration); list.addItemDecoration(multiselectItemDecoration);
list.setItemAnimator(multiselectItemAnimator); list.setItemAnimator(multiselectItemAnimator);
getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration); getViewLifecycleOwner().getLifecycle().addObserver(multiselectItemDecoration);
if (Build.VERSION.SDK_INT >= 31) {
list.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
snapToTopDataObserver = new ConversationSnapToTopDataObserver(list, new ConversationScrollRequestValidator()); snapToTopDataObserver = new ConversationSnapToTopDataObserver(list, new ConversationScrollRequestValidator());
conversationBanner = (ConversationBannerView) inflater.inflate(R.layout.conversation_item_banner, container, false); conversationBanner = (ConversationBannerView) inflater.inflate(R.layout.conversation_item_banner, container, false);
topLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false); topLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false);
@ -319,6 +318,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class); this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class);
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class); this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> { conversationViewModel.getMessages().observe(getViewLifecycleOwner(), messages -> {
ConversationAdapter adapter = getListAdapter(); ConversationAdapter adapter = getListAdapter();
if (adapter != null) { if (adapter != null) {
@ -350,9 +350,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
updateToolbarDependentMargins(); updateToolbarDependentMargins();
colorizer = new Colorizer(); colorizer = new Colorizer();
RecyclerViewColorizer recyclerViewColorizer = new RecyclerViewColorizer(list);
conversationViewModel.getChatColors().observe(getViewLifecycleOwner(), recyclerViewColorizer::setChatColors);
conversationViewModel.getNameColorsMap().observe(getViewLifecycleOwner(), nameColorsMap -> { conversationViewModel.getNameColorsMap().observe(getViewLifecycleOwner(), nameColorsMap -> {
colorizer.onNameColorsChanged(nameColorsMap); colorizer.onNameColorsChanged(nameColorsMap);
@ -387,11 +384,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
return callback; return callback;
} }
private @NonNull MaskView.MaskTarget getMaskTarget(@NonNull View itemView) { public void clearFocusedItem() {
int adapterPosition = list.getChildAdapterPosition(itemView); multiselectItemDecoration.setFocusedItem(null);
View videoPlayer = giphyMp4ProjectionRecycler.getVideoPlayerAtAdapterPosition(adapterPosition); list.invalidateItemDecorations();
return new ConversationItemMaskTarget((ConversationItem) itemView, videoPlayer);
} }
private void setupListLayoutListeners() { private void setupListLayoutListeners() {
@ -419,9 +414,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
list.setTranslationY(Math.min(0, -chTop)); list.setTranslationY(Math.min(0, -chTop));
list.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER); list.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);
} }
int offset = WindowUtil.isStatusBarPresent(requireActivity().getWindow()) ? ViewUtil.getStatusBarHeight(list) : 0;
listener.onListVerticalTranslationChanged(list.getTranslationY() - offset);
} }
private void updateConversationItemTimestamps() { private void updateConversationItemTimestamps() {
@ -1285,14 +1277,11 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
void onMessageActionToolbarOpened(); void onMessageActionToolbarOpened();
void onForwardClicked(); void onForwardClicked();
void onMessageRequest(@NonNull MessageRequestViewModel viewModel); void onMessageRequest(@NonNull MessageRequestViewModel viewModel);
void handleReaction(@NonNull MaskView.MaskTarget maskTarget, void handleReaction(@NonNull ConversationMessage conversationMessage,
@NonNull ConversationMessage conversationMessage,
@NonNull Toolbar.OnMenuItemClickListener toolbarListener, @NonNull Toolbar.OnMenuItemClickListener toolbarListener,
@NonNull ConversationReactionOverlay.OnHideListener onHideListener); @NonNull ConversationReactionOverlay.OnHideListener onHideListener);
void onCursorChanged(); void onCursorChanged();
void onListVerticalTranslationChanged(float translationY);
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
void handleReactionDetails(@NonNull MaskView.MaskTarget maskTarget);
void onVoiceNotePause(@NonNull Uri uri); void onVoiceNotePause(@NonNull Uri uri);
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress); void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress);
void onVoiceNoteSeekTo(@NonNull Uri uri, double progress); void onVoiceNoteSeekTo(@NonNull Uri uri, double progress);
@ -1402,14 +1391,19 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
(!recipient.get().isGroup() || recipient.get().isActiveGroup()) && (!recipient.get().isGroup() || recipient.get().isActiveGroup()) &&
((ConversationAdapter) list.getAdapter()).getSelectedItems().isEmpty()) ((ConversationAdapter) list.getAdapter()).getSelectedItems().isEmpty())
{ {
multiselectItemDecoration.setFocusedItem(new MultiselectPart.Message(item.getConversationMessage()));
list.invalidateItemDecorations();
isReacting = true; isReacting = true;
list.setLayoutFrozen(true); list.setLayoutFrozen(true);
listener.handleReaction(getMaskTarget(itemView), item.getConversationMessage(), new ReactionsToolbarListener(item.getConversationMessage()), () -> { listener.handleReaction(item.getConversationMessage(), new ReactionsToolbarListener(item.getConversationMessage()), () -> {
isReacting = false; isReacting = false;
list.setLayoutFrozen(false); list.setLayoutFrozen(false);
WindowUtil.setLightStatusBarFromTheme(requireActivity()); WindowUtil.setLightStatusBarFromTheme(requireActivity());
clearFocusedItem();
}); });
} else { } else {
clearFocusedItem();
((ConversationAdapter) list.getAdapter()).toggleSelection(item); ((ConversationAdapter) list.getAdapter()).toggleSelection(item);
list.getAdapter().notifyDataSetChanged(); list.getAdapter().notifyDataSetChanged();
@ -1550,10 +1544,10 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
} }
@Override @Override
public void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms) { public void onReactionClicked(@NonNull MultiselectPart multiselectPart, long messageId, boolean isMms) {
if (getContext() == null) return; if (getContext() == null) return;
listener.handleReactionDetails(getMaskTarget(reactionTarget)); multiselectItemDecoration.setFocusedItem(multiselectPart);
ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(requireFragmentManager(), null); ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(requireFragmentManager(), null);
} }

Wyświetl plik

@ -142,6 +142,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* A view that displays an individual conversation item within a conversation * A view that displays an individual conversation item within a conversation
@ -1368,7 +1369,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
reactionsView.setOnClickListener(v -> { reactionsView.setOnClickListener(v -> {
if (eventListener == null) return; if (eventListener == null) return;
eventListener.onReactionClicked(this, current.getId(), current.isMms()); eventListener.onReactionClicked(new MultiselectPart.Message(conversationMessage), current.getId(), current.isMms());
}); });
} }
@ -1720,10 +1721,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (messageRecord.isOutgoing() && if (messageRecord.isOutgoing() &&
!hasNoBubble(messageRecord) && !hasNoBubble(messageRecord) &&
!messageRecord.isRemoteDelete() && !messageRecord.isRemoteDelete() &&
bodyBubbleCorners != null && bodyBubbleCorners != null)
bodyBubble.getProjections().isEmpty())
{ {
projections.add(Projection.relativeToViewRoot(bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX())); Projection bodyBubbleToRoot = Projection.relativeToViewRoot(bodyBubble, bodyBubbleCorners).translateX(bodyBubble.getTranslationX());
Projection videoToBubble = bodyBubble.getVideoPlayerProjection();
if (videoToBubble != null) {
Projection videoToRoot = Projection.translateFromDescendantToParentCoords(videoToBubble, bodyBubble, (ViewGroup) getRootView());
projections.addAll(Projection.getCapAndTail(bodyBubbleToRoot, videoToRoot));
} else {
projections.add(bodyBubbleToRoot);
}
} }
if (messageRecord.isOutgoing() && if (messageRecord.isOutgoing() &&

Wyświetl plik

@ -69,6 +69,10 @@ public class ConversationItemBodyBubble extends LinearLayout {
clipProjectionDrawable.setProjections(getProjections()); clipProjectionDrawable.setProjections(getProjections());
} }
public @Nullable Projection getVideoPlayerProjection() {
return videoPlayerProjection;
}
public @NonNull Set<Projection> getProjections() { public @NonNull Set<Projection> getProjections() {
return Stream.of(quoteViewProjection, videoPlayerProjection) return Stream.of(quoteViewProjection, videoPlayerProjection)
.filterNot(Objects::isNull) .filterNot(Objects::isNull)

Wyświetl plik

@ -1,66 +0,0 @@
package org.thoughtcrime.securesms.conversation;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.util.Projection;
import java.util.Arrays;
import java.util.List;
/**
* Masking area to ensure proper rendering of Reactions overlay.
*/
public final class ConversationItemMaskTarget extends MaskView.MaskTarget {
private final ConversationItem conversationItem;
private final View videoContainer;
private final Paint paint;
public ConversationItemMaskTarget(@NonNull ConversationItem conversationItem,
@Nullable View videoContainer)
{
super(conversationItem);
this.conversationItem = conversationItem;
this.videoContainer = videoContainer;
this.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected @NonNull List<View> getAllTargets() {
if (videoContainer == null) {
return super.getAllTargets();
} else {
return Arrays.asList(conversationItem, videoContainer);
}
}
@Override
protected void draw(@NonNull Canvas canvas) {
super.draw(canvas);
List<Projection> projections = Stream.of(conversationItem.getColorizerProjections()).map(p ->
Projection.translateFromRootToDescendantCoords(p, conversationItem)
).toList();
if (videoContainer != null) {
projections.add(conversationItem.getGiphyMp4PlayableProjection((RecyclerView) conversationItem.getParent()));
}
for (Projection projection : projections) {
canvas.drawPath(projection.getPath(), paint);
}
}
}

Wyświetl plik

@ -7,7 +7,6 @@ import android.view.MotionEvent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.util.views.Stub;
@ -27,7 +26,6 @@ final class ConversationReactionDelegate {
private ConversationReactionOverlay.OnReactionSelectedListener onReactionSelectedListener; private ConversationReactionOverlay.OnReactionSelectedListener onReactionSelectedListener;
private Toolbar.OnMenuItemClickListener onToolbarItemClickedListener; private Toolbar.OnMenuItemClickListener onToolbarItemClickedListener;
private ConversationReactionOverlay.OnHideListener onHideListener; private ConversationReactionOverlay.OnHideListener onHideListener;
private float translationY;
ConversationReactionDelegate(@NonNull Stub<ConversationReactionOverlay> overlayStub) { ConversationReactionDelegate(@NonNull Stub<ConversationReactionOverlay> overlayStub) {
this.overlayStub = overlayStub; this.overlayStub = overlayStub;
@ -38,17 +36,11 @@ final class ConversationReactionDelegate {
} }
void show(@NonNull Activity activity, void show(@NonNull Activity activity,
@NonNull MaskView.MaskTarget maskTarget,
@NonNull Recipient conversationRecipient, @NonNull Recipient conversationRecipient,
@NonNull ConversationMessage conversationMessage, @NonNull ConversationMessage conversationMessage,
int maskPaddingBottom,
boolean isNonAdminInAnnouncementGroup) boolean isNonAdminInAnnouncementGroup)
{ {
resolveOverlay().show(activity, maskTarget, conversationRecipient, conversationMessage, maskPaddingBottom, lastSeenDownPoint, isNonAdminInAnnouncementGroup); resolveOverlay().show(activity, conversationRecipient, conversationMessage, lastSeenDownPoint, isNonAdminInAnnouncementGroup);
}
void showMask(@NonNull MaskView.MaskTarget maskTarget, int maskPaddingTop, int maskPaddingBottom) {
resolveOverlay().showMask(maskTarget, maskPaddingTop, maskPaddingBottom);
} }
void hide() { void hide() {
@ -59,10 +51,6 @@ final class ConversationReactionDelegate {
overlayStub.get().hideForReactWithAny(); overlayStub.get().hideForReactWithAny();
} }
void hideMask() {
overlayStub.get().hideMask();
}
void setOnReactionSelectedListener(@NonNull ConversationReactionOverlay.OnReactionSelectedListener onReactionSelectedListener) { void setOnReactionSelectedListener(@NonNull ConversationReactionOverlay.OnReactionSelectedListener onReactionSelectedListener) {
this.onReactionSelectedListener = onReactionSelectedListener; this.onReactionSelectedListener = onReactionSelectedListener;
@ -87,14 +75,6 @@ final class ConversationReactionDelegate {
} }
} }
void setListVerticalTranslation(float translationY) {
this.translationY = translationY;
if (overlayStub.resolved()) {
overlayStub.get().setListVerticalTranslation(translationY);
}
}
@NonNull MessageRecord getMessageRecord() { @NonNull MessageRecord getMessageRecord() {
if (!overlayStub.resolved()) { if (!overlayStub.resolved()) {
throw new IllegalStateException("Cannot call getMessageRecord right now."); throw new IllegalStateException("Cannot call getMessageRecord right now.");
@ -118,7 +98,6 @@ final class ConversationReactionDelegate {
ConversationReactionOverlay overlay = overlayStub.get(); ConversationReactionOverlay overlay = overlayStub.get();
overlay.requestFitSystemWindows(); overlay.requestFitSystemWindows();
overlay.setListVerticalTranslation(translationY);
overlay.setOnHideListener(onHideListener); overlay.setOnHideListener(onHideListener);
overlay.setOnToolbarItemClickedListener(onToolbarItemClickedListener); overlay.setOnToolbarItemClickedListener(onToolbarItemClickedListener);
overlay.setOnReactionSelectedListener(onReactionSelectedListener); overlay.setOnReactionSelectedListener(onReactionSelectedListener);

Wyświetl plik

@ -28,7 +28,6 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener; import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.components.MaskView;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView; import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil; import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -40,7 +39,6 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.WindowUtil;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -72,7 +70,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
private ConstraintLayout foregroundView; private ConstraintLayout foregroundView;
private View selectedView; private View selectedView;
private EmojiImageView[] emojiViews; private EmojiImageView[] emojiViews;
private MaskView maskView;
private Toolbar toolbar; private Toolbar toolbar;
private float touchDownDeadZoneSize; private float touchDownDeadZoneSize;
@ -112,7 +109,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
backgroundView = findViewById(R.id.conversation_reaction_scrubber_background); backgroundView = findViewById(R.id.conversation_reaction_scrubber_background);
foregroundView = findViewById(R.id.conversation_reaction_scrubber_foreground); foregroundView = findViewById(R.id.conversation_reaction_scrubber_foreground);
selectedView = findViewById(R.id.conversation_reaction_current_selection_indicator); selectedView = findViewById(R.id.conversation_reaction_current_selection_indicator);
maskView = findViewById(R.id.conversation_reaction_mask);
toolbar = findViewById(R.id.conversation_reaction_toolbar); toolbar = findViewById(R.id.conversation_reaction_toolbar);
toolbar.setOnMenuItemClickListener(this::handleToolbarItemClicked); toolbar.setOnMenuItemClickListener(this::handleToolbarItemClicked);
@ -144,15 +140,9 @@ public final class ConversationReactionOverlay extends RelativeLayout {
initAnimators(); initAnimators();
} }
public void setListVerticalTranslation(float translationY) {
maskView.setTargetParentTranslationY(translationY);
}
public void show(@NonNull Activity activity, public void show(@NonNull Activity activity,
@NonNull MaskView.MaskTarget maskTarget,
@NonNull Recipient conversationRecipient, @NonNull Recipient conversationRecipient,
@NonNull ConversationMessage conversationMessage, @NonNull ConversationMessage conversationMessage,
int maskPaddingBottom,
@NonNull PointF lastSeenDownPoint, @NonNull PointF lastSeenDownPoint,
boolean isNonAdminInAnnouncementGroup) boolean isNonAdminInAnnouncementGroup)
{ {
@ -195,9 +185,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
verticalScrubBoundary.update(lastSeenDownPoint.y - distanceFromTouchDownPointToTopOfScrubberDeadZone, verticalScrubBoundary.update(lastSeenDownPoint.y - distanceFromTouchDownPointToTopOfScrubberDeadZone,
lastSeenDownPoint.y + distanceFromTouchDownPointToBottomOfScrubberDeadZone); lastSeenDownPoint.y + distanceFromTouchDownPointToBottomOfScrubberDeadZone);
maskView.setPadding(0, 0, 0, maskPaddingBottom);
maskView.setTarget(maskTarget);
hideAnimatorSet.end(); hideAnimatorSet.end();
toolbar.setVisibility(VISIBLE); toolbar.setVisibility(VISIBLE);
setVisibility(View.VISIBLE); setVisibility(View.VISIBLE);
@ -214,18 +201,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
} }
} }
public void showMask(@NonNull MaskView.MaskTarget maskTarget, int maskPaddingTop, int maskPaddingBottom) {
maskView.setPadding(0, maskPaddingTop, 0, maskPaddingBottom);
maskView.setTarget(maskTarget);
hideAnimatorSet.end();
toolbar.setVisibility(GONE);
setVisibility(VISIBLE);
revealMaskAnimatorSet.start();
}
public void hide() { public void hide() {
maskView.setTarget(null);
hideInternal(hideAnimatorSet, onHideListener); hideInternal(hideAnimatorSet, onHideListener);
} }
@ -233,14 +209,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
hideInternal(hideAnimatorSet, null); hideInternal(hideAnimatorSet, null);
} }
public void hideMask() {
hideMaskAnimatorSet.start();
if (onHideListener != null) {
onHideListener.onHide();
}
}
private void hideInternal(@NonNull AnimatorSet hideAnimatorSet, @Nullable OnHideListener onHideListener) { private void hideInternal(@NonNull AnimatorSet hideAnimatorSet, @Nullable OnHideListener onHideListener) {
overlayState = OverlayState.HIDDEN; overlayState = OverlayState.HIDDEN;
@ -540,7 +508,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
.toList(); .toList();
Animator overlayRevealAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_in); Animator overlayRevealAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_in);
overlayRevealAnim.setTarget(maskView);
overlayRevealAnim.setDuration(duration); overlayRevealAnim.setDuration(duration);
reveals.add(overlayRevealAnim); reveals.add(overlayRevealAnim);
@ -575,7 +542,6 @@ public final class ConversationReactionOverlay extends RelativeLayout {
.toList(); .toList();
Animator overlayHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out); Animator overlayHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);
overlayHideAnim.setTarget(maskView);
overlayHideAnim.setDuration(duration); overlayHideAnim.setDuration(duration);
Animator backgroundHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out); Animator backgroundHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out);

Wyświetl plik

@ -118,6 +118,7 @@ class RecyclerViewColorizer(private val recyclerView: RecyclerView) {
mask.setBounds(0, 0, parent.width, parent.height) mask.setBounds(0, 0, parent.width, parent.height)
mask.draw(canvas) mask.draw(canvas)
} else { } else {
colorPaint.color = chatColors.asSingleColor()
canvas.drawRect( canvas.drawRect(
0f, 0f,
0f, 0f,

Wyświetl plik

@ -9,9 +9,11 @@ import android.graphics.Path
import android.graphics.Rect import android.graphics.Rect
import android.graphics.Region import android.graphics.Region
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.forEach
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -54,6 +56,12 @@ class MultiselectItemDecoration(
private var checkedBitmap: Bitmap? = null private var checkedBitmap: Bitmap? = null
private var focusedItem: MultiselectPart? = null
fun setFocusedItem(multiselectPart: MultiselectPart?) {
this.focusedItem = multiselectPart
}
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
val bitmap = Bitmap.createBitmap(circleRadius * 2, circleRadius * 2, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(circleRadius * 2, circleRadius * 2, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
@ -67,6 +75,8 @@ class MultiselectItemDecoration(
checkedBitmap = null checkedBitmap = null
} }
private val shadeColor = ContextCompat.getColor(context, R.color.reactions_screen_shade_color)
private val unselectedPaint = Paint().apply { private val unselectedPaint = Paint().apply {
isAntiAlias = true isAntiAlias = true
strokeWidth = 1.5f strokeWidth = 1.5f
@ -102,6 +112,7 @@ class MultiselectItemDecoration(
val adapter = parent.adapter as ConversationAdapter val adapter = parent.adapter as ConversationAdapter
if (adapter.selectedItems.isEmpty()) { if (adapter.selectedItems.isEmpty()) {
drawFocusShadeUnderIfNecessary(canvas, parent)
return return
} }
@ -116,7 +127,7 @@ class MultiselectItemDecoration(
val parts: MultiselectCollection = child.conversationMessage.multiselectCollection val parts: MultiselectCollection = child.conversationMessage.multiselectCollection
val projections: List<Projection> = child.colorizerProjections val projections: List<Projection> = child.colorizerProjections + if (child.canPlayContent()) listOf(child.getGiphyMp4PlayableProjection(child.rootView as ViewGroup)) else emptyList()
path.reset() path.reset()
projections.forEach { it.applyToPath(path) } projections.forEach { it.applyToPath(path) }
@ -148,6 +159,7 @@ class MultiselectItemDecoration(
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val adapter = parent.adapter as ConversationAdapter val adapter = parent.adapter as ConversationAdapter
if (adapter.selectedItems.isEmpty()) { if (adapter.selectedItems.isEmpty()) {
drawFocusShadeOverIfNecessary(canvas, parent)
return return
} }
@ -285,4 +297,48 @@ class MultiselectItemDecoration(
child.translationX = 0f child.translationX = 0f
} }
} }
private fun drawFocusShadeUnderIfNecessary(canvas: Canvas, parent: RecyclerView) {
val inFocus = focusedItem
if (inFocus != null) {
path.reset()
canvas.save()
parent.forEach { child ->
if (child is Multiselectable && child.conversationMessage == inFocus.conversationMessage) {
path.addRect(child.left.toFloat(), child.top.toFloat(), child.right.toFloat(), child.bottom.toFloat(), Path.Direction.CW)
child.colorizerProjections.forEach {
path.op(it.path, Path.Op.DIFFERENCE)
}
if (child.canPlayContent()) {
val mp4GifProjection = child.getGiphyMp4PlayableProjection(child.rootView as ViewGroup)
path.op(mp4GifProjection.path, Path.Op.DIFFERENCE)
}
}
}
canvas.clipPath(path)
canvas.drawColor(shadeColor)
canvas.restore()
}
}
private fun drawFocusShadeOverIfNecessary(canvas: Canvas, parent: RecyclerView) {
val inFocus = focusedItem
if (inFocus != null) {
path.reset()
canvas.save()
parent.forEach { child ->
if (child is Multiselectable && child.conversationMessage == inFocus.conversationMessage) {
path.addRect(child.left.toFloat(), child.top.toFloat(), child.right.toFloat(), child.bottom.toFloat(), Path.Direction.CW)
}
}
canvas.clipPath(path, Region.Op.DIFFERENCE)
canvas.drawColor(shadeColor)
canvas.restore()
}
}
} }

Wyświetl plik

@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.conversation.mutiselect
import android.view.View import android.view.View
import org.thoughtcrime.securesms.conversation.ConversationMessage import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.Colorizable import org.thoughtcrime.securesms.conversation.colors.Colorizable
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable
interface Multiselectable : Colorizable { interface Multiselectable : Colorizable, GiphyMp4Playable {
val conversationMessage: ConversationMessage val conversationMessage: ConversationMessage
fun getTopBoundaryOfMultiselectPart(multiselectPart: MultiselectPart): Int fun getTopBoundaryOfMultiselectPart(multiselectPart: MultiselectPart): Int

Wyświetl plik

@ -39,7 +39,7 @@ public final class GiphyMp4ProjectionRecycler implements GiphyMp4PlaybackControl
for (final GiphyMp4Playable holder : holders) { for (final GiphyMp4Playable holder : holders) {
if (playbackSet.contains(holder.getAdapterPosition())) { if (playbackSet.contains(holder.getAdapterPosition())) {
startPlayback(acquireHolderForPosition(holder.getAdapterPosition()), holder); startPlayback(recyclerView, acquireHolderForPosition(holder.getAdapterPosition()), holder);
} else { } else {
holder.showProjectionArea(); holder.showProjectionArea();
} }
@ -107,16 +107,22 @@ public final class GiphyMp4ProjectionRecycler implements GiphyMp4PlaybackControl
holder.setCorners(projection.getCorners()); holder.setCorners(projection.getCorners());
} }
private void startPlayback(@NonNull GiphyMp4ProjectionPlayerHolder holder, @NonNull GiphyMp4Playable giphyMp4Playable) { private void startPlayback(@NonNull RecyclerView parent, @NonNull GiphyMp4ProjectionPlayerHolder holder, @NonNull GiphyMp4Playable giphyMp4Playable) {
if (!Objects.equals(holder.getMediaItem(), giphyMp4Playable.getMediaItem())) { if (!Objects.equals(holder.getMediaItem(), giphyMp4Playable.getMediaItem())) {
holder.setOnPlaybackReady(null); holder.setOnPlaybackReady(null);
giphyMp4Playable.showProjectionArea(); giphyMp4Playable.showProjectionArea();
holder.show(); holder.show();
holder.setOnPlaybackReady(giphyMp4Playable::hideProjectionArea); holder.setOnPlaybackReady(() -> {
giphyMp4Playable.hideProjectionArea();
parent.invalidateItemDecorations();
});
holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer()); holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer());
} else { } else {
holder.setOnPlaybackReady(giphyMp4Playable::hideProjectionArea); holder.setOnPlaybackReady(() -> {
giphyMp4Playable.hideProjectionArea();
parent.invalidateItemDecorations();
});
} }
} }

Wyświetl plik

@ -14,6 +14,9 @@ import androidx.recyclerview.widget.RecyclerView;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.CornerMask; import org.thoughtcrime.securesms.components.CornerMask;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
@ -159,6 +162,42 @@ public final class Projection {
return new Projection(viewBounds.left, viewBounds.top, descendantProjection.width, descendantProjection.height, descendantProjection.corners); return new Projection(viewBounds.left, viewBounds.top, descendantProjection.width, descendantProjection.height, descendantProjection.corners);
} }
public static @NonNull List<Projection> getCapAndTail(@NonNull Projection parentProjection, @NonNull Projection childProjection) {
if (parentProjection.equals(childProjection)) {
return Collections.emptyList();
}
float topX = parentProjection.x;
float topY = parentProjection.y;
int topWidth = parentProjection.getWidth();
int topHeight = (int) (childProjection.y - parentProjection.y);
final Corners topCorners;
Corners parentCorners = parentProjection.getCorners();
if (parentCorners != null) {
topCorners = new Corners(parentCorners.topLeft, parentCorners.topRight, 0f, 0f);
} else {
topCorners = null;
}
float bottomX = parentProjection.x;
float bottomY = parentProjection.y + topHeight + childProjection.getHeight();
int bottomWidth = parentProjection.getWidth();
int bottomHeight = (int) ((parentProjection.y + parentProjection.getHeight()) - bottomY);
final Corners bottomCorners;
if (parentCorners != null) {
bottomCorners = new Corners(0f, 0f, parentCorners.bottomRight, parentCorners.bottomLeft);
} else {
bottomCorners = null;
}
return Arrays.asList(
new Projection(topX, topY, topWidth, topHeight, topCorners),
new Projection(bottomX, bottomY, bottomWidth, bottomHeight, bottomCorners)
);
}
public static final class Corners { public static final class Corners {
private final float topLeft; private final float topLeft;
private final float topRight; private final float topRight;

Wyświetl plik

@ -13,13 +13,6 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<org.thoughtcrime.securesms.components.MaskView
android:id="@+id/conversation_reaction_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
android:background="@color/reactions_screen_shade_color" />
<include <include
android:id="@+id/conversation_reaction_toolbar" android:id="@+id/conversation_reaction_toolbar"
layout="@layout/conversation_reaction_long_press_toolbar" /> layout="@layout/conversation_reaction_long_press_toolbar" />