mattermost-mobile/app/screens/gallery/index.tsx
Elias Nahum a5a1e53827
Biometric prompt, Jailbreak / Root detection and screenshot prevention (#8645)
* Handle biometric authentication

* jailbreak/root detection and biometric small fixes

* remove server from initializeSecurityManager and fix loginEntry

* Add screen capture prevention and other small fixes

* added unit tests to SecurityManager

* added shielded nativeID to protect views

* use MobilePreventScreenCapture instead of MobileAllowScreenshots in config type definition

* Apply Swizzle for screen capture on iOS

* Apply patch to bottom sheet to prevent screen captures

* fix ios sendReply

* Fix SDWebImage swizzle to use the correct session

* Fix potential crash on Android when using hardware keyboard

* rename patch for network library to remove warning

* add temp emm reference

* fix initializeSecurityManager tests

* fix translations

* use siteName for jailbreak detection when connecting to a new server

* fix i18n typo

* do not query the entire config from the db only the required fields

* migrate manage_apps to use defineMessages

* use TestHelper.wait in tests

* use defineMessages for security manager

* fix missing else statement for gm_to_channel

* created a TestHelper function to mockQuery and replace as jest.Mock with jest.mocked

* fix unit tests

* fix unit tests (again) and include setting the test environment to UTC

* Fix keyboard disappearing on iOS

* update react-native-emm
2025-03-13 14:07:41 -04:00

125 lines
4.6 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, useMemo, useRef, useState} from 'react';
import {Platform, StyleSheet, View} from 'react-native';
import {CaptionsEnabledContext} from '@calls/context';
import {hasCaptions} from '@calls/utils';
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 styles = StyleSheet.create({
flex: {flex: 1},
});
const GalleryScreen = ({componentId, galleryIdentifier, hideActions, initialIndex, items}: Props) => {
const dim = useWindowDimensions();
const isTablet = useIsTablet();
const [localIndex, setLocalIndex] = useState(initialIndex);
const [captionsEnabled, setCaptionsEnabled] = useState<boolean[]>(new Array(items.length).fill(true));
const [captionsAvailable, setCaptionsAvailable] = useState<boolean[]>([]);
const {setControlsHidden, headerStyles, footerStyles} = useGalleryControls();
const dimensions = useMemo(() => ({width: dim.width, height: dim.height}), [dim]);
const galleryRef = useRef<GalleryRef>(null);
useEffect(() => {
const captions = items.reduce((acc, item) => {
acc.push(hasCaptions(item.postProps));
return acc;
}, [] as boolean[]);
setCaptionsAvailable(captions);
}, [items]);
const onCaptionsPressIdx = useCallback((idx: number) => {
const enabled = [...captionsEnabled];
enabled[idx] = !enabled[idx];
setCaptionsEnabled(enabled);
}, [captionsEnabled, setCaptionsEnabled]);
const onCaptionsPress = useCallback(() => onCaptionsPressIdx(localIndex), [localIndex, onCaptionsPressIdx]);
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);
});
}, [isTablet]);
const onIndexChange = useCallback((index: number) => {
setLocalIndex(index);
}, []);
useAndroidHardwareBackHandler(componentId, close);
return (
<CaptionsEnabledContext.Provider value={captionsEnabled}>
<View
style={styles.flex}
nativeID={SecurityManager.getShieldScreenId(componentId)}
>
<Header
index={localIndex}
onClose={onClose}
style={headerStyles}
total={items.length}
/>
<Gallery
galleryIdentifier={galleryIdentifier}
initialIndex={initialIndex}
items={items}
onHide={close}
onIndexChange={onIndexChange}
onShouldHideControls={setControlsHidden}
ref={galleryRef}
targetDimensions={dimensions}
/>
<Footer
hideActions={hideActions}
item={items[localIndex]}
style={footerStyles}
hasCaptions={captionsAvailable[localIndex]}
captionEnabled={captionsEnabled[localIndex]}
onCaptionsPress={onCaptionsPress}
/>
</View>
</CaptionsEnabledContext.Provider>
);
};
export default GalleryScreen;