[FIX] Swipe animations (#1044)
* Comment removeClippedSubviews * Comment width animation * Remove redux from RoomItem * Fix wrong re-render comparison * Remove listener * Raise minDeltaX * memo actions * Spring with native driver * Refactor functions * Fix props issues * Remove RoomItem.height * Long swipe * Refactor animations * this.rowTranslation -> this.transX * Moved state to this * Fix favorite button
This commit is contained in:
parent
ba217ca5ff
commit
c2497145fc
|
@ -0,0 +1,129 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Animated, View, Text } from 'react-native';
|
||||||
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
|
||||||
|
export const LeftActions = React.memo(({
|
||||||
|
transX, isRead, width, onToggleReadPress
|
||||||
|
}) => {
|
||||||
|
const translateX = transX.interpolate({
|
||||||
|
inputRange: [0, ACTION_WIDTH],
|
||||||
|
outputRange: [-ACTION_WIDTH, 0]
|
||||||
|
});
|
||||||
|
const translateXIcon = transX.interpolate({
|
||||||
|
inputRange: [0, ACTION_WIDTH, LONG_SWIPE - 2, LONG_SWIPE],
|
||||||
|
outputRange: [0, 0, -LONG_SWIPE + ACTION_WIDTH + 2, 0],
|
||||||
|
extrapolate: 'clamp'
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={styles.actionsContainer}
|
||||||
|
pointerEvents='box-none'
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionLeftButtonContainer,
|
||||||
|
{
|
||||||
|
right: width - ACTION_WIDTH,
|
||||||
|
width,
|
||||||
|
transform: [{ translateX }]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionLeftButtonContainer,
|
||||||
|
{
|
||||||
|
right: 0,
|
||||||
|
transform: [{ translateX: translateXIcon }]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RectButton style={styles.actionButton} onPress={onToggleReadPress}>
|
||||||
|
<React.Fragment>
|
||||||
|
<CustomIcon size={20} name={isRead ? 'flag' : 'check'} color='white' />
|
||||||
|
<Text style={styles.actionText}>{I18n.t(isRead ? 'Unread' : 'Read')}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
</RectButton>
|
||||||
|
</Animated.View>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RightActions = React.memo(({
|
||||||
|
transX, favorite, width, toggleFav, onHidePress
|
||||||
|
}) => {
|
||||||
|
const translateXFav = transX.interpolate({
|
||||||
|
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
|
||||||
|
outputRange: [width / 2, width - ACTION_WIDTH * 2, width]
|
||||||
|
});
|
||||||
|
const translateXHide = transX.interpolate({
|
||||||
|
inputRange: [-width, -LONG_SWIPE, -ACTION_WIDTH * 2, 0],
|
||||||
|
outputRange: [0, width - LONG_SWIPE, width - ACTION_WIDTH, width]
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 75,
|
||||||
|
flexDirection: 'row'
|
||||||
|
}}
|
||||||
|
pointerEvents='box-none'
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionRightButtonContainer,
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
transform: [{ translateX: translateXFav }]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RectButton style={[styles.actionButton, { backgroundColor: '#ffbb00' }]} onPress={toggleFav}>
|
||||||
|
<React.Fragment>
|
||||||
|
<CustomIcon size={20} name={favorite ? 'Star-filled' : 'star'} color='white' />
|
||||||
|
<Text style={styles.actionText}>{I18n.t(favorite ? 'Unfavorite' : 'Favorite')}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
</RectButton>
|
||||||
|
</Animated.View>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.actionRightButtonContainer,
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
transform: [{ translateX: translateXHide }]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<RectButton style={[styles.actionButton, { backgroundColor: '#54585e' }]} onPress={onHidePress}>
|
||||||
|
<React.Fragment>
|
||||||
|
<CustomIcon size={20} name='eye-off' color='white' />
|
||||||
|
<Text style={styles.actionText}>{I18n.t('Hide')}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
</RectButton>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
LeftActions.propTypes = {
|
||||||
|
transX: PropTypes.object,
|
||||||
|
isRead: PropTypes.bool,
|
||||||
|
width: PropTypes.number,
|
||||||
|
onToggleReadPress: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
RightActions.propTypes = {
|
||||||
|
transX: PropTypes.object,
|
||||||
|
favorite: PropTypes.bool,
|
||||||
|
width: PropTypes.number,
|
||||||
|
toggleFav: PropTypes.func,
|
||||||
|
onHidePress: PropTypes.func
|
||||||
|
};
|
|
@ -2,27 +2,22 @@ import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, Animated } from 'react-native';
|
import { View, Text, Animated } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { RectButton, PanGestureHandler, State } from 'react-native-gesture-handler';
|
import { RectButton, PanGestureHandler, State } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, {
|
||||||
|
ROW_HEIGHT, ACTION_WIDTH, SMALL_SWIPE, LONG_SWIPE
|
||||||
|
} from './styles';
|
||||||
import UnreadBadge from './UnreadBadge';
|
import UnreadBadge from './UnreadBadge';
|
||||||
import TypeIcon from './TypeIcon';
|
import TypeIcon from './TypeIcon';
|
||||||
import LastMessage from './LastMessage';
|
import LastMessage from './LastMessage';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { LeftActions, RightActions } from './Actions';
|
||||||
|
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
|
||||||
const OPTION_WIDTH = 80;
|
const attrs = ['name', 'unread', 'userMentions', 'showLastMessage', 'alert', 'type', 'width', 'isRead', 'favorite'];
|
||||||
const SMALL_SWIPE = 40;
|
|
||||||
const attrs = ['name', 'unread', 'userMentions', 'showLastMessage', 'alert', 'type', 'width'];
|
|
||||||
@connect(state => ({
|
|
||||||
userId: state.login.user && state.login.user.id,
|
|
||||||
username: state.login.user && state.login.user.username,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
}))
|
|
||||||
export default class RoomItem extends React.Component {
|
export default class RoomItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
@ -43,7 +38,6 @@ export default class RoomItem extends React.Component {
|
||||||
avatarSize: PropTypes.number,
|
avatarSize: PropTypes.number,
|
||||||
testID: PropTypes.string,
|
testID: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
|
||||||
favorite: PropTypes.bool,
|
favorite: PropTypes.bool,
|
||||||
isRead: PropTypes.bool,
|
isRead: PropTypes.bool,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
|
@ -60,46 +54,36 @@ export default class RoomItem extends React.Component {
|
||||||
// eslint-disable-next-line no-useless-constructor
|
// eslint-disable-next-line no-useless-constructor
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const dragX = new Animated.Value(0);
|
this.dragX = new Animated.Value(0);
|
||||||
const rowOffSet = new Animated.Value(0);
|
this.rowOffSet = new Animated.Value(0);
|
||||||
this.rowTranslation = Animated.add(
|
this.transX = Animated.add(
|
||||||
rowOffSet,
|
this.rowOffSet,
|
||||||
dragX
|
this.dragX
|
||||||
);
|
);
|
||||||
this.state = {
|
this.state = {
|
||||||
dragX,
|
|
||||||
rowOffSet,
|
|
||||||
rowState: 0 // 0: closed, 1: right opened, -1: left opened
|
rowState: 0 // 0: closed, 1: right opened, -1: left opened
|
||||||
};
|
};
|
||||||
this._onGestureEvent = Animated.event(
|
this._onGestureEvent = Animated.event(
|
||||||
[{ nativeEvent: { translationX: dragX } }]
|
[{ nativeEvent: { translationX: this.dragX } }]
|
||||||
);
|
);
|
||||||
this._value = 0;
|
this._value = 0;
|
||||||
this.rowTranslation.addListener(({ value }) => { this._value = value; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const { lastMessage, _updatedAt, isRead } = this.props;
|
const { lastMessage, _updatedAt } = this.props;
|
||||||
const oldlastMessage = lastMessage;
|
const oldlastMessage = lastMessage;
|
||||||
const newLastmessage = nextProps.lastMessage;
|
const newLastmessage = nextProps.lastMessage;
|
||||||
|
|
||||||
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt !== _updatedAt) {
|
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toISOString() !== _updatedAt.toISOString()) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isRead !== nextProps.isRead) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react/destructuring-assignment
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
return attrs.some(key => nextProps[key] !== this.props[key]);
|
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.rowTranslation.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
_onHandlerStateChange = ({ nativeEvent }) => {
|
_onHandlerStateChange = ({ nativeEvent }) => {
|
||||||
if (nativeEvent.oldState === State.ACTIVE) {
|
if (nativeEvent.oldState === State.ACTIVE) {
|
||||||
this._handleRelease(nativeEvent);
|
this._handleRelease(nativeEvent);
|
||||||
|
@ -109,21 +93,22 @@ export default class RoomItem extends React.Component {
|
||||||
_handleRelease = (nativeEvent) => {
|
_handleRelease = (nativeEvent) => {
|
||||||
const { translationX } = nativeEvent;
|
const { translationX } = nativeEvent;
|
||||||
const { rowState } = this.state;
|
const { rowState } = this.state;
|
||||||
const { width } = this.props;
|
this._value = this._value + translationX;
|
||||||
const halfScreen = width / 2;
|
|
||||||
let toValue = 0;
|
let toValue = 0;
|
||||||
if (rowState === 0) { // if no option is opened
|
if (rowState === 0) { // if no option is opened
|
||||||
if (translationX > 0 && translationX < halfScreen) {
|
if (translationX > 0 && translationX < LONG_SWIPE) {
|
||||||
toValue = OPTION_WIDTH; // open left option if he swipe right but not enough to trigger action
|
toValue = ACTION_WIDTH; // open left option if he swipe right but not enough to trigger action
|
||||||
this.setState({ rowState: -1 });
|
this.setState({ rowState: -1 });
|
||||||
} else if (translationX >= halfScreen) {
|
} else if (translationX >= LONG_SWIPE) {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
this.toggleRead();
|
this.toggleRead();
|
||||||
} else if (translationX < 0 && translationX > -halfScreen) {
|
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
|
||||||
toValue = -2 * OPTION_WIDTH; // open right option if he swipe left
|
toValue = -2 * ACTION_WIDTH; // open right option if he swipe left
|
||||||
this.setState({ rowState: 1 });
|
this.setState({ rowState: 1 });
|
||||||
} else if (translationX <= -halfScreen) {
|
} else if (translationX <= -LONG_SWIPE) {
|
||||||
toValue = -width;
|
toValue = 0;
|
||||||
|
this.setState({ rowState: 0 });
|
||||||
this.hideChannel();
|
this.hideChannel();
|
||||||
} else {
|
} else {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
|
@ -134,12 +119,12 @@ export default class RoomItem extends React.Component {
|
||||||
if (this._value < SMALL_SWIPE) {
|
if (this._value < SMALL_SWIPE) {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
this.setState({ rowState: 0 });
|
this.setState({ rowState: 0 });
|
||||||
} else if (this._value > halfScreen) {
|
} else if (this._value > LONG_SWIPE) {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
this.setState({ rowState: 0 });
|
this.setState({ rowState: 0 });
|
||||||
this.toggleRead();
|
this.toggleRead();
|
||||||
} else {
|
} else {
|
||||||
toValue = OPTION_WIDTH;
|
toValue = ACTION_WIDTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,32 +132,28 @@ export default class RoomItem extends React.Component {
|
||||||
if (this._value > -2 * SMALL_SWIPE) {
|
if (this._value > -2 * SMALL_SWIPE) {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
this.setState({ rowState: 0 });
|
this.setState({ rowState: 0 });
|
||||||
} else if (this._value < -halfScreen) {
|
} else if (this._value < -LONG_SWIPE) {
|
||||||
toValue = 0;
|
toValue = 0;
|
||||||
this.setState({ rowState: 0 });
|
this.setState({ rowState: 0 });
|
||||||
this.hideChannel();
|
this.hideChannel();
|
||||||
} else {
|
} else {
|
||||||
toValue = -2 * OPTION_WIDTH;
|
toValue = -2 * ACTION_WIDTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._animateRow(toValue);
|
this._animateRow(toValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_animateRow = (toValue) => {
|
_animateRow = (toValue) => {
|
||||||
const { dragX, rowOffSet } = this.state;
|
this.rowOffSet.setValue(this._value);
|
||||||
rowOffSet.setValue(this._value);
|
this._value = toValue;
|
||||||
dragX.setValue(0);
|
this.dragX.setValue(0);
|
||||||
Animated.spring(rowOffSet, {
|
Animated.spring(this.rowOffSet, {
|
||||||
toValue,
|
toValue,
|
||||||
bounciness: 0
|
bounciness: 0,
|
||||||
|
useNativeDriver: true
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLeftButtonPress = () => {
|
|
||||||
this.toggleRead();
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
this.setState({ rowState: 0 });
|
this.setState({ rowState: 0 });
|
||||||
this._animateRow(0);
|
this._animateRow(0);
|
||||||
|
@ -193,11 +174,6 @@ export default class RoomItem extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHideButtonPress = () => {
|
|
||||||
this.hideChannel();
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
hideChannel = () => {
|
hideChannel = () => {
|
||||||
const { hideChannel, rid, type } = this.props;
|
const { hideChannel, rid, type } = this.props;
|
||||||
if (hideChannel) {
|
if (hideChannel) {
|
||||||
|
@ -205,6 +181,16 @@ export default class RoomItem extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onToggleReadPress = () => {
|
||||||
|
this.toggleRead();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onHidePress = () => {
|
||||||
|
this.hideChannel();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
const { rowState } = this.state;
|
const { rowState } = this.state;
|
||||||
if (rowState !== 0) {
|
if (rowState !== 0) {
|
||||||
|
@ -217,109 +203,6 @@ export default class RoomItem extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLeftActions = () => {
|
|
||||||
const { isRead, width } = this.props;
|
|
||||||
const halfWidth = width / 2;
|
|
||||||
const trans = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [0, OPTION_WIDTH],
|
|
||||||
outputRange: [-width, -width + OPTION_WIDTH]
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconTrans = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [0, OPTION_WIDTH, halfWidth - 1, halfWidth, width],
|
|
||||||
outputRange: [0, 0, -(OPTION_WIDTH + 10), 0, 0]
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
styles.leftAction,
|
|
||||||
{ transform: [{ translateX: trans }] }
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<RectButton style={styles.actionButtonLeft} onPress={this.handleLeftButtonPress}>
|
|
||||||
<Animated.View
|
|
||||||
style={{ transform: [{ translateX: iconTrans }] }}
|
|
||||||
>
|
|
||||||
{isRead ? (
|
|
||||||
<View style={styles.actionView}>
|
|
||||||
<CustomIcon size={20} name='flag' color='white' />
|
|
||||||
<Text style={styles.actionText}>{I18n.t('Unread')}</Text>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={styles.actionView}>
|
|
||||||
<CustomIcon size={20} name='check' color='white' />
|
|
||||||
<Text style={styles.actionText}>{I18n.t('Read')}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
|
||||||
</RectButton>
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderRightActions = () => {
|
|
||||||
const { favorite, width } = this.props;
|
|
||||||
const halfWidth = width / 2;
|
|
||||||
const trans = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [-OPTION_WIDTH, 0],
|
|
||||||
outputRange: [width - OPTION_WIDTH, width]
|
|
||||||
});
|
|
||||||
const iconHideTrans = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [-(halfWidth - 20), -2 * OPTION_WIDTH, 0],
|
|
||||||
outputRange: [0, 0, -OPTION_WIDTH]
|
|
||||||
});
|
|
||||||
const iconFavWidth = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [-halfWidth, -2 * OPTION_WIDTH, 0],
|
|
||||||
outputRange: [0, OPTION_WIDTH, OPTION_WIDTH],
|
|
||||||
extrapolate: 'clamp'
|
|
||||||
});
|
|
||||||
const iconHideWidth = this.rowTranslation.interpolate({
|
|
||||||
inputRange: [-width, -halfWidth, -2 * OPTION_WIDTH, 0],
|
|
||||||
outputRange: [width, halfWidth, OPTION_WIDTH, OPTION_WIDTH]
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
styles.rightAction,
|
|
||||||
{ transform: [{ translateX: trans }] }
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Animated.View
|
|
||||||
style={{ width: iconFavWidth }}
|
|
||||||
>
|
|
||||||
<RectButton style={[styles.actionButtonRightFav]} onPress={this.toggleFav}>
|
|
||||||
{favorite ? (
|
|
||||||
<View style={styles.actionView}>
|
|
||||||
<CustomIcon size={20} name='Star-filled' color='white' />
|
|
||||||
<Text style={styles.actionText}>{I18n.t('Unfavorite')}</Text>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={styles.actionView}>
|
|
||||||
<CustomIcon size={20} name='star' color='white' />
|
|
||||||
<Text style={styles.actionText}>{I18n.t('Favorite')}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</RectButton>
|
|
||||||
</Animated.View>
|
|
||||||
<Animated.View style={[
|
|
||||||
{ width: iconHideWidth },
|
|
||||||
{ transform: [{ translateX: iconHideTrans }] }
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<RectButton
|
|
||||||
style={[styles.actionButtonRightHide]}
|
|
||||||
onPress={this.handleHideButtonPress}
|
|
||||||
>
|
|
||||||
<View style={styles.actionView}>
|
|
||||||
<CustomIcon size={20} name='eye-off' color='white' />
|
|
||||||
<Text style={styles.actionText}>{I18n.t('Hide')}</Text>
|
|
||||||
</View>
|
|
||||||
</RectButton>
|
|
||||||
</Animated.View>
|
|
||||||
</Animated.View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate = date => moment(date).calendar(null, {
|
formatDate = date => moment(date).calendar(null, {
|
||||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||||
sameDay: 'h:mm A',
|
sameDay: 'h:mm A',
|
||||||
|
@ -329,7 +212,7 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
unread, userMentions, name, _updatedAt, alert, testID, height, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, lastMessage
|
unread, userMentions, name, _updatedAt, alert, testID, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, lastMessage, isRead, width, favorite
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const date = this.formatDate(_updatedAt);
|
const date = this.formatDate(_updatedAt);
|
||||||
|
@ -351,17 +234,28 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanGestureHandler
|
<PanGestureHandler
|
||||||
minDeltaX={10}
|
minDeltaX={20}
|
||||||
onGestureEvent={this._onGestureEvent}
|
onGestureEvent={this._onGestureEvent}
|
||||||
onHandlerStateChange={this._onHandlerStateChange}
|
onHandlerStateChange={this._onHandlerStateChange}
|
||||||
>
|
>
|
||||||
<View style={styles.upperContainer}>
|
<Animated.View>
|
||||||
{this.renderLeftActions()}
|
<LeftActions
|
||||||
{this.renderRightActions()}
|
transX={this.transX}
|
||||||
|
isRead={isRead}
|
||||||
|
width={width}
|
||||||
|
onToggleReadPress={this.onToggleReadPress}
|
||||||
|
/>
|
||||||
|
<RightActions
|
||||||
|
transX={this.transX}
|
||||||
|
favorite={favorite}
|
||||||
|
width={width}
|
||||||
|
toggleFav={this.toggleFav}
|
||||||
|
onHidePress={this.onHidePress}
|
||||||
|
/>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
transform: [{ translateX: this.rowTranslation }]
|
transform: [{ translateX: this.transX }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -373,7 +267,7 @@ export default class RoomItem extends React.Component {
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={[styles.container, height && { height }]}
|
style={styles.container}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
>
|
>
|
||||||
<Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
<Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
||||||
|
@ -391,7 +285,7 @@ export default class RoomItem extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
</RectButton>
|
</RectButton>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</Animated.View>
|
||||||
</PanGestureHandler>
|
</PanGestureHandler>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ import {
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
|
|
||||||
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
||||||
|
export const ACTION_WIDTH = 80;
|
||||||
|
export const SMALL_SWIPE = ACTION_WIDTH / 2;
|
||||||
|
export const LONG_SWIPE = ACTION_WIDTH * 3;
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -97,6 +100,15 @@ export default StyleSheet.create({
|
||||||
avatar: {
|
avatar: {
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
},
|
},
|
||||||
|
upperContainer: {
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
actionsContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: ROW_HEIGHT
|
||||||
|
},
|
||||||
actionText: {
|
actionText: {
|
||||||
color: COLOR_WHITE,
|
color: COLOR_WHITE,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
@ -105,37 +117,24 @@ export default StyleSheet.create({
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
...sharedStyles.textSemibold
|
...sharedStyles.textSemibold
|
||||||
},
|
},
|
||||||
actionButtonLeft: {
|
actionLeftButtonContainer: {
|
||||||
flex: 1,
|
position: 'absolute',
|
||||||
backgroundColor: '#497AFC',
|
height: ROW_HEIGHT,
|
||||||
|
backgroundColor: COLOR_PRIMARY,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'flex-end'
|
top: 0
|
||||||
},
|
},
|
||||||
actionButtonRightFav: {
|
actionRightButtonContainer: {
|
||||||
flex: 1,
|
position: 'absolute',
|
||||||
|
height: ROW_HEIGHT,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'flex-start',
|
top: 0,
|
||||||
backgroundColor: '#F4BD3E'
|
backgroundColor: '#54585e'
|
||||||
},
|
},
|
||||||
actionButtonRightHide: {
|
actionButton: {
|
||||||
flex: 1,
|
width: ACTION_WIDTH,
|
||||||
justifyContent: 'center',
|
height: '100%',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'center',
|
||||||
backgroundColor: '#55585D'
|
justifyContent: 'center'
|
||||||
},
|
|
||||||
actionView: {
|
|
||||||
width: 80,
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
leftAction: {
|
|
||||||
...StyleSheet.absoluteFill,
|
|
||||||
flexDirection: 'row-reverse'
|
|
||||||
},
|
|
||||||
rightAction: {
|
|
||||||
...StyleSheet.absoluteFill,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
upperContainer: {
|
|
||||||
overflow: 'hidden'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,6 +39,8 @@ const keyExtractor = item => item.rid;
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
userId: state.login.user && state.login.user.id,
|
userId: state.login.user && state.login.user.id,
|
||||||
|
username: state.login.user && state.login.user.username,
|
||||||
|
token: state.login.user && state.login.user.token,
|
||||||
isAuthenticated: state.login.isAuthenticated,
|
isAuthenticated: state.login.isAuthenticated,
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
|
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
|
||||||
|
@ -95,6 +97,8 @@ export default class RoomsListView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
|
username: PropTypes.string,
|
||||||
|
token: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
server: PropTypes.string,
|
server: PropTypes.string,
|
||||||
searchText: PropTypes.string,
|
searchText: PropTypes.string,
|
||||||
|
@ -395,7 +399,15 @@ export default class RoomsListView extends React.Component {
|
||||||
|
|
||||||
toggleFav = async(rid, favorite) => {
|
toggleFav = async(rid, favorite) => {
|
||||||
try {
|
try {
|
||||||
await RocketChat.toggleFavorite(rid, !favorite);
|
const result = await RocketChat.toggleFavorite(rid, !favorite);
|
||||||
|
if (result.success) {
|
||||||
|
database.write(() => {
|
||||||
|
const sub = database.objects('subscriptions').filtered('rid == $0', rid)[0];
|
||||||
|
if (sub) {
|
||||||
|
sub.f = !favorite;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('error_toggle_favorite', e);
|
log('error_toggle_favorite', e);
|
||||||
}
|
}
|
||||||
|
@ -461,7 +473,7 @@ export default class RoomsListView extends React.Component {
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const { width } = this.state;
|
const { width } = this.state;
|
||||||
const {
|
const {
|
||||||
userId, baseUrl, StoreLastMessage
|
userId, username, token, baseUrl, StoreLastMessage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const id = item.rid.replace(userId, '').trim();
|
const id = item.rid.replace(userId, '').trim();
|
||||||
|
|
||||||
|
@ -478,6 +490,9 @@ export default class RoomsListView extends React.Component {
|
||||||
_updatedAt={item.roomUpdatedAt}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
id={id}
|
id={id}
|
||||||
|
userId={userId}
|
||||||
|
username={username}
|
||||||
|
token={token}
|
||||||
rid={item.rid}
|
rid={item.rid}
|
||||||
type={item.t}
|
type={item.t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
@ -486,7 +501,6 @@ export default class RoomsListView extends React.Component {
|
||||||
onPress={() => this._onPressItem(item)}
|
onPress={() => this._onPressItem(item)}
|
||||||
testID={`rooms-list-view-item-${ item.name }`}
|
testID={`rooms-list-view-item-${ item.name }`}
|
||||||
width={width}
|
width={width}
|
||||||
height={ROW_HEIGHT}
|
|
||||||
toggleFav={this.toggleFav}
|
toggleFav={this.toggleFav}
|
||||||
toggleRead={this.toggleRead}
|
toggleRead={this.toggleRead}
|
||||||
hideChannel={this.hideChannel}
|
hideChannel={this.hideChannel}
|
||||||
|
@ -591,7 +605,7 @@ export default class RoomsListView extends React.Component {
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
ListHeaderComponent={this.renderListHeader}
|
ListHeaderComponent={this.renderListHeader}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
removeClippedSubviews
|
// removeClippedSubviews
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
initialNumToRender={9}
|
initialNumToRender={9}
|
||||||
windowSize={9}
|
windowSize={9}
|
||||||
|
|
Loading…
Reference in New Issue