mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Improve showing context menu with keyboard open.
This commit is contained in:
@@ -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<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> 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<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> 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();
|
||||
|
||||
@@ -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);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user