chore: migrate AttachmentView to hooks (#5003)
* migrate AttachmentView to hooks * fix types * fix types * fix lint
This commit is contained in:
parent
01577d9534
commit
47fbde566d
|
@ -0,0 +1,43 @@
|
|||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import { TNavigation } from '../../stacks/stackType';
|
||||
import {
|
||||
AdminPanelStackParamList,
|
||||
ChatsStackParamList,
|
||||
DisplayPrefStackParamList,
|
||||
DrawerParamList,
|
||||
E2EEnterYourPasswordStackParamList,
|
||||
E2ESaveYourPasswordStackParamList,
|
||||
InsideStackParamList,
|
||||
NewMessageStackParamList,
|
||||
OutsideModalParamList,
|
||||
OutsideParamList,
|
||||
ProfileStackParamList,
|
||||
SettingsStackParamList
|
||||
} from '../../stacks/types';
|
||||
|
||||
type TRoutes =
|
||||
| ChatsStackParamList
|
||||
| ProfileStackParamList
|
||||
| SettingsStackParamList
|
||||
| AdminPanelStackParamList
|
||||
| DisplayPrefStackParamList
|
||||
| DrawerParamList
|
||||
| NewMessageStackParamList
|
||||
| E2ESaveYourPasswordStackParamList
|
||||
| E2EEnterYourPasswordStackParamList
|
||||
| InsideStackParamList
|
||||
| OutsideParamList
|
||||
| OutsideModalParamList
|
||||
| TNavigation;
|
||||
|
||||
export function useAppNavigation<ParamList extends TRoutes, RouteName extends keyof ParamList = keyof ParamList>() {
|
||||
const navigation = useNavigation<StackNavigationProp<ParamList, RouteName>>();
|
||||
return navigation;
|
||||
}
|
||||
|
||||
export function useAppRoute<ParamList extends TRoutes, RouteName extends keyof ParamList = keyof ParamList>() {
|
||||
const route = useRoute<RouteProp<ParamList, RouteName>>();
|
||||
return route;
|
||||
}
|
|
@ -82,6 +82,7 @@ import {
|
|||
SettingsStackParamList
|
||||
} from './types';
|
||||
import { isIOS } from '../lib/methods/helpers';
|
||||
import { TNavigation } from './stackType';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator<ChatsStackParamList>();
|
||||
|
@ -306,7 +307,7 @@ const E2EEnterYourPasswordStackNavigator = () => {
|
|||
};
|
||||
|
||||
// InsideStackNavigator
|
||||
const InsideStack = createStackNavigator<InsideStackParamList>();
|
||||
const InsideStack = createStackNavigator<InsideStackParamList & TNavigation>();
|
||||
const InsideStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ import {
|
|||
ModalStackParamList
|
||||
} from './types';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { TNavigation } from '../stackType';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
|
||||
|
@ -206,7 +207,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
});
|
||||
|
||||
// InsideStackNavigator
|
||||
const InsideStack = createStackNavigator<MasterDetailInsideStackParamList>();
|
||||
const InsideStack = createStackNavigator<MasterDetailInsideStackParamList & TNavigation>();
|
||||
const InsideStackNavigator = React.memo(() => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
return (
|
||||
|
|
|
@ -207,9 +207,6 @@ export type ModalStackParamList = {
|
|||
export type MasterDetailInsideStackParamList = {
|
||||
DrawerNavigator: NavigatorScreenParams<Partial<MasterDetailDrawerParamList>>; // TODO: Change
|
||||
ModalStackNavigator: NavigatorScreenParams<ModalStackParamList>;
|
||||
AttachmentView: {
|
||||
attachment: IAttachment;
|
||||
};
|
||||
ModalBlockView: {
|
||||
data: any; // TODO: Change
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { IAttachment } from '../definitions';
|
||||
import { IOptionsField } from '../views/NotificationPreferencesView/options';
|
||||
|
||||
export type TNavigation = {
|
||||
PickerView: {
|
||||
title: string;
|
||||
data: IOptionsField[];
|
||||
value?: string;
|
||||
onSearch: (text?: string, offset?: number) => Promise<any>;
|
||||
onEndReached?: (text?: string, offset?: number) => Promise<any>;
|
||||
total?: number;
|
||||
onChangeValue: Function;
|
||||
};
|
||||
ForwardLivechatView: {
|
||||
rid: string;
|
||||
};
|
||||
AttachmentView: {
|
||||
attachment: IAttachment;
|
||||
};
|
||||
};
|
|
@ -288,9 +288,6 @@ export type InsideStackParamList = {
|
|||
NewMessageStackNavigator: NavigatorScreenParams<NewMessageStackParamList>;
|
||||
E2ESaveYourPasswordStackNavigator: NavigatorScreenParams<E2ESaveYourPasswordStackParamList>;
|
||||
E2EEnterYourPasswordStackNavigator: NavigatorScreenParams<E2EEnterYourPasswordStackParamList>;
|
||||
AttachmentView: {
|
||||
attachment: IAttachment;
|
||||
};
|
||||
StatusView: undefined;
|
||||
ShareView: {
|
||||
attachments: IAttachment[];
|
||||
|
|
|
@ -1,83 +1,114 @@
|
|||
import React from 'react';
|
||||
import { PermissionsAndroid, StyleSheet, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import CameraRoll from '@react-native-community/cameraroll';
|
||||
import * as mime from 'react-native-mime-types';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { Video, ResizeMode } from 'expo-av';
|
||||
import { HeaderBackground, useHeaderHeight } from '@react-navigation/elements';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { ResizeMode, Video } from 'expo-av';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { HeaderBackground, HeaderHeightContext } from '@react-navigation/elements';
|
||||
import React from 'react';
|
||||
import { PermissionsAndroid, useWindowDimensions, View } from 'react-native';
|
||||
import * as mime from 'react-native-mime-types';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
import { LISTENER } from '../containers/Toast';
|
||||
import EventEmitter from '../lib/methods/helpers/events';
|
||||
import I18n from '../i18n';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { ImageViewer } from '../containers/ImageViewer';
|
||||
import { themes } from '../lib/constants';
|
||||
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import { isAndroid, formatAttachmentUrl } from '../lib/methods/helpers';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import { withDimensions } from '../dimensions';
|
||||
import { ImageViewer } from '../containers/ImageViewer';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { InsideStackParamList } from '../stacks/types';
|
||||
import { IApplicationState, IUser, IAttachment } from '../definitions';
|
||||
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';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
||||
interface IAttachmentViewState {
|
||||
const RenderContent = ({
|
||||
setLoading,
|
||||
attachment
|
||||
}: {
|
||||
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
attachment: IAttachment;
|
||||
loading: boolean;
|
||||
}
|
||||
}) => {
|
||||
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
|
||||
);
|
||||
|
||||
interface IAttachmentViewProps {
|
||||
navigation: StackNavigationProp<InsideStackParamList, 'AttachmentView'>;
|
||||
route: RouteProp<InsideStackParamList, 'AttachmentView'>;
|
||||
theme?: TSupportedThemes;
|
||||
baseUrl: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
insets?: { left: number; bottom: number; right: number; top: number };
|
||||
user: IUser;
|
||||
Allow_Save_Media_to_Gallery: boolean;
|
||||
}
|
||||
|
||||
class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentViewState> {
|
||||
private unsubscribeBlur: (() => void) | undefined;
|
||||
private videoRef: any;
|
||||
|
||||
constructor(props: IAttachmentViewProps) {
|
||||
super(props);
|
||||
const attachment = props.route.params?.attachment;
|
||||
this.state = { attachment, loading: true };
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
if (this.videoRef && this.videoRef.stopAsync) {
|
||||
this.videoRef.stopAsync();
|
||||
React.useLayoutEffect(() => {
|
||||
const blurSub = navigation.addListener('blur', () => {
|
||||
if (videoRef.current && videoRef.current.stopAsync) {
|
||||
videoRef.current.stopAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
blurSub();
|
||||
};
|
||||
}, [navigation]);
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.unsubscribeBlur) {
|
||||
this.unsubscribeBlur();
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
setHeader = () => {
|
||||
const { route, navigation, theme, Allow_Save_Media_to_Gallery } = this.props;
|
||||
const attachment = route.params?.attachment;
|
||||
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) {
|
||||
|
@ -89,30 +120,30 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
|
|||
const options: StackNavigationOptions = {
|
||||
title: title || '',
|
||||
headerTitleAlign: 'center',
|
||||
headerTitleStyle: { color: themes[theme!].previewTintColor },
|
||||
headerTintColor: themes[theme!].previewTintColor,
|
||||
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={themes[theme!].previewTintColor} />
|
||||
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={colors.previewTintColor} />
|
||||
),
|
||||
headerRight: () =>
|
||||
Allow_Save_Media_to_Gallery ? (
|
||||
<HeaderButton.Download testID='save-image' onPress={this.handleSave} color={themes[theme!].previewTintColor} />
|
||||
<HeaderButton.Download testID='save-image' onPress={handleSave} color={colors.previewTintColor} />
|
||||
) : null,
|
||||
headerBackground: () => (
|
||||
<HeaderBackground style={{ backgroundColor: themes[theme!].previewBackground, shadowOpacity: 0, elevation: 0 }} />
|
||||
<HeaderBackground style={{ backgroundColor: colors.previewBackground, shadowOpacity: 0, elevation: 0 }} />
|
||||
)
|
||||
};
|
||||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
getVideoRef = (ref: Video) => (this.videoRef = ref);
|
||||
React.useLayoutEffect(() => {
|
||||
setHeader();
|
||||
}, [navigation]);
|
||||
|
||||
handleSave = async () => {
|
||||
const { attachment } = this.state;
|
||||
const { user, baseUrl } = this.props;
|
||||
const handleSave = async () => {
|
||||
const { title_link, image_url, image_type, video_url, video_type } = attachment;
|
||||
const url = title_link || image_url || video_url;
|
||||
|
||||
|
@ -133,7 +164,7 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
|
|||
}
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
setLoading(true);
|
||||
try {
|
||||
const extension = image_url
|
||||
? `.${mime.extension(image_type) || 'jpg'}`
|
||||
|
@ -141,77 +172,24 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
|
|||
// The return of mime.extension('video/quicktime') is .qt,
|
||||
// this format the iOS isn't recognize and can't save on gallery
|
||||
const documentDir = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
|
||||
const path = `${documentDir + sha256(url!) + extension}`;
|
||||
const path = `${documentDir + sha256(url) + extension}`;
|
||||
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
|
||||
await CameraRoll.save(path, { album: 'Rocket.Chat' });
|
||||
await file.flush();
|
||||
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') });
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
renderImage = (uri: string) => {
|
||||
const { width, height, insets } = this.props;
|
||||
return (
|
||||
<HeaderHeightContext.Consumer>
|
||||
{headerHeight => (
|
||||
<ImageViewer
|
||||
uri={uri}
|
||||
onLoadEnd={() => this.setState({ loading: false })}
|
||||
width={width!}
|
||||
height={height! - insets!.top - insets!.bottom - (headerHeight || 0)}
|
||||
/>
|
||||
)}
|
||||
</HeaderHeightContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
renderVideo = (uri: string) => (
|
||||
<Video
|
||||
source={{ uri }}
|
||||
rate={1.0}
|
||||
volume={1.0}
|
||||
isMuted={false}
|
||||
resizeMode={ResizeMode.CONTAIN}
|
||||
shouldPlay
|
||||
isLooping={false}
|
||||
style={styles.container}
|
||||
useNativeControls
|
||||
onLoad={() => this.setState({ loading: false })}
|
||||
onError={console.log}
|
||||
ref={this.getVideoRef}
|
||||
/>
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading, attachment } = this.state;
|
||||
const { theme, user, baseUrl } = this.props;
|
||||
let content = null;
|
||||
|
||||
if (attachment && attachment.image_url) {
|
||||
const uri = formatAttachmentUrl(attachment.title_link || attachment.image_url, user.id, user.token, baseUrl);
|
||||
content = this.renderImage(encodeURI(uri));
|
||||
} else if (attachment && attachment.video_url) {
|
||||
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
||||
content = this.renderVideo(encodeURI(uri));
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme!].backgroundColor }]}>
|
||||
<StatusBar barStyle='light-content' backgroundColor={themes[theme!].previewBackground} />
|
||||
{content}
|
||||
{loading ? <RCActivityIndicator absolute size='large' /> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
Allow_Save_Media_to_Gallery: (state.settings.Allow_Save_Media_to_Gallery as boolean) ?? true
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(withDimensions(withSafeAreaInsets(AttachmentView))));
|
||||
export default AttachmentView;
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
ICustomEmoji
|
||||
} from '../../definitions';
|
||||
import { Services } from '../../lib/services';
|
||||
import { TNavigation } from '../../stacks/stackType';
|
||||
|
||||
interface IMessagesViewProps {
|
||||
user: {
|
||||
|
@ -43,7 +44,7 @@ interface IMessagesViewProps {
|
|||
baseUrl: string;
|
||||
navigation: CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'MessagesView'>,
|
||||
StackNavigationProp<MasterDetailInsideStackParamList>
|
||||
StackNavigationProp<MasterDetailInsideStackParamList & TNavigation>
|
||||
>;
|
||||
route: RouteProp<ChatsStackParamList, 'MessagesView'>;
|
||||
customEmojis: { [key: string]: ICustomEmoji };
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
ICustomEmoji
|
||||
} from '../../definitions';
|
||||
import { Services } from '../../lib/services';
|
||||
import { TNavigation } from '../../stacks/stackType';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
|
@ -59,7 +60,7 @@ export interface IRoomInfoParam {
|
|||
interface INavigationOption {
|
||||
navigation: CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'SearchMessagesView'>,
|
||||
StackNavigationProp<InsideStackParamList>
|
||||
StackNavigationProp<InsideStackParamList & TNavigation>
|
||||
>;
|
||||
route: RouteProp<ChatsStackParamList, 'SearchMessagesView'>;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue