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'