[NEW] Read receipt (#975)

* switching to ubountu

* added read Recipt functionality to the app
fix: #542

* placed the check icon on the end of timestamp

* removed linting errors

* updating snapshots

* done requested changes

* removed width scrollView

* done required changes

* fixed linting errors

* added migrations

* resolved conflicts and done requested changes

* undone uneesasary changes

* adding migrations

* done requested changes

* Add stories and fix some issues
This commit is contained in:
Diego Mello 2019-06-10 15:36:31 -03:00 committed by GitHub
parent 467a2d4002
commit d68eb01b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 912 additions and 10 deletions

View File

@ -9216,6 +9216,612 @@ exports[`Storyshots Message list 1`] = `
</View>
</View>
</View>
<Text
style={
Array [
Object {
"fontSize": 20,
"fontWeight": "300",
"marginLeft": 10,
"marginTop": 30,
},
Object {
"marginBottom": 0,
"marginTop": 30,
},
]
}
>
Message with read receipt
</Text>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 36,
"width": 36,
},
Object {
"marginTop": 4,
},
]
}
>
<View
style={
Array [
Object {
"overflow": "hidden",
},
Object {
"borderRadius": 4,
"height": 36,
"width": 36,
},
]
}
>
<FastImageView
resizeMode="cover"
source={
Object {
"priority": "high",
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8",
}
}
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
</View>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
Object {
"marginLeft": 10,
},
]
}
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "500",
"lineHeight": 22,
}
}
>
diego.mello
</Text>
</View>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#9ca2a8",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 22,
"paddingLeft": 10,
}
}
>
10:00 AM
</Text>
</View>
<View
style={Object {}}
>
<Text
numberOfLines={0}
style={
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
"justifyContent": "flex-start",
"marginBottom": 0,
"marginTop": 0,
}
}
>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
}
}
>
<Text>
Im fine!
</Text>
</Text>
</Text>
</View>
</View>
</View>
</View>
</View>
</View>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
false,
]
}
>
<View
style={Object {}}
>
<Text
numberOfLines={0}
style={
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
"justifyContent": "flex-start",
"marginBottom": 0,
"marginTop": 0,
}
}
>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
}
}
>
<Text>
Im fine!
</Text>
</Text>
</Text>
</View>
</View>
</View>
</View>
</View>
</View>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 36,
"width": 36,
},
Object {
"marginTop": 4,
},
]
}
>
<View
style={
Array [
Object {
"overflow": "hidden",
},
Object {
"borderRadius": 4,
"height": 36,
"width": 36,
},
]
}
>
<FastImageView
resizeMode="cover"
source={
Object {
"priority": "high",
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8",
}
}
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
</View>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
Object {
"marginLeft": 10,
},
]
}
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "500",
"lineHeight": 22,
}
}
>
diego.mello
</Text>
</View>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#9ca2a8",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "300",
"lineHeight": 22,
"paddingLeft": 10,
}
}
>
10:00 AM
</Text>
</View>
<View
style={Object {}}
>
<Text
numberOfLines={0}
style={
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
"justifyContent": "flex-start",
"marginBottom": 0,
"marginTop": 0,
}
}
>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
}
}
>
<Text>
Im fine!
</Text>
</Text>
</Text>
</View>
</View>
<Text
allowFontScaling={false}
style={
Array [
Object {
"color": "#1d74f5",
"fontSize": 15,
},
Object {
"lineHeight": 20,
},
Object {
"fontFamily": "custom",
"fontStyle": "normal",
"fontWeight": "normal",
},
Object {},
]
}
>
</Text>
</View>
</View>
</View>
</View>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
false,
]
}
>
<View
style={Object {}}
>
<Text
numberOfLines={0}
style={
Object {
"alignItems": "flex-start",
"flexDirection": "row",
"flexWrap": "wrap",
"justifyContent": "flex-start",
"marginBottom": 0,
"marginTop": 0,
}
}
>
<Text
style={
Object {
"backgroundColor": "transparent",
"color": "#2F343D",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
}
}
>
<Text>
Im fine!
</Text>
</Text>
</Text>
</View>
</View>
<Text
allowFontScaling={false}
style={
Array [
Object {
"color": "#1d74f5",
"fontSize": 15,
},
Object {
"lineHeight": 20,
},
Object {
"fontFamily": "custom",
"fontStyle": "normal",
"fontWeight": "normal",
},
Object {},
]
}
>
</Text>
</View>
</View>
</View>
</View>
<Text
style={
Array [

View File

@ -59,6 +59,12 @@ export default {
Assets_favicon_512: {
type: null
},
Message_Read_Receipt_Enabled: {
type: 'valueAsBoolean'
},
Message_Read_Receipt_Store_Users: {
type: 'valueAsBoolean'
},
Threads_enabled: {
type: null
},

View File

@ -17,6 +17,7 @@ import { vibrate } from '../utils/vibration';
import RocketChat from '../lib/rocketchat';
import I18n from '../i18n';
import log from '../utils/log';
import Navigation from '../lib/Navigation';
@connect(
state => ({
@ -26,7 +27,8 @@ import log from '../utils/log';
Message_AllowEditing: state.settings.Message_AllowEditing,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring
Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users
}),
dispatch => ({
actionsHide: () => dispatch(actionsHideAction()),
@ -56,7 +58,8 @@ export default class MessageActions extends React.Component {
Message_AllowEditing: PropTypes.bool,
Message_AllowEditing_BlockEditInMinutes: PropTypes.number,
Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool
Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: PropTypes.bool
};
constructor(props) {
@ -64,7 +67,7 @@ export default class MessageActions extends React.Component {
this.handleActionPress = this.handleActionPress.bind(this);
this.setPermissions();
const { Message_AllowStarring, Message_AllowPinning } = this.props;
const { Message_AllowStarring, Message_AllowPinning, Message_Read_Receipt_Store_Users } = this.props;
// Cancel
this.options = [I18n.t('Cancel')];
@ -118,6 +121,12 @@ export default class MessageActions extends React.Component {
this.REACTION_INDEX = this.options.length - 1;
}
// Read Receipts
if (Message_Read_Receipt_Store_Users) {
this.options.push(I18n.t('Read_Receipt'));
this.READ_RECEIPT_INDEX = this.options.length - 1;
}
// Report
this.options.push(I18n.t('Report'));
this.REPORT_INDEX = this.options.length - 1;
@ -302,6 +311,11 @@ export default class MessageActions extends React.Component {
toggleReactionPicker(actionMessage);
}
handleReadReceipt = () => {
const { actionMessage } = this.props;
Navigation.navigate('ReadReceiptsView', { messageId: actionMessage._id });
}
handleReport = async() => {
const { actionMessage } = this.props;
try {
@ -348,6 +362,9 @@ export default class MessageActions extends React.Component {
case this.DELETE_INDEX:
this.handleDelete();
break;
case this.READ_RECEIPT_INDEX:
this.handleReadReceipt();
break;
default:
break;
}

View File

@ -16,6 +16,7 @@ import Reactions from './Reactions';
import Broadcast from './Broadcast';
import Discussion from './Discussion';
import Content from './Content';
import ReadReceipt from './ReadReceipt';
const MessageInner = React.memo((props) => {
if (props.type === 'discussion-created') {
@ -72,6 +73,10 @@ const Message = React.memo((props) => {
>
<MessageInner {...props} />
</View>
<ReadReceipt
isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread}
/>
</View>
</View>
);
@ -119,7 +124,9 @@ Message.propTypes = {
hasError: PropTypes.bool,
style: PropTypes.any,
onLongPress: PropTypes.func,
onPress: PropTypes.func
onPress: PropTypes.func,
isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool
};
MessageInner.propTypes = {

View File

@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { COLOR_PRIMARY } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }) => {
if (isReadReceiptEnabled && !unread && unread !== null) {
return <CustomIcon name='check' color={COLOR_PRIMARY} size={15} style={styles.readReceipt} />;
}
return null;
});
ReadReceipt.displayName = 'MessageReadReceipt';
ReadReceipt.propTypes = {
isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool
};
export default ReadReceipt;

View File

@ -24,6 +24,7 @@ export default class MessageContainer extends React.Component {
_updatedAt: PropTypes.instanceOf(Date),
baseUrl: PropTypes.string,
Message_GroupingPeriod: PropTypes.number,
isReadReceiptEnabled: PropTypes.bool,
useRealName: PropTypes.bool,
useMarkdown: PropTypes.bool,
status: PropTypes.number,
@ -57,6 +58,9 @@ export default class MessageContainer extends React.Component {
if (item.tmsg !== nextProps.item.tmsg) {
return true;
}
if (item.unread !== nextProps.item.unread) {
return true;
}
return _updatedAt.toISOString() !== nextProps._updatedAt.toISOString();
}
@ -187,10 +191,10 @@ export default class MessageContainer extends React.Component {
render() {
const {
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled
} = this.props;
const {
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread
} = item;
return (
@ -213,6 +217,8 @@ export default class MessageContainer extends React.Component {
broadcast={broadcast}
baseUrl={baseUrl}
useRealName={useRealName}
isReadReceiptEnabled={isReadReceiptEnabled}
unread={unread}
role={role}
drid={drid}
dcount={dcount}

View File

@ -234,5 +234,8 @@ export default StyleSheet.create({
flex: 1,
color: COLOR_PRIMARY,
...sharedStyles.textRegular
},
readReceipt: {
lineHeight: 20
}
});

View File

@ -233,6 +233,7 @@ export default {
No_Message: 'No Message',
No_messages_yet: 'No messages yet',
No_Reactions: 'No Reactions',
No_Read_Receipts: 'No Read Receipts',
Not_logged: 'Not logged',
Nothing_to_save: 'Nothing to save!',
Notify_active_in_this_room: 'Notify active users in this room',
@ -265,6 +266,7 @@ export default {
Reactions: 'Reactions',
Read_Only_Channel: 'Read Only Channel',
Read_Only: 'Read Only',
Read_Receipt: 'Read Receipt',
Register: 'Register',
Repeat_Password: 'Repeat Password',
Replied_on: 'Replied on:',

View File

@ -266,6 +266,7 @@ export default {
Read_Only_Channel: 'Canal Somente Leitura',
Read_Only: 'Somente Leitura',
Register: 'Registrar',
Read_Receipt: 'Lida por',
Repeat_Password: 'Repetir Senha',
Replied_on: 'Respondido em:',
replies: 'respostas',

View File

@ -29,6 +29,7 @@ import RoomInfoView from './views/RoomInfoView';
import RoomInfoEditView from './views/RoomInfoEditView';
import RoomMembersView from './views/RoomMembersView';
import SearchMessagesView from './views/SearchMessagesView';
import ReadReceiptsView from './views/ReadReceiptView';
import ThreadMessagesView from './views/ThreadMessagesView';
import MessagesView from './views/MessagesView';
import SelectedUsersView from './views/SelectedUsersView';
@ -114,6 +115,7 @@ const ChatsStack = createStackNavigator({
SelectedUsersView,
ThreadMessagesView,
MessagesView,
ReadReceiptsView,
DirectoryView
}, {
defaultNavigationOptions: defaultHeader

View File

@ -26,6 +26,7 @@ export default (msg) => {
msg = normalizeAttachments(msg);
msg.reactions = msg.reactions || [];
msg.unread = msg.unread || false;
// TODO: api problems
// if (Array.isArray(msg.reactions)) {
// msg.reactions = msg.reactions.map((value, key) => ({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));

View File

@ -197,7 +197,8 @@ const messagesSchema = {
tlm: { type: 'date', optional: true },
replies: 'string[]',
mentions: { type: 'list', objectType: 'users' },
channels: { type: 'list', objectType: 'rooms' }
channels: { type: 'list', objectType: 'rooms' },
unread: { type: 'bool', optional: true }
}
};
@ -415,7 +416,7 @@ class DB {
return this.databases.activeDB = new Realm({
path: `${ path }.realm`,
schema,
schemaVersion: 11,
schemaVersion: 12,
migration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 11) {
const newSubs = newRealm.objects('subscriptions');

View File

@ -771,6 +771,12 @@ const RocketChat = {
sort: { ts: -1 }
});
},
getReadReceipts(messageId) {
return this.sdk.get('chat.getMessageReadReceipts', {
messageId
});
},
searchMessages(roomId, searchText) {
// RC 0.60.0
return this.sdk.get('chat.search', {

View File

@ -0,0 +1,146 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import moment from 'moment';
import { connect } from 'react-redux';
import Avatar from '../../containers/Avatar';
import styles from './styles';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import RocketChat from '../../lib/rocketchat';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
Message_TimeFormat: state.settings.Message_TimeFormat,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
userId: state.login.user && state.login.user.id,
token: state.login.user && state.login.user.token
}))
export default class ReadReceiptsView extends React.Component {
static navigationOptions = {
title: I18n.t('Read_Receipt')
}
static propTypes = {
navigation: PropTypes.object,
Message_TimeFormat: PropTypes.string,
baseUrl: PropTypes.string,
userId: PropTypes.string,
token: PropTypes.string
}
constructor(props) {
super(props);
this.messageId = props.navigation.getParam('messageId');
this.state = {
loading: false,
receipts: []
};
}
componentDidMount() {
this.load();
}
shouldComponentUpdate(nextProps, nextState) {
const { loading, receipts } = this.state;
if (nextState.loading !== loading) {
return true;
}
if (!equal(nextState.receipts, receipts)) {
return true;
}
return false;
}
load = async() => {
const { loading } = this.state;
if (loading) {
return;
}
this.setState({ loading: true });
try {
const result = await RocketChat.getReadReceipts(this.messageId);
if (result.success) {
this.setState({
receipts: result.receipts,
loading: false
});
}
} catch (error) {
this.setState({ loading: false });
console.log('err_fetch_read_receipts', error);
}
}
renderEmpty = () => (
<View style={styles.listEmptyContainer} testID='read-receipt-view'>
<Text>{I18n.t('No_Read_Receipts')}</Text>
</View>
)
renderItem = ({ item }) => {
const {
Message_TimeFormat, userId, baseUrl, token
} = this.props;
const time = moment(item.ts).format(Message_TimeFormat);
return (
<View style={styles.itemContainer}>
<Avatar
text={item.user.username}
size={40}
baseUrl={baseUrl}
userId={userId}
token={token}
/>
<View style={styles.infoContainer}>
<View style={styles.item}>
<Text style={styles.name}>
{item.user.name}
</Text>
<Text>
{time}
</Text>
</View>
<Text>
{`@${ item.user.username }`}
</Text>
</View>
</View>
);
}
renderSeparator = () => <View style={styles.separator} />;
render() {
const { receipts, loading } = this.state;
if (!loading && receipts.length === 0) {
return this.renderEmpty();
}
return (
<SafeAreaView style={styles.container} testID='read-receipt-view' forceInset={{ bottom: 'always' }}>
<StatusBar />
<View>
{loading
? <RCActivityIndicator />
: (
<FlatList
data={receipts}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
style={styles.list}
keyExtractor={item => item._id}
/>
)}
</View>
</SafeAreaView>
);
}
}

View File

@ -0,0 +1,50 @@
import { StyleSheet } from 'react-native';
import { COLOR_SEPARATOR, COLOR_WHITE, COLOR_BACKGROUND_CONTAINER } from '../../constants/colors';
import sharedStyles from '../Styles';
export default StyleSheet.create({
listEmptyContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: COLOR_BACKGROUND_CONTAINER
},
item: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between'
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: COLOR_SEPARATOR
},
name: {
...sharedStyles.textRegular,
...sharedStyles.textColorTitle,
fontSize: 17
},
username: {
flex: 1,
...sharedStyles.textRegular,
...sharedStyles.textColorDescription,
fontSize: 14
},
infoContainer: {
flex: 1,
marginLeft: 10
},
itemContainer: {
flex: 1,
flexDirection: 'row',
padding: 10,
backgroundColor: COLOR_WHITE
},
container: {
flex: 1,
backgroundColor: COLOR_BACKGROUND_CONTAINER
},
list: {
...sharedStyles.separatorVertical,
marginVertical: 10
}
});

View File

@ -60,7 +60,8 @@ import { Toast } from '../../utils/info';
Message_GroupingPeriod: state.settings.Message_GroupingPeriod,
Message_TimeFormat: state.settings.Message_TimeFormat,
useMarkdown: state.markdown.useMarkdown,
baseUrl: state.settings.baseUrl || state.server ? state.server.server : ''
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
Message_Read_Receipt_Enabled: state.settings.Message_Read_Receipt_Enabled
}), dispatch => ({
editCancel: () => dispatch(editCancelAction()),
replyCancel: () => dispatch(replyCancelAction()),
@ -116,6 +117,7 @@ export default class RoomView extends React.Component {
isAuthenticated: PropTypes.bool,
Message_GroupingPeriod: PropTypes.number,
Message_TimeFormat: PropTypes.string,
Message_Read_Receipt_Enabled: PropTypes.bool,
editing: PropTypes.bool,
replying: PropTypes.bool,
baseUrl: PropTypes.string,
@ -499,7 +501,7 @@ export default class RoomView extends React.Component {
renderItem = (item, previousItem) => {
const { room, lastOpen } = this.state;
const {
user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, useMarkdown
user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, useMarkdown, Message_Read_Receipt_Enabled
} = this.props;
let dateSeparator = null;
let showUnreadSeparator = false;
@ -541,6 +543,7 @@ export default class RoomView extends React.Component {
timeFormat={Message_TimeFormat}
useRealName={useRealName}
useMarkdown={useMarkdown}
isReadReceiptEnabled={Message_Read_Receipt_Enabled}
/>
);

View File

@ -311,6 +311,30 @@ export default (
}]}
/>
<Separator title='Message with read receipt' />
<Message
msg="I'm fine!"
isReadReceiptEnabled
unread
/>
<Message
msg="I'm fine!"
isReadReceiptEnabled
unread
isHeader={false}
/>
<Message
msg="I'm fine!"
isReadReceiptEnabled
read
/>
<Message
msg="I'm fine!"
isReadReceiptEnabled
read
isHeader={false}
/>
<Separator title='Message with thread' />
<Message
msg='How are you?'