diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java index b385d1a9d3..5db4e07860 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiKeyboardProvider.java @@ -133,7 +133,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider, @Override public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) { - EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener); + EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true); page.setModel(pages.get(position)); container.addView(page); return page; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java index 61eb8823e7..3245394f3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageView.java @@ -26,7 +26,8 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe public EmojiPageView(@NonNull Context context, @NonNull EmojiEventListener emojiSelectionListener, - @NonNull VariationSelectorListener variationSelectorListener) + @NonNull VariationSelectorListener variationSelectorListener, + boolean allowVariations) { super(context); final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true); @@ -40,7 +41,8 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe adapter = new EmojiPageViewGridAdapter(EmojiProvider.getInstance(context), popup, emojiSelectionListener, - this); + this, + allowVariations); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); @@ -83,6 +85,10 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe } } + public void setRecyclerNestedScrollingEnabled(boolean enabled) { + recyclerView.setNestedScrollingEnabled(enabled); + } + private static class ScrollDisabler implements RecyclerView.OnItemTouchListener { @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageViewGridAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageViewGridAdapter.java index 7f19e2d8a8..bfe83f1da5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageViewGridAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiPageViewGridAdapter.java @@ -22,17 +22,20 @@ public class EmojiPageViewGridAdapter extends RecyclerView.Adapter(); this.emojiProvider = emojiProvider; this.popup = popup; this.emojiEventListener = emojiEventListener; this.variationSelectorListener = variationSelectorListener; + this.allowVariations = allowVariations; popup.setOnDismissListener(this); } @@ -65,7 +68,7 @@ public class EmojiPageViewGridAdapter extends RecyclerView.Adapter 1) { + if (allowVariations && emoji.getVariations().size() > 1) { viewHolder.itemView.setOnLongClickListener(v -> { popup.dismiss(); popup.setVariations(emoji.getVariations()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java index 7a2e06d906..97a583a8ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiUtil.java @@ -6,6 +6,7 @@ import org.whispersystems.libsignal.util.Pair; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -36,6 +37,10 @@ public final class EmojiUtil { private EmojiUtil() {} + public static List getDisplayPages() { + return EmojiPages.DISPLAY_PAGES; + } + /** * This will return all ways we know of expressing a singular emoji. This is to aid in search, * where some platforms may send an emoji we've locally marked as 'obsolete'. 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 d0c99459a3..d3d6758bc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -197,6 +197,7 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.GroupShareProfileView; import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; import org.thoughtcrime.securesms.providers.BlobProvider; +import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientExporter; @@ -269,7 +270,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity ComposeText.CursorPositionChangedListener, ConversationSearchBottomBar.EventListener, StickerKeyboardProvider.StickerEventListener, - AttachmentKeyboard.Callback + AttachmentKeyboard.Callback, + ConversationReactionOverlay.OnReactionSelectedListener, + ReactWithAnyEmojiBottomSheetDialogFragment.Callback { private static final int SHORTCUT_ICON_SIZE = Build.VERSION.SDK_INT >= 26 ? ViewUtil.dpToPx(72) : ViewUtil.dpToPx(48 + 16 * 2); @@ -1714,7 +1717,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment()); - reactionOverlay.setOnReactionSelectedListener(this::onReactionSelected); + reactionOverlay.setOnReactionSelectedListener(this); } protected void initializeActionBar() { @@ -1831,10 +1834,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity .show(TooltipPopup.POSITION_ABOVE); } - - private void onReactionSelected(MessageRecord messageRecord, String emoji) { + @Override + public void onReactionSelected(MessageRecord messageRecord, String emoji) { final Context context = getApplicationContext(); + reactionOverlay.hide(); + SignalExecutors.BOUNDED.execute(() -> { ReactionRecord oldRecord = Stream.of(messageRecord.getReactions()) .filter(record -> record.getAuthor().equals(Recipient.self().getId())) @@ -1849,6 +1854,35 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }); } + @Override + public void onCustomReactionSelected(@NonNull MessageRecord messageRecord, boolean hasAddedCustomEmoji) { + ReactionRecord oldRecord = Stream.of(messageRecord.getReactions()) + .filter(record -> record.getAuthor().equals(Recipient.self().getId())) + .findFirst() + .orElse(null); + + if (oldRecord != null && hasAddedCustomEmoji) { + final Context context = getApplicationContext(); + + reactionOverlay.hide(); + + SignalExecutors.BOUNDED.execute(() -> MessageSender.sendReactionRemoval(context, + messageRecord.getId(), + messageRecord.isMms(), + oldRecord)); + } else { + reactionOverlay.hideAllButMask(); + + ReactWithAnyEmojiBottomSheetDialogFragment.createForMessageRecord(messageRecord) + .show(getSupportFragmentManager(), "BOTTOM"); + } + } + + @Override + public void onReactWithAnyEmojiDialogDismissed() { + reactionOverlay.hideMask(); + } + @Override public void onSearchMoveUpPressed() { searchViewModel.onMoveUp(); 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 46e3535410..b42b7f33d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -19,6 +19,7 @@ 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; @@ -30,14 +31,17 @@ import com.annimon.stream.Stream; 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.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord; 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; import java.util.Collections; +import java.util.LinkedList; import java.util.List; public final class ConversationReactionOverlay extends RelativeLayout { @@ -60,12 +64,13 @@ public final class ConversationReactionOverlay extends RelativeLayout { private boolean downIsOurs; private boolean isToolbarTouch; private int selected = -1; + private int customEmojiIndex; private int originalStatusBarColor; private View backgroundView; private ConstraintLayout foregroundView; private View selectedView; - private View[] emojiViews; + private EmojiImageView[] emojiViews; private MaskView maskView; private Toolbar toolbar; @@ -85,8 +90,10 @@ public final class ConversationReactionOverlay extends RelativeLayout { private Toolbar.OnMenuItemClickListener onToolbarItemClickedListener; private OnHideListener onHideListener; - private AnimatorSet revealAnimatorSet = new AnimatorSet(); - private AnimatorSet hideAnimatorSet = new AnimatorSet(); + private AnimatorSet revealAnimatorSet = new AnimatorSet(); + private AnimatorSet hideAnimatorSet = new AnimatorSet(); + private AnimatorSet hideAllButMaskAnimatorSet = new AnimatorSet(); + private AnimatorSet hideMaskAnimatorSet = new AnimatorSet(); public ConversationReactionOverlay(@NonNull Context context) { super(context); @@ -111,7 +118,10 @@ public final class ConversationReactionOverlay extends RelativeLayout { emojiViews = Stream.of(ReactionEmoji.values()) .map(e -> findViewById(e.viewId)) - .toArray(View[]::new); + .toArray(EmojiImageView[]::new); + + customEmojiIndex = FeatureFlags.reactWithAnyEmoji() ? ReactionEmoji.values().length - 1 + : ReactionEmoji.values().length; distanceFromTouchDownPointToTopOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_top); distanceFromTouchDownPointToBottomOfScrubberDeadZone = getResources().getDimensionPixelSize(R.dimen.conversation_reaction_scrub_deadzone_distance_from_touch_bottom); @@ -144,7 +154,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { selected = -1; setupToolbarMenuItems(); - setupSelectedEmojiBackground(); + setupSelectedEmoji(); if (Build.VERSION.SDK_INT >= 21) { View statusBarBackground = activity.findViewById(android.R.id.statusBarBackground); @@ -188,6 +198,22 @@ public final class ConversationReactionOverlay extends RelativeLayout { public void hide() { maskView.setTarget(null); + hideInternal(hideAnimatorSet, onHideListener); + } + + public void hideAllButMask() { + hideInternal(hideAllButMaskAnimatorSet, null); + } + + public void hideMask() { + hideMaskAnimatorSet.start(); + + if (onHideListener != null) { + onHideListener.onHide(); + } + } + + private void hideInternal(@NonNull AnimatorSet hideAnimatorSet, @Nullable OnHideListener onHideListener) { overlayState = OverlayState.HIDDEN; revealAnimatorSet.end(); @@ -316,20 +342,30 @@ public final class ConversationReactionOverlay extends RelativeLayout { } } - private void setupSelectedEmojiBackground() { + private void setupSelectedEmoji() { final String oldEmoji = getOldEmoji(messageRecord); if (oldEmoji == null) { selectedView.setVisibility(View.GONE); } + boolean foundSelected = false; + for (int i = 0; i < emojiViews.length; i++) { - final View view = emojiViews[i]; + final EmojiImageView view = emojiViews[i]; + view.setScaleX(1.0f); view.setScaleY(1.0f); view.setTranslationY(0); - if (ReactionEmoji.values()[i].emoji.equals(oldEmoji)) { + boolean isAtCustomIndex = i == customEmojiIndex; + boolean isNotAtCustomIndexAndOldEmojiMatches = !isAtCustomIndex && ReactionEmoji.values()[i].emoji.equals(oldEmoji); + boolean isAtCustomIndexAndOldEmojiExists = isAtCustomIndex && oldEmoji != null; + + if (!foundSelected && + (isNotAtCustomIndexAndOldEmojiMatches || isAtCustomIndexAndOldEmojiExists)) + { + foundSelected = true; selectedView.setVisibility(View.VISIBLE); ConstraintSet constraintSet = new ConstraintSet(); @@ -339,6 +375,18 @@ public final class ConversationReactionOverlay extends RelativeLayout { constraintSet.connect(selectedView.getId(), ConstraintSet.LEFT, view.getId(), ConstraintSet.LEFT); constraintSet.connect(selectedView.getId(), ConstraintSet.RIGHT, view.getId(), ConstraintSet.RIGHT); constraintSet.applyTo(foregroundView); + + if (isAtCustomIndex) { + view.setImageEmoji(oldEmoji); + view.setTag(oldEmoji); + } else { + view.setImageEmoji(ReactionEmoji.values()[i].emoji); + } + } else if (isAtCustomIndex) { + view.setImageDrawable(AppCompatResources.getDrawable(getContext(), R.drawable.ic_any_emoji_32)); + view.setTag(null); + } else { + view.setImageEmoji(ReactionEmoji.values()[i].emoji); } } } @@ -396,9 +444,14 @@ public final class ConversationReactionOverlay extends RelativeLayout { } private void handleUpEvent() { - hide(); if (selected != -1 && onReactionSelectedListener != null) { - onReactionSelectedListener.onReactionSelected(messageRecord, ReactionEmoji.values()[selected].emoji); + if (selected == customEmojiIndex) { + onReactionSelectedListener.onCustomReactionSelected(messageRecord, emojiViews[selected].getTag() != null); + } else { + onReactionSelectedListener.onReactionSelected(messageRecord, ReactionEmoji.values()[selected].emoji); + } + } else { + hide(); } } @@ -494,7 +547,6 @@ public final class ConversationReactionOverlay extends RelativeLayout { Animator overlayHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out); overlayHideAnim.setTarget(maskView); overlayHideAnim.setDuration(duration); - hides.add(overlayHideAnim); Animator backgroundHideAnim = AnimatorInflaterCompat.loadAnimator(getContext(), android.R.animator.fade_out); backgroundHideAnim.setTarget(backgroundView); @@ -511,15 +563,26 @@ public final class ConversationReactionOverlay extends RelativeLayout { toolbarHideAnim.setDuration(duration); hides.add(toolbarHideAnim); - hideAnimatorSet.addListener(new AnimationCompleteListener() { + AnimationCompleteListener hideListener = new AnimationCompleteListener() { @Override public void onAnimationEnd(Animator animation) { setVisibility(View.GONE); } - }); + }; + List hideAllAnimators = new LinkedList<>(hides); + hideAllAnimators.add(overlayHideAnim); + + hideAnimatorSet.addListener(hideListener); hideAnimatorSet.setInterpolator(INTERPOLATOR); - hideAnimatorSet.playTogether(hides); + hideAnimatorSet.playTogether(hideAllAnimators); + + hideAllButMaskAnimatorSet.setInterpolator(INTERPOLATOR); + hideAllButMaskAnimatorSet.playTogether(hides); + + hideMaskAnimatorSet.addListener(hideListener); + hideMaskAnimatorSet.setInterpolator(INTERPOLATOR); + hideMaskAnimatorSet.playTogether(overlayHideAnim); } public interface OnHideListener { @@ -528,6 +591,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { public interface OnReactionSelectedListener { void onReactionSelected(@NonNull MessageRecord messageRecord, String emoji); + void onCustomReactionSelected(@NonNull MessageRecord messageRecord, boolean hasAddedCustomEmoji); } private static class Boundary { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionBottomSheetDialogFragment.java index 18bcb9238b..fc2c16f908 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/AvatarSelectionBottomSheetDialogFragment.java @@ -21,7 +21,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import org.thoughtcrime.securesms.ClearProfileAvatarActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.util.ThemeUtil; import java.util.ArrayList; @@ -63,8 +62,8 @@ public class AvatarSelectionBottomSheetDialogFragment extends BottomSheetDialogF @Override public void onCreate(@Nullable Bundle savedInstanceState) { setStyle(DialogFragment.STYLE_NORMAL, - ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Design_BottomSheetDialog_Fixed - : R.style.Theme_Design_Light_BottomSheetDialog_Fixed); + ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_BottomSheetDialog_Fixed + : R.style.Theme_Signal_Light_BottomSheetDialog_Fixed); super.onCreate(savedInstanceState); 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 acddb60530..fa7ff6ab3b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsBottomSheetDialogFragment.java @@ -46,9 +46,9 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF public void onCreate(@Nullable Bundle savedInstanceState) { if (ThemeUtil.isDarkTheme(requireContext())) { - setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Design_BottomSheetDialog_Fixed); + setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_BottomSheetDialog_Fixed); } else { - setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Design_Light_BottomSheetDialog_Fixed); + setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed); } super.onCreate(savedInstanceState); diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiAdapter.java new file mode 100644 index 0000000000..4be162cde4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiAdapter.java @@ -0,0 +1,88 @@ +package org.thoughtcrime.securesms.reactions.any; + +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.util.Consumer; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; +import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; +import org.thoughtcrime.securesms.components.emoji.EmojiPageView; +import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter; + +import java.util.List; + +final class ReactWithAnyEmojiAdapter extends RecyclerView.Adapter { + + private final List models; + private final EmojiKeyboardProvider.EmojiEventListener emojiEventListener; + private final EmojiPageViewGridAdapter.VariationSelectorListener variationSelectorListener; + private final Callbacks callbacks; + + ReactWithAnyEmojiAdapter(@NonNull List models, + @NonNull EmojiKeyboardProvider.EmojiEventListener emojiEventListener, + @NonNull EmojiPageViewGridAdapter.VariationSelectorListener variationSelectorListener, + @NonNull Callbacks callbacks) + { + this.models = models; + this.emojiEventListener = emojiEventListener; + this.variationSelectorListener = variationSelectorListener; + this.callbacks = callbacks; + } + + @Override + public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(new EmojiPageView(parent.getContext(), emojiEventListener, variationSelectorListener, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(models.get(position)); + } + + @Override + public int getItemCount() { + return models.size(); + } + + @Override + public void onViewAttachedToWindow(@NonNull ViewHolder holder) { + callbacks.onViewHolderAttached(holder.getAdapterPosition(), holder.emojiPageView); + } + + @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 final class ViewHolder extends RecyclerView.ViewHolder { + + private final EmojiPageView emojiPageView; + + ViewHolder(@NonNull EmojiPageView itemView) { + super(itemView); + + emojiPageView = itemView; + + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + + emojiPageView.setLayoutParams(params); + } + + void bind(@NonNull EmojiPageModel model) { + emojiPageView.setModel(model); + } + } + + interface Callbacks { + void onViewHolderAttached(int adapterPosition, EmojiPageView pageView); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java new file mode 100644 index 0000000000..1c8acee6aa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiBottomSheetDialogFragment.java @@ -0,0 +1,268 @@ +package org.thoughtcrime.securesms.reactions.any; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextSwitcher; + +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.shape.CornerFamily; +import com.google.android.material.shape.MaterialShapeDrawable; +import com.google.android.material.shape.ShapeAppearanceModel; +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.EmojiKeyboardProvider; +import org.thoughtcrime.securesms.components.emoji.EmojiPageView; +import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter; +import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.util.ThemeUtil; +import org.thoughtcrime.securesms.util.ViewUtil; + +import static org.thoughtcrime.securesms.R.layout.react_with_any_emoji_tab; + +public final class ReactWithAnyEmojiBottomSheetDialogFragment extends BottomSheetDialogFragment implements EmojiKeyboardProvider.EmojiEventListener, EmojiPageViewGridAdapter.VariationSelectorListener { + + private static final String ARG_MESSAGE_ID = "arg_message_id"; + private static final String ARG_IS_MMS = "arg_is_mms"; + + private ReactWithAnyEmojiViewModel viewModel; + private TextSwitcher categoryLabel; + private ViewPager2 categoryPager; + private ReactWithAnyEmojiAdapter adapter; + private OnPageChanged onPageChanged; + private SparseArray pageArray = new SparseArray<>(); + private Callback callback; + + public static DialogFragment createForMessageRecord(@NonNull MessageRecord messageRecord) { + DialogFragment fragment = new ReactWithAnyEmojiBottomSheetDialogFragment(); + Bundle args = new Bundle(); + + args.putLong(ARG_MESSAGE_ID, messageRecord.getId()); + args.putBoolean(ARG_IS_MMS, messageRecord.isMms()); + fragment.setArguments(args); + + 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_ReactWithAny); + } else { + setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_Signal_Light_BottomSheetDialog_Fixed_ReactWithAny); + } + + super.onCreate(savedInstanceState); + } + + @Override + public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) { + BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState); + ShapeAppearanceModel shapeAppearanceModel = ShapeAppearanceModel.builder() + .setTopLeftCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 8)) + .setTopRightCorner(CornerFamily.ROUNDED, ViewUtil.dpToPx(requireContext(), 8)) + .build(); + MaterialShapeDrawable dialogBackground = new MaterialShapeDrawable(shapeAppearanceModel); + + dialogBackground.setTint(ThemeUtil.getThemedColor(requireContext(), R.attr.dialog_background_color)); + + dialog.getBehavior().addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_EXPANDED) { + ViewCompat.setBackground(bottomSheet, dialogBackground); + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + } + }); + + return dialog; + } + + @Override + public @Nullable View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + return inflater.inflate(R.layout.react_with_any_emoji_bottom_sheet_dialog_fragment, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + initializeViewModel(); + + categoryLabel = view.findViewById(R.id.category_label); + categoryPager = view.findViewById(R.id.category_pager); + + adapter = new ReactWithAnyEmojiAdapter(viewModel.getEmojiPageModels(), this, this, (position, pageView) -> { + pageArray.put(position, pageView); + + if (categoryPager.getCurrentItem() == position) { + updateFocusedRecycler(position); + } + }); + + onPageChanged = new OnPageChanged(); + + categoryPager.setAdapter(adapter); + categoryPager.registerOnPageChangeCallback(onPageChanged); + + int startPateIndex = viewModel.getStartIndex(); + + categoryPager.setCurrentItem(startPateIndex, false); + presentCategoryLabel(viewModel.getCategoryIconAttr(startPateIndex)); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + 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 categoryTabs = (TabLayout) layoutInflater.inflate(R.layout.react_with_any_emoji_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(categoryTabs); + + ViewCompat.setOnApplyWindowInsetsListener(container, (v, insets) -> insets.consumeSystemWindowInsets()); + + new TabLayoutMediator(categoryTabs, categoryPager, (tab, position) -> { + tab.setCustomView(react_with_any_emoji_tab) + .setIcon(ThemeUtil.getThemedDrawable(requireContext(), viewModel.getCategoryIconAttr(position))); + }).attach(); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + categoryPager.unregisterOnPageChangeCallback(onPageChanged); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + + callback.onReactWithAnyEmojiDialogDismissed(); + } + + private void initializeViewModel() { + Bundle args = requireArguments(); + ReactWithAnyEmojiRepository repository = new ReactWithAnyEmojiRepository(requireContext()); + ReactWithAnyEmojiViewModel.Factory factory = new ReactWithAnyEmojiViewModel.Factory(repository, args.getLong(ARG_MESSAGE_ID), args.getBoolean(ARG_IS_MMS)); + + viewModel = ViewModelProviders.of(this, factory).get(ReactWithAnyEmojiViewModel.class); + } + + @Override + public void onEmojiSelected(String emoji) { + viewModel.onEmojiSelected(emoji); + dismiss(); + } + + @Override + public void onKeyEvent(KeyEvent keyEvent) { + } + + @Override + public void onVariationSelectorStateChanged(boolean open) { + } + + private void updateFocusedRecycler(int position) { + for (int i = 0; i < pageArray.size(); i++) { + pageArray.valueAt(i).setRecyclerNestedScrollingEnabled(false); + } + + EmojiPageView toFocus = pageArray.get(position); + if (toFocus != null) { + toFocus.setRecyclerNestedScrollingEnabled(true); + categoryPager.requestLayout(); + } + + presentCategoryLabel(viewModel.getCategoryIconAttr(position)); + } + + private void presentCategoryLabel(@AttrRes int iconAttr) { + switch (iconAttr) { + case R.attr.emoji_category_recent: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__recently_used)); + break; + case R.attr.emoji_category_people: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people)); + break; + case R.attr.emoji_category_nature: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__nature)); + break; + case R.attr.emoji_category_foods: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__food)); + break; + case R.attr.emoji_category_activity: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__activities)); + break; + case R.attr.emoji_category_places: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__places)); + break; + case R.attr.emoji_category_objects: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__objects)); + break; + case R.attr.emoji_category_symbols: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__symbols)); + break; + case R.attr.emoji_category_flags: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__flags)); + break; + case R.attr.emoji_category_emoticons: + categoryLabel.setText(getString(R.string.ReactWithAnyEmojiBottomSheetDialogFragment__emoticons)); + break; + default: + throw new AssertionError(); + } + } + + private class OnPageChanged extends ViewPager2.OnPageChangeCallback { + @Override + public void onPageSelected(int position) { + updateFocusedRecycler(position); + } + } + + public interface Callback { + void onReactWithAnyEmojiDialogDismissed(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java new file mode 100644 index 0000000000..246bb31c17 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.reactions.any; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.annimon.stream.Stream; + +import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; +import org.thoughtcrime.securesms.components.emoji.EmojiUtil; +import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; + +import java.util.LinkedList; +import java.util.List; + +final class ReactWithAnyEmojiRepository { + + private final Context context; + private final RecentEmojiPageModel recentEmojiPageModel; + private final List emojiPageModels; + + ReactWithAnyEmojiRepository(@NonNull Context context) { + this.context = context; + this.recentEmojiPageModel = new RecentEmojiPageModel(context); + this.emojiPageModels = new LinkedList<>(); + + emojiPageModels.add(recentEmojiPageModel); + emojiPageModels.addAll(EmojiUtil.getDisplayPages()); + emojiPageModels.remove(emojiPageModels.size() - 1); + } + + List getEmojiPageModels() { + return emojiPageModels; + } + + void addEmojiToMessage(@NonNull String emoji, long messageId, boolean isMms) { + recentEmojiPageModel.onCodePointSelected(emoji); + + SignalExecutors.BOUNDED.execute(() -> MessageSender.sendNewReaction(context, messageId, isMms, emoji)); + } +} 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 new file mode 100644 index 0000000000..fa95cd6785 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiViewModel.java @@ -0,0 +1,59 @@ +package org.thoughtcrime.securesms.reactions.any; + +import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; + +import java.util.List; + +public final class ReactWithAnyEmojiViewModel extends ViewModel { + + private final ReactWithAnyEmojiRepository repository; + private final long messageId; + private final boolean isMms; + + private ReactWithAnyEmojiViewModel(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) { + this.repository = repository; + this.messageId = messageId; + this.isMms = isMms; + } + + List getEmojiPageModels() { + return repository.getEmojiPageModels(); + } + + int getStartIndex() { + return repository.getEmojiPageModels().get(0).getEmoji().size() == 0 ? 1 : 0; + } + + void onEmojiSelected(@NonNull String emoji) { + repository.addEmojiToMessage(emoji, messageId, isMms); + } + + @AttrRes int getCategoryIconAttr(int position) { + return repository.getEmojiPageModels().get(position).getIconAttr(); + } + + static class Factory implements ViewModelProvider.Factory { + + private final ReactWithAnyEmojiRepository repository; + private final long messageId; + private final boolean isMms; + + Factory(@NonNull ReactWithAnyEmojiRepository repository, long messageId, boolean isMms) { + this.repository = repository; + this.messageId = messageId; + this.isMms = isMms; + } + + @Override + public @NonNull T create(@NonNull Class modelClass) { + //noinspection ConstantConditions + return modelClass.cast(new ReactWithAnyEmojiViewModel(repository, messageId, isMms)); + } + } + +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index afb2fed2e7..88dde0aecb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -59,6 +59,7 @@ public final class FeatureFlags { private static final String PROFILE_FOR_CALLING = "android.profileForCalling"; private static final String CALLING_PIP = "android.callingPip"; private static final String NEW_GROUP_UI = "android.newGroupUI"; + private static final String REACT_WITH_ANY_EMOJI = "android.reactWithAnyEmoji"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -76,7 +77,8 @@ public final class FeatureFlags { REMOTE_DELETE, PROFILE_FOR_CALLING, CALLING_PIP, - NEW_GROUP_UI + NEW_GROUP_UI, + REACT_WITH_ANY_EMOJI ); /** @@ -98,7 +100,8 @@ public final class FeatureFlags { */ private static final Set HOT_SWAPPABLE = Sets.newHashSet( PINS_MEGAPHONE_KILL_SWITCH, - ATTACHMENTS_V3 + ATTACHMENTS_V3, + REACT_WITH_ANY_EMOJI ); /** @@ -247,6 +250,11 @@ public final class FeatureFlags { return getBoolean(NEW_GROUP_UI, false); } + /** React with Any Emoji */ + public static boolean reactWithAnyEmoji() { + return getBoolean(REACT_WITH_ANY_EMOJI, false); + } + /** Only for rendering debug info. */ public static synchronized @NonNull Map getMemoryValues() { return new TreeMap<>(REMOTE_VALUES); diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_angry_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_angry_48.webp deleted file mode 100644 index a3982e3b0f..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_angry_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_dislike_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_dislike_48.webp deleted file mode 100644 index 9874306653..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_dislike_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_haha_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_haha_48.webp deleted file mode 100644 index b935b7a1ab..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_haha_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_like_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_like_48.webp deleted file mode 100644 index 31ce2ae4f1..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_like_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_love_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_love_48.webp deleted file mode 100644 index 8711d7628a..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_love_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_sad_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_sad_48.webp deleted file mode 100644 index a259b431aa..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_sad_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/reaction_scrubber_wow_48.webp b/app/src/main/res/drawable-mdpi/reaction_scrubber_wow_48.webp deleted file mode 100644 index f2a5e54b60..0000000000 Binary files a/app/src/main/res/drawable-mdpi/reaction_scrubber_wow_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_angry_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_angry_48.webp deleted file mode 100644 index 6040982388..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_angry_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_dislike_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_dislike_48.webp deleted file mode 100644 index 989dceb244..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_dislike_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_haha_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_haha_48.webp deleted file mode 100644 index d6021256b1..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_haha_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_like_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_like_48.webp deleted file mode 100644 index 75eadbcecc..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_like_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_love_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_love_48.webp deleted file mode 100644 index bfe2f6dfbb..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_love_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_sad_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_sad_48.webp deleted file mode 100644 index 249ff57626..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_sad_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/reaction_scrubber_wow_48.webp b/app/src/main/res/drawable-xhdpi/reaction_scrubber_wow_48.webp deleted file mode 100644 index d0f49e0aac..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/reaction_scrubber_wow_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_angry_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_angry_48.webp deleted file mode 100644 index 849a3fa39a..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_angry_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_dislike_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_dislike_48.webp deleted file mode 100644 index 2eac134d20..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_dislike_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_haha_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_haha_48.webp deleted file mode 100644 index 1c084eb9e4..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_haha_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_like_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_like_48.webp deleted file mode 100644 index 4883a86302..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_like_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_love_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_love_48.webp deleted file mode 100644 index 5b7f268f0a..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_love_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_sad_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_sad_48.webp deleted file mode 100644 index af7e6f6dfe..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_sad_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_wow_48.webp b/app/src/main/res/drawable-xxhdpi/reaction_scrubber_wow_48.webp deleted file mode 100644 index a994666ca2..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/reaction_scrubber_wow_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_angry_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_angry_48.webp deleted file mode 100644 index d57b985f33..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_angry_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_dislike_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_dislike_48.webp deleted file mode 100644 index 10510812ec..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_dislike_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_haha_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_haha_48.webp deleted file mode 100644 index b2d7dcc15f..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_haha_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_like_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_like_48.webp deleted file mode 100644 index e5898a40eb..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_like_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_love_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_love_48.webp deleted file mode 100644 index 89b950eab6..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_love_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_sad_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_sad_48.webp deleted file mode 100644 index 927773e9b7..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_sad_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_wow_48.webp b/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_wow_48.webp deleted file mode 100644 index 7e0f196ec6..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/reaction_scrubber_wow_48.webp and /dev/null differ diff --git a/app/src/main/res/drawable/ic_any_emoji_32.xml b/app/src/main/res/drawable/ic_any_emoji_32.xml new file mode 100644 index 0000000000..3e9a813a16 --- /dev/null +++ b/app/src/main/res/drawable/ic_any_emoji_32.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/conversation_reaction_scrubber.xml b/app/src/main/res/layout/conversation_reaction_scrubber.xml index f7d7e1bca5..b248aba015 100644 --- a/app/src/main/res/layout/conversation_reaction_scrubber.xml +++ b/app/src/main/res/layout/conversation_reaction_scrubber.xml @@ -49,12 +49,11 @@ app:layout_constraintRight_toRightOf="@id/reaction_3" app:layout_constraintTop_toTopOf="parent" /> - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/react_with_any_emoji_status_fade.xml b/app/src/main/res/layout/react_with_any_emoji_status_fade.xml new file mode 100644 index 0000000000..0ae7aeacf6 --- /dev/null +++ b/app/src/main/res/layout/react_with_any_emoji_status_fade.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/react_with_any_emoji_tab.xml b/app/src/main/res/layout/react_with_any_emoji_tab.xml new file mode 100644 index 0000000000..9b129d7562 --- /dev/null +++ b/app/src/main/res/layout/react_with_any_emoji_tab.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/react_with_any_emoji_tabs.xml b/app/src/main/res/layout/react_with_any_emoji_tabs.xml new file mode 100644 index 0000000000..6f2f5a267e --- /dev/null +++ b/app/src/main/res/layout/react_with_any_emoji_tabs.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml index a465348c79..ea0a02094d 100644 --- a/app/src/main/res/values-v21/themes.xml +++ b/app/src/main/res/values-v21/themes.xml @@ -35,12 +35,12 @@ @color/media_preview_bar_background - - - - - - - + + + + - + + + + + + + +