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 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 {getIsCRTEnabled, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
|
||||
import {generateId} from '@utils/general';
|
||||
@@ -233,3 +233,12 @@ export async function storePostsForChannel(
|
||||
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) => {
|
||||
'worklet';
|
||||
|
||||
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 {queryChannelsById} from '@queries/servers/channel';
|
||||
import {queryPostsById} from '@queries/servers/post';
|
||||
import {observeLicense, observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {getTimezone} from '@utils/user';
|
||||
@@ -16,19 +15,12 @@ import {getTimezone} from '@utils/user';
|
||||
import Results from './results';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
type enhancedProps = WithDatabaseArgs & {
|
||||
postIds: string[];
|
||||
fileChannelIds: string[];
|
||||
}
|
||||
|
||||
const sortPosts = (a: PostModel, b: PostModel) => a.createAt - b.createAt;
|
||||
|
||||
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 enhance = withObservables(['fileChannelIds'], ({database, fileChannelIds}: enhancedProps) => {
|
||||
const fileChannels = queryChannelsById(database, fileChannelIds).observeWithColumns(['displayName']);
|
||||
const currentUser = observeCurrentUser(database);
|
||||
|
||||
@@ -45,7 +37,6 @@ const enhance = withObservables(['postIds', 'fileChannelIds'], ({database, postI
|
||||
return {
|
||||
currentTimezone: currentUser.pipe((switchMap((user) => of$(getTimezone(user?.timezone))))),
|
||||
isTimezoneEnabled: observeConfigBooleanValue(database, 'ExperimentalTimezone'),
|
||||
posts,
|
||||
fileChannels,
|
||||
canDownloadFiles,
|
||||
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.
|
||||
// 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 Animated from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import React, {useMemo} from 'react';
|
||||
import {ScaledSize, StyleSheet, useWindowDimensions, View} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import File from '@components/files/file';
|
||||
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 Loading from '@app/components/loading';
|
||||
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 {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import FileOptions from './file_options';
|
||||
import {HEADER_HEIGHT} from './file_options/header';
|
||||
import FileResults from './file_results';
|
||||
import PostResults from './post_results';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
marginHorizontal: 20,
|
||||
},
|
||||
});
|
||||
const duration = 250;
|
||||
|
||||
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 = {
|
||||
canDownloadFiles: boolean;
|
||||
@@ -50,13 +46,12 @@ type Props = {
|
||||
loading: boolean;
|
||||
posts: PostModel[];
|
||||
publicLinkEnabled: boolean;
|
||||
scrollPaddingTop: number;
|
||||
searchValue: string;
|
||||
selectedTab: TabType;
|
||||
}
|
||||
|
||||
const galleryIdentifier = 'search-files-location';
|
||||
|
||||
const SearchResults = ({
|
||||
const Results = ({
|
||||
canDownloadFiles,
|
||||
currentTimezone,
|
||||
fileChannels,
|
||||
@@ -65,207 +60,61 @@ const SearchResults = ({
|
||||
loading,
|
||||
posts,
|
||||
publicLinkEnabled,
|
||||
scrollPaddingTop,
|
||||
searchValue,
|
||||
selectedTab,
|
||||
}: Props) => {
|
||||
const dimensions = useWindowDimensions();
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [lastViewedIndex, setLastViewedIndex] = useState<number | undefined>(undefined);
|
||||
const styles = useMemo(() => getStyles(dimensions), [dimensions]);
|
||||
|
||||
const orderedPosts = useMemo(() => selectOrderedPosts(posts, 0, false, '', '', false, isTimezoneEnabled, currentTimezone, false).reverse(), [posts]);
|
||||
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 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]}
|
||||
/>
|
||||
);
|
||||
const transform = useAnimatedStyle(() => {
|
||||
const translateX = selectedTab === TabTypes.MESSAGES ? 0 : -dimensions.width;
|
||||
return {
|
||||
transform: [
|
||||
{translateX: withTiming(translateX, {duration})},
|
||||
],
|
||||
};
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-search-file-options',
|
||||
renderContent,
|
||||
snapPoints,
|
||||
theme,
|
||||
title: '',
|
||||
});
|
||||
}, [orderedFilesForGallery, snapPoints, theme]);
|
||||
}, [selectedTab, dimensions.width]);
|
||||
|
||||
// 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() === 'BottomSheet') {
|
||||
dismissBottomSheet().then(() => {
|
||||
handleOptionsPress(lastViewedIndex);
|
||||
});
|
||||
}
|
||||
}, [canDownloadFiles, publicLinkEnabled]);
|
||||
const paddingTop = useMemo(() => (
|
||||
{paddingTop: scrollPaddingTop, flexGrow: 1}
|
||||
), [scrollPaddingTop]);
|
||||
|
||||
const renderItem = useCallback(({item}: ListRenderItemInfo<string|FileInfo | Post>) => {
|
||||
if (item === 'loading') {
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{loading &&
|
||||
<Loading
|
||||
color={theme.buttonBg}
|
||||
size='large'
|
||||
containerStyle={[styles.loading, paddingTop]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const updateFileForGallery = (idx: number, file: FileInfo) => {
|
||||
'worklet';
|
||||
orderedFilesForGallery[idx] = file;
|
||||
};
|
||||
|
||||
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
|
||||
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'
|
||||
/>
|
||||
{!loading &&
|
||||
<Animated.View style={[styles.container, transform]}>
|
||||
<View style={styles.result} >
|
||||
<PostResults
|
||||
currentTimezone={currentTimezone}
|
||||
isTimezoneEnabled={isTimezoneEnabled}
|
||||
posts={posts}
|
||||
paddingTop={paddingTop}
|
||||
searchValue={searchValue}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.result} >
|
||||
<FileResults
|
||||
canDownloadFiles={canDownloadFiles}
|
||||
fileChannels={fileChannels}
|
||||
fileInfos={fileInfos}
|
||||
publicLinkEnabled={publicLinkEnabled}
|
||||
paddingTop={paddingTop}
|
||||
searchValue={searchValue}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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 {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {getPosts} from '@actions/local/post';
|
||||
import {addSearchToTeamSearchHistory} from '@actions/local/team';
|
||||
import {searchPosts, searchFiles} from '@actions/remote/search';
|
||||
import Autocomplete from '@components/autocomplete';
|
||||
@@ -27,11 +28,13 @@ import Initial from './initial';
|
||||
import Results from './results';
|
||||
import Header from './results/header';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const EDGES: Edge[] = ['bottom', 'left', 'right'];
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
|
||||
|
||||
const emptyFileResults: FileInfo[] = [];
|
||||
const emptyPostResults: string[] = [];
|
||||
const emptyPosts: PostModel[] = [];
|
||||
const emptyChannelIds: string[] = [];
|
||||
|
||||
const dummyData = [1];
|
||||
@@ -87,8 +90,7 @@ const SearchScreen = ({teamId}: Props) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [resultsLoading, setResultsLoading] = useState(false);
|
||||
const [lastSearchedValue, setLastSearchedValue] = useState('');
|
||||
|
||||
const [postIds, setPostIds] = useState<string[]>(emptyPostResults);
|
||||
const [posts, setPosts] = useState<PostModel[]>(emptyPosts);
|
||||
const [fileInfos, setFileInfos] = useState<FileInfo[]>(emptyFileResults);
|
||||
const [fileChannelIds, setFileChannelIds] = useState<string[]>([]);
|
||||
|
||||
@@ -130,9 +132,11 @@ const SearchScreen = ({teamId}: Props) => {
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
handleLoading(false);
|
||||
setShowResults(true);
|
||||
}, [handleCancelAndClearSearch, handleLoading, showResults]);
|
||||
@@ -184,29 +188,14 @@ const SearchScreen = ({teamId}: Props) => {
|
||||
/>
|
||||
), [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(() => {
|
||||
if (loading) {
|
||||
return loadingComponent;
|
||||
}
|
||||
if (!showResults) {
|
||||
return initialComponent;
|
||||
}
|
||||
return resultsComponent;
|
||||
return initialComponent;
|
||||
}, [
|
||||
loading && loadingComponent,
|
||||
!loading && !showResults && initialComponent,
|
||||
!loading && showResults && resultsComponent,
|
||||
initialComponent,
|
||||
]);
|
||||
|
||||
const animated = useAnimatedStyle(() => {
|
||||
@@ -243,7 +232,7 @@ const SearchScreen = ({teamId}: Props) => {
|
||||
setTeamId={handleResultsTeamChange}
|
||||
onTabSelect={setSelectedTab}
|
||||
onFilterChanged={handleFilterChange}
|
||||
numberMessages={postIds.length}
|
||||
numberMessages={posts.length}
|
||||
selectedTab={selectedTab}
|
||||
numberFiles={fileInfos.length}
|
||||
selectedFilter={filter}
|
||||
@@ -300,21 +289,34 @@ const SearchScreen = ({teamId}: Props) => {
|
||||
<RoundedHeaderContext/>
|
||||
{header}
|
||||
</Animated.View>
|
||||
<AnimatedFlatList
|
||||
data={dummyData}
|
||||
contentContainerStyle={containerStyle}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
keyboardDismissMode={'interactive'}
|
||||
nestedScrollEnabled={true}
|
||||
indicatorStyle='black'
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
removeClippedSubviews={false}
|
||||
scrollToOverflowEnabled={true}
|
||||
overScrollMode='always'
|
||||
ref={scrollRef}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
{!showResults &&
|
||||
<AnimatedFlatList
|
||||
data={dummyData}
|
||||
contentContainerStyle={containerStyle}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
keyboardDismissMode={'interactive'}
|
||||
nestedScrollEnabled={true}
|
||||
indicatorStyle='black'
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
removeClippedSubviews={false}
|
||||
scrollToOverflowEnabled={true}
|
||||
overScrollMode='always'
|
||||
ref={scrollRef}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
}
|
||||
{showResults && !loading &&
|
||||
<Results
|
||||
loading={resultsLoading}
|
||||
selectedTab={selectedTab}
|
||||
searchValue={lastSearchedValue}
|
||||
posts={posts}
|
||||
fileInfos={fileInfos}
|
||||
scrollPaddingTop={scrollPaddingTop}
|
||||
fileChannelIds={fileChannelIds}
|
||||
/>
|
||||
}
|
||||
</Animated.View>
|
||||
</SafeAreaView>
|
||||
</FreezeScreen>
|
||||
|
||||
Reference in New Issue
Block a user