From 946e3acb9fb8ae3d3f30e0077aa916d8d132ea30 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Aug 2025 11:35:38 +0200 Subject: [PATCH 01/21] fix: modals bottom safe area handling --- .../app/src/main/res/values-v26/styles.xml | 8 ++- .../app/src/main/res/values-v31/styles.xml | 9 ++++ android/app/src/main/res/values/styles.xml | 1 - src/components/AccountSwitcher.tsx | 4 +- src/components/Modal/BaseModal.tsx | 14 ++++- src/components/Popover/index.tsx | 3 ++ src/components/Popover/types.ts | 7 +++ src/components/PopoverMenu.tsx | 52 ++++++++++++++++--- .../PopoverWithoutOverlay/index.tsx | 15 +++++- src/components/ThreeDotsMenu/index.tsx | 1 + .../utils/generators/ModalStyleUtils.ts | 15 ++++-- 11 files changed, 111 insertions(+), 18 deletions(-) diff --git a/android/app/src/main/res/values-v26/styles.xml b/android/app/src/main/res/values-v26/styles.xml index fbef3b91f60f..e114055c9447 100644 --- a/android/app/src/main/res/values-v26/styles.xml +++ b/android/app/src/main/res/values-v26/styles.xml @@ -1,8 +1,14 @@ - diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml index eea420608451..d53942151288 100644 --- a/android/app/src/main/res/values-v31/styles.xml +++ b/android/app/src/main/res/values-v31/styles.xml @@ -1,4 +1,13 @@ + - diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index 07c446cb7de1..823ff641fe91 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -251,12 +251,12 @@ function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) { }} menuItems={menuItems()} headerText={translate('delegate.switchAccount')} - containerStyles={[{maxHeight: windowHeight / 2}, styles.pb0, styles.mw100, shouldUseNarrowLayout ? {} : styles.wFitContent]} + containerStyles={[{maxHeight: windowHeight / 2}, styles.mw100, shouldUseNarrowLayout ? {} : styles.wFitContent]} headerStyles={styles.pt0} innerContainerStyle={styles.pb0} - scrollContainerStyle={styles.pb4} shouldUseScrollView shouldUpdateFocusedIndex={false} + enableEdgeToEdgeBottomSafeAreaPadding /> )} { diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index 1d685bea5d2f..12d3d56f2e60 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -28,6 +28,7 @@ function Popover(props: PopoverProps) { animationIn = 'fadeIn', animationOut = 'fadeOut', shouldCloseWhenBrowserNavigationChanged = true, + enableEdgeToEdgeBottomSafeAreaPadding = false, } = props; // We need to use isSmallScreenWidth to apply the correct modal type and popoverAnchorPosition @@ -76,6 +77,7 @@ function Popover(props: PopoverProps) { onLayout={onLayout} animationIn={animationIn} animationOut={animationOut} + enableEdgeToEdgeBottomSafeAreaPadding={enableEdgeToEdgeBottomSafeAreaPadding} />, document.body, ); @@ -108,6 +110,7 @@ function Popover(props: PopoverProps) { onLayout={onLayout} animationIn={animationIn} animationOut={animationOut} + enableEdgeToEdgeBottomSafeAreaPadding={enableEdgeToEdgeBottomSafeAreaPadding} /> ); } diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index aa5f0f63aad2..e8f8a5dedcfb 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -39,6 +39,13 @@ type PopoverProps = BaseModalProps & /** Whether we should close when browser navigation change. This doesn't affect native platform */ shouldCloseWhenBrowserNavigationChanged?: boolean; + + /** + * Temporary flag to disable safe area bottom spacing in modals and to allow edge-to-edge content. + * Modals should not always apply bottom safe area padding, instead it should be applied to the scrollable/bottom-docked content directly. + * This flag can be removed, once all components/screens have switched to edge-to-edge safe area handling. + */ + enableEdgeToEdgeBottomSafeAreaPadding?: boolean; }; export default PopoverProps; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 3d3a8825bf99..4e4cc32c0da0 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -7,6 +7,7 @@ import type {GestureResponderEvent, LayoutChangeEvent, StyleProp, TextStyle, Vie import type {ModalProps} from 'react-native-modal'; import type {SvgProps} from 'react-native-svg'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -158,20 +159,47 @@ type PopoverMenuProps = Partial & { /** Used to locate the component in the tests */ testID?: string; -}; -const renderWithConditionalWrapper = (shouldUseScrollView: boolean, contentContainerStyle: StyleProp, children: ReactNode): React.JSX.Element => { - if (shouldUseScrollView) { - return {children}; - } - // eslint-disable-next-line react/jsx-no-useless-fragment - return <>{children}; + /** + * Temporary flag to disable safe area bottom spacing in modals and to allow edge-to-edge content. + * Modals should not always apply bottom safe area padding, instead it should be applied to the scrollable/bottom-docked content directly. + * This flag can be removed, once all components/screens have switched to edge-to-edge safe area handling. + */ + enableEdgeToEdgeBottomSafeAreaPadding?: boolean; }; function getSelectedItemIndex(menuItems: PopoverMenuItem[]) { return menuItems.findIndex((option) => option.isSelected); } +function PopoverMenuContent({ + shouldUseScrollView, + contentContainerStyle, + children, + addBottomSafeAreaPadding, +}: { + shouldUseScrollView: boolean; + contentContainerStyle: StyleProp; + children: ReactNode; + addBottomSafeAreaPadding?: boolean; +}): React.JSX.Element { + const style = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding, style: contentContainerStyle}); + + if (shouldUseScrollView) { + return ( + + {children} + + ); + } + // eslint-disable-next-line react/jsx-no-useless-fragment + return {children}; +} +PopoverMenuContent.displayName = 'PopoverMenuContent'; + function PopoverMenu({ menuItems, onItemSelected, @@ -209,6 +237,7 @@ function PopoverMenu({ shouldUseModalPaddingStyle, shouldAvoidSafariException = false, testID, + enableEdgeToEdgeBottomSafeAreaPadding, }: PopoverMenuProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -423,6 +452,7 @@ function PopoverMenu({ innerContainerStyle={innerContainerStyle} shouldUseModalPaddingStyle={shouldUseModalPaddingStyle} testID={testID} + enableEdgeToEdgeBottomSafeAreaPadding={enableEdgeToEdgeBottomSafeAreaPadding} > {renderHeaderText()} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} - {renderWithConditionalWrapper(shouldUseScrollView, scrollContainerStyle, renderedMenuItems)} + + {renderedMenuItems} + diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx index 33fae243571b..611d3aeef0c6 100644 --- a/src/components/PopoverWithoutOverlay/index.tsx +++ b/src/components/PopoverWithoutOverlay/index.tsx @@ -26,6 +26,7 @@ function PopoverWithoutOverlay( onClose, onModalHide = () => {}, children, + enableEdgeToEdgeBottomSafeAreaPadding, }: PopoverWithoutOverlayProps, ref: ForwardedRef, ) { @@ -45,6 +46,7 @@ function PopoverWithoutOverlay( anchorPosition, innerContainerStyle, outerStyle, + enableEdgeToEdgeBottomSafeAreaPadding, ); useEffect(() => { @@ -80,12 +82,21 @@ function PopoverWithoutOverlay( StyleUtils.getModalPaddingStyles({ shouldAddBottomSafeAreaMargin, shouldAddTopSafeAreaMargin, - shouldAddBottomSafeAreaPadding, + shouldAddBottomSafeAreaPadding: enableEdgeToEdgeBottomSafeAreaPadding === undefined && shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaPadding, modalContainerStyle, insets, }), - [StyleUtils, insets, modalContainerStyle, shouldAddBottomSafeAreaMargin, shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaMargin, shouldAddTopSafeAreaPadding], + [ + StyleUtils, + enableEdgeToEdgeBottomSafeAreaPadding, + insets, + modalContainerStyle, + shouldAddBottomSafeAreaMargin, + shouldAddBottomSafeAreaPadding, + shouldAddTopSafeAreaMargin, + shouldAddTopSafeAreaPadding, + ], ); if (!isVisible) { diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx index 7466c1f78c7e..b2d0dddb8613 100644 --- a/src/components/ThreeDotsMenu/index.tsx +++ b/src/components/ThreeDotsMenu/index.tsx @@ -156,6 +156,7 @@ function ThreeDotsMenu({ anchorRef={buttonRef} shouldEnableNewFocusManagement restoreFocusType={restoreFocusType} + enableEdgeToEdgeBottomSafeAreaPadding /> ); diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts index 2b9684622bc7..6179dc8ebdfc 100644 --- a/src/styles/utils/generators/ModalStyleUtils.ts +++ b/src/styles/utils/generators/ModalStyleUtils.ts @@ -42,11 +42,20 @@ type GetModalStylesStyleUtil = { innerContainerStyle?: ViewStyle, outerStyle?: ViewStyle, shouldUseModalPaddingStyle?: boolean, + enableEdgeToEdgeBottomSafeAreaPadding?: boolean, ) => GetModalStyles; }; const createModalStyleUtils: StyleUtilGenerator = ({theme, styles}) => ({ - getModalStyles: (type, windowDimensions, popoverAnchorPosition = {}, innerContainerStyle = {}, outerStyle = {}, shouldUseModalPaddingStyle = true): GetModalStyles => { + getModalStyles: ( + type, + windowDimensions, + popoverAnchorPosition = {}, + innerContainerStyle = {}, + outerStyle = {}, + shouldUseModalPaddingStyle = true, + enableEdgeToEdgeBottomSafeAreaPadding = false, + ): GetModalStyles => { const {windowWidth, isSmallScreenWidth} = windowDimensions; let modalStyle: GetModalStyles['modalStyle'] = { @@ -223,7 +232,7 @@ const createModalStyleUtils: StyleUtilGenerator = ({the modalContainerStyle.paddingBottom = variables.componentBorderRadiusLarge; } - shouldAddBottomSafeAreaPadding = true; + shouldAddBottomSafeAreaPadding = !enableEdgeToEdgeBottomSafeAreaPadding; swipeDirection = undefined; animationIn = 'slideInUp'; animationOut = 'slideOutDown'; @@ -286,7 +295,7 @@ const createModalStyleUtils: StyleUtilGenerator = ({the }; hideBackdrop = true; swipeDirection = undefined; - shouldAddBottomSafeAreaPadding = true; + shouldAddBottomSafeAreaPadding = !enableEdgeToEdgeBottomSafeAreaPadding; shouldAddTopSafeAreaPadding = true; break; default: From 56e846262b923296cf7ce4f1eb52ceb23975d7f2 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Aug 2025 13:08:05 +0200 Subject: [PATCH 02/21] move variable --- src/components/Modal/BaseModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 7c19bf7b2f72..aa0aa35e08fd 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -152,8 +152,6 @@ function BaseModal( }: BaseModalProps, ref: React.ForwardedRef, ) { - // When the `enableEdgeToEdgeBottomSafeAreaPadding` prop is explicitly set, we enable edge-to-edge mode. - const isUsingEdgeToEdgeMode = enableEdgeToEdgeBottomSafeAreaPadding !== undefined; const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -342,6 +340,8 @@ function BaseModal( ], ); + // When the `enableEdgeToEdgeBottomSafeAreaPadding` prop is explicitly set, we enable edge-to-edge mode. + const isUsingEdgeToEdgeMode = enableEdgeToEdgeBottomSafeAreaPadding !== undefined; const modalPaddingStyles = useMemo(() => { const paddings = StyleUtils.getModalPaddingStyles({ shouldAddBottomSafeAreaMargin, From 5f2c290a8ff5bbf11f916f240197a2179fe54ef2 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Aug 2025 13:08:20 +0200 Subject: [PATCH 03/21] style: update line spacing --- src/components/Modal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 962508caffad..0980770e91f3 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -86,6 +86,6 @@ function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = ( ); } - Modal.displayName = 'Modal'; + export default Modal; From 349891c882e0427e0ed6aaf9c1639ae1abca7724 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 19 Aug 2025 13:28:12 +0200 Subject: [PATCH 04/21] refactor: migrate ConfirmModal --- src/components/ConfirmContent.tsx | 4 +++- src/components/ConfirmModal.tsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index e15e1276f97d..f93abd95ac5d 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -2,6 +2,7 @@ import type {ReactNode} from 'react'; import React from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; @@ -138,6 +139,7 @@ function ConfirmContent({ const {translate} = useLocalize(); const theme = useTheme(); const {isOffline} = useNetwork(); + const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true}); const isCentered = shouldCenterContent; @@ -155,7 +157,7 @@ function ConfirmContent({ )} - + {shouldShowDismissIcon && ( diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index f5c105e6f1b5..5708acf353c2 100755 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -175,6 +175,7 @@ function ConfirmModal({ restoreFocusType={restoreFocusType} shouldHandleNavigationBack={shouldHandleNavigationBack} shouldUseReanimatedModal + enableEdgeToEdgeBottomSafeAreaPadding > Date: Thu, 19 Feb 2026 14:53:01 +0000 Subject: [PATCH 05/21] fix: remove unused component --- src/components/PopoverMenu.tsx | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index a7ab536f4328..3ba4c2ae9f5f 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -640,35 +640,6 @@ function BasePopoverMenu({ ); } -BasePopoverMenu.displayName = 'BasePopoverMenu'; - -function PopoverMenuContent({ - shouldUseScrollView, - contentContainerStyle, - children, - addBottomSafeAreaPadding, -}: { - shouldUseScrollView: boolean; - contentContainerStyle: StyleProp; - children: ReactNode; - addBottomSafeAreaPadding?: boolean; -}): React.JSX.Element { - const style = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding, style: contentContainerStyle}); - - if (shouldUseScrollView) { - return ( - - {children} - - ); - } - // eslint-disable-next-line react/jsx-no-useless-fragment - return {children}; -} -PopoverMenuContent.displayName = 'PopoverMenuContent'; export default React.memo( PopoverMenu, From 345e201e9541cd6672c41d8b8dca5c0053755e0e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 20 Feb 2026 21:10:51 +0000 Subject: [PATCH 06/21] fix: RN modal translucency patch --- ....4+024+fix-modal-transparent-navigation-bar.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch b/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch index 966af101c2fa..57a2682f0b81 100644 --- a/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch +++ b/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch @@ -1,9 +1,9 @@ diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt -index c711027..7255545 100644 +index c711027..63469ea 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt -@@ -382,6 +382,20 @@ public class ReactModalHostView(context: ThemedReactContext) : - } +@@ -390,6 +390,20 @@ public class ReactModalHostView(context: ThemedReactContext) : + dialogWindow.setStatusBarTranslucency(statusBarTranslucent) } + // If enforceNavigationBarContrast is explicitly set in the app theme (AppTheme), @@ -20,6 +20,6 @@ index c711027..7255545 100644 + ta.recycle() + } + - // Navigation bar cannot be translucent without status bar being translucent too - if (navigationBarTranslucent) { - dialogWindow.enableEdgeToEdge() + if (transparent) { + dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + } else { From 175709665a3e9c30448e6472c599b05f501b9802 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 11:06:29 +0000 Subject: [PATCH 07/21] fix: remove unsupported `android: enforceNavigationBarContrast` style from v26 --- android/app/src/main/res/values-v26/styles.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/app/src/main/res/values-v26/styles.xml b/android/app/src/main/res/values-v26/styles.xml index e114055c9447..18f4cd42531f 100644 --- a/android/app/src/main/res/values-v26/styles.xml +++ b/android/app/src/main/res/values-v26/styles.xml @@ -8,7 +8,6 @@ true false @android:color/transparent - false From a6af79dbf8bf17f882e63d5246f2638a0822d556 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 11:06:42 +0000 Subject: [PATCH 08/21] fix: add `android:navigationBarColor` style to v29 --- android/app/src/main/res/values-v29/styles.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/res/values-v29/styles.xml b/android/app/src/main/res/values-v29/styles.xml index 24a6c3efcc6f..87475dd7426f 100644 --- a/android/app/src/main/res/values-v29/styles.xml +++ b/android/app/src/main/res/values-v29/styles.xml @@ -7,5 +7,6 @@ true @android:color/transparent false + @android:color/transparent - \ No newline at end of file + From f2753504fff0b36bbd6fc10173bb988a65a74c5e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 12:14:07 +0000 Subject: [PATCH 09/21] fix: update modal transparent navigation bar patch --- ...fix-modal-transparent-navigation-bar.patch | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch b/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch index 57a2682f0b81..1cd26a419a6f 100644 --- a/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch +++ b/patches/react-native/react-native+0.81.4+024+fix-modal-transparent-navigation-bar.patch @@ -1,25 +1,17 @@ -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt -index c711027..63469ea 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt -@@ -390,6 +390,20 @@ public class ReactModalHostView(context: ThemedReactContext) : - dialogWindow.setStatusBarTranslucency(statusBarTranslucent) - } +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +index 60f2a94..6be6541 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +@@ -109,7 +109,11 @@ internal fun Window.enableEdgeToEdge() { -+ // If enforceNavigationBarContrast is explicitly set in the app theme (AppTheme), -+ // we need to override the default behaviour for edge-to-edge mode in WindowUtils.setSystemBarsTranslucency. -+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { -+ val attrs = intArrayOf(android.R.attr.enforceNavigationBarContrast) -+ val ta = context.obtainStyledAttributes(R.style.Theme_FullScreenDialog, attrs) -+ -+ if (ta.length() == 1) { -+ val enforceNavigationBarContrastStyleValue = ta.getBoolean(0, false) -+ dialogWindow.isNavigationBarContrastEnforced = enforceNavigationBarContrastStyleValue -+ } -+ -+ ta.recycle() -+ } -+ - if (transparent) { - dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + isStatusBarContrastEnforced = false +- isNavigationBarContrastEnforced = true ++ // Respect app theme so android:enforceNavigationBarContrast is applied (e.g. false for transparent nav bar). ++ val contrastAttrs = intArrayOf(android.R.attr.enforceNavigationBarContrast) ++ val ta = context.theme.obtainStyledAttributes(contrastAttrs) ++ isNavigationBarContrastEnforced = if (ta.length() == 1) ta.getBoolean(0, false) else false ++ ta.recycle() + } + + statusBarColor = Color.TRANSPARENT From 9651bedcb118c43057897dacc5ccfc5b30840791 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 12:23:16 +0000 Subject: [PATCH 10/21] fix: remove duplicate comment --- src/components/Modal/BaseModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 581ebfaf8db1..c8f7e7605a7e 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -70,7 +70,6 @@ function BaseModal({ ref, shouldDisplayBelowModals = false, }: BaseModalProps) { - // When the `enableEdgeToEdgeBottomSafeAreaPadding` prop is explicitly set, we enable edge-to-edge mode. const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); From 41513b81b84f70997730c86427c0c0d818e57073 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 12:23:46 +0000 Subject: [PATCH 11/21] fix: add bottom safe are padding to `ReportActionContextMenu` --- .../inbox/report/ContextMenu/BaseReportActionContextMenu.tsx | 5 ++++- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx index 5550bb4900cf..a6df837fa79f 100755 --- a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -42,6 +42,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OriginalMessageIOU, ReportAction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import type {ContextMenuAction, ContextMenuActionPayload} from './ContextMenuActions'; import ContextMenuActions from './ContextMenuActions'; import type {ContextMenuAnchor, ContextMenuType} from './ReportActionContextMenu'; @@ -360,12 +361,14 @@ function BaseReportActionContextMenu({ // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const card = useGetExpensifyCardFromReportAction({reportAction: (reportAction ?? null) as ReportAction, policyID}); + const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true}); + return ( (isVisible || shouldKeepOpen || !isMini) && ( {filteredContextMenuActions.map((contextAction, index) => { const closePopup = !isMini; diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index bb36f3a702a1..68a8e70ceb09 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -459,6 +459,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro anchorDimensions={contextMenuDimensions.current} anchorRef={anchorRef} shouldSwitchPositionIfOverflow={shouldSwitchPositionIfOverflow} + enableEdgeToEdgeBottomSafeAreaPadding > Date: Mon, 23 Feb 2026 12:25:17 +0000 Subject: [PATCH 12/21] fix: disable modal container style in edge-to-edge mode --- src/styles/utils/generators/ModalStyleUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts index 8763c4d2d769..262781474cbd 100644 --- a/src/styles/utils/generators/ModalStyleUtils.ts +++ b/src/styles/utils/generators/ModalStyleUtils.ts @@ -245,7 +245,10 @@ const createModalStyleUtils: StyleUtilGenerator = ({the if (shouldUseModalPaddingStyle) { modalContainerStyle.paddingTop = variables.componentBorderRadiusLarge; - modalContainerStyle.paddingBottom = variables.componentBorderRadiusLarge; + + if (!enableEdgeToEdgeBottomSafeAreaPadding) { + modalContainerStyle.paddingBottom = variables.componentBorderRadiusLarge; + } } shouldAddBottomSafeAreaPadding = !enableEdgeToEdgeBottomSafeAreaPadding && !safeAreaOptions?.shouldDisableBottomSafeAreaPadding; From 8884d74a5688ae7ce7c9c30a61f5720495630bdb Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 12:46:36 +0000 Subject: [PATCH 13/21] fix: make `BaseModal` component RC compatible --- src/components/Modal/BaseModal.tsx | 107 ++++++++++------------------- 1 file changed, 37 insertions(+), 70 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index c8f7e7605a7e..d0afa3b6d45d 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useRef, useState} from 'react'; import type {LayoutChangeEvent} from 'react-native'; // Animated required for side panel navigation // eslint-disable-next-line no-restricted-imports @@ -61,7 +61,7 @@ function BaseModal({ swipeDirection, shouldPreventScrollOnFocus = false, enableEdgeToEdgeBottomSafeAreaPadding, - shouldApplySidePanelOffset = type === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED, + shouldApplySidePanelOffset: shouldApplySidePanelOffsetProp, hasBackdrop, backdropOpacity, shouldDisableBottomSafeAreaPadding = false, @@ -80,6 +80,9 @@ function BaseModal({ const {isSmallScreenWidth, shouldUseNarrowLayout, isInNarrowPaneModal} = useResponsiveLayout(); const {sidePanelOffset} = useSidePanelState(); + + // This prop does not have a default value, because React Compiler throws an internal error if this is provided as a default value. + const shouldApplySidePanelOffset = shouldApplySidePanelOffsetProp ?? type === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED; const sidePanelAnimatedStyle = shouldApplySidePanelOffset && !isSmallScreenWidth ? {transform: [{translateX: Animated.multiply(sidePanelOffset.current, -1)}]} : undefined; const keyboardStateContextValue = useKeyboardState(); @@ -93,13 +96,13 @@ function BaseModal({ const wasVisible = usePrevious(isVisible); - const uniqueModalId = useMemo(() => modalId ?? ComposerFocusManager.getId(), [modalId]); - const saveFocusState = useCallback(() => { + const uniqueModalId = modalId ?? ComposerFocusManager.getId(); + const saveFocusState = () => { if (shouldEnableNewFocusManagement) { ComposerFocusManager.saveFocusState(uniqueModalId); } ComposerFocusManager.resetReadyToFocus(uniqueModalId); - }, [shouldEnableNewFocusManagement, uniqueModalId]); + }; /** * Hides modal * @param callHideCallback - Should we call the onModalHide callback @@ -161,18 +164,17 @@ function BaseModal({ } hideModalCallbackRef.current?.(true); }, - // eslint-disable-next-line react-hooks/exhaustive-deps [], ); useEffect(() => () => DeviceEventEmitter.emit(CONST.MODAL_EVENTS.CLOSED), []); - const handleShowModal = useCallback(() => { + const handleShowModal = () => { if (shouldSetModalVisibility) { setModalVisibility(true, type); } onModalShow(); - }, [onModalShow, shouldSetModalVisibility, type]); + }; const handleBackdropPress = (e?: KeyboardEvent) => { if (e?.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) { @@ -222,85 +224,50 @@ function BaseModal({ shouldAddTopSafeAreaPadding, shouldAddBottomSafeAreaPadding, hideBackdrop, - } = useMemo( - () => - StyleUtils.getModalStyles({ - type, - windowDimensions: { - windowWidth, - windowHeight, - isSmallScreenWidth, - shouldUseNarrowLayout, - }, - popoverAnchorPosition, - innerContainerStyle, - outerStyle, - shouldUseModalPaddingStyle, - safeAreaOptions: { - modalOverlapsWithTopSafeArea, - shouldDisableBottomSafeAreaPadding: !!shouldDisableBottomSafeAreaPadding, - }, - enableEdgeToEdgeBottomSafeAreaPadding, - shouldDisplayBelowModals, - }), - [ - StyleUtils, - type, + } = StyleUtils.getModalStyles({ + type, + windowDimensions: { windowWidth, windowHeight, isSmallScreenWidth, shouldUseNarrowLayout, - popoverAnchorPosition, - innerContainerStyle, - outerStyle, - shouldUseModalPaddingStyle, + }, + popoverAnchorPosition, + innerContainerStyle, + outerStyle, + shouldUseModalPaddingStyle, + safeAreaOptions: { modalOverlapsWithTopSafeArea, - shouldDisableBottomSafeAreaPadding, - shouldDisplayBelowModals, - enableEdgeToEdgeBottomSafeAreaPadding, - ], - ); + shouldDisableBottomSafeAreaPadding: !!shouldDisableBottomSafeAreaPadding, + }, + enableEdgeToEdgeBottomSafeAreaPadding, + shouldDisplayBelowModals, + }); // When the `enableEdgeToEdgeBottomSafeAreaPadding` prop is explicitly set, we enable edge-to-edge mode. const isUsingEdgeToEdgeMode = enableEdgeToEdgeBottomSafeAreaPadding !== undefined; - const modalPaddingStyles = useMemo(() => { - const paddings = StyleUtils.getModalPaddingStyles({ - shouldAddBottomSafeAreaMargin, - shouldAddTopSafeAreaMargin, - // enableEdgeToEdgeBottomSafeAreaPadding is used as a temporary solution to disable safe area bottom spacing on modals, to allow edge-to-edge content - shouldAddBottomSafeAreaPadding: !isUsingEdgeToEdgeMode && (!avoidKeyboard || !keyboardStateContextValue.isKeyboardActive) && shouldAddBottomSafeAreaPadding, - shouldAddTopSafeAreaPadding, - modalContainerStyle, - insets, - }); - return shouldUseModalPaddingStyle ? paddings : {paddingLeft: paddings.paddingLeft, paddingRight: paddings.paddingRight}; - }, [ - StyleUtils, - avoidKeyboard, - insets, - isUsingEdgeToEdgeMode, - keyboardStateContextValue.isKeyboardActive, - modalContainerStyle, + + const paddings = StyleUtils.getModalPaddingStyles({ shouldAddBottomSafeAreaMargin, - shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaMargin, + // enableEdgeToEdgeBottomSafeAreaPadding is used as a temporary solution to disable safe area bottom spacing on modals, to allow edge-to-edge content + shouldAddBottomSafeAreaPadding: !isUsingEdgeToEdgeMode && (!avoidKeyboard || !keyboardStateContextValue.isKeyboardActive) && shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaPadding, - shouldUseModalPaddingStyle, - ]); + modalContainerStyle, + insets, + }); + const modalPaddingStyles = shouldUseModalPaddingStyle ? paddings : {paddingLeft: paddings.paddingLeft, paddingRight: paddings.paddingRight}; - const modalContextValue = useMemo( - () => ({ - activeModalType: isVisible ? type : undefined, - default: false, - }), - [isVisible, type], - ); + const modalContextValue = { + activeModalType: isVisible ? type : undefined, + default: false, + }; // In Modals we need to reset the ScreenWrapperOfflineIndicatorContext to allow nested ScreenWrapper components to render offline indicators, // except if we are in a narrow pane navigator. In this case, we use the narrow pane's original values. const {isInNarrowPane} = useContext(NarrowPaneContext); const {originalValues} = useContext(ScreenWrapperOfflineIndicatorContext); - const offlineIndicatorContextValue = useMemo(() => (isInNarrowPane ? (originalValues ?? {}) : {}), [isInNarrowPane, originalValues]); + const offlineIndicatorContextValue = isInNarrowPane ? (originalValues ?? {}) : {}; const backdropOpacityAdjusted = hideBackdrop || (type === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED && !isSmallScreenWidth && (isInNarrowPane || isInNarrowPaneModal)) // right_docked modals shouldn't add backdrops when opened in same-width RHP From 2181dab0108cbeb4c7722ff2dee656404106ef2e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 12:48:52 +0000 Subject: [PATCH 14/21] fix: invalid style --- .../inbox/report/ContextMenu/BaseReportActionContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx index a6df837fa79f..7b59385f4b8b 100755 --- a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -368,7 +368,7 @@ function BaseReportActionContextMenu({ {filteredContextMenuActions.map((contextAction, index) => { const closePopup = !isMini; From d10f5ddd6924de09c532479230f22e1c714ed52c Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 13:10:32 +0000 Subject: [PATCH 15/21] refactor: `DatePickerModal` --- src/components/DatePicker/DatePickerModal.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/DatePicker/DatePickerModal.tsx b/src/components/DatePicker/DatePickerModal.tsx index 88f71d53cabf..f83441f8b430 100644 --- a/src/components/DatePicker/DatePickerModal.tsx +++ b/src/components/DatePicker/DatePickerModal.tsx @@ -1,11 +1,12 @@ import {setYear} from 'date-fns'; import React, {useEffect, useRef, useState} from 'react'; -import type {View} from 'react-native'; +import {View} from 'react-native'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {setDraftValues} from '@userActions/FormActions'; import CONST from '@src/CONST'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import CalendarPicker from './CalendarPicker'; import type {DatePickerProps} from './types'; @@ -60,6 +61,8 @@ function DatePickerModal({ setSelectedDate(newValue); }; + const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true}); + return ( - + + + ); } From e05e885c4e5285938d1ae0642ea2db1ac04aea78 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 13:11:05 +0000 Subject: [PATCH 16/21] fix: omit `style` property, since we are not using it --- src/components/Modal/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 4db73a9bd119..56a9c5a34663 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -20,7 +20,7 @@ type WindowState = { shouldGoBack: boolean; }; -type BaseModalProps = Partial & +type BaseModalProps = Omit, 'style'> & ForwardedFSClassProps & { /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ fullscreen?: boolean; From 7f65b86d568b51c04c6f3215d62a27f0e116bd03 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 23 Feb 2026 13:13:38 +0000 Subject: [PATCH 17/21] fix: `EmojiPicker` modal --- src/components/EmojiPicker/EmojiPicker.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index 1fec2354705b..d9cb24c09c40 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -22,6 +22,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import {close} from '@userActions/Modal'; import CONST from '@src/CONST'; import KeyboardUtils from '@src/utils/keyboard'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import EmojiPickerMenu from './EmojiPickerMenu'; const DEFAULT_ANCHOR_ORIGIN = { @@ -235,6 +236,8 @@ function EmojiPicker({viewportOffsetTop, ref}: EmojiPickerProps) { }; }, [isEmojiPickerVisible, shouldUseNarrowLayout, emojiPopoverAnchorOrigin, getEmojiPopoverAnchor, hideEmojiPicker]); + const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true}); + return ( - + Date: Mon, 13 Apr 2026 23:26:16 +0100 Subject: [PATCH 18/21] fix: prettier errors --- src/components/DatePicker/DatePickerModal.tsx | 2 +- .../inbox/report/ContextMenu/BaseReportActionContextMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DatePicker/DatePickerModal.tsx b/src/components/DatePicker/DatePickerModal.tsx index 94f04fa1cead..a0091ed5466f 100644 --- a/src/components/DatePicker/DatePickerModal.tsx +++ b/src/components/DatePicker/DatePickerModal.tsx @@ -2,11 +2,11 @@ import {setYear} from 'date-fns'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {setDraftValues} from '@userActions/FormActions'; import CONST from '@src/CONST'; -import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import CalendarPicker from './CalendarPicker'; import type {DatePickerProps} from './types'; diff --git a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx index cb2728690c1f..49eb75549b24 100755 --- a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -12,6 +12,7 @@ import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/ import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal'; import {useSession} from '@components/OnyxListItemProvider'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; +import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useEnvironment from '@hooks/useEnvironment'; import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromReportAction'; @@ -44,7 +45,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OriginalMessageIOU, ReportAction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; import type {ContextMenuAction, ContextMenuActionPayload} from './ContextMenuActions'; import ContextMenuActions from './ContextMenuActions'; import type {ContextMenuAnchor, ContextMenuType} from './ReportActionContextMenu'; From 96da0d750c0dc9875fe2f3bb5ee5acad68bfeb24 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 13 Apr 2026 23:26:22 +0100 Subject: [PATCH 19/21] fix: spell check --- src/components/PopoverMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index b43b85a09c68..b4b7d938fb12 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -202,7 +202,7 @@ type PopoverMenuContentProps = { addBottomSafeAreaPadding?: boolean; }; -function PoppoverMenuContent({shouldUseScrollView, contentContainerStyle, children, addBottomSafeAreaPadding}: PopoverMenuContentProps): React.JSX.Element { +function PopoverMenuContent({shouldUseScrollView, contentContainerStyle, children, addBottomSafeAreaPadding}: PopoverMenuContentProps): React.JSX.Element { const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding}); if (shouldUseScrollView) { @@ -654,7 +654,7 @@ function BasePopoverMenu({ onLayout={onLayout} style={[restMenuContainerStyle, restContainerStyles, isWeb ? styles.flex1 : styles.flexGrow1]} > - 0 && renderBackButtonItem()} {renderedMenuItems} - + From 7a9ba4ea8746e4b438a33f6eb454b131725213e9 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 13 Apr 2026 23:34:00 +0100 Subject: [PATCH 20/21] fix: TS errors --- src/components/Modal/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 6112b5dd7a3a..e13c7b82020f 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -20,7 +20,7 @@ type WindowState = { shouldGoBack: boolean; }; -type BaseModalProps = Omit, 'style'> & +type BaseModalProps = Partial & ForwardedFSClassProps & { /** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */ fullscreen?: boolean; From ffa83ed12f702f9d5a94570ffb432e98e3c21009 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 13 Apr 2026 23:35:01 +0100 Subject: [PATCH 21/21] revert: navigation bar translucency changes Extracted to https://github.com/Expensify/App/pull/87810 --- .../app/src/main/res/values-v26/styles.xml | 7 +--- .../app/src/main/res/values-v29/styles.xml | 3 +- .../app/src/main/res/values-v31/styles.xml | 9 ----- android/app/src/main/res/values/styles.xml | 1 + ...fix-modal-transparent-navigation-bar.patch | 40 +++++++++++-------- 5 files changed, 27 insertions(+), 33 deletions(-) diff --git a/android/app/src/main/res/values-v26/styles.xml b/android/app/src/main/res/values-v26/styles.xml index 18f4cd42531f..fbef3b91f60f 100644 --- a/android/app/src/main/res/values-v26/styles.xml +++ b/android/app/src/main/res/values-v26/styles.xml @@ -1,13 +1,8 @@ + diff --git a/android/app/src/main/res/values-v29/styles.xml b/android/app/src/main/res/values-v29/styles.xml index 87475dd7426f..24a6c3efcc6f 100644 --- a/android/app/src/main/res/values-v29/styles.xml +++ b/android/app/src/main/res/values-v29/styles.xml @@ -7,6 +7,5 @@ true @android:color/transparent false - @android:color/transparent - + \ No newline at end of file diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml index d53942151288..eea420608451 100644 --- a/android/app/src/main/res/values-v31/styles.xml +++ b/android/app/src/main/res/values-v31/styles.xml @@ -1,13 +1,4 @@ - + diff --git a/patches/react-native/react-native+0.83.1+022+fix-modal-transparent-navigation-bar.patch b/patches/react-native/react-native+0.83.1+022+fix-modal-transparent-navigation-bar.patch index 1cd26a419a6f..966af101c2fa 100644 --- a/patches/react-native/react-native+0.83.1+022+fix-modal-transparent-navigation-bar.patch +++ b/patches/react-native/react-native+0.83.1+022+fix-modal-transparent-navigation-bar.patch @@ -1,17 +1,25 @@ -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt -index 60f2a94..6be6541 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt -@@ -109,7 +109,11 @@ internal fun Window.enableEdgeToEdge() { +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +index c711027..7255545 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +@@ -382,6 +382,20 @@ public class ReactModalHostView(context: ThemedReactContext) : + } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - isStatusBarContrastEnforced = false -- isNavigationBarContrastEnforced = true -+ // Respect app theme so android:enforceNavigationBarContrast is applied (e.g. false for transparent nav bar). -+ val contrastAttrs = intArrayOf(android.R.attr.enforceNavigationBarContrast) -+ val ta = context.theme.obtainStyledAttributes(contrastAttrs) -+ isNavigationBarContrastEnforced = if (ta.length() == 1) ta.getBoolean(0, false) else false -+ ta.recycle() - } - - statusBarColor = Color.TRANSPARENT ++ // If enforceNavigationBarContrast is explicitly set in the app theme (AppTheme), ++ // we need to override the default behaviour for edge-to-edge mode in WindowUtils.setSystemBarsTranslucency. ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ++ val attrs = intArrayOf(android.R.attr.enforceNavigationBarContrast) ++ val ta = context.obtainStyledAttributes(R.style.Theme_FullScreenDialog, attrs) ++ ++ if (ta.length() == 1) { ++ val enforceNavigationBarContrastStyleValue = ta.getBoolean(0, false) ++ dialogWindow.isNavigationBarContrastEnforced = enforceNavigationBarContrastStyleValue ++ } ++ ++ ta.recycle() ++ } ++ + // Navigation bar cannot be translucent without status bar being translucent too + if (navigationBarTranslucent) { + dialogWindow.enableEdgeToEdge()