203 lines
6.8 KiB
TypeScript
203 lines
6.8 KiB
TypeScript
import { CameraRoll } from '@react-native-camera-roll/camera-roll';
|
|
import { HeaderBackground, useHeaderHeight } from '@react-navigation/elements';
|
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
|
import { ResizeMode, Video } from 'expo-av';
|
|
import React from 'react';
|
|
import { PermissionsAndroid, useWindowDimensions, View } from 'react-native';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { shallowEqual } from 'react-redux';
|
|
import RNFetchBlob from 'rn-fetch-blob';
|
|
|
|
import { isImageBase64 } from '../lib/methods';
|
|
import RCActivityIndicator from '../containers/ActivityIndicator';
|
|
import * as HeaderButton from '../containers/HeaderButton';
|
|
import { ImageViewer } from '../containers/ImageViewer';
|
|
import StatusBar from '../containers/StatusBar';
|
|
import { LISTENER } from '../containers/Toast';
|
|
import { IAttachment } from '../definitions';
|
|
import I18n from '../i18n';
|
|
import { useAppSelector } from '../lib/hooks';
|
|
import { useAppNavigation, useAppRoute } from '../lib/hooks/navigation';
|
|
import { formatAttachmentUrl, isAndroid } from '../lib/methods/helpers';
|
|
import EventEmitter from '../lib/methods/helpers/events';
|
|
import { getUserSelector } from '../selectors/login';
|
|
import { TNavigation } from '../stacks/stackType';
|
|
import { useTheme } from '../theme';
|
|
import { LOCAL_DOCUMENT_DIRECTORY, getFilename } from '../lib/methods/handleMediaDownload';
|
|
|
|
const RenderContent = ({
|
|
setLoading,
|
|
attachment
|
|
}: {
|
|
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
|
attachment: IAttachment;
|
|
}) => {
|
|
const videoRef = React.useRef<Video>(null);
|
|
const insets = useSafeAreaInsets();
|
|
const { width, height } = useWindowDimensions();
|
|
const headerHeight = useHeaderHeight();
|
|
const navigation = useAppNavigation<TNavigation, 'AttachmentView'>();
|
|
const { baseUrl, user } = useAppSelector(
|
|
state => ({
|
|
baseUrl: state.server.server,
|
|
user: { id: getUserSelector(state).id, token: getUserSelector(state).token }
|
|
}),
|
|
shallowEqual
|
|
);
|
|
|
|
React.useLayoutEffect(() => {
|
|
const blurSub = navigation.addListener('blur', () => {
|
|
if (videoRef.current && videoRef.current.stopAsync) {
|
|
videoRef.current.stopAsync();
|
|
}
|
|
});
|
|
return () => {
|
|
blurSub();
|
|
};
|
|
}, [navigation]);
|
|
|
|
if (attachment.image_url) {
|
|
const url = formatAttachmentUrl(attachment.title_link || attachment.image_url, user.id, user.token, baseUrl);
|
|
const uri = encodeURI(url);
|
|
return (
|
|
<ImageViewer
|
|
uri={uri}
|
|
onLoadEnd={() => setLoading(false)}
|
|
width={width}
|
|
height={height - insets.top - insets.bottom - (headerHeight || 0)}
|
|
/>
|
|
);
|
|
}
|
|
if (attachment.video_url) {
|
|
const url = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
|
const uri = encodeURI(url);
|
|
return (
|
|
<Video
|
|
source={{ uri }}
|
|
rate={1.0}
|
|
volume={1.0}
|
|
isMuted={false}
|
|
resizeMode={ResizeMode.CONTAIN}
|
|
shouldPlay
|
|
isLooping={false}
|
|
style={{ flex: 1 }}
|
|
useNativeControls
|
|
onLoad={() => setLoading(false)}
|
|
onError={console.log}
|
|
ref={videoRef}
|
|
/>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const AttachmentView = (): React.ReactElement => {
|
|
const navigation = useAppNavigation<TNavigation, 'AttachmentView'>();
|
|
const {
|
|
params: { attachment }
|
|
} = useAppRoute<TNavigation, 'AttachmentView'>();
|
|
const [loading, setLoading] = React.useState(true);
|
|
const { colors } = useTheme();
|
|
|
|
const { baseUrl, user, Allow_Save_Media_to_Gallery } = useAppSelector(
|
|
state => ({
|
|
baseUrl: state.server.server,
|
|
user: { id: getUserSelector(state).id, token: getUserSelector(state).token },
|
|
Allow_Save_Media_to_Gallery: (state.settings.Allow_Save_Media_to_Gallery as boolean) ?? true
|
|
}),
|
|
shallowEqual
|
|
);
|
|
|
|
const setHeader = () => {
|
|
let { title } = attachment;
|
|
|
|
try {
|
|
if (title) {
|
|
title = decodeURI(title);
|
|
}
|
|
} catch {
|
|
// Do nothing
|
|
}
|
|
const options: StackNavigationOptions = {
|
|
title: title || '',
|
|
headerTitleAlign: 'center',
|
|
headerTitleStyle: { color: colors.previewTintColor },
|
|
headerTintColor: colors.previewTintColor,
|
|
headerTitleContainerStyle: { flex: 1, maxWidth: undefined },
|
|
headerLeftContainerStyle: { flexGrow: undefined, flexBasis: undefined },
|
|
headerRightContainerStyle: { flexGrow: undefined, flexBasis: undefined },
|
|
headerLeft: () => (
|
|
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={colors.previewTintColor} />
|
|
),
|
|
headerRight: () =>
|
|
Allow_Save_Media_to_Gallery && !isImageBase64(attachment.image_url) ? (
|
|
<HeaderButton.Download testID='save-image' onPress={handleSave} color={colors.previewTintColor} />
|
|
) : null,
|
|
headerBackground: () => (
|
|
<HeaderBackground style={{ backgroundColor: colors.previewBackground, shadowOpacity: 0, elevation: 0 }} />
|
|
)
|
|
};
|
|
navigation.setOptions(options);
|
|
};
|
|
|
|
React.useLayoutEffect(() => {
|
|
setHeader();
|
|
}, [navigation]);
|
|
|
|
const handleSave = async () => {
|
|
const { title_link, image_url, image_type, video_url, video_type } = attachment;
|
|
// When the attachment is a video, the video_url refers to local file and the title_link to the link
|
|
const url = video_url || title_link || image_url;
|
|
|
|
if (!url) {
|
|
return;
|
|
}
|
|
|
|
if (isAndroid) {
|
|
const rationale = {
|
|
title: I18n.t('Write_External_Permission'),
|
|
message: I18n.t('Write_External_Permission_Message'),
|
|
buttonPositive: 'Ok'
|
|
};
|
|
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, rationale);
|
|
if (!(result || result === PermissionsAndroid.RESULTS.GRANTED)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
if (LOCAL_DOCUMENT_DIRECTORY && url.startsWith(LOCAL_DOCUMENT_DIRECTORY)) {
|
|
await CameraRoll.save(url, { album: 'Rocket.Chat' });
|
|
} else {
|
|
const mediaAttachment = formatAttachmentUrl(url, user.id, user.token, baseUrl);
|
|
let filename = '';
|
|
if (image_url) {
|
|
filename = getFilename({ title: attachment.title, type: 'image', mimeType: image_type, url });
|
|
} else {
|
|
filename = getFilename({ title: attachment.title, type: 'video', mimeType: video_type, url });
|
|
}
|
|
const documentDir = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
|
|
const path = `${documentDir + filename}`;
|
|
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
|
|
await CameraRoll.save(path, { album: 'Rocket.Chat' });
|
|
file.flush();
|
|
}
|
|
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
|
} catch (e) {
|
|
EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') });
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
return (
|
|
<View style={{ backgroundColor: colors.backgroundColor, flex: 1 }}>
|
|
<StatusBar barStyle='light-content' backgroundColor={colors.previewBackground} />
|
|
<RenderContent attachment={attachment} setLoading={setLoading} />
|
|
{loading ? <RCActivityIndicator absolute size='large' /> : null}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default AttachmentView;
|