From f024e00ca690f5b72e67ea247ee391ec38bdfac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Vay=C3=A1?= Date: Mon, 12 Jan 2026 14:52:41 +0100 Subject: [PATCH] [MM-67056] Show actions on image gallery for android (#9386) * show actions on image gallery for android * use a constant, update dependencies * use original window metrics if safe insets fail * restore deps --- app/constants/gallery.ts | 7 +++++++ app/hooks/gallery.ts | 8 ++++---- app/screens/gallery/footer/footer.tsx | 6 ------ app/screens/gallery/index.tsx | 9 ++++++++- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/constants/gallery.ts b/app/constants/gallery.ts index c780fb0d2..e0819f787 100644 --- a/app/constants/gallery.ts +++ b/app/constants/gallery.ts @@ -5,6 +5,13 @@ export const GALLERY_FOOTER_HEIGHT = 75; export const VIDEO_INSET = 100; export const ANDROID_VIDEO_INSET = 20; +// Fallback for Android navigation bar height when SafeAreaContext returns 0 in overlays +// Typical Android 3-button navigation bar height is 48-63dp +export const ANDROID_NAV_BAR_HEIGHT = 63; + +// Additional padding for Android gallery footer to ensure visibility above navigation bar +export const ANDROID_GALLERY_FOOTER_PADDING = 10; + export const DOUBLE_TAP_SCALE = 4; export const MAX_SCALE = 7; export const MIN_SCALE = 0.7; diff --git a/app/hooks/gallery.ts b/app/hooks/gallery.ts index ff444aa9b..1a346add5 100644 --- a/app/hooks/gallery.ts +++ b/app/hooks/gallery.ts @@ -37,7 +37,7 @@ export const translateYConfig: WithTimingConfig = { easing: Easing.bezier(0.33, 0.01, 0, 1), }; -export function useGalleryControls() { +export function useGalleryControls(bottomInset = 0) { const headerAndFooterHidden = useSharedValue(false); const headerStyles = useAnimatedStyle(() => ({ @@ -61,10 +61,10 @@ export function useGalleryControls() { }, ], position: 'absolute', - bottom: 0, + bottom: bottomInset, width: '100%', zIndex: 1, - })); + }), [bottomInset]); const hideHeaderAndFooter = useCallback((hidden?: boolean) => { 'worklet'; @@ -80,7 +80,7 @@ export function useGalleryControls() { } headerAndFooterHidden.value = hidden; - }, []); + }, [headerAndFooterHidden]); return { headerAndFooterHidden, diff --git a/app/screens/gallery/footer/footer.tsx b/app/screens/gallery/footer/footer.tsx index 51dadc9e3..3849e14ca 100644 --- a/app/screens/gallery/footer/footer.tsx +++ b/app/screens/gallery/footer/footer.tsx @@ -4,7 +4,6 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {DeviceEventEmitter, type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native'; import Animated from 'react-native-reanimated'; -import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {Events} from '@constants'; import {GALLERY_FOOTER_HEIGHT} from '@constants/gallery'; @@ -48,7 +47,6 @@ const styles = StyleSheet.create({ backgroundColor: '#000', borderTopColor: changeOpacity('#fff', 0.4), borderTopWidth: 1, - flex: 1, flexDirection: 'row', justifyContent: 'center', height: GALLERY_FOOTER_HEIGHT, @@ -65,9 +63,6 @@ const Footer = ({ const serverUrl = useServerUrl(); const showActions = !hideActions && Boolean(item.id) && !item.id?.startsWith('uid'); const [action, setAction] = useState('none'); - const {bottom} = useSafeAreaInsets(); - - const bottomStyle = useMemo(() => ({height: bottom, backgroundColor: '#000'}), [bottom]); let overrideIconUrl; if (enablePostIconOverride && post?.props?.use_user_icon !== 'true' && post?.props?.override_icon_url) { @@ -157,7 +152,6 @@ const Footer = ({ /> } - ); }; diff --git a/app/screens/gallery/index.tsx b/app/screens/gallery/index.tsx index f389e86ae..db810dbb9 100644 --- a/app/screens/gallery/index.tsx +++ b/app/screens/gallery/index.tsx @@ -4,8 +4,10 @@ import RNUtils from '@mattermost/rnutils'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {DeviceEventEmitter, Platform, View} from 'react-native'; +import {initialWindowMetrics, useSafeAreaInsets} from 'react-native-safe-area-context'; import {Events} from '@constants'; +import {ANDROID_GALLERY_FOOTER_PADDING, ANDROID_NAV_BAR_HEIGHT} from '@constants/gallery'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; import {useIsTablet, useWindowDimensions} from '@hooks/device'; import {useGalleryControls} from '@hooks/gallery'; @@ -31,8 +33,13 @@ type Props = { const GalleryScreen = ({componentId, galleryIdentifier, hideActions, initialIndex, items}: Props) => { const dim = useWindowDimensions(); const isTablet = useIsTablet(); + const {bottom: bottomInset} = useSafeAreaInsets(); const [localIndex, setLocalIndex] = useState(initialIndex); - const {headerAndFooterHidden, hideHeaderAndFooter, headerStyles, footerStyles} = useGalleryControls(); + + // Fallback for Android when SafeAreaContext returns 0 in overlays + const androidBottom = (initialWindowMetrics?.insets.bottom || ANDROID_NAV_BAR_HEIGHT) + ANDROID_GALLERY_FOOTER_PADDING; + const bottom = bottomInset || Platform.select({android: androidBottom, default: 0}); + const {headerAndFooterHidden, hideHeaderAndFooter, headerStyles, footerStyles} = useGalleryControls(bottom); const galleryRef = useRef(null); const containerStyle = dim;