Improve showing context menu with keyboard open.
This commit is contained in:
parent
f0414922be
commit
24a875c73a
17 changed files with 248 additions and 97 deletions
|
@ -1308,6 +1308,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
|
||||
|
||||
public interface ConversationFragmentListener extends VoiceNoteMediaControllerOwner {
|
||||
boolean isKeyboardOpen();
|
||||
void setThreadId(long threadId);
|
||||
void handleReplyMessage(ConversationMessage conversationMessage);
|
||||
void onMessageActionToolbarOpened();
|
||||
|
@ -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,11 +1471,22 @@ 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, () -> {
|
||||
ViewUtil.hideKeyboard(requireContext(), conversationItem);
|
||||
|
||||
listener.handleReaction(item.getConversationMessage(),
|
||||
new ReactionsToolbarListener(item.getConversationMessage()),
|
||||
selectedConversationModel,
|
||||
new ConversationReactionOverlay.OnHideListener() {
|
||||
@Override public void startHide() {
|
||||
multiselectItemDecoration.hideShade(list);
|
||||
}
|
||||
|
||||
@Override public void onHide() {
|
||||
reactionsShade.setVisibility(View.GONE);
|
||||
list.setLayoutFrozen(false);
|
||||
|
||||
|
@ -1481,6 +1495,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
}
|
||||
|
||||
WindowUtil.setLightStatusBarFromTheme(requireActivity());
|
||||
WindowUtil.setLightNavigationBarFromTheme(requireActivity());
|
||||
clearFocusedItem();
|
||||
|
||||
if (mp4Holder != null) {
|
||||
|
@ -1489,6 +1504,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
|||
}
|
||||
|
||||
bodyBubble.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
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,6 +236,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
|||
Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap();
|
||||
boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth();
|
||||
|
||||
int overlayHeight = getHeight() - bottomNavigationBarHeight;
|
||||
int bubbleWidth = selectedConversationModel.getBubbleWidth();
|
||||
|
||||
float endX = selectedConversationModel.getBubbleX();
|
||||
|
@ -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,7 +782,14 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
|||
}
|
||||
|
||||
private void handleActionItemClicked(@NonNull Action action) {
|
||||
hideInternal(() -> {
|
||||
hideInternal(new OnHideListener() {
|
||||
@Override public void startHide() {
|
||||
if (onHideListener != null) {
|
||||
onHideListener.startHide();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onHide() {
|
||||
if (onHideListener != null) {
|
||||
onHideListener.onHide();
|
||||
}
|
||||
|
@ -737,6 +797,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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?,
|
||||
)
|
||||
|
|
|
@ -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<MultiselectPart> = mutableSetOf()
|
||||
private var enterExitAnimation: ValueAnimator? = null
|
||||
private var hideShadeAnimation: ValueAnimator? = null
|
||||
private val multiselectPartAnimatorMap: MutableMap<MultiselectPart, ValueAnimator> = 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<objectAnimator
|
||||
android:duration="@integer/reaction_scrubber_reveal_emoji_duration"
|
||||
android:duration="@integer/reaction_scrubber_hide_duration"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:propertyName="translationY"
|
||||
android:valueTo="@dimen/reaction_scrubber_anim_start_translation_y"
|
||||
android:valueFrom="@dimen/reaction_scrubber_anim_end_translation_y" />
|
||||
|
||||
<objectAnimator
|
||||
android:duration="@integer/reaction_scrubber_reveal_emoji_duration"
|
||||
android:duration="@integer/reaction_scrubber_hide_duration"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:propertyName="alpha"
|
||||
android:valueTo="0"
|
||||
|
|
|
@ -261,7 +261,7 @@
|
|||
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_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/parent_start_guideline"
|
||||
app:layout_constraintEnd_toEndOf="@+id/parent_end_guideline"
|
||||
android:inflatedId="@+id/conversation_reaction_scrubber"
|
||||
|
|
|
@ -27,11 +27,12 @@
|
|||
android:overScrollMode="ifContentScrolls"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
<FrameLayout
|
||||
android:id="@+id/reactions_shade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
android:background="@color/reactions_screen_light_shade_color"
|
||||
android:foreground="@color/reactions_screen_dark_shade_color"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintTop_toBottomOf="@android:id/list" />
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<View
|
||||
<FrameLayout
|
||||
android:id="@+id/toolbar_shade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
android:layout_alignParentTop="true" />
|
||||
android:background="@color/reactions_screen_light_shade_color"
|
||||
android:foreground="@color/reactions_screen_dark_shade_color" />
|
||||
|
||||
<View
|
||||
<FrameLayout
|
||||
android:id="@+id/input_shade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
android:layout_alignParentBottom="true" />
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@color/reactions_screen_light_shade_color"
|
||||
android:foreground="@color/reactions_screen_dark_shade_color" />
|
||||
|
||||
<View
|
||||
android:id="@+id/conversation_item"
|
||||
|
|
|
@ -58,12 +58,13 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/edit_reactions_fragment_scrubber" />
|
||||
|
||||
<View
|
||||
<FrameLayout
|
||||
android:id="@+id/edit_reactions_fragment_reaction_mask"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:alpha="0"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
android:background="@color/reactions_screen_light_shade_color"
|
||||
android:foreground="@color/reactions_screen_dark_shade_color"
|
||||
app:elevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/reactions_screen_shade_color"
|
||||
android:background="@color/reactions_screen_light_shade_color"
|
||||
android:foreground="@color/reactions_screen_dark_shade_color"
|
||||
android:fitsSystemWindows="true" />
|
|
@ -126,8 +126,9 @@
|
|||
|
||||
<color name="reactions_pill_text_color">@color/core_grey_35</color>
|
||||
<color name="reactions_pill_selected_text_color">@color/core_grey_15</color>
|
||||
<color name="reactions_screen_shade_color">@color/transparent_black_60</color>
|
||||
<color name="reactions_status_bar_shade">#070707</color>
|
||||
<color name="reactions_screen_dark_shade_color">@color/transparent_black_70</color>
|
||||
<color name="reactions_screen_light_shade_color">#df5e5e5e</color>
|
||||
<color name="reactions_status_bar_shade">#191919</color>
|
||||
|
||||
<color name="recipient_contact_button_color">@color/core_grey_80</color>
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<color name="transparent_black_40">#66000000</color>
|
||||
<color name="transparent_black_50">#80000000</color>
|
||||
<color name="transparent_black_60">#99000000</color>
|
||||
<color name="transparent_black_70">#b3000000</color>
|
||||
<color name="transparent_black_80">#CC000000</color>
|
||||
<color name="transparent_black_90">#e6000000</color>
|
||||
|
||||
|
@ -26,12 +27,13 @@
|
|||
<color name="transparent_white_40">#66ffffff</color>
|
||||
<color name="transparent_white_60">#99ffffff</color>
|
||||
<color name="transparent_white_80">#ccffffff</color>
|
||||
<color name="transparent_white_87">#dfffffff</color>
|
||||
<color name="transparent_white_90">#e6ffffff</color>
|
||||
<color name="transparent_white_95">#f3ffffff</color>
|
||||
|
||||
<color name="conversation_compose_divider">#32000000</color>
|
||||
|
||||
<color name="conversation_item_selected_system_ui">@color/core_grey_60</color>
|
||||
<color name="conversation_item_selected_system_ui">#4d4d4d</color>
|
||||
<color name="touch_highlight">#400099cc</color>
|
||||
|
||||
<color name="device_link_item_background_light">#ffffffff</color>
|
||||
|
|
|
@ -3,6 +3,5 @@
|
|||
<integer name="reaction_scrubber_reveal_duration">200</integer>
|
||||
<integer name="reaction_scrubber_reveal_offset">100</integer>
|
||||
<integer name="reaction_scrubber_hide_duration">150</integer>
|
||||
<integer name="reaction_scrubber_reveal_emoji_duration">380</integer>
|
||||
<integer name="reaction_scrubber_emoji_reveal_duration_start_delay_factor">10</integer>
|
||||
</resources>
|
|
@ -126,8 +126,9 @@
|
|||
|
||||
<color name="reactions_pill_text_color">@color/core_grey_60</color>
|
||||
<color name="reactions_pill_selected_text_color">@color/core_grey_75</color>
|
||||
<color name="reactions_screen_shade_color">@color/transparent_black_50</color>
|
||||
<color name="reactions_status_bar_shade">#808080</color>
|
||||
<color name="reactions_screen_dark_shade_color">@color/transparent_black_70</color>
|
||||
<color name="reactions_screen_light_shade_color">@color/transparent_white_87</color>
|
||||
<color name="reactions_status_bar_shade">#4d4d4d</color>
|
||||
|
||||
<color name="recipient_contact_button_color">@color/core_grey_02</color>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue