From 9d3764c5d901b3266b52e8df5e45953387b0c0d7 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 29 Jul 2020 13:44:23 -0300 Subject: [PATCH] Reactions UX polish. --- .../securesms/BindableConversationItem.java | 2 +- .../securesms/components/MaskView.java | 2 +- .../conversation/ConversationActivity.java | 14 +- .../conversation/ConversationFragment.java | 4 +- .../conversation/ConversationItem.java | 2 +- .../ConversationReactionOverlay.java | 42 ++++-- .../securesms/keyvalue/EmojiValues.java | 35 +++++ .../securesms/keyvalue/SignalStore.java | 6 + .../securesms/reactions/EmojiCount.java | 28 +++- .../securesms/reactions/ReactionDetails.java | 35 +++++ .../reactions/ReactionEmojiCountAdapter.java | 130 ----------------- .../reactions/ReactionRecipientsAdapter.java | 9 +- .../reactions/ReactionViewPagerAdapter.java | 91 ++++++++++++ .../ReactionsBottomSheetDialogFragment.java | 135 +++++++++++++----- .../securesms/reactions/ReactionsLoader.java | 43 ++---- .../reactions/ReactionsViewModel.java | 46 +++--- .../any/ReactWithAnyEmojiViewModel.java | 2 + ...tion_reaction_overlay_background_light.xml | 5 + ..._emoji_32.xml => ic_any_emoji_32_dark.xml} | 0 .../res/drawable/ic_any_emoji_32_light.xml | 18 +++ .../res/drawable/ic_x_reaction_overlay.xml | 9 ++ .../reactions_old_background_light.xml | 5 + ...nversation_reaction_long_press_toolbar.xml | 6 +- .../layout/conversation_reaction_scrubber.xml | 4 +- ...reactions_bottom_sheet_dialog_fragment.xml | 24 +--- ...ottom_sheet_dialog_fragment_emoji_item.xml | 14 +- ..._bottom_sheet_dialog_fragment_recycler.xml | 10 ++ ...ions_bottom_sheet_dialog_fragment_tabs.xml | 17 +++ ...conversation_reactions_long_press_menu.xml | 7 + app/src/main/res/values/attrs.xml | 3 + app/src/main/res/values/styles.xml | 15 ++ app/src/main/res/values/themes.xml | 10 +- 32 files changed, 489 insertions(+), 284 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionEmojiCountAdapter.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionViewPagerAdapter.java create mode 100644 app/src/main/res/drawable/conversation_reaction_overlay_background_light.xml rename app/src/main/res/drawable/{ic_any_emoji_32.xml => ic_any_emoji_32_dark.xml} (100%) create mode 100644 app/src/main/res/drawable/ic_any_emoji_32_light.xml create mode 100644 app/src/main/res/drawable/ic_x_reaction_overlay.xml create mode 100644 app/src/main/res/drawable/reactions_old_background_light.xml create mode 100644 app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_recycler.xml create mode 100644 app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_tabs.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index da6e680244..fc6273bb87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -46,7 +46,7 @@ public interface BindableConversationItem extends Unbindable { void onAddToContactsClicked(@NonNull Contact contact); void onMessageSharedContactClicked(@NonNull List choices); void onInviteSharedContactClicked(@NonNull List choices); - void onReactionClicked(long messageId, boolean isMms); + void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms); void onGroupMemberAvatarClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MaskView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MaskView.java index 7f75d4b75d..dcd00eb690 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MaskView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/MaskView.java @@ -79,7 +79,6 @@ public class MaskView extends View { target.getDrawingRect(drawingRect); activityContentView.offsetDescendantRectToMyCoords(target, drawingRect); - drawingRect.bottom = Math.min(drawingRect.bottom, getBottom() - getPaddingBottom()); drawingRect.top += targetParentTranslationY; drawingRect.bottom += targetParentTranslationY; @@ -88,6 +87,7 @@ public class MaskView extends View { target.draw(maskCanvas); + canvas.clipRect(drawingRect.left, Math.max(drawingRect.top, getTop() + getPaddingTop()), drawingRect.right, Math.min(drawingRect.bottom, getBottom() - getPaddingBottom())); canvas.drawBitmap(mask, 0, drawingRect.top, maskPaint); mask.recycle(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 320557c5de..3b9a74c608 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -201,6 +201,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.GroupShareProfileView; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment; import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; @@ -281,7 +282,8 @@ public class ConversationActivity extends PassphraseRequiredActivity AttachmentKeyboard.Callback, ConversationReactionOverlay.OnReactionSelectedListener, ReactWithAnyEmojiBottomSheetDialogFragment.Callback, - SafetyNumberChangeDialog.Callback + SafetyNumberChangeDialog.Callback, + ReactionsBottomSheetDialogFragment.Callback { private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2); @@ -2763,6 +2765,11 @@ public class ConversationActivity extends PassphraseRequiredActivity typingTextWatcher.setEnabled(true); } + @Override + public void onReactionsDialogDismissed() { + reactionOverlay.hideMask(); + } + // Listeners private class QuickCameraToggleListener implements OnClickListener { @@ -2950,6 +2957,11 @@ public class ConversationActivity extends PassphraseRequiredActivity } } + @Override + public void handleReactionDetails(@NonNull View maskTarget) { + reactionOverlay.showMask(maskTarget, titleView.getMeasuredHeight(), panelParent.getMeasuredHeight()); + } + @Override public void onCursorChanged() { if (!reactionOverlay.isShowing()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 5d830cbddd..088dd913d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1014,6 +1014,7 @@ public class ConversationFragment extends LoggingFragment { void onCursorChanged(); void onListVerticalTranslationChanged(float translationY); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); + void handleReactionDetails(@NonNull View maskTarget); } private class ConversationScrollListener extends OnScrollListener { @@ -1264,9 +1265,10 @@ public class ConversationFragment extends LoggingFragment { } @Override - public void onReactionClicked(long messageId, boolean isMms) { + public void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms) { if (getContext() == null) return; + listener.handleReactionDetails(reactionTarget); ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(requireFragmentManager(), null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index c4117d4de3..146a6ed953 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -970,7 +970,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati reactionsView.setOnClickListener(v -> { if (eventListener == null) return; - eventListener.onReactionClicked(current.getId(), current.isMms()); + eventListener.onReactionClicked(this, current.getId(), current.isMms()); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index 151bc3fc72..5034569fdb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -5,7 +5,9 @@ import android.animation.AnimatorSet; import android.app.Activity; import android.content.Context; import android.graphics.PointF; +import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; @@ -19,7 +21,6 @@ import android.widget.RelativeLayout; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; @@ -32,10 +33,11 @@ import org.thoughtcrime.securesms.R; 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.EmojiUtil; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -91,6 +93,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { private OnHideListener onHideListener; private AnimatorSet revealAnimatorSet = new AnimatorSet(); + private AnimatorSet revealMaskAnimatorSet = new AnimatorSet(); private AnimatorSet hideAnimatorSet = new AnimatorSet(); private AnimatorSet hideAllButMaskAnimatorSet = new AnimatorSet(); private AnimatorSet hideMaskAnimatorSet = new AnimatorSet(); @@ -185,16 +188,31 @@ public final class ConversationReactionOverlay extends RelativeLayout { maskView.setTarget(maskTarget); hideAnimatorSet.end(); + toolbar.setVisibility(VISIBLE); setVisibility(View.VISIBLE); revealAnimatorSet.start(); if (Build.VERSION.SDK_INT >= 21) { this.activity = activity; originalStatusBarColor = activity.getWindow().getStatusBarColor(); - activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, R.color.action_mode_status_bar)); + activity.getWindow().setStatusBarColor(ThemeUtil.getThemedColor(getContext(), R.attr.reactions_overlay_toolbar_background_color)); + + if (!ThemeUtil.isDarkTheme(getContext()) && Build.VERSION.SDK_INT >= 23) { + activity.getWindow().getDecorView().setSystemUiVisibility(activity.getWindow().getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } } } + public void showMask(@NonNull View 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() { maskView.setTarget(null); hideInternal(hideAnimatorSet, onHideListener); @@ -220,6 +238,11 @@ public final class ConversationReactionOverlay extends RelativeLayout { if (Build.VERSION.SDK_INT >= 21 && activity != null) { activity.getWindow().setStatusBarColor(originalStatusBarColor); + + if (!ThemeUtil.isDarkTheme(getContext()) && Build.VERSION.SDK_INT >= 23) { + activity.getWindow().getDecorView().setSystemUiVisibility(activity.getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + activity = null; } @@ -358,7 +381,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { view.setTranslationY(0); boolean isAtCustomIndex = i == customEmojiIndex; - boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && ReactionEmoji.values()[i].emoji.equals(oldEmoji); + boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && oldEmoji != null && ReactionEmoji.values()[i].emoji.equals(EmojiUtil.getCanonicalRepresentation(oldEmoji)); boolean isAtCustomIndexAndOldEmojiExists = isAtCustomIndex && oldEmoji != null; if (!foundSelected && @@ -379,13 +402,13 @@ public final class ConversationReactionOverlay extends RelativeLayout { view.setImageEmoji(oldEmoji); view.setTag(oldEmoji); } else { - view.setImageEmoji(ReactionEmoji.values()[i].emoji); + view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[i].emoji)); } } else if (isAtCustomIndex) { - view.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.ic_any_emoji_32)); + view.setImageDrawable(ThemeUtil.getThemedDrawable(getContext(), R.attr.reactions_overlay_custom_emoji_icon)); view.setTag(null); } else { - view.setImageEmoji(ReactionEmoji.values()[i].emoji); + view.setImageEmoji(SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[i].emoji)); } } } @@ -447,7 +470,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { if (selected == customEmojiIndex) { onReactionSelectedListener.onCustomReactionSelected(messageRecord, emojiViews[selected].getTag() != null); } else { - onReactionSelectedListener.onReactionSelected(messageRecord, ReactionEmoji.values()[selected].emoji); + onReactionSelectedListener.onReactionSelected(messageRecord, SignalStore.emojiValues().getPreferredVariation(ReactionEmoji.values()[selected].emoji)); } } else { hide(); @@ -534,6 +557,9 @@ public final class ConversationReactionOverlay extends RelativeLayout { revealAnimatorSet.setInterpolator(INTERPOLATOR); revealAnimatorSet.playTogether(reveals); + revealMaskAnimatorSet.setInterpolator(INTERPOLATOR); + revealMaskAnimatorSet.playTogether(overlayRevealAnim); + List hides = Stream.of(emojiViews) .mapIndexed((idx, v) -> { Animator anim = AnimatorInflaterCompat.loadAnimator(getContext(), R.animator.reactions_scrubber_hide); diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java new file mode 100644 index 0000000000..ac1cddf077 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/EmojiValues.java @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.keyvalue; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.components.emoji.EmojiUtil; + +public class EmojiValues extends SignalStoreValues { + + private static final String PREFIX = "emojiPref__"; + + EmojiValues(@NonNull KeyValueStore store) { + super(store); + } + + @Override + void onFirstEverAppLaunch() { + + } + + public void setPreferredVariation(@NonNull String emoji) { + String canonical = EmojiUtil.getCanonicalRepresentation(emoji); + + if (canonical.equals(emoji)) { + getStore().beginWrite().remove(PREFIX + canonical).apply(); + } else { + putString(PREFIX + canonical, emoji); + } + } + + public @NonNull String getPreferredVariation(@NonNull String emoji) { + String canonical = EmojiUtil.getCanonicalRepresentation(emoji); + + return getString(PREFIX + canonical, emoji); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java index 8cc979255f..68deca7c21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java @@ -24,6 +24,7 @@ public final class SignalStore { private final TooltipValues tooltipValues; private final MiscellaneousValues misc; private final InternalValues internalValues; + private final EmojiValues emojiValues; private SignalStore() { this.store = ApplicationDependencies.getKeyValueStore(); @@ -36,6 +37,7 @@ public final class SignalStore { this.tooltipValues = new TooltipValues(store); this.misc = new MiscellaneousValues(store); this.internalValues = new InternalValues(store); + this.emojiValues = new EmojiValues(store); } public static void onFirstEverAppLaunch() { @@ -86,6 +88,10 @@ public final class SignalStore { return INSTANCE.internalValues; } + public static @NonNull EmojiValues emojiValues() { + return INSTANCE.emojiValues; + } + public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() { return new GroupsV2AuthorizationSignalStoreCache(getStore()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java index 88842356c6..4b1a14aada 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/EmojiCount.java @@ -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 reactions) { + return new EmojiCount("", "", reactions); + } + + private final String baseEmoji; + private final String displayEmoji; + private final List reactions; + + EmojiCount(@NonNull String baseEmoji, + @NonNull String emoji, + @NonNull List 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 getReactions() { + return reactions; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.java new file mode 100644 index 0000000000..15d6165f82 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionDetails.java @@ -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; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionEmojiCountAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionEmojiCountAdapter.java deleted file mode 100644 index 07b1be83bb..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionEmojiCountAdapter.java +++ /dev/null @@ -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 { - - private List 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 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); - } -} 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 ab6247eed3..57c7a2b9bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -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 { - private List data = Collections.emptyList(); + private List data = Collections.emptyList(); - public void updateData(List newData) { + public void updateData(List newData) { data = newData; notifyDataSetChanged(); } @@ -44,7 +43,7 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter { + + 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 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); + } + } +} 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 fa7ff6ab3b..2ba912e747 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java @@ -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(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsLoader.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsLoader.java index 4386732c01..7873ee0e4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsLoader.java @@ -29,7 +29,7 @@ public class ReactionsLoader implements ReactionsViewModel.Repository, LoaderMan private final boolean isMms; private final Context appContext; - private MutableLiveData> internalLiveData = new MutableLiveData<>(); + private MutableLiveData> 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 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> getReactions() { + public LiveData> 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; - } - } } 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 9fcb1d7f93..03061dbab5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsViewModel.java @@ -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 filterEmoji = new MutableLiveData<>(); public ReactionsViewModel(@NonNull Repository repository) { this.repository = repository; } - public @NonNull LiveData> 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> 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 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> lhs, @NonNull Map.Entry> rhs) { + private int compareReactions(@NonNull Map.Entry> lhs, @NonNull Map.Entry> 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 reactions) { + private long getLatestTimestamp(List 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 reactions) { - for (Reaction reaction : reactions) { + private @NonNull String getCountDisplayEmoji(@NonNull List reactions) { + for (ReactionDetails reaction : reactions) { if (reaction.getSender().isLocalNumber()) { return reaction.getDisplayEmoji(); } @@ -72,7 +64,7 @@ public class ReactionsViewModel extends ViewModel { } interface Repository { - LiveData> getReactions(); + LiveData> getReactions(); } static final class Factory implements ViewModelProvider.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java index fa95cd6785..ff5022d003 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java @@ -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); } diff --git a/app/src/main/res/drawable/conversation_reaction_overlay_background_light.xml b/app/src/main/res/drawable/conversation_reaction_overlay_background_light.xml new file mode 100644 index 0000000000..d92e2eb75a --- /dev/null +++ b/app/src/main/res/drawable/conversation_reaction_overlay_background_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_any_emoji_32.xml b/app/src/main/res/drawable/ic_any_emoji_32_dark.xml similarity index 100% rename from app/src/main/res/drawable/ic_any_emoji_32.xml rename to app/src/main/res/drawable/ic_any_emoji_32_dark.xml diff --git a/app/src/main/res/drawable/ic_any_emoji_32_light.xml b/app/src/main/res/drawable/ic_any_emoji_32_light.xml new file mode 100644 index 0000000000..ab79446437 --- /dev/null +++ b/app/src/main/res/drawable/ic_any_emoji_32_light.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_x_reaction_overlay.xml b/app/src/main/res/drawable/ic_x_reaction_overlay.xml new file mode 100644 index 0000000000..6113338695 --- /dev/null +++ b/app/src/main/res/drawable/ic_x_reaction_overlay.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/reactions_old_background_light.xml b/app/src/main/res/drawable/reactions_old_background_light.xml new file mode 100644 index 0000000000..ddb1cd660d --- /dev/null +++ b/app/src/main/res/drawable/reactions_old_background_light.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_reaction_long_press_toolbar.xml b/app/src/main/res/layout/conversation_reaction_long_press_toolbar.xml index 5341e4d963..f4b6f0478f 100644 --- a/app/src/main/res/layout/conversation_reaction_long_press_toolbar.xml +++ b/app/src/main/res/layout/conversation_reaction_long_press_toolbar.xml @@ -3,11 +3,11 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="@color/action_mode_status_bar" - android:theme="@style/TextSecure.DarkActionBar.Conversation" + android:background="?attr/reactions_overlay_toolbar_background_color" + android:theme="@style/TextSecure.DarkActionBar.ReactionOverlay" app:contentInsetStart="0dp" app:contentInsetStartWithNavigation="48sp" app:menu="@menu/conversation_reactions_long_press_menu" - app:navigationIcon="@drawable/ic_x_conversation"> + app:navigationIcon="@drawable/ic_x_tinted"> \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_reaction_scrubber.xml b/app/src/main/res/layout/conversation_reaction_scrubber.xml index b248aba015..607b9674b9 100644 --- a/app/src/main/res/layout/conversation_reaction_scrubber.xml +++ b/app/src/main/res/layout/conversation_reaction_scrubber.xml @@ -27,9 +27,9 @@ android:layout_marginTop="40dp" android:layout_marginBottom="40dp" android:alpha="0" - tools:alpha="1" android:background="?reactions_overlay_scrubber_background" - android:elevation="4dp" /> + android:elevation="4dp" + tools:alpha="1" /> + android:orientation="vertical" + android:paddingBottom="@dimen/react_with_any_emoji_bottom_sheet_dialog_fragment_tabs_height"> - - - + android:layout_weight="1" /> diff --git a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml index 5a0a37d466..78405f56e4 100644 --- a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml +++ b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_emoji_item.xml @@ -2,7 +2,7 @@ - + app:emoji_forceCustom="true" /> + diff --git a/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_tabs.xml b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_tabs.xml new file mode 100644 index 0000000000..0d646f58c5 --- /dev/null +++ b/app/src/main/res/layout/reactions_bottom_sheet_dialog_fragment_tabs.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/conversation_reactions_long_press_menu.xml b/app/src/main/res/menu/conversation_reactions_long_press_menu.xml index 5da3518829..cfcc9f08b1 100644 --- a/app/src/main/res/menu/conversation_reactions_long_press_menu.xml +++ b/app/src/main/res/menu/conversation_reactions_long_press_menu.xml @@ -5,36 +5,43 @@ android:id="@+id/action_info" android:icon="?menu_info_icon" android:title="@string/conversation_context__menu_message_details" + app:iconTint="?attr/reactions_overlay_toolbar_icon_tint" app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 68ce3da0a2..bf7865454b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -107,6 +107,9 @@ + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eea6dfa6b5..e62b0efed7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -109,6 +109,21 @@ @color/core_ultramarine + +