Room item layout (#835)
This commit is contained in:
parent
03adaa3f81
commit
0266cc2e01
|
@ -20808,6 +20808,10 @@ exports[`Storyshots RoomItem list 1`] = `
|
||||||
View
|
View
|
||||||
View
|
View
|
||||||
View
|
View
|
||||||
|
View
|
||||||
|
View
|
||||||
|
View
|
||||||
|
View
|
||||||
<Text
|
<Text
|
||||||
style={
|
style={
|
||||||
Array [
|
Array [
|
||||||
|
|
|
@ -1,73 +1,69 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, ViewPropTypes } from 'react-native';
|
import { View, ViewPropTypes } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
export default class Avatar extends PureComponent {
|
const Avatar = React.memo(({
|
||||||
static propTypes = {
|
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token
|
||||||
baseUrl: PropTypes.string.isRequired,
|
}) => {
|
||||||
style: ViewPropTypes.style,
|
const avatarStyle = {
|
||||||
text: PropTypes.string,
|
width: size,
|
||||||
avatar: PropTypes.string,
|
height: size,
|
||||||
size: PropTypes.number,
|
borderRadius
|
||||||
borderRadius: PropTypes.number,
|
};
|
||||||
type: PropTypes.string,
|
|
||||||
children: PropTypes.object,
|
if (!text && !avatar) {
|
||||||
user: PropTypes.shape({
|
return null;
|
||||||
id: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
const room = type === 'd' ? text : `@${ text }`;
|
||||||
text: '',
|
|
||||||
size: 25,
|
// Avoid requesting several sizes by having only two sizes on cache
|
||||||
type: 'd',
|
const uriSize = size === 100 ? 100 : 50;
|
||||||
borderRadius: 4
|
|
||||||
|
let avatarAuthURLFragment = '';
|
||||||
|
if (userId && token) {
|
||||||
|
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const uri = avatar || `${ baseUrl }/avatar/${ room }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }`;
|
||||||
const {
|
|
||||||
text, size, baseUrl, borderRadius, style, avatar, type, children, user
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const avatarStyle = {
|
const image = (
|
||||||
width: size,
|
<FastImage
|
||||||
height: size,
|
style={avatarStyle}
|
||||||
borderRadius
|
source={{
|
||||||
};
|
uri,
|
||||||
|
priority: FastImage.priority.high
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
if (!text && !avatar) {
|
return (
|
||||||
return null;
|
<View style={[avatarStyle, style]}>
|
||||||
}
|
{image}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const room = type === 'd' ? text : `@${ text }`;
|
Avatar.propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
style: ViewPropTypes.style,
|
||||||
|
text: PropTypes.string,
|
||||||
|
avatar: PropTypes.string,
|
||||||
|
size: PropTypes.number,
|
||||||
|
borderRadius: PropTypes.number,
|
||||||
|
type: PropTypes.string,
|
||||||
|
children: PropTypes.object,
|
||||||
|
userId: PropTypes.string,
|
||||||
|
token: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
// Avoid requesting several sizes by having only two sizes on cache
|
Avatar.defaultProps = {
|
||||||
const uriSize = size === 100 ? 100 : 50;
|
text: '',
|
||||||
|
size: 25,
|
||||||
|
type: 'd',
|
||||||
|
borderRadius: 4
|
||||||
|
};
|
||||||
|
|
||||||
let avatarAuthURLFragment = '';
|
export default Avatar;
|
||||||
if (user && user.id && user.token) {
|
|
||||||
avatarAuthURLFragment = `&rc_token=${ user.token }&rc_uid=${ user.id }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = avatar || `${ baseUrl }/avatar/${ room }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }`;
|
|
||||||
|
|
||||||
const image = (
|
|
||||||
<FastImage
|
|
||||||
style={avatarStyle}
|
|
||||||
source={{
|
|
||||||
uri,
|
|
||||||
priority: FastImage.priority.high
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[avatarStyle, style]}>
|
|
||||||
{image}
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -706,7 +706,8 @@ class MessageBox extends Component {
|
||||||
size={30}
|
size={30}
|
||||||
type={item.username ? 'd' : 'c'}
|
type={item.username ? 'd' : 'c'}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
/>,
|
/>,
|
||||||
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name }</Text>
|
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name }</Text>
|
||||||
]
|
]
|
||||||
|
|
|
@ -238,7 +238,8 @@ export default class Message extends PureComponent {
|
||||||
borderRadius={4}
|
borderRadius={4}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,278 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import moment from 'moment';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {
|
|
||||||
View, Text, StyleSheet, PixelRatio
|
|
||||||
} from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { emojify } from 'react-emojione';
|
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
|
||||||
|
|
||||||
import Avatar from '../containers/Avatar';
|
|
||||||
import Status from '../containers/Status';
|
|
||||||
import RoomTypeIcon from '../containers/RoomTypeIcon';
|
|
||||||
import I18n from '../i18n';
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
import { COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE } from '../constants/colors';
|
|
||||||
|
|
||||||
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: 14,
|
|
||||||
height: ROW_HEIGHT
|
|
||||||
},
|
|
||||||
centerContainer: {
|
|
||||||
flex: 1,
|
|
||||||
paddingVertical: 10,
|
|
||||||
paddingRight: 14,
|
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
||||||
borderColor: COLOR_SEPARATOR
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
flex: 1,
|
|
||||||
fontSize: 17,
|
|
||||||
lineHeight: 20,
|
|
||||||
...sharedStyles.textColorNormal,
|
|
||||||
...sharedStyles.textMedium
|
|
||||||
},
|
|
||||||
alert: {
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start'
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
width: '100%',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
date: {
|
|
||||||
fontSize: 13,
|
|
||||||
marginLeft: 4,
|
|
||||||
...sharedStyles.textColorDescription,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
updateAlert: {
|
|
||||||
color: COLOR_PRIMARY,
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
},
|
|
||||||
unreadNumberContainer: {
|
|
||||||
minWidth: 23,
|
|
||||||
padding: 3,
|
|
||||||
borderRadius: 4,
|
|
||||||
backgroundColor: COLOR_PRIMARY,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginLeft: 10
|
|
||||||
},
|
|
||||||
unreadNumberText: {
|
|
||||||
color: COLOR_WHITE,
|
|
||||||
overflow: 'hidden',
|
|
||||||
fontSize: 13,
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
letterSpacing: 0.56
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
marginRight: 7,
|
|
||||||
marginTop: 3
|
|
||||||
},
|
|
||||||
markdownText: {
|
|
||||||
flex: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 17,
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
...sharedStyles.textColorDescription
|
|
||||||
},
|
|
||||||
markdownTextAlert: {
|
|
||||||
...sharedStyles.textColorNormal
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
marginRight: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderNumber = (unread, userMentions) => {
|
|
||||||
if (!unread || unread <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unread >= 1000) {
|
|
||||||
unread = '999+';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userMentions > 0) {
|
|
||||||
unread = `@ ${ unread }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.unreadNumberContainer}>
|
|
||||||
<Text style={styles.unreadNumberText}>{ unread }</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const attrs = ['name', 'unread', 'userMentions', 'showLastMessage', 'alert', 'type'];
|
|
||||||
@connect(state => ({
|
|
||||||
user: {
|
|
||||||
id: 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 {
|
|
||||||
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,
|
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
username: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
}),
|
|
||||||
avatarSize: PropTypes.number,
|
|
||||||
testID: PropTypes.string,
|
|
||||||
height: PropTypes.number
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
avatarSize: 48
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
const { lastMessage, _updatedAt } = this.props;
|
|
||||||
const oldlastMessage = lastMessage;
|
|
||||||
const newLastmessage = nextProps.lastMessage;
|
|
||||||
|
|
||||||
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt !== _updatedAt) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react/destructuring-assignment
|
|
||||||
return attrs.some(key => nextProps[key] !== this.props[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
get avatar() {
|
|
||||||
const {
|
|
||||||
type, name, avatarSize, baseUrl, user
|
|
||||||
} = this.props;
|
|
||||||
return <Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={styles.avatar} user={user} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastMessage() {
|
|
||||||
const {
|
|
||||||
lastMessage, type, showLastMessage, user
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!showLastMessage) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!lastMessage) {
|
|
||||||
return I18n.t('No_Message');
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = '';
|
|
||||||
const me = lastMessage.u.username === user.username;
|
|
||||||
|
|
||||||
if (!lastMessage.msg && Object.keys(lastMessage.attachments).length > 0) {
|
|
||||||
if (me) {
|
|
||||||
return I18n.t('User_sent_an_attachment', { user: I18n.t('You') });
|
|
||||||
} else {
|
|
||||||
return I18n.t('User_sent_an_attachment', { user: lastMessage.u.username });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me) {
|
|
||||||
prefix = I18n.t('You_colon');
|
|
||||||
} else if (type !== 'd') {
|
|
||||||
prefix = `${ lastMessage.u.username }: `;
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
|
|
||||||
msg = emojify(msg, { output: 'unicode' });
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
get type() {
|
|
||||||
const { type, id, prid } = this.props;
|
|
||||||
if (type === 'd') {
|
|
||||||
return <Status style={styles.status} size={10} id={id} />;
|
|
||||||
}
|
|
||||||
return <RoomTypeIcon type={prid ? 'discussion' : type} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate = date => moment(date).calendar(null, {
|
|
||||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
|
||||||
sameDay: 'h:mm A',
|
|
||||||
lastWeek: 'dddd',
|
|
||||||
sameElse: 'MMM D'
|
|
||||||
})
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
unread, userMentions, name, _updatedAt, alert, testID, height, onPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const date = this.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 (
|
|
||||||
<RectButton
|
|
||||||
onPress={onPress}
|
|
||||||
activeOpacity={0.8}
|
|
||||||
underlayColor='#e1e5e8'
|
|
||||||
testID={testID}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[styles.container, height && { height }]}
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
>
|
|
||||||
{this.avatar}
|
|
||||||
<View style={styles.centerContainer}>
|
|
||||||
<View style={styles.titleContainer}>
|
|
||||||
{this.type}
|
|
||||||
<Text style={[styles.title, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
|
|
||||||
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
|
||||||
</View>
|
|
||||||
<View style={styles.row}>
|
|
||||||
<Text style={[styles.markdownText, alert && styles.markdownTextAlert]} numberOfLines={2}>
|
|
||||||
{this.lastMessage}
|
|
||||||
</Text>
|
|
||||||
{renderNumber(unread, userMentions)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</RectButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
import { emojify } from 'react-emojione';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const formatMsg = ({
|
||||||
|
lastMessage, type, showLastMessage, username
|
||||||
|
}) => {
|
||||||
|
if (!showLastMessage) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!lastMessage) {
|
||||||
|
return I18n.t('No_Message');
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = '';
|
||||||
|
const isLastMessageSentByMe = lastMessage.u.username === username;
|
||||||
|
|
||||||
|
if (!lastMessage.msg && Object.keys(lastMessage.attachments).length) {
|
||||||
|
const user = isLastMessageSentByMe ? I18n.t('You') : lastMessage.u.username;
|
||||||
|
return I18n.t('User_sent_an_attachment', { user });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLastMessageSentByMe) {
|
||||||
|
prefix = I18n.t('You_colon');
|
||||||
|
} else if (type !== 'd') {
|
||||||
|
prefix = `${ lastMessage.u.username }: `;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = `${ prefix }${ lastMessage.msg.replace(/[\n\t\r]/igm, '') }`;
|
||||||
|
if (msg) {
|
||||||
|
msg = emojify(msg, { output: 'unicode' });
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps);
|
||||||
|
|
||||||
|
const LastMessage = React.memo(({
|
||||||
|
lastMessage, type, showLastMessage, username
|
||||||
|
}) => (
|
||||||
|
<Text style={[styles.markdownText, alert && styles.markdownTextAlert]} numberOfLines={2}>
|
||||||
|
{formatMsg({
|
||||||
|
lastMessage, type, showLastMessage, username
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
), arePropsEqual);
|
||||||
|
|
||||||
|
LastMessage.propTypes = {
|
||||||
|
lastMessage: PropTypes.object,
|
||||||
|
type: PropTypes.string,
|
||||||
|
showLastMessage: PropTypes.bool,
|
||||||
|
username: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LastMessage;
|
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Status from '../../containers/Status';
|
||||||
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const TypeIcon = React.memo(({ type, id, prid }) => {
|
||||||
|
if (type === 'd') {
|
||||||
|
return <Status style={styles.status} size={10} id={id} />;
|
||||||
|
}
|
||||||
|
return <RoomTypeIcon type={prid ? 'discussion' : type} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
TypeIcon.propTypes = {
|
||||||
|
type: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
|
prid: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TypeIcon;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const UnreadBadge = React.memo(({ unread, userMentions, type }) => {
|
||||||
|
if (!unread || unread <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (unread >= 1000) {
|
||||||
|
unread = '999+';
|
||||||
|
}
|
||||||
|
const mentioned = userMentions > 0 && type !== 'd';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.unreadNumberContainer, mentioned && styles.unreadMentioned]}>
|
||||||
|
<Text style={styles.unreadNumberText}>{ unread }</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
UnreadBadge.propTypes = {
|
||||||
|
unread: PropTypes.number,
|
||||||
|
userMentions: PropTypes.number,
|
||||||
|
type: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnreadBadge;
|
|
@ -0,0 +1,120 @@
|
||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
|
import Avatar from '../../containers/Avatar';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
|
import UnreadBadge from './UnreadBadge';
|
||||||
|
import TypeIcon from './TypeIcon';
|
||||||
|
import LastMessage from './LastMessage';
|
||||||
|
|
||||||
|
export { ROW_HEIGHT };
|
||||||
|
|
||||||
|
const attrs = ['name', 'unread', 'userMentions', 'showLastMessage', 'alert', 'type'];
|
||||||
|
@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 {
|
||||||
|
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,
|
||||||
|
height: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
avatarSize: 48
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
const { lastMessage, _updatedAt } = this.props;
|
||||||
|
const oldlastMessage = lastMessage;
|
||||||
|
const newLastmessage = nextProps.lastMessage;
|
||||||
|
|
||||||
|
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt !== _updatedAt) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
|
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDate = date => moment(date).calendar(null, {
|
||||||
|
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||||
|
sameDay: 'h:mm A',
|
||||||
|
lastWeek: 'dddd',
|
||||||
|
sameElse: 'MMM D'
|
||||||
|
})
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
unread, userMentions, name, _updatedAt, alert, testID, height, type, avatarSize, baseUrl, userId, username, token, onPress, id, prid, showLastMessage, lastMessage
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const date = this.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 (
|
||||||
|
<RectButton
|
||||||
|
onPress={onPress}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
underlayColor='#e1e5e8'
|
||||||
|
testID={testID}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[styles.container, height && { height }]}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
>
|
||||||
|
<Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
||||||
|
<View style={styles.centerContainer}>
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
<TypeIcon type={type} id={id} prid={prid} />
|
||||||
|
<Text style={[styles.title, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
|
||||||
|
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
||||||
|
</View>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<LastMessage lastMessage={lastMessage} type={type} showLastMessage={showLastMessage} username={username} />
|
||||||
|
<UnreadBadge unread={unread} userMentions={userMentions} type={type} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</RectButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { StyleSheet, PixelRatio } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import {
|
||||||
|
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT
|
||||||
|
} from '../../constants/colors';
|
||||||
|
|
||||||
|
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 14,
|
||||||
|
height: ROW_HEIGHT
|
||||||
|
},
|
||||||
|
centerContainer: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingRight: 14,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderColor: COLOR_SEPARATOR
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 17,
|
||||||
|
lineHeight: 20,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
fontSize: 13,
|
||||||
|
marginLeft: 4,
|
||||||
|
...sharedStyles.textColorDescription,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
updateAlert: {
|
||||||
|
color: COLOR_PRIMARY,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
unreadNumberContainer: {
|
||||||
|
minWidth: 22,
|
||||||
|
height: 22,
|
||||||
|
paddingVertical: 3,
|
||||||
|
paddingHorizontal: 5,
|
||||||
|
borderRadius: 14,
|
||||||
|
backgroundColor: COLOR_TEXT,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginLeft: 10
|
||||||
|
},
|
||||||
|
unreadMentioned: {
|
||||||
|
backgroundColor: COLOR_PRIMARY
|
||||||
|
},
|
||||||
|
unreadNumberText: {
|
||||||
|
color: COLOR_WHITE,
|
||||||
|
overflow: 'hidden',
|
||||||
|
fontSize: 13,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
letterSpacing: 0.56,
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
marginRight: 7,
|
||||||
|
marginTop: 3
|
||||||
|
},
|
||||||
|
markdownText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 17,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...sharedStyles.textColorDescription
|
||||||
|
},
|
||||||
|
markdownTextAlert: {
|
||||||
|
...sharedStyles.textColorNormal
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
marginRight: 10
|
||||||
|
}
|
||||||
|
});
|
|
@ -49,7 +49,7 @@ const UserItem = ({
|
||||||
}) => (
|
}) => (
|
||||||
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
|
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} user={user} />
|
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<Text style={styles.name}>{name}</Text>
|
<Text style={styles.name}>{name}</Text>
|
||||||
<Text style={styles.username}>@{username}</Text>
|
<Text style={styles.username}>@{username}</Text>
|
||||||
|
|
|
@ -286,7 +286,7 @@ export default class ProfileView extends LoggedView {
|
||||||
return (
|
return (
|
||||||
<View style={styles.avatarButtons}>
|
<View style={styles.avatarButtons}>
|
||||||
{this.renderAvatarButton({
|
{this.renderAvatarButton({
|
||||||
child: <Avatar text={`@${ user.username }`} size={50} baseUrl={baseUrl} user={user} />,
|
child: <Avatar text={`@${ user.username }`} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
||||||
onPress: () => this.resetAvatar(),
|
onPress: () => this.resetAvatar(),
|
||||||
key: 'profile-view-reset-avatar'
|
key: 'profile-view-reset-avatar'
|
||||||
})}
|
})}
|
||||||
|
@ -305,7 +305,7 @@ export default class ProfileView extends LoggedView {
|
||||||
const { url, blob, contentType } = avatarSuggestions[service];
|
const { url, blob, contentType } = avatarSuggestions[service];
|
||||||
return this.renderAvatarButton({
|
return this.renderAvatarButton({
|
||||||
key: `profile-view-avatar-${ service }`,
|
key: `profile-view-avatar-${ service }`,
|
||||||
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} user={user} />,
|
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
||||||
onPress: () => this.setAvatar({
|
onPress: () => this.setAvatar({
|
||||||
url, data: blob, service, contentType
|
url, data: blob, service, contentType
|
||||||
})
|
})
|
||||||
|
@ -399,7 +399,8 @@ export default class ProfileView extends LoggedView {
|
||||||
avatar={avatar && avatar.url}
|
avatar={avatar && avatar.url}
|
||||||
size={100}
|
size={100}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
|
|
|
@ -397,7 +397,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={t}
|
type={t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
>
|
>
|
||||||
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||||
</Avatar>,
|
</Avatar>,
|
||||||
|
|
|
@ -234,7 +234,8 @@ export default class RoomInfoView extends LoggedView {
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={room.t}
|
type={room.t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
>
|
>
|
||||||
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} size={24} id={roomUser._id} /> : null}
|
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} size={24} id={roomUser._id} /> : null}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
|
@ -230,7 +230,8 @@ export default class Sidebar extends Component {
|
||||||
size={30}
|
size={30}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
user={user}
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
/>
|
/>
|
||||||
<View style={styles.headerTextContainer}>
|
<View style={styles.headerTextContainer}>
|
||||||
<View style={styles.headerUsername}>
|
<View style={styles.headerUsername}>
|
||||||
|
|
|
@ -57,6 +57,10 @@ export default (
|
||||||
<RoomItem alert unread={1000} />
|
<RoomItem alert unread={1000} />
|
||||||
<RoomItem alert unread={1} userMentions={1} />
|
<RoomItem alert unread={1} userMentions={1} />
|
||||||
<RoomItem alert unread={1000} userMentions={1} />
|
<RoomItem alert unread={1000} userMentions={1} />
|
||||||
|
<RoomItem alert name='general' unread={1} type='c' />
|
||||||
|
<RoomItem alert name='general' unread={1000} type='c' />
|
||||||
|
<RoomItem alert name='general' unread={1} userMentions={1} type='c' />
|
||||||
|
<RoomItem alert name='general' unread={1000} userMentions={1} type='c' />
|
||||||
|
|
||||||
<StoriesSeparator title='Last Message' />
|
<StoriesSeparator title='Last Message' />
|
||||||
<RoomItem
|
<RoomItem
|
||||||
|
|
Loading…
Reference in New Issue