[Gekidou MM-43527] Add threads item to the channel switcher (#6657)

* Threads item in channel switcher

* Misc

* Updated test & renamed type

* Reverted unwanted changes

* Changed thread text to i18n

* feedback fix

* Merge conflict steps

* Merge fix

* test fix

* Added onPress to the dependencies

* Moved the term matching logic to the useMemo block

* Misc

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Anurag Shivarathri
2022-11-25 18:51:04 +05:30
committed by GitHub
parent a300cc651e
commit 5dd3121bbc
7 changed files with 182 additions and 23 deletions

View File

@@ -36,7 +36,7 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"minHeight": 40,
"paddingHorizontal": 20,
},
undefined,
false,
]
}
>
@@ -48,6 +48,7 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"color": "rgba(255,255,255,0.5)",
"fontSize": 24,
},
false,
undefined,
]
}
@@ -70,8 +71,98 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"paddingLeft": 12,
"paddingRight": 20,
},
false,
false,
undefined,
undefined,
]
}
>
Threads
</Text>
</View>
</View>
</View>
`;
exports[`Thread item in the channel list Threads Component should match snapshot with isInfo 1`] = `
<View
accessible={true}
collapsable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
{
"opacity": 1,
}
}
testID="channel_list.threads.button"
>
<View
style={
{
"marginLeft": -18,
"marginRight": -20,
}
}
>
<View
style={
[
{
"alignItems": "center",
"flexDirection": "row",
"minHeight": 40,
"paddingHorizontal": 20,
},
false,
]
}
>
<Icon
name="message-text-outline"
style={
[
{
"color": "rgba(255,255,255,0.5)",
"fontSize": 24,
},
false,
{
"color": "rgba(63,67,80,0.72)",
},
]
}
/>
<Text
style={
[
{
"flex": 1,
},
{
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
{
"color": "rgba(255,255,255,0.72)",
"marginTop": -1,
"paddingLeft": 12,
"paddingRight": 20,
},
false,
false,
{
"color": "#3f4350",
"paddingRight": 20,
},
]
}
>
@@ -120,7 +211,7 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"minHeight": 40,
"paddingHorizontal": 20,
},
undefined,
false,
]
}
>
@@ -132,6 +223,7 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"color": "rgba(255,255,255,0.5)",
"fontSize": 24,
},
false,
undefined,
]
}
@@ -154,7 +246,8 @@ exports[`Thread item in the channel list Threads Component should match snapshot
"paddingLeft": 12,
"paddingRight": 20,
},
undefined,
false,
false,
undefined,
]
}

View File

@@ -36,6 +36,17 @@ describe('Thread item in the channel list', () => {
expect(toJSON()).toMatchSnapshot();
});
test('Threads Component should match snapshot with isInfo', () => {
const {toJSON} = renderWithIntlAndTheme(
<Threads
{...baseProps}
isInfo={true}
/>,
);
expect(toJSON()).toMatchSnapshot();
});
test('Threads Component should match snapshot, groupUnreadsSeparately false, always show', () => {
const {toJSON} = renderWithIntlAndTheme(
<Threads

View File

@@ -30,6 +30,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
iconActive: {
color: theme.sidebarText,
},
iconInfo: {
color: changeOpacity(theme.centerChannelColor, 0.72),
},
text: {
flex: 1,
},
@@ -37,15 +40,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
type Props = {
currentChannelId: string;
onlyUnreads: boolean;
groupUnreadsSeparately: boolean;
isInfo?: boolean;
onlyUnreads: boolean;
onPress?: () => void;
unreadsAndMentions: {
unreads: boolean;
mentions: number;
};
};
const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, onlyUnreads, unreadsAndMentions}: Props) => {
const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, isInfo, onlyUnreads, onPress, unreadsAndMentions}: Props) => {
const isTablet = useIsTablet();
const serverUrl = useServerUrl();
@@ -54,33 +59,44 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, onlyUnreads, u
const customStyles = getStyleSheet(theme);
const handlePress = useCallback(preventDoubleTap(() => {
switchToGlobalThreads(serverUrl);
}), [serverUrl]);
if (onPress) {
onPress();
} else {
switchToGlobalThreads(serverUrl);
}
}), [onPress, serverUrl]);
const {unreads, mentions} = unreadsAndMentions;
const isActive = isTablet && !currentChannelId;
const [containerStyle, iconStyle, textStyle] = useMemo(() => {
const [containerStyle, iconStyle, textStyle, badgeStyle] = useMemo(() => {
const container = [
styles.container,
isActive ? styles.activeItem : undefined,
isActive && styles.activeItem,
];
const icon = [
customStyles.icon,
isActive || unreads ? customStyles.iconActive : undefined,
(isActive || unreads) && customStyles.iconActive,
isInfo && customStyles.iconInfo,
];
const text = [
customStyles.text,
unreads ? channelItemTextStyle.bold : channelItemTextStyle.regular,
styles.text,
unreads ? styles.highlight : undefined,
isActive ? styles.textActive : undefined,
unreads && styles.highlight,
isActive && styles.textActive,
isInfo && styles.textInfo,
];
return [container, icon, text];
}, [customStyles, isActive, styles, unreads]);
const badge = [
styles.badge,
isInfo && styles.infoBadge,
];
return [container, icon, text, badge];
}, [customStyles, isActive, isInfo, styles, unreads]);
if (groupUnreadsSeparately && (onlyUnreads && !isActive && !unreads && !mentions)) {
return null;
@@ -104,7 +120,7 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, onlyUnreads, u
/>
<Badge
value={mentions}
style={styles.badge}
style={badgeStyle}
visible={mentions > 0}
/>
</View>
@@ -113,4 +129,4 @@ const ThreadsButton = ({currentChannelId, groupUnreadsSeparately, onlyUnreads, u
);
};
export default ThreadsButton;
export default React.memo(ThreadsButton);

View File

@@ -7,11 +7,13 @@ import {useIntl} from 'react-intl';
import {Alert, FlatList, ListRenderItemInfo, StyleSheet, View} from 'react-native';
import Animated, {FadeInDown, FadeOutUp} from 'react-native-reanimated';
import {switchToGlobalThreads} from '@actions/local/thread';
import {joinChannelIfNeeded, makeDirectChannel, searchAllChannels, switchToChannelById} from '@actions/remote/channel';
import {searchProfiles} from '@actions/remote/user';
import ChannelItem from '@components/channel_item';
import Loading from '@components/loading';
import NoResultsWithTerm from '@components/no_results_with_term';
import ThreadsButton from '@components/threads_button';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {sortChannelsByDisplayName} from '@utils/channel';
@@ -23,6 +25,8 @@ import UserItem from './user_item';
import type ChannelModel from '@typings/database/models/servers/channel';
import type UserModel from '@typings/database/models/servers/user';
type ResultItem = ChannelModel|Channel|UserModel|'thread';
type RemoteChannels = {
archived: Channel[];
startWith: Channel[];
@@ -35,6 +39,7 @@ type Props = {
channelsMatch: ChannelModel[];
channelsMatchStart: ChannelModel[];
currentTeamId: string;
isCRTEnabled: boolean;
keyboardHeight: number;
loading: boolean;
onLoading: (loading: boolean) => void;
@@ -77,7 +82,7 @@ const sortByUserOrChannel = <T extends Channel |UserModel>(locale: string, teamm
const FilteredList = ({
archivedChannels, close, channelsMatch, channelsMatchStart, currentTeamId,
keyboardHeight, loading, onLoading, restrictDirectMessage, showTeamName,
isCRTEnabled, keyboardHeight, loading, onLoading, restrictDirectMessage, showTeamName,
teamIds, teammateDisplayNameSetting, term, usersMatch, usersMatchStart, testID,
}: Props) => {
const bounce = useRef<DebouncedFunc<() => void>>();
@@ -87,6 +92,7 @@ const FilteredList = ({
const {locale, formatMessage} = useIntl();
const flatListStyle = useMemo(() => ({flexGrow: 1, paddingBottom: keyboardHeight}), [keyboardHeight]);
const [remoteChannels, setRemoteChannels] = useState<RemoteChannels>({archived: [], startWith: [], matches: []});
const totalLocalResults = channelsMatchStart.length + channelsMatch.length + usersMatchStart.length;
const search = async () => {
@@ -182,6 +188,11 @@ const FilteredList = ({
switchToChannelById(serverUrl, channelId);
}, [serverUrl, close]);
const onSwitchToThreads = useCallback(async () => {
await close();
switchToGlobalThreads(serverUrl);
}, [serverUrl, close]);
const renderEmpty = useCallback(() => {
if (loading) {
return (
@@ -204,7 +215,15 @@ const FilteredList = ({
return null;
}, [term, loading, theme]);
const renderItem = useCallback(({item}: ListRenderItemInfo<ChannelModel|Channel|UserModel>) => {
const renderItem = useCallback(({item}: ListRenderItemInfo<ResultItem>) => {
if (item === 'thread') {
return (
<ThreadsButton
isInfo={true}
onPress={onSwitchToThreads}
/>
);
}
if ('teamId' in item) {
return (
<ChannelItem
@@ -235,8 +254,26 @@ const FilteredList = ({
);
}, [onJoinChannel, onOpenDirectMessage, onSwitchToChannel, showTeamName, teammateDisplayNameSetting]);
const threadLabel = useMemo(
() => formatMessage({
id: 'threads',
defaultMessage: 'Threads',
}).toLowerCase(),
[locale],
);
const data = useMemo(() => {
const items: Array<ChannelModel|Channel|UserModel> = [...channelsMatchStart];
const items: ResultItem[] = [];
// Add threads item to show it on the top of the list
if (isCRTEnabled) {
const isThreadTerm = threadLabel.indexOf(term.toLowerCase()) === 0;
if (isThreadTerm) {
items.push('thread');
}
}
items.push(...channelsMatchStart);
// Channels that matches
if (items.length < MAX_RESULTS) {
@@ -275,14 +312,14 @@ const FilteredList = ({
}
return [...new Set(items)].slice(0, MAX_RESULTS + 1);
}, [archivedChannels, channelsMatchStart, channelsMatch, remoteChannels, usersMatch, usersMatchStart, locale, teammateDisplayNameSetting]);
}, [archivedChannels, channelsMatchStart, channelsMatch, isCRTEnabled, remoteChannels, usersMatch, usersMatchStart, locale, teammateDisplayNameSetting, term, threadLabel]);
useEffect(() => {
mounted.current = true;
return () => {
mounted.current = false;
};
});
}, []);
useEffect(() => {
bounce.current = debounce(search, 500);

View File

@@ -10,6 +10,7 @@ import {General} from '@constants';
import {observeArchiveChannelsByTerm, observeDirectChannelsByTerm, observeJoinedChannelsByTerm, observeNotDirectChannelsByTerm} from '@queries/servers/channel';
import {observeConfigValue, observeCurrentTeamId} from '@queries/servers/system';
import {queryJoinedTeams} from '@queries/servers/team';
import {observeIsCRTEnabled} from '@queries/servers/thread';
import {observeTeammateNameDisplay} from '@queries/servers/user';
import {retrieveChannels} from '@screens/find_channels/utils';
@@ -61,6 +62,7 @@ const enhanced = withObservables(['term'], ({database, term}: EnhanceProps) => {
channelsMatch,
channelsMatchStart,
currentTeamId: observeCurrentTeamId(database),
isCRTEnabled: observeIsCRTEnabled(database),
restrictDirectMessage,
showTeamName: teamIds.pipe(switchMap((ids) => of$(ids.size > 1))),
teamIds,

View File

@@ -4,6 +4,7 @@
import React, {useEffect} from 'react';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import ThreadsButton from '@components/threads_button';
import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view';
import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
@@ -12,7 +13,6 @@ import Categories from './categories';
import ChannelListHeader from './header';
import LoadChannelsError from './load_channels_error';
import SubHeader from './subheader';
import ThreadsButton from './threads_button';
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {