forked from Ivasoft/mattermost-mobile
[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:
committed by
GitHub
parent
a300cc651e
commit
5dd3121bbc
@@ -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,
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user