rn: replace 3rd party chat library with custom implementation
This commit is contained in:
parent
1cb9bbc7a4
commit
0b6c51f666
|
@ -2423,15 +2423,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
|
||||
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
|
||||
},
|
||||
"@expo/react-native-action-sheet": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@expo/react-native-action-sheet/-/react-native-action-sheet-1.1.2.tgz",
|
||||
"integrity": "sha512-//2EvHVBFVGSAzuJvG0I1UoQVzGJBo2f1CkO+RMnEWdR0FeWYmV7+pCThIroL1czRm/oOtoMxiGS6FgXt6QgVA==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "^2.2.2",
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"@jitsi/sdp-interop": {
|
||||
"version": "0.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-0.1.14.tgz",
|
||||
|
@ -2843,12 +2834,13 @@
|
|||
"@segment/top-domain": "^3.0.0",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"json3": "^3.3.2",
|
||||
"lodash": "^4.17.4"
|
||||
"lodash": "^4.17.4",
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": {
|
||||
"version": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d",
|
||||
"from": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
"from": "github:amplitude/ua-parser-js#ed538f1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3506,14 +3498,6 @@
|
|||
"util.promisify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-check-es2015-constants": {
|
||||
"version": "6.22.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
|
||||
"integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
|
||||
"requires": {
|
||||
"babel-runtime": "^6.22.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-emotion": {
|
||||
"version": "9.2.11",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz",
|
||||
|
@ -8868,11 +8852,6 @@
|
|||
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
|
||||
"integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
|
||||
},
|
||||
"keymirror": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz",
|
||||
"integrity": "sha1-kYiJ6hP40KQufFVyUO7nE63JXDU="
|
||||
},
|
||||
"killable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||
|
@ -12181,37 +12160,11 @@
|
|||
"jssha": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"react-native-communications": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-communications/-/react-native-communications-2.2.1.tgz",
|
||||
"integrity": "sha1-eIO1ayCgAu63kMET+GFuqGksp5U="
|
||||
},
|
||||
"react-native-fast-image": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz",
|
||||
"integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA=="
|
||||
},
|
||||
"react-native-gifted-chat": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gifted-chat/-/react-native-gifted-chat-0.6.0.tgz",
|
||||
"integrity": "sha512-KYI/okKUZmjcJM3I6BP10KG1WNkCKBZhY8N47wk407dr+KqLS4+LR13UKo7j3f++5SrX2Ex+7vYvIQ2pBdzCiA==",
|
||||
"requires": {
|
||||
"@expo/react-native-action-sheet": "^1.0.1",
|
||||
"moment": "^2.19.0",
|
||||
"react-native-communications": "2.2.1",
|
||||
"react-native-lightbox": "^0.7.0",
|
||||
"react-native-parsed-text": "^0.0.20",
|
||||
"react-native-video": "^3.2.1",
|
||||
"uuid": "3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.0.tgz",
|
||||
"integrity": "sha512-ijO9N2xY/YaOqQ5yz5c4sy2ZjWmA6AR6zASb/gdpeKZ8+948CxwfMW9RrKVk5may6ev8c0/Xguu32e2Llelpqw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-google-signin": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-1.0.2.tgz",
|
||||
|
@ -12227,28 +12180,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-4.0.0.tgz",
|
||||
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
|
||||
},
|
||||
"react-native-lightbox": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-lightbox/-/react-native-lightbox-0.7.0.tgz",
|
||||
"integrity": "sha512-HS3T4WlCd0Gb3us2d6Jse5m6KjNhngnKm35Wapq30WtQa9s+/VMmtuktbGPGaWtswcDyOj6qByeJBw9W80iPCA==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-native-linear-gradient": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.3.tgz",
|
||||
"integrity": "sha512-XdusrOXXlkI+yQpUW7YLeiq9cZiBwkvQX4XEkHPVrJ9H47gsKmdgBwObkZBzBQUP0dKK/Sg6aVpETEis4w43bQ=="
|
||||
},
|
||||
"react-native-parsed-text": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/react-native-parsed-text/-/react-native-parsed-text-0.0.20.tgz",
|
||||
"integrity": "sha512-n77hYu64Tr3oclzIXBXXaiLh1WbMKdA2Y0x6bX/yqwxAM4afcObENY5VrNB+EsTBJBEDqrypA9D1p2cLEIHkuQ==",
|
||||
"requires": {
|
||||
"babel-plugin-check-es2015-constants": "6.22.0",
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-native-sound": {
|
||||
"version": "0.10.12",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.12.tgz",
|
||||
|
@ -12305,15 +12241,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-native-video": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-3.2.1.tgz",
|
||||
"integrity": "sha512-Xansfoo/to80FwhM1HKlf7pCxDZ5RtV+kG3piCVvsNAhPY4GGwiOGUH9y3Y+mFQIDEWcY8I9j16lsFYAbnue3g==",
|
||||
"requires": {
|
||||
"keymirror": "0.1.1",
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "github:jitsi/react-native-webrtc#659d2fe417b52356b1b706636de97e23bae3e9f5",
|
||||
"from": "github:jitsi/react-native-webrtc#659d2fe417b52356b1b706636de97e23bae3e9f5",
|
||||
|
|
|
@ -67,7 +67,6 @@
|
|||
"react-native-calendar-events": "1.6.4",
|
||||
"react-native-callstats": "3.58.2",
|
||||
"react-native-fast-image": "5.1.1",
|
||||
"react-native-gifted-chat": "0.6.0",
|
||||
"react-native-google-signin": "1.0.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
|
|
|
@ -43,6 +43,6 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
|||
const { message } = ownProps;
|
||||
|
||||
return {
|
||||
_avatarURL: getAvatarURLByParticipantId(state, message.user._id)
|
||||
_avatarURL: getAvatarURLByParticipantId(state, message.id)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
import { GiftedChat } from 'react-native-gifted-chat';
|
||||
import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
|
@ -20,7 +19,8 @@ import AbstractChat, {
|
|||
type Props
|
||||
} from '../AbstractChat';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
|
@ -28,111 +28,31 @@ import styles from './styles';
|
|||
* the mobile client.
|
||||
*/
|
||||
class Chat extends AbstractChat<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onSend = this._onSend.bind(this);
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
this._transformMessage = this._transformMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
// Gifted chat requires a special object format and a reversed list
|
||||
// of messages.
|
||||
const messages
|
||||
= this.props._messages.map(this._transformMessage).reverse();
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
position = 'bottom'
|
||||
show = { this.props._isOpen } >
|
||||
<View style = { styles.chatContainer }>
|
||||
<KeyboardAvoidingView
|
||||
behavior = 'padding'
|
||||
style = { styles.chatContainer }>
|
||||
<Header>
|
||||
<BackButton onPress = { this.props._onToggleChat } />
|
||||
<HeaderLabel labelKey = 'chat.title' />
|
||||
</Header>
|
||||
<SafeAreaView style = { styles.backdrop }>
|
||||
<GiftedChat
|
||||
messages = { messages }
|
||||
onSend = { this._onSend }
|
||||
renderMessage = { this._renderMessage } />
|
||||
<MessageContainer messages = { this.props._messages } />
|
||||
<ChatInputBar onSend = { this.props._onSendMessage } />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SlidingView>
|
||||
);
|
||||
}
|
||||
|
||||
_onSend: (Array<Object>) => void;
|
||||
|
||||
/**
|
||||
* Callback to trigger a message send action.
|
||||
*
|
||||
* @param {string} message - The chat message to display.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSend([ message ]) {
|
||||
this.props._onSendMessage(message.text);
|
||||
}
|
||||
|
||||
_renderMessage: Object => React$Element<*>
|
||||
|
||||
/**
|
||||
* Renders a single message.
|
||||
*
|
||||
* @param {Object} messageProps - The message props object to be rendered.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage(messageProps) {
|
||||
const { currentMessage } = messageProps;
|
||||
|
||||
return (
|
||||
<ChatMessage message = { currentMessage } />
|
||||
);
|
||||
}
|
||||
|
||||
_transformMessage: (Object, number) => Object;
|
||||
|
||||
/**
|
||||
* Transforms a Jitsi message object to a format that gifted-chat can
|
||||
* handle.
|
||||
*
|
||||
* @param {Object} message - The chat message in our internal format.
|
||||
* @param {number} index - The index of the message in the array.
|
||||
* @returns {Object}
|
||||
*/
|
||||
_transformMessage(message, index) {
|
||||
const system = message.messageType === 'error';
|
||||
|
||||
return (
|
||||
{
|
||||
_id: index,
|
||||
createdAt: new Date(message.timestamp),
|
||||
messageType: message.messageType,
|
||||
system,
|
||||
text: system
|
||||
? this.props.t('chat.error', {
|
||||
error: message.error,
|
||||
originalText: message.message
|
||||
})
|
||||
: message.message,
|
||||
user: {
|
||||
_id: message.id,
|
||||
name: message.displayName
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { TextInput, View } from 'react-native';
|
||||
|
||||
import { Platform } from '../../../base/react';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Boolean to show if an extra padding needs to be added to the bar.
|
||||
*/
|
||||
addPadding: boolean,
|
||||
|
||||
/**
|
||||
* The value of the input field.
|
||||
*/
|
||||
message: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the chat input bar with text field and action(s).
|
||||
*/
|
||||
export default class ChatInputBar extends Component<Props, State> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
addPadding: false,
|
||||
message: ''
|
||||
};
|
||||
|
||||
this._onChangeText = this._onChangeText.bind(this);
|
||||
this._onFocused = this._onFocused.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.inputBar,
|
||||
this.state.addPadding ? styles.extraBarPadding : null
|
||||
] }>
|
||||
<TextInput
|
||||
blurOnSubmit = { false }
|
||||
multiline = { false }
|
||||
onBlur = { this._onFocused(false) }
|
||||
onChangeText = { this._onChangeText }
|
||||
onFocus = { this._onFocused(true) }
|
||||
onSubmitEditing = { this._onSubmit }
|
||||
returnKeyType = 'send'
|
||||
style = { styles.inputField }
|
||||
value = { this.state.message } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_onChangeText: string => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the change of the value of the text field.
|
||||
*
|
||||
* @param {string} text - The current value of the field.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeText(text) {
|
||||
this.setState({
|
||||
message: text
|
||||
});
|
||||
}
|
||||
|
||||
_onFocused: boolean => Function;
|
||||
|
||||
/**
|
||||
* Constructs a callback to be used to update the padding of the field if necessary.
|
||||
*
|
||||
* @param {boolean} focused - True of the field is focused.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onFocused(focused) {
|
||||
return () => {
|
||||
Platform.OS === 'android' && this.setState({
|
||||
addPadding: focused
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
|
||||
/**
|
||||
* Callback to handle the submit event of the text field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmit() {
|
||||
const message = this.state.message.trim();
|
||||
|
||||
message && this.props.onSend(message);
|
||||
this.setState({ message: '' });
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
render() {
|
||||
const { message } = this.props;
|
||||
const timeStamp = getLocalizedDateFormatter(
|
||||
message.createdAt).format(TIMESTAMP_FORMAT);
|
||||
new Date(message.timestamp)).format(TIMESTAMP_FORMAT);
|
||||
const localMessage = message.messageType === 'local';
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
|
@ -53,7 +53,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.ownTextWrapper);
|
||||
} else if (message.system) {
|
||||
} else if (message.messageType === 'error') {
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.systemTextWrapper);
|
||||
}
|
||||
|
@ -74,7 +74,12 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
!localMessage && this._renderDisplayName()
|
||||
}
|
||||
<Text style = { styles.messageText }>
|
||||
{ message.text }
|
||||
{ message.messageType === 'error'
|
||||
? this.props.t('chat.error', {
|
||||
error: message.error,
|
||||
originalText: message.message
|
||||
})
|
||||
: message.message }
|
||||
</Text>
|
||||
</View>
|
||||
<Text style = { styles.timeText }>
|
||||
|
@ -112,7 +117,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
|
||||
return (
|
||||
<Text style = { styles.displayName }>
|
||||
{ message.user.name }
|
||||
{ message.displayName }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class MessageContainer extends Component<Props> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<FlatList
|
||||
data = { this.props.messages }
|
||||
inverted = { true }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
renderItem = { this._renderMessage }
|
||||
style = { styles.messageContainer } />
|
||||
);
|
||||
}
|
||||
|
||||
_keyExtractor: Object => string
|
||||
|
||||
/**
|
||||
* Key extractor for the flatlist.
|
||||
*
|
||||
* @param {Object} item - The flatlist item that we need the key to be
|
||||
* generated for.
|
||||
* @param {number} index - The index of the element.
|
||||
* @returns {string}
|
||||
*/
|
||||
_keyExtractor(item, index) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
_renderMessage: Object => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage({ item: message }) {
|
||||
return (
|
||||
<ChatMessage message = { message } />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the feature chat.
|
||||
|
@ -28,6 +28,7 @@ export default {
|
|||
},
|
||||
|
||||
chatContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
@ -49,6 +50,29 @@ export default {
|
|||
fontSize: 13
|
||||
},
|
||||
|
||||
/**
|
||||
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
|
||||
*/
|
||||
extraBarPadding: {
|
||||
paddingBottom: 30
|
||||
},
|
||||
|
||||
inputBar: {
|
||||
borderTopColor: 'rgb(209, 219, 231)',
|
||||
borderTopWidth: 1,
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: BoxModel.padding
|
||||
},
|
||||
|
||||
inputField: {
|
||||
flex: 1,
|
||||
height: 48
|
||||
},
|
||||
|
||||
messageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The message text itself.
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,11 @@ export function getUnreadCount(state: Object) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (navigator.product === 'ReactNative') {
|
||||
// React native stores the messages in a reversed order.
|
||||
return messages.indexOf(lastReadMessage);
|
||||
}
|
||||
|
||||
const lastReadIndex = messages.lastIndexOf(lastReadMessage);
|
||||
|
||||
return messagesCount - (lastReadIndex + 1);
|
||||
|
|
|
@ -22,14 +22,22 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
|||
timestamp: action.timestamp
|
||||
};
|
||||
|
||||
// React native, unlike web, needs a reverse sorted message list.
|
||||
const messages = navigator.product === 'ReactNative'
|
||||
? [
|
||||
newMessage,
|
||||
...state.messages
|
||||
]
|
||||
: [
|
||||
...state.messages,
|
||||
newMessage
|
||||
];
|
||||
|
||||
return {
|
||||
...state,
|
||||
lastReadMessage:
|
||||
action.hasRead ? newMessage : state.lastReadMessage,
|
||||
messages: [
|
||||
...state.messages,
|
||||
newMessage
|
||||
]
|
||||
messages
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -44,7 +52,8 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
|||
return {
|
||||
...state,
|
||||
isOpen: !state.isOpen,
|
||||
lastReadMessage: state.messages[state.messages.length - 1]
|
||||
lastReadMessage: state.messages[
|
||||
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue