diff --git a/react/features/base/color-scheme/ColorSchemeRegistry.js b/react/features/base/color-scheme/ColorSchemeRegistry.js index 69bcb5bae..71736eb84 100644 --- a/react/features/base/color-scheme/ColorSchemeRegistry.js +++ b/react/features/base/color-scheme/ColorSchemeRegistry.js @@ -153,6 +153,8 @@ class ColorSchemeRegistry { const colorScheme = toState(stateful)['features/base/color-scheme']; return { + ...defaultScheme._defaultTheme, + ...colorScheme._defaultTheme, ...defaultScheme[componentName], ...colorScheme[componentName] }[colorDefinition]; diff --git a/react/features/base/color-scheme/defaultScheme.js b/react/features/base/color-scheme/defaultScheme.js index 98c8fa7ad..0d7cdd0ed 100644 --- a/react/features/base/color-scheme/defaultScheme.js +++ b/react/features/base/color-scheme/defaultScheme.js @@ -6,18 +6,26 @@ import { ColorPalette, getRGBAFormat } from '../styles'; * The default color scheme of the application. */ 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)', - icon: '#1c2025', - label: '#1c2025' + icon: 'rgb(28, 32, 37)', + 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': { - background: 'rgb(255, 255, 255)', border: 'rgba(0, 3, 6, 0.6)', buttonBackground: ColorPalette.blue, - buttonLabel: ColorPalette.white, - icon: '#1c2025', - text: '#1c2025' + buttonLabel: ColorPalette.white }, 'Header': { background: ColorPalette.blue, @@ -30,8 +38,7 @@ export default { background: 'rgb(42, 58, 75)' }, 'LoadConfigOverlay': { - background: 'rgb(249, 249, 249)', - text: 'rgb(28, 32, 37)' + background: 'rgb(249, 249, 249)' }, 'Thumbnail': { activeParticipantHighlight: 'rgb(81, 214, 170)', diff --git a/react/features/base/dialog/components/native/styles.js b/react/features/base/dialog/components/native/styles.js index 5747e1906..66804d943 100644 --- a/react/features/base/dialog/components/native/styles.js +++ b/react/features/base/dialog/components/native/styles.js @@ -147,7 +147,7 @@ ColorSchemeRegistry.register('BottomSheet', { * Style for the label in a generic item rendered in the menu. */ labelStyle: { - color: schemeColor('label'), + color: schemeColor('text'), flexShrink: 1, fontSize: MD_FONT_SIZE, marginLeft: 32, diff --git a/react/features/chat/components/AbstractChatMessage.js b/react/features/chat/components/AbstractChatMessage.js index 74324ceff..10e6e145e 100644 --- a/react/features/chat/components/AbstractChatMessage.js +++ b/react/features/chat/components/AbstractChatMessage.js @@ -4,6 +4,8 @@ import { PureComponent } from 'react'; import { getLocalizedDateFormatter } from '../../base/i18n'; +import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants'; + /** * Formatter string to display the message timestamp. */ @@ -65,7 +67,7 @@ export default class AbstractChatMessage extends PureComponent

