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",
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
|
||||||
"integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
|
"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": {
|
"@jitsi/sdp-interop": {
|
||||||
"version": "0.1.14",
|
"version": "0.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-0.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-0.1.14.tgz",
|
||||||
|
@ -2843,12 +2834,13 @@
|
||||||
"@segment/top-domain": "^3.0.0",
|
"@segment/top-domain": "^3.0.0",
|
||||||
"blueimp-md5": "^2.10.0",
|
"blueimp-md5": "^2.10.0",
|
||||||
"json3": "^3.3.2",
|
"json3": "^3.3.2",
|
||||||
"lodash": "^4.17.4"
|
"lodash": "^4.17.4",
|
||||||
|
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d",
|
"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"
|
"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": {
|
"babel-plugin-emotion": {
|
||||||
"version": "9.2.11",
|
"version": "9.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
|
||||||
"integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
|
"integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
|
||||||
},
|
},
|
||||||
"keymirror": {
|
|
||||||
"version": "0.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz",
|
|
||||||
"integrity": "sha1-kYiJ6hP40KQufFVyUO7nE63JXDU="
|
|
||||||
},
|
|
||||||
"killable": {
|
"killable": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||||
|
@ -12181,37 +12160,11 @@
|
||||||
"jssha": "^2.2.0"
|
"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": {
|
"react-native-fast-image": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz",
|
||||||
"integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA=="
|
"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": {
|
"react-native-google-signin": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-1.0.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-4.0.0.tgz",
|
||||||
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
|
"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": {
|
"react-native-linear-gradient": {
|
||||||
"version": "2.5.3",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.3.tgz",
|
||||||
"integrity": "sha512-XdusrOXXlkI+yQpUW7YLeiq9cZiBwkvQX4XEkHPVrJ9H47gsKmdgBwObkZBzBQUP0dKK/Sg6aVpETEis4w43bQ=="
|
"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": {
|
"react-native-sound": {
|
||||||
"version": "0.10.12",
|
"version": "0.10.12",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.12.tgz",
|
"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": {
|
"react-native-webrtc": {
|
||||||
"version": "github:jitsi/react-native-webrtc#659d2fe417b52356b1b706636de97e23bae3e9f5",
|
"version": "github:jitsi/react-native-webrtc#659d2fe417b52356b1b706636de97e23bae3e9f5",
|
||||||
"from": "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-calendar-events": "1.6.4",
|
||||||
"react-native-callstats": "3.58.2",
|
"react-native-callstats": "3.58.2",
|
||||||
"react-native-fast-image": "5.1.1",
|
"react-native-fast-image": "5.1.1",
|
||||||
"react-native-gifted-chat": "0.6.0",
|
|
||||||
"react-native-google-signin": "1.0.2",
|
"react-native-google-signin": "1.0.2",
|
||||||
"react-native-immersive": "2.0.0",
|
"react-native-immersive": "2.0.0",
|
||||||
"react-native-keep-awake": "4.0.0",
|
"react-native-keep-awake": "4.0.0",
|
||||||
|
|
|
@ -43,6 +43,6 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||||
const { message } = ownProps;
|
const { message } = ownProps;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_avatarURL: getAvatarURLByParticipantId(state, message.user._id)
|
_avatarURL: getAvatarURLByParticipantId(state, message.id)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SafeAreaView, View } from 'react-native';
|
import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
|
||||||
import { GiftedChat } from 'react-native-gifted-chat';
|
|
||||||
|
|
||||||
import { translate } from '../../../base/i18n';
|
import { translate } from '../../../base/i18n';
|
||||||
|
|
||||||
|
@ -20,7 +19,8 @@ import AbstractChat, {
|
||||||
type Props
|
type Props
|
||||||
} from '../AbstractChat';
|
} from '../AbstractChat';
|
||||||
|
|
||||||
import ChatMessage from './ChatMessage';
|
import ChatInputBar from './ChatInputBar';
|
||||||
|
import MessageContainer from './MessageContainer';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,111 +28,31 @@ import styles from './styles';
|
||||||
* the mobile client.
|
* the mobile client.
|
||||||
*/
|
*/
|
||||||
class Chat extends AbstractChat<Props> {
|
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()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
// Gifted chat requires a special object format and a reversed list
|
|
||||||
// of messages.
|
|
||||||
const messages
|
|
||||||
= this.props._messages.map(this._transformMessage).reverse();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SlidingView
|
<SlidingView
|
||||||
position = 'bottom'
|
position = 'bottom'
|
||||||
show = { this.props._isOpen } >
|
show = { this.props._isOpen } >
|
||||||
<View style = { styles.chatContainer }>
|
<KeyboardAvoidingView
|
||||||
|
behavior = 'padding'
|
||||||
|
style = { styles.chatContainer }>
|
||||||
<Header>
|
<Header>
|
||||||
<BackButton onPress = { this.props._onToggleChat } />
|
<BackButton onPress = { this.props._onToggleChat } />
|
||||||
<HeaderLabel labelKey = 'chat.title' />
|
<HeaderLabel labelKey = 'chat.title' />
|
||||||
</Header>
|
</Header>
|
||||||
<SafeAreaView style = { styles.backdrop }>
|
<SafeAreaView style = { styles.backdrop }>
|
||||||
<GiftedChat
|
<MessageContainer messages = { this.props._messages } />
|
||||||
messages = { messages }
|
<ChatInputBar onSend = { this.props._onSendMessage } />
|
||||||
onSend = { this._onSend }
|
|
||||||
renderMessage = { this._renderMessage } />
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</View>
|
</KeyboardAvoidingView>
|
||||||
</SlidingView>
|
</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));
|
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() {
|
render() {
|
||||||
const { message } = this.props;
|
const { message } = this.props;
|
||||||
const timeStamp = getLocalizedDateFormatter(
|
const timeStamp = getLocalizedDateFormatter(
|
||||||
message.createdAt).format(TIMESTAMP_FORMAT);
|
new Date(message.timestamp)).format(TIMESTAMP_FORMAT);
|
||||||
const localMessage = message.messageType === 'local';
|
const localMessage = message.messageType === 'local';
|
||||||
|
|
||||||
// Style arrays that need to be updated in various scenarios, such as
|
// 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.
|
// The bubble needs to be differently styled.
|
||||||
textWrapperStyle.push(styles.ownTextWrapper);
|
textWrapperStyle.push(styles.ownTextWrapper);
|
||||||
} else if (message.system) {
|
} else if (message.messageType === 'error') {
|
||||||
// The bubble needs to be differently styled.
|
// The bubble needs to be differently styled.
|
||||||
textWrapperStyle.push(styles.systemTextWrapper);
|
textWrapperStyle.push(styles.systemTextWrapper);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,12 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||||
!localMessage && this._renderDisplayName()
|
!localMessage && this._renderDisplayName()
|
||||||
}
|
}
|
||||||
<Text style = { styles.messageText }>
|
<Text style = { styles.messageText }>
|
||||||
{ message.text }
|
{ message.messageType === 'error'
|
||||||
|
? this.props.t('chat.error', {
|
||||||
|
error: message.error,
|
||||||
|
originalText: message.message
|
||||||
|
})
|
||||||
|
: message.message }
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style = { styles.timeText }>
|
<Text style = { styles.timeText }>
|
||||||
|
@ -112,7 +117,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text style = { styles.displayName }>
|
<Text style = { styles.displayName }>
|
||||||
{ message.user.name }
|
{ message.displayName }
|
||||||
</Text>
|
</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
|
// @flow
|
||||||
|
|
||||||
import { ColorPalette } from '../../../base/styles';
|
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The styles of the feature chat.
|
* The styles of the feature chat.
|
||||||
|
@ -28,6 +28,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
chatContainer: {
|
chatContainer: {
|
||||||
|
alignItems: 'stretch',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
},
|
},
|
||||||
|
@ -49,6 +50,29 @@ export default {
|
||||||
fontSize: 13
|
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.
|
* The message text itself.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,6 +14,11 @@ export function getUnreadCount(state: Object) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (navigator.product === 'ReactNative') {
|
||||||
|
// React native stores the messages in a reversed order.
|
||||||
|
return messages.indexOf(lastReadMessage);
|
||||||
|
}
|
||||||
|
|
||||||
const lastReadIndex = messages.lastIndexOf(lastReadMessage);
|
const lastReadIndex = messages.lastIndexOf(lastReadMessage);
|
||||||
|
|
||||||
return messagesCount - (lastReadIndex + 1);
|
return messagesCount - (lastReadIndex + 1);
|
||||||
|
|
|
@ -22,14 +22,22 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||||
timestamp: action.timestamp
|
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 {
|
return {
|
||||||
...state,
|
...state,
|
||||||
lastReadMessage:
|
lastReadMessage:
|
||||||
action.hasRead ? newMessage : state.lastReadMessage,
|
action.hasRead ? newMessage : state.lastReadMessage,
|
||||||
messages: [
|
messages
|
||||||
...state.messages,
|
|
||||||
newMessage
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +52,8 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isOpen: !state.isOpen,
|
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