This commit is contained in:
136
react/features/chat/components/native/Chat.tsx
Normal file
136
react/features/chat/components/native/Chat.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import { Route, useIsFocused } from '@react-navigation/native';
|
||||
import React, { Component, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { closeChat, sendMessage } from '../../actions.native';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
navigation: any;
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
route: Route<'', { privateMessageRecipient: { name: string; }; }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
*/
|
||||
class Chat extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractChat} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractChat} instance with.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override render() {
|
||||
const { _messages, route } = this.props;
|
||||
const privateMessageRecipient = route?.params?.privateMessageRecipient;
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
disableForcedKeyboardDismiss = { true }
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
footerComponent = { () =>
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
}
|
||||
hasBottomTextInput = { true }
|
||||
hasExtraHeaderHeight = { true }
|
||||
style = { styles.chatContainer }>
|
||||
{/* @ts-ignore */}
|
||||
<MessageContainer messages = { _messages } />
|
||||
<MessageRecipient privateMessageRecipient = { privateMessageRecipient } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onSendMessage(text: string) {
|
||||
this.props.dispatch(sendMessage(text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { messages, nbUnreadMessages } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
const { _nbUnreadMessages, dispatch, navigation, t } = props;
|
||||
const unreadMessagesNr = _nbUnreadMessages > 0;
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
useEffect(() => {
|
||||
navigation?.setOptions({
|
||||
tabBarLabel: () => (
|
||||
<TabBarLabelCounter
|
||||
activeUnreadNr = { unreadMessagesNr }
|
||||
isFocused = { isFocused }
|
||||
label = { t('chat.tabs.chat') }
|
||||
nbUnread = { _nbUnreadMessages } />
|
||||
)
|
||||
});
|
||||
|
||||
return () => {
|
||||
isFocused && dispatch(closeChat());
|
||||
};
|
||||
}, [ isFocused, _nbUnreadMessages ]);
|
||||
|
||||
return (
|
||||
<Chat { ...props } />
|
||||
);
|
||||
}));
|
||||
80
react/features/chat/components/native/ChatButton.ts
Normal file
80
react/features/chat/components/native/ChatButton.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CHAT_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconChatUnread, IconMessage } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The unread message count.
|
||||
*/
|
||||
_unreadMessageCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the chat screen on mobile.
|
||||
*/
|
||||
class ChatButton extends AbstractButton<IProps> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.chat';
|
||||
override icon = IconMessage;
|
||||
override label = 'toolbar.chat';
|
||||
override toggledIcon = IconChatUnread;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
this.props._isPollsDisabled
|
||||
? navigate(screen.conference.chat)
|
||||
: navigate(screen.conference.chatandpolls.main);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the button toggled when there are unread messages.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return Boolean(this.props._unreadMessageCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component instance.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
_isPollsDisabled: arePollsDisabled(state),
|
||||
|
||||
// The toggled icon should also be available for new polls
|
||||
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatButton));
|
||||
209
react/features/chat/components/native/ChatInputBar.tsx
Normal file
209
react/features/chat/components/native/ChatInputBar.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Platform, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { Text } from 'react-native-paper';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconSend } from '../../../base/icons/svg';
|
||||
import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import Input from '../../../base/ui/components/native/Input';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { isSendGroupChatDisabled } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether sending group chat messages is disabled.
|
||||
*/
|
||||
_isSendGroupChatDisabled: boolean;
|
||||
|
||||
/**
|
||||
* The id of the message recipient, if any.
|
||||
*/
|
||||
_privateMessageRecipientId?: string;
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
aspectRatio: Symbol;
|
||||
|
||||
/**
|
||||
* Callback to invoke on message send.
|
||||
*/
|
||||
onSend: Function;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* Boolean to show if an extra padding needs to be added to the bar.
|
||||
*/
|
||||
addPadding: boolean;
|
||||
|
||||
/**
|
||||
* The value of the input field.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Boolean to show or hide the send button.
|
||||
*/
|
||||
showSend: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the chat input bar with text field and action(s).
|
||||
*/
|
||||
class ChatInputBar extends Component<IProps, IState> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
addPadding: false,
|
||||
message: '',
|
||||
showSend: false
|
||||
};
|
||||
|
||||
this._onChangeText = this._onChangeText.bind(this);
|
||||
this._onFocused = this._onFocused.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override render() {
|
||||
let inputBarStyles;
|
||||
|
||||
if (this.props.aspectRatio === ASPECT_RATIO_WIDE) {
|
||||
inputBarStyles = styles.inputBarWide;
|
||||
} else {
|
||||
inputBarStyles = styles.inputBarNarrow;
|
||||
}
|
||||
|
||||
if (this.props._isSendGroupChatDisabled && !this.props._privateMessageRecipientId) {
|
||||
return (
|
||||
<View
|
||||
id = 'no-messages-message'
|
||||
style = { styles.disabledSendWrapper as ViewStyle }>
|
||||
<Text style = { styles.emptyComponentText as TextStyle }>
|
||||
{ this.props.t('chat.disabled') }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
id = 'chat-input'
|
||||
style = { [
|
||||
inputBarStyles,
|
||||
this.state.addPadding ? styles.extraBarPadding : null
|
||||
] as ViewStyle[] }>
|
||||
<Input
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ container: styles.customInputContainer }}
|
||||
id = 'chat-input-messagebox'
|
||||
multiline = { false }
|
||||
onBlur = { this._onFocused(false) }
|
||||
onChange = { this._onChangeText }
|
||||
onFocus = { this._onFocused(true) }
|
||||
onSubmitEditing = { this._onSubmit }
|
||||
placeholder = { this.props.t('chat.fieldPlaceHolder') }
|
||||
returnKeyType = 'send'
|
||||
value = { this.state.message } />
|
||||
<IconButton
|
||||
disabled = { !this.state.message }
|
||||
id = { this.props.t('chat.sendButton') }
|
||||
onPress = { this._onSubmit }
|
||||
src = { IconSend }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: string) {
|
||||
this.setState({
|
||||
message: text,
|
||||
showSend: Boolean(text)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: boolean) {
|
||||
return () => {
|
||||
Platform.OS === 'android' && this.setState({
|
||||
addPadding: focused
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to handle the submit event of the text field.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmit() {
|
||||
const {
|
||||
_isSendGroupChatDisabled,
|
||||
_privateMessageRecipientId,
|
||||
onSend
|
||||
} = this.props;
|
||||
|
||||
if (_isSendGroupChatDisabled && !_privateMessageRecipientId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = this.state.message.trim();
|
||||
|
||||
message && onSend(message);
|
||||
this.setState({
|
||||
message: '',
|
||||
showSend: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { aspectRatio } = state['features/base/responsive-ui'];
|
||||
const { privateMessageRecipient } = state['features/chat'];
|
||||
const isGroupChatDisabled = isSendGroupChatDisabled(state);
|
||||
|
||||
return {
|
||||
_isSendGroupChatDisabled: isGroupChatDisabled,
|
||||
_privateMessageRecipientId: privateMessageRecipient?.id,
|
||||
aspectRatio
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatInputBar));
|
||||
244
react/features/chat/components/native/ChatMessage.tsx
Normal file
244
react/features/chat/components/native/ChatMessage.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Linkify from '../../../base/react/components/native/Linkify';
|
||||
import { isGifEnabled, isGifMessage } from '../../../gifs/functions.native';
|
||||
import { CHAR_LIMIT, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import {
|
||||
getCanReplyToMessage,
|
||||
getFormattedTimestamp,
|
||||
getMessageText,
|
||||
getPrivateNoticeMessage,
|
||||
replaceNonUnicodeEmojis
|
||||
} from '../../functions';
|
||||
import { IChatMessageProps } from '../../types';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override render() {
|
||||
const { gifEnabled, message, knocking } = this.props;
|
||||
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
|
||||
const { privateMessage, lobbyChat } = message;
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle: ViewStyle[] = [
|
||||
styles.detailsWrapper as ViewStyle
|
||||
];
|
||||
const messageBubbleStyle: ViewStyle[] = [
|
||||
styles.messageBubble as ViewStyle
|
||||
];
|
||||
|
||||
if (localMessage) {
|
||||
// This is a message sent by the local participant.
|
||||
|
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper as ViewStyle);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (lobbyChat && !knocking) {
|
||||
messageBubbleStyle.push(styles.lobbyMessageBubble);
|
||||
}
|
||||
|
||||
const messageText = getMessageText(this.props.message);
|
||||
|
||||
return (
|
||||
<View
|
||||
id = { message.messageId }
|
||||
style = { styles.messageWrapper as ViewStyle } >
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper as ViewStyle } >
|
||||
{ this._renderDisplayName() }
|
||||
{ gifEnabled && isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
: this._renderMessageTextComponent(messageText) }
|
||||
{ this._renderPrivateNotice() }
|
||||
</View>
|
||||
{ this._renderPrivateReplyButton() }
|
||||
</View>
|
||||
{ this._renderTimestamp() }
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar of the sender.
|
||||
*
|
||||
* @returns {React.ReactElement<*>}
|
||||
*/
|
||||
_renderAvatar() {
|
||||
const { message } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.avatarWrapper }>
|
||||
{ this.props.showAvatar && <Avatar
|
||||
displayName = { message.displayName }
|
||||
participantId = { message.participantId }
|
||||
size = { styles.avatarWrapper.width } />
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the display name of the sender if necessary.
|
||||
*
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { message, showDisplayName, t } = this.props;
|
||||
|
||||
if (!showDisplayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { displayName, isFromVisitor } = message;
|
||||
|
||||
return (
|
||||
<Text style = { styles.senderDisplayName }>
|
||||
{ `${displayName}${isFromVisitor ? ` ${t('visitors.chatIndicator')}` : ''}` }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message text based on number of characters.
|
||||
*
|
||||
* @param {string} messageText - The message text.
|
||||
* @returns {React.ReactElement<*>}
|
||||
*/
|
||||
_renderMessageTextComponent(messageText: string) {
|
||||
|
||||
if (messageText.length >= CHAR_LIMIT) {
|
||||
return (
|
||||
<Text
|
||||
selectable = { true }
|
||||
style = { styles.chatMessage }>
|
||||
{ messageText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Linkify
|
||||
linkStyle = { styles.chatLink }
|
||||
style = { styles.chatMessage }>
|
||||
{ replaceNonUnicodeEmojis(messageText) }
|
||||
</Linkify>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message privacy notice, if necessary.
|
||||
*
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
const { message, knocking } = this.props;
|
||||
|
||||
if (!(message.privateMessage || (message.lobbyChat && !knocking))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { message.lobbyChat ? styles.lobbyMsgNotice : styles.privateNotice }>
|
||||
{ getPrivateNoticeMessage(this.props.message) }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the private reply button, if necessary.
|
||||
*
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderPrivateReplyButton() {
|
||||
const { message, canReply } = this.props;
|
||||
const { lobbyChat } = message;
|
||||
|
||||
if (!canReply) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.replyContainer as ViewStyle }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { lobbyChat }
|
||||
participantID = { message.participantId }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { styles.replyStyles } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent, if necessary.
|
||||
*
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
if (!this.props.showTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.timeText }>
|
||||
{ getFormattedTimestamp(this.props.message) }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IChatMessageProps} message - Message object.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, { message }: IChatMessageProps) {
|
||||
return {
|
||||
canReply: getCanReplyToMessage(state, message),
|
||||
gifEnabled: isGifEnabled(state),
|
||||
knocking: state['features/lobby'].knocking
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatMessage));
|
||||
81
react/features/chat/components/native/ChatMessageGroup.tsx
Normal file
81
react/features/chat/components/native/ChatMessageGroup.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The messages array to render.
|
||||
*/
|
||||
messages: Array<IMessage>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
export default class ChatMessageGroup extends Component<IProps> {
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderMessage = this._renderMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override render() {
|
||||
return (
|
||||
<FlatList
|
||||
data = { this.props.messages }
|
||||
inverted = { true }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
renderItem = { this._renderMessage } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: Object, index: number) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Object} message - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessage({ index, item: message }: { index: number; item: IMessage; }) {
|
||||
return (
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
showAvatar = {
|
||||
this.props.messages[0].messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showDisplayName = {
|
||||
this.props.messages[0].messageType === MESSAGE_TYPE_REMOTE
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showTimestamp = { index === 0 } />
|
||||
);
|
||||
}
|
||||
}
|
||||
30
react/features/chat/components/native/ChatPrivacyDialog.tsx
Normal file
30
react/features/chat/components/native/ChatPrivacyDialog.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ConfirmDialog from '../../../base/dialog/components/native/ConfirmDialog';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { AbstractChatPrivacyDialog, _mapDispatchToProps, _mapStateToProps } from '../AbstractChatPrivacyDialog';
|
||||
|
||||
/**
|
||||
* Implements a component for the dialog displayed to avoid mis-sending private messages.
|
||||
*/
|
||||
class ChatPrivacyDialog extends AbstractChatPrivacyDialog {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.sendPrivateMessageCancel'
|
||||
confirmLabel = 'dialog.sendPrivateMessageOk'
|
||||
descriptionKey = 'dialog.sendPrivateMessage'
|
||||
onCancel = { this._onSendGroupMessage }
|
||||
onSubmit = { this._onSendPrivateMessage } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog));
|
||||
28
react/features/chat/components/native/GifMessage.tsx
Normal file
28
react/features/chat/components/native/GifMessage.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Image, ImageStyle, View } from 'react-native';
|
||||
|
||||
import { extractGifURL } from '../../../gifs/function.any';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The formatted gif message.
|
||||
*/
|
||||
message: string;
|
||||
}
|
||||
|
||||
const GifMessage = ({ message }: IProps) => {
|
||||
const url = extractGifURL(message);
|
||||
|
||||
return (<View
|
||||
id = 'gif-message'
|
||||
style = { styles.gifContainer }>
|
||||
<Image
|
||||
source = {{ uri: url }}
|
||||
style = { styles.gifImage as ImageStyle } />
|
||||
</View>);
|
||||
};
|
||||
|
||||
export default GifMessage;
|
||||
117
react/features/chat/components/native/MessageContainer.tsx
Normal file
117
react/features/chat/components/native/MessageContainer.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IMessageGroup, groupMessagesBySender } from '../../../base/util/messageGrouping';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps {
|
||||
messages: IMessage[];
|
||||
t: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a container to render all the chat messages in a conference.
|
||||
*/
|
||||
class MessageContainer extends Component<IProps, any> {
|
||||
|
||||
static defaultProps = {
|
||||
messages: [] as IMessage[]
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._keyExtractor = this._keyExtractor.bind(this);
|
||||
this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
|
||||
this._renderMessageGroup = this._renderMessageGroup.bind(this);
|
||||
this._getMessagesGroupedBySender = this._getMessagesGroupedBySender.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
override render() {
|
||||
const data = this._getMessagesGroupedBySender();
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
ListEmptyComponent = { this._renderListEmptyComponent }
|
||||
bounces = { false }
|
||||
data = { data }
|
||||
|
||||
// Workaround for RN bug:
|
||||
// https://github.com/facebook/react-native/issues/21196
|
||||
inverted = { Boolean(data.length) }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
keyboardShouldPersistTaps = 'handled'
|
||||
renderItem = { this._renderMessageGroup } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: Object, index: number) {
|
||||
return `key_${index}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a message when there are no messages in the chat yet.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
_renderListEmptyComponent() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
id = 'no-messages-message'
|
||||
style = { styles.emptyComponentWrapper as ViewStyle }>
|
||||
<Text style = { styles.emptyComponentText as TextStyle }>
|
||||
{ t('chat.noMessagesMessage') }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {Array<Object>} messages - The chat message to render.
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderMessageGroup({ item: group }: { item: IMessageGroup<IMessage>; }) {
|
||||
const { messages } = group;
|
||||
|
||||
return <ChatMessageGroup messages = { messages } />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of message groups, where each group is an array of messages
|
||||
* grouped by the sender.
|
||||
*
|
||||
* @returns {Array<Array<Object>>}
|
||||
*/
|
||||
_getMessagesGroupedBySender() {
|
||||
return groupMessagesBySender(this.props.messages);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(MessageContainer));
|
||||
163
react/features/chat/components/native/MessageRecipient.tsx
Normal file
163
react/features/chat/components/native/MessageRecipient.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { ILocalParticipant } from '../../../base/participants/types';
|
||||
import {
|
||||
setParams
|
||||
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../../actions.any';
|
||||
import AbstractMessageRecipient, {
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps as _mapStateToPropsAbstract
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Is lobby messaging active.
|
||||
*/
|
||||
isLobbyChatActive: boolean;
|
||||
|
||||
/**
|
||||
* The participant string for lobby chat messaging.
|
||||
*/
|
||||
lobbyMessageRecipient?: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | ILocalParticipant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient<IProps> {
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @param {IProps} props - The props of the component.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onResetPrivateMessageRecipient = this._onResetPrivateMessageRecipient.bind(this);
|
||||
this._onResetLobbyMessageRecipient = this._onResetLobbyMessageRecipient.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets lobby message recipient from state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onResetLobbyMessageRecipient() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(setLobbyChatActiveState(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets private message recipient from state.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onResetPrivateMessageRecipient() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(setPrivateMessageRecipient());
|
||||
|
||||
setParams({
|
||||
privateMessageRecipient: undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
override render() {
|
||||
const {
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient,
|
||||
_privateMessageRecipient,
|
||||
_isVisitor,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
if (isLobbyChatActive) {
|
||||
return (
|
||||
<View
|
||||
id = 'chat-recipient'
|
||||
style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.lobbyChatMessageTo', {
|
||||
recipient: lobbyMessageRecipient?.name
|
||||
}) }
|
||||
</Text>
|
||||
<TouchableHighlight
|
||||
onPress = { this._onResetLobbyMessageRecipient }>
|
||||
<Icon
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!_privateMessageRecipient) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
id = 'message-recipient'
|
||||
style = { styles.messageRecipientContainer as ViewStyle }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: `${_privateMessageRecipient}${_isVisitor ? ` ${t('visitors.chatIndicator')}` : ''}`
|
||||
}) }
|
||||
</Text>
|
||||
<TouchableHighlight
|
||||
id = 'message-recipient-cancel-button'
|
||||
onPress = { this._onResetPrivateMessageRecipient }
|
||||
underlayColor = { 'transparent' }>
|
||||
<Icon
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {any} _ownProps - Component's own props.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
|
||||
return {
|
||||
..._mapStateToPropsAbstract(state, _ownProps),
|
||||
isLobbyChatActive,
|
||||
lobbyMessageRecipient
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(MessageRecipient));
|
||||
110
react/features/chat/components/native/PrivateMessageButton.tsx
Normal file
110
react/features/chat/components/native/PrivateMessageButton.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CHAT_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconMessage, IconReply } from '../../../base/icons/svg';
|
||||
import { getParticipantById } from '../../../base/participants/functions';
|
||||
import { IParticipant } from '../../../base/participants/types';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { arePollsDisabled } from '../../../conference/functions.any';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { handleLobbyChatInitialized, openChat } from '../../actions.native';
|
||||
|
||||
export interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if message is a lobby chat message.
|
||||
*/
|
||||
_isLobbyMessage: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The participant object retrieved from Redux.
|
||||
*/
|
||||
_participant?: IParticipant;
|
||||
|
||||
/**
|
||||
* The ID of the participant that the message is to be sent.
|
||||
*/
|
||||
participantID: string;
|
||||
|
||||
/**
|
||||
* True if the button is rendered as a reply button.
|
||||
*/
|
||||
reply: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to render a button that initiates the sending of a private message through chat.
|
||||
*/
|
||||
class PrivateMessageButton extends AbstractButton<IProps, any> {
|
||||
override accessibilityLabel = 'toolbar.accessibilityLabel.privateMessage';
|
||||
override icon = IconMessage;
|
||||
override label = 'toolbar.privateMessage';
|
||||
override toggledIcon = IconReply;
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
override _handleClick() {
|
||||
if (this.props._isLobbyMessage) {
|
||||
this.props.dispatch(handleLobbyChatInitialized(this.props.participantID));
|
||||
}
|
||||
|
||||
this.props.dispatch(openChat(this.props._participant));
|
||||
|
||||
this.props._isPollsDisabled
|
||||
? navigate(screen.conference.chat, {
|
||||
privateMessageRecipient: this.props._participant
|
||||
})
|
||||
: navigate(screen.conference.chatandpolls.main, {
|
||||
screen: screen.conference.chatandpolls.tab.chat,
|
||||
params: {
|
||||
privateMessageRecipient: this.props._participant
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to be implemented by subclasses, which must return a
|
||||
* {@code boolean} value indicating if this button is toggled or not.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
override _isToggled() {
|
||||
return this.props.reply;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { visible = enabled, isLobbyMessage, participantID } = ownProps;
|
||||
|
||||
return {
|
||||
_isPollsDisabled: arePollsDisabled(state),
|
||||
_participant: getParticipantById(state, participantID),
|
||||
_isLobbyMessage: isLobbyMessage,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(PrivateMessageButton));
|
||||
272
react/features/chat/components/native/styles.ts
Normal file
272
react/features/chat/components/native/styles.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { BoxModel } from '../../../base/styles/components/styles/BoxModel';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const BUBBLE_RADIUS = 8;
|
||||
|
||||
const recipientContainer = {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.support05,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
marginHorizontal: BaseTheme.spacing[3],
|
||||
padding: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
const inputBar = {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the feature chat.
|
||||
*
|
||||
* NOTE: Sizes and colors come from the 8x8 guidelines. This is the first
|
||||
* component to receive this treating, if others happen to have similar, we
|
||||
* need to extract the brand colors and sizes into a branding feature (planned
|
||||
* for the future).
|
||||
*/
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: BaseTheme.palette.ui10,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
chatDisabled: {
|
||||
padding: BaseTheme.spacing[2],
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
emptyComponentText: {
|
||||
color: BaseTheme.palette.text03,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
lobbyMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.support06
|
||||
},
|
||||
|
||||
lobbyMsgNotice: {
|
||||
color: BaseTheme.palette.text04,
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
...BaseTheme.palette.bodyShortRegular,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
privateMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.support05
|
||||
},
|
||||
|
||||
remoteMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.ui02,
|
||||
borderTopLeftRadius: 0
|
||||
},
|
||||
|
||||
replyContainer: {
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 22,
|
||||
padding: BaseTheme.spacing[2]
|
||||
},
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the avatar.
|
||||
*/
|
||||
avatarWrapper: {
|
||||
marginRight: BaseTheme.spacing[2],
|
||||
width: 32
|
||||
},
|
||||
|
||||
chatLink: {
|
||||
color: BaseTheme.palette.link01
|
||||
},
|
||||
|
||||
chatMessage: {
|
||||
...BaseTheme.typography.bodyShortRegular,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for the details together, such as name, message and time.
|
||||
*/
|
||||
detailsWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
emptyComponentWrapper: {
|
||||
alignSelf: 'center',
|
||||
flex: 1,
|
||||
padding: BoxModel.padding,
|
||||
paddingTop: '8%',
|
||||
maxWidth: '80%'
|
||||
},
|
||||
|
||||
disabledSendWrapper: {
|
||||
alignSelf: 'center',
|
||||
flex: 0,
|
||||
padding: BoxModel.padding,
|
||||
paddingBottom: '8%',
|
||||
paddingTop: '8%',
|
||||
maxWidth: '80%'
|
||||
},
|
||||
|
||||
/**
|
||||
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
|
||||
*/
|
||||
extraBarPadding: {
|
||||
paddingBottom: 30
|
||||
},
|
||||
|
||||
inputBarNarrow: {
|
||||
...inputBar,
|
||||
height: 112,
|
||||
marginHorizontal: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
inputBarWide: {
|
||||
...inputBar,
|
||||
height: 88,
|
||||
marginHorizontal: BaseTheme.spacing[9]
|
||||
},
|
||||
|
||||
customInputContainer: {
|
||||
width: '75%'
|
||||
},
|
||||
|
||||
messageBubble: {
|
||||
alignItems: 'center',
|
||||
borderRadius: BUBBLE_RADIUS,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the entire block.
|
||||
*/
|
||||
messageWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 17,
|
||||
marginVertical: 4
|
||||
},
|
||||
|
||||
/**
|
||||
* Style modifier for the {@code detailsWrapper} for own messages.
|
||||
*/
|
||||
ownMessageDetailsWrapper: {
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
replyWrapper: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
/**
|
||||
* Style modifier for system (error) messages.
|
||||
*/
|
||||
systemMessageBubble: {
|
||||
backgroundColor: 'rgb(247, 215, 215)'
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for the name and the message text.
|
||||
*/
|
||||
textWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'column',
|
||||
padding: 9
|
||||
},
|
||||
|
||||
/**
|
||||
* Text node for the timestamp.
|
||||
*/
|
||||
timeText: {
|
||||
color: BaseTheme.palette.text03,
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
chatContainer: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
tabContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
tabLeftButton: {
|
||||
flex: 1,
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomLeftRadius: 0
|
||||
},
|
||||
|
||||
tabRightButton: {
|
||||
flex: 1,
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0
|
||||
},
|
||||
|
||||
gifContainer: {
|
||||
maxHeight: 150
|
||||
},
|
||||
|
||||
gifImage: {
|
||||
resizeMode: 'contain',
|
||||
width: 250,
|
||||
height: undefined,
|
||||
flexGrow: 1
|
||||
},
|
||||
|
||||
senderDisplayName: {
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
localMessageBubble: {
|
||||
backgroundColor: BaseTheme.palette.ui04,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
lobbyMessageRecipientContainer: {
|
||||
...recipientContainer,
|
||||
backgroundColor: BaseTheme.palette.support06
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
...recipientContainer
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
...BaseTheme.typography.bodyShortRegular,
|
||||
color: BaseTheme.palette.text01,
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user