From b66bc1f8637105a046151cc87f063749453c83a1 Mon Sep 17 00:00:00 2001 From: wch423 Date: Wed, 4 Feb 2026 18:04:34 +0100 Subject: [PATCH] Handle specific case when leaving group; also move floating button to avoid overaapping with system bar --- .../securesms/ConversationActivity.java | 8 +-- .../securesms/ConversationFragment.java | 34 ++++++++--- .../securesms/WebViewActivity.java | 2 +- .../thoughtcrime/securesms/util/ViewUtil.java | 59 +++++++++++++++---- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index f27917989..e6cb30b9d 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -995,10 +995,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if(chatId == DcChat.DC_CHAT_NO_CHAT) throw new IllegalStateException("can't display a conversation for no chat."); dcChat = DcHelper.getContext(context).getChat(chatId); - if (!dcChat.canSend()) { - ViewUtil.applyWindowInsets(findViewById(R.id.root_layout), true, false, true, false); - fragment.handleAdjustBottomLayout(); - } recipient = new Recipient(this, dcChat); glideRequests = GlideApp.with(this); @@ -1010,10 +1006,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (dcChat.canSend()) { composePanel.setVisibility(View.VISIBLE); attachmentManager.setHidden(false); + ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, true); + fragment.handleRemoveBottomInsets(); } else { composePanel.setVisibility(View.GONE); attachmentManager.setHidden(true); hideSoftKeyboard(); + ViewUtil.forceApplyWindowInsets(findViewById(R.id.root_layout), true, false, true, false); + fragment.handleAddBottomInsets(); } } diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java index b8fe5cdfb..1276f7a30 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationFragment.java @@ -108,7 +108,8 @@ public class ConversationFragment extends MessageSelectorFragment public boolean isPaused; private Debouncer markseenDebouncer; private Rpc rpc; - private boolean pendingAdjustBottomLayout; + private boolean pendingAddBottomInsets; + private boolean pendingRemoveBottomInsets; @Override public void onCreate(Bundle icicle) { @@ -161,10 +162,18 @@ public class ConversationFragment extends MessageSelectorFragment // with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be rendered into a texture" list.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - if (pendingAdjustBottomLayout) { + if (pendingAddBottomInsets) { bottomDivider.setVisibility(View.GONE); ViewUtil.applyWindowInsets(list, false, true, false, true); - pendingAdjustBottomLayout = false; + ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, true, true, true, true); + pendingAddBottomInsets = false; + } + + if (pendingRemoveBottomInsets) { + bottomDivider.setVisibility(View.VISIBLE); + ViewUtil.applyWindowInsets(list, false, true, false, false); + ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, true, true, true, false); + pendingRemoveBottomInsets = false; } return view; @@ -205,14 +214,25 @@ public class ConversationFragment extends MessageSelectorFragment } } - public void handleAdjustBottomLayout() { + public void handleAddBottomInsets() { if (bottomDivider != null) { bottomDivider.setVisibility(View.GONE); ViewUtil.applyWindowInsets(list, false, true, false, true); - pendingAdjustBottomLayout = false; + ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, false, false, false, true); + pendingAddBottomInsets = false; + } else { + pendingAddBottomInsets = true; } - else { - pendingAdjustBottomLayout = true; + } + + public void handleRemoveBottomInsets() { + if (bottomDivider != null) { + bottomDivider.setVisibility(View.VISIBLE); + ViewUtil.applyWindowInsets(list, false, true, false, false); + ViewUtil.forceApplyWindowInsetsAsMargin(scrollToBottomButton, false, false, false, false); + pendingRemoveBottomInsets = false; + } else { + pendingRemoveBottomInsets = true; } } diff --git a/src/main/java/org/thoughtcrime/securesms/WebViewActivity.java b/src/main/java/org/thoughtcrime/securesms/WebViewActivity.java index 87094a35d..f6d1ec8fe 100644 --- a/src/main/java/org/thoughtcrime/securesms/WebViewActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WebViewActivity.java @@ -77,7 +77,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity findViewById(R.id.status_bar_background).setBackgroundResource(R.drawable.search_toolbar_shadow); } else { // add padding to avoid content hidden behind system bars - ViewUtil.applyWindowInsets(findViewById(R.id.content_container), true, true, true, true, true); + ViewUtil.applyWindowInsets(findViewById(R.id.content_container), true, true, true, true, true, false); } webView.setWebViewClient(new WebViewClient() { diff --git a/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java b/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java index 91c4d3a6b..c90480836 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/ViewUtil.java @@ -323,7 +323,15 @@ public class ViewUtil { * @param view The view to apply insets to */ public static void applyWindowInsetsAsMargin(@NonNull View view) { - applyWindowInsetsAsMargin(view, true, true, true, true); + applyWindowInsetsAsMargin(view, true, true, true, true, false); + } + + public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) { + applyWindowInsetsAsMargin(view, left, top, right, bottom, false); + } + + public static void forceApplyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) { + applyWindowInsetsAsMargin(view, left, top, right, bottom, true); } /** @@ -337,8 +345,9 @@ public class ViewUtil { * @param top Whether to apply top inset * @param right Whether to apply right inset * @param bottom Whether to apply bottom inset + * @param forceDispatch Whether to force application of insets */ - public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) { + public static void applyWindowInsetsAsMargin(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom, boolean forceDispatch) { // Only enable on API 30+ where WindowInsets APIs work correctly if (!isEdgeToEdgeSupported()) return; @@ -371,10 +380,10 @@ public class ViewUtil { ViewGroup.LayoutParams layoutParams = v.getLayoutParams(); if (layoutParams instanceof ViewGroup.MarginLayoutParams) { ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) layoutParams; - marginParams.leftMargin = baseMarginLeft + insets.left; - marginParams.topMargin = baseMarginTop + insets.top; - marginParams.rightMargin = baseMarginRight + insets.right; - marginParams.bottomMargin = baseMarginBottom + insets.bottom; + marginParams.leftMargin = left ? baseMarginLeft + insets.left : baseMarginLeft; + marginParams.topMargin = top ? baseMarginTop + insets.top : baseMarginTop; + marginParams.rightMargin = right ? baseMarginRight + insets.right : baseMarginRight; + marginParams.bottomMargin = bottom ? baseMarginBottom + insets.bottom : baseMarginBottom; v.setLayoutParams(marginParams); } @@ -383,7 +392,14 @@ public class ViewUtil { // Request the initial insets to be dispatched if the view is attached if (view.isAttachedToWindow()) { - ViewCompat.requestApplyInsets(view); + if (forceDispatch) { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); + if (insets != null) { + ViewCompat.dispatchApplyWindowInsets(view, insets); + } + } else { + ViewCompat.requestApplyInsets(view); + } } } @@ -395,7 +411,7 @@ public class ViewUtil { * @param view The view to apply insets to */ public static void applyWindowInsets(@NonNull View view) { - applyWindowInsets(view, true, true, true, true, false); + applyWindowInsets(view, true, true, true, true, false, false); } /** @@ -410,7 +426,20 @@ public class ViewUtil { * @param bottom Whether to apply bottom inset */ public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) { - applyWindowInsets(view, left, top, right, bottom, false); + applyWindowInsets(view, left, top, right, bottom, false, false); + } + + /** + * Force applying window insets to a view by adding padding to avoid drawing elements behind system bars. + * + * @param view The view to apply insets to + * @param left Whether to apply left inset + * @param top Whether to apply top inset + * @param right Whether to apply right inset + * @param bottom Whether to apply bottom inset + */ + public static void forceApplyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom) { + applyWindowInsets(view, left, top, right, bottom, false, true); } /** @@ -425,8 +454,9 @@ public class ViewUtil { * @param right Whether to apply right inset * @param bottom Whether to apply bottom inset * @param consumeImeInsets Whether to consume IME insets so they don't propagate to child views + * @param forceDispatch Force application of Insets, regardless if system think it shall dispatch */ - public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom, boolean consumeImeInsets) { + public static void applyWindowInsets(@NonNull View view, boolean left, boolean top, boolean right, boolean bottom, boolean consumeImeInsets, boolean forceDispatch) { // Only enable on API 30+ where WindowInsets APIs work correctly if (!isEdgeToEdgeSupported()) return; @@ -469,7 +499,14 @@ public class ViewUtil { // Request the initial insets to be dispatched if the view is attached if (view.isAttachedToWindow()) { - ViewCompat.requestApplyInsets(view); + if (forceDispatch) { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); + if (insets != null) { + ViewCompat.dispatchApplyWindowInsets(view, insets); + } + } else { + ViewCompat.requestApplyInsets(view); + } } }