More edge-to-edge support.

This commit is contained in:
Greyson Parrelli
2026-07-27 12:12:35 -04:00
committed by Michelle Tang
parent a57572cf64
commit bd5c60d442
57 changed files with 389 additions and 363 deletions
@@ -974,7 +974,6 @@ class MainActivity :
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
setStatusBarColor = {},
lifecycleOwner = lifecycleOwner
).attach(recyclerView)
}
@@ -984,7 +983,6 @@ class MainActivity :
activity = this,
views = listOf(chatFolders),
viewStubs = listOf(),
setStatusBarColor = {},
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
@@ -63,6 +63,7 @@ class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragme
val tabLayout: ControllableTabLayout = view.findViewById(R.id.text_avatar_creation_tabs)
val doneButton: View = view.findViewById(R.id.text_avatar_creation_done)
val keyboardAwareLayout: KeyboardAwareLinearLayout = view.findViewById(R.id.keyboard_aware_layout)
keyboardAwareLayout.setInsetPaddingMode(KeyboardAwareLinearLayout.InsetPaddingMode.SAFE_AREA)
withRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_content)
withoutRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_content_hidden_recycler)
@@ -21,6 +21,7 @@ import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.InputAwareLayout
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
@@ -94,6 +95,7 @@ class GiftFlowConfirmationFragment :
.create()
inputAwareLayout = requireView().findViewById(R.id.input_aware_layout)
inputAwareLayout.setInsetPaddingMode(KeyboardAwareLinearLayout.InsetPaddingMode.KEYBOARD_AND_NAVIGATION_BAR)
emojiKeyboard = requireView().findViewById(R.id.emoji_drawer)
emojiKeyboard.setFragmentManager(childFragmentManager)
@@ -50,8 +50,7 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
return Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(scrollShadow),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = {}
lifecycleOwner = viewLifecycleOwner
)
}
@@ -11,7 +11,9 @@ import android.view.WindowManager
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -43,6 +45,7 @@ import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeDialogFragment
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.ui.rememberIsSplitPane
import org.signal.core.util.BreakIteratorCompat
import org.thoughtcrime.securesms.R
@@ -68,6 +71,7 @@ class EditCallLinkNameDialogFragment : ComposeDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
dialog.window?.enableEdgeToEdge()
return dialog
}
@@ -143,7 +147,12 @@ private fun EditCallLinkNameScreen(
val focusRequester = remember { FocusRequester() }
val breakIterator = remember { BreakIteratorCompat.getInstance() }
Surface(modifier = Modifier.padding(paddingValues)) {
Surface(
modifier = Modifier
.padding(paddingValues)
.consumeWindowInsets(paddingValues)
.imePadding()
) {
Column(
modifier = Modifier
.padding(
@@ -8,8 +8,6 @@ package org.thoughtcrime.securesms.chats
import android.os.Bundle
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -124,8 +122,6 @@ private fun MessageDetailsEntry(route: MainNavigationDetailLocation.Chats.Messag
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.statusBarsPadding()
.navigationBarsPadding()
)
}
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -12,7 +13,9 @@ import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import org.signal.core.ui.WindowExtensionsKt;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter;
import org.thoughtcrime.securesms.util.WindowUtil;
/**
@@ -30,6 +33,15 @@ public abstract class FullScreenDialogFragment extends DialogFragment {
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen);
}
@Override
public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
if (dialog.getWindow() != null) {
WindowExtensionsKt.enableEdgeToEdge(dialog.getWindow());
}
return dialog;
}
@Override
public final @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.full_screen_dialog_fragment, container, false);
@@ -41,6 +53,8 @@ public abstract class FullScreenDialogFragment extends DialogFragment {
}
toolbar.setNavigationOnClickListener(v -> onNavigateUp());
SystemWindowInsetsSetter.attach(view, getViewLifecycleOwner());
return view;
}
@@ -17,40 +17,37 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Surface;
import android.view.View;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.signal.core.util.ServiceUtil;
import org.signal.core.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
/**
* LinearLayout that, when a view container, will report back when it thinks a soft keyboard
* has been opened and what its height would be.
* has been opened and what its height would be. Driven by {@link WindowInsetsCompat.Type#ime()}
* insets, so it requires an edge-to-edge host window (all activity and dialog windows in the app
* are edge-to-edge). The reported height excludes the navigation bar, matching what a custom
* keyboard sitting above a navigation-bar-padded container should be sized to.
*/
public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
private static final String TAG = Log.tag(KeyboardAwareLinearLayout.class);
private static final long KEYBOARD_DEBOUNCE = 150;
private final Rect rect = new Rect();
private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>();
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
private final DisplayMetrics displayMetrics = new DisplayMetrics();
private final int minKeyboardSize;
private final int minCustomKeyboardSize;
@@ -58,14 +55,14 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
private final int minCustomKeyboardTopMarginPortrait;
private final int minCustomKeyboardTopMarginLandscape;
private final int minCustomKeyboardTopMarginLandscapeBubble;
private final int statusBarHeight;
private int viewInset;
private boolean keyboardOpen = false;
private int rotation = 0;
private boolean isBubble = false;
private long openedAt = 0;
private int lastKeyboardHeight;
private boolean lastKeyboardVisible;
private InsetPaddingMode insetPaddingMode = InsetPaddingMode.NONE;
public KeyboardAwareLinearLayout(Context context) {
this(context, null);
@@ -83,48 +80,71 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
minCustomKeyboardTopMarginPortrait = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait);
minCustomKeyboardTopMarginLandscape = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_portrait);
minCustomKeyboardTopMarginLandscapeBubble = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin_landscape_bubble);
statusBarHeight = ViewUtil.getStatusBarHeight(this);
viewInset = getViewInset();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updateRotation();
updateKeyboardState();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewCompat.setOnApplyWindowInsetsListener(this, (view, insets) -> {
Insets ime = insets.getInsets(WindowInsetsCompat.Type.ime());
Insets nav = insets.getInsets(WindowInsetsCompat.Type.navigationBars());
Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
lastKeyboardHeight = Math.max(0, ime.bottom - nav.bottom);
lastKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
switch (insetPaddingMode) {
case NONE:
break;
case KEYBOARD:
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), lastKeyboardVisible ? lastKeyboardHeight : 0);
break;
case KEYBOARD_AND_NAVIGATION_BAR:
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), Math.max(nav.bottom, ime.bottom));
break;
case SAFE_AREA:
setPadding(bars.left, bars.top, bars.right, Math.max(bars.bottom, ime.bottom));
break;
}
updateKeyboardState();
return insets;
});
}
public void setIsBubble(boolean isBubble) {
this.isBubble = isBubble;
}
private void updateRotation() {
int oldRotation = rotation;
rotation = getDeviceRotation();
if (oldRotation != rotation) {
Log.i(TAG, "rotation changed");
onKeyboardClose();
}
/**
* Opt-in self-padding, replacing the window resize behavior these layouts relied on before the
* host windows went edge-to-edge. [InsetPaddingMode.NONE] keeps the layout as a pure keyboard
* event source (e.g. when a parent container already manages insets).
*/
public void setInsetPaddingMode(@NonNull InsetPaddingMode mode) {
this.insetPaddingMode = mode;
ViewCompat.requestApplyInsets(this);
}
public enum InsetPaddingMode {
/** Apply no padding; the layout only reports keyboard events. */
NONE,
/** Pad the bottom by the keyboard height above the navigation bar (use inside a container that already clears the navigation bar). */
KEYBOARD,
/** Pad the bottom by the keyboard or navigation bar, whichever is larger. */
KEYBOARD_AND_NAVIGATION_BAR,
/** Pad all sides by the system bars, and the bottom by the keyboard when it is taller. */
SAFE_AREA
}
private void updateKeyboardState() {
if (viewInset == 0) viewInset = getViewInset();
getWindowVisibleDisplayFrame(rect);
final int availableHeight = getAvailableHeight();
final int keyboardHeight = availableHeight - rect.bottom;
if (keyboardHeight > minKeyboardSize) {
if (getKeyboardHeight() != keyboardHeight) {
if (lastKeyboardVisible && lastKeyboardHeight > minKeyboardSize) {
if (getKeyboardHeight() != lastKeyboardHeight) {
if (isLandscape()) {
setKeyboardLandscapeHeight(keyboardHeight);
setKeyboardLandscapeHeight(lastKeyboardHeight);
} else {
setKeyboardPortraitHeight(keyboardHeight);
setKeyboardPortraitHeight(lastKeyboardHeight);
}
}
if (!keyboardOpen) {
onKeyboardOpen(keyboardHeight);
onKeyboardOpen(lastKeyboardHeight);
}
} else if (keyboardOpen) {
onKeyboardClose();
@@ -134,53 +154,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
rotation = getDeviceRotation();
if (getRootWindowInsets() != null) {
int bottomInset;
WindowInsets windowInsets = getRootWindowInsets();
if (Build.VERSION.SDK_INT >= 30) {
bottomInset = windowInsets.getInsets(WindowInsets.Type.navigationBars()).bottom;
} else {
bottomInset = windowInsets.getStableInsetBottom();
}
if (bottomInset != 0 && (viewInset == 0 || viewInset == statusBarHeight)) {
Log.i(TAG, "Updating view inset based on WindowInsets. viewInset: " + viewInset + " windowInset: " + bottomInset);
viewInset = bottomInset;
}
}
}
private int getViewInset() {
try {
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
attachInfoField.setAccessible(true);
Object attachInfo = attachInfoField.get(this);
if (attachInfo != null) {
Field stableInsetsField = attachInfo.getClass().getDeclaredField("mStableInsets");
stableInsetsField.setAccessible(true);
Rect insets = (Rect) stableInsetsField.get(attachInfo);
if (insets != null) {
return insets.bottom;
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// Do nothing
}
return statusBarHeight;
}
private int getAvailableHeight() {
final int availableHeight = this.getRootView().getHeight() - viewInset;
final int availableWidth = this.getRootView().getWidth();
if (isLandscape() && availableHeight > availableWidth) {
//noinspection SuspiciousNameCombination
return availableWidth;
}
return availableHeight;
ViewCompat.requestApplyInsets(this);
}
protected void onKeyboardOpen(int keyboardHeight) {
@@ -213,21 +187,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
public boolean isLandscape() {
int rotation = getDeviceRotation();
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
}
private int getDeviceRotation() {
if (isInEditMode()) {
return Surface.ROTATION_0;
}
if (Build.VERSION.SDK_INT >= 30) {
getContext().getDisplay().getRealMetrics(displayMetrics);
} else {
ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRealMetrics(displayMetrics);
}
return displayMetrics.widthPixels > displayMetrics.heightPixels ? Surface.ROTATION_90 : Surface.ROTATION_0;
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
private int getKeyboardLandscapeHeight() {
@@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.view.WindowManager
import androidx.annotation.LayoutRes
import androidx.fragment.app.DialogFragment
import org.signal.core.ui.enableEdgeToEdge
import org.thoughtcrime.securesms.R
/**
@@ -38,6 +39,7 @@ abstract class KeyboardEntryDialogFragment(@LayoutRes contentLayoutId: Int) :
}
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.window?.enableEdgeToEdge()
return dialog
}
@@ -47,6 +49,7 @@ abstract class KeyboardEntryDialogFragment(@LayoutRes contentLayoutId: Int) :
val view = super.onCreateView(inflater, container, savedInstanceState)
return if (view is KeyboardAwareLinearLayout) {
view.setInsetPaddingMode(KeyboardAwareLinearLayout.InsetPaddingMode.SAFE_AREA)
view.addOnKeyboardShownListener(this)
view.addOnKeyboardHiddenListener(this)
view
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.components
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.ui.initializeScreenshotSecurity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.fragments.findListener
@@ -41,6 +43,12 @@ abstract class WrapperDialogFragment : DialogFragment(R.layout.fragment_containe
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
@@ -14,8 +14,6 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -88,15 +86,17 @@ abstract class DSLSettingsFragment(
}
/**
* Activity windows are all edge-to-edge (enabled centrally in [org.thoughtcrime.securesms.BaseActivity]),
* so the toolbar tint must extend behind the (transparent) status bar and the list must scroll clear of
* the navigation bar. Skipped for dialog windows (not edge-to-edge) and for fragments embedded directly in
* All host windows are edge-to-edge — activities centrally via [org.thoughtcrime.securesms.BaseActivity],
* and the full-screen dialog hosts ([org.thoughtcrime.securesms.components.WrapperDialogFragment],
* [org.thoughtcrime.securesms.stories.settings.create.CreateStoryFlowDialogFragment]) via their own
* `enableEdgeToEdge` calls — so the toolbar tint must extend behind the (transparent) status bar and the
* list must scroll clear of the navigation bar. Skipped only for fragments embedded directly in
* [MainActivity]'s Compose scaffolding, which insets those containers itself — except the
* conversation-settings detail entry ([ConversationSettingsNavHostFragment]), which leaves insets to these
* fragments so the toolbar scroll tint can reach behind the status bar.
*/
private fun applyEdgeToEdgeInsets() {
if (isDialogHosted() || isInsetByMainActivityHost()) {
if (isInsetByMainActivityHost()) {
return
}
@@ -111,17 +111,6 @@ abstract class DSLSettingsFragment(
}
}
private fun isDialogHosted(): Boolean {
var fragment: Fragment? = this
while (fragment != null) {
if (fragment is DialogFragment && fragment.showsDialog) {
return true
}
fragment = fragment.parentFragment
}
return false
}
private fun isInsetByMainActivityHost(): Boolean {
if (activity !is MainActivity) {
return false
@@ -137,8 +126,7 @@ abstract class DSLSettingsFragment(
return Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(toolbar),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = {}
lifecycleOwner = viewLifecycleOwner
)
}
@@ -114,9 +114,9 @@ class DonateToSignalFragment :
}
override fun getMaterial3OnScrollHelper(toolbar: Toolbar?): Material3OnScrollHelper {
return object : Material3OnScrollHelper(activity = requireActivity(), views = listOf(toolbar!!), lifecycleOwner = viewLifecycleOwner, setStatusBarColor = {}) {
override val activeColorSet: ColorSet = ColorSet(R.color.transparent, CoreUiR.color.signal_colorBackground)
override val inactiveColorSet: ColorSet = ColorSet(R.color.transparent, CoreUiR.color.signal_colorBackground)
return object : Material3OnScrollHelper(activity = requireActivity(), views = listOf(toolbar!!), lifecycleOwner = viewLifecycleOwner) {
override val activeColorSet: ColorSet = ColorSet(R.color.transparent)
override val inactiveColorSet: ColorSet = ColorSet(R.color.transparent)
}
}
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.graphics.Bitmap
import android.os.Bundle
@@ -15,12 +16,14 @@ import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.fragment.navArgs
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.settings.app.subscription.PayPalRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationWebViewOnBackPressedCallback
import org.thoughtcrime.securesms.databinding.DonationWebviewFragmentBinding
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.visible
/**
@@ -49,8 +52,16 @@ class PayPalConfirmationDialogFragment : DialogFragment(R.layout.donation_webvie
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
SystemWindowInsetsSetter.attach(view, viewLifecycleOwner)
val client = PayPalWebClient()
viewLifecycleOwner.lifecycle.addObserver(client)
binding.webView.webViewClient = client
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Bitmap
@@ -24,6 +25,7 @@ import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.donations.StripeIntentAccessor
import org.thoughtcrime.securesms.R
@@ -35,6 +37,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.databinding.DonationWebviewFragmentBinding
import org.thoughtcrime.securesms.util.Environment
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.visible
/**
@@ -64,9 +67,16 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.donation_webview_fragmen
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
@SuppressLint("SetJavaScriptEnabled", "SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lifecycleDisposable.bindTo(viewLifecycleOwner)
SystemWindowInsetsSetter.attach(view, viewLifecycleOwner)
dialog!!.window!!.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
@@ -9,9 +9,11 @@ import android.os.Bundle
import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardActions
@@ -231,6 +233,8 @@ fun BankTransferDetailsContent(
.fillMaxWidth()
.fillMaxHeight()
.padding(it)
.consumeWindowInsets(it)
.imePadding()
) {
val focusManager = LocalFocusManager.current
val focusRequester = remember { FocusRequester() }
@@ -9,8 +9,10 @@ import android.os.Bundle
import android.view.View
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardActions
@@ -237,7 +239,10 @@ private fun IdealTransferDetailsContent(
Column(
horizontalAlignment = CenterHorizontally,
modifier = Modifier.padding(it)
modifier = Modifier
.padding(it)
.consumeWindowInsets(it)
.imePadding()
) {
LazyColumn(
modifier = Modifier
@@ -137,9 +137,9 @@ class ManageDonationsFragment :
}
override fun getMaterial3OnScrollHelper(toolbar: Toolbar?): Material3OnScrollHelper {
return object : Material3OnScrollHelper(activity = requireActivity(), views = listOf(toolbar!!), lifecycleOwner = viewLifecycleOwner, setStatusBarColor = {}) {
override val activeColorSet: ColorSet = ColorSet(R.color.transparent, CoreUiR.color.signal_colorBackground)
override val inactiveColorSet: ColorSet = ColorSet(R.color.transparent, CoreUiR.color.signal_colorBackground)
return object : Material3OnScrollHelper(activity = requireActivity(), views = listOf(toolbar!!), lifecycleOwner = viewLifecycleOwner) {
override val activeColorSet: ColorSet = ColorSet(R.color.transparent)
override val inactiveColorSet: ColorSet = ColorSet(R.color.transparent)
}
}
@@ -272,12 +272,10 @@ class ConversationSettingsFragment :
return object : Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(toolbar!!),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = {}
lifecycleOwner = viewLifecycleOwner
) {
override val inactiveColorSet = ColorSet(
toolbarColorRes = CoreUiR.color.signal_colorBackground_0,
statusBarColorRes = CoreUiR.color.signal_colorBackground
toolbarColorRes = CoreUiR.color.signal_colorBackground_0
)
}
}
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.conversation.clicklisteners
import android.app.Dialog
import android.os.Bundle
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -48,6 +49,7 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.AvatarImage
@@ -88,6 +90,12 @@ class PollVotesFragment : ComposeDialogFragment(), RecipientBottomSheetDialogFra
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
@Composable
override fun DialogContent() {
val state by viewModel.state.collectAsStateWithLifecycle()
@@ -203,7 +203,9 @@ class MultiselectForwardFragment :
isSplitPane = isSplitPane,
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
// Bottom-sheet hosts already clear the navigation bar via BottomSheetBehavior's
// edge-to-edge padding; padding again here would double up.
.then(if (args.isWrappedInBottomSheet) Modifier else Modifier.navigationBarsPadding())
)
}
}
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.conversation.mutiselect.forward
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -7,6 +8,7 @@ import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import org.signal.core.ui.enableEdgeToEdge
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.fragments.findListener
@@ -19,6 +21,12 @@ class MultiselectForwardFullScreenDialogFragment : DialogFragment(), Multiselect
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
override fun onFinishForwardAction() {
findListener<Callback>()?.onFinishForwardAction()
}
@@ -57,7 +57,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnPreDraw
@@ -5178,9 +5177,7 @@ class ConversationFragment :
private object MediaKeyboardFragmentCreator : InputAwareConstraintLayout.FragmentCreator {
override val id: Int = MEDIA_KEYBOARD_FRAGMENT_CREATOR_ID
override fun create(): Fragment = KeyboardPagerFragment().apply {
arguments = bundleOf(KeyboardPagerFragment.ARG_SET_NAV_COLOR to false)
}
override fun create(): Fragment = KeyboardPagerFragment()
}
private inner class KeyboardEvents :
@@ -22,8 +22,7 @@ class ConversationToolbarOnScrollHelper(
) : Material3OnScrollHelper(
activity = activity,
views = listOf(toolbarBackground),
lifecycleOwner = lifecycleOwner,
setStatusBarColor = {}
lifecycleOwner = lifecycleOwner
) {
override val activeColorSet: ColorSet
get() = when {
@@ -63,6 +63,7 @@ import org.signal.core.ui.compose.list.ReorderListEvent
import org.signal.core.ui.compose.list.ReorderableItem
import org.signal.core.ui.compose.list.rememberReorderableListState
import org.signal.core.ui.compose.list.reorderableList
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.polls.Poll
@@ -97,6 +98,7 @@ class CreatePollFragment : ComposeDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
dialog.window?.enableEdgeToEdge()
return dialog
}
@@ -4,11 +4,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import org.signal.core.ui.util.ThemeUtil
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
@@ -18,17 +15,12 @@ import org.thoughtcrime.securesms.keyboard.sticker.StickerKeyboardPageFragment
import org.thoughtcrime.securesms.util.ThemedFragment.themeResId
import org.thoughtcrime.securesms.util.ThemedFragment.themedInflate
import org.thoughtcrime.securesms.util.ThemedFragment.withTheme
import org.thoughtcrime.securesms.util.WindowUtil
import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.visible
import kotlin.reflect.KClass
class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragment {
companion object {
val ARG_SET_NAV_COLOR = "args.setNavColor"
}
private lateinit var emojiButton: View
private lateinit var stickerButton: View
private lateinit var gifButton: View
@@ -37,9 +29,6 @@ class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragme
private val fragments: MutableMap<KClass<*>, Fragment> = mutableMapOf()
private var currentFragment: Fragment? = null
private val shouldSetNavColor: Boolean
get() = arguments?.getBoolean(ARG_SET_NAV_COLOR) ?: true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return themedInflate(R.layout.keyboard_pager_fragment, inflater, container)
}
@@ -61,20 +50,6 @@ class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragme
emojiButton.setOnClickListener { viewModel.switchToPage(KeyboardPage.EMOJI) }
stickerButton.setOnClickListener { viewModel.switchToPage(KeyboardPage.STICKER) }
gifButton.setOnClickListener { viewModel.switchToPage(KeyboardPage.GIF) }
onHiddenChanged(false)
}
override fun onHiddenChanged(hidden: Boolean) {
if (shouldSetNavColor) {
getWindow()?.let { window ->
if (hidden) {
WindowUtil.setNavigationBarColor(requireContext(), window, ThemeUtil.getThemedColor(requireContext(), android.R.attr.navigationBarColor))
} else {
WindowUtil.setNavigationBarColor(requireContext(), window, ThemeUtil.getThemedColor(requireContext(), R.attr.mediaKeyboardBottomBarBackgroundColor))
}
}
}
}
@Suppress("DEPRECATION")
@@ -83,19 +58,6 @@ class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragme
viewModel.page().value?.let(this::onPageSelected)
}
private fun getWindow(): Window? {
var parent: Fragment? = parentFragment
while (parent != null) {
if (parent is DialogFragment) {
return parent.dialog?.window
}
parent = parent.parentFragment
}
return activity?.window
}
private fun onPageSelected(page: KeyboardPage) {
emojiButton.isSelected = page == KeyboardPage.EMOJI
stickerButton.isSelected = page == KeyboardPage.STICKER
@@ -143,8 +105,6 @@ class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragme
override fun show() {
findListener<MediaKeyboard.MediaKeyboardListener>()?.onShown()
if (isAdded && view != null) {
onHiddenChanged(false)
viewModel.page().value?.let(this::onPageSelected)
}
}
@@ -152,8 +112,6 @@ class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragme
override fun hide() {
findListener<MediaKeyboard.MediaKeyboardListener>()?.onHidden()
if (isAdded && view != null) {
onHiddenChanged(true)
val transaction = childFragmentManager.beginTransaction()
fragments.values.forEach { transaction.remove(it) }
transaction.commitAllowingStateLoss()
@@ -1,16 +1,20 @@
package org.thoughtcrime.securesms.keyboard.sticker
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.Px
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import org.signal.core.ui.enableEdgeToEdge
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
import org.thoughtcrime.securesms.stickers.StickerEventListener
@@ -37,12 +41,24 @@ class StickerSearchDialogFragment : DialogFragment(), KeyboardStickerListAdapter
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_Animated_Bottom)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.sticker_search_dialog_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime())
v.setPadding(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom)
insets
}
search = view.findViewById(R.id.sticker_search_text)
list = view.findViewById(R.id.sticker_search_list)
noResults = view.findViewById(R.id.sticker_search_no_results)
@@ -16,12 +16,15 @@ import android.os.Bundle;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import androidx.activity.EdgeToEdge;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import com.google.android.gms.maps.CameraUpdateFactory;
@@ -73,6 +76,7 @@ public final class PlacePickerActivity extends AppCompatActivity {
private LatLng currentLocation = new LatLng(0, 0);
private AddressLookup addressLookup;
private GoogleMap googleMap;
private Insets systemBarInsets;
public static void startActivityForResultAtCurrentLocation(@NonNull Fragment fragment, int requestCode, @ColorInt int chatColor) {
fragment.startActivityForResult(new Intent(fragment.requireActivity(), PlacePickerActivity.class).putExtra(KEY_CHAT_COLOR, chatColor), requestCode);
@@ -85,15 +89,23 @@ public final class PlacePickerActivity extends AppCompatActivity {
@SuppressLint("MissingInflatedId")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
dynamicTheme.onCreate(this);
setContentView(R.layout.activity_place_picker);
bottomSheet = findViewById(R.id.bottom_sheet);
View markerImage = findViewById(R.id.marker_image_view);
View fab = findViewById(R.id.place_chosen_button);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (view, insets) -> {
systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
bottomSheet.setPadding(systemBarInsets.left, 0, systemBarInsets.right, systemBarInsets.bottom);
applyMapPadding();
return insets;
});
ViewCompat.setBackgroundTintList(fab, ColorStateList.valueOf(getIntent().getIntExtra(KEY_CHAT_COLOR, Color.RED)));
fab.setOnClickListener(v -> finishWithAddress());
@@ -167,9 +179,16 @@ public final class PlacePickerActivity extends AppCompatActivity {
private void setMap(GoogleMap googleMap) {
this.googleMap = googleMap;
applyMapPadding();
moveMapToInitialIfPossible();
}
private void applyMapPadding() {
if (googleMap != null && systemBarInsets != null) {
googleMap.setPadding(systemBarInsets.left, systemBarInsets.top, systemBarInsets.right, systemBarInsets.bottom);
}
}
private void moveMapToInitialIfPossible() {
if (initialLocation != null && googleMap != null) {
Log.d(TAG, "Moving map to initial location");
@@ -5,8 +5,8 @@ import android.content.Intent;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
@@ -63,12 +63,9 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
);
setContentView(R.layout.avatar_selection_activity);
if (isGalleryFirst()) {
@@ -115,8 +115,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(binding.mediaGalleryToolbar, binding.mediaGalleryStatusBarBackground),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = {}
lifecycleOwner = viewLifecycleOwner
).attach(binding.mediaGalleryGrid)
if (callbacks.isCameraEnabled()) {
@@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet.forOutgoingMessageRecord
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.fragments.requireListener
class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks {
@@ -70,6 +71,8 @@ class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks {
toolbar.setTitle(R.string.AndroidManifest__message_details)
toolbar.setNavigationOnClickListener { requireActivity().onBackPressedDispatcher.onBackPressed() }
SystemWindowInsetsSetter.attach(view, viewLifecycleOwner)
return view
}
@@ -2,9 +2,14 @@ package org.thoughtcrime.securesms.pin;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
@@ -19,9 +24,16 @@ public final class PinRestoreActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
dynamicTheme.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.pin_restore_activity);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (view, insets) -> {
Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime());
view.setPadding(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
return insets;
});
}
@Override
@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.profiles.username
import android.app.Dialog
import android.os.Bundle
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
@@ -32,6 +33,7 @@ import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.enableEdgeToEdge
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.profiles.manage.EditProfileActivity
import org.signal.core.ui.R as CoreUiR
@@ -48,6 +50,12 @@ class NewWaysToConnectDialogFragment : ComposeDialogFragment() {
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
@Composable
override fun DialogContent() {
NewWaysToConnectDialogContent(
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.reactions;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -11,7 +10,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewpager2.widget.ViewPager2;
@@ -23,7 +21,6 @@ import com.google.android.material.tabs.TabLayoutMediator;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.util.FullscreenHelper;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.util.WindowUtil;
@@ -81,16 +78,6 @@ public final class ReactionsBottomSheetDialogFragment extends BottomSheetDialogF
super.onCreate(savedInstanceState);
}
@Override
public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
FullscreenHelper.showSystemUI(dialog.getWindow());
WindowUtil.setNavigationBarColor(requireContext(), dialog.getWindow(), ContextCompat.getColor(requireContext(), org.signal.core.ui.R.color.signal_colorSurface1));
return dialog;
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.recipients.ui.sharablegrouplink.qr;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -19,6 +20,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.ui.BottomSheetUtil;
import org.signal.core.ui.WindowExtensionsKt;
import org.signal.core.ui.util.ThemeUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
@@ -26,6 +28,7 @@ import org.thoughtcrime.securesms.components.qr.QrView;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.qr.QrCodeUtil;
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -59,6 +62,15 @@ public class GroupLinkShareQrDialogFragment extends DialogFragment {
: R.style.TextSecure_LightTheme);
}
@Override
public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
if (dialog.getWindow() != null) {
WindowExtensionsKt.enableEdgeToEdge(dialog.getWindow());
}
return dialog;
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@@ -69,6 +81,7 @@ public class GroupLinkShareQrDialogFragment extends DialogFragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
SystemWindowInsetsSetter.attach(view, getViewLifecycleOwner());
initializeViewModel();
initializeViews(view);
}
@@ -5,10 +5,14 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
@@ -36,8 +40,15 @@ public final class ImageEditorStickerSelectActivity extends AppCompatActivity im
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.scribble_select_new_sticker_activity);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (view, insets) -> {
Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime());
view.setPadding(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
return insets;
});
}
@Override
@@ -1,12 +1,13 @@
package org.thoughtcrime.securesms.stories.settings.create
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import org.signal.core.ui.enableEdgeToEdge
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.settings.select.BaseStoryRecipientSelectionFragment
import org.thoughtcrime.securesms.util.WindowUtil
class CreateStoryFlowDialogFragment : DialogFragment(R.layout.create_story_flow_dialog_fragment), BaseStoryRecipientSelectionFragment.Callback, CreateStoryWithViewersFragment.Callback {
@@ -15,6 +16,12 @@ class CreateStoryFlowDialogFragment : DialogFragment(R.layout.create_story_flow_
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
override fun exitFlow() {
dismissAllowingStateLoss()
}
@@ -28,12 +35,4 @@ class CreateStoryFlowDialogFragment : DialogFragment(R.layout.create_story_flow_
)
dismissAllowingStateLoss()
}
override fun setStatusBarColor(color: Int) {
WindowUtil.setStatusBarColor(requireDialog().window!!, color)
}
override fun getStatusBarColor(): Int {
return WindowUtil.getStatusBarColor(requireDialog().window!!)
}
}
@@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.stories.settings.create
import android.os.Bundle
import android.view.View
import androidx.annotation.ColorInt
import androidx.appcompat.widget.Toolbar
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
@@ -16,11 +16,11 @@ import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.databinding.StoriesCreateWithRecipientsFragmentBinding
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.fragments.requireListener
import org.thoughtcrime.securesms.util.viewholders.RecipientMappingModel
import org.thoughtcrime.securesms.util.viewholders.RecipientViewHolder
@@ -80,11 +80,10 @@ class CreateStoryWithViewersFragment : DSLSettingsFragment(
Material3OnScrollHelper(
activity = requireActivity(),
setStatusBarColor = { requireListener<Callback>().setStatusBarColor(it) },
getStatusBarColor = { requireListener<Callback>().getStatusBarColor() },
views = listOf(binding.toolbar),
lifecycleOwner = viewLifecycleOwner
).attach(binding.appBarLayout)
SystemWindowInsetsSetter.attach(binding.create, viewLifecycleOwner, WindowInsetsCompat.Type.navigationBars(), SystemWindowInsetsSetter.ApplyMode.MARGIN)
ViewUtil.focusAndShowKeyboard(binding.nameField.editText)
}
@@ -142,8 +141,6 @@ class CreateStoryWithViewersFragment : DSLSettingsFragment(
}
interface Callback {
fun setStatusBarColor(@ColorInt color: Int)
fun getStatusBarColor(): Int
fun onDone(recipientId: RecipientId)
}
}
@@ -2,8 +2,11 @@ package org.thoughtcrime.securesms.stories.settings.select
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.widget.Toolbar
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -20,6 +23,7 @@ import org.thoughtcrime.securesms.contacts.selection.ContactSelectionArguments
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.groups.SelectionLimits
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.fragments.findListener
import java.util.Optional
import java.util.function.Consumer
@@ -54,6 +58,11 @@ abstract class BaseStoryRecipientSelectionFragment : Fragment(R.layout.stories_b
toolbar.setTitle(toolbarTitleId)
toolbar.updateLayoutParams { height = ViewGroup.LayoutParams.WRAP_CONTENT }
SystemWindowInsetsSetter.attach(toolbar, viewLifecycleOwner, WindowInsetsCompat.Type.statusBars())
SystemWindowInsetsSetter.attach(view.findViewById(R.id.contacts_container), viewLifecycleOwner, WindowInsetsCompat.Type.navigationBars())
SystemWindowInsetsSetter.attach(actionButton, viewLifecycleOwner, WindowInsetsCompat.Type.navigationBars(), SystemWindowInsetsSetter.ApplyMode.MARGIN)
lifecycleDisposable.bindTo(viewLifecycleOwner)
actionButton.setText(actionButtonLabel)
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.viewer.first
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
@@ -10,6 +11,7 @@ import androidx.core.app.ActivityCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.util.concurrent.LifecycleDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -34,13 +36,13 @@ class StoryFirstTimeNavigationFragment : DialogFragment(R.layout.story_viewer_fi
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.window!!.enableEdgeToEdge()
dialog.window!!.addFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
if (Build.VERSION.SDK_INT >= 28) {
dialog.window!!.attributes = dialog.window!!.attributes.apply {
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
return dialog
}
@@ -21,6 +21,7 @@ import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ComposeText
import org.thoughtcrime.securesms.components.InputAwareLayout
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout
import org.thoughtcrime.securesms.components.emoji.Emoji
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
@@ -170,6 +171,10 @@ class StoryReplyComposer @JvmOverloads constructor(
return Input(trimmedText, mentions, bodyRanges)
}
fun setInsetPaddingMode(mode: KeyboardAwareLinearLayout.InsetPaddingMode) {
inputAwareLayout.setInsetPaddingMode(mode)
}
fun openEmojiSearch() {
emojiDrawer.onOpenEmojiSearch()
}
@@ -24,6 +24,7 @@ import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.getParcelableCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
@@ -172,6 +173,8 @@ class StoryGroupReplyFragment :
composer.callback = this
composer.hint = getString(R.string.StoryViewerPageFragment__reply_to_group)
// The hosting bottom sheet already clears the navigation bar; the composer only needs to ride above the keyboard.
composer.setInsetPaddingMode(KeyboardAwareLinearLayout.InsetPaddingMode.KEYBOARD)
onPageSelected(findListener<StoryViewsAndRepliesPagerParent>()?.selectedChild ?: StoryViewsAndRepliesPagerParent.Child.REPLIES)
@@ -22,8 +22,6 @@ import org.signal.core.ui.R as CoreUiR
*/
open class Material3OnScrollHelper(
private val activity: FragmentActivity,
private val setStatusBarColor: (Int) -> Unit = { WindowUtil.setStatusBarColor(activity.window, it) },
getStatusBarColor: () -> Int = { WindowUtil.getStatusBarColor(activity.window) },
private val setChatFolderColor: (Int) -> Unit = {},
private val onSetToolbarColor: (Int) -> Unit = {},
private val views: List<View> = emptyList(),
@@ -37,23 +35,19 @@ open class Material3OnScrollHelper(
*/
@JvmStatic
fun create(activity: FragmentActivity, toolbar: View): Material3OnScrollHelper {
return Material3OnScrollHelper(activity = activity, views = listOf(toolbar), lifecycleOwner = activity, setStatusBarColor = {})
return Material3OnScrollHelper(activity = activity, views = listOf(toolbar), lifecycleOwner = activity)
}
}
open val activeColorSet: ColorSet = ColorSet(
toolbarColorRes = CoreUiR.color.signal_colorSurface2,
statusBarColorRes = CoreUiR.color.signal_colorSurface2,
chatFolderColorRes = CoreUiR.color.signal_colorBackground
)
open val inactiveColorSet: ColorSet = ColorSet(
toolbarColorRes = CoreUiR.color.signal_colorBackground,
statusBarColorRes = CoreUiR.color.signal_colorBackground,
chatFolderColorRes = CoreUiR.color.signal_colorSurface2
)
protected var previousStatusBarColor: Int = getStatusBarColor()
private var animator: ValueAnimator? = null
private var active: Boolean? = null
@@ -65,7 +59,6 @@ open class Material3OnScrollHelper(
override fun onDestroy(owner: LifecycleOwner) {
animator?.cancel()
setStatusBarColor(previousStatusBarColor)
}
})
}
@@ -105,7 +98,6 @@ open class Material3OnScrollHelper(
animator?.cancel()
val colorSet = if (active == true) activeColorSet else inactiveColorSet
setToolbarColor(ContextCompat.getColor(activity, colorSet.toolbarColorRes))
setStatusBarColor(ContextCompat.getColor(activity, colorSet.statusBarColorRes))
setChatFolderColor(ContextCompat.getColor(activity, colorSet.chatFolderColorRes))
}
@@ -129,8 +121,6 @@ open class Material3OnScrollHelper(
if (hadActiveState) {
val startToolbarColor = ContextCompat.getColor(activity, startColorSet.toolbarColorRes)
val endToolbarColor = ContextCompat.getColor(activity, endColorSet.toolbarColorRes)
val startStatusBarColor = ContextCompat.getColor(activity, startColorSet.statusBarColorRes)
val endStatusBarColor = ContextCompat.getColor(activity, endColorSet.statusBarColorRes)
val startChatFolderColor = ContextCompat.getColor(activity, startColorSet.chatFolderColorRes)
val endChatFolderColor = ContextCompat.getColor(activity, endColorSet.chatFolderColorRes)
@@ -138,7 +128,6 @@ open class Material3OnScrollHelper(
duration = 200
addUpdateListener {
setToolbarColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startToolbarColor, endToolbarColor))
setStatusBarColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startStatusBarColor, endStatusBarColor))
setChatFolderColor(ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, startChatFolderColor, endChatFolderColor))
}
start()
@@ -174,10 +163,6 @@ open class Material3OnScrollHelper(
*/
data class ColorSet(
@ColorRes val toolbarColorRes: Int,
@ColorRes val statusBarColorRes: Int,
@ColorRes val chatFolderColorRes: Int
) {
constructor(@ColorRes color: Int) : this(color, color)
constructor(@ColorRes toolbarColorRes: Int, @ColorRes statusBarColorRes: Int) : this(toolbarColorRes, statusBarColorRes, toolbarColorRes)
}
@ColorRes val chatFolderColorRes: Int = toolbarColorRes
)
}
@@ -5,7 +5,6 @@ import android.content.Context;
import android.os.Build;
import android.view.Window;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.core.view.WindowInsetsControllerCompat;
@@ -41,18 +40,6 @@ public final class WindowUtil {
controller(window).setAppearanceLightNavigationBars(true);
}
public static void setNavigationBarColor(@NonNull Activity activity, @ColorInt int color) {
setNavigationBarColor(activity, activity.getWindow(), color);
}
public static void setNavigationBarColor(@NonNull Context context, @NonNull Window window, @ColorInt int color) {
if (Build.VERSION.SDK_INT < 27) {
window.setNavigationBarColor(ThemeUtil.getThemedColor(context, android.R.attr.navigationBarColor));
} else {
window.setNavigationBarColor(color);
}
}
public static void setLightStatusBarFromTheme(@NonNull Activity activity) {
final boolean isLightStatusBar = ThemeUtil.getThemedBoolean(activity, android.R.attr.windowLightStatusBar);
@@ -68,14 +55,6 @@ public final class WindowUtil {
controller(window).setAppearanceLightStatusBars(true);
}
public static void setStatusBarColor(@NonNull Window window, @ColorInt int color) {
window.setStatusBarColor(color);
}
public static int getStatusBarColor(@NonNull Window window) {
return window.getStatusBarColor();
}
private static @NonNull WindowInsetsControllerCompat controller(@NonNull Window window) {
return new WindowInsetsControllerCompat(window, window.getDecorView());
}
@@ -8,7 +8,6 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.OneShotPreDrawListener
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.util.concurrent.LifecycleDisposable
@@ -39,9 +38,8 @@ class VerifyScanFragment : Fragment() {
cameraScrim = view.findViewById(R.id.camera_scrim)
cameraMarks = view.findViewById(R.id.camera_marks)
if (!isDialogHosted()) {
SystemWindowInsetsSetter.attach(view.findViewById<TextView>(R.id.information), viewLifecycleOwner, WindowInsetsCompat.Type.navigationBars(), SystemWindowInsetsSetter.ApplyMode.MARGIN)
}
SystemWindowInsetsSetter.attach(view.findViewById<TextView>(R.id.information), viewLifecycleOwner, WindowInsetsCompat.Type.navigationBars(), SystemWindowInsetsSetter.ApplyMode.MARGIN)
OneShotPreDrawListener.add(cameraScrim) {
val width = cameraScrim.scrimWidth
val height = cameraScrim.scrimHeight
@@ -60,8 +58,4 @@ class VerifyScanFragment : Fragment() {
findListener<ScanListener>()?.onQrDataFound(qrData)
}
}
private fun isDialogHosted(): Boolean {
return generateSequence<Fragment>(this) { it.parentFragment }.any { it is DialogFragment && it.showsDialog }
}
}
@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.wallpaper.crop;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
@@ -37,12 +37,9 @@ public final class WallpaperImageSelectionActivity extends AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
);
setContentView(R.layout.wallpaper_image_selection_activity);
getSupportFragmentManager().beginTransaction()
+4 -34
View File
@@ -1,18 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="TextSecure.LightTheme" parent="TextSecure.BaseLightTheme">
<item name="android:statusBarColor">@color/core_grey_60</item>
</style>
<style name="TextSecure.DarkTheme" parent="@style/TextSecure.BaseDarkTheme">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.DarkNoActionBar" parent="@style/TextSecure.BaseDarkNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:colorControlActivated">@color/core_ultramarine_light</item>
</style>
@@ -23,41 +12,22 @@
<item name="android:windowSharedElementExitTransition">@transition/change_transform</item>
</style>
<style name="TextSecure.LightNoActionBar" parent="TextSecure.BaseLightNoActionBar">
<item name="android:statusBarColor">@color/core_grey_60</item>
</style>
<style name="TextSecure.FullScreenMedia" parent="TextSecure.DarkNoActionBar">
<item name="android:windowFullscreen">true</item>
</style>
<style name="TextSecure.LightRegistrationTheme" parent="TextSecure.LightNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.DarkRegistrationTheme" parent="TextSecure.DarkNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.MediaPreview" parent="@style/TextSecure.BaseMediaPreview">
<item name="android:navigationBarColor">@color/media_preview_bar_background</item>
<item name="android:windowContentTransitions">true</item>
</style>
<style name="Theme.Signal.Light.BottomSheetDialog" parent="Theme.Material3.Light.BottomSheetDialog">
<item name="android:navigationBarColor">@color/signal_colorSurface1</item>
<item name="android:navigationBarColor">@color/signal_bottom_sheet_navigation_bar</item>
<item name="enableEdgeToEdge">true</item>
</style>
<style name="Theme.Signal.BottomSheetDialog" parent="Theme.Material3.Dark.BottomSheetDialog">
<item name="android:navigationBarColor">@color/signal_colorSurface1</item>
</style>
<style name="Theme.Signal.BottomSheetDialog.Fixed">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/transparent</item>
<item name="android:navigationBarColor">@color/signal_bottom_sheet_navigation_bar</item>
<item name="enableEdgeToEdge">true</item>
</style>
<style name="TextSecure.LightNoActionBar.ConversationSettings" parent="TextSecure.LightNoActionBar">
+1 -11
View File
@@ -2,41 +2,31 @@
<resources>
<style name="TextSecure.LightTheme" parent="TextSecure.BaseLightTheme">
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.DarkTheme" parent="TextSecure.BaseDarkTheme">
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">false</item>
</style>
<style name="TextSecure.LightRegistrationTheme" parent="TextSecure.LightNoActionBar">
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.DarkRegistrationTheme" parent="TextSecure.DarkNoActionBar">
<item name="android:windowLightStatusBar">false</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="TextSecure.LightNoActionBar" parent="TextSecure.BaseLightNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">true</item>
</style>
<style name="TextSecure.DarkNoActionBar" parent="TextSecure.BaseDarkNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
</style>
<style name="Theme.Signal.BottomSheetDialog.Fixed">
<item name="android:windowIsFloating">false</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorSurface1</item>
<item name="android:statusBarColor">@color/transparent</item>
<item name="android:windowLightStatusBar">false</item>
</style>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Transparent on API 27+: Material's BottomSheetDialog only lays the sheet out edge-to-edge
(and pads it for the navigation bar) when the window navigation bar color is non-opaque. -->
<color name="bottom_sheet_navigation_bar_surface">@android:color/transparent</color>
</resources>
+2 -13
View File
@@ -3,43 +3,31 @@
<style name="TextSecure.LightTheme" parent="TextSecure.BaseLightTheme">
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
<style name="TextSecure.DarkTheme" parent="TextSecure.BaseDarkTheme">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
<style name="TextSecure.LightRegistrationTheme" parent="TextSecure.LightNoActionBar">
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
<style name="TextSecure.DarkRegistrationTheme" parent="TextSecure.DarkNoActionBar">
<item name="android:windowLightStatusBar">false</item>
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
<style name="TextSecure.LightNoActionBar" parent="TextSecure.BaseLightNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
<style name="TextSecure.DarkNoActionBar" parent="TextSecure.BaseDarkNoActionBar">
<item name="android:statusBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
@@ -47,7 +35,8 @@
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@color/transparent</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:navigationBarColor">@color/signal_colorBackground</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="enableEdgeToEdge">true</item>
</style>
</resources>
+5
View File
@@ -64,4 +64,9 @@
<color name="safety_number_card_blue">#ff506ECD</color>
<color name="notification_background_ultramarine">#3B45FD</color>
<!-- Bottom-sheet dialog navigation bar color. Opaque below API 27 (keeps the legacy colored
strip); overridden to transparent in values-v27 so Material's BottomSheetDialog lays the
sheet out edge-to-edge and pads it for the navigation bar itself. -->
<color name="bottom_sheet_navigation_bar_surface">@color/signal_colorSurface</color>
</resources>
+5 -15
View File
@@ -50,20 +50,14 @@
<style name="Theme.Signal.WallpaperCropper" parent="@style/TextSecure.DarkNoActionBar">
<item name="colorPrimaryDark">@color/wallpaper_preview_background</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/wallpaper_preview_background</item>
<item name="android:windowLightStatusBar" tools:ignore="NewApi">false</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/wallpaper_preview_background</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
<style name="Theme.Signal.WallpaperCropper.Light" parent="@style/TextSecure.LightNoActionBar">
<item name="colorPrimary">@color/wallpaper_preview_background</item>
<item name="colorPrimaryDark">@color/wallpaper_preview_background</item>
<item name="android:textColorSecondary">@color/white</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/wallpaper_preview_background</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/wallpaper_preview_background</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
</style>
@@ -96,8 +90,6 @@
<style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="27">default</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/signal_colorSurface</item>
<item name="android:navigationBarColor">@color/signal_colorSurface</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
@@ -113,8 +105,6 @@
<style name="TextSecure.DarkIntroTheme" parent="@style/Theme.AppCompat">
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="27">default</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/signal_colorSurface</item>
<item name="android:navigationBarColor">@color/signal_colorSurface</item>
<item name="android:windowLightStatusBar" tools:ignore="NewApi">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
@@ -446,7 +436,7 @@
<style name="Widget.Signal.FixedRoundedCorners.Messages" parent="Widget.Signal.FixedRoundedCorners">
<item name="bottomSheetStyle">@style/Widget.Signal.FixedRoundedCorners.Messages.BottomSheet</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/signal_colorSurface</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/bottom_sheet_navigation_bar_surface</item>
</style>
<style name="Widget.Signal.FixedRoundedCorners.Messages.BottomSheet" parent="Widget.Material3.BottomSheet">
@@ -457,7 +447,7 @@
<style name="Widget.Signal.FixedRoundedCorners.Stories">
<item name="bottomSheetStyle">@style/Widget.Signal.FixedRoundedCorners.BottomSheet.Stories</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/signal_colorSurface</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/bottom_sheet_navigation_bar_surface</item>
</style>
<style name="Widget.Signal.FixedRoundedCorners.BottomSheet.Stories" parent="Widget.CoreUi.FixedRoundedCorners.BottomSheet">
@@ -550,7 +540,7 @@
<item name="colorAccent">@color/core_ultramarine</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/signal_colorSurface1</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/signal_bottom_sheet_navigation_bar</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item>
<item name="android:colorForeground">@color/black</item>
@@ -565,7 +555,7 @@
<item name="colorAccent">@color/core_ultramarine_light</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/signal_colorSurface1</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/signal_bottom_sheet_navigation_bar</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/transparent</item>
<item name="materialButtonStyle">@style/Widget.Signal.BottomSheet.MaterialButtonStyle</item>
@@ -581,7 +571,7 @@
<item name="colorAccent">@color/signal_colorPrimary</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/signal_colorSurface</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/bottom_sheet_navigation_bar_surface</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
<item name="android:statusBarColor" tools:ignore="NewApi">@color/transparent</item>
</style>
@@ -5,8 +5,15 @@
package org.signal.core.ui
import android.graphics.Color
import android.os.Build
import android.view.Window
import android.view.WindowManager
import androidx.core.view.WindowCompat
import org.signal.core.ui.util.ThemeUtil
private val DARK_NAVIGATION_BAR_SCRIM = Color.argb(0x80, 0x1b, 0x1b, 0x1b)
private val LIGHT_NAVIGATION_BAR_SCRIM = Color.argb(0xe6, 0xff, 0xff, 0xff)
/**
* Initializes screenshot security on the window based on user preferences.
@@ -18,3 +25,22 @@ fun Window.initializeScreenshotSecurity() {
clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
/**
* Dialog-window analog of [androidx.activity.enableEdgeToEdge]: lays the window out edge-to-edge with
* transparent (or scrimmed, pre-29) system bars, matching what targetSdk 36 enforces. Dialog windows are
* not covered by the activity-level call in BaseActivity, so every non-floating dialog must opt in itself.
*
* Bar icon appearance (`windowLightStatusBar` / `windowLightNavigationBar`) is left to the window's theme,
* which stays honored under edge-to-edge.
*/
@Suppress("DEPRECATION")
fun Window.enableEdgeToEdge() {
WindowCompat.setDecorFitsSystemWindows(this, false)
statusBarColor = Color.TRANSPARENT
navigationBarColor = when {
Build.VERSION.SDK_INT >= 29 -> Color.TRANSPARENT
Build.VERSION.SDK_INT >= 27 && ThemeUtil.getThemedBoolean(context, android.R.attr.windowLightNavigationBar) -> LIGHT_NAVIGATION_BAR_SCRIM
else -> DARK_NAVIGATION_BAR_SCRIM
}
}
@@ -10,7 +10,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -45,8 +49,12 @@ abstract class ComposeBottomSheetDialogFragment : FixedRoundedCornerBottomSheetD
}
CompositionLocalProvider(LocalFragmentManager provides childFragmentManager) {
SignalTheme(isDarkMode = isDark) {
// No navigation bar padding here: BottomSheetBehavior already bottom-pads the sheet
// for the navigation bar (paddingBottomSystemWindowInsets) once the window is
// edge-to-edge, so padding again would double up. The keyboard inset excludes the
// navigation bar for the same reason.
Surface(
modifier = Modifier.navigationBarsPadding(),
modifier = Modifier.windowInsetsPadding(WindowInsets.ime.exclude(WindowInsets.navigationBars)),
shape = RoundedCornerShape(cornerRadius.dp, cornerRadius.dp),
color = SignalTheme.colors.colorSurface1,
contentColor = MaterialTheme.colorScheme.onSurface
@@ -5,6 +5,7 @@
package org.signal.core.ui.compose
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -20,6 +21,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.DialogFragment
import org.signal.core.ui.R
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.ui.enableEdgeToEdge
import org.signal.core.ui.initializeScreenshotSecurity
import org.signal.core.ui.util.ThemeUtil
@@ -37,6 +39,12 @@ abstract class ComposeFullScreenDialogFragment : DialogFragment() {
setStyle(STYLE_NO_FRAME, fullScreenDialogStyle)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
window?.enableEdgeToEdge()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Transparent on API 27+: Material's BottomSheetDialog only lays the sheet out edge-to-edge
(and pads it for the navigation bar) when the window navigation bar color is non-opaque. -->
<color name="signal_bottom_sheet_navigation_bar">@android:color/transparent</color>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Bottom-sheet dialog navigation bar color. Opaque below API 27 (keeps the legacy colored
strip); overridden to transparent in values-v27 so Material's BottomSheetDialog lays the
sheet out edge-to-edge and pads it for the navigation bar itself. -->
<color name="signal_bottom_sheet_navigation_bar">@color/signal_colorSurface1</color>
</resources>
+1 -1
View File
@@ -7,7 +7,7 @@
<item name="bottomSheetStyle">@style/Widget.CoreUi.FixedRoundedCorners.BottomSheet</item>
<item name="android:windowEnterAnimation">@anim/slide_from_bottom</item>
<item name="android:windowExitAnimation">@anim/slide_to_bottom</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/signal_colorSurface1</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/signal_bottom_sheet_navigation_bar</item>
</style>
<style name="Widget.CoreUi.FixedRoundedCorners.BottomSheet" parent="Widget.Material3.BottomSheet.Modal">