mattermost-mobile/app/screens/gallery/index.tsx
Guillermo Vayá f024e00ca6
[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
2026-01-12 14:52:41 +01:00

121 lines
4.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
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';
import SecurityManager from '@managers/security_manager';
import {dismissOverlay, setScreensOrientation} from '@screens/navigation';
import {freezeOtherScreens} from '@utils/gallery';
import Footer from './footer';
import Gallery, {type GalleryRef} from './gallery';
import Header from './header';
import type {GalleryItemType} from '@typings/screens/gallery';
import type {AvailableScreens} from '@typings/screens/navigation';
type Props = {
componentId: AvailableScreens;
galleryIdentifier: string;
hideActions: boolean;
initialIndex: number;
items: GalleryItemType[];
}
const GalleryScreen = ({componentId, galleryIdentifier, hideActions, initialIndex, items}: Props) => {
const dim = useWindowDimensions();
const isTablet = useIsTablet();
const {bottom: bottomInset} = useSafeAreaInsets();
const [localIndex, setLocalIndex] = useState(initialIndex);
// 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;
const onClose = useCallback(() => {
// We keep the un freeze here as we want
// the screen to be visible when the gallery
// starts to dismiss as the hanlder for shouldHandleEvent
// of the lightbox is not called
freezeOtherScreens(false);
requestAnimationFrame(() => {
galleryRef.current?.close();
});
}, []);
const close = useCallback(() => {
setScreensOrientation(isTablet);
if (Platform.OS === 'ios' && !isTablet) {
// We need both the navigation & the module
RNUtils.lockPortrait();
}
freezeOtherScreens(false);
requestAnimationFrame(async () => {
dismissOverlay(componentId);
});
}, [componentId, isTablet]);
const onIndexChange = useCallback((index: number) => {
setLocalIndex(index);
}, []);
useEffect(() => {
const listener = DeviceEventEmitter.addListener(Events.CLOSE_GALLERY, () => {
onClose();
});
if (Platform.OS === 'android' && Platform.Version >= 34) {
RNUtils.setNavigationBarColor('black', true);
}
return () => {
listener.remove();
};
}, [onClose]);
useAndroidHardwareBackHandler(componentId, close);
return (
<View
style={containerStyle}
nativeID={SecurityManager.getShieldScreenId(componentId)}
>
<Header
index={localIndex}
onClose={onClose}
style={headerStyles}
total={items.length}
/>
<Gallery
headerAndFooterHidden={headerAndFooterHidden}
galleryIdentifier={galleryIdentifier}
initialIndex={initialIndex}
items={items}
onHide={close}
onIndexChange={onIndexChange}
hideHeaderAndFooter={hideHeaderAndFooter}
ref={galleryRef}
targetDimensions={dim}
/>
<Footer
hideActions={hideActions}
item={items[localIndex]}
style={footerStyles}
/>
</View>
);
};
export default GalleryScreen;