feat(chat/polls/native) Update chat and polls UI (#12550)

* feat(chat/polls/native): update ui
This commit is contained in:
Calinteodor 2022-11-18 14:46:54 +02:00 committed by GitHub
parent 1259e54d46
commit 971fe0481f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 316 additions and 252 deletions

View File

@ -1,5 +1,3 @@
// @flow
import { ColorPalette, getRGBAFormat } from '../styles';
/**
@ -14,21 +12,6 @@ export default {
icon: 'rgb(28, 32, 37)',
text: 'rgb(28, 32, 37)'
},
'Chat': {
displayName: 'rgb(94, 109, 121)',
localMsgBackground: 'rgb(215, 230, 249)',
lobbyMsgBackground: 'rgb(106, 80, 211)',
lobbyMsgNotice: 'rgb(16, 10, 41)',
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)'
},
'Conference': {
inviteButtonBackground: 'rgb(0, 119, 225)',
onVideoText: 'white'
},
'Dialog': {},
'Header': {
background: ColorPalette.blue,

View File

@ -19,7 +19,12 @@ type Props = {
/**
* The extra styles to be applied to links.
*/
linkStyle: StyleType
linkStyle: StyleType,
/**
* The extra styles to be applied to text.
*/
style?: StyleType
};
/**
@ -46,7 +51,9 @@ export default class Linkify extends Component<Props> {
return (
<ReactLinkify
componentDecorator = { this._componentDecorator }>
<Text selectable = { true }>
<Text
selectable = { true }
style = { this.props.style }>
{ this.props.children }
</Text>
</ReactLinkify>

View File

@ -1,4 +1,4 @@
// @flow
/* eslint-disable react/no-multi-comp */
import { useIsFocused } from '@react-navigation/native';
import React, { useEffect } from 'react';
@ -6,7 +6,8 @@ import React, { useEffect } from 'react';
import { translate } from '../../../base/i18n';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { connect } from '../../../base/redux';
import { closeChat } from '../../actions.any';
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
import { closeChat } from '../../actions.native';
import AbstractChat, {
type Props as AbstractProps,
_mapStateToProps
@ -17,14 +18,8 @@ import MessageContainer from './MessageContainer';
import MessageRecipient from './MessageRecipient';
import styles from './styles';
type Props = AbstractProps & {
/**
* Is this screen focused or not(React Navigation).
*/
isChatScreenFocused: boolean,
/**
* Default prop for navigating between screen components(React Navigation).
*/
@ -41,7 +36,6 @@ type Props = AbstractProps & {
* the mobile client.
*/
class Chat extends AbstractChat<Props> {
/**
* Implements React's {@link Component#render()}.
*
@ -49,7 +43,7 @@ class Chat extends AbstractChat<Props> {
*/
render() {
const { _messages, route } = this.props;
const privateMessageRecipient = route.params?.privateMessageRecipient;
const privateMessageRecipient = route?.params?.privateMessageRecipient;
return (
<JitsiScreen
@ -68,28 +62,26 @@ class Chat extends AbstractChat<Props> {
}
export default translate(connect(_mapStateToProps)(props => {
const {
_nbUnreadMessages,
navigation,
t
} = props;
const isChatScreenFocused = useIsFocused();
const { _nbUnreadMessages, dispatch, navigation, t } = props;
const unreadMessagesNr = _nbUnreadMessages > 0;
const nrUnreadMessages
= !isChatScreenFocused && _nbUnreadMessages > 0
? `(${_nbUnreadMessages})` : '';
const isFocused = useIsFocused();
useEffect(() => {
navigation.setOptions({
tabBarLabel: `${t('chat.tabs.chat')} ${nrUnreadMessages}`
navigation?.setOptions({
tabBarLabel: () => (
<TabBarLabelCounter
activeUnreadNr = { unreadMessagesNr }
isFocused = { isFocused }
label = { t('chat.tabs.chat') }
nbUnread = { _nbUnreadMessages } />
)
});
return () => props.dispatch(closeChat());
}, [ nrUnreadMessages ]);
return () => isFocused && dispatch(closeChat());
}, [ isFocused, _nbUnreadMessages ]);
return (
<Chat
{ ...props }
isChatScreenFocused = { isChatScreenFocused } />
<Chat { ...props } />
);
}));

View File

@ -1,5 +1,3 @@
// @flow
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconChatUnread, IconMessage } from '../../../base/icons';
@ -10,9 +8,9 @@ import {
} from '../../../base/toolbox/components';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount } from '../../functions';
type Props = AbstractButtonProps & {
/**
@ -72,7 +70,9 @@ function _mapStateToProps(state, ownProps) {
return {
_isPollsDisabled: disablePolls,
_unreadMessageCount: getUnreadCount(state),
// The toggled icon should also be available for new polls
_unreadMessageCount: getUnreadCount(state) || getUnreadPollCount(state),
visible
};
}

View File

@ -1,30 +1,19 @@
// @flow
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 { isGifMessage } from '../../../gifs/functions';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import { replaceNonUnicodeEmojis } from '../../functions';
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
import GifMessage from './GifMessage';
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.
@ -36,7 +25,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
* @inheritdoc
*/
render() {
const { _styles, message, knocking } = this.props;
const { message, knocking } = this.props;
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
const { privateMessage, lobbyChat } = message;
@ -56,7 +45,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
// The bubble needs some additional styling
messageBubbleStyle.push(_styles.localMessageBubble);
messageBubbleStyle.push(styles.localMessageBubble);
} else if (message.messageType === MESSAGE_TYPE_ERROR) {
// This is a system message.
@ -66,15 +55,15 @@ class ChatMessage extends AbstractChatMessage<Props> {
// This is a remote message sent by a remote participant.
// The bubble needs some additional styling
messageBubbleStyle.push(_styles.remoteMessageBubble);
messageBubbleStyle.push(styles.remoteMessageBubble);
}
if (privateMessage) {
messageBubbleStyle.push(_styles.privateMessageBubble);
messageBubbleStyle.push(styles.privateMessageBubble);
}
if (lobbyChat && !knocking) {
messageBubbleStyle.push(_styles.lobbyMessageBubble);
messageBubbleStyle.push(styles.lobbyMessageBubble);
}
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
@ -86,11 +75,13 @@ class ChatMessage extends AbstractChatMessage<Props> {
<View style = { messageBubbleStyle }>
<View style = { styles.textWrapper } >
{ this._renderDisplayName() }
{isGifMessage(messageText)
{ isGifMessage(messageText)
? <GifMessage message = { messageText } />
: (
<Linkify linkStyle = { styles.chatLink }>
{messageText}
<Linkify
linkStyle = { styles.chatLink }
style = { styles.chatMessage }>
{ messageText }
</Linkify>
)}
{ this._renderPrivateNotice() }
@ -134,14 +125,14 @@ class ChatMessage extends AbstractChatMessage<Props> {
* @returns {React$Element<*> | null}
*/
_renderDisplayName() {
const { _styles, message, showDisplayName } = this.props;
const { message, showDisplayName } = this.props;
if (!showDisplayName) {
return null;
}
return (
<Text style = { _styles.displayName }>
<Text style = { styles.senderDisplayName }>
{ message.displayName }
</Text>
);
@ -153,14 +144,14 @@ class ChatMessage extends AbstractChatMessage<Props> {
* @returns {React$Element<*> | null}
*/
_renderPrivateNotice() {
const { _styles, message, knocking } = this.props;
const { message, knocking } = this.props;
if (!(message.privateMessage || (message.lobbyChat && !knocking))) {
return null;
}
return (
<Text style = { message.lobbyChat ? _styles.lobbyMsgNotice : _styles.privateNotice }>
<Text style = { message.lobbyChat ? styles.lobbyMsgNotice : styles.privateNotice }>
{ this._getPrivateNoticeMessage() }
</Text>
);
@ -172,7 +163,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
* @returns {React$Element<*> | null}
*/
_renderPrivateReplyButton() {
const { _styles, message, knocking } = this.props;
const { message, knocking } = this.props;
const { messageType, privateMessage, lobbyChat } = message;
if (!(privateMessage || lobbyChat) || messageType === MESSAGE_TYPE_LOCAL || knocking) {
@ -180,13 +171,13 @@ class ChatMessage extends AbstractChatMessage<Props> {
}
return (
<View style = { _styles.replyContainer }>
<View style = { styles.replyContainer }>
<PrivateMessageButton
isLobbyMessage = { lobbyChat }
participantID = { message.id }
reply = { true }
showLabel = { false }
toggledStyles = { _styles.replyStyles } />
toggledStyles = { styles.replyStyles } />
</View>
);
}
@ -217,7 +208,6 @@ class ChatMessage extends AbstractChatMessage<Props> {
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Chat'),
knocking: state['features/lobby'].knocking
};
}

View File

@ -1,6 +1,4 @@
// @flow
import React, { Component } from 'react';
import React, { Component, ReactElement } from 'react';
import { FlatList } from 'react-native';
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
@ -60,7 +58,7 @@ export default class ChatMessageGroup extends Component<Props> {
return `key_${index}`;
}
_renderMessage: Object => React$Element<*>;
_renderMessage: Object => ReactElement;
/**
* Renders a single chat message.

View File

@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { ConfirmDialog } from '../../../base/dialog';

View File

@ -1,12 +1,8 @@
// @flow
import React from 'react';
import React, { ReactElement } from 'react';
import { FlatList, Text, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import AbstractMessageContainer, { type Props as AbstractProps }
from '../AbstractMessageContainer';
@ -15,11 +11,6 @@ import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Function to be used to translate i18n labels.
*/
@ -82,7 +73,7 @@ class MessageContainer extends AbstractMessageContainer<Props> {
return `key_${index}`;
}
_renderListEmptyComponent: () => React$Element<any>;
_renderListEmptyComponent: () => ReactElement;
/**
* Renders a message when there are no messages in the chat yet.
@ -90,18 +81,18 @@ class MessageContainer extends AbstractMessageContainer<Props> {
* @returns {React$Element<any>}
*/
_renderListEmptyComponent() {
const { _styles, t } = this.props;
const { t } = this.props;
return (
<View style = { styles.emptyComponentWrapper }>
<Text style = { _styles.emptyComponentText }>
<Text style = { styles.emptyComponentText }>
{ t('chat.noMessagesMessage') }
</Text>
</View>
);
}
_renderMessageGroup: Object => React$Element<any>;
_renderMessageGroup: Object => ReactElement;
/**
* Renders a single chat message.
@ -114,16 +105,4 @@ class MessageContainer extends AbstractMessageContainer<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 {
_styles: ColorSchemeRegistry.get(state, 'Chat')
};
}
export default translate(connect(_mapStateToProps)(MessageContainer));
export default translate(connect()(MessageContainer));

View File

@ -1,13 +1,9 @@
// @flow
import React from 'react';
import { Text, TouchableHighlight, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { Icon, IconCloseCircle } from '../../../base/icons';
import { Icon, IconCloseLarge } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { type StyleType } from '../../../base/styles';
import {
setParams
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
@ -16,14 +12,11 @@ import AbstractMessageRecipient, {
type Props as AbstractProps
} from '../AbstractMessageRecipient';
import styles from './styles';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* The Redux dispatch function.
*/
@ -99,14 +92,17 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
* @returns {ReactElement}
*/
render() {
const { _styles, privateMessageRecipient, t,
isLobbyChatActive, lobbyMessageRecipient } = this.props;
const {
isLobbyChatActive,
lobbyMessageRecipient,
privateMessageRecipient,
t
} = this.props;
if (isLobbyChatActive) {
return (
<View style = { _styles.lobbyMessageRecipientContainer }>
<Text style = { _styles.messageRecipientText }>
<View style = { styles.lobbyMessageRecipientContainer }>
<Text style = { styles.messageRecipientText }>
{ t('chat.lobbyChatMessageTo', {
recipient: lobbyMessageRecipient.name
}) }
@ -114,8 +110,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
<TouchableHighlight
onPress = { this._onResetLobbyMessageRecipient }>
<Icon
src = { IconCloseCircle }
style = { _styles.messageRecipientCancelIcon } />
src = { IconCloseLarge }
style = { styles.messageRecipientCancelIcon } />
</TouchableHighlight>
</View>
);
@ -126,8 +122,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
}
return (
<View style = { _styles.messageRecipientContainer }>
<Text style = { _styles.messageRecipientText }>
<View style = { styles.messageRecipientContainer }>
<Text style = { styles.messageRecipientText }>
{ t('chat.messageTo', {
recipient: privateMessageRecipient.name
}) }
@ -136,8 +132,8 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
onPress = { this._onResetPrivateMessageRecipient }
underlayColor = { 'transparent' }>
<Icon
src = { IconCloseCircle }
style = { _styles.messageRecipientCancelIcon } />
src = { IconCloseLarge }
style = { styles.messageRecipientCancelIcon } />
</TouchableHighlight>
</View>
);
@ -154,7 +150,6 @@ function _mapStateToProps(state) {
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
return {
_styles: ColorSchemeRegistry.get(state, 'Chat'),
isLobbyChatActive,
lobbyMessageRecipient
};

View File

@ -1,5 +1,3 @@
// @flow
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconMessage, IconReply } from '../../../base/icons';
@ -10,7 +8,6 @@ import { handleLobbyChatInitialized, openChat } from '../../../chat/actions';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
export type Props = AbstractButtonProps & {
/**
@ -103,7 +100,7 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
export function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
export function _mapStateToProps(state: Object, ownProps: Props) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled, isLobbyMessage, participantID } = ownProps;

View File

@ -1,11 +1,19 @@
// @flow
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette } from '../../../base/styles';
import { BoxModel } from '../../../base/styles';
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]
};
/**
* The styles of the feature chat.
*
@ -16,16 +24,72 @@ const BUBBLE_RADIUS = 8;
*/
export default {
/**
* Background of the chat screen.
*/
backdrop: {
backgroundColor: BaseTheme.palette.ui10,
flex: 1
},
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: 8,
marginRight: BaseTheme.spacing[2],
width: 32
},
chatLink: {
color: ColorPalette.blue
color: BaseTheme.palette.link01
},
chatMessage: {
...BaseTheme.typography.bodyShortRegular,
color: BaseTheme.palette.text01
},
/**
@ -61,7 +125,7 @@ export default {
},
customInput: {
width: 280
width: 272
},
messageBubble: {
@ -117,7 +181,7 @@ export default {
* Text node for the timestamp.
*/
timeText: {
color: 'rgb(164, 184, 209)',
color: BaseTheme.palette.text03,
fontSize: 13
},
@ -154,97 +218,35 @@ export default {
width: 250,
height: undefined,
flexGrow: 1
}
};
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
},
emptyComponentText: {
color: BaseTheme.palette.text03,
textAlign: 'center'
},
lobbyMessageBubble: {
backgroundColor: schemeColor('lobbyMsgBackground')
},
lobbyMsgNotice: {
color: schemeColor('lobbyMsgNotice'),
fontSize: 11,
marginTop: 6
},
lobbyMessageRecipientContainer: {
alignItems: 'center',
backgroundColor: schemeColor('lobbyMsgBackground'),
flexDirection: 'row',
padding: BoxModel.padding
senderDisplayName: {
...BaseTheme.typography.bodyShortBold,
color: BaseTheme.palette.text02
},
localMessageBubble: {
backgroundColor: schemeColor('localMsgBackground'),
backgroundColor: BaseTheme.palette.ui04,
borderTopRightRadius: 0
},
lobbyMessageRecipientContainer: {
...recipientContainer,
backgroundColor: BaseTheme.palette.support06
},
messageRecipientCancelIcon: {
color: schemeColor('icon'),
color: BaseTheme.palette.icon01,
fontSize: 18
},
messageRecipientContainer: {
alignItems: 'center',
backgroundColor: schemeColor('privateMsgBackground'),
flexDirection: 'row',
padding: BoxModel.padding
...recipientContainer
},
messageRecipientText: {
color: schemeColor('text'),
...BaseTheme.typography.bodyShortRegular,
color: BaseTheme.palette.text01,
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

@ -1,5 +1,3 @@
// @flow
import clsx from 'clsx';
import React from 'react';

View File

@ -0,0 +1,41 @@
// @ts-ignore
import React from 'react';
import { StyleProp, Text, TextStyle, View } from 'react-native';
// @ts-ignore
import { navigationStyles } from './styles';
interface ITabBarLabelCounterProps {
activeUnreadNr: boolean;
isFocused: boolean;
label: string;
nbUnread?: number;
}
export const TabBarLabelCounter = ({ activeUnreadNr, isFocused, label, nbUnread }: ITabBarLabelCounterProps) => {
const labelStyles = isFocused
? navigationStyles.unreadCounterDescriptionFocused
: navigationStyles.unreadCounterDescription;
return (
<View
style = {
navigationStyles.unreadCounterContainer as StyleProp<TextStyle> }>
<Text
style = { labelStyles }>
{ label && label }
</Text>
{
activeUnreadNr && (
<View
style = { navigationStyles.unreadCounterCircle as StyleProp<TextStyle> }>
<Text
style = { navigationStyles.unreadCounter as StyleProp<TextStyle> }>
{ nbUnread }
</Text>
</View>
)
}
</View>
);
};

View File

@ -1,9 +1,11 @@
/* eslint-disable lines-around-comment */
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
// @ts-ignore
import React from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../../../app/types';
import {
getClientHeight,
getClientWidth
@ -11,6 +13,8 @@ import {
} from '../../../../../base/modal/components/functions';
// @ts-ignore
import { Chat } from '../../../../../chat';
import { setIsPollsTabFocused } from '../../../../../chat/actions.native';
import { resetNbUnreadPollsMessages } from '../../../../../polls/actions';
// @ts-ignore
import { PollsPane } from '../../../../../polls/components';
// @ts-ignore
@ -23,6 +27,11 @@ const ChatTab = createMaterialTopTabNavigator();
const ChatAndPolls = () => {
const clientHeight = useSelector(getClientHeight);
const clientWidth = useSelector(getClientWidth);
const dispatch = useDispatch();
const { isPollsTabFocused } = useSelector((state: IReduxState) => state['features/chat']);
const initialRouteName = isPollsTabFocused
? screen.conference.chatandpolls.tab.polls
: screen.conference.chatandpolls.tab.chat;
return (
// @ts-ignore
@ -32,12 +41,24 @@ const ChatAndPolls = () => {
height: clientHeight,
width: clientWidth
}}
initialRouteName = { initialRouteName }
screenOptions = { chatTabBarOptions }>
<ChatTab.Screen
component = { Chat }
listeners = {{
tabPress: () => {
dispatch(setIsPollsTabFocused(false));
}
}}
name = { screen.conference.chatandpolls.tab.chat } />
<ChatTab.Screen
component = { PollsPane }
listeners = {{
tabPress: () => {
dispatch(setIsPollsTabFocused(true));
dispatch(resetNbUnreadPollsMessages);
}
}}
name = { screen.conference.chatandpolls.tab.polls } />
</ChatTab.Navigator>
);

View File

@ -1,9 +1,14 @@
import { BoxModel } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export const TEXT_COLOR = BaseTheme.palette.text01;
const unreadCounterDescription = {
...BaseTheme.typography.bodyShortBoldLarge,
color: BaseTheme.palette.text03
};
/**
* Styles of the navigation feature.
*/
@ -27,5 +32,35 @@ export const navigationStyles = {
connectingScreenText: {
color: TEXT_COLOR
},
unreadCounterContainer: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row'
},
unreadCounterDescription: {
...unreadCounterDescription
},
unreadCounterDescriptionFocused: {
...unreadCounterDescription,
color: BaseTheme.palette.text01
},
unreadCounterCircle: {
backgroundColor: BaseTheme.palette.warning01,
borderRadius: BaseTheme.spacing[4] / 2,
height: BaseTheme.spacing[4],
justifyContent: 'center',
marginLeft: BaseTheme.spacing[2],
width: BaseTheme.spacing[4]
},
unreadCounter: {
...BaseTheme.typography.bodyShortBold,
alignSelf: 'center',
color: BaseTheme.palette.text04
}
};

View File

@ -67,19 +67,14 @@ export const conferenceScreenOptions = fullScreenOptions;
* Tab bar options for chat screen.
*/
export const chatTabBarOptions = {
tabBarActiveTintColor: BaseTheme.palette.field02,
tabBarLabelStyle: {
fontSize: BaseTheme.typography.labelRegular.fontSize,
textTransform: 'capitalize'
},
tabBarInactiveTintColor: BaseTheme.palette.text03,
swipeEnabled: false,
tabBarIndicatorStyle: {
backgroundColor: BaseTheme.palette.field02
backgroundColor: BaseTheme.palette.link01Active
},
tabBarStyle: {
backgroundColor: BaseTheme.palette.ui01,
borderBottomColor: BaseTheme.palette.border05,
borderBottomWidth: 1
borderBottomWidth: 0.4
}
};

View File

@ -4,7 +4,7 @@ import { FlatList, View } from 'react-native';
import { Text } from 'react-native-paper';
import { useSelector } from 'react-redux';
import { Icon, IconChatUnread } from '../../../base/icons';
import { Icon, IconMessage } from '../../../base/icons';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
@ -41,7 +41,7 @@ const PollsList = () => {
<Icon
color = { BaseTheme.palette.icon03 }
size = { 160 }
src = { IconChatUnread } />
src = { IconMessage } />
<Text style = { chatStyles.noPollText } >
{
t('polls.results.empty')

View File

@ -1,14 +1,14 @@
/* eslint-disable react-native/no-color-literals */
// @flow
import { useIsFocused, useNavigation } from '@react-navigation/native';
import { useNavigation } from '@react-navigation/native';
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { getUnreadPollCount } from '../../functions';
import { TabBarLabelCounter }
from '../../../mobile/navigation/components/TabBarLabelCounter';
import AbstractPollsPane from '../AbstractPollsPane';
import type { AbstractProps } from '../AbstractPollsPane';
@ -19,19 +19,25 @@ import { chatStyles } from './styles';
const PollsPane = (props: AbstractProps) => {
const { createMode, onCreate, setCreateMode, t } = props;
const isPollsScreenFocused = useIsFocused();
const navigation = useNavigation();
const nbUnreadPolls = useSelector(getUnreadPollCount);
const nrUnreadPolls = !isPollsScreenFocused && nbUnreadPolls > 0
? `(${nbUnreadPolls})`
: '';
const { isPollsTabFocused } = useSelector(state => state['features/chat']);
const { nbUnreadPolls } = useSelector(state => state['features/polls']);
useEffect(() => {
const activeUnreadPollsNr = !isPollsTabFocused && nbUnreadPolls > 0;
navigation.setOptions({
tabBarLabel: `${t('chat.tabs.polls')} ${nrUnreadPolls}`
// eslint-disable-next-line react/no-multi-comp
tabBarLabel: () => (
<TabBarLabelCounter
activeUnreadNr = { activeUnreadPollsNr }
isFocused = { isPollsTabFocused }
label = { t('chat.tabs.polls') }
nbUnread = { nbUnreadPolls } />
)
});
}, [ nrUnreadPolls ]);
}, [ isPollsTabFocused, nbUnreadPolls ]);
return (
<JitsiScreen

View File

@ -110,10 +110,12 @@ export const chatStyles = createStyleSheet({
noPollContent: {
alignItems: 'center',
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
paddingTop: '4%'
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
top: '25%'
},
noPollText: {
@ -149,7 +151,7 @@ export const chatStyles = createStyleSheet({
pollCreateButton: {
flex: 1,
marginHorizontal: BaseTheme.spacing[2]
marginHorizontal: BaseTheme.spacing[1]
},
pollSendLabel: {
@ -191,7 +193,8 @@ export const chatStyles = createStyleSheet({
},
pollCreateAddButton: {
margin: BaseTheme.spacing[2]
marginHorizontal: BaseTheme.spacing[1],
marginVertical: BaseTheme.spacing[2]
},
toggleText: {
@ -200,8 +203,8 @@ export const chatStyles = createStyleSheet({
},
createPollButton: {
marginHorizontal: BaseTheme.spacing[4],
marginVertical: '8%'
marginHorizontal: BaseTheme.spacing[3],
marginVertical: 34
},
pollPane: {
@ -218,5 +221,28 @@ export const chatStyles = createStyleSheet({
flexDirection: 'row',
justifyContent: 'space-between',
marginHorizontal: BaseTheme.spacing[2]
},
unreadPollsCounterContainer: {
display: 'flex',
flexDirection: 'row'
},
unreadPollsCounterDescription: {
color: BaseTheme.palette.text01
},
unreadPollsCounterCircle: {
backgroundColor: BaseTheme.palette.warning01,
borderRadius: BaseTheme.spacing[3] / 2,
height: BaseTheme.spacing[3],
justifyContent: 'center',
marginLeft: BaseTheme.spacing[2],
width: BaseTheme.spacing[3]
},
unreadPollsCounter: {
alignSelf: 'center',
color: BaseTheme.palette.text04
}
});

View File

@ -1,4 +1,5 @@
/* eslint-disable lines-around-comment */
// @ts-ignore
import React from 'react';
import Button from '../../../base/ui/components/web/Button';