forked from Ivasoft/mattermost-mobile
[Gekidou MM-46370] Fix slow response time which switching between files and messages tabs (#6579)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import {ActionType, Post} from '@constants';
|
import {ActionType, Post} from '@constants';
|
||||||
import DatabaseManager from '@database/manager';
|
import DatabaseManager from '@database/manager';
|
||||||
import {getPostById, prepareDeletePost} from '@queries/servers/post';
|
import {getPostById, prepareDeletePost, queryPostsById} from '@queries/servers/post';
|
||||||
import {getCurrentUserId} from '@queries/servers/system';
|
import {getCurrentUserId} from '@queries/servers/system';
|
||||||
import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
import {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
||||||
import {generateId} from '@utils/general';
|
import {generateId} from '@utils/general';
|
||||||
@@ -233,3 +233,12 @@ export async function storePostsForChannel(
|
|||||||
return {error};
|
return {error};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPosts(serverUrl: string, ids: string[]) {
|
||||||
|
try {
|
||||||
|
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||||
|
return queryPostsById(database, ids).fetch();
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, l
|
|||||||
|
|
||||||
const updateFileForGallery = (idx: number, file: FileInfo) => {
|
const updateFileForGallery = (idx: number, file: FileInfo) => {
|
||||||
'worklet';
|
'worklet';
|
||||||
|
|
||||||
filesForGallery.value[idx] = file;
|
filesForGallery.value[idx] = file;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
196
app/screens/home/search/results/file_results.tsx
Normal file
196
app/screens/home/search/results/file_results.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||||
|
import {StyleSheet, FlatList, ListRenderItemInfo, StyleProp, View, ViewStyle} from 'react-native';
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import File from '@components/files/file';
|
||||||
|
import NoResultsWithTerm from '@components/no_results_with_term';
|
||||||
|
import {ITEM_HEIGHT} from '@components/option_item';
|
||||||
|
import {Screens} from '@constants';
|
||||||
|
import {useTheme} from '@context/theme';
|
||||||
|
import {useIsTablet} from '@hooks/device';
|
||||||
|
import {useImageAttachments} from '@hooks/files';
|
||||||
|
import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
|
||||||
|
import NavigationStore from '@store/navigation_store';
|
||||||
|
import {isImage, isVideo} from '@utils/file';
|
||||||
|
import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery';
|
||||||
|
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||||
|
import {getViewPortWidth} from '@utils/images';
|
||||||
|
import {TabTypes} from '@utils/search';
|
||||||
|
import {preventDoubleTap} from '@utils/tap';
|
||||||
|
|
||||||
|
import FileOptions from './file_options';
|
||||||
|
import {HEADER_HEIGHT} from './file_options/header';
|
||||||
|
|
||||||
|
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
canDownloadFiles: boolean;
|
||||||
|
fileChannels: ChannelModel[];
|
||||||
|
fileInfos: FileInfo[];
|
||||||
|
publicLinkEnabled: boolean;
|
||||||
|
paddingTop: StyleProp<ViewStyle>;
|
||||||
|
searchValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const galleryIdentifier = 'search-files-location';
|
||||||
|
|
||||||
|
const FileResults = ({
|
||||||
|
canDownloadFiles,
|
||||||
|
fileChannels,
|
||||||
|
fileInfos,
|
||||||
|
publicLinkEnabled,
|
||||||
|
paddingTop,
|
||||||
|
searchValue,
|
||||||
|
}: Props) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isTablet = useIsTablet();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const [lastViewedIndex, setLastViewedIndex] = useState<number | undefined>(undefined);
|
||||||
|
const containerStyle = useMemo(() => ({top: fileInfos.length ? 8 : 0}), [fileInfos]);
|
||||||
|
|
||||||
|
const {images: imageAttachments, nonImages: nonImageAttachments} = useImageAttachments(fileInfos, publicLinkEnabled);
|
||||||
|
const channelNames = useMemo(() => fileChannels.reduce<{[id: string]: string | undefined}>((acc, v) => {
|
||||||
|
acc[v.id] = v.displayName;
|
||||||
|
return acc;
|
||||||
|
}, {}), [fileChannels]);
|
||||||
|
|
||||||
|
const orderedFilesForGallery = useMemo(() => {
|
||||||
|
const filesForGallery = imageAttachments.concat(nonImageAttachments);
|
||||||
|
return filesForGallery.sort((a: FileInfo, b: FileInfo) => {
|
||||||
|
return (b.create_at || 0) - (a.create_at || 0);
|
||||||
|
});
|
||||||
|
}, [imageAttachments, nonImageAttachments]);
|
||||||
|
|
||||||
|
const filesForGalleryIndexes = useMemo(() => orderedFilesForGallery.reduce<{[id: string]: number | undefined}>((acc, v, idx) => {
|
||||||
|
if (v.id) {
|
||||||
|
acc[v.id] = idx;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {}), [orderedFilesForGallery]);
|
||||||
|
|
||||||
|
const handlePreviewPress = useCallback(preventDoubleTap((idx: number) => {
|
||||||
|
const items = orderedFilesForGallery.map((f) => fileToGalleryItem(f, f.user_id));
|
||||||
|
openGalleryAtIndex(galleryIdentifier, idx, items);
|
||||||
|
}), [orderedFilesForGallery]);
|
||||||
|
|
||||||
|
const handleOptionsPress = useCallback((item: number) => {
|
||||||
|
setLastViewedIndex(item);
|
||||||
|
let numberOptions = 1;
|
||||||
|
numberOptions += canDownloadFiles ? 1 : 0;
|
||||||
|
numberOptions += publicLinkEnabled ? 1 : 0;
|
||||||
|
const renderContent = () => (
|
||||||
|
<FileOptions
|
||||||
|
fileInfo={orderedFilesForGallery[item]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
bottomSheet({
|
||||||
|
closeButtonId: 'close-search-file-options',
|
||||||
|
renderContent,
|
||||||
|
snapPoints: [bottomSheetSnapPoint(numberOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT, 10],
|
||||||
|
theme,
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
}, [canDownloadFiles, publicLinkEnabled, orderedFilesForGallery, theme]);
|
||||||
|
|
||||||
|
// This effect handles the case where a user has the FileOptions Modal
|
||||||
|
// open and the server changes the ability to download files or copy public
|
||||||
|
// links. Reopen the Bottom Sheet again so the new options are added or
|
||||||
|
// removed.
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastViewedIndex === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (NavigationStore.getNavigationTopComponentId() === Screens.BOTTOM_SHEET) {
|
||||||
|
dismissBottomSheet().then(() => {
|
||||||
|
handleOptionsPress(lastViewedIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [canDownloadFiles, publicLinkEnabled]);
|
||||||
|
|
||||||
|
const updateFileForGallery = (idx: number, file: FileInfo) => {
|
||||||
|
'worklet';
|
||||||
|
orderedFilesForGallery[idx] = file;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItem = useCallback(({item}: ListRenderItemInfo<FileInfo>) => {
|
||||||
|
const container: StyleProp<ViewStyle> = fileInfos.length > 1 ? styles.container : undefined;
|
||||||
|
const isSingleImage = orderedFilesForGallery.length === 1 && (isImage(orderedFilesForGallery[0]) || isVideo(orderedFilesForGallery[0]));
|
||||||
|
const isReplyPost = false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={container}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<File
|
||||||
|
asCard={true}
|
||||||
|
canDownloadFiles={canDownloadFiles}
|
||||||
|
channelName={channelNames[item.channel_id!]}
|
||||||
|
file={item}
|
||||||
|
galleryIdentifier={galleryIdentifier}
|
||||||
|
inViewPort={true}
|
||||||
|
index={filesForGalleryIndexes[item.id!] || 0}
|
||||||
|
isSingleImage={isSingleImage}
|
||||||
|
key={item.id}
|
||||||
|
nonVisibleImagesCount={0}
|
||||||
|
onOptionsPress={handleOptionsPress}
|
||||||
|
onPress={handlePreviewPress}
|
||||||
|
publicLinkEnabled={publicLinkEnabled}
|
||||||
|
showDate={true}
|
||||||
|
updateFileForGallery={updateFileForGallery}
|
||||||
|
wrapperWidth={(getViewPortWidth(isReplyPost, isTablet) - 6)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
(orderedFilesForGallery.length === 1) && orderedFilesForGallery[0].mime_type,
|
||||||
|
canDownloadFiles,
|
||||||
|
channelNames,
|
||||||
|
fileInfos.length > 1,
|
||||||
|
filesForGalleryIndexes,
|
||||||
|
handleOptionsPress,
|
||||||
|
handlePreviewPress,
|
||||||
|
isTablet,
|
||||||
|
publicLinkEnabled,
|
||||||
|
theme,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const noResults = useMemo(() => (
|
||||||
|
<NoResultsWithTerm
|
||||||
|
term={searchValue}
|
||||||
|
type={TabTypes.FILES}
|
||||||
|
/>
|
||||||
|
), [searchValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
ListEmptyComponent={noResults}
|
||||||
|
contentContainerStyle={[paddingTop, containerStyle]}
|
||||||
|
data={orderedFilesForGallery}
|
||||||
|
indicatorStyle='black'
|
||||||
|
initialNumToRender={10}
|
||||||
|
listKey={'files'}
|
||||||
|
maxToRenderPerBatch={5}
|
||||||
|
nestedScrollEnabled={true}
|
||||||
|
refreshing={false}
|
||||||
|
removeClippedSubviews={true}
|
||||||
|
renderItem={renderItem}
|
||||||
|
scrollEventThrottle={16}
|
||||||
|
scrollToOverflowEnabled={true}
|
||||||
|
showsVerticalScrollIndicator={true}
|
||||||
|
testID='search_results.post_list.flat_list'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileResults;
|
||||||
@@ -8,7 +8,6 @@ import {combineLatest, of as of$} from 'rxjs';
|
|||||||
import {map, switchMap} from 'rxjs/operators';
|
import {map, switchMap} from 'rxjs/operators';
|
||||||
|
|
||||||
import {queryChannelsById} from '@queries/servers/channel';
|
import {queryChannelsById} from '@queries/servers/channel';
|
||||||
import {queryPostsById} from '@queries/servers/post';
|
|
||||||
import {observeLicense, observeConfigBooleanValue} from '@queries/servers/system';
|
import {observeLicense, observeConfigBooleanValue} from '@queries/servers/system';
|
||||||
import {observeCurrentUser} from '@queries/servers/user';
|
import {observeCurrentUser} from '@queries/servers/user';
|
||||||
import {getTimezone} from '@utils/user';
|
import {getTimezone} from '@utils/user';
|
||||||
@@ -16,19 +15,12 @@ import {getTimezone} from '@utils/user';
|
|||||||
import Results from './results';
|
import Results from './results';
|
||||||
|
|
||||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||||
import type PostModel from '@typings/database/models/servers/post';
|
|
||||||
|
|
||||||
type enhancedProps = WithDatabaseArgs & {
|
type enhancedProps = WithDatabaseArgs & {
|
||||||
postIds: string[];
|
|
||||||
fileChannelIds: string[];
|
fileChannelIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortPosts = (a: PostModel, b: PostModel) => a.createAt - b.createAt;
|
const enhance = withObservables(['fileChannelIds'], ({database, fileChannelIds}: enhancedProps) => {
|
||||||
|
|
||||||
const enhance = withObservables(['postIds', 'fileChannelIds'], ({database, postIds, fileChannelIds}: enhancedProps) => {
|
|
||||||
const posts = queryPostsById(database, postIds).observeWithColumns(['type', 'createAt']).pipe(
|
|
||||||
switchMap((pp) => of$(pp.sort(sortPosts))),
|
|
||||||
);
|
|
||||||
const fileChannels = queryChannelsById(database, fileChannelIds).observeWithColumns(['displayName']);
|
const fileChannels = queryChannelsById(database, fileChannelIds).observeWithColumns(['displayName']);
|
||||||
const currentUser = observeCurrentUser(database);
|
const currentUser = observeCurrentUser(database);
|
||||||
|
|
||||||
@@ -45,7 +37,6 @@ const enhance = withObservables(['postIds', 'fileChannelIds'], ({database, postI
|
|||||||
return {
|
return {
|
||||||
currentTimezone: currentUser.pipe((switchMap((user) => of$(getTimezone(user?.timezone))))),
|
currentTimezone: currentUser.pipe((switchMap((user) => of$(getTimezone(user?.timezone))))),
|
||||||
isTimezoneEnabled: observeConfigBooleanValue(database, 'ExperimentalTimezone'),
|
isTimezoneEnabled: observeConfigBooleanValue(database, 'ExperimentalTimezone'),
|
||||||
posts,
|
|
||||||
fileChannels,
|
fileChannels,
|
||||||
canDownloadFiles,
|
canDownloadFiles,
|
||||||
publicLinkEnabled: observeConfigBooleanValue(database, 'EnablePublicLink'),
|
publicLinkEnabled: observeConfigBooleanValue(database, 'EnablePublicLink'),
|
||||||
|
|||||||
88
app/screens/home/search/results/post_results.tsx
Normal file
88
app/screens/home/search/results/post_results.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useCallback, useMemo} from 'react';
|
||||||
|
import {FlatList, ListRenderItemInfo, StyleProp, ViewStyle} from 'react-native';
|
||||||
|
|
||||||
|
import NoResultsWithTerm from '@components/no_results_with_term';
|
||||||
|
import DateSeparator from '@components/post_list/date_separator';
|
||||||
|
import PostWithChannelInfo from '@components/post_with_channel_info';
|
||||||
|
import {Screens} from '@constants';
|
||||||
|
import {getDateForDateLine, isDateLine, selectOrderedPosts} from '@utils/post_list';
|
||||||
|
import {TabTypes} from '@utils/search';
|
||||||
|
|
||||||
|
import type PostModel from '@typings/database/models/servers/post';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentTimezone: string;
|
||||||
|
isTimezoneEnabled: boolean;
|
||||||
|
posts: PostModel[];
|
||||||
|
paddingTop: StyleProp<ViewStyle>;
|
||||||
|
searchValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostResults = ({
|
||||||
|
currentTimezone,
|
||||||
|
isTimezoneEnabled,
|
||||||
|
posts,
|
||||||
|
paddingTop,
|
||||||
|
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 renderItem = useCallback(({item}: ListRenderItemInfo<string|PostModel>) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
if (isDateLine(item)) {
|
||||||
|
return (
|
||||||
|
<DateSeparator
|
||||||
|
date={getDateForDateLine(item)}
|
||||||
|
timezone={isTimezoneEnabled ? currentTimezone : null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('message' in item) {
|
||||||
|
return (
|
||||||
|
<PostWithChannelInfo
|
||||||
|
location={Screens.SEARCH}
|
||||||
|
post={item}
|
||||||
|
testID='search_results.post_list'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const noResults = useMemo(() => (
|
||||||
|
<NoResultsWithTerm
|
||||||
|
term={searchValue}
|
||||||
|
type={TabTypes.MESSAGES}
|
||||||
|
/>
|
||||||
|
), [searchValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
ListEmptyComponent={noResults}
|
||||||
|
contentContainerStyle={[paddingTop, containerStyle]}
|
||||||
|
data={orderedPosts}
|
||||||
|
indicatorStyle='black'
|
||||||
|
initialNumToRender={5}
|
||||||
|
listKey={'posts'}
|
||||||
|
maxToRenderPerBatch={5}
|
||||||
|
nestedScrollEnabled={true}
|
||||||
|
refreshing={false}
|
||||||
|
removeClippedSubviews={true}
|
||||||
|
renderItem={renderItem}
|
||||||
|
scrollEventThrottle={16}
|
||||||
|
scrollToOverflowEnabled={true}
|
||||||
|
showsVerticalScrollIndicator={true}
|
||||||
|
style={containerStyle}
|
||||||
|
testID='search_results.post_list.flat_list'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostResults;
|
||||||
@@ -1,45 +1,41 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import {StyleSheet, FlatList, ListRenderItemInfo, StyleProp, View, ViewStyle} from 'react-native';
|
import {ScaledSize, StyleSheet, useWindowDimensions, View} from 'react-native';
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|
||||||
|
|
||||||
import File from '@components/files/file';
|
import Loading from '@app/components/loading';
|
||||||
import Loading from '@components/loading';
|
|
||||||
import NoResultsWithTerm from '@components/no_results_with_term';
|
|
||||||
import {ITEM_HEIGHT} from '@components/option_item';
|
|
||||||
import DateSeparator from '@components/post_list/date_separator';
|
|
||||||
import PostWithChannelInfo from '@components/post_with_channel_info';
|
|
||||||
import {Screens} from '@constants';
|
|
||||||
import {useTheme} from '@context/theme';
|
import {useTheme} from '@context/theme';
|
||||||
import {useIsTablet} from '@hooks/device';
|
|
||||||
import {useImageAttachments} from '@hooks/files';
|
|
||||||
import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
|
|
||||||
import NavigationStore from '@store/navigation_store';
|
|
||||||
import {isImage, isVideo} from '@utils/file';
|
|
||||||
import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery';
|
|
||||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
|
||||||
import {getViewPortWidth} from '@utils/images';
|
|
||||||
import {getDateForDateLine, isDateLine, selectOrderedPosts} from '@utils/post_list';
|
|
||||||
import {TabTypes, TabType} from '@utils/search';
|
import {TabTypes, TabType} from '@utils/search';
|
||||||
import {preventDoubleTap} from '@utils/tap';
|
|
||||||
|
|
||||||
import FileOptions from './file_options';
|
import FileResults from './file_results';
|
||||||
import {HEADER_HEIGHT} from './file_options/header';
|
import PostResults from './post_results';
|
||||||
|
|
||||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||||
import type PostModel from '@typings/database/models/servers/post';
|
import type PostModel from '@typings/database/models/servers/post';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const duration = 250;
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
marginHorizontal: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
const getStyles = (dimensions: ScaledSize) => {
|
||||||
|
return StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: dimensions.width * 2,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
flex: 1,
|
||||||
|
width: dimensions.width,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
flex: 1,
|
||||||
|
width: dimensions.width,
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
canDownloadFiles: boolean;
|
canDownloadFiles: boolean;
|
||||||
@@ -50,13 +46,12 @@ type Props = {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
posts: PostModel[];
|
posts: PostModel[];
|
||||||
publicLinkEnabled: boolean;
|
publicLinkEnabled: boolean;
|
||||||
|
scrollPaddingTop: number;
|
||||||
searchValue: string;
|
searchValue: string;
|
||||||
selectedTab: TabType;
|
selectedTab: TabType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const galleryIdentifier = 'search-files-location';
|
const Results = ({
|
||||||
|
|
||||||
const SearchResults = ({
|
|
||||||
canDownloadFiles,
|
canDownloadFiles,
|
||||||
currentTimezone,
|
currentTimezone,
|
||||||
fileChannels,
|
fileChannels,
|
||||||
@@ -65,207 +60,61 @@ const SearchResults = ({
|
|||||||
loading,
|
loading,
|
||||||
posts,
|
posts,
|
||||||
publicLinkEnabled,
|
publicLinkEnabled,
|
||||||
|
scrollPaddingTop,
|
||||||
searchValue,
|
searchValue,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const dimensions = useWindowDimensions();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useIsTablet();
|
const styles = useMemo(() => getStyles(dimensions), [dimensions]);
|
||||||
const insets = useSafeAreaInsets();
|
|
||||||
const [lastViewedIndex, setLastViewedIndex] = useState<number | undefined>(undefined);
|
|
||||||
|
|
||||||
const orderedPosts = useMemo(() => selectOrderedPosts(posts, 0, false, '', '', false, isTimezoneEnabled, currentTimezone, false).reverse(), [posts]);
|
const transform = useAnimatedStyle(() => {
|
||||||
const {images: imageAttachments, nonImages: nonImageAttachments} = useImageAttachments(fileInfos, publicLinkEnabled);
|
const translateX = selectedTab === TabTypes.MESSAGES ? 0 : -dimensions.width;
|
||||||
const channelNames = useMemo(() => fileChannels.reduce<{[id: string]: string | undefined}>((acc, v) => {
|
return {
|
||||||
acc[v.id] = v.displayName;
|
transform: [
|
||||||
return acc;
|
{translateX: withTiming(translateX, {duration})},
|
||||||
}, {}), [fileChannels]);
|
],
|
||||||
|
|
||||||
const containerStyle = useMemo(() => {
|
|
||||||
let padding = 0;
|
|
||||||
if (selectedTab === TabTypes.MESSAGES) {
|
|
||||||
padding = posts.length ? 4 : 8;
|
|
||||||
} else {
|
|
||||||
padding = fileInfos.length ? 8 : 0;
|
|
||||||
}
|
|
||||||
return {top: padding};
|
|
||||||
}, [selectedTab, posts, fileInfos]);
|
|
||||||
|
|
||||||
const filesForGallery = useMemo(() => imageAttachments.concat(nonImageAttachments),
|
|
||||||
[imageAttachments, nonImageAttachments]);
|
|
||||||
|
|
||||||
const orderedFilesForGallery = useMemo(() => (
|
|
||||||
filesForGallery.sort((a: FileInfo, b: FileInfo) => {
|
|
||||||
return (b.create_at || 0) - (a.create_at || 0);
|
|
||||||
})
|
|
||||||
), [filesForGallery]);
|
|
||||||
|
|
||||||
const filesForGalleryIndexes = useMemo(() => orderedFilesForGallery.reduce<{[id: string]: number | undefined}>((acc, v, idx) => {
|
|
||||||
if (v.id) {
|
|
||||||
acc[v.id] = idx;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {}), [orderedFilesForGallery]);
|
|
||||||
|
|
||||||
const handlePreviewPress = useCallback(preventDoubleTap((idx: number) => {
|
|
||||||
const items = orderedFilesForGallery.map((f) => fileToGalleryItem(f, f.user_id));
|
|
||||||
openGalleryAtIndex(galleryIdentifier, idx, items);
|
|
||||||
}), [orderedFilesForGallery]);
|
|
||||||
|
|
||||||
const snapPoints = useMemo(() => {
|
|
||||||
let numberOptions = 1;
|
|
||||||
if (canDownloadFiles) {
|
|
||||||
numberOptions += 1;
|
|
||||||
}
|
|
||||||
if (publicLinkEnabled) {
|
|
||||||
numberOptions += 1;
|
|
||||||
}
|
|
||||||
return [bottomSheetSnapPoint(numberOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT, 10];
|
|
||||||
}, [canDownloadFiles, publicLinkEnabled]);
|
|
||||||
|
|
||||||
const handleOptionsPress = useCallback((item: number) => {
|
|
||||||
setLastViewedIndex(item);
|
|
||||||
const renderContent = () => {
|
|
||||||
return (
|
|
||||||
<FileOptions
|
|
||||||
fileInfo={orderedFilesForGallery[item]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
bottomSheet({
|
}, [selectedTab, dimensions.width]);
|
||||||
closeButtonId: 'close-search-file-options',
|
|
||||||
renderContent,
|
|
||||||
snapPoints,
|
|
||||||
theme,
|
|
||||||
title: '',
|
|
||||||
});
|
|
||||||
}, [orderedFilesForGallery, snapPoints, theme]);
|
|
||||||
|
|
||||||
// This effect handles the case where a user has the FileOptions Modal
|
const paddingTop = useMemo(() => (
|
||||||
// open and the server changes the ability to download files or copy public
|
{paddingTop: scrollPaddingTop, flexGrow: 1}
|
||||||
// links. Reopen the Bottom Sheet again so the new options are added or
|
), [scrollPaddingTop]);
|
||||||
// removed.
|
|
||||||
useEffect(() => {
|
|
||||||
if (lastViewedIndex === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (NavigationStore.getNavigationTopComponentId() === 'BottomSheet') {
|
|
||||||
dismissBottomSheet().then(() => {
|
|
||||||
handleOptionsPress(lastViewedIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [canDownloadFiles, publicLinkEnabled]);
|
|
||||||
|
|
||||||
const renderItem = useCallback(({item}: ListRenderItemInfo<string|FileInfo | Post>) => {
|
return (
|
||||||
if (item === 'loading') {
|
<>
|
||||||
return (
|
{loading &&
|
||||||
<Loading
|
<Loading
|
||||||
color={theme.buttonBg}
|
color={theme.buttonBg}
|
||||||
size='large'
|
size='large'
|
||||||
|
containerStyle={[styles.loading, paddingTop]}
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof item === 'string') {
|
|
||||||
if (isDateLine(item)) {
|
|
||||||
return (
|
|
||||||
<DateSeparator
|
|
||||||
date={getDateForDateLine(item)}
|
|
||||||
timezone={isTimezoneEnabled ? currentTimezone : null}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
{!loading &&
|
||||||
}
|
<Animated.View style={[styles.container, transform]}>
|
||||||
|
<View style={styles.result} >
|
||||||
if ('message' in item) {
|
<PostResults
|
||||||
return (
|
currentTimezone={currentTimezone}
|
||||||
<PostWithChannelInfo
|
isTimezoneEnabled={isTimezoneEnabled}
|
||||||
location={Screens.SEARCH}
|
posts={posts}
|
||||||
post={item}
|
paddingTop={paddingTop}
|
||||||
testID='search_results.post_list'
|
searchValue={searchValue}
|
||||||
/>
|
/>
|
||||||
);
|
</View>
|
||||||
}
|
<View style={styles.result} >
|
||||||
|
<FileResults
|
||||||
const updateFileForGallery = (idx: number, file: FileInfo) => {
|
canDownloadFiles={canDownloadFiles}
|
||||||
'worklet';
|
fileChannels={fileChannels}
|
||||||
orderedFilesForGallery[idx] = file;
|
fileInfos={fileInfos}
|
||||||
};
|
publicLinkEnabled={publicLinkEnabled}
|
||||||
|
paddingTop={paddingTop}
|
||||||
const container: StyleProp<ViewStyle> = fileInfos.length > 1 ? styles.container : undefined;
|
searchValue={searchValue}
|
||||||
const isSingleImage = orderedFilesForGallery.length === 1 && (isImage(orderedFilesForGallery[0]) || isVideo(orderedFilesForGallery[0]));
|
/>
|
||||||
const isReplyPost = false;
|
</View>
|
||||||
|
</Animated.View>
|
||||||
return (
|
}
|
||||||
<View
|
</>
|
||||||
style={container}
|
|
||||||
key={item.id}
|
|
||||||
>
|
|
||||||
<File
|
|
||||||
channelName={channelNames[item.channel_id!]}
|
|
||||||
galleryIdentifier={galleryIdentifier}
|
|
||||||
key={item.id}
|
|
||||||
canDownloadFiles={canDownloadFiles}
|
|
||||||
file={item}
|
|
||||||
index={filesForGalleryIndexes[item.id!] || 0}
|
|
||||||
onPress={handlePreviewPress}
|
|
||||||
onOptionsPress={handleOptionsPress}
|
|
||||||
isSingleImage={isSingleImage}
|
|
||||||
showDate={true}
|
|
||||||
publicLinkEnabled={publicLinkEnabled}
|
|
||||||
updateFileForGallery={updateFileForGallery}
|
|
||||||
inViewPort={true}
|
|
||||||
wrapperWidth={(getViewPortWidth(isReplyPost, isTablet) - 6)}
|
|
||||||
nonVisibleImagesCount={0}
|
|
||||||
asCard={true}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
theme,
|
|
||||||
(orderedFilesForGallery.length === 1) && orderedFilesForGallery[0].mime_type,
|
|
||||||
handleOptionsPress,
|
|
||||||
channelNames,
|
|
||||||
filesForGalleryIndexes,
|
|
||||||
canDownloadFiles,
|
|
||||||
handlePreviewPress,
|
|
||||||
publicLinkEnabled,
|
|
||||||
isTablet,
|
|
||||||
fileInfos.length > 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const noResults = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<NoResultsWithTerm
|
|
||||||
term={searchValue}
|
|
||||||
type={selectedTab}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [searchValue, selectedTab]);
|
|
||||||
|
|
||||||
let data;
|
|
||||||
if (loading) {
|
|
||||||
data = ['loading'];
|
|
||||||
} else {
|
|
||||||
data = selectedTab === TabTypes.MESSAGES ? orderedPosts : orderedFilesForGallery;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatedFlatList
|
|
||||||
ListEmptyComponent={noResults}
|
|
||||||
data={data}
|
|
||||||
scrollToOverflowEnabled={true}
|
|
||||||
showsVerticalScrollIndicator={true}
|
|
||||||
scrollEventThrottle={16}
|
|
||||||
indicatorStyle='black'
|
|
||||||
refreshing={false}
|
|
||||||
renderItem={renderItem}
|
|
||||||
nestedScrollEnabled={true}
|
|
||||||
removeClippedSubviews={true}
|
|
||||||
style={containerStyle}
|
|
||||||
testID='search_results.post_list.flat_list'
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SearchResults;
|
export default Results;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {FlatList, LayoutChangeEvent, Platform, StyleSheet, ViewProps} from 'reac
|
|||||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
import {getPosts} from '@actions/local/post';
|
||||||
import {addSearchToTeamSearchHistory} from '@actions/local/team';
|
import {addSearchToTeamSearchHistory} from '@actions/local/team';
|
||||||
import {searchPosts, searchFiles} from '@actions/remote/search';
|
import {searchPosts, searchFiles} from '@actions/remote/search';
|
||||||
import Autocomplete from '@components/autocomplete';
|
import Autocomplete from '@components/autocomplete';
|
||||||
@@ -27,11 +28,13 @@ import Initial from './initial';
|
|||||||
import Results from './results';
|
import Results from './results';
|
||||||
import Header from './results/header';
|
import Header from './results/header';
|
||||||
|
|
||||||
|
import type PostModel from '@typings/database/models/servers/post';
|
||||||
|
|
||||||
const EDGES: Edge[] = ['bottom', 'left', 'right'];
|
const EDGES: Edge[] = ['bottom', 'left', 'right'];
|
||||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
||||||
|
|
||||||
const emptyFileResults: FileInfo[] = [];
|
const emptyFileResults: FileInfo[] = [];
|
||||||
const emptyPostResults: string[] = [];
|
const emptyPosts: PostModel[] = [];
|
||||||
const emptyChannelIds: string[] = [];
|
const emptyChannelIds: string[] = [];
|
||||||
|
|
||||||
const dummyData = [1];
|
const dummyData = [1];
|
||||||
@@ -87,8 +90,7 @@ const SearchScreen = ({teamId}: Props) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [resultsLoading, setResultsLoading] = useState(false);
|
const [resultsLoading, setResultsLoading] = useState(false);
|
||||||
const [lastSearchedValue, setLastSearchedValue] = useState('');
|
const [lastSearchedValue, setLastSearchedValue] = useState('');
|
||||||
|
const [posts, setPosts] = useState<PostModel[]>(emptyPosts);
|
||||||
const [postIds, setPostIds] = useState<string[]>(emptyPostResults);
|
|
||||||
const [fileInfos, setFileInfos] = useState<FileInfo[]>(emptyFileResults);
|
const [fileInfos, setFileInfos] = useState<FileInfo[]>(emptyFileResults);
|
||||||
const [fileChannelIds, setFileChannelIds] = useState<string[]>([]);
|
const [fileChannelIds, setFileChannelIds] = useState<string[]>([]);
|
||||||
|
|
||||||
@@ -130,9 +132,11 @@ const SearchScreen = ({teamId}: Props) => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
setFileInfos(files?.length ? files : emptyFileResults);
|
setFileInfos(files?.length ? files : emptyFileResults);
|
||||||
setPostIds(postResults?.order?.length ? postResults.order : emptyPostResults);
|
if (postResults.order) {
|
||||||
|
const postModels = await getPosts(serverUrl, postResults.order);
|
||||||
|
setPosts(postModels.length ? postModels : emptyPosts);
|
||||||
|
}
|
||||||
setFileChannelIds(channels?.length ? channels : emptyChannelIds);
|
setFileChannelIds(channels?.length ? channels : emptyChannelIds);
|
||||||
|
|
||||||
handleLoading(false);
|
handleLoading(false);
|
||||||
setShowResults(true);
|
setShowResults(true);
|
||||||
}, [handleCancelAndClearSearch, handleLoading, showResults]);
|
}, [handleCancelAndClearSearch, handleLoading, showResults]);
|
||||||
@@ -184,29 +188,14 @@ const SearchScreen = ({teamId}: Props) => {
|
|||||||
/>
|
/>
|
||||||
), [searchValue, searchTeamId, handleRecentSearch, handleTextChange]);
|
), [searchValue, searchTeamId, handleRecentSearch, handleTextChange]);
|
||||||
|
|
||||||
const resultsComponent = useMemo(() => (
|
|
||||||
<Results
|
|
||||||
loading={resultsLoading}
|
|
||||||
selectedTab={selectedTab}
|
|
||||||
searchValue={lastSearchedValue}
|
|
||||||
postIds={postIds}
|
|
||||||
fileInfos={fileInfos}
|
|
||||||
fileChannelIds={fileChannelIds}
|
|
||||||
/>
|
|
||||||
), [selectedTab, lastSearchedValue, postIds, fileInfos, fileChannelIds, resultsLoading]);
|
|
||||||
|
|
||||||
const renderItem = useCallback(() => {
|
const renderItem = useCallback(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return loadingComponent;
|
return loadingComponent;
|
||||||
}
|
}
|
||||||
if (!showResults) {
|
return initialComponent;
|
||||||
return initialComponent;
|
|
||||||
}
|
|
||||||
return resultsComponent;
|
|
||||||
}, [
|
}, [
|
||||||
loading && loadingComponent,
|
loading && loadingComponent,
|
||||||
!loading && !showResults && initialComponent,
|
initialComponent,
|
||||||
!loading && showResults && resultsComponent,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const animated = useAnimatedStyle(() => {
|
const animated = useAnimatedStyle(() => {
|
||||||
@@ -243,7 +232,7 @@ const SearchScreen = ({teamId}: Props) => {
|
|||||||
setTeamId={handleResultsTeamChange}
|
setTeamId={handleResultsTeamChange}
|
||||||
onTabSelect={setSelectedTab}
|
onTabSelect={setSelectedTab}
|
||||||
onFilterChanged={handleFilterChange}
|
onFilterChanged={handleFilterChange}
|
||||||
numberMessages={postIds.length}
|
numberMessages={posts.length}
|
||||||
selectedTab={selectedTab}
|
selectedTab={selectedTab}
|
||||||
numberFiles={fileInfos.length}
|
numberFiles={fileInfos.length}
|
||||||
selectedFilter={filter}
|
selectedFilter={filter}
|
||||||
@@ -300,21 +289,34 @@ const SearchScreen = ({teamId}: Props) => {
|
|||||||
<RoundedHeaderContext/>
|
<RoundedHeaderContext/>
|
||||||
{header}
|
{header}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
<AnimatedFlatList
|
{!showResults &&
|
||||||
data={dummyData}
|
<AnimatedFlatList
|
||||||
contentContainerStyle={containerStyle}
|
data={dummyData}
|
||||||
keyboardShouldPersistTaps='handled'
|
contentContainerStyle={containerStyle}
|
||||||
keyboardDismissMode={'interactive'}
|
keyboardShouldPersistTaps='handled'
|
||||||
nestedScrollEnabled={true}
|
keyboardDismissMode={'interactive'}
|
||||||
indicatorStyle='black'
|
nestedScrollEnabled={true}
|
||||||
onScroll={onScroll}
|
indicatorStyle='black'
|
||||||
scrollEventThrottle={16}
|
onScroll={onScroll}
|
||||||
removeClippedSubviews={false}
|
scrollEventThrottle={16}
|
||||||
scrollToOverflowEnabled={true}
|
removeClippedSubviews={false}
|
||||||
overScrollMode='always'
|
scrollToOverflowEnabled={true}
|
||||||
ref={scrollRef}
|
overScrollMode='always'
|
||||||
renderItem={renderItem}
|
ref={scrollRef}
|
||||||
/>
|
renderItem={renderItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{showResults && !loading &&
|
||||||
|
<Results
|
||||||
|
loading={resultsLoading}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
searchValue={lastSearchedValue}
|
||||||
|
posts={posts}
|
||||||
|
fileInfos={fileInfos}
|
||||||
|
scrollPaddingTop={scrollPaddingTop}
|
||||||
|
fileChannelIds={fileChannelIds}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</FreezeScreen>
|
</FreezeScreen>
|
||||||
|
|||||||
Reference in New Issue
Block a user