[MM-60405] Crossteam search (#8411)
* Add new team picker for search * try fix result header * fix style * add test for team picker * add some tests * add tests on team list and team list item * hide All Teams search behind FF * use style variable for separator * ALL TEAMS does not have a search history * Update app/components/team_list/index.test.tsx Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * move ALL_TEAMS_ID to a constant file * memoize the construction of team list to prevent useless allocation * combine pushes * move style to stylesheet * revert changes to Package.resolved * improve team list index tests * add test to ensure team picker does not show for just one team * add test to ensure the file icon filter only shows when i'm on the file tab * improve jsx readability by making the if test positive * add test to make sure team picket does not show if there's only one team * test: remove snapshot and add expect that separator exists in index 0 but index 1 (#8474) * Trigger Build --------- Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
This commit is contained in:
parent
1207b0e0cd
commit
fb9760151c
19 changed files with 631 additions and 160 deletions
69
app/components/team_list/index.test.tsx
Normal file
69
app/components/team_list/index.test.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {fireEvent, renderWithEverything} from '@test/intl-test-helper';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import TeamList from './index';
|
||||
|
||||
import type Database from '@nozbe/watermelondb/Database';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
describe('TeamList', () => {
|
||||
let database: Database;
|
||||
beforeAll(async () => {
|
||||
const server = await TestHelper.setupServerDatabase();
|
||||
database = server.database;
|
||||
});
|
||||
const teams = [
|
||||
{id: 'team1', displayName: 'Team 1'} as TeamModel,
|
||||
{id: 'team2', displayName: 'Team 2'} as TeamModel,
|
||||
];
|
||||
|
||||
it('should call onPress when a team is pressed', () => {
|
||||
const onPress = jest.fn();
|
||||
const {getByText} = renderWithEverything(
|
||||
<TeamList
|
||||
teams={teams}
|
||||
onPress={onPress}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
fireEvent.press(getByText('Team 1'));
|
||||
expect(onPress).toHaveBeenCalledWith('team1');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{loading: true, shouldShow: true, label: 'should render loading component when loading is true'},
|
||||
{loading: false, shouldShow: false, label: 'should not render loading component when loading is false'},
|
||||
])('$label', ({loading, shouldShow}) => {
|
||||
const {queryByTestId} = renderWithEverything(
|
||||
<TeamList
|
||||
teams={teams}
|
||||
onPress={jest.fn()}
|
||||
loading={loading}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
if (shouldShow) {
|
||||
expect(queryByTestId('team_list.loading')).toBeTruthy();
|
||||
} else {
|
||||
expect(queryByTestId('team_list.loading')).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it('should render separator after the first item when separatorAfterFirstItem is true', () => {
|
||||
const {queryByTestId, getByTestId} = renderWithEverything(
|
||||
<TeamList
|
||||
teams={teams}
|
||||
onPress={jest.fn()}
|
||||
separatorAfterFirstItem={true}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
expect(getByTestId('team_list.separator-0')).toBeTruthy();
|
||||
expect(queryByTestId('team_list.separator-1')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {type ListRenderItemInfo, StyleSheet, View} from 'react-native';
|
||||
import {type ListRenderItemInfo, View} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler'; // Keep the FlatList from gesture handler so it works well with bottom sheet
|
||||
|
||||
import Loading from '@components/loading';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import TeamListItem from './team_list_item';
|
||||
|
||||
|
|
@ -23,15 +25,23 @@ type Props = {
|
|||
testID?: string;
|
||||
textColor?: string;
|
||||
type?: BottomSheetList;
|
||||
hideIcon?: boolean;
|
||||
separatorAfterFirstItem?: boolean;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const keyExtractor = (item: TeamModel) => item.id;
|
||||
|
|
@ -47,11 +57,15 @@ export default function TeamList({
|
|||
testID,
|
||||
textColor,
|
||||
type = 'FlatList',
|
||||
hideIcon = false,
|
||||
separatorAfterFirstItem = false,
|
||||
}: Props) {
|
||||
const List = useMemo(() => (type === 'FlatList' ? FlatList : BottomSheetFlatList), [type]);
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const renderTeam = useCallback(({item: t}: ListRenderItemInfo<Team|TeamModel>) => {
|
||||
return (
|
||||
const renderTeam = useCallback(({item: t, index: i}: ListRenderItemInfo<Team|TeamModel>) => {
|
||||
let teamListItem = (
|
||||
<TeamListItem
|
||||
onPress={onPress}
|
||||
team={t}
|
||||
|
|
@ -59,13 +73,24 @@ export default function TeamList({
|
|||
iconBackgroundColor={iconBackgroundColor}
|
||||
iconTextColor={iconTextColor}
|
||||
selectedTeamId={selectedTeamId}
|
||||
hideIcon={hideIcon}
|
||||
/>
|
||||
);
|
||||
}, [textColor, iconTextColor, iconBackgroundColor, onPress, selectedTeamId]);
|
||||
if (separatorAfterFirstItem && i === 0) {
|
||||
teamListItem = (<>
|
||||
{teamListItem}
|
||||
<View
|
||||
style={styles.separator}
|
||||
testID={`team_list.separator-${i}`}
|
||||
/>
|
||||
</>);
|
||||
}
|
||||
return teamListItem;
|
||||
}, [textColor, iconTextColor, iconBackgroundColor, onPress, selectedTeamId, hideIcon, separatorAfterFirstItem, styles.separator]);
|
||||
|
||||
let footer;
|
||||
if (loading) {
|
||||
footer = (<Loading/>);
|
||||
footer = (<Loading testID='team_list.loading'/>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme, fireEvent} from '@test/intl-test-helper';
|
||||
|
||||
import TeamListItem from './team_list_item';
|
||||
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
describe('TeamListItem', () => {
|
||||
const team = {
|
||||
id: 'team_id',
|
||||
displayName: 'Team Display Name',
|
||||
lastTeamIconUpdatedAt: 0,
|
||||
} as TeamModel;
|
||||
const iconTestId = `team_sidebar.team_list.team_list_item.${team.id}.team_icon`;
|
||||
|
||||
it('should call onPress when pressed', () => {
|
||||
const onPressMock = jest.fn();
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<TeamListItem
|
||||
team={team}
|
||||
onPress={onPressMock}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.press(getByText('Team Display Name'));
|
||||
|
||||
expect(onPressMock).toHaveBeenCalledWith('team_id');
|
||||
});
|
||||
|
||||
it('should render TeamIcon when hideIcon is false', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<TeamListItem
|
||||
team={team}
|
||||
onPress={jest.fn()}
|
||||
hideIcon={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByTestId(iconTestId)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not render TeamIcon when hideIcon is true', () => {
|
||||
const {queryByTestId} = renderWithIntlAndTheme(
|
||||
<TeamListItem
|
||||
team={team}
|
||||
onPress={jest.fn()}
|
||||
hideIcon={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByTestId(iconTestId)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -20,6 +20,7 @@ type Props = {
|
|||
iconBackgroundColor?: string;
|
||||
selectedTeamId?: string;
|
||||
onPress: (teamId: string) => void;
|
||||
hideIcon?: boolean;
|
||||
}
|
||||
|
||||
export const ITEM_HEIGHT = 56;
|
||||
|
|
@ -50,7 +51,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
export default function TeamListItem({team, textColor, iconTextColor, iconBackgroundColor, selectedTeamId, onPress}: Props) {
|
||||
export default function TeamListItem({team, textColor, iconTextColor, iconBackgroundColor, selectedTeamId, onPress, hideIcon}: Props) {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -58,6 +59,8 @@ export default function TeamListItem({team, textColor, iconTextColor, iconBackgr
|
|||
const lastTeamIconUpdateAt = 'lastTeamIconUpdatedAt' in team ? team.lastTeamIconUpdatedAt : team.last_team_icon_update;
|
||||
const teamListItemTestId = `team_sidebar.team_list.team_list_item.${team.id}`;
|
||||
|
||||
const showIcon = !hideIcon;
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
onPress(team.id);
|
||||
}, [team.id, onPress]);
|
||||
|
|
@ -68,17 +71,19 @@ export default function TeamListItem({team, textColor, iconTextColor, iconBackgr
|
|||
type='opacity'
|
||||
style={styles.touchable}
|
||||
>
|
||||
<View style={styles.icon_container}>
|
||||
<TeamIcon
|
||||
id={team.id}
|
||||
displayName={displayName}
|
||||
lastIconUpdate={lastTeamIconUpdateAt}
|
||||
selected={false}
|
||||
textColor={iconTextColor || theme.centerChannelColor}
|
||||
backgroundColor={iconBackgroundColor || changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
testID={`${teamListItemTestId}.team_icon`}
|
||||
/>
|
||||
</View>
|
||||
{showIcon &&
|
||||
<View style={styles.icon_container}>
|
||||
<TeamIcon
|
||||
id={team.id}
|
||||
displayName={displayName}
|
||||
lastIconUpdate={lastTeamIconUpdateAt}
|
||||
selected={false}
|
||||
textColor={iconTextColor || theme.centerChannelColor}
|
||||
backgroundColor={iconBackgroundColor || changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
testID={`${teamListItemTestId}.team_icon`}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
<Text
|
||||
style={[styles.text, Boolean(textColor) && {color: textColor}]}
|
||||
numberOfLines={1}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import ServerErrors from './server_errors';
|
|||
import SnackBar from './snack_bar';
|
||||
import Sso from './sso';
|
||||
import SupportedServer from './supported_server';
|
||||
import Team from './team';
|
||||
import Tutorial from './tutorial';
|
||||
import View from './view';
|
||||
import WebsocketEvents from './websocket';
|
||||
|
|
@ -74,6 +75,7 @@ export {
|
|||
SnackBar,
|
||||
Sso,
|
||||
SupportedServer,
|
||||
Team,
|
||||
Tutorial,
|
||||
View,
|
||||
WebsocketEvents,
|
||||
|
|
|
|||
8
app/constants/team.ts
Normal file
8
app/constants/team.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const ALL_TEAMS_ID = '';
|
||||
|
||||
export default {
|
||||
ALL_TEAMS_ID,
|
||||
};
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import TeamList from '@components/team_list';
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import BottomSheetContent from '@screens/bottom_sheet/content';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
|
|
@ -15,9 +17,11 @@ type Props = {
|
|||
teamId: string;
|
||||
setTeamId: (teamId: string) => void;
|
||||
title: string;
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
|
||||
export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: Props) {
|
||||
export default function BottomSheetTeamList({teams, title, setTeamId, teamId, crossTeamSearchEnabled}: Props) {
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const showTitle = !isTablet && Boolean(teams.length);
|
||||
|
||||
|
|
@ -26,6 +30,14 @@ export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: P
|
|||
dismissBottomSheet();
|
||||
}, [setTeamId]);
|
||||
|
||||
const teamList = useMemo(() => {
|
||||
const list = [...teams];
|
||||
if (crossTeamSearchEnabled) {
|
||||
list.unshift({id: ALL_TEAMS_ID, displayName: intl.formatMessage({id: 'mobile.search.team.all_teams', defaultMessage: 'All teams'})} as TeamModel);
|
||||
}
|
||||
return list;
|
||||
}, [teams, crossTeamSearchEnabled, intl]);
|
||||
|
||||
return (
|
||||
<BottomSheetContent
|
||||
showButton={false}
|
||||
|
|
@ -35,10 +47,12 @@ export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: P
|
|||
>
|
||||
<TeamList
|
||||
selectedTeamId={teamId}
|
||||
teams={teams}
|
||||
teams={teamList}
|
||||
onPress={onPress}
|
||||
testID='search.select_team_slide_up.team_list'
|
||||
type={isTablet ? 'FlatList' : 'BottomSheetFlatList'}
|
||||
hideIcon={true}
|
||||
separatorAfterFirstItem={crossTeamSearchEnabled}
|
||||
/>
|
||||
</BottomSheetContent>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import compose from 'lodash/fp/compose';
|
||||
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeConfigBooleanValue, observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {queryJoinedTeams} from '@queries/servers/team';
|
||||
|
||||
import SearchScreen from './search';
|
||||
|
|
@ -16,6 +16,7 @@ const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
return {
|
||||
teamId: currentTeamId,
|
||||
teams: queryJoinedTeams(database).observe(),
|
||||
crossTeamSearchEnabled: observeConfigBooleanValue(database, 'FeatureFlagExperimentalCrossTeamSearch'),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import compose from 'lodash/fp/compose';
|
|||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeTeam, queryTeamSearchHistoryByTeamId} from '@queries/servers/team';
|
||||
|
||||
import Initial from './initial';
|
||||
|
|
@ -22,6 +23,7 @@ const enhance = withObservables(['teamId'], ({database, teamId}: EnhanceProps) =
|
|||
switchMap((t) => of$(t?.displayName || '')),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
crossTeamSearchEnabled: observeConfigBooleanValue(database, 'FeatureFlagExperimentalCrossTeamSearch'),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import React, {type Dispatch, type RefObject, type SetStateAction} from 'react';
|
||||
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
|
||||
import Modifiers from './modifiers';
|
||||
import RecentSearches from './recent_searches';
|
||||
|
||||
|
|
@ -22,9 +24,11 @@ type Props = {
|
|||
teamId: string;
|
||||
teamName: string;
|
||||
teams: TeamModel[];
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
|
||||
const Initial = ({recentSearches, scrollEnabled, searchValue, setRecentValue, searchRef, teamId, teamName, teams, setTeamId, setSearchValue}: Props) => {
|
||||
const Initial = ({recentSearches, scrollEnabled, searchValue, setRecentValue, searchRef, teamId, teamName, teams, setTeamId, setSearchValue, crossTeamSearchEnabled}: Props) => {
|
||||
const showRecentSearches = Boolean(recentSearches.length) && teamId !== ALL_TEAMS_ID;
|
||||
return (
|
||||
<>
|
||||
<Modifiers
|
||||
|
|
@ -35,8 +39,9 @@ const Initial = ({recentSearches, scrollEnabled, searchValue, setRecentValue, se
|
|||
teamId={teamId}
|
||||
teams={teams}
|
||||
scrollEnabled={scrollEnabled}
|
||||
crossTeamSearchEnabled={crossTeamSearchEnabled}
|
||||
/>
|
||||
{Boolean(recentSearches.length) &&
|
||||
{showRecentSearches &&
|
||||
<RecentSearches
|
||||
recentSearches={recentSearches}
|
||||
setRecentValue={setRecentValue}
|
||||
|
|
|
|||
67
app/screens/home/search/initial/modifiers/index.test.tsx
Normal file
67
app/screens/home/search/initial/modifiers/index.test.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useSharedValue} from 'react-native-reanimated';
|
||||
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import TeamPicker from '@screens/home/search/team_picker';
|
||||
import {renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import Modifiers from './index';
|
||||
|
||||
import type {SearchRef} from '@components/search';
|
||||
import type {TeamModel} from '@database/models/server';
|
||||
|
||||
jest.mock('@screens/home/search/team_picker', () => jest.fn(() => null));
|
||||
jest.mock('./show_more', () => jest.fn(() => null));
|
||||
jest.mock('@react-native-camera-roll/camera-roll', () => jest.fn());
|
||||
|
||||
describe('Modifiers', () => {
|
||||
const scrollEnabled = useSharedValue(true);
|
||||
const searchRef = React.createRef<SearchRef>();
|
||||
const setSearchValue = jest.fn();
|
||||
const setTeamId = jest.fn();
|
||||
const teams = [{id: 'team1', displayName: 'Team 1'}, {id: 'team2', displayName: 'Team 2'}] as TeamModel[];
|
||||
|
||||
const renderComponent = (teamId = ALL_TEAMS_ID, teamList?: TeamModel[]) => {
|
||||
return renderWithIntlAndTheme(
|
||||
<Modifiers
|
||||
setSearchValue={setSearchValue}
|
||||
setTeamId={setTeamId}
|
||||
teamId={teamId}
|
||||
teams={teamList || teams}
|
||||
scrollEnabled={scrollEnabled}
|
||||
searchRef={searchRef}
|
||||
crossTeamSearchEnabled={true}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
|
||||
it('should render correctly', () => {
|
||||
const {getByTestId} = renderComponent();
|
||||
expect(getByTestId('search.modifier.header')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render TeamPicker when there are multiple teams', () => {
|
||||
renderComponent();
|
||||
expect(TeamPicker).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not render TeamPicker when there is only one team', () => {
|
||||
renderComponent(ALL_TEAMS_ID, [teams[0]]);
|
||||
expect(TeamPicker).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render the From: and In: modifiers when a team is selected', () => {
|
||||
const {getByTestId} = renderComponent('team1');
|
||||
expect(getByTestId('search.modifier.from')).toBeTruthy();
|
||||
expect(getByTestId('search.modifier.in')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not render the From: and In: modifiers when all teams are selected', () => {
|
||||
const {queryByTestId} = renderComponent(ALL_TEAMS_ID);
|
||||
expect(queryByTestId('search.modifier.from')).toBeNull();
|
||||
expect(queryByTestId('search.modifier.in')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -4,11 +4,12 @@
|
|||
import React, {type Dispatch, type RefObject, type SetStateAction, useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {type IntlShape, useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import Animated, {useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import Animated, {type SharedValue, useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import {useTheme} from '@context/theme';
|
||||
import TeamPickerIcon from '@screens/home/search/team_picker_icon';
|
||||
import TeamPicker from '@screens/home/search/team_picker';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -19,31 +20,37 @@ import type {SearchRef} from '@components/search';
|
|||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
const MODIFIER_LABEL_HEIGHT = 48;
|
||||
const TEAM_PICKER_ICON_SIZE = 32;
|
||||
const NUM_ITEMS_BEFORE_EXPAND = 4;
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
titleContainer: {
|
||||
header: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
marginTop: 20,
|
||||
marginRight: 18,
|
||||
marginHorizontal: 18,
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingLeft: 18,
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 300, 'SemiBold'),
|
||||
},
|
||||
teamPickerContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const getModifiersSectionsData = (intl: IntlShape): ModifierItem[] => {
|
||||
const getModifiersSectionsData = (intl: IntlShape, teamId: string): ModifierItem[] => {
|
||||
const formatMessage = intl.formatMessage;
|
||||
const sectionsData = [
|
||||
{
|
||||
|
||||
const sectionsData = [];
|
||||
if (teamId !== ALL_TEAMS_ID) {
|
||||
sectionsData.push({
|
||||
term: 'From:',
|
||||
testID: 'search.modifier.from',
|
||||
description: formatMessage({id: 'mobile.search.modifier.from', defaultMessage: ' a specific user'}),
|
||||
|
|
@ -51,52 +58,40 @@ const getModifiersSectionsData = (intl: IntlShape): ModifierItem[] => {
|
|||
term: 'In:',
|
||||
testID: 'search.modifier.in',
|
||||
description: formatMessage({id: 'mobile.search.modifier.in', defaultMessage: ' a specific channel'}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sectionsData.push({
|
||||
term: '-',
|
||||
testID: 'search.modifier.exclude',
|
||||
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: ' exclude search terms'}),
|
||||
}, {
|
||||
term: '""',
|
||||
testID: 'search.modifier.phrases',
|
||||
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: ' messages with phrases'}),
|
||||
cursorPosition: -1,
|
||||
});
|
||||
|
||||
// {
|
||||
// term: 'On:',
|
||||
// testID: 'search.modifier.on',
|
||||
// description: formatMessage({id: 'mobile.search.modifier.on', defaultMessage: ' a specific date'}),
|
||||
// },
|
||||
// {
|
||||
// term: 'After:',
|
||||
// testID: 'search.modifier.after',
|
||||
// description: formatMessage({id: 'mobile.search.modifier.after', defaultMessage: ' after a date'}),
|
||||
// }, {
|
||||
// term: 'Before:',
|
||||
// testID: 'search.modifier.before',
|
||||
// description: formatMessage({id: 'mobile.search.modifier.before', defaultMessage: ' before a date'}),
|
||||
// },
|
||||
{
|
||||
term: '-',
|
||||
testID: 'search.modifier.exclude',
|
||||
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: ' exclude search terms'}),
|
||||
}, {
|
||||
term: '""',
|
||||
testID: 'search.modifier.phrases',
|
||||
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: ' messages with phrases'}),
|
||||
cursorPosition: -1,
|
||||
},
|
||||
];
|
||||
return sectionsData;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
scrollEnabled: Animated.SharedValue<boolean>;
|
||||
scrollEnabled: SharedValue<boolean>;
|
||||
searchRef: RefObject<SearchRef>;
|
||||
setSearchValue: Dispatch<SetStateAction<string>>;
|
||||
searchValue?: string;
|
||||
setTeamId: (id: string) => void;
|
||||
teamId: string;
|
||||
teams: TeamModel[];
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
const Modifiers = ({scrollEnabled, searchValue, setSearchValue, searchRef, setTeamId, teamId, teams}: Props) => {
|
||||
const Modifiers = ({scrollEnabled, searchValue, setSearchValue, searchRef, setTeamId, teamId, teams, crossTeamSearchEnabled}: Props) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
const height = useSharedValue(NUM_ITEMS_BEFORE_EXPAND * MODIFIER_LABEL_HEIGHT);
|
||||
const data = useMemo(() => getModifiersSectionsData(intl), [intl]);
|
||||
const data = useMemo(() => getModifiersSectionsData(intl, teamId), [intl, teamId]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
|
||||
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
|
@ -143,20 +138,25 @@ const Modifiers = ({scrollEnabled, searchValue, setSearchValue, searchRef, setTe
|
|||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.titleContainer}>
|
||||
<FormattedText
|
||||
style={styles.title}
|
||||
id={'screen.search.modifier.header'}
|
||||
defaultMessage='Search options'
|
||||
testID='search.modifier.header'
|
||||
/>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.titleContainer}>
|
||||
<FormattedText
|
||||
style={styles.title}
|
||||
id={'screen.search.modifier.header'}
|
||||
defaultMessage='Search options'
|
||||
testID='search.modifier.header'
|
||||
/>
|
||||
</View>
|
||||
|
||||
{teams.length > 1 &&
|
||||
<TeamPickerIcon
|
||||
size={TEAM_PICKER_ICON_SIZE}
|
||||
setTeamId={setTeamId}
|
||||
teamId={teamId}
|
||||
teams={teams}
|
||||
/>
|
||||
<View style={styles.teamPickerContainer}>
|
||||
<TeamPicker
|
||||
setTeamId={setTeamId}
|
||||
teamId={teamId}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={crossTeamSearchEnabled}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
<Animated.View style={animatedStyle}>
|
||||
|
|
|
|||
138
app/screens/home/search/results/header.test.tsx
Normal file
138
app/screens/home/search/results/header.test.tsx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithIntlAndTheme, fireEvent} from '@test/intl-test-helper';
|
||||
import {FileFilters} from '@utils/file';
|
||||
import {TabTypes} from '@utils/search';
|
||||
|
||||
import Header from './header';
|
||||
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
// Some subcomponents require react-native-camera-roll, which is not available in the test environment
|
||||
jest.mock('@react-native-camera-roll/camera-roll', () => ({}));
|
||||
|
||||
describe('Header', () => {
|
||||
const onTabSelect = jest.fn();
|
||||
const onFilterChanged = jest.fn();
|
||||
const setTeamId = jest.fn();
|
||||
|
||||
const teams = [
|
||||
{id: 'team1', displayName: 'Team 1'},
|
||||
{id: 'team2', displayName: 'Team 2'},
|
||||
] as TeamModel[];
|
||||
|
||||
it('should render correctly', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={TabTypes.MESSAGES}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('Messages')).toBeTruthy();
|
||||
expect(getByText('Files')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call onTabSelect with MESSAGES when Messages button is pressed', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={TabTypes.MESSAGES}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.press(getByText('Messages'));
|
||||
expect(onTabSelect).toHaveBeenCalledWith(TabTypes.MESSAGES);
|
||||
});
|
||||
|
||||
it('should call onTabSelect with FILES when Files button is pressed', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={TabTypes.MESSAGES}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.press(getByText('Files'));
|
||||
expect(onTabSelect).toHaveBeenCalledWith(TabTypes.FILES);
|
||||
});
|
||||
|
||||
it('should not render TeamPicker when there is only one team', () => {
|
||||
const {queryByText} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={TabTypes.MESSAGES}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={[teams[0]]}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByText('Team 1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render TeamPicker when there are multiple teams', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={TabTypes.MESSAGES}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('Team 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{selectedTab: TabTypes.FILES, shouldShowIcon: true, label: 'should render the file type filter when the Files tab is selected'},
|
||||
{selectedTab: TabTypes.MESSAGES, shouldShowIcon: false, label: 'should not render the file type filter when the Messages tab is selected'},
|
||||
])('$label', ({selectedTab, shouldShowIcon}) => {
|
||||
const {queryByTestId} = renderWithIntlAndTheme(
|
||||
<Header
|
||||
teamId='team1'
|
||||
setTeamId={setTeamId}
|
||||
onTabSelect={onTabSelect}
|
||||
onFilterChanged={onFilterChanged}
|
||||
selectedTab={selectedTab}
|
||||
selectedFilter={FileFilters.ALL}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
if (shouldShowIcon) {
|
||||
expect(queryByTestId('search.filters.file_type_icon')).toBeTruthy();
|
||||
} else {
|
||||
expect(queryByTestId('search.filters.file_type_icon')).toBeFalsy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ import Filter, {DIVIDERS_HEIGHT, FILTER_ITEM_HEIGHT, NUMBER_FILTER_ITEMS} from '
|
|||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {TITLE_SEPARATOR_MARGIN, TITLE_SEPARATOR_MARGIN_TABLET, TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import TeamPickerIcon from '@screens/home/search/team_picker_icon';
|
||||
import TeamPicker from '@screens/home/search/team_picker';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {type FileFilter, FileFilters} from '@utils/file';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
|
|
@ -29,13 +29,13 @@ type Props = {
|
|||
setTeamId: (id: string) => void;
|
||||
teamId: string;
|
||||
teams: TeamModel[];
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginTop: 10,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
|
|
@ -44,15 +44,26 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
borderColor: theme.centerChannelBg,
|
||||
marginTop: 2,
|
||||
},
|
||||
buttonsContainer: {
|
||||
header: {
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
iconsContainer: {
|
||||
alignItems: 'center',
|
||||
buttonContainer: {
|
||||
flexDirection: 'row',
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
teamPickerContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
filterContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
maxWidth: 32,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -65,6 +76,7 @@ const Header = ({
|
|||
selectedTab,
|
||||
selectedFilter,
|
||||
teams,
|
||||
crossTeamSearchEnabled,
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
|
@ -113,50 +125,50 @@ const Header = ({
|
|||
theme,
|
||||
title,
|
||||
});
|
||||
}, [onFilterChanged, selectedFilter]);
|
||||
|
||||
const filterStyle = useMemo(() => ({marginRight: teams.length > 1 ? 0 : 8}), [teams.length > 1]);
|
||||
}, [onFilterChanged, selectedFilter, snapPoints, title, theme]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.buttonsContainer}>
|
||||
<SelectButton
|
||||
selected={selectedTab === TabTypes.MESSAGES}
|
||||
onPress={handleMessagesPress}
|
||||
text={messagesText}
|
||||
/>
|
||||
<SelectButton
|
||||
selected={selectedTab === TabTypes.FILES}
|
||||
onPress={handleFilesPress}
|
||||
text={filesText}
|
||||
/>
|
||||
<View style={styles.iconsContainer}>
|
||||
{showFilterIcon && (
|
||||
<View style={filterStyle}>
|
||||
<CompassIcon
|
||||
name={'filter-variant'}
|
||||
size={24}
|
||||
color={changeOpacity(
|
||||
theme.centerChannelColor,
|
||||
0.56,
|
||||
)}
|
||||
onPress={handleFilterPress}
|
||||
/>
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
visible={hasFilters}
|
||||
testID={'search.filters.badge'}
|
||||
value={-1}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.buttonContainer}>
|
||||
<SelectButton
|
||||
selected={selectedTab === TabTypes.MESSAGES}
|
||||
onPress={handleMessagesPress}
|
||||
text={messagesText}
|
||||
/>
|
||||
<SelectButton
|
||||
selected={selectedTab === TabTypes.FILES}
|
||||
onPress={handleFilesPress}
|
||||
text={filesText}
|
||||
/>
|
||||
</View>
|
||||
{showFilterIcon && (
|
||||
<View style={styles.filterContainer}>
|
||||
<CompassIcon
|
||||
name={'filter-variant'}
|
||||
testID='search.filters.file_type_icon'
|
||||
size={24}
|
||||
color={changeOpacity(
|
||||
theme.centerChannelColor,
|
||||
0.56,
|
||||
)}
|
||||
onPress={handleFilterPress}
|
||||
/>
|
||||
<Badge
|
||||
style={styles.badge}
|
||||
visible={hasFilters}
|
||||
testID={'search.filters.badge'}
|
||||
value={-1}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.teamPickerContainer}>
|
||||
{teams.length > 1 && (
|
||||
<TeamPickerIcon
|
||||
size={32}
|
||||
divider={true}
|
||||
<TeamPicker
|
||||
setTeamId={setTeamId}
|
||||
teamId={teamId}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={crossTeamSearchEnabled}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Loading from '@components/loading';
|
|||
import NavigationHeader from '@components/navigation_header';
|
||||
import RoundedHeaderContext from '@components/rounded_header_context';
|
||||
import {Screens} from '@constants';
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import {BOTTOM_TAB_HEIGHT} from '@constants/view';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -51,6 +52,7 @@ const AutocompletePaddingTop = 4;
|
|||
type Props = {
|
||||
teamId: string;
|
||||
teams: TeamModel[];
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
|
|
@ -79,7 +81,7 @@ const getSearchParams = (terms: string, filterValue?: FileFilter) => {
|
|||
|
||||
const searchScreenIndex = 1;
|
||||
|
||||
const SearchScreen = ({teamId, teams}: Props) => {
|
||||
const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => {
|
||||
const nav = useNavigation();
|
||||
const isFocused = useIsFocused();
|
||||
const intl = useIntl();
|
||||
|
|
@ -189,7 +191,10 @@ const SearchScreen = ({teamId, teams}: Props) => {
|
|||
hideHeader(true);
|
||||
handleLoading(true);
|
||||
setLastSearchedValue(term);
|
||||
addSearchToTeamSearchHistory(serverUrl, newSearchTeamId, term);
|
||||
|
||||
if (newSearchTeamId !== ALL_TEAMS_ID) {
|
||||
addSearchToTeamSearchHistory(serverUrl, newSearchTeamId, term);
|
||||
}
|
||||
const [postResults, {files, channels}] = await Promise.all([
|
||||
searchPosts(serverUrl, newSearchTeamId, searchParams),
|
||||
searchFiles(serverUrl, newSearchTeamId, searchParams),
|
||||
|
|
@ -396,6 +401,7 @@ const SearchScreen = ({teamId, teams}: Props) => {
|
|||
selectedTab={selectedTab}
|
||||
selectedFilter={filter}
|
||||
teams={teams}
|
||||
crossTeamSearchEnabled={crossTeamSearchEnabled}
|
||||
/>
|
||||
}
|
||||
</Animated.View>
|
||||
|
|
|
|||
63
app/screens/home/search/team_picker.test.tsx
Normal file
63
app/screens/home/search/team_picker.test.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper';
|
||||
|
||||
import TeamPicker from './team_picker';
|
||||
|
||||
import type {TeamModel} from '@database/models/server';
|
||||
|
||||
jest.mock('@screens/navigation', () => ({
|
||||
bottomSheet: jest.fn(),
|
||||
}));
|
||||
|
||||
// Some subcomponents require react-native-camera-roll, which is not available in the test environment
|
||||
jest.mock('@react-native-camera-roll/camera-roll', () => ({}));
|
||||
|
||||
describe('TeamPicker', () => {
|
||||
const teams = [
|
||||
{id: 'team1', displayName: 'Team 1'} as TeamModel,
|
||||
{id: 'team2', displayName: 'Team 2'} as TeamModel,
|
||||
];
|
||||
|
||||
it('should render the selected team name', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<TeamPicker
|
||||
setTeamId={jest.fn()}
|
||||
teams={teams}
|
||||
teamId={'team1'}
|
||||
crossTeamSearchEnabled={true}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('Team 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render "All teams" when teamId is ALL_TEAMS_ID', () => {
|
||||
const {getByText} = renderWithIntlAndTheme(
|
||||
<TeamPicker
|
||||
setTeamId={jest.fn()}
|
||||
teams={teams}
|
||||
teamId={ALL_TEAMS_ID}
|
||||
crossTeamSearchEnabled={true}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('All teams')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call bottomSheet when the team picker is pressed', () => {
|
||||
const {getByTestId} = renderWithIntlAndTheme(
|
||||
<TeamPicker
|
||||
setTeamId={jest.fn()}
|
||||
teams={teams}
|
||||
teamId={'team1'}
|
||||
crossTeamSearchEnabled={true}
|
||||
/>,
|
||||
);
|
||||
fireEvent.press(getByTestId('team_picker.button'));
|
||||
expect(bottomSheet).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TeamIcon from '@components/team_sidebar/team_list/team_item/team_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {ALL_TEAMS_ID} from '@constants/team';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
|
|
@ -25,39 +25,38 @@ const NO_TEAMS_HEIGHT = 392;
|
|||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
teamContainer: {
|
||||
paddingLeft: 8,
|
||||
teamPicker: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
},
|
||||
border: {
|
||||
marginLeft: 12,
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.16),
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
teamIcon: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
compass: {
|
||||
alignItems: 'center',
|
||||
marginLeft: 0,
|
||||
teamName: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 12,
|
||||
textAlign: 'right',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type Props = {
|
||||
size?: number;
|
||||
divider?: boolean;
|
||||
teams: TeamModel[];
|
||||
setTeamId: (id: string) => void;
|
||||
teamId: string;
|
||||
crossTeamSearchEnabled: boolean;
|
||||
}
|
||||
const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}: Props) => {
|
||||
const TeamPicker = ({setTeamId, teams, teamId, crossTeamSearchEnabled}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
const selectedTeam = teams.find((t) => t.id === teamId);
|
||||
let selectedTeam = teams.find((t) => t.id === teamId);
|
||||
if (teamId === ALL_TEAMS_ID) {
|
||||
selectedTeam = {id: ALL_TEAMS_ID, displayName: intl.formatMessage({id: 'mobile.search.team.all_teams', defaultMessage: 'All teams'})} as TeamModel;
|
||||
}
|
||||
|
||||
const title = intl.formatMessage({id: 'mobile.search.team.select', defaultMessage: 'Select a team to search'});
|
||||
|
||||
|
|
@ -69,6 +68,7 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
|
|||
teams={teams}
|
||||
teamId={teamId}
|
||||
title={title}
|
||||
crossTeamSearchEnabled={crossTeamSearchEnabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -98,23 +98,18 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
|
|||
onPress={handleTeamChange}
|
||||
type='opacity'
|
||||
testID='team_picker.button'
|
||||
style={styles.teamPicker}
|
||||
>
|
||||
<View style={[styles.teamContainer, divider && styles.border]}>
|
||||
<View style={[styles.teamIcon, {width: size, height: size}]}>
|
||||
<TeamIcon
|
||||
displayName={selectedTeam.displayName}
|
||||
id={selectedTeam.id}
|
||||
lastIconUpdate={selectedTeam.lastTeamIconUpdatedAt}
|
||||
textColor={theme.centerChannelColor}
|
||||
backgroundColor={changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
selected={false}
|
||||
testID={`team_picker.${selectedTeam.id}.team_icon`}
|
||||
smallText={true}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
<Text
|
||||
id={selectedTeam.id}
|
||||
numberOfLines={1}
|
||||
style={styles.teamName}
|
||||
>{selectedTeam.displayName}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<CompassIcon
|
||||
color={changeOpacity(theme.centerChannelColor, 0.56)}
|
||||
style={styles.compass}
|
||||
name='menu-down'
|
||||
size={MENU_DOWN_ICON_SIZE}
|
||||
/>
|
||||
|
|
@ -124,4 +119,4 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
|
|||
</>
|
||||
);
|
||||
};
|
||||
export default TeamPickerIcon;
|
||||
export default TeamPicker;
|
||||
|
|
@ -787,6 +787,7 @@
|
|||
"mobile.search.results": "{count} search {count, plural, one {result} other {results}}",
|
||||
"mobile.search.show_less": "Show less",
|
||||
"mobile.search.show_more": "Show more",
|
||||
"mobile.search.team.all_teams": "All teams",
|
||||
"mobile.search.team.select": "Select a team to search",
|
||||
"mobile.server_identifier.exists": "You are already connected to this server.",
|
||||
"mobile.server_link.error.text": "The link could not be found on this server.",
|
||||
|
|
|
|||
1
types/api/config.d.ts
vendored
1
types/api/config.d.ts
vendored
|
|
@ -121,6 +121,7 @@ interface ClientConfig {
|
|||
FeatureFlagCollapsedThreads?: string;
|
||||
FeatureFlagPostPriority?: string;
|
||||
FeatureFlagChannelBookmarks?: string;
|
||||
FeatureFlagExperimentalCrossTeamSearch?: string;
|
||||
FeatureFlagCustomProfileAttributes?: string;
|
||||
ForgotPasswordLink?: string;
|
||||
GfycatApiKey: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue