From 196f922b6a36acc3509f0e06d2faaf01831a2fc7 Mon Sep 17 00:00:00 2001 From: Jason Frerich Date: Wed, 15 Jun 2022 08:29:32 -0500 Subject: [PATCH] [Gekidou - MM-44645] Search Screen - show results from server (#6314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Espino GarcĂ­a Co-authored-by: Elias Nahum --- app/actions/remote/search.ts | 85 +++++---- app/client/rest/files.ts | 12 ++ app/client/rest/posts.ts | 2 +- .../at_mention_group/at_mention_group.tsx | 2 +- app/components/user_item/user_item.tsx | 2 +- app/constants/files.ts | 13 +- app/screens/bottom_sheet/button.tsx | 56 +++--- app/screens/bottom_sheet/content.tsx | 8 +- .../channel_info/options/members/members.tsx | 2 +- .../notification_preference.tsx | 4 +- .../pinned_messages/pinned_messages.tsx | 2 +- .../title/direct_message/direct_message.tsx | 2 +- .../categories/body/category_body.tsx | 2 +- app/screens/home/index.tsx | 7 +- app/screens/home/search/index.tsx | 157 ++------------- app/screens/home/search/results/fileCard.tsx | 53 ++++++ app/screens/home/search/results/filter.tsx | 156 +++++++++++++++ app/screens/home/search/results/header.tsx | 77 +++++++- app/screens/home/search/results/index.tsx | 37 ++++ app/screens/home/search/results/loader.tsx | 30 +++ app/screens/home/search/results/results.tsx | 142 ++++++++++---- app/screens/home/search/search.tsx | 179 ++++++++++++++++++ app/utils/file/index.ts | 18 ++ assets/base/i18n/en.json | 10 + types/api/files.d.ts | 5 + 25 files changed, 784 insertions(+), 279 deletions(-) create mode 100644 app/screens/home/search/results/fileCard.tsx create mode 100644 app/screens/home/search/results/filter.tsx create mode 100644 app/screens/home/search/results/index.tsx create mode 100644 app/screens/home/search/results/loader.tsx create mode 100644 app/screens/home/search/search.tsx diff --git a/app/actions/remote/search.ts b/app/actions/remote/search.ts index b5c00ef988..3ebe14da2e 100644 --- a/app/actions/remote/search.ts +++ b/app/actions/remote/search.ts @@ -7,7 +7,6 @@ import NetworkManager from '@managers/network_manager'; import {prepareMissingChannelsForAllTeams} from '@queries/servers/channel'; import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import {processPostsFetched} from '@utils/post'; import {fetchPostAuthors, fetchMissingChannelsFromPosts} from './post'; import {forceLogoutIfNecessary} from './session'; @@ -15,6 +14,14 @@ import {forceLogoutIfNecessary} from './session'; import type {Client} from '@client/rest'; import type Model from '@nozbe/watermelondb/Model'; +type FileSearchRequest = { + error?: unknown; + file_infos?: {[id: string]: FileInfo}; + next_file_info_id?: string; + order?: string[]; + prev_file_info_id?: string; +} + type PostSearchRequest = { error?: unknown; order?: string[]; @@ -22,25 +29,9 @@ type PostSearchRequest = { } export async function fetchRecentMentions(serverUrl: string): Promise { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - let client: Client; try { - client = NetworkManager.getClient(serverUrl); - } catch (error) { - return {error}; - } - - let posts: Record = {}; - let postsArray: Post[] = []; - let order: string[] = []; - - try { - const currentUser = await getCurrentUser(operator.database); + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentUser = await getCurrentUser(database); if (!currentUser) { return { posts: [], @@ -48,17 +39,15 @@ export async function fetchRecentMentions(serverUrl: string): Promise key).join(' ').trim() + ' '; - const data = await client.searchPosts('', terms, true); - - posts = data.posts || {}; - order = data.order || []; + const results = await searchPosts(serverUrl, {terms, is_or_search: true}); + if (results.error) { + throw results.error; + } const promises: Array> = []; - postsArray = order.map((id) => posts[id]); - const mentions: IdValue = { id: SYSTEM_IDENTIFIERS.RECENT_MENTIONS, - value: JSON.stringify(order), + value: JSON.stringify(results.order), }; promises.push(operator.handleSystem({ @@ -66,6 +55,25 @@ export async function fetchRecentMentions(serverUrl: string): Promise => { + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const client = NetworkManager.getClient(serverUrl); + + let postsArray: Post[] = []; + const data = await client.searchPosts('', params.terms, params.is_or_search); + + const posts = data.posts || {}; + const order = data.order || []; + + const promises: Array> = []; + postsArray = order.map((id) => posts[id]); if (postsArray.length) { const isCRTEnabled = await getIsCRTEnabled(operator.database); if (isCRTEnabled) { @@ -111,18 +119,19 @@ export async function fetchRecentMentions(serverUrl: string): Promise => { +export const searchFiles = async (serverUrl: string, teamId: string, params: FileSearchParams): Promise => { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { @@ -138,17 +147,11 @@ export const searchPosts = async (serverUrl: string, params: PostSearchParams): let data; try { - data = await client.searchPosts('', params.terms, params.is_or_search); + data = await client.searchFiles(teamId, params.terms); } catch (error) { forceLogoutIfNecessary(serverUrl, error as ClientErrorProps); return {error}; } - const result = processPostsFetched(data); - await operator.handlePosts({ - ...result, - actionType: '', - }); - - return result; + return data; }; diff --git a/app/client/rest/files.ts b/app/client/rest/files.ts index 1b5e2d1061..df057fdc4e 100644 --- a/app/client/rest/files.ts +++ b/app/client/rest/files.ts @@ -16,6 +16,8 @@ export interface ClientFilesMix { onError: (response: ClientResponseError) => void, skipBytes?: number, ) => () => void; + searchFiles: (teamId: string, terms: string) => Promise; + searchFilesWithParams: (teamId: string, FileSearchParams: string) => Promise; } const ClientFiles = (superclass: any) => class extends superclass { @@ -75,6 +77,16 @@ const ClientFiles = (superclass: any) => class extends superclass { promise.progress!(onProgress).then(onComplete).catch(onError); return promise.cancel!; }; + + searchFilesWithParams = async (teamId: string, params: FileSearchParams) => { + this.analytics.trackAPI('api_files_search'); + const endpoint = teamId ? `${this.getTeamRoute(teamId)}/files/search` : `${this.getFilesRoute()}/search`; + return this.doFetch(endpoint, {method: 'post', body: params}); + }; + + searchFiles = async (teamId: string, terms: string, isOrSearch: boolean) => { + return this.searchFilesWithParams(teamId, {terms, is_or_search: isOrSearch}); + }; }; export default ClientFiles; diff --git a/app/client/rest/posts.ts b/app/client/rest/posts.ts index 1cd1b51e5e..18b7f94d9e 100644 --- a/app/client/rest/posts.ts +++ b/app/client/rest/posts.ts @@ -26,7 +26,7 @@ export interface ClientPostsMix { removeReaction: (userId: string, postId: string, emojiName: string) => Promise; getReactionsForPost: (postId: string) => Promise; searchPostsWithParams: (teamId: string, params: PostSearchParams) => Promise; - searchPosts: (teamId: string, terms: string, isOrSearch: boolean) => Promise; + searchPosts: (teamId: string, terms: string, isOrSearch: boolean) => Promise; doPostAction: (postId: string, actionId: string, selectedOption?: string) => Promise; doPostActionWithCookie: (postId: string, actionId: string, actionCookie: string, selectedOption?: string) => Promise; } diff --git a/app/components/autocomplete/at_mention_group/at_mention_group.tsx b/app/components/autocomplete/at_mention_group/at_mention_group.tsx index 717d4a2579..fe74f4a96d 100644 --- a/app/components/autocomplete/at_mention_group/at_mention_group.tsx +++ b/app/components/autocomplete/at_mention_group/at_mention_group.tsx @@ -8,11 +8,11 @@ import { } from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import {typography} from '@app/utils/typography'; import CompassIcon from '@components/compass_icon'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {useTheme} from '@context/theme'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; +import {typography} from '@utils/typography'; const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { diff --git a/app/components/user_item/user_item.tsx b/app/components/user_item/user_item.tsx index 1c95f8f41f..17fc294b2f 100644 --- a/app/components/user_item/user_item.tsx +++ b/app/components/user_item/user_item.tsx @@ -5,7 +5,6 @@ import React from 'react'; import {IntlShape, useIntl} from 'react-intl'; import {StyleProp, Text, View, ViewStyle} from 'react-native'; -import {typography} from '@app/utils/typography'; import ChannelIcon from '@components/channel_icon'; import CustomStatusEmoji from '@components/custom_status/custom_status_emoji'; import FormattedText from '@components/formatted_text'; @@ -14,6 +13,7 @@ import {BotTag, GuestTag} from '@components/tag'; import {General} from '@constants'; import {useTheme} from '@context/theme'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; +import {typography} from '@utils/typography'; import {getUserCustomStatus, isBot, isGuest, isShared} from '@utils/user'; import type UserModel from '@typings/database/models/servers/user'; diff --git a/app/constants/files.ts b/app/constants/files.ts index 8968d9934f..12d34ae1bc 100644 --- a/app/constants/files.ts +++ b/app/constants/files.ts @@ -30,17 +30,18 @@ export const VALID_IMAGE_MIME_TYPES = [ const Files: Record = { AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac', 'ogg'], - CODE_TYPES: ['as', 'applescript', 'osascript', 'scpt', 'bash', 'sh', 'zsh', 'clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic', 'coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced', 'cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp', 'cs', 'csharp', 'css', 'd', 'di', 'dart', 'delphi', 'dpr', 'dfm', 'pas', 'pascal', 'freepascal', 'lazarus', 'lpr', 'lfm', 'diff', 'django', 'jinja', 'dockerfile', 'docker', 'erl', 'f90', 'f95', 'fsharp', 'fs', 'gcode', 'nc', 'go', 'groovy', 'handlebars', 'hbs', 'html.hbs', 'html.handlebars', 'hs', 'hx', 'java', 'jsp', 'js', 'jsx', 'json', 'jl', 'kt', 'ktm', 'kts', 'less', 'lisp', 'lua', 'mk', 'mak', 'md', 'mkdown', 'mkd', 'matlab', 'm', 'mm', 'objc', 'obj-c', 'ml', 'perl', 'pl', 'php', 'php3', 'php4', 'php5', 'php6', 'ps', 'ps1', 'pp', 'py', 'gyp', 'r', 'ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb', 'rs', 'scala', 'scm', 'sld', 'scss', 'st', 'sql', 'swift', 'tex', 'vbnet', 'vb', 'bas', 'vbs', 'v', 'veo', 'xml', 'html', 'xhtml', 'rss', 'atom', 'xsl', 'plist', 'yaml'], - IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg', 'tiff', 'tif'], + CODE_TYPES: ['as', 'applescript', 'osascript', 'scpt', 'bash', 'sh', 'zsh', 'clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic', 'coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced', 'cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp', 'cs', 'csharp', 'css', 'd', 'di', 'dart', 'delphi', 'dpr', 'dfm', 'pas', 'pascal', 'freepascal', 'lazarus', 'lpr', 'lfm', 'diff', 'django', 'jinja', 'dockerfile', 'docker', 'erl', 'f90', 'f95', 'fsharp', 'fs', 'gcode', 'nc', 'go', 'groovy', 'handlebars', 'hbs', 'html.hbs', 'html.handlebars', 'hs', 'hx', 'java', 'jsp', 'js', 'jsx', 'json', 'jl', 'kt', 'ktm', 'kts', 'less', 'lisp', 'lua', 'mk', 'mak', 'md', 'mkdown', 'mkd', 'matlab', 'm', 'mm', 'objc', 'obj-c', 'ml', 'perl', 'pl', 'php', 'php3', 'php4', 'php5', 'php6', 'ps', 'ps1', 'pp', 'py', 'gyp', 'r', 'ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb', 'rs', 'scala', 'scm', 'sld', 'scss', 'st', 'sql', 'swift', 'ts', 'tex', 'vbnet', 'vb', 'bas', 'vbs', 'v', 'veo', 'xml', 'html', 'xhtml', 'rss', 'atom', 'xsl', 'plist', 'yaml'], + IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg', 'tiff', 'tif', 'svg', 'psd', 'xcf'], PATCH_TYPES: ['patch'], PDF_TYPES: ['pdf'], - PRESENTATION_TYPES: ['ppt', 'pptx'], - SPREADSHEET_TYPES: ['xlsx', 'csv'], + PRESENTATION_TYPES: ['ppt', 'pptx', 'odp'], + SPREADSHEET_TYPES: ['xls, xlsx', 'csv', 'ods'], TEXT_TYPES: ['txt', 'rtf'], - VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'], - WORD_TYPES: ['doc', 'docx'], + VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv', 'ogm', 'mpeg'], + WORD_TYPES: ['doc', 'docx', 'odt'], ZIP_TYPES: ['zip'], }; +Files.DOCUMENT_TYPES = Files.WORD_TYPES.concat(Files.PDF_TYPES, Files.TEXT_TYPES); export const PROGRESS_TIME_TO_STORE = 60000; // 60 * 1000 (60s) diff --git a/app/screens/bottom_sheet/button.tsx b/app/screens/bottom_sheet/button.tsx index 74960caa2a..8bff6d715d 100644 --- a/app/screens/bottom_sheet/button.tsx +++ b/app/screens/bottom_sheet/button.tsx @@ -2,56 +2,48 @@ // See LICENSE.txt for license information. import React from 'react'; -import {GestureResponderEvent, Text, View} from 'react-native'; +import {GestureResponderEvent, StyleSheet, Text, View} from 'react-native'; import CompassIcon from '@components/compass_icon'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {useTheme} from '@context/theme'; -import {makeStyleSheetFromTheme} from '@utils/theme'; -import {typography} from '@utils/typography'; +import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; +import {changeOpacity} from '@utils/theme'; type Props = { + disabled?: boolean; onPress?: (e: GestureResponderEvent) => void; icon?: string; testID?: string; text?: string; } -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { - return { - button: { - backgroundColor: theme.buttonBg, - display: 'flex', - flexDirection: 'row', - paddingVertical: 14, - paddingHorizontal: 24, - borderRadius: 4, - alignItems: 'center', - justifyContent: 'center', - height: 48, - }, - text: { - color: theme.buttonColor, - paddingHorizontal: 8, - ...typography('Body', 200, 'SemiBold'), - }, - icon_container: { - width: 24, - height: 24, - marginTop: 2, - }, - }; +const styles = StyleSheet.create({ + button: { + display: 'flex', + flexDirection: 'row', + }, + icon_container: { + width: 24, + height: 24, + marginTop: 2, + }, }); -export default function BottomSheetButton({onPress, icon, testID, text}: Props) { +export default function BottomSheetButton({disabled = false, onPress, icon, testID, text}: Props) { const theme = useTheme(); - const styles = getStyleSheet(theme); + + const buttonType = disabled ? 'disabled' : 'default'; + const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType); + const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType); + + const iconColor = disabled ? changeOpacity(theme.centerChannelColor, 0.32) : theme.buttonColor; return ( {icon && ( @@ -59,13 +51,13 @@ export default function BottomSheetButton({onPress, icon, testID, text}: Props) )} {text && ( {text} )} diff --git a/app/screens/bottom_sheet/content.tsx b/app/screens/bottom_sheet/content.tsx index 5703657eea..cdaad3c275 100644 --- a/app/screens/bottom_sheet/content.tsx +++ b/app/screens/bottom_sheet/content.tsx @@ -14,11 +14,13 @@ type Props = { buttonIcon?: string; buttonText?: string; children: React.ReactNode; + disableButton?: boolean; onPress?: (e: GestureResponderEvent) => void; showButton: boolean; showTitle: boolean; testID?: string; title?: string; + titleSeparator?: boolean; } export const TITLE_HEIGHT = 38; @@ -45,7 +47,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }; }); -const BottomSheetContent = ({buttonText, buttonIcon, children, onPress, showButton, showTitle, testID, title}: Props) => { +const BottomSheetContent = ({buttonText, buttonIcon, children, disableButton, onPress, showButton, showTitle, testID, title, titleSeparator}: Props) => { const dimensions = useWindowDimensions(); const theme = useTheme(); const isTablet = useIsTablet(); @@ -68,6 +70,9 @@ const BottomSheetContent = ({buttonText, buttonIcon, children, onPress, showButt } + {titleSeparator && + + } <> {children} @@ -75,6 +80,7 @@ const BottomSheetContent = ({buttonText, buttonIcon, children, onPress, showButt <>