{ _getMessageText() { const { message } = this.props; - return message.messageType === 'error' + return message.messageType === MESSAGE_TYPE_ERROR ? this.props.t('chat.error', { error: message.message }) @@ -81,7 +83,7 @@ export default class AbstractChatMessage extends PureComponent

{ const { message, t } = this.props; return t('chat.privateNotice', { - recipient: message.messageType === 'local' ? message.recipient : t('chat.you') + recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : t('chat.you') }); } } diff --git a/react/features/chat/components/AbstractMessageRecipient.js b/react/features/chat/components/AbstractMessageRecipient.js index baf37c534..25ebd14c4 100644 --- a/react/features/chat/components/AbstractMessageRecipient.js +++ b/react/features/chat/components/AbstractMessageRecipient.js @@ -6,7 +6,7 @@ import { getParticipantDisplayName } from '../../base/participants'; import { setPrivateMessageRecipient } from '../actions'; -type Props = { +export type Props = { /** * Function used to translate i18n labels. @@ -27,7 +27,7 @@ type Props = { /** * Abstract class for the {@code MessageRecipient} component. */ -export default class AbstractMessageRecipient extends PureComponent { +export default class AbstractMessageRecipient extends PureComponent

{ } diff --git a/react/features/chat/components/native/Chat.js b/react/features/chat/components/native/Chat.js index 73909cf09..bc73c8615 100644 --- a/react/features/chat/components/native/Chat.js +++ b/react/features/chat/components/native/Chat.js @@ -3,15 +3,16 @@ import React from 'react'; import { KeyboardAvoidingView, SafeAreaView } from 'react-native'; +import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { translate } from '../../../base/i18n'; - import { HeaderWithNavigation, SlidingView } from '../../../base/react'; import { connect } from '../../../base/redux'; +import { StyleType } from '../../../base/styles'; import AbstractChat, { _mapDispatchToProps, - _mapStateToProps, - type Props + _mapStateToProps as _abstractMapStateToProps, + type Props as AbstractProps } from '../AbstractChat'; import ChatInputBar from './ChatInputBar'; @@ -19,6 +20,14 @@ import MessageContainer from './MessageContainer'; import MessageRecipient from './MessageRecipient'; 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 * the mobile client. @@ -41,6 +50,8 @@ class Chat extends AbstractChat { * @inheritdoc */ render() { + const { _styles } = this.props; + return ( { - + @@ -80,4 +91,17 @@ class Chat extends AbstractChat { } } +/** + * 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)); diff --git a/react/features/chat/components/native/ChatMessage.js b/react/features/chat/components/native/ChatMessage.js index 5409bac01..14956ea57 100644 --- a/react/features/chat/components/native/ChatMessage.js +++ b/react/features/chat/components/native/ChatMessage.js @@ -4,16 +4,28 @@ import React from 'react'; import { Text, View } from 'react-native'; import { Avatar } from '../../../base/avatar'; +import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { translate } from '../../../base/i18n'; 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 AbstractChatMessage, { type Props } from '../AbstractChatMessage'; +import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage'; import PrivateMessageButton from '../PrivateMessageButton'; import styles from './styles'; +type Props = AbstractProps & { + + /** + * The color-schemed stylesheet of the feature. + */ + _styles: StyleType +}; + /** * Renders a single chat message. */ @@ -24,55 +36,58 @@ class ChatMessage extends AbstractChatMessage { * @inheritdoc */ render() { - const { message } = this.props; - const localMessage = message.messageType === 'local'; + const { _styles, message } = this.props; + const localMessage = message.messageType === MESSAGE_TYPE_LOCAL; + const { privateMessage } = message; // Style arrays that need to be updated in various scenarios, such as // error messages or others. const detailsWrapperStyle = [ styles.detailsWrapper ]; - const textWrapperStyle = [ - styles.textWrapper + const messageBubbleStyle = [ + styles.messageBubble ]; if (localMessage) { + // This is a message sent by the local participant. + // The wrapper needs to be aligned to the right. detailsWrapperStyle.push(styles.ownMessageDetailsWrapper); - // The bubble needs to be differently styled. - textWrapperStyle.push(styles.ownTextWrapper); - } else if (message.messageType === 'error') { - // The bubble needs to be differently styled. - textWrapperStyle.push(styles.systemTextWrapper); + // The bubble needs some additional styling + messageBubbleStyle.push(_styles.localMessageBubble); + } else if (message.messageType === MESSAGE_TYPE_ERROR) { + // This is a system message. + + // 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 ( { this._renderAvatar() } - - - { - this.props.showDisplayName - && this._renderDisplayName() - } + + + { this._renderDisplayName() } { replaceNonUnicodeEmojis(this._getMessageText()) } - { - message.privateMessage - && this._renderPrivateNotice() - } + { this._renderPrivateNotice() } - { message.privateMessage && !localMessage - && } + { this._renderPrivateReplyButton() } - { this.props.showTimestamp && this._renderTimestamp() } + { this._renderTimestamp() } ); @@ -104,37 +119,77 @@ class ChatMessage extends AbstractChatMessage { } /** - * Renders the display name of the sender. + * Renders the display name of the sender if necessary. * - * @returns {React$Element<*>} + * @returns {React$Element<*> | null} */ _renderDisplayName() { + const { _styles, message, showDisplayName } = this.props; + + if (!showDisplayName) { + return null; + } + return ( - - { this.props.message.displayName } + + { message.displayName } ); } /** - * Renders the message privacy notice. + * Renders the message privacy notice, if necessary. * - * @returns {React$Element<*>} + * @returns {React$Element<*> | null} */ _renderPrivateNotice() { + const { _styles, message } = this.props; + + if (!message.privateMessage) { + return null; + } + return ( - + { this._getPrivateNoticeMessage() } ); } /** - * 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 ( + + + + ); + } + + /** + * Renders the time at which the message was sent, if necessary. + * + * @returns {React$Element<*> | null} */ _renderTimestamp() { + if (!this.props.showTimestamp) { + return null; + } + return ( { this._getFormattedTimestamp() } @@ -143,4 +198,16 @@ class ChatMessage extends AbstractChatMessage { } } -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)); diff --git a/react/features/chat/components/native/ChatMessageGroup.js b/react/features/chat/components/native/ChatMessageGroup.js index 36d10534f..9edd5a004 100644 --- a/react/features/chat/components/native/ChatMessageGroup.js +++ b/react/features/chat/components/native/ChatMessageGroup.js @@ -3,6 +3,8 @@ import React, { Component } from 'react'; import { FlatList } from 'react-native'; +import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants'; + import ChatMessage from './ChatMessage'; import styles from './styles'; @@ -73,11 +75,11 @@ export default class ChatMessageGroup extends Component { diff --git a/react/features/chat/components/native/MessageRecipient.js b/react/features/chat/components/native/MessageRecipient.js index 5ee48404f..fbdc1c822 100644 --- a/react/features/chat/components/native/MessageRecipient.js +++ b/react/features/chat/components/native/MessageRecipient.js @@ -3,28 +3,37 @@ import React from 'react'; import { Text, TouchableHighlight, View } from 'react-native'; +import { ColorSchemeRegistry } from '../../../base/color-scheme'; import { translate } from '../../../base/i18n'; import { Icon, IconCancelSelection } from '../../../base/icons'; import { connect } from '../../../base/redux'; +import { type StyleType } from '../../../base/styles'; import AbstractMessageRecipient, { _mapDispatchToProps, - _mapStateToProps + _mapStateToProps as _abstractMapStateToProps, + type Props as AbstractProps } 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 MessageRecipient extends AbstractMessageRecipient { +class MessageRecipient extends AbstractMessageRecipient { /** * Implements {@code PureComponent#render}. * * @inheritdoc */ render() { - const { _privateMessageRecipient } = this.props; + const { _privateMessageRecipient, _styles } = this.props; if (!_privateMessageRecipient) { return null; @@ -33,8 +42,8 @@ class MessageRecipient extends AbstractMessageRecipient { const { t } = this.props; return ( - - + + { t('chat.messageTo', { recipient: _privateMessageRecipient }) } @@ -42,11 +51,24 @@ class MessageRecipient extends AbstractMessageRecipient { + style = { _styles.messageRecipientCancelIcon } /> ); } } +/** + * 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)); diff --git a/react/features/chat/components/native/styles.js b/react/features/chat/components/native/styles.js index 4f15de8cc..53276706a 100644 --- a/react/features/chat/components/native/styles.js +++ b/react/features/chat/components/native/styles.js @@ -1,7 +1,10 @@ // @flow +import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme'; import { BoxModel, ColorPalette } from '../../../base/styles'; +const BUBBLE_RADIUS = 8; + /** * The styles of the feature chat. * @@ -20,14 +23,6 @@ export default { width: 32 }, - /** - * Background of the chat screen. - */ - backdrop: { - backgroundColor: ColorPalette.white, - flex: 1 - }, - chatContainer: { alignItems: 'stretch', flex: 1, @@ -47,14 +42,6 @@ export default { 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). */ @@ -76,35 +63,16 @@ export default { height: 48 }, + messageBubble: { + alignItems: 'center', + borderRadius: BUBBLE_RADIUS, + flexDirection: 'row' + }, + messageContainer: { 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. */ @@ -123,34 +91,11 @@ export default { alignItems: 'flex-end' }, - /** - * Style modifier for the {@code textWrapper} for own messages. - */ - ownTextWrapper: { - backgroundColor: 'rgb(210, 231, 249)', - borderTopLeftRadius: 8, - borderTopRightRadius: 0 - }, - replyWrapper: { alignItems: 'center', flexDirection: 'row' }, - replyStyles: { - iconStyle: { - color: 'rgb(118, 136, 152)', - fontSize: 22, - margin: BoxModel.margin / 2 - } - }, - - privateNotice: { - color: ColorPalette.warning, - fontSize: 13, - fontStyle: 'italic' - }, - sendButtonIcon: { color: ColorPalette.darkGrey, fontSize: 22 @@ -159,7 +104,7 @@ export default { /** * Style modifier for system (error) messages. */ - systemTextWrapper: { + systemMessageBubble: { backgroundColor: 'rgb(247, 215, 215)' }, @@ -168,9 +113,6 @@ export default { */ textWrapper: { alignItems: 'flex-start', - backgroundColor: 'rgb(240, 243, 247)', - borderRadius: 8, - borderTopLeftRadius: 0, flexDirection: 'column', padding: 9 }, @@ -183,3 +125,73 @@ export default { 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 + } + } +}); diff --git a/react/features/chat/components/web/ChatMessage.js b/react/features/chat/components/web/ChatMessage.js index 46a855dcb..208d60b97 100644 --- a/react/features/chat/components/web/ChatMessage.js +++ b/react/features/chat/components/web/ChatMessage.js @@ -7,6 +7,8 @@ import { toArray } from 'react-emoji-render'; import { translate } from '../../../base/i18n'; import { Linkify } from '../../../base/react'; +import { MESSAGE_TYPE_LOCAL } from '../../constants'; + import AbstractChatMessage, { type Props } from '../AbstractChatMessage'; @@ -47,7 +49,7 @@ class ChatMessage extends AbstractChatMessage { { message.privateMessage && this._renderPrivateNotice() } - { message.privateMessage && message.messageType !== 'local' + { message.privateMessage && message.messageType !== MESSAGE_TYPE_LOCAL && ); diff --git a/react/features/chat/components/web/MessageRecipient.js b/react/features/chat/components/web/MessageRecipient.js index 7e5a74d4d..0b787b993 100644 --- a/react/features/chat/components/web/MessageRecipient.js +++ b/react/features/chat/components/web/MessageRecipient.js @@ -8,13 +8,14 @@ import { connect } from '../../../base/redux'; import AbstractMessageRecipient, { _mapDispatchToProps, - _mapStateToProps + _mapStateToProps, + type Props } from '../AbstractMessageRecipient'; /** * Class to implement the displaying of the recipient of the next message. */ -class MessageRecipient extends AbstractMessageRecipient { +class MessageRecipient extends AbstractMessageRecipient { /** * Implements {@code PureComponent#render}. * diff --git a/react/features/chat/constants.js b/react/features/chat/constants.js index 25c5e6551..9a7a05cca 100644 --- a/react/features/chat/constants.js +++ b/react/features/chat/constants.js @@ -1,3 +1,5 @@ +// @flow + /** * The audio ID of the audio element for which the {@link playAudio} action is * triggered when new chat message is received. @@ -5,3 +7,18 @@ * @type {string} */ 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'; diff --git a/react/features/chat/middleware.js b/react/features/chat/middleware.js index 40bb890c0..089e960f9 100644 --- a/react/features/chat/middleware.js +++ b/react/features/chat/middleware.js @@ -22,7 +22,7 @@ import { isButtonEnabled, showToolbox } from '../toolbox'; import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes'; import { addMessage, clearMessages, toggleChat } from './actions'; 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'; declare var APP: Object; @@ -194,7 +194,7 @@ function _addChatMsgListener(conference, store) { function _handleChatError({ dispatch }, error) { dispatch(addMessage({ hasRead: true, - messageType: 'error', + messageType: MESSAGE_TYPE_ERROR, message: error, privateMessage: false, timestamp: Date.now() @@ -231,7 +231,7 @@ function _handleReceivedMessage({ dispatch, getState }, { id, message, nick, pri displayName, hasRead, id, - messageType: participant.local ? 'local' : 'remote', + messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE, message, privateMessage, recipient: getParticipantDisplayName(state, localParticipant.id), @@ -285,7 +285,7 @@ function _persistSentPrivateMessage({ dispatch, getState }, recipientID, message displayName, hasRead: true, id: localParticipant.id, - messageType: 'local', + messageType: MESSAGE_TYPE_LOCAL, message, privateMessage: true, recipient: getParticipantDisplayName(getState, recipientID), @@ -323,7 +323,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string { const lastMessage = navigator.product === 'ReactNative' ? 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 // a message since then. Doesn't make sense to display the notice now. return undefined; @@ -339,7 +339,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string { const now = Date.now(); const recentPrivateMessages = messages.filter( message => - message.messageType !== 'local' + message.messageType !== MESSAGE_TYPE_LOCAL && message.privateMessage && message.timestamp + PRIVACY_NOTICE_TIMEOUT > now); const recentPrivateMessage = navigator.product === 'ReactNative'