feat: add chat color scheming

This commit is contained in:
Bettenbuk Zoltan 2019-10-15 16:08:23 +02:00 committed by Zoltan Bettenbuk
parent 8be02f9ca1
commit a35099f949
15 changed files with 302 additions and 142 deletions

View File

@ -153,6 +153,8 @@ class ColorSchemeRegistry {
const colorScheme = toState(stateful)['features/base/color-scheme']; const colorScheme = toState(stateful)['features/base/color-scheme'];
return { return {
...defaultScheme._defaultTheme,
...colorScheme._defaultTheme,
...defaultScheme[componentName], ...defaultScheme[componentName],
...colorScheme[componentName] ...colorScheme[componentName]
}[colorDefinition]; }[colorDefinition];

View File

@ -6,18 +6,26 @@ import { ColorPalette, getRGBAFormat } from '../styles';
* The default color scheme of the application. * The default color scheme of the application.
*/ */
export default { export default {
'BottomSheet': { '_defaultTheme': {
// Generic app theme colors that are used accross the entire app.
// All scheme definitions below inherit these values.
background: 'rgb(255, 255, 255)', background: 'rgb(255, 255, 255)',
icon: '#1c2025', icon: 'rgb(28, 32, 37)',
label: '#1c2025' text: 'rgb(28, 32, 37)'
},
'Chat': {
displayName: 'rgb(94, 109, 121)',
localMsgBackground: 'rgb(215, 230, 249)',
privateMsgBackground: 'rgb(250, 219, 219)',
privateMsgNotice: 'rgb(186, 39, 58)',
remoteMsgBackground: 'rgb(241, 242, 246)',
replyBorder: 'rgb(219, 197, 200)',
replyIcon: 'rgb(94, 109, 121)'
}, },
'Dialog': { 'Dialog': {
background: 'rgb(255, 255, 255)',
border: 'rgba(0, 3, 6, 0.6)', border: 'rgba(0, 3, 6, 0.6)',
buttonBackground: ColorPalette.blue, buttonBackground: ColorPalette.blue,
buttonLabel: ColorPalette.white, buttonLabel: ColorPalette.white
icon: '#1c2025',
text: '#1c2025'
}, },
'Header': { 'Header': {
background: ColorPalette.blue, background: ColorPalette.blue,
@ -30,8 +38,7 @@ export default {
background: 'rgb(42, 58, 75)' background: 'rgb(42, 58, 75)'
}, },
'LoadConfigOverlay': { 'LoadConfigOverlay': {
background: 'rgb(249, 249, 249)', background: 'rgb(249, 249, 249)'
text: 'rgb(28, 32, 37)'
}, },
'Thumbnail': { 'Thumbnail': {
activeParticipantHighlight: 'rgb(81, 214, 170)', activeParticipantHighlight: 'rgb(81, 214, 170)',

View File

@ -147,7 +147,7 @@ ColorSchemeRegistry.register('BottomSheet', {
* Style for the label in a generic item rendered in the menu. * Style for the label in a generic item rendered in the menu.
*/ */
labelStyle: { labelStyle: {
color: schemeColor('label'), color: schemeColor('text'),
flexShrink: 1, flexShrink: 1,
fontSize: MD_FONT_SIZE, fontSize: MD_FONT_SIZE,
marginLeft: 32, marginLeft: 32,

View File

@ -4,6 +4,8 @@ import { PureComponent } from 'react';
import { getLocalizedDateFormatter } from '../../base/i18n'; import { getLocalizedDateFormatter } from '../../base/i18n';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants';
/** /**
* Formatter string to display the message timestamp. * Formatter string to display the message timestamp.
*/ */
@ -65,7 +67,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
_getMessageText() { _getMessageText() {
const { message } = this.props; const { message } = this.props;
return message.messageType === 'error' return message.messageType === MESSAGE_TYPE_ERROR
? this.props.t('chat.error', { ? this.props.t('chat.error', {
error: message.message error: message.message
}) })
@ -81,7 +83,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
const { message, t } = this.props; const { message, t } = this.props;
return t('chat.privateNotice', { return t('chat.privateNotice', {
recipient: message.messageType === 'local' ? message.recipient : t('chat.you') recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : t('chat.you')
}); });
} }
} }

View File

@ -6,7 +6,7 @@ import { getParticipantDisplayName } from '../../base/participants';
import { setPrivateMessageRecipient } from '../actions'; import { setPrivateMessageRecipient } from '../actions';
type Props = { export type Props = {
/** /**
* Function used to translate i18n labels. * Function used to translate i18n labels.
@ -27,7 +27,7 @@ type Props = {
/** /**
* Abstract class for the {@code MessageRecipient} component. * Abstract class for the {@code MessageRecipient} component.
*/ */
export default class AbstractMessageRecipient extends PureComponent<Props> { export default class AbstractMessageRecipient<P: Props> extends PureComponent<P> {
} }

View File

@ -3,15 +3,16 @@
import React from 'react'; import React from 'react';
import { KeyboardAvoidingView, SafeAreaView } from 'react-native'; import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { HeaderWithNavigation, SlidingView } from '../../../base/react'; import { HeaderWithNavigation, SlidingView } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import AbstractChat, { import AbstractChat, {
_mapDispatchToProps, _mapDispatchToProps,
_mapStateToProps, _mapStateToProps as _abstractMapStateToProps,
type Props type Props as AbstractProps
} from '../AbstractChat'; } from '../AbstractChat';
import ChatInputBar from './ChatInputBar'; import ChatInputBar from './ChatInputBar';
@ -19,6 +20,14 @@ import MessageContainer from './MessageContainer';
import MessageRecipient from './MessageRecipient'; import MessageRecipient from './MessageRecipient';
import styles from './styles'; import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType
};
/** /**
* Implements a React native component that renders the chat window (modal) of * Implements a React native component that renders the chat window (modal) of
* the mobile client. * the mobile client.
@ -41,6 +50,8 @@ class Chat extends AbstractChat<Props> {
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { _styles } = this.props;
return ( return (
<SlidingView <SlidingView
onHide = { this._onClose } onHide = { this._onClose }
@ -52,7 +63,7 @@ class Chat extends AbstractChat<Props> {
<HeaderWithNavigation <HeaderWithNavigation
headerLabelKey = 'chat.title' headerLabelKey = 'chat.title'
onPressBack = { this._onClose } /> onPressBack = { this._onClose } />
<SafeAreaView style = { styles.backdrop }> <SafeAreaView style = { _styles.backdrop }>
<MessageContainer messages = { this.props._messages } /> <MessageContainer messages = { this.props._messages } />
<MessageRecipient /> <MessageRecipient />
<ChatInputBar onSend = { this.props._onSendMessage } /> <ChatInputBar onSend = { this.props._onSendMessage } />
@ -80,4 +91,17 @@ class Chat extends AbstractChat<Props> {
} }
} }
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat)); export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));

View File

@ -4,16 +4,28 @@ import React from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { Avatar } from '../../../base/avatar'; import { Avatar } from '../../../base/avatar';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Linkify } from '../../../base/react'; import { Linkify } from '../../../base/react';
import { connect } from '../../../base/redux';
import { type StyleType } from '../../../base/styles';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import { replaceNonUnicodeEmojis } from '../../functions'; import { replaceNonUnicodeEmojis } from '../../functions';
import AbstractChatMessage, { type Props } from '../AbstractChatMessage'; import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
import PrivateMessageButton from '../PrivateMessageButton'; import PrivateMessageButton from '../PrivateMessageButton';
import styles from './styles'; import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType
};
/** /**
* Renders a single chat message. * Renders a single chat message.
*/ */
@ -24,55 +36,58 @@ class ChatMessage extends AbstractChatMessage<Props> {
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { message } = this.props; const { _styles, message } = this.props;
const localMessage = message.messageType === 'local'; const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
const { privateMessage } = message;
// Style arrays that need to be updated in various scenarios, such as // Style arrays that need to be updated in various scenarios, such as
// error messages or others. // error messages or others.
const detailsWrapperStyle = [ const detailsWrapperStyle = [
styles.detailsWrapper styles.detailsWrapper
]; ];
const textWrapperStyle = [ const messageBubbleStyle = [
styles.textWrapper styles.messageBubble
]; ];
if (localMessage) { if (localMessage) {
// This is a message sent by the local participant.
// The wrapper needs to be aligned to the right. // The wrapper needs to be aligned to the right.
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper); detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
// The bubble needs to be differently styled. // The bubble needs some additional styling
textWrapperStyle.push(styles.ownTextWrapper); messageBubbleStyle.push(_styles.localMessageBubble);
} else if (message.messageType === 'error') { } else if (message.messageType === MESSAGE_TYPE_ERROR) {
// The bubble needs to be differently styled. // This is a system message.
textWrapperStyle.push(styles.systemTextWrapper);
// The bubble needs some additional styling
messageBubbleStyle.push(styles.systemMessageBubble);
} else {
// This is a remote message sent by a remote participant.
// The bubble needs some additional styling
messageBubbleStyle.push(_styles.remoteMessageBubble);
}
if (privateMessage) {
messageBubbleStyle.push(_styles.privateMessageBubble);
} }
return ( return (
<View style = { styles.messageWrapper } > <View style = { styles.messageWrapper } >
{ this._renderAvatar() } { this._renderAvatar() }
<View style = { detailsWrapperStyle }> <View style = { detailsWrapperStyle }>
<View style = { styles.replyWrapper }> <View style = { messageBubbleStyle }>
<View style = { textWrapperStyle } > <View style = { styles.textWrapper } >
{ { this._renderDisplayName() }
this.props.showDisplayName
&& this._renderDisplayName()
}
<Linkify linkStyle = { styles.chatLink }> <Linkify linkStyle = { styles.chatLink }>
{ replaceNonUnicodeEmojis(this._getMessageText()) } { replaceNonUnicodeEmojis(this._getMessageText()) }
</Linkify> </Linkify>
{ { this._renderPrivateNotice() }
message.privateMessage
&& this._renderPrivateNotice()
}
</View> </View>
{ message.privateMessage && !localMessage { this._renderPrivateReplyButton() }
&& <PrivateMessageButton
participantID = { message.id }
reply = { true }
showLabel = { false }
toggledStyles = { styles.replyStyles } /> }
</View> </View>
{ this.props.showTimestamp && this._renderTimestamp() } { this._renderTimestamp() }
</View> </View>
</View> </View>
); );
@ -104,37 +119,77 @@ class ChatMessage extends AbstractChatMessage<Props> {
} }
/** /**
* Renders the display name of the sender. * Renders the display name of the sender if necessary.
* *
* @returns {React$Element<*>} * @returns {React$Element<*> | null}
*/ */
_renderDisplayName() { _renderDisplayName() {
const { _styles, message, showDisplayName } = this.props;
if (!showDisplayName) {
return null;
}
return ( return (
<Text style = { styles.displayName }> <Text style = { _styles.displayName }>
{ this.props.message.displayName } { message.displayName }
</Text> </Text>
); );
} }
/** /**
* Renders the message privacy notice. * Renders the message privacy notice, if necessary.
* *
* @returns {React$Element<*>} * @returns {React$Element<*> | null}
*/ */
_renderPrivateNotice() { _renderPrivateNotice() {
const { _styles, message } = this.props;
if (!message.privateMessage) {
return null;
}
return ( return (
<Text style = { styles.privateNotice }> <Text style = { _styles.privateNotice }>
{ this._getPrivateNoticeMessage() } { this._getPrivateNoticeMessage() }
</Text> </Text>
); );
} }
/** /**
* Renders the time at which the message was sent. * Renders the private reply button, if necessary.
* *
* @returns {React$Element<*>} * @returns {React$Element<*> | null}
*/
_renderPrivateReplyButton() {
const { _styles, message } = this.props;
const { messageType, privateMessage } = message;
if (!privateMessage || messageType === MESSAGE_TYPE_LOCAL) {
return null;
}
return (
<View style = { _styles.replyContainer }>
<PrivateMessageButton
participantID = { message.id }
reply = { true }
showLabel = { false }
toggledStyles = { _styles.replyStyles } />
</View>
);
}
/**
* Renders the time at which the message was sent, if necessary.
*
* @returns {React$Element<*> | null}
*/ */
_renderTimestamp() { _renderTimestamp() {
if (!this.props.showTimestamp) {
return null;
}
return ( return (
<Text style = { styles.timeText }> <Text style = { styles.timeText }>
{ this._getFormattedTimestamp() } { this._getFormattedTimestamp() }
@ -143,4 +198,16 @@ class ChatMessage extends AbstractChatMessage<Props> {
} }
} }
export default translate(ChatMessage); /**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps)(ChatMessage));

View File

@ -3,6 +3,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
import ChatMessage from './ChatMessage'; import ChatMessage from './ChatMessage';
import styles from './styles'; import styles from './styles';
@ -73,11 +75,11 @@ export default class ChatMessageGroup extends Component<Props> {
<ChatMessage <ChatMessage
message = { message } message = { message }
showAvatar = { showAvatar = {
this.props.messages[0].messageType !== 'local' this.props.messages[0].messageType !== MESSAGE_TYPE_LOCAL
&& index === this.props.messages.length - 1 && index === this.props.messages.length - 1
} }
showDisplayName = { showDisplayName = {
this.props.messages[0].messageType === 'remote' this.props.messages[0].messageType === MESSAGE_TYPE_REMOTE
&& index === this.props.messages.length - 1 && index === this.props.messages.length - 1
} }
showTimestamp = { index === 0 } /> showTimestamp = { index === 0 } />

View File

@ -3,28 +3,37 @@
import React from 'react'; import React from 'react';
import { Text, TouchableHighlight, View } from 'react-native'; import { Text, TouchableHighlight, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Icon, IconCancelSelection } from '../../../base/icons'; import { Icon, IconCancelSelection } from '../../../base/icons';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { type StyleType } from '../../../base/styles';
import AbstractMessageRecipient, { import AbstractMessageRecipient, {
_mapDispatchToProps, _mapDispatchToProps,
_mapStateToProps _mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from '../AbstractMessageRecipient'; } from '../AbstractMessageRecipient';
import styles from './styles'; type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType
};
/** /**
* Class to implement the displaying of the recipient of the next message. * Class to implement the displaying of the recipient of the next message.
*/ */
class MessageRecipient extends AbstractMessageRecipient { class MessageRecipient extends AbstractMessageRecipient<Props> {
/** /**
* Implements {@code PureComponent#render}. * Implements {@code PureComponent#render}.
* *
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { _privateMessageRecipient } = this.props; const { _privateMessageRecipient, _styles } = this.props;
if (!_privateMessageRecipient) { if (!_privateMessageRecipient) {
return null; return null;
@ -33,8 +42,8 @@ class MessageRecipient extends AbstractMessageRecipient {
const { t } = this.props; const { t } = this.props;
return ( return (
<View style = { styles.messageRecipientContainer }> <View style = { _styles.messageRecipientContainer }>
<Text style = { styles.messageRecipientText }> <Text style = { _styles.messageRecipientText }>
{ t('chat.messageTo', { { t('chat.messageTo', {
recipient: _privateMessageRecipient recipient: _privateMessageRecipient
}) } }) }
@ -42,11 +51,24 @@ class MessageRecipient extends AbstractMessageRecipient {
<TouchableHighlight onPress = { this.props._onRemovePrivateMessageRecipient }> <TouchableHighlight onPress = { this.props._onRemovePrivateMessageRecipient }>
<Icon <Icon
src = { IconCancelSelection } src = { IconCancelSelection }
style = { styles.messageRecipientCancelIcon } /> style = { _styles.messageRecipientCancelIcon } />
</TouchableHighlight> </TouchableHighlight>
</View> </View>
); );
} }
} }
/**
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(MessageRecipient)); export default translate(connect(_mapStateToProps, _mapDispatchToProps)(MessageRecipient));

View File

@ -1,7 +1,10 @@
// @flow // @flow
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette } from '../../../base/styles'; import { BoxModel, ColorPalette } from '../../../base/styles';
const BUBBLE_RADIUS = 8;
/** /**
* The styles of the feature chat. * The styles of the feature chat.
* *
@ -20,14 +23,6 @@ export default {
width: 32 width: 32
}, },
/**
* Background of the chat screen.
*/
backdrop: {
backgroundColor: ColorPalette.white,
flex: 1
},
chatContainer: { chatContainer: {
alignItems: 'stretch', alignItems: 'stretch',
flex: 1, flex: 1,
@ -47,14 +42,6 @@ export default {
flexDirection: 'column' flexDirection: 'column'
}, },
/**
* The text node for the display name.
*/
displayName: {
color: 'rgb(118, 136, 152)',
fontSize: 13
},
/** /**
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar). * A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
*/ */
@ -76,35 +63,16 @@ export default {
height: 48 height: 48
}, },
messageBubble: {
alignItems: 'center',
borderRadius: BUBBLE_RADIUS,
flexDirection: 'row'
},
messageContainer: { messageContainer: {
flex: 1 flex: 1
}, },
messageRecipientCancelIcon: {
color: ColorPalette.white,
fontSize: 18
},
messageRecipientContainer: {
alignItems: 'center',
backgroundColor: ColorPalette.warning,
flexDirection: 'row',
padding: BoxModel.padding
},
messageRecipientText: {
color: ColorPalette.white,
flex: 1
},
/**
* The message text itself.
*/
messageText: {
color: 'rgb(28, 32, 37)',
fontSize: 15
},
/** /**
* Wrapper View for the entire block. * Wrapper View for the entire block.
*/ */
@ -123,34 +91,11 @@ export default {
alignItems: 'flex-end' alignItems: 'flex-end'
}, },
/**
* Style modifier for the {@code textWrapper} for own messages.
*/
ownTextWrapper: {
backgroundColor: 'rgb(210, 231, 249)',
borderTopLeftRadius: 8,
borderTopRightRadius: 0
},
replyWrapper: { replyWrapper: {
alignItems: 'center', alignItems: 'center',
flexDirection: 'row' flexDirection: 'row'
}, },
replyStyles: {
iconStyle: {
color: 'rgb(118, 136, 152)',
fontSize: 22,
margin: BoxModel.margin / 2
}
},
privateNotice: {
color: ColorPalette.warning,
fontSize: 13,
fontStyle: 'italic'
},
sendButtonIcon: { sendButtonIcon: {
color: ColorPalette.darkGrey, color: ColorPalette.darkGrey,
fontSize: 22 fontSize: 22
@ -159,7 +104,7 @@ export default {
/** /**
* Style modifier for system (error) messages. * Style modifier for system (error) messages.
*/ */
systemTextWrapper: { systemMessageBubble: {
backgroundColor: 'rgb(247, 215, 215)' backgroundColor: 'rgb(247, 215, 215)'
}, },
@ -168,9 +113,6 @@ export default {
*/ */
textWrapper: { textWrapper: {
alignItems: 'flex-start', alignItems: 'flex-start',
backgroundColor: 'rgb(240, 243, 247)',
borderRadius: 8,
borderTopLeftRadius: 0,
flexDirection: 'column', flexDirection: 'column',
padding: 9 padding: 9
}, },
@ -183,3 +125,73 @@ export default {
fontSize: 13 fontSize: 13
} }
}; };
ColorSchemeRegistry.register('Chat', {
/**
* Background of the chat screen.
*/
backdrop: {
backgroundColor: schemeColor('background'),
flex: 1
},
/**
* The text node for the display name.
*/
displayName: {
color: schemeColor('displayName'),
fontSize: 13
},
localMessageBubble: {
backgroundColor: schemeColor('localMsgBackground'),
borderTopRightRadius: 0
},
messageRecipientCancelIcon: {
color: schemeColor('icon'),
fontSize: 18
},
messageRecipientContainer: {
alignItems: 'center',
backgroundColor: schemeColor('privateMsgBackground'),
flexDirection: 'row',
padding: BoxModel.padding
},
messageRecipientText: {
color: schemeColor('text'),
flex: 1
},
privateNotice: {
color: schemeColor('privateMsgNotice'),
fontSize: 11,
marginTop: 6
},
privateMessageBubble: {
backgroundColor: schemeColor('privateMsgBackground')
},
remoteMessageBubble: {
backgroundColor: schemeColor('remoteMsgBackground'),
borderTopLeftRadius: 0
},
replyContainer: {
alignSelf: 'stretch',
borderLeftColor: schemeColor('replyBorder'),
borderLeftWidth: 1,
justifyContent: 'center'
},
replyStyles: {
iconStyle: {
color: schemeColor('replyIcon'),
fontSize: 22,
padding: 8
}
}
});

View File

@ -7,6 +7,8 @@ import { toArray } from 'react-emoji-render';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Linkify } from '../../../base/react'; import { Linkify } from '../../../base/react';
import { MESSAGE_TYPE_LOCAL } from '../../constants';
import AbstractChatMessage, { import AbstractChatMessage, {
type Props type Props
} from '../AbstractChatMessage'; } from '../AbstractChatMessage';
@ -47,7 +49,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
</div> </div>
{ message.privateMessage && this._renderPrivateNotice() } { message.privateMessage && this._renderPrivateNotice() }
</div> </div>
{ message.privateMessage && message.messageType !== 'local' { message.privateMessage && message.messageType !== MESSAGE_TYPE_LOCAL
&& <PrivateMessageButton && <PrivateMessageButton
participantID = { message.id } participantID = { message.id }
reply = { true } reply = { true }

View File

@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import { MESSAGE_TYPE_REMOTE } from '../../constants';
import AbstractMessageContainer, { type Props } import AbstractMessageContainer, { type Props }
from '../AbstractMessageContainer'; from '../AbstractMessageContainer';
@ -61,7 +63,7 @@ export default class MessageContainer extends AbstractMessageContainer {
return ( return (
<ChatMessageGroup <ChatMessageGroup
className = { messageType || 'remote' } className = { messageType || MESSAGE_TYPE_REMOTE }
key = { index } key = { index }
messages = { group } /> messages = { group } />
); );

View File

@ -8,13 +8,14 @@ import { connect } from '../../../base/redux';
import AbstractMessageRecipient, { import AbstractMessageRecipient, {
_mapDispatchToProps, _mapDispatchToProps,
_mapStateToProps _mapStateToProps,
type Props
} from '../AbstractMessageRecipient'; } from '../AbstractMessageRecipient';
/** /**
* Class to implement the displaying of the recipient of the next message. * Class to implement the displaying of the recipient of the next message.
*/ */
class MessageRecipient extends AbstractMessageRecipient { class MessageRecipient extends AbstractMessageRecipient<Props> {
/** /**
* Implements {@code PureComponent#render}. * Implements {@code PureComponent#render}.
* *

View File

@ -1,3 +1,5 @@
// @flow
/** /**
* The audio ID of the audio element for which the {@link playAudio} action is * The audio ID of the audio element for which the {@link playAudio} action is
* triggered when new chat message is received. * triggered when new chat message is received.
@ -5,3 +7,18 @@
* @type {string} * @type {string}
*/ */
export const INCOMING_MSG_SOUND_ID = 'INCOMING_MSG_SOUND'; export const INCOMING_MSG_SOUND_ID = 'INCOMING_MSG_SOUND';
/**
* The {@code messageType} of error (system) messages.
*/
export const MESSAGE_TYPE_ERROR = 'error';
/**
* The {@code messageType} of local messages.
*/
export const MESSAGE_TYPE_LOCAL = 'local';
/**
* The {@code messageType} of remote messages.
*/
export const MESSAGE_TYPE_REMOTE = 'remote';

View File

@ -22,7 +22,7 @@ import { isButtonEnabled, showToolbox } from '../toolbox';
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes'; import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
import { addMessage, clearMessages, toggleChat } from './actions'; import { addMessage, clearMessages, toggleChat } from './actions';
import { ChatPrivacyDialog } from './components'; import { ChatPrivacyDialog } from './components';
import { INCOMING_MSG_SOUND_ID } from './constants'; import { INCOMING_MSG_SOUND_ID, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from './constants';
import { INCOMING_MSG_SOUND_FILE } from './sounds'; import { INCOMING_MSG_SOUND_FILE } from './sounds';
declare var APP: Object; declare var APP: Object;
@ -194,7 +194,7 @@ function _addChatMsgListener(conference, store) {
function _handleChatError({ dispatch }, error) { function _handleChatError({ dispatch }, error) {
dispatch(addMessage({ dispatch(addMessage({
hasRead: true, hasRead: true,
messageType: 'error', messageType: MESSAGE_TYPE_ERROR,
message: error, message: error,
privateMessage: false, privateMessage: false,
timestamp: Date.now() timestamp: Date.now()
@ -231,7 +231,7 @@ function _handleReceivedMessage({ dispatch, getState }, { id, message, nick, pri
displayName, displayName,
hasRead, hasRead,
id, id,
messageType: participant.local ? 'local' : 'remote', messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
message, message,
privateMessage, privateMessage,
recipient: getParticipantDisplayName(state, localParticipant.id), recipient: getParticipantDisplayName(state, localParticipant.id),
@ -285,7 +285,7 @@ function _persistSentPrivateMessage({ dispatch, getState }, recipientID, message
displayName, displayName,
hasRead: true, hasRead: true,
id: localParticipant.id, id: localParticipant.id,
messageType: 'local', messageType: MESSAGE_TYPE_LOCAL,
message, message,
privateMessage: true, privateMessage: true,
recipient: getParticipantDisplayName(getState, recipientID), recipient: getParticipantDisplayName(getState, recipientID),
@ -323,7 +323,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
const lastMessage = navigator.product === 'ReactNative' const lastMessage = navigator.product === 'ReactNative'
? messages[0] : messages[messages.length - 1]; ? messages[0] : messages[messages.length - 1];
if (lastMessage.messageType === 'local') { if (lastMessage.messageType === MESSAGE_TYPE_LOCAL) {
// The sender is probably aware of any private messages as already sent // The sender is probably aware of any private messages as already sent
// a message since then. Doesn't make sense to display the notice now. // a message since then. Doesn't make sense to display the notice now.
return undefined; return undefined;
@ -339,7 +339,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
const now = Date.now(); const now = Date.now();
const recentPrivateMessages = messages.filter( const recentPrivateMessages = messages.filter(
message => message =>
message.messageType !== 'local' message.messageType !== MESSAGE_TYPE_LOCAL
&& message.privateMessage && message.privateMessage
&& message.timestamp + PRIVACY_NOTICE_TIMEOUT > now); && message.timestamp + PRIVACY_NOTICE_TIMEOUT > now);
const recentPrivateMessage = navigator.product === 'ReactNative' const recentPrivateMessage = navigator.product === 'ReactNative'