[NEW] Redesign reactions list (#4346)
This commit is contained in:
parent
7aa94e0a9e
commit
28bb6a045d
|
@ -140,6 +140,8 @@ const ActionSheet = React.memo(
|
||||||
style={{ ...styles.container, ...bottomSheet }}
|
style={{ ...styles.container, ...bottomSheet }}
|
||||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||||
onChange={index => index === -1 && onClose()}
|
onChange={index => index === -1 && onClose()}
|
||||||
|
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker
|
||||||
|
enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
|
||||||
{...androidTablet}>
|
{...androidTablet}>
|
||||||
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
|
|
|
@ -22,6 +22,7 @@ export type TActionSheetOptions = {
|
||||||
children?: React.ReactElement | null;
|
children?: React.ReactElement | null;
|
||||||
snaps?: (string | number)[];
|
snaps?: (string | number)[];
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
enableContentPanningGesture?: boolean;
|
||||||
};
|
};
|
||||||
export interface IActionSheetProvider {
|
export interface IActionSheetProvider {
|
||||||
showActionSheet: (item: TActionSheetOptions) => void;
|
showActionSheet: (item: TActionSheetOptions) => void;
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, Pressable, View, ScrollView } from 'react-native';
|
||||||
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
|
import Emoji from './message/Emoji';
|
||||||
|
import { useTheme } from '../theme';
|
||||||
|
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
||||||
|
import { IReaction } from '../definitions';
|
||||||
|
import Avatar from './Avatar';
|
||||||
|
import sharedStyles from '../views/Styles';
|
||||||
|
|
||||||
|
const MIN_TAB_WIDTH = 70;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
reactionsListContainer: { height: '100%', width: '100%' },
|
||||||
|
tabBarItem: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingBottom: 10,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
reactionCount: { marginLeft: 5 },
|
||||||
|
emojiName: { margin: 10 },
|
||||||
|
userItemContainer: { marginHorizontal: 10, marginVertical: 5, flexDirection: 'row' },
|
||||||
|
usernameContainer: { marginHorizontal: 10, justifyContent: 'center' },
|
||||||
|
usernameText: { fontSize: 17, ...sharedStyles.textMedium },
|
||||||
|
standardEmojiStyle: { fontSize: 20, color: '#fff' },
|
||||||
|
customEmojiStyle: { width: 25, height: 25 }
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IReactionsListBase {
|
||||||
|
baseUrl: string;
|
||||||
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReactionsListProps extends IReactionsListBase {
|
||||||
|
reactions?: IReaction[];
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITabBarItem extends IReactionsListBase {
|
||||||
|
tab: IReaction;
|
||||||
|
index: number;
|
||||||
|
goToPage?: (index: number) => void;
|
||||||
|
}
|
||||||
|
interface IReactionsTabBar extends IReactionsListBase {
|
||||||
|
activeTab?: number;
|
||||||
|
tabs?: IReaction[];
|
||||||
|
goToPage?: (index: number) => void;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabBarItem = ({ tab, index, goToPage, baseUrl, getCustomEmoji }: ITabBarItem) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
key={tab.emoji}
|
||||||
|
onPress={() => {
|
||||||
|
goToPage?.(index);
|
||||||
|
}}
|
||||||
|
style={({ pressed }: { pressed: boolean }) => ({
|
||||||
|
opacity: pressed ? 0.7 : 1
|
||||||
|
})}>
|
||||||
|
<View style={styles.tabBarItem}>
|
||||||
|
<Emoji
|
||||||
|
content={tab.emoji}
|
||||||
|
standardEmojiStyle={styles.standardEmojiStyle}
|
||||||
|
customEmojiStyle={styles.customEmojiStyle}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
/>
|
||||||
|
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReactionsTabBar = ({ tabs, activeTab, goToPage, baseUrl, getCustomEmoji, width }: IReactionsTabBar) => {
|
||||||
|
const tabWidth = tabs && Math.max(width / tabs.length, MIN_TAB_WIDTH);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
|
||||||
|
{tabs?.map((tab, index) => {
|
||||||
|
const isActiveTab = activeTab === index;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: tabWidth,
|
||||||
|
borderBottomWidth: isActiveTab ? 2 : 1,
|
||||||
|
borderColor: isActiveTab ? colors.tintActive : colors.separatorColor
|
||||||
|
}}>
|
||||||
|
<TabBarItem tab={tab} index={index} goToPage={goToPage} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UsersList = ({ tabLabel }: { tabLabel: IReaction }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const { emoji, usernames } = tabLabel;
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={usernames}
|
||||||
|
ListHeaderComponent={() => (
|
||||||
|
<View style={styles.emojiName}>
|
||||||
|
<Text style={{ color: colors.auxiliaryTintColor }}>{emoji}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View style={styles.userItemContainer}>
|
||||||
|
<Avatar text={item} size={36} />
|
||||||
|
<View style={styles.usernameContainer}>
|
||||||
|
<Text style={[styles.usernameText, { color: colors.titleText }]}>{item}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
keyExtractor={item => item}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReactionsList = ({ reactions, baseUrl, getCustomEmoji, width }: IReactionsListProps): React.ReactElement => {
|
||||||
|
// sorting reactions in descending order on the basic of number of users reacted
|
||||||
|
const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.reactionsListContainer}>
|
||||||
|
<ScrollableTabView renderTabBar={() => <ReactionsTabBar baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} width={width} />}>
|
||||||
|
{sortedReactions?.map(reaction => (
|
||||||
|
<UsersList tabLabel={reaction} key={reaction.emoji} />
|
||||||
|
))}
|
||||||
|
</ScrollableTabView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReactionsList;
|
|
@ -1,166 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
|
||||||
import Modal from 'react-native-modal';
|
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
|
|
||||||
import Emoji from './message/Emoji';
|
|
||||||
import I18n from '../i18n';
|
|
||||||
import { CustomIcon } from './CustomIcon';
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
import { themes } from '../lib/constants';
|
|
||||||
import { TSupportedThemes, useTheme, withTheme } from '../theme';
|
|
||||||
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
|
||||||
import { TMessageModel, ILoggedUser } from '../definitions';
|
|
||||||
import SafeAreaView from './SafeAreaView';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
safeArea: {
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 10
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 16,
|
|
||||||
...sharedStyles.textSemibold,
|
|
||||||
...sharedStyles.textAlignCenter
|
|
||||||
},
|
|
||||||
reactCount: {
|
|
||||||
fontSize: 13,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
peopleReacted: {
|
|
||||||
fontSize: 14,
|
|
||||||
...sharedStyles.textMedium
|
|
||||||
},
|
|
||||||
peopleItemContainer: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
emojiContainer: {
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
itemContainer: {
|
|
||||||
height: 50,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
listContainer: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
closeButton: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
top: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const standardEmojiStyle = { fontSize: 20 };
|
|
||||||
const customEmojiStyle = { width: 20, height: 20 };
|
|
||||||
|
|
||||||
interface ISharedFields {
|
|
||||||
user?: Pick<ILoggedUser, 'username'>;
|
|
||||||
baseUrl: string;
|
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IItem extends ISharedFields {
|
|
||||||
item: {
|
|
||||||
usernames: string[];
|
|
||||||
emoji: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IModalContent extends ISharedFields {
|
|
||||||
message?: TMessageModel;
|
|
||||||
onClose: () => void;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IReactionsModal extends ISharedFields {
|
|
||||||
message?: TMessageModel;
|
|
||||||
isVisible: boolean;
|
|
||||||
onClose(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji }: IItem) => {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const count = item.usernames.length;
|
|
||||||
let usernames = item.usernames
|
|
||||||
.slice(0, 3)
|
|
||||||
.map((username: string) => (username === user?.username ? I18n.t('you') : username))
|
|
||||||
.join(', ');
|
|
||||||
if (count > 3) {
|
|
||||||
usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`;
|
|
||||||
} else {
|
|
||||||
usernames = usernames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.itemContainer}>
|
|
||||||
<View style={styles.emojiContainer}>
|
|
||||||
<Emoji
|
|
||||||
content={item.emoji}
|
|
||||||
standardEmojiStyle={standardEmojiStyle}
|
|
||||||
customEmojiStyle={customEmojiStyle}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.peopleItemContainer}>
|
|
||||||
<Text style={[styles.reactCount, { color: themes[theme].buttonText }]}>
|
|
||||||
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
|
||||||
</Text>
|
|
||||||
<Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{usernames}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent) => {
|
|
||||||
if (message && message.reactions) {
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={styles.safeArea}>
|
|
||||||
<Touchable onPress={onClose}>
|
|
||||||
<View style={styles.titleContainer}>
|
|
||||||
<CustomIcon name='close' size={20} color={themes[props.theme].buttonText} style={styles.closeButton} />
|
|
||||||
<Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text>
|
|
||||||
</View>
|
|
||||||
</Touchable>
|
|
||||||
<FlatList
|
|
||||||
style={styles.listContainer}
|
|
||||||
data={message.reactions}
|
|
||||||
renderItem={({ item }) => <Item item={item} {...props} />}
|
|
||||||
keyExtractor={item => item.emoji}
|
|
||||||
/>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const ReactionsModal = React.memo(
|
|
||||||
({ isVisible, onClose, ...props }: IReactionsModal) => {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isVisible={isVisible}
|
|
||||||
onBackdropPress={onClose}
|
|
||||||
onBackButtonPress={onClose}
|
|
||||||
backdropOpacity={0.8}
|
|
||||||
onSwipeComplete={onClose}
|
|
||||||
swipeDirection={['up', 'left', 'right', 'down']}>
|
|
||||||
<ModalContent onClose={onClose} theme={theme} {...props} />
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible
|
|
||||||
);
|
|
||||||
|
|
||||||
ReactionsModal.displayName = 'ReactionsModal';
|
|
||||||
ModalContent.displayName = 'ReactionsModalContent';
|
|
||||||
Item.displayName = 'ReactionsModalItem';
|
|
||||||
|
|
||||||
export default withTheme(ReactionsModal);
|
|
|
@ -28,6 +28,7 @@ export * from './ICredentials';
|
||||||
export * from './ISearch';
|
export * from './ISearch';
|
||||||
export * from './TUserStatus';
|
export * from './TUserStatus';
|
||||||
export * from './IProfile';
|
export * from './IProfile';
|
||||||
|
export * from './IReaction';
|
||||||
|
|
||||||
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
||||||
navigation: StackNavigationProp<T, S>;
|
navigation: StackNavigationProp<T, S>;
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { InteractionManager, Text, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import parse from 'url-parse';
|
import parse from 'url-parse';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Haptics from 'expo-haptics';
|
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
@ -22,7 +21,7 @@ import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import RoomHeader from '../../containers/RoomHeader';
|
import RoomHeader from '../../containers/RoomHeader';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import ReactionsModal from '../../containers/ReactionsModal';
|
import ReactionsList from '../../containers/ReactionsList';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import { getBadgeColor, isBlocked, makeThreadName } from '../../lib/methods/helpers/room';
|
import { getBadgeColor, isBlocked, makeThreadName } from '../../lib/methods/helpers/room';
|
||||||
import { isReadOnly } from '../../lib/methods/helpers/isReadOnly';
|
import { isReadOnly } from '../../lib/methods/helpers/isReadOnly';
|
||||||
|
@ -100,6 +99,7 @@ import {
|
||||||
hasPermission
|
hasPermission
|
||||||
} from '../../lib/methods/helpers';
|
} from '../../lib/methods/helpers';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
|
import { withActionSheet, TActionSheetOptions } from '../../containers/ActionSheet';
|
||||||
|
|
||||||
type TStateAttrsUpdate = keyof IRoomViewState;
|
type TStateAttrsUpdate = keyof IRoomViewState;
|
||||||
|
|
||||||
|
@ -170,6 +170,7 @@ interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> {
|
||||||
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
|
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
|
||||||
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
|
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
|
||||||
livechatAllowManualOnHold?: boolean;
|
livechatAllowManualOnHold?: boolean;
|
||||||
|
showActionSheet: (options: TActionSheetOptions) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoomViewState {
|
interface IRoomViewState {
|
||||||
|
@ -853,12 +854,21 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onReactionLongPress = (message: TAnyMessageModel) => {
|
onReactionLongPress = (message: TAnyMessageModel) => {
|
||||||
this.setState({ selectedMessage: message, reactionsModalVisible: true });
|
this.setState({ selectedMessage: message });
|
||||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
const { showActionSheet, baseUrl, width } = this.props;
|
||||||
};
|
const { selectedMessage } = this.state;
|
||||||
|
showActionSheet({
|
||||||
onCloseReactionsModal = () => {
|
children: (
|
||||||
this.setState({ selectedMessage: undefined, reactionsModalVisible: false });
|
<ReactionsList
|
||||||
|
reactions={selectedMessage?.reactions}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
snaps: ['50%'],
|
||||||
|
enableContentPanningGesture: false
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onEncryptedPress = () => {
|
onEncryptedPress = () => {
|
||||||
|
@ -1469,7 +1479,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.count(`${this.constructor.name}.render calls`);
|
console.count(`${this.constructor.name}.render calls`);
|
||||||
const { room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader } = this.state;
|
const { room, selectedMessage, loading, reacting, showingBlockingLoader } = this.state;
|
||||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
let sysMes;
|
let sysMes;
|
||||||
|
@ -1512,14 +1522,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
|
<UploadProgress rid={rid} user={user} baseUrl={baseUrl} width={width} />
|
||||||
<ReactionsModal
|
|
||||||
message={selectedMessage}
|
|
||||||
isVisible={reactionsModalVisible}
|
|
||||||
user={user}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
onClose={this.onCloseReactionsModal}
|
|
||||||
getCustomEmoji={this.getCustomEmoji}
|
|
||||||
/>
|
|
||||||
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
|
<JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} />
|
||||||
<Loading visible={showingBlockingLoader} />
|
<Loading visible={showingBlockingLoader} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
@ -1545,4 +1547,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean
|
livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomView))));
|
export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(withActionSheet(RoomView)))));
|
||||||
|
|
Loading…
Reference in New Issue