[IMPROVE] Add `All` tab in Reactions List (#4409)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
43ebae9b19
commit
2f03ca52c5
|
@ -6,11 +6,13 @@ import RNBootSplash from 'react-native-bootsplash';
|
|||
import { selectServerRequest } from '../app/actions/server';
|
||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||
import database from '../app/lib/database';
|
||||
import { setUser } from '../app/actions/login';
|
||||
|
||||
RNBootSplash.hide();
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
store.dispatch(selectServerRequest(baseUrl));
|
||||
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||
database.setActiveDB(baseUrl);
|
||||
|
||||
const StorybookUIRoot = getStorybookUI({});
|
||||
|
|
|
@ -30,6 +30,7 @@ const getStories = () => {
|
|||
require("../app/containers/markdown/new/NewMarkdown.stories.tsx"),
|
||||
require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"),
|
||||
require("../app/containers/message/Message.stories.tsx"),
|
||||
require("../app/containers/ReactionsList/ReactionsList.stories.tsx"),
|
||||
require("../app/containers/RoomHeader/RoomHeader.stories.tsx"),
|
||||
require("../app/containers/RoomItem/RoomItem.stories.tsx"),
|
||||
require("../app/containers/SearchBox/SearchBox.stories.tsx"),
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,145 +0,0 @@
|
|||
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;
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react';
|
||||
import { Text, View, FlatList } from 'react-native';
|
||||
|
||||
import Emoji from '../message/Emoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction } from '../../definitions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
interface IAllReactionsListItemProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
item: IReaction;
|
||||
}
|
||||
|
||||
interface IAllTabProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
tabLabel: IReaction;
|
||||
reactions?: IReaction[];
|
||||
}
|
||||
|
||||
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
|
||||
const { colors } = useTheme();
|
||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const server = useAppSelector(state => state.server.server);
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
const count = item.usernames.length;
|
||||
|
||||
let displayNames;
|
||||
if (useRealName && item.names) {
|
||||
displayNames = item.names
|
||||
.slice(0, 3)
|
||||
.map((name, index) => (item.usernames[index] === username ? I18n.t('you') : name))
|
||||
.join(', ');
|
||||
} else {
|
||||
displayNames = item.usernames
|
||||
.slice(0, 3)
|
||||
.map((otherUsername: string) => (username === otherUsername ? I18n.t('you') : otherUsername))
|
||||
.join(', ');
|
||||
}
|
||||
if (count > 3) {
|
||||
displayNames = `${displayNames} ${I18n.t('and_more')} ${count - 3}`;
|
||||
} else {
|
||||
displayNames = displayNames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
|
||||
}
|
||||
return (
|
||||
<View style={styles.listItemContainer}>
|
||||
<Emoji
|
||||
content={item.emoji}
|
||||
standardEmojiStyle={styles.allTabStandardEmojiStyle}
|
||||
customEmojiStyle={styles.allTabCustomEmojiStyle}
|
||||
baseUrl={server}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.allListNPeopleReacted, { color: colors.bodyText }]}>
|
||||
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
||||
</Text>
|
||||
<Text style={[styles.allListWhoReacted, { color: colors.auxiliaryText }]}>{displayNames}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const AllTab = ({ reactions, getCustomEmoji }: IAllTabProps): React.ReactElement => (
|
||||
<View style={styles.allTabContainer} testID='reactionsListAllTab'>
|
||||
<FlatList
|
||||
data={reactions}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
renderItem={({ item }) => <AllReactionsListItem item={item} getCustomEmoji={getCustomEmoji} />}
|
||||
keyExtractor={item => item.emoji}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default AllTab;
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { TGetCustomEmoji, IEmoji } from '../../definitions';
|
||||
import ReactionsList from '.';
|
||||
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||
import { updateSettings } from '../../actions/settings';
|
||||
|
||||
const getCustomEmoji: TGetCustomEmoji = content => {
|
||||
const customEmoji = {
|
||||
marioparty: { name: content, extension: 'gif' },
|
||||
react_rocket: { name: content, extension: 'png' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content] as IEmoji;
|
||||
return customEmoji;
|
||||
};
|
||||
|
||||
const reactions = [
|
||||
{
|
||||
emoji: ':marioparty:',
|
||||
_id: 'marioparty',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':react_rocket:',
|
||||
_id: 'react_rocket',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':nyan_rocket:',
|
||||
_id: 'nyan_rocket',
|
||||
usernames: ['rocket.cat'],
|
||||
names: ['Rocket Cat']
|
||||
},
|
||||
{
|
||||
emoji: ':grinning:',
|
||||
_id: 'grinning',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':tada:',
|
||||
_id: 'tada',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
}
|
||||
];
|
||||
|
||||
export const ReactionsListStory = () => {
|
||||
store.dispatch(updateSettings('UI_Use_Real_Name', false));
|
||||
return (
|
||||
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReactionsListFullName = () => {
|
||||
store.dispatch(updateSettings('UI_Use_Real_Name', true));
|
||||
return (
|
||||
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'ReactionsList'
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render, within } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import ReactionsList from '.';
|
||||
import { mockedStore } from '../../reducers/mockedStore';
|
||||
|
||||
const getCustomEmoji = jest.fn();
|
||||
const reactions = [
|
||||
{
|
||||
emoji: 'marioparty',
|
||||
_id: 'marioparty',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: 'react_rocket',
|
||||
_id: 'react_rocket',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: 'nyan_rocket',
|
||||
_id: 'nyan_rocket',
|
||||
usernames: ['rocket.cat'],
|
||||
names: ['Rocket Cat']
|
||||
},
|
||||
{
|
||||
emoji: 'grinning',
|
||||
_id: 'grinning',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
}
|
||||
];
|
||||
|
||||
const Render = () => (
|
||||
<Provider store={mockedStore}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
describe('ReactionsList', () => {
|
||||
test('should render Reactions List', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const ReactionsListView = await findByTestId('reactionsList');
|
||||
expect(ReactionsListView).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render tab bar', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const AllTab = await findByTestId('reactionsTabBar');
|
||||
expect(AllTab).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render All tab', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const AllTab = await findByTestId('reactionsListAllTab');
|
||||
expect(AllTab).toBeTruthy();
|
||||
});
|
||||
|
||||
test('correct tab on clicking tab item', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||
fireEvent.press(tab);
|
||||
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||
expect(usersList).toBeTruthy();
|
||||
const emojiName = await within(usersList).getByTestId(`usersListEmojiName`);
|
||||
expect(emojiName.props.children).toEqual(reactions[0].emoji);
|
||||
});
|
||||
|
||||
test('should render correct number of reactions', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||
fireEvent.press(tab);
|
||||
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||
const allReactions = await within(usersList).getAllByTestId('userItem');
|
||||
expect(allReactions).toHaveLength(reactions[0].usernames.length);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { Text, Pressable, View, ScrollView } from 'react-native';
|
||||
|
||||
import Emoji from '../message/Emoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction } from '../../definitions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import I18n from '../../i18n';
|
||||
import styles, { MIN_TAB_WIDTH } from './styles';
|
||||
import { useDimensions, useOrientation } from '../../dimensions';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
interface ITabBarItem {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
tab: IReaction;
|
||||
index: number;
|
||||
goToPage?: (index: number) => void;
|
||||
}
|
||||
interface IReactionsTabBar {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
activeTab?: number;
|
||||
tabs?: IReaction[];
|
||||
goToPage?: (index: number) => void;
|
||||
}
|
||||
|
||||
const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
|
||||
const { colors } = useTheme();
|
||||
const server = useAppSelector(state => state.server.server);
|
||||
return (
|
||||
<Pressable
|
||||
key={tab.emoji}
|
||||
onPress={() => {
|
||||
goToPage?.(index);
|
||||
}}
|
||||
style={({ pressed }: { pressed: boolean }) => ({
|
||||
opacity: pressed ? 0.7 : 1
|
||||
})}
|
||||
testID={`tabBarItem-${tab.emoji}`}
|
||||
>
|
||||
<View style={styles.tabBarItem}>
|
||||
{tab._id === 'All' ? (
|
||||
<Text style={[styles.allTabItem, { color: colors.auxiliaryTintColor }]}>{I18n.t('All')}</Text>
|
||||
) : (
|
||||
<>
|
||||
<Emoji
|
||||
content={tab.emoji}
|
||||
standardEmojiStyle={styles.standardEmojiStyle}
|
||||
customEmojiStyle={styles.customEmojiStyle}
|
||||
baseUrl={server}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const ReactionsTabBar = ({ tabs, activeTab, goToPage, getCustomEmoji }: IReactionsTabBar): React.ReactElement => {
|
||||
const { isLandscape } = useOrientation();
|
||||
const { width } = useDimensions();
|
||||
const reactionsListWidth = isLandscape ? width / 2 : width;
|
||||
const tabWidth = tabs && Math.max(reactionsListWidth / tabs.length, MIN_TAB_WIDTH);
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View testID='reactionsTabBar'>
|
||||
<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
|
||||
}}
|
||||
key={tab.emoji}
|
||||
>
|
||||
<TabBarItem tab={tab} index={index} goToPage={goToPage} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionsTabBar;
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { Text, View, FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction, IApplicationState } from '../../definitions';
|
||||
import Avatar from '../Avatar';
|
||||
import styles from './styles';
|
||||
|
||||
const UsersList = ({ tabLabel }: { tabLabel: IReaction }): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const useRealName = useSelector((state: IApplicationState) => state.settings.UI_Use_Real_Name);
|
||||
|
||||
const { emoji, usernames, names } = tabLabel;
|
||||
const users =
|
||||
names?.length > 0
|
||||
? usernames.map((username, index) => ({ username, name: names[index] }))
|
||||
: usernames.map(username => ({ username, name: '' }));
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
ListHeaderComponent={
|
||||
<View style={styles.emojiNameContainer}>
|
||||
<Text style={[styles.emojiName, { color: colors.auxiliaryText }]} testID='usersListEmojiName'>
|
||||
{emoji}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.listItemContainer} testID='userItem'>
|
||||
<Avatar text={item.username} size={36} />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.usernameText, { color: colors.bodyText }]} numberOfLines={1}>
|
||||
{useRealName && item.name ? item.name : item.username}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => item.username}
|
||||
testID={`usersList-${emoji}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersList;
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IReaction } from '../../definitions';
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
import AllTab from './AllTab';
|
||||
import UsersList from './UsersList';
|
||||
import ReactionsTabBar from './ReactionsTabBar';
|
||||
|
||||
interface IReactionsListProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
reactions?: IReaction[];
|
||||
}
|
||||
|
||||
const ReactionsList = ({ reactions, getCustomEmoji }: 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);
|
||||
const allTabLabel = { emoji: I18n.t('All'), usernames: [], names: [], _id: 'All' };
|
||||
return (
|
||||
<View style={styles.container} testID='reactionsList'>
|
||||
<ScrollableTabView renderTabBar={() => <ReactionsTabBar getCustomEmoji={getCustomEmoji} />}>
|
||||
<AllTab tabLabel={allTabLabel} reactions={sortedReactions} getCustomEmoji={getCustomEmoji} />
|
||||
{sortedReactions?.map(reaction => (
|
||||
<UsersList tabLabel={reaction} key={reaction.emoji} />
|
||||
))}
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionsList;
|
|
@ -0,0 +1,88 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const MIN_TAB_WIDTH = 70;
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
allTabContainer: {
|
||||
flex: 1
|
||||
},
|
||||
tabBarItem: {
|
||||
paddingBottom: 4,
|
||||
height: 44,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
listContainer: {
|
||||
marginHorizontal: 12,
|
||||
marginVertical: 8,
|
||||
paddingBottom: 30
|
||||
},
|
||||
reactionCount: {
|
||||
marginLeft: 4,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
emojiNameContainer: {
|
||||
marginVertical: 8
|
||||
},
|
||||
emojiName: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
listItemContainer: {
|
||||
marginVertical: 6,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 8,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
usernameText: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
standardEmojiStyle: {
|
||||
fontSize: 20,
|
||||
width: 24,
|
||||
height: 24,
|
||||
textAlign: 'center',
|
||||
color: '#fff'
|
||||
},
|
||||
customEmojiStyle: {
|
||||
width: 24,
|
||||
height: 24
|
||||
},
|
||||
allTabItem: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
allTabStandardEmojiStyle: {
|
||||
fontSize: 30,
|
||||
width: 36,
|
||||
textAlign: 'center',
|
||||
color: '#fff'
|
||||
},
|
||||
allTabCustomEmojiStyle: {
|
||||
width: 36,
|
||||
height: 36
|
||||
},
|
||||
allListItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
allListNPeopleReacted: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
allListWhoReacted: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
|
@ -114,7 +114,16 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
);
|
||||
}
|
||||
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} messageId={id} />;
|
||||
return (
|
||||
<Reply
|
||||
key={index}
|
||||
index={index}
|
||||
attachment={file}
|
||||
timeFormat={timeFormat}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
messageId={id}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <>{attachmentsElements}</>;
|
||||
},
|
||||
|
|
|
@ -2,4 +2,5 @@ export interface IReaction {
|
|||
_id: string;
|
||||
emoji: string;
|
||||
usernames: string[];
|
||||
names: string[];
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ export default (msg: any): IMessage | IThreadResult | null => {
|
|||
msg.reactions = Object.keys(msg.reactions).map(key => ({
|
||||
_id: `${msg._id}${key}`,
|
||||
emoji: key,
|
||||
usernames: msg.reactions ? msg.reactions[key].usernames : []
|
||||
usernames: msg.reactions ? msg.reactions[key].usernames : [],
|
||||
names: msg.reactions ? msg.reactions[key].names : []
|
||||
}));
|
||||
}
|
||||
if (msg.translations && Object.keys(msg.translations).length) {
|
||||
|
|
|
@ -862,18 +862,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
|
||||
onReactionLongPress = (message: TAnyMessageModel) => {
|
||||
this.setState({ selectedMessage: message });
|
||||
const { showActionSheet, baseUrl, width } = this.props;
|
||||
const { showActionSheet } = this.props;
|
||||
const { selectedMessage } = this.state;
|
||||
this.messagebox?.current?.closeEmojiAndAction(showActionSheet, {
|
||||
children: (
|
||||
<ReactionsList
|
||||
reactions={selectedMessage?.reactions}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={this.getCustomEmoji}
|
||||
width={width}
|
||||
/>
|
||||
),
|
||||
snaps: ['50%'],
|
||||
children: <ReactionsList reactions={selectedMessage?.reactions} getCustomEmoji={this.getCustomEmoji} />,
|
||||
snaps: ['50%', '80%'],
|
||||
enableContentPanningGesture: false
|
||||
});
|
||||
};
|
||||
|
|
|
@ -306,7 +306,7 @@ describe('Room screen', () => {
|
|||
.withTimeout(60000);
|
||||
});
|
||||
|
||||
it('should show reaction picker on add reaction button pressed and have frequently used emoji, and dismiss review nag', async () => {
|
||||
it('should show reaction picker on add reaction button pressed and have frequently used emoji', async () => {
|
||||
await element(by.id('message-add-reaction')).tap();
|
||||
await waitFor(element(by.id('reaction-picker')))
|
||||
.toExist()
|
||||
|
@ -324,6 +324,19 @@ describe('Room screen', () => {
|
|||
.withTimeout(60000);
|
||||
});
|
||||
|
||||
it('should ask for review', async () => {
|
||||
await dismissReviewNag(); // TODO: Create a proper test for this elsewhere.
|
||||
});
|
||||
|
||||
it('should open/close reactions list', async () => {
|
||||
await element(by.id('message-reaction-:grinning:')).longPress();
|
||||
await waitFor(element(by.id('reactionsList')))
|
||||
.toExist()
|
||||
.withTimeout(4000);
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.5);
|
||||
});
|
||||
|
||||
it('should remove reaction', async () => {
|
||||
await element(by.id('message-reaction-:grinning:')).tap();
|
||||
await waitFor(element(by.id('message-reaction-:grinning:')))
|
||||
|
@ -331,10 +344,6 @@ describe('Room screen', () => {
|
|||
.withTimeout(60000);
|
||||
});
|
||||
|
||||
it('should ask for review', async () => {
|
||||
await dismissReviewNag(); // TODO: Create a proper test for this elsewhere.
|
||||
});
|
||||
|
||||
it('should edit message', async () => {
|
||||
await mockMessage('edit');
|
||||
await element(by[textMatcher](`${data.random}edit`))
|
||||
|
|
Loading…
Reference in New Issue