From d15a5612dbdf2bdab7c4227a820a554784f0f147 Mon Sep 17 00:00:00 2001 From: Prateek Jain <44807945+Prateek93a@users.noreply.github.com> Date: Tue, 29 Oct 2019 19:23:58 +0530 Subject: [PATCH] [CHORE] Refactor RoomItem touchable (#1331) --- app/presentation/RoomItem/Touchable.js | 216 ++++++++++++ app/presentation/RoomItem/index.js | 438 ++++++++----------------- 2 files changed, 349 insertions(+), 305 deletions(-) create mode 100644 app/presentation/RoomItem/Touchable.js diff --git a/app/presentation/RoomItem/Touchable.js b/app/presentation/RoomItem/Touchable.js new file mode 100644 index 00000000..8ed00d01 --- /dev/null +++ b/app/presentation/RoomItem/Touchable.js @@ -0,0 +1,216 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Animated } from 'react-native'; +import { + RectButton, + PanGestureHandler, + State +} from 'react-native-gesture-handler'; +import styles, { + ACTION_WIDTH, + SMALL_SWIPE, + LONG_SWIPE +} from './styles'; +import { LeftActions, RightActions } from './Actions'; + +class Touchable extends React.Component { + static propTypes = { + type: PropTypes.string.isRequired, + onPress: PropTypes.func, + testID: PropTypes.string, + width: PropTypes.number, + favorite: PropTypes.bool, + isRead: PropTypes.bool, + rid: PropTypes.string, + toggleFav: PropTypes.func, + toggleRead: PropTypes.func, + hideChannel: PropTypes.func, + children: PropTypes.element + } + + constructor(props) { + super(props); + this.dragX = new Animated.Value(0); + this.rowOffSet = new Animated.Value(0); + this.transX = Animated.add( + this.rowOffSet, + this.dragX + ); + this.state = { + rowState: 0 // 0: closed, 1: right opened, -1: left opened + }; + this._onGestureEvent = Animated.event( + [{ nativeEvent: { translationX: this.dragX } }] + ); + this._value = 0; + } + + _onHandlerStateChange = ({ nativeEvent }) => { + if (nativeEvent.oldState === State.ACTIVE) { + this._handleRelease(nativeEvent); + } + } + + + _handleRelease = (nativeEvent) => { + const { translationX } = nativeEvent; + const { rowState } = this.state; + this._value = this._value + translationX; + + let toValue = 0; + if (rowState === 0) { // if no option is opened + if (translationX > 0 && translationX < LONG_SWIPE) { + toValue = ACTION_WIDTH; // open left option if he swipe right but not enough to trigger action + this.setState({ rowState: -1 }); + } else if (translationX >= LONG_SWIPE) { + toValue = 0; + this.toggleRead(); + } else if (translationX < 0 && translationX > -LONG_SWIPE) { + toValue = -2 * ACTION_WIDTH; // open right option if he swipe left + this.setState({ rowState: 1 }); + } else if (translationX <= -LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + this.hideChannel(); + } else { + toValue = 0; + } + } + + if (rowState === -1) { // if left option is opened + if (this._value < SMALL_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + } else if (this._value > LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + this.toggleRead(); + } else { + toValue = ACTION_WIDTH; + } + } + + if (rowState === 1) { // if right option is opened + if (this._value > -2 * SMALL_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + } else if (this._value < -LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + this.hideChannel(); + } else { + toValue = -2 * ACTION_WIDTH; + } + } + this._animateRow(toValue); + } + + _animateRow = (toValue) => { + this.rowOffSet.setValue(this._value); + this._value = toValue; + this.dragX.setValue(0); + Animated.spring(this.rowOffSet, { + toValue, + bounciness: 0, + useNativeDriver: true + }).start(); + } + + close = () => { + this.setState({ rowState: 0 }); + this._animateRow(0); + } + + toggleFav = () => { + const { toggleFav, rid, favorite } = this.props; + if (toggleFav) { + toggleFav(rid, favorite); + } + this.close(); + }; + + toggleRead = () => { + const { toggleRead, rid, isRead } = this.props; + if (toggleRead) { + toggleRead(rid, isRead); + } + }; + + hideChannel = () => { + const { hideChannel, rid, type } = this.props; + if (hideChannel) { + hideChannel(rid, type); + } + }; + + onToggleReadPress = () => { + this.toggleRead(); + this.close(); + }; + + onHidePress = () => { + this.hideChannel(); + this.close(); + }; + + onPress = () => { + const { rowState } = this.state; + if (rowState !== 0) { + this.close(); + return; + } + const { onPress } = this.props; + if (onPress) { + onPress(); + } + }; + + render() { + const { + testID, isRead, width, favorite, children + } = this.props; + + return ( + + + + + + + + {children} + + + + + + ); + } +} + +export default Touchable; diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index c340ba96..87cdffd6 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -1,26 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, Animated } from 'react-native'; -import { - RectButton, - PanGestureHandler, - State -} from 'react-native-gesture-handler'; +import { View, Text } from 'react-native'; import { connect } from 'react-redux'; import Avatar from '../../containers/Avatar'; import I18n from '../../i18n'; -import styles, { - ROW_HEIGHT, - ACTION_WIDTH, - SMALL_SWIPE, - LONG_SWIPE -} from './styles'; +import styles, { ROW_HEIGHT } from './styles'; import UnreadBadge from './UnreadBadge'; import TypeIcon from './TypeIcon'; import LastMessage from './LastMessage'; import { capitalize, formatDate } from '../../utils/room'; -import { LeftActions, RightActions } from './Actions'; +import Touchable from './Touchable'; export { ROW_HEIGHT }; @@ -37,307 +27,145 @@ const attrs = [ 'status' ]; -class RoomItem extends React.Component { - static propTypes = { - type: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - baseUrl: PropTypes.string.isRequired, - showLastMessage: PropTypes.bool, - _updatedAt: PropTypes.string, - lastMessage: PropTypes.object, - alert: PropTypes.bool, - unread: PropTypes.number, - userMentions: PropTypes.number, - id: PropTypes.string, - prid: PropTypes.string, - onPress: PropTypes.func, - userId: PropTypes.string, - username: PropTypes.string, - token: PropTypes.string, - avatarSize: PropTypes.number, - testID: PropTypes.string, - width: PropTypes.number, - favorite: PropTypes.bool, - isRead: PropTypes.bool, - rid: PropTypes.string, - status: PropTypes.string, - toggleFav: PropTypes.func, - toggleRead: PropTypes.func, - hideChannel: PropTypes.func, - avatar: PropTypes.bool, - hideUnreadStatus: PropTypes.bool +const arePropsEqual = (oldProps, newProps) => { + const { _updatedAt: _updatedAtOld } = oldProps; + const { _updatedAt: _updatedAtNew } = newProps; + if (_updatedAtOld && _updatedAtNew && _updatedAtOld.toISOString() !== _updatedAtNew.toISOString()) { + return false; + } + return attrs.every(key => oldProps[key] === newProps[key]); +}; + +const RoomItem = React.memo(({ + onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar +}) => { + const date = formatDate(_updatedAt); + + let accessibilityLabel = name; + if (unread === 1) { + accessibilityLabel += `, ${ unread } ${ I18n.t('alert') }`; + } else if (unread > 1) { + accessibilityLabel += `, ${ unread } ${ I18n.t('alerts') }`; } - static defaultProps = { - avatarSize: 48 - }; - - constructor(props) { - super(props); - this.dragX = new Animated.Value(0); - this.rowOffSet = new Animated.Value(0); - this.transX = Animated.add( - this.rowOffSet, - this.dragX - ); - this.state = { - rowState: 0 // 0: closed, 1: right opened, -1: left opened - }; - this._onGestureEvent = Animated.event( - [{ nativeEvent: { translationX: this.dragX } }] - ); - this._value = 0; + if (userMentions > 0) { + accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; } - shouldComponentUpdate(nextProps) { - const { _updatedAt } = this.props; - if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toISOString() !== _updatedAt.toISOString()) { - return true; - } - // eslint-disable-next-line react/destructuring-assignment - return attrs.some(key => nextProps[key] !== this.props[key]); + if (date) { + accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; } - _onHandlerStateChange = ({ nativeEvent }) => { - if (nativeEvent.oldState === State.ACTIVE) { - this._handleRelease(nativeEvent); - } - }; - - _handleRelease = (nativeEvent) => { - const { translationX } = nativeEvent; - const { rowState } = this.state; - this._value = this._value + translationX; - - let toValue = 0; - if (rowState === 0) { // if no option is opened - if (translationX > 0 && translationX < LONG_SWIPE) { - toValue = ACTION_WIDTH; // open left option if he swipe right but not enough to trigger action - this.setState({ rowState: -1 }); - } else if (translationX >= LONG_SWIPE) { - toValue = 0; - this.toggleRead(); - } else if (translationX < 0 && translationX > -LONG_SWIPE) { - toValue = -2 * ACTION_WIDTH; // open right option if he swipe left - this.setState({ rowState: 1 }); - } else if (translationX <= -LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - this.hideChannel(); - } else { - toValue = 0; - } - } - - if (rowState === -1) { // if left option is opened - if (this._value < SMALL_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - } else if (this._value > LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - this.toggleRead(); - } else { - toValue = ACTION_WIDTH; - } - } - - if (rowState === 1) { // if right option is opened - if (this._value > -2 * SMALL_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - } else if (this._value < -LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - this.hideChannel(); - } else { - toValue = -2 * ACTION_WIDTH; - } - } - this._animateRow(toValue); - } - - _animateRow = (toValue) => { - this.rowOffSet.setValue(this._value); - this._value = toValue; - this.dragX.setValue(0); - Animated.spring(this.rowOffSet, { - toValue, - bounciness: 0, - useNativeDriver: true - }).start(); - } - - close = () => { - this.setState({ rowState: 0 }); - this._animateRow(0); - } - - toggleFav = () => { - const { toggleFav, rid, favorite } = this.props; - if (toggleFav) { - toggleFav(rid, favorite); - } - this.close(); - }; - - toggleRead = () => { - const { toggleRead, rid, isRead } = this.props; - if (toggleRead) { - toggleRead(rid, isRead); - } - }; - - hideChannel = () => { - const { hideChannel, rid, type } = this.props; - if (hideChannel) { - hideChannel(rid, type); - } - }; - - onToggleReadPress = () => { - this.toggleRead(); - this.close(); - }; - - onHidePress = () => { - this.hideChannel(); - this.close(); - }; - - onPress = () => { - const { rowState } = this.state; - if (rowState !== 0) { - this.close(); - return; - } - const { onPress } = this.props; - if (onPress) { - onPress(); - } - }; - - render() { - const { - unread, userMentions, name, _updatedAt, alert, testID, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, lastMessage, isRead, width, favorite, status, avatar, hideUnreadStatus - } = this.props; - - const date = formatDate(_updatedAt); - - let accessibilityLabel = name; - if (unread === 1) { - accessibilityLabel += `, ${ unread } ${ I18n.t('alert') }`; - } else if (unread > 1) { - accessibilityLabel += `, ${ unread } ${ I18n.t('alerts') }`; - } - - if (userMentions > 0) { - accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; - } - - if (date) { - accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; - } - - return ( - + - - - - - + + + + - + {_updatedAt ? ( + - - - - - - {name} - - {_updatedAt ? ( - - {capitalize(date)} - - ) : null} - - - - - - - - - - - - ); - } -} + {capitalize(date)} + + ) : null} + + + + + + + + + ); +}, arePropsEqual); + +RoomItem.propTypes = { + type: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + baseUrl: PropTypes.string.isRequired, + showLastMessage: PropTypes.bool, + _updatedAt: PropTypes.string, + lastMessage: PropTypes.object, + alert: PropTypes.bool, + unread: PropTypes.number, + userMentions: PropTypes.number, + id: PropTypes.string, + prid: PropTypes.string, + onPress: PropTypes.func, + userId: PropTypes.string, + username: PropTypes.string, + token: PropTypes.string, + avatarSize: PropTypes.number, + testID: PropTypes.string, + width: PropTypes.number, + favorite: PropTypes.bool, + isRead: PropTypes.bool, + rid: PropTypes.string, + status: PropTypes.string, + toggleFav: PropTypes.func, + toggleRead: PropTypes.func, + hideChannel: PropTypes.func, + avatar: PropTypes.bool, + hideUnreadStatus: PropTypes.bool +}; + +RoomItem.defaultProps = { + avatarSize: 48 +}; const mapStateToProps = (state, ownProps) => ({ status: state.meteor.connected && ownProps.type === 'd' ? state.activeUsers[ownProps.id] : 'offline'