import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Animated, Easing } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import equal from 'deep-equal'; import { responsive } from 'react-native-responsive-ui'; import Touchable from 'react-native-platform-touchable'; import { hasNotch, isIOS, isTablet } from '../../utils/deviceInfo'; import { CustomIcon } from '../../lib/Icons'; import { themes } from '../../constants/colors'; import Avatar from '../../containers/Avatar'; import { removeNotification as removeNotificationAction } from '../../actions/notification'; import sharedStyles from '../../views/Styles'; import { ROW_HEIGHT } from '../../presentation/RoomItem'; import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; const AVATAR_SIZE = 48; const ANIMATION_DURATION = 300; const NOTIFICATION_DURATION = 3000; const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 }; const ANIMATION_PROPS = { duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), useNativeDriver: true }; const styles = StyleSheet.create({ container: { height: ROW_HEIGHT, paddingHorizontal: 14, flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', position: 'absolute', zIndex: 2, width: '100%', borderBottomWidth: StyleSheet.hairlineWidth }, content: { flex: 1, flexDirection: 'row', alignItems: 'center' }, inner: { flex: 1 }, avatar: { marginRight: 10 }, roomName: { fontSize: 17, lineHeight: 20, ...sharedStyles.textMedium }, message: { fontSize: 14, lineHeight: 17, ...sharedStyles.textRegular }, close: { marginLeft: 10 } }); class NotificationBadge extends React.Component { static propTypes = { navigation: PropTypes.object, baseUrl: PropTypes.string, user: PropTypes.object, notification: PropTypes.object, window: PropTypes.object, removeNotification: PropTypes.func, theme: PropTypes.string } constructor(props) { super(props); this.animatedValue = new Animated.Value(0); } shouldComponentUpdate(nextProps) { const { notification: nextNotification } = nextProps; const { notification: { payload }, window, theme } = this.props; if (nextProps.theme !== theme) { return true; } if (!equal(nextNotification.payload, payload)) { return true; } if (nextProps.window.width !== window.width) { return true; } return false; } componentDidUpdate() { const { notification: { payload }, navigation } = this.props; const navState = this.getNavState(navigation.state); if (payload.rid) { if (navState && navState.routeName === 'RoomView' && navState.params && navState.params.rid === payload.rid) { return; } this.show(); } } componentWillUnmount() { this.clearTimeout(); } show = () => { Animated.timing( this.animatedValue, { toValue: 1, ...ANIMATION_PROPS } ).start(() => { this.clearTimeout(); this.timeout = setTimeout(() => { this.hide(); }, NOTIFICATION_DURATION); }); } hide = () => { const { removeNotification } = this.props; Animated.timing( this.animatedValue, { toValue: 0, ...ANIMATION_PROPS } ).start(); setTimeout(removeNotification, ANIMATION_DURATION); } clearTimeout = () => { if (this.timeout) { clearTimeout(this.timeout); } } getNavState = (routes) => { if (!routes.routes) { return routes; } return this.getNavState(routes.routes[routes.index]); } goToRoom = async() => { const { notification, navigation, baseUrl } = this.props; const { payload } = notification; const { rid, type, prid } = payload; if (!rid) { return; } const name = type === 'd' ? payload.sender.username : payload.name; // if sub is not on local database, title will be null, so we use payload from notification const { title = name } = notification; await navigation.navigate('RoomsListView'); navigation.navigate('RoomView', { rid, name: title, t: type, prid, baseUrl }); this.hide(); } render() { const { baseUrl, user: { id: userId, token }, notification, window, theme } = this.props; const { message, payload } = notification; const { type } = payload; const name = type === 'd' ? payload.sender.username : payload.name; // if sub is not on local database, title and avatar will be null, so we use payload from notification const { title = name, avatar = name } = notification; let top = 0; if (isIOS) { const portrait = window.height > window.width; if (portrait) { top = hasNotch ? 45 : 20; } else { top = isTablet ? 20 : 0; } } const translateY = this.animatedValue.interpolate({ inputRange: [0, 1], outputRange: [-top - ROW_HEIGHT, top] }); return ( <> {title} {message} ); } } const mapStateToProps = state => ({ user: getUserSelector(state), baseUrl: state.server.server, notification: state.notification }); const mapDispatchToProps = dispatch => ({ removeNotification: () => dispatch(removeNotificationAction()) }); export default responsive(connect(mapStateToProps, mapDispatchToProps)(withTheme(NotificationBadge)));