From 24a875c73a5160a5c39415ab91ad0578974a440e Mon Sep 17 00:00:00 2001 From: Rashad Sookram Date: Thu, 3 Feb 2022 17:06:17 -0500 Subject: [PATCH] Improve showing context menu with keyboard open. --- .../conversation/ConversationFragment.java | 84 ++++++----- .../conversation/ConversationItemSelection.kt | 14 ++ .../ConversationParentFragment.java | 5 + .../ConversationReactionOverlay.java | 133 +++++++++++++----- .../conversation/SelectedConversationModel.kt | 2 + .../mutiselect/MultiselectItemDecoration.kt | 45 +++++- .../thoughtcrime/securesms/util/ViewUtil.java | 9 ++ .../res/animator/reactions_scrubber_hide.xml | 4 +- .../main/res/layout/conversation_activity.xml | 2 +- .../main/res/layout/conversation_fragment.xml | 5 +- .../layout/conversation_reaction_scrubber.xml | 17 +-- .../res/layout/edit_reactions_fragment.xml | 5 +- .../react_with_any_emoji_status_fade.xml | 5 +- app/src/main/res/values-night/dark_colors.xml | 5 +- app/src/main/res/values/colors.xml | 4 +- app/src/main/res/values/integers.xml | 1 - app/src/main/res/values/light_colors.xml | 5 +- 17 files changed, 248 insertions(+), 97 deletions(-) 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 cae208656f..6d3d4bd7fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1308,25 +1308,26 @@ public class ConversationFragment extends LoggingFragment implements Multiselect public interface ConversationFragmentListener extends VoiceNoteMediaControllerOwner { - void setThreadId(long threadId); - void handleReplyMessage(ConversationMessage conversationMessage); - void onMessageActionToolbarOpened(); - void onBottomActionBarVisibilityChanged(int visibility); - void onForwardClicked(); - void onMessageRequest(@NonNull MessageRequestViewModel viewModel); - void handleReaction(@NonNull ConversationMessage conversationMessage, - @NonNull ConversationReactionOverlay.OnActionSelectedListener onActionSelectedListener, - @NonNull SelectedConversationModel selectedConversationModel, - @NonNull ConversationReactionOverlay.OnHideListener onHideListener); - void onCursorChanged(); - void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); - void onVoiceNotePause(@NonNull Uri uri); - void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress); - void onVoiceNoteResume(@NonNull Uri uri, long messageId); - void onVoiceNoteSeekTo(@NonNull Uri uri, double progress); - void onVoiceNotePlaybackSpeedChanged(@NonNull Uri uri, float speed); - void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); - void onUnregisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); + boolean isKeyboardOpen(); + void setThreadId(long threadId); + void handleReplyMessage(ConversationMessage conversationMessage); + void onMessageActionToolbarOpened(); + void onBottomActionBarVisibilityChanged(int visibility); + void onForwardClicked(); + void onMessageRequest(@NonNull MessageRequestViewModel viewModel); + void handleReaction(@NonNull ConversationMessage conversationMessage, + @NonNull ConversationReactionOverlay.OnActionSelectedListener onActionSelectedListener, + @NonNull SelectedConversationModel selectedConversationModel, + @NonNull ConversationReactionOverlay.OnHideListener onHideListener); + void onCursorChanged(); + void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); + void onVoiceNotePause(@NonNull Uri uri); + void onVoiceNotePlay(@NonNull Uri uri, long messageId, double progress); + void onVoiceNoteResume(@NonNull Uri uri, long messageId); + void onVoiceNoteSeekTo(@NonNull Uri uri, double progress); + void onVoiceNotePlaybackSpeedChanged(@NonNull Uri uri, float speed); + void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); + void onUnregisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); } private class ConversationScrollListener extends OnScrollListener { @@ -1460,6 +1461,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect ConversationItem conversationItem = (ConversationItem) itemView; Bitmap bitmap = ConversationItemSelection.snapshotView(conversationItem, list, messageRecord, videoBitmap); + View focusedView = listener.isKeyboardOpen() ? conversationItem.getRootView().findFocus() : null; + final ConversationItemBodyBubble bodyBubble = conversationItem.bodyBubble; SelectedConversationModel selectedConversationModel = new SelectedConversationModel(bitmap, itemView.getX(), @@ -1468,28 +1471,41 @@ public class ConversationFragment extends LoggingFragment implements Multiselect bodyBubble.getY(), bodyBubble.getWidth(), audioUri, - messageRecord.isOutgoing()); + messageRecord.isOutgoing(), + focusedView); bodyBubble.setVisibility(View.INVISIBLE); - listener.handleReaction(item.getConversationMessage(), new ReactionsToolbarListener(item.getConversationMessage()), selectedConversationModel, () -> { - reactionsShade.setVisibility(View.GONE); - list.setLayoutFrozen(false); + ViewUtil.hideKeyboard(requireContext(), conversationItem); - if (selectedConversationModel.getAudioUri() != null) { - listener.onVoiceNoteResume(selectedConversationModel.getAudioUri(), messageRecord.getId()); - } + listener.handleReaction(item.getConversationMessage(), + new ReactionsToolbarListener(item.getConversationMessage()), + selectedConversationModel, + new ConversationReactionOverlay.OnHideListener() { + @Override public void startHide() { + multiselectItemDecoration.hideShade(list); + } - WindowUtil.setLightStatusBarFromTheme(requireActivity()); - clearFocusedItem(); + @Override public void onHide() { + reactionsShade.setVisibility(View.GONE); + list.setLayoutFrozen(false); - if (mp4Holder != null) { - mp4Holder.show(); - mp4Holder.resume(); - } + if (selectedConversationModel.getAudioUri() != null) { + listener.onVoiceNoteResume(selectedConversationModel.getAudioUri(), messageRecord.getId()); + } - bodyBubble.setVisibility(View.VISIBLE); - }); + WindowUtil.setLightStatusBarFromTheme(requireActivity()); + WindowUtil.setLightNavigationBarFromTheme(requireActivity()); + clearFocusedItem(); + + if (mp4Holder != null) { + mp4Holder.show(); + mp4Holder.resume(); + } + + bodyBubble.setVisibility(View.VISIBLE); + } + }); } } else { clearFocusedItem(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt index 1de18c5ef0..0cf9cee408 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSelection.kt @@ -3,10 +3,12 @@ package org.thoughtcrime.securesms.conversation import android.graphics.Bitmap import android.graphics.Path import android.view.View +import android.view.ViewGroup import androidx.core.graphics.applyCanvas import androidx.core.graphics.createBitmap import androidx.core.graphics.withClip import androidx.core.graphics.withTranslation +import androidx.core.view.children import androidx.recyclerview.widget.RecyclerView import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.util.hasNoBubble @@ -76,6 +78,8 @@ object ConversationItemSelection { } } + conversationItem.destroyAllDrawingCaches() + return createBitmap(conversationItem.bodyBubble.width, conversationItem.bodyBubble.height).applyCanvas { if (drawConversationItem) { conversationItem.bodyBubble.draw(this) @@ -98,3 +102,13 @@ object ConversationItemSelection { } } } + +private fun ViewGroup.destroyAllDrawingCaches() { + children.forEach { + it.destroyDrawingCache() + + if (it is ViewGroup) { + it.destroyAllDrawingCaches() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index a09cea8ed7..dd9287ed66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -3791,6 +3791,11 @@ public class ConversationParentFragment extends Fragment }); } + @Override + public boolean isKeyboardOpen() { + return container.isKeyboardOpen(); + } + @Override public void setThreadId(long threadId) { this.threadId = threadId; 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 95db5e5ff7..921beb6e7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.conversation; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.Rect; @@ -14,11 +16,10 @@ import android.util.AttributeSet; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import android.widget.RelativeLayout; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,7 +52,7 @@ import java.util.List; import kotlin.Unit; -public final class ConversationReactionOverlay extends RelativeLayout { +public final class ConversationReactionOverlay extends FrameLayout { private static final Interpolator INTERPOLATOR = new DecelerateInterpolator(); @@ -94,6 +95,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { private int scrubberHorizontalMargin; private int animationEmojiStartDelayFactor; private int statusBarHeight; + private int bottomNavigationBarHeight; private OnReactionSelectedListener onReactionSelectedListener; private OnActionSelectedListener onActionSelectedListener; @@ -168,16 +170,24 @@ public final class ConversationReactionOverlay extends RelativeLayout { if (Build.VERSION.SDK_INT >= 21) { View statusBarBackground = activity.findViewById(android.R.id.statusBarBackground); statusBarHeight = statusBarBackground == null ? 0 : statusBarBackground.getHeight(); + + View navigationBarBackground = activity.findViewById(android.R.id.navigationBarBackground); + bottomNavigationBarHeight = navigationBarBackground == null ? 0 : navigationBarBackground.getHeight(); } else { - statusBarHeight = ViewUtil.getStatusBarHeight(this); + statusBarHeight = ViewUtil.getStatusBarHeight(this); + bottomNavigationBarHeight = ViewUtil.getNavigationBarHeight(this); } - ViewGroup.LayoutParams layoutParams = inputShade.getLayoutParams(); - layoutParams.height = getInputPanelHeight(activity); - inputShade.setLayoutParams(layoutParams); + boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + if (isLandscape) { + bottomNavigationBarHeight = 0; + } toolbarShade.setVisibility(VISIBLE); + toolbarShade.setAlpha(1f); + inputShade.setVisibility(VISIBLE); + inputShade.setAlpha(1f); Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap(); @@ -191,6 +201,11 @@ public final class ConversationReactionOverlay extends RelativeLayout { setVisibility(View.INVISIBLE); + if (Build.VERSION.SDK_INT >= 21) { + this.activity = activity; + updateSystemUiOnShow(activity); + } + ViewKt.doOnLayout(this, v -> { showAfterLayout(activity, conversationMessage, lastSeenDownPoint, isMessageOnLeft); return Unit.INSTANCE; @@ -198,7 +213,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { } private int getInputPanelHeight(@NonNull Activity activity) { - View bottomPanel = activity.findViewById(R.id.bottom_panel); + View bottomPanel = activity.findViewById(R.id.conversation_activity_panel_parent); View emojiDrawer = activity.findViewById(R.id.emoji_drawer); return bottomPanel.getHeight() + (emojiDrawer != null && emojiDrawer.getVisibility() == VISIBLE ? emojiDrawer.getHeight() : 0); @@ -208,6 +223,11 @@ public final class ConversationReactionOverlay extends RelativeLayout { @NonNull ConversationMessage conversationMessage, @NonNull PointF lastSeenDownPoint, boolean isMessageOnLeft) { + LayoutParams layoutParams = (LayoutParams) inputShade.getLayoutParams(); + layoutParams.bottomMargin = bottomNavigationBarHeight; + layoutParams.height = getInputPanelHeight(activity); + inputShade.setLayoutParams(layoutParams); + contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(conversationMessage)); conversationItem.setX(selectedConversationModel.getBubbleX()); @@ -216,7 +236,8 @@ public final class ConversationReactionOverlay extends RelativeLayout { Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap(); boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth(); - int bubbleWidth = selectedConversationModel.getBubbleWidth(); + int overlayHeight = getHeight() - bottomNavigationBarHeight; + int bubbleWidth = selectedConversationModel.getBubbleWidth(); float endX = selectedConversationModel.getBubbleX(); float endY = conversationItem.getY(); @@ -230,7 +251,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { float reactionBarBackgroundY; if (isWideLayout) { - boolean everythingFitsVertically = reactionBarHeight + menuPadding + reactionBarTopPadding + conversationItemSnapshot.getHeight() < getHeight(); + boolean everythingFitsVertically = reactionBarHeight + menuPadding + reactionBarTopPadding + conversationItemSnapshot.getHeight() < overlayHeight; if (everythingFitsVertically) { boolean reactionBarFitsAboveItem = conversationItem.getY() > reactionBarHeight + menuPadding + reactionBarTopPadding; @@ -241,7 +262,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { reactionBarBackgroundY = reactionBarTopPadding; } } else { - float spaceAvailableForItem = getHeight() - reactionBarHeight - menuPadding * 2; + float spaceAvailableForItem = overlayHeight - reactionBarHeight - menuPadding - reactionBarTopPadding; endScale = spaceAvailableForItem / conversationItem.getHeight(); endX += Util.halfOffsetFromScale(conversationItemSnapshot.getWidth(), endScale) * (isMessageOnLeft ? -1 : 1); @@ -249,27 +270,27 @@ public final class ConversationReactionOverlay extends RelativeLayout { reactionBarBackgroundY = reactionBarTopPadding; } } else { - boolean everythingFitsVertically = contextMenu.getMaxHeight() + conversationItemSnapshot.getHeight() + menuPadding + reactionBarHeight + reactionBarTopPadding < getHeight(); + boolean everythingFitsVertically = contextMenu.getMaxHeight() + conversationItemSnapshot.getHeight() + menuPadding + reactionBarHeight + reactionBarTopPadding < overlayHeight; if (everythingFitsVertically) { float bubbleBottom = selectedConversationModel.getItemY() + selectedConversationModel.getBubbleY() + conversationItemSnapshot.getHeight(); - boolean menuFitsBelowItem = bubbleBottom + menuPadding + contextMenu.getMaxHeight() <= getHeight() + statusBarHeight; + boolean menuFitsBelowItem = bubbleBottom + menuPadding + contextMenu.getMaxHeight() <= overlayHeight + statusBarHeight; if (menuFitsBelowItem) { reactionBarBackgroundY = conversationItem.getY() - menuPadding - reactionBarHeight; if (reactionBarBackgroundY < reactionBarTopPadding) { - endY = backgroundView.getHeight() + menuPadding; + endY = backgroundView.getHeight() + menuPadding + reactionBarTopPadding; reactionBarBackgroundY = reactionBarTopPadding; } } else { - endY = getHeight() - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight(); + endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight(); reactionBarBackgroundY = endY - menuPadding - reactionBarHeight; } endApparentTop = endY; - } else if (reactionBarHeight + contextMenu.getMaxHeight() + menuPadding * 2 < getHeight()) { - float spaceAvailableForItem = (float) getHeight() - contextMenu.getMaxHeight() - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding; + } else if (reactionBarHeight + contextMenu.getMaxHeight() + menuPadding * 2 < overlayHeight) { + float spaceAvailableForItem = (float) overlayHeight - contextMenu.getMaxHeight() - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding; endScale = spaceAvailableForItem / conversationItemSnapshot.getHeight(); endX += Util.halfOffsetFromScale(conversationItemSnapshot.getWidth(), endScale) * (isMessageOnLeft ? -1 : 1); @@ -280,11 +301,11 @@ public final class ConversationReactionOverlay extends RelativeLayout { contextMenu.setHeight(contextMenu.getMaxHeight() / 2); int menuHeight = contextMenu.getHeight(); - boolean fitsVertically = menuHeight + conversationItem.getHeight() + menuPadding * 2 + reactionBarHeight + reactionBarTopPadding < getHeight(); + boolean fitsVertically = menuHeight + conversationItem.getHeight() + menuPadding * 2 + reactionBarHeight + reactionBarTopPadding < overlayHeight; if (fitsVertically) { float bubbleBottom = selectedConversationModel.getItemY() + selectedConversationModel.getBubbleY() + conversationItemSnapshot.getHeight(); - boolean menuFitsBelowItem = bubbleBottom + menuPadding + menuHeight <= getHeight() + statusBarHeight; + boolean menuFitsBelowItem = bubbleBottom + menuPadding + menuHeight <= overlayHeight + statusBarHeight; if (menuFitsBelowItem) { reactionBarBackgroundY = conversationItem.getY() - menuPadding - reactionBarHeight; @@ -294,12 +315,12 @@ public final class ConversationReactionOverlay extends RelativeLayout { reactionBarBackgroundY = reactionBarTopPadding; } } else { - endY = getHeight() - menuHeight - menuPadding - conversationItemSnapshot.getHeight(); + endY = overlayHeight - menuHeight - menuPadding - conversationItemSnapshot.getHeight(); reactionBarBackgroundY = endY - reactionBarHeight - menuPadding; } endApparentTop = endY; } else { - float spaceAvailableForItem = (float) getHeight() - menuHeight - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding; + float spaceAvailableForItem = (float) overlayHeight - menuHeight - menuPadding * 2 - reactionBarHeight - reactionBarTopPadding; endScale = spaceAvailableForItem / conversationItemSnapshot.getHeight(); endX += Util.halfOffsetFromScale(conversationItemSnapshot.getWidth(), endScale) * (isMessageOnLeft ? -1 : 1); @@ -316,11 +337,6 @@ public final class ConversationReactionOverlay extends RelativeLayout { hideAnimatorSet = newHideAnimatorSet(); setVisibility(View.VISIBLE); - if (Build.VERSION.SDK_INT >= 21) { - this.activity = activity; - updateSystemUiOnShow(activity); - } - float scrubberX; if (isMessageOnLeft) { scrubberX = scrubberHorizontalMargin; @@ -344,7 +360,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { if (isWideLayout) { float scrubberRight = scrubberX + scrubberWidth; float offsetX = isMessageOnLeft ? scrubberRight + menuPadding : scrubberX - contextMenu.getMaxWidth() - menuPadding; - contextMenu.show((int) offsetX, (int) Math.min(backgroundView.getY(), getHeight() - contextMenu.getMaxHeight())); + contextMenu.show((int) offsetX, (int) Math.min(backgroundView.getY(), overlayHeight - contextMenu.getMaxHeight())); } else { float contentX = selectedConversationModel.getBubbleX(); float offsetX = isMessageOnLeft ? contentX : -contextMenu.getMaxWidth() + contentX + bubbleWidth; @@ -376,6 +392,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { if (!ThemeUtil.isDarkTheme(getContext())) { WindowUtil.clearLightStatusBar(window); + WindowUtil.clearLightNavigationBar(window); } } @@ -422,11 +439,49 @@ public final class ConversationReactionOverlay extends RelativeLayout { itemYAnim.setDuration(duration); hides.add(itemYAnim); + ObjectAnimator toolbarShadeAnim = new ObjectAnimator(); + toolbarShadeAnim.setProperty(View.ALPHA); + toolbarShadeAnim.setFloatValues(0f); + toolbarShadeAnim.setTarget(toolbarShade); + toolbarShadeAnim.setDuration(duration); + hides.add(toolbarShadeAnim); + + ObjectAnimator inputShadeAnim = new ObjectAnimator(); + inputShadeAnim.setProperty(View.ALPHA); + inputShadeAnim.setFloatValues(0f); + inputShadeAnim.setTarget(inputShade); + inputShadeAnim.setDuration(duration); + hides.add(inputShadeAnim); + + if (Build.VERSION.SDK_INT >= 21 && activity != null) { + ValueAnimator statusBarAnim = ValueAnimator.ofArgb(activity.getWindow().getStatusBarColor(), originalStatusBarColor); + statusBarAnim.setDuration(duration); + statusBarAnim.addUpdateListener(animation -> { + WindowUtil.setStatusBarColor(activity.getWindow(), (int) animation.getAnimatedValue()); + }); + hides.add(statusBarAnim); + + ValueAnimator navigationBarAnim = ValueAnimator.ofArgb(activity.getWindow().getStatusBarColor(), originalNavigationBarColor); + navigationBarAnim.setDuration(duration); + navigationBarAnim.addUpdateListener(animation -> { + WindowUtil.setNavigationBarColor(activity.getWindow(), (int) animation.getAnimatedValue()); + }); + hides.add(navigationBarAnim); + } + hideAnimatorSet.playTogether(hides); revealAnimatorSet.end(); hideAnimatorSet.start(); + if (onHideListener != null) { + onHideListener.startHide(); + } + + if (selectedConversationModel.getFocusedView() != null) { + ViewUtil.focusAndShowKeyboard(selectedConversationModel.getFocusedView()); + } + hideAnimatorSet.addListener(new AnimationCompleteListener() { @Override public void onAnimationEnd(Animator animation) { hideAnimatorSet.removeListener(this); @@ -435,8 +490,6 @@ public final class ConversationReactionOverlay extends RelativeLayout { inputShade.setVisibility(INVISIBLE); if (Build.VERSION.SDK_INT >= 21 && activity != null) { - WindowUtil.setStatusBarColor(activity.getWindow(), originalStatusBarColor); - WindowUtil.setNavigationBarColor(activity.getWindow(), originalNavigationBarColor); activity = null; } @@ -729,13 +782,21 @@ public final class ConversationReactionOverlay extends RelativeLayout { } private void handleActionItemClicked(@NonNull Action action) { - hideInternal(() -> { - if (onHideListener != null) { - onHideListener.onHide(); + hideInternal(new OnHideListener() { + @Override public void startHide() { + if (onHideListener != null) { + onHideListener.startHide(); + } } - if (onActionSelectedListener != null) { - onActionSelectedListener.onActionSelected(action); + @Override public void onHide() { + if (onHideListener != null) { + onHideListener.onHide(); + } + + if (onActionSelectedListener != null) { + onActionSelectedListener.onActionSelected(action); + } } }); } @@ -749,7 +810,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { .mapIndexed((idx, v) -> { Animator anim = AnimatorInflaterCompat.loadAnimator(getContext(), R.animator.reactions_scrubber_reveal); anim.setTarget(v); - anim.setStartDelay(revealOffset + idx * animationEmojiStartDelayFactor); + anim.setStartDelay(idx * animationEmojiStartDelayFactor); return anim; }) .toList(); @@ -773,7 +834,6 @@ public final class ConversationReactionOverlay extends RelativeLayout { .mapIndexed((idx, v) -> { Animator anim = AnimatorInflaterCompat.loadAnimator(getContext(), R.animator.reactions_scrubber_hide); anim.setTarget(v); - anim.setStartDelay(idx * animationEmojiStartDelayFactor); return anim; }) .toList(); @@ -812,6 +872,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { } public interface OnHideListener { + void startHide(); void onHide(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt index 0369f1185b..64a36e08cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/SelectedConversationModel.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation import android.graphics.Bitmap import android.net.Uri +import android.view.View /** * Contains information on a single selected conversation item. This is used when transitioning @@ -16,4 +17,5 @@ data class SelectedConversationModel( val bubbleWidth: Int, val audioUri: Uri? = null, val isOutgoing: Boolean, + val focusedView: View?, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt index 76f220d32d..0ef7c71e23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/MultiselectItemDecoration.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.conversation.mutiselect +import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.Context import android.graphics.Bitmap @@ -12,6 +13,7 @@ import android.graphics.Region import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources +import androidx.core.animation.doOnEnd import androidx.core.content.ContextCompat import androidx.core.view.children import androidx.core.view.forEach @@ -54,6 +56,7 @@ class MultiselectItemDecoration( private val selectedParts: MutableSet = mutableSetOf() private var enterExitAnimation: ValueAnimator? = null + private var hideShadeAnimation: ValueAnimator? = null private val multiselectPartAnimatorMap: MutableMap = mutableMapOf() private var checkedBitmap: Bitmap? = null @@ -77,7 +80,10 @@ class MultiselectItemDecoration( checkedBitmap = null } - private val shadeColor = ContextCompat.getColor(context, R.color.reactions_screen_shade_color) + private val darkShadeColor = ContextCompat.getColor(context, R.color.reactions_screen_dark_shade_color) + private val lightShadeColor = ContextCompat.getColor(context, R.color.reactions_screen_light_shade_color) + + private val argbEvaluator = ArgbEvaluator() private val unselectedPaint = Paint().apply { isAntiAlias = true @@ -371,7 +377,7 @@ class MultiselectItemDecoration( } canvas.clipPath(path) - canvas.drawColor(shadeColor) + canvas.drawShade() canvas.restore() } } @@ -389,11 +395,39 @@ class MultiselectItemDecoration( } canvas.clipPath(path, Region.Op.DIFFERENCE) - canvas.drawColor(shadeColor) + canvas.drawShade() canvas.restore() } } + private fun Canvas.drawShade() { + val progress = hideShadeAnimation?.animatedValue as? Float + if (progress == null) { + drawColor(lightShadeColor) + drawColor(darkShadeColor) + return + } + + drawColor(argbEvaluator.evaluate(progress, lightShadeColor, Color.TRANSPARENT) as Int) + drawColor(argbEvaluator.evaluate(progress, darkShadeColor, Color.TRANSPARENT) as Int) + } + + fun hideShade(list: RecyclerView) { + hideShadeAnimation = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 150L + + addUpdateListener { + invalidateIfAnimatorsAreRunning(list) + } + + doOnEnd { + hideShadeAnimation = null + } + + start() + } + } + private fun isInitialAnimation(): Boolean { return (enterExitAnimation?.animatedFraction ?: 0f) < 1f } @@ -441,7 +475,10 @@ class MultiselectItemDecoration( } private fun invalidateIfAnimatorsAreRunning(parent: RecyclerView) { - if (enterExitAnimation?.isRunning == true || multiselectPartAnimatorMap.values.any { it.isRunning }) { + if (enterExitAnimation?.isRunning == true || + multiselectPartAnimatorMap.values.any { it.isRunning } || + hideShadeAnimation?.isRunning == true + ) { parent.invalidate() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java index a74cccdda9..d8d0e76ef4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java @@ -341,6 +341,15 @@ public final class ViewUtil { return result; } + public static int getNavigationBarHeight(@NonNull View view) { + int result = 0; + int resourceId = view.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = view.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + public static void hideKeyboard(@NonNull Context context, @NonNull View view) { InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); diff --git a/app/src/main/res/animator/reactions_scrubber_hide.xml b/app/src/main/res/animator/reactions_scrubber_hide.xml index bfaf01ea4f..0a5fc05468 100644 --- a/app/src/main/res/animator/reactions_scrubber_hide.xml +++ b/app/src/main/res/animator/reactions_scrubber_hide.xml @@ -1,14 +1,14 @@ - diff --git a/app/src/main/res/layout/conversation_reaction_scrubber.xml b/app/src/main/res/layout/conversation_reaction_scrubber.xml index e147e8e62f..565b7f4595 100644 --- a/app/src/main/res/layout/conversation_reaction_scrubber.xml +++ b/app/src/main/res/layout/conversation_reaction_scrubber.xml @@ -6,8 +6,8 @@ android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="@+id/status_bar_guideline" - app:layout_constraintBottom_toBottomOf="@+id/navigation_bar_guideline" app:layout_constraintStart_toStartOf="@+id/parent_start_guideline" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/parent_end_guideline" android:elevation="1000dp" android:visibility="gone" @@ -17,22 +17,23 @@ android:id="@+id/dropdown_anchor" android:layout_width="0dp" android:layout_height="0dp" - android:layout_alignParentLeft="true" + android:layout_gravity="left" tools:ignore="RtlHardcoded" /> - + android:background="@color/reactions_screen_light_shade_color" + android:foreground="@color/reactions_screen_dark_shade_color" /> - + android:layout_gravity="bottom" + android:background="@color/reactions_screen_light_shade_color" + android:foreground="@color/reactions_screen_dark_shade_color" /> - - \ No newline at end of file diff --git a/app/src/main/res/values-night/dark_colors.xml b/app/src/main/res/values-night/dark_colors.xml index a698c35a49..69e4ce63c2 100644 --- a/app/src/main/res/values-night/dark_colors.xml +++ b/app/src/main/res/values-night/dark_colors.xml @@ -126,8 +126,9 @@ @color/core_grey_35 @color/core_grey_15 - @color/transparent_black_60 - #070707 + @color/transparent_black_70 + #df5e5e5e + #191919 @color/core_grey_80 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index cae7602a62..ae78249641 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -14,6 +14,7 @@ #66000000 #80000000 #99000000 + #b3000000 #CC000000 #e6000000 @@ -26,12 +27,13 @@ #66ffffff #99ffffff #ccffffff + #dfffffff #e6ffffff #f3ffffff #32000000 - @color/core_grey_60 + #4d4d4d #400099cc #ffffffff diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml index 3414bc7e9e..a607429b33 100644 --- a/app/src/main/res/values/integers.xml +++ b/app/src/main/res/values/integers.xml @@ -3,6 +3,5 @@ 200 100 150 - 380 10 \ No newline at end of file diff --git a/app/src/main/res/values/light_colors.xml b/app/src/main/res/values/light_colors.xml index 4699c24d0f..c3edb0e4fb 100644 --- a/app/src/main/res/values/light_colors.xml +++ b/app/src/main/res/values/light_colors.xml @@ -126,8 +126,9 @@ @color/core_grey_60 @color/core_grey_75 - @color/transparent_black_50 - #808080 + @color/transparent_black_70 + @color/transparent_white_87 + #4d4d4d @color/core_grey_02