diff --git a/app/screens/home/search/results/filter.tsx b/app/components/files/file_filter.tsx similarity index 88% rename from app/screens/home/search/results/filter.tsx rename to app/components/files/file_filter.tsx index b2c65badf..9c2cd317a 100644 --- a/app/screens/home/search/results/filter.tsx +++ b/app/components/files/file_filter.tsx @@ -30,43 +30,51 @@ type FilterItem = { filterType: FileFilter; separator?: boolean; } - -const data: FilterItem[] = [ - { +export const FilterData = { + [FileFilters.ALL]: { id: t('screen.search.results.filter.all_file_types'), defaultMessage: 'All file types', filterType: FileFilters.ALL, - }, { + }, + [FileFilters.DOCUMENTS]: { id: t('screen.search.results.filter.documents'), defaultMessage: 'Documents', filterType: FileFilters.DOCUMENTS, - }, { + }, + [FileFilters.SPREADSHEETS]: { id: t('screen.search.results.filter.spreadsheets'), defaultMessage: 'Spreadsheets', filterType: FileFilters.SPREADSHEETS, - }, { + }, + [FileFilters.PRESENTATIONS]: { id: t('screen.search.results.filter.presentations'), defaultMessage: 'Presentations', filterType: FileFilters.PRESENTATIONS, - }, { + }, + [FileFilters.CODE]: { id: t('screen.search.results.filter.code'), defaultMessage: 'Code', filterType: FileFilters.CODE, - }, { + }, + [FileFilters.IMAGES]: { id: t('screen.search.results.filter.images'), defaultMessage: 'Images', filterType: FileFilters.IMAGES, - }, { + }, + [FileFilters.AUDIO]: { id: t('screen.search.results.filter.audio'), defaultMessage: 'Audio', filterType: FileFilters.AUDIO, - }, { + }, + [FileFilters.VIDEOS]: { id: t('screen.search.results.filter.videos'), defaultMessage: 'Videos', filterType: FileFilters.VIDEOS, separator: false, }, -]; +}; + +const data: FilterItem[] = Object.values(FilterData); export const NUMBER_FILTER_ITEMS = data.length; export const FILTER_ITEM_HEIGHT = ITEM_HEIGHT; @@ -78,7 +86,7 @@ type FilterProps = { title: string; } -const Filter = ({initialFilter, setFilter, title}: FilterProps) => { +const File_filter = ({initialFilter, setFilter, title}: FilterProps) => { const intl = useIntl(); const theme = useTheme(); const style = getStyleSheet(theme); @@ -122,4 +130,4 @@ const Filter = ({initialFilter, setFilter, title}: FilterProps) => { ); }; -export default Filter; +export default File_filter; diff --git a/app/screens/home/search/results/file_options/header.tsx b/app/components/files_search/file_options/header.tsx similarity index 100% rename from app/screens/home/search/results/file_options/header.tsx rename to app/components/files_search/file_options/header.tsx diff --git a/app/screens/home/search/results/file_options/icon.tsx b/app/components/files_search/file_options/icon.tsx similarity index 100% rename from app/screens/home/search/results/file_options/icon.tsx rename to app/components/files_search/file_options/icon.tsx diff --git a/app/screens/home/search/results/file_options/mobile_options.tsx b/app/components/files_search/file_options/mobile_options.tsx similarity index 100% rename from app/screens/home/search/results/file_options/mobile_options.tsx rename to app/components/files_search/file_options/mobile_options.tsx diff --git a/app/screens/home/search/results/file_options/option_menus/index.tsx b/app/components/files_search/file_options/option_menus/index.tsx similarity index 100% rename from app/screens/home/search/results/file_options/option_menus/index.tsx rename to app/components/files_search/file_options/option_menus/index.tsx diff --git a/app/screens/home/search/results/file_options/option_menus/option_menus.tsx b/app/components/files_search/file_options/option_menus/option_menus.tsx similarity index 100% rename from app/screens/home/search/results/file_options/option_menus/option_menus.tsx rename to app/components/files_search/file_options/option_menus/option_menus.tsx diff --git a/app/screens/home/search/results/file_options/tablet_options.tsx b/app/components/files_search/file_options/tablet_options.tsx similarity index 100% rename from app/screens/home/search/results/file_options/tablet_options.tsx rename to app/components/files_search/file_options/tablet_options.tsx diff --git a/app/screens/home/search/results/file_options/toasts.tsx b/app/components/files_search/file_options/toasts.tsx similarity index 100% rename from app/screens/home/search/results/file_options/toasts.tsx rename to app/components/files_search/file_options/toasts.tsx diff --git a/app/screens/home/search/results/file_result.tsx b/app/components/files_search/file_result.tsx similarity index 99% rename from app/screens/home/search/results/file_result.tsx rename to app/components/files_search/file_result.tsx index 9a536e71b..424378a2f 100644 --- a/app/screens/home/search/results/file_result.tsx +++ b/app/components/files_search/file_result.tsx @@ -23,7 +23,7 @@ const styles = StyleSheet.create({ type Props = { canDownloadFiles: boolean; - channelName: string | undefined; + channelName?: string; fileInfo: FileInfo; index: number; numOptions: number; diff --git a/app/screens/home/search/results/file_results.tsx b/app/components/files_search/file_results.tsx similarity index 81% rename from app/screens/home/search/results/file_results.tsx rename to app/components/files_search/file_results.tsx index 7912cac36..a20064286 100644 --- a/app/screens/home/search/results/file_results.tsx +++ b/app/components/files_search/file_results.tsx @@ -5,6 +5,7 @@ import React, {useCallback, useMemo, useState} from 'react'; import {FlatList, ListRenderItemInfo, StyleProp, ViewStyle} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import NoResults from '@components/files_search/no_results'; import NoResultsWithTerm from '@components/no_results_with_term'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; @@ -34,6 +35,8 @@ type Props = { paddingTop: StyleProp; publicLinkEnabled: boolean; searchValue: string; + isChannelFiles?: boolean; + isFilterEnabled?: boolean; } const galleryIdentifier = 'search-files-location'; @@ -45,6 +48,8 @@ const FileResults = ({ paddingTop, publicLinkEnabled, searchValue, + isChannelFiles, + isFilterEnabled, }: Props) => { const theme = useTheme(); const insets = useSafeAreaInsets(); @@ -53,16 +58,16 @@ const FileResults = ({ const [action, setAction] = useState('none'); const [lastViewedFileInfo, setLastViewedFileInfo] = useState(undefined); - const containerStyle = useMemo(() => ([paddingTop, {top: fileInfos.length ? 8 : 0}]), [fileInfos, paddingTop]); + const containerStyle = useMemo(() => ([paddingTop, {top: fileInfos.length ? 8 : 0, flexGrow: 1}]), [fileInfos, paddingTop]); const numOptions = getNumberFileMenuOptions(canDownloadFiles, publicLinkEnabled); const {images: imageAttachments, nonImages: nonImageAttachments} = useImageAttachments(fileInfos, publicLinkEnabled); - const filesForGallery = imageAttachments.concat(nonImageAttachments); + const filesForGallery = useMemo(() => imageAttachments.concat(nonImageAttachments), [imageAttachments, nonImageAttachments]); const channelNames = useMemo(() => getChannelNamesWithID(fileChannels), [fileChannels]); - const orderedFileInfos = useMemo(() => getOrderedFileInfos(filesForGallery), []); - const fileInfosIndexes = useMemo(() => getFileInfosIndexes(orderedFileInfos), []); - const orderedGalleryItems = useMemo(() => getOrderedGalleryItems(orderedFileInfos), []); + const orderedFileInfos = useMemo(() => getOrderedFileInfos(filesForGallery), [filesForGallery]); + const fileInfosIndexes = useMemo(() => getFileInfosIndexes(orderedFileInfos), [orderedFileInfos]); + const orderedGalleryItems = useMemo(() => getOrderedGalleryItems(orderedFileInfos), [orderedFileInfos]); const onPreviewPress = useCallback(preventDoubleTap((idx: number) => { openGalleryAtIndex(galleryIdentifier, idx, orderedGalleryItems); @@ -88,10 +93,15 @@ const FileResults = ({ }, [insets, isTablet, numOptions, theme]); const renderItem = useCallback(({item}: ListRenderItemInfo) => { + let channelName: string | undefined; + if (!isChannelFiles && item.channel_id) { + channelName = channelNames[item.channel_id]; + } + return ( ( - - ), [searchValue]); + const noResults = useMemo(() => { + if (!searchValue && isChannelFiles) { + return ( + + ); + } + return ( + + ); + }, [searchValue]); return ( <> diff --git a/app/components/files_search/no_results.tsx b/app/components/files_search/no_results.tsx new file mode 100644 index 000000000..581b6f4ae --- /dev/null +++ b/app/components/files_search/no_results.tsx @@ -0,0 +1,81 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React from 'react'; +import {View} from 'react-native'; + +import FormattedText from '@components/formatted_text'; +import SearchFilesIllustration from '@components/no_results_with_term/search_files_illustration'; +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +const TEST_ID = 'channel_files'; + +const getStyleSheet = makeStyleSheetFromTheme((theme) => ({ + container: { + flexGrow: 1, + paddingHorizontal: 32, + height: '100%', + alignItems: 'center' as const, + justifyContent: 'center' as const, + }, + title: { + color: theme.centerChannelColor, + textAlign: 'center', + ...typography('Heading', 400, 'SemiBold'), + }, + paragraph: { + marginTop: 8, + textAlign: 'center', + color: changeOpacity(theme.centerChannelColor, 0.72), + ...typography('Body', 200), + }, +})); + +type Props = { + isFilterEnabled?: boolean; +} +function NoResults({isFilterEnabled}: Props) { + const theme = useTheme(); + const styles = getStyleSheet(theme); + + return ( + + + {!isFilterEnabled && ( + <> + + + + )} + {isFilterEnabled && ( + <> + + + + )} + + ); +} + +export default NoResults; diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 087a82020..9a5aca00a 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -9,6 +9,7 @@ export const BROWSE_CHANNELS = 'BrowseChannels'; export const CALL = 'Call'; export const CHANNEL = 'Channel'; export const CHANNEL_ADD_MEMBERS = 'ChannelAddMembers'; +export const CHANNEL_FILES = 'ChannelFiles'; export const CHANNEL_INFO = 'ChannelInfo'; export const CHANNEL_NOTIFICATION_PREFERENCES = 'ChannelNotificationPreferences'; export const CODE = 'Code'; @@ -78,6 +79,7 @@ export default { CALL, CHANNEL, CHANNEL_ADD_MEMBERS, + CHANNEL_FILES, CHANNEL_INFO, CHANNEL_NOTIFICATION_PREFERENCES, CODE, diff --git a/app/screens/channel_files/channel_files.tsx b/app/screens/channel_files/channel_files.tsx new file mode 100644 index 000000000..0710ffc42 --- /dev/null +++ b/app/screens/channel_files/channel_files.tsx @@ -0,0 +1,172 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {useIntl} from 'react-intl'; +import {Platform, StyleSheet, View} from 'react-native'; +import {Edge, SafeAreaView} from 'react-native-safe-area-context'; + +import {searchFiles} from '@actions/remote/search'; +import FileResults from '@components/files_search/file_results'; +import Loading from '@components/loading'; +import Search from '@components/search'; +import {General} from '@constants'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import {FileFilter, FileFilters, filterFileExtensions} from '@utils/file'; +import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme'; + +import Header from './header'; + +import type ChannelModel from '@typings/database/models/servers/channel'; + +const TEST_ID = 'channel_files'; + +type Props = { + channel: ChannelModel; + canDownloadFiles: boolean; + publicLinkEnabled: boolean; +} + +const edges: Edge[] = ['bottom', 'left', 'right']; + +const styles = StyleSheet.create({ + flex: { + flex: 1, + }, + empty: { + alignItems: 'center', + flex: 1, + justifyContent: 'center', + }, + list: { + paddingVertical: 8, + }, + loading: { + justifyContent: 'center', + flex: 1, + }, + searchBar: { + marginLeft: 20, + marginRight: Platform.select({ios: 12, default: 20}), + marginTop: 20, + }, + noPaddingTop: {paddingTop: 0}, +}); + +const getSearchParams = (channelId: string, searchTerm?: string, filterValue?: FileFilter) => { + const term = `channel:${channelId}`; + const fileExtensions = filterFileExtensions(filterValue); + const extensionTerms = fileExtensions ? ' ' + fileExtensions : ''; + const searchTerms = searchTerm ? ' ' + searchTerm : ''; + return { + terms: term + searchTerms + extensionTerms, + is_or_search: true, + }; +}; + +const emptyFileResults: FileInfo[] = []; + +function ChannelFiles({ + channel, + canDownloadFiles, + publicLinkEnabled, +}: Props) { + const theme = useTheme(); + const serverUrl = useServerUrl(); + const {formatMessage} = useIntl(); + const searchTimeoutId = useRef(null); + const [filter, setFilter] = useState(FileFilters.ALL); + const [loading, setLoading] = useState(true); + const [term, setTerm] = useState(''); + const lastSearchRequest = useRef(); + + const [fileInfos, setFileInfos] = useState(emptyFileResults); + + const handleSearch = useCallback(async (searchTerm: string, ftr: FileFilter) => { + const t = Date.now(); + lastSearchRequest.current = t; + const searchParams = getSearchParams(channel.id, searchTerm, ftr); + const {files} = await searchFiles(serverUrl, channel.teamId, searchParams); + if (lastSearchRequest.current !== t) { + return; + } + setFileInfos(files?.length ? files : emptyFileResults); + setLoading(false); + }, [serverUrl, channel]); + + useEffect(() => { + if (searchTimeoutId.current) { + clearTimeout(searchTimeoutId.current); + } + + searchTimeoutId.current = setTimeout(() => { + handleSearch(term, filter); + }, General.SEARCH_TIMEOUT_MILLISECONDS); + }, [filter, term, handleSearch]); + + const handleFilterChange = useCallback(async (filterValue: FileFilter) => { + setLoading(true); + setFilter(filterValue); + }, []); + + const clearSearch = useCallback(() => { + setTerm(''); + }, []); + + const onTextChange = useCallback((searchTerm: string) => { + if (term !== searchTerm) { + setLoading(true); + setTerm(searchTerm); + } + }, [term]); + + const fileChannels = useMemo(() => [channel], [channel]); + + return ( + + + + +
+ {loading && + + } + {!loading && + + } + + ); +} + +export default ChannelFiles; diff --git a/app/screens/channel_files/header.tsx b/app/screens/channel_files/header.tsx new file mode 100644 index 000000000..7bafc780b --- /dev/null +++ b/app/screens/channel_files/header.tsx @@ -0,0 +1,132 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React, {useCallback, useMemo} from 'react'; +import {useIntl} from 'react-intl'; +import {Text, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; + +import Badge from '@components/badge'; +import CompassIcon from '@components/compass_icon'; +import Filter, {DIVIDERS_HEIGHT, FILTER_ITEM_HEIGHT, FilterData, NUMBER_FILTER_ITEMS} from '@components/files/file_filter'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {t} from '@i18n'; +import {TITLE_SEPARATOR_MARGIN, TITLE_SEPARATOR_MARGIN_TABLET, TITLE_HEIGHT} from '@screens/bottom_sheet/content'; +import {bottomSheet} from '@screens/navigation'; +import {FileFilter, FileFilters} from '@utils/file'; +import {bottomSheetSnapPoint} from '@utils/helpers'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +const TEST_ID = 'channel_files'; + +type Props = { + onFilterChanged: (filter: FileFilter) => void; + selectedFilter: FileFilter; +} + +const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 20, + paddingTop: 20, + backgroundColor: theme.centerChannelBg, + }, + title: { + color: theme.centerChannelColor, + ...typography('Heading', 300, 'SemiBold'), + }, + badge: { + backgroundColor: theme.buttonBg, + borderColor: theme.centerChannelBg, + marginTop: 2, + }, + iconsContainer: { + alignItems: 'center', + flexDirection: 'row', + marginLeft: 'auto', + }, + }; +}); + +const Header = ({ + onFilterChanged, + selectedFilter, +}: Props) => { + const theme = useTheme(); + const styles = getStyleFromTheme(theme); + const intl = useIntl(); + const {bottom} = useSafeAreaInsets(); + const isTablet = useIsTablet(); + const hasFilters = selectedFilter !== FileFilters.ALL; + const messageObject = hasFilters ? { + id: FilterData[selectedFilter].id, + defaultMessage: FilterData[selectedFilter].defaultMessage, + } : { + id: t('screen.channel_files.header.recent_files'), + defaultMessage: 'Recent Files', + }; + + const messagesText = intl.formatMessage(messageObject); + const title = intl.formatMessage({id: 'screen.channel_files.results.filter.title', defaultMessage: 'Filter by file type'}); + + const snapPoints = useMemo(() => { + return [ + 1, + bottomSheetSnapPoint( + NUMBER_FILTER_ITEMS, + FILTER_ITEM_HEIGHT, + bottom, + ) + TITLE_HEIGHT + DIVIDERS_HEIGHT + (isTablet ? TITLE_SEPARATOR_MARGIN_TABLET : TITLE_SEPARATOR_MARGIN), + ]; + }, []); + + const handleFilterPress = useCallback(() => { + const renderContent = () => { + return ( + + ); + }; + bottomSheet({ + closeButtonId: 'close-search-filters', + renderContent, + snapPoints, + theme, + title, + }); + }, [onFilterChanged, selectedFilter]); + + return ( + + + {messagesText} + + + + + + + ); +}; + +export default Header; + diff --git a/app/screens/channel_files/index.ts b/app/screens/channel_files/index.ts new file mode 100644 index 000000000..9979852e5 --- /dev/null +++ b/app/screens/channel_files/index.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; + +import {observeChannel} from '@queries/servers/channel'; +import {observeCanDownloadFiles, observeConfigBooleanValue} from '@queries/servers/system'; + +import ChannelFiles from './channel_files'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +type Props = WithDatabaseArgs & { + channelId: string; +} + +const enhance = withObservables(['channelId'], ({channelId, database}: Props) => { + const channel = observeChannel(database, channelId); + return { + channel, + canDownloadFiles: observeCanDownloadFiles(database), + publicLinkEnabled: observeConfigBooleanValue(database, 'EnablePublicLink'), + }; +}); + +export default withDatabase(enhance(ChannelFiles)); diff --git a/app/screens/channel_info/options/channel_files/channel_files.tsx b/app/screens/channel_info/options/channel_files/channel_files.tsx new file mode 100644 index 000000000..447c9f8dd --- /dev/null +++ b/app/screens/channel_info/options/channel_files/channel_files.tsx @@ -0,0 +1,53 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; +import {Platform} from 'react-native'; + +import OptionItem from '@components/option_item'; +import {Screens} from '@constants'; +import {useTheme} from '@context/theme'; +import {goToScreen} from '@screens/navigation'; +import {preventDoubleTap} from '@utils/tap'; +import {changeOpacity} from '@utils/theme'; + +type Props = { + channelId: string; + count: number; + displayName: string; +} + +const ChannelFiles = ({channelId, count, displayName}: Props) => { + const theme = useTheme(); + const {formatMessage} = useIntl(); + const title = formatMessage({id: 'channel_info.channel_files', defaultMessage: 'Files'}); + + const goToChannelFiles = preventDoubleTap(() => { + const options = { + topBar: { + title: { + text: title, + }, + subtitle: { + color: changeOpacity(theme.sidebarHeaderTextColor, 0.72), + text: displayName, + }, + }, + }; + goToScreen(Screens.CHANNEL_FILES, title, {channelId}, options); + }); + + return ( + + ); +}; + +export default ChannelFiles; diff --git a/app/screens/channel_info/options/channel_files/index.ts b/app/screens/channel_info/options/channel_files/index.ts new file mode 100644 index 000000000..bc819c9e1 --- /dev/null +++ b/app/screens/channel_info/options/channel_files/index.ts @@ -0,0 +1,33 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; +import {of as of$} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; + +import {observeChannel, observeChannelInfo} from '@queries/servers/channel'; + +import ChannelFiles from './channel_files'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +type Props = WithDatabaseArgs & { + channelId: string; +} + +const enhanced = withObservables(['channelId'], ({channelId, database}: Props) => { + const channel = observeChannel(database, channelId); + const info = observeChannelInfo(database, channelId); + const count = info.pipe( + switchMap((i) => of$(i?.filesCount || 0)), + ); + const displayName = channel.pipe(switchMap((c) => of$(c?.displayName))); + + return { + count, + displayName, + }; +}); + +export default withDatabase(enhanced(ChannelFiles)); diff --git a/app/screens/channel_info/options/index.tsx b/app/screens/channel_info/options/index.tsx index 93fd1f94e..dab34eefb 100644 --- a/app/screens/channel_info/options/index.tsx +++ b/app/screens/channel_info/options/index.tsx @@ -8,6 +8,7 @@ import {General} from '@constants'; import {isTypeDMorGM} from '@utils/channel'; import AddMembers from './add_members'; +import ChannelFiles from './channel_files'; import EditChannel from './edit_channel'; import IgnoreMentions from './ignore_mentions'; import Members from './members'; @@ -36,6 +37,7 @@ const Options = ({ } + {type !== General.DM_CHANNEL && } diff --git a/app/screens/home/search/results/header.tsx b/app/screens/home/search/results/header.tsx index 17abcdfdb..36b22fcfa 100644 --- a/app/screens/home/search/results/header.tsx +++ b/app/screens/home/search/results/header.tsx @@ -7,6 +7,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'; import Badge from '@components/badge'; import CompassIcon from '@components/compass_icon'; +import Filter, {DIVIDERS_HEIGHT, FILTER_ITEM_HEIGHT, NUMBER_FILTER_ITEMS} from '@components/files/file_filter'; 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'; @@ -17,7 +18,6 @@ import {bottomSheetSnapPoint} from '@utils/helpers'; import {TabTypes, TabType} from '@utils/search'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import Filter, {DIVIDERS_HEIGHT, FILTER_ITEM_HEIGHT, NUMBER_FILTER_ITEMS} from './filter'; import SelectButton from './header_button'; import type TeamModel from '@typings/database/models/servers/team'; @@ -45,7 +45,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => { badge: { backgroundColor: theme.buttonBg, borderColor: theme.centerChannelBg, - marginTop: 8, + marginTop: 2, }, buttonsContainer: { marginBottom: 12, diff --git a/app/screens/home/search/results/post_results.tsx b/app/screens/home/search/results/post_results.tsx index 92edfb4a9..a86ae13cf 100644 --- a/app/screens/home/search/results/post_results.tsx +++ b/app/screens/home/search/results/post_results.tsx @@ -34,7 +34,7 @@ const PostResults = ({ searchValue, }: Props) => { const orderedPosts = useMemo(() => selectOrderedPosts(posts, 0, false, '', '', false, isTimezoneEnabled, currentTimezone, false).reverse(), [posts]); - const containerStyle = useMemo(() => ({top: posts.length ? 4 : 8}), [posts]); + const containerStyle = useMemo(() => ({top: posts.length ? 4 : 8, flexGrow: 1}), [posts]); const renderItem = useCallback(({item}: ListRenderItemInfo) => { switch (item.type) { diff --git a/app/screens/home/search/results/results.tsx b/app/screens/home/search/results/results.tsx index 6cedb3e65..829d2d75a 100644 --- a/app/screens/home/search/results/results.tsx +++ b/app/screens/home/search/results/results.tsx @@ -5,11 +5,11 @@ import React, {useMemo} from 'react'; import {StyleSheet, useWindowDimensions, View} from 'react-native'; import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'; +import FileResults from '@components/files_search/file_results'; import Loading from '@components/loading'; import {useTheme} from '@context/theme'; import {TabTypes, TabType} from '@utils/search'; -import FileResults from './file_results'; import PostResults from './post_results'; import type ChannelModel from '@typings/database/models/servers/channel'; @@ -84,7 +84,7 @@ const Results = ({ }, [selectedTab, width, !loading]); const paddingTop = useMemo(() => ( - {paddingTop: scrollPaddingTop, flexGrow: 1} + {paddingTop: scrollPaddingTop} ), [scrollPaddingTop]); return ( diff --git a/app/screens/index.tsx b/app/screens/index.tsx index 7dda3bdc6..d33108196 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -82,6 +82,9 @@ Navigation.setLazyComponentRegistrator((screenName) => { case Screens.CHANNEL_NOTIFICATION_PREFERENCES: screen = withServerDatabase(require('@screens/channel_notification_preferences').default); break; + case Screens.CHANNEL_FILES: + screen = withServerDatabase(require('@screens/channel_files').default); + break; case Screens.CHANNEL_INFO: screen = withServerDatabase(require('@screens/channel_info').default); break; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index f237a2b9b..f78528d98 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -96,6 +96,10 @@ "camera_type.video.option": "Record Video", "center_panel.archived.closeChannel": "Close Channel", "channel_add_members.add_members.button": "Add Members", + "channel_files.empty.paragraph": "Files posted in this channel will show here.", + "channel_files.empty.title": "No files yet", + "channel_files.noFiles.paragraph": "This channel doesn't contain any files with the applied filters", + "channel_files.noFiles.title": "No files Found", "channel_header.directchannel.you": "{displayName} (you)", "channel_header.info": "View info", "channel_header.member_count": "{count, plural, one {# member} other {# members}}", @@ -108,6 +112,7 @@ "channel_info.archive_description.cannot_view_archived": "This will archive the channel from the team and remove it from the user interface. Archived channels can be unarchived if needed again.\n\nAre you sure you wish to archive the {term} {name}?", "channel_info.archive_failed": "An error occurred trying to archive the channel {displayName}", "channel_info.archive_title": "Archive {term}", + "channel_info.channel_files": "Files", "channel_info.close": "Close", "channel_info.close_dm": "Close direct message", "channel_info.close_dm_channel": "Are you sure you want to close this direct message? This will remove it from your home screen, but you can always open it again.", @@ -822,6 +827,8 @@ "rate.title": "Enjoying Mattermost?", "saved_messages.empty.paragraph": "To save something for later, long-press on a message and choose Save from the menu. Saved messages are only visible to you.", "saved_messages.empty.title": "No saved messages yet", + "screen.channel_files.header.recent_files": "Recent Files", + "screen.channel_files.results.filter.title": "Filter by file type", "screen.mentions.subtitle": "Messages you've been mentioned in", "screen.mentions.title": "Recent Mentions", "screen.saved_messages.subtitle": "All messages you've saved for follow up",