[MM-67056] Show actions on image gallery for android (#9386) (#9423)

* show actions on image gallery for android

* use a constant, update dependencies

* use original window metrics if safe insets fail

* restore deps

(cherry picked from commit f024e00ca6)

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>
This commit is contained in:
Mattermost Build 2026-01-19 11:13:09 +02:00 committed by GitHub
parent d066425e6d
commit e48a4c0e09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 19 additions and 11 deletions

View file

@ -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;

View file

@ -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,

View file

@ -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<GalleryAction>('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 = ({
/>
}
</View>
<View style={bottomStyle}/>
</Animated.View>
);
};

View file

@ -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<GalleryRef>(null);
const containerStyle = dim;