mattermost-mobile/app/screens/notification/index.tsx
Elias Nahum 016a725732
MM-28105 do not crash when in-app notification shows (#4915)
* MM-28105 do not crash when in-app notification shows

* Use index.tsx to register the notification screen

* Fix notification e2e and some import cleanup

* Update iOS notification json

* e2e: Show the add reaction screen before receiving a notification

* Update snapshots

* Patch react-native-notifications to prevent iOS crash when opening

* Apply TM4J id

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
2020-10-22 14:34:49 -03:00

210 lines
6.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useRef, useState} from 'react';
import {Platform, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import * as Animatable from 'react-native-animatable';
import {Navigation} from 'react-native-navigation';
import {PanGestureHandler} from 'react-native-gesture-handler';
import {useDispatch} from 'react-redux';
import {popToRoot, dismissAllModals, dismissOverlay} from '@actions/navigation';
import {loadFromPushNotification} from '@actions/views/root';
import {NavigationTypes} from '@constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {changeOpacity} from '@utils/theme';
import NotificationIcon from './notification_icon';
import NotificationTitle from './notification_title';
interface NotificationProps {
componentId: string;
notification: PushNotificationData;
}
interface SlideAnimation extends Animatable.CustomAnimation {
from: {
translateY: number;
};
to: {
translateY: number;
};
}
const AUTO_DISMISS_TIME_MILLIS = 5000;
const initialAnimation: SlideAnimation = {
from: {
translateY: -100,
},
to: {
translateY: 0,
},
};
const Notification = ({componentId, notification}: NotificationProps) => {
const [animation, setAnimation] = useState<SlideAnimation>(initialAnimation);
const dispatch = useDispatch();
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
const tapped = useRef<boolean>(false);
const animateDismissOverlay = () => {
cancelDismissTimer();
setAnimation({
from: {
translateY: 0,
},
to: {
translateY: -100,
},
});
dismissTimerRef.current = setTimeout(dismiss, 1000);
};
const cancelDismissTimer = () => {
if (dismissTimerRef.current) {
clearTimeout(dismissTimerRef.current);
}
};
const dismiss = () => {
cancelDismissTimer();
dismissOverlay(componentId);
};
const notificationTapped = () => {
tapped.current = true;
EventEmitter.emit(NavigationTypes.CLOSE_MAIN_SIDEBAR);
EventEmitter.emit(NavigationTypes.CLOSE_SETTINGS_SIDEBAR);
dismiss();
if (!notification.userInfo?.localNotification) {
dispatch(loadFromPushNotification(notification));
}
};
useEffect(() => {
dismissTimerRef.current = setTimeout(() => {
if (!tapped.current) {
animateDismissOverlay();
}
}, AUTO_DISMISS_TIME_MILLIS);
return cancelDismissTimer;
}, []);
useEffect(() => {
const didDismissListener = Navigation.events().registerComponentDidDisappearListener(async ({componentId: screen}) => {
if (componentId === screen && tapped.current) {
await dismissAllModals();
await popToRoot();
}
});
return () => {
didDismissListener.remove();
};
}, []);
useEffect(() => {
EventEmitter.on(NavigationTypes.NAVIGATION_SHOW_OVERLAY, dismiss);
return () => EventEmitter.off(NavigationTypes.NAVIGATION_SHOW_OVERLAY, dismiss);
}, []);
let message = notification.message;
if (typeof notification.message === 'object') {
message = notification.message.body;
}
return (
<PanGestureHandler
onGestureEvent={animateDismissOverlay}
minOffsetY={-20}
>
<Animatable.View
duration={250}
style={styles.container}
useNativeDriver={true}
animation={animation}
testID='in_app_notification'
>
<View style={styles.flex}>
<TouchableOpacity
style={styles.touchable}
onPress={notificationTapped}
activeOpacity={1}
>
<NotificationIcon
fromWebhook={notification.data?.from_webhook === 'true'}
senderId={notification.data?.sender_id || ''}
useUserIcon={notification.data?.use_user_icon === 'true'}
overrideIconUrl={notification.data?.override_icon_url}
/>
<View style={styles.titleContainer}>
<NotificationTitle channelName={notification.data?.channel_name || ''}/>
<View style={styles.flex}>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={styles.message}
testID='in_app_notification.message'
>
{message}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
</Animatable.View>
</PanGestureHandler>
);
};
const styles = StyleSheet.create({
container: {
alignItems: 'flex-start',
backgroundColor: changeOpacity('#000', 1),
flexDirection: 'row',
justifyContent: 'flex-start',
paddingHorizontal: 10,
width: '100%',
...Platform.select({
android: {
height: 68,
},
ios: {
height: 88,
},
}),
},
flex: {
flex: 1,
},
message: {
color: '#FFFFFF',
fontSize: 14,
},
titleContainer: {
flex: 1,
flexDirection: 'column',
alignSelf: 'stretch',
alignItems: 'flex-start',
marginLeft: 10,
...Platform.select({
android: {
marginTop: 17,
height: 50,
},
ios: {
paddingTop: 37,
},
}),
},
touchable: {
flex: 1,
flexDirection: 'row',
},
});
export default Notification;