diff --git a/app/components/files/file.tsx b/app/components/files/file.tsx
index 90db548c05..bcd1287958 100644
--- a/app/components/files/file.tsx
+++ b/app/components/files/file.tsx
@@ -30,7 +30,8 @@ type FileProps = {
onPress: (index: number) => void;
publicLinkEnabled: boolean;
channelName?: string;
- onOptionsPress?: (index: number) => void;
+ onOptionsPress?: (fileInfo: FileInfo) => void;
+ optionSelected?: boolean;
wrapperWidth?: number;
showDate?: boolean;
updateFileForGallery: (idx: number, file: FileInfo) => void;
@@ -74,6 +75,7 @@ const File = ({
nonVisibleImagesCount = 0,
onOptionsPress,
onPress,
+ optionSelected,
publicLinkEnabled,
showDate = false,
updateFileForGallery,
@@ -94,19 +96,15 @@ const File = ({
const {styles, onGestureEvent, ref} = useGalleryItem(galleryIdentifier, index, handlePreviewPress);
const handleOnOptionsPress = useCallback(() => {
- onOptionsPress?.(index);
- }, [index, onOptionsPress]);
+ onOptionsPress?.(file);
+ }, [file, onOptionsPress]);
- const renderOptionsButton = () => {
- if (onOptionsPress) {
- return (
-
- );
- }
- return null;
- };
+ const optionsButton = (
+
+ );
const fileInfo = (
{fileInfo}
- {renderOptionsButton()}
+ {onOptionsPress && optionsButton}
);
};
@@ -189,7 +187,7 @@ const File = ({
{renderDocumentFile}
{fileInfo}
- {renderOptionsButton()}
+ {onOptionsPress && optionsButton}
);
} else {
diff --git a/app/components/files/file_options_icon.tsx b/app/components/files/file_options_icon.tsx
index 0a77f2bf8b..bf22d49913 100644
--- a/app/components/files/file_options_icon.tsx
+++ b/app/components/files/file_options_icon.tsx
@@ -2,32 +2,38 @@
// See LICENSE.txt for license information.
import React from 'react';
-import {TouchableOpacity, StyleSheet} from 'react-native';
+import {TouchableOpacity} from 'react-native';
import CompassIcon from '@components/compass_icon';
import {useTheme} from '@context/theme';
-import {changeOpacity} from '@utils/theme';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
type Props = {
onPress: () => void;
+ selected?: boolean;
}
-const styles = StyleSheet.create({
- threeDotContainer: {
- alignItems: 'flex-end',
- marginHorizontal: 20,
- },
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
+ return {
+ threeDotContainer: {
+ alignItems: 'flex-end',
+ borderRadius: 4,
+ marginHorizontal: 20,
+ padding: 7,
+ },
+ selected: {
+ backgroundColor: changeOpacity(theme.buttonBg, 0.08),
+ },
+ };
});
-const hitSlop = {top: 5, bottom: 5, left: 5, right: 5};
-
-export default function FileOptionsIcon({onPress}: Props) {
+export default function FileOptionsIcon({onPress, selected = false}: Props) {
const theme = useTheme();
+ const styles = getStyleSheet(theme);
return (
({
center: {
@@ -53,10 +57,10 @@ const Toast = ({animatedStyle, children, style, iconName, message, textStyle}: T
const theme = useTheme();
const styles = getStyleSheet(theme);
const dim = useWindowDimensions();
+ const isTablet = useIsTablet();
const containerStyle = useMemo(() => {
- const totalMargin = 40;
- const width = Math.min(dim.height, dim.width, 400) - totalMargin;
-
+ const toast_width = isTablet ? WIDTH_TABLET : WIDTH_MOBILE;
+ const width = Math.min(dim.height, dim.width, toast_width) - TOAST_MARGIN;
return [styles.container, {width}, style];
}, [dim, styles.container, style]);
diff --git a/app/screens/gallery/footer/copy_public_link/index.tsx b/app/screens/gallery/footer/copy_public_link/index.tsx
index 3925fcdcff..15816dafa8 100644
--- a/app/screens/gallery/footer/copy_public_link/index.tsx
+++ b/app/screens/gallery/footer/copy_public_link/index.tsx
@@ -16,6 +16,7 @@ import {useServerUrl} from '@context/server';
import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
type Props = {
+ galleryView?: boolean;
item: GalleryItemType;
setAction: (action: GalleryAction) => void;
}
@@ -29,7 +30,7 @@ const styles = StyleSheet.create({
},
});
-const CopyPublicLink = ({item, setAction}: Props) => {
+const CopyPublicLink = ({item, galleryView = true, setAction}: Props) => {
const {formatMessage} = useIntl();
const serverUrl = useServerUrl();
const insets = useSafeAreaInsets();
@@ -37,11 +38,14 @@ const CopyPublicLink = ({item, setAction}: Props) => {
const [error, setError] = useState('');
const mounted = useRef(false);
- const animatedStyle = useAnimatedStyle(() => ({
- position: 'absolute',
- bottom: GALLERY_FOOTER_HEIGHT + 8 + insets.bottom,
- opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
- }));
+ const animatedStyle = useAnimatedStyle(() => {
+ const marginBottom = galleryView ? GALLERY_FOOTER_HEIGHT + 8 : 0;
+ return {
+ position: 'absolute',
+ bottom: insets.bottom + marginBottom,
+ opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
+ };
+ });
const copyLink = async () => {
try {
@@ -87,7 +91,7 @@ const CopyPublicLink = ({item, setAction}: Props) => {
animatedStyle={animatedStyle}
style={error ? styles.error : styles.toast}
message={error || formatMessage({id: 'public_link_copied', defaultMessage: 'Link copied to clipboard'})}
- iconName='check'
+ iconName='link-variant'
/>
);
};
diff --git a/app/screens/gallery/footer/download_with_action/index.tsx b/app/screens/gallery/footer/download_with_action/index.tsx
index cba4d9478f..5e1fdc7af6 100644
--- a/app/screens/gallery/footer/download_with_action/index.tsx
+++ b/app/screens/gallery/footer/download_with_action/index.tsx
@@ -29,6 +29,7 @@ import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
type Props = {
action: GalleryAction;
+ galleryView?: boolean;
item: GalleryItemType;
setAction: (action: GalleryAction) => void;
onDownloadSuccess?: (path: string) => void;
@@ -65,7 +66,7 @@ const styles = StyleSheet.create({
},
});
-const DownloadWithAction = ({action, item, onDownloadSuccess, setAction}: Props) => {
+const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, galleryView = true}: Props) => {
const intl = useIntl();
const serverUrl = useServerUrl();
const insets = useSafeAreaInsets();
@@ -111,11 +112,14 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction}: Props)
}
}
- const animatedStyle = useAnimatedStyle(() => ({
- position: 'absolute',
- bottom: GALLERY_FOOTER_HEIGHT + 8 + insets.bottom,
- opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
- }));
+ const animatedStyle = useAnimatedStyle(() => {
+ const marginBottom = galleryView ? GALLERY_FOOTER_HEIGHT + 8 : 0;
+ return {
+ position: 'absolute',
+ bottom: insets.bottom + marginBottom,
+ opacity: withTiming(showToast ? 1 : 0, {duration: 300}),
+ };
+ });
const cancel = async () => {
try {
diff --git a/app/screens/home/search/results/file_options/index.ts b/app/screens/home/search/results/file_options/index.ts
deleted file mode 100644
index 072bc86aff..0000000000
--- a/app/screens/home/search/results/file_options/index.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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 {combineLatest, of as of$} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
-
-import {observeConfigBooleanValue, observeLicense} from '@queries/servers/system';
-
-import FileOptions from './file_options';
-
-import type {WithDatabaseArgs} from '@typings/database/database';
-
-const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
- const license = observeLicense(database);
- const enablePublicLink = observeConfigBooleanValue(database, 'EnablePublicLink');
- const enableMobileFileDownload = observeConfigBooleanValue(database, 'EnableMobileFileDownload');
-
- const complianceDisabled = license.pipe(switchMap((l) => of$(l?.IsLicensed === 'false' || l?.Compliance === 'false')));
- const canDownloadFiles = combineLatest([enableMobileFileDownload, complianceDisabled]).pipe(
- switchMap(([download, compliance]) => of$(compliance || download)),
- );
- return {
- canDownloadFiles,
- enablePublicLink,
- };
-});
-
-export default withDatabase(enhanced(FileOptions));
diff --git a/app/screens/home/search/results/file_options/mobile_options.tsx b/app/screens/home/search/results/file_options/mobile_options.tsx
new file mode 100644
index 0000000000..a2a0c4f3f0
--- /dev/null
+++ b/app/screens/home/search/results/file_options/mobile_options.tsx
@@ -0,0 +1,48 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React from 'react';
+import {EdgeInsets} from 'react-native-safe-area-context';
+
+import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
+import {bottomSheet} from '@screens/navigation';
+import {GalleryAction} from '@typings/screens/gallery';
+import {bottomSheetSnapPoint} from '@utils/helpers';
+
+import Header, {HEADER_HEIGHT} from './header';
+import OptionMenus from './option_menus';
+
+type Props = {
+ fileInfo: FileInfo;
+ insets: EdgeInsets;
+ numOptions: number;
+ setAction: (action: GalleryAction) => void;
+ theme: Theme;
+}
+
+export const showMobileOptionsBottomSheet = ({
+ fileInfo,
+ insets,
+ numOptions,
+ setAction,
+ theme,
+}: Props) => {
+ const renderContent = () => (
+ <>
+
+
+ >
+ );
+
+ bottomSheet({
+ closeButtonId: 'close-search-file-options',
+ renderContent,
+ snapPoints: [
+ bottomSheetSnapPoint(numOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT, 10,
+ ],
+ theme,
+ title: '',
+ });
+};
diff --git a/app/screens/home/search/results/file_options/option_menus/index.tsx b/app/screens/home/search/results/file_options/option_menus/index.tsx
new file mode 100644
index 0000000000..117a2b9b04
--- /dev/null
+++ b/app/screens/home/search/results/file_options/option_menus/index.tsx
@@ -0,0 +1,36 @@
+// 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 compose from 'lodash/fp/compose';
+import {combineLatest, of as of$} from 'rxjs';
+import {map, switchMap} from 'rxjs/operators';
+
+import {observeLicense, observeConfigBooleanValue} from '@queries/servers/system';
+
+import OptionMenus from './option_menus';
+
+import type {WithDatabaseArgs} from '@typings/database/database';
+
+const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
+ const enableMobileFileDownload = observeConfigBooleanValue(database, 'EnableMobileFileDownload');
+
+ const complianceDisabled = observeLicense(database).pipe(
+ switchMap((lcs) => of$(lcs?.IsLicensed === 'false' || lcs?.Compliance === 'false')),
+ );
+
+ const canDownloadFiles = combineLatest([enableMobileFileDownload, complianceDisabled]).pipe(
+ map(([download, compliance]) => compliance || download),
+ );
+
+ return {
+ canDownloadFiles,
+ enablePublicLink: observeConfigBooleanValue(database, 'EnablePublicLink'),
+ };
+});
+
+export default compose(
+ withDatabase,
+ enhance,
+)(OptionMenus);
diff --git a/app/screens/home/search/results/file_options/file_options.tsx b/app/screens/home/search/results/file_options/option_menus/option_menus.tsx
similarity index 51%
rename from app/screens/home/search/results/file_options/file_options.tsx
rename to app/screens/home/search/results/file_options/option_menus/option_menus.tsx
index a16f8f198e..2c254273c3 100644
--- a/app/screens/home/search/results/file_options/file_options.tsx
+++ b/app/screens/home/search/results/file_options/option_menus/option_menus.tsx
@@ -1,55 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useCallback, useState} from 'react';
+import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
-import {View, StyleSheet} from 'react-native';
import {showPermalink} from '@actions/remote/permalink';
import OptionItem from '@components/option_item';
import {useServerUrl} from '@context/server';
-import CopyPublicLink from '@screens/gallery/footer/copy_public_link';
-import DownloadWithAction from '@screens/gallery/footer/download_with_action';
-
-import Header from './header';
-
-import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
-
-const styles = StyleSheet.create({
- toast: {
- marginTop: 100,
- alignItems: 'center',
- },
-});
+import {useIsTablet} from '@hooks/device';
+import {dismissBottomSheet} from '@screens/navigation';
+import {GalleryAction} from '@typings/screens/gallery';
type Props = {
- canDownloadFiles: boolean;
- enablePublicLink: boolean;
+ canDownloadFiles?: boolean;
+ enablePublicLink?: boolean;
fileInfo: FileInfo;
+ setAction: (action: GalleryAction) => void;
}
-const FileOptions = ({fileInfo, canDownloadFiles, enablePublicLink}: Props) => {
- const intl = useIntl();
+const OptionMenus = ({
+ canDownloadFiles,
+ enablePublicLink,
+ fileInfo,
+ setAction,
+}: Props) => {
const serverUrl = useServerUrl();
- const [action, setAction] = useState('none');
-
- const galleryItem = {...fileInfo, type: 'image'} as GalleryItemType;
+ const isTablet = useIsTablet();
+ const intl = useIntl();
const handleDownload = useCallback(() => {
+ if (!isTablet) {
+ dismissBottomSheet();
+ }
setAction('downloading');
- }, []);
+ }, [setAction]);
const handleCopyLink = useCallback(() => {
+ if (!isTablet) {
+ dismissBottomSheet();
+ }
setAction('copying');
- }, []);
+ }, [setAction]);
const handlePermalink = useCallback(() => {
- showPermalink(serverUrl, '', fileInfo.post_id, intl);
- }, [serverUrl, fileInfo.post_id, intl]);
+ if (fileInfo.post_id) {
+ showPermalink(serverUrl, '', fileInfo.post_id, intl);
+ setAction('opening');
+ }
+ }, [intl, serverUrl, fileInfo.post_id, setAction]);
return (
-
-
+ <>
{canDownloadFiles &&
{
/>
}
{
/>
{enablePublicLink &&
}
-
- {action === 'downloading' &&
-
- }
- {action === 'copying' &&
-
- }
-
-
+ >
);
};
-export default FileOptions;
+export default OptionMenus;
diff --git a/app/screens/home/search/results/file_options/tablet_options.tsx b/app/screens/home/search/results/file_options/tablet_options.tsx
new file mode 100644
index 0000000000..9dc4641c76
--- /dev/null
+++ b/app/screens/home/search/results/file_options/tablet_options.tsx
@@ -0,0 +1,87 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React, {useCallback, useMemo} from 'react';
+import {Overlay} from 'react-native-elements';
+
+import {ITEM_HEIGHT} from '@components/option_item';
+import {useTheme} from '@context/theme';
+import {GalleryAction} from '@typings/screens/gallery';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+
+import {XyOffset} from '../file_result';
+
+import OptionMenus from './option_menus';
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
+ tablet: {
+ backgroundColor: theme.centerChannelBg,
+ borderColor: changeOpacity(theme.centerChannelColor, 0.16),
+ borderRadius: 8,
+ borderWidth: 1,
+ paddingLeft: 20,
+ position: 'absolute',
+ right: 20,
+ width: 252,
+ marginRight: 20,
+ shadowColor: theme.centerChannelColor,
+ shadowOffset: {
+ width: 0,
+ height: 8,
+ },
+ shadowOpacity: 0.12,
+ shadowRadius: 24,
+ },
+ backDrop: {opacity: 0},
+}));
+
+const openDownMargin = 64;
+
+type Props = {
+ fileInfo: FileInfo;
+ numOptions: number;
+ openUp?: boolean;
+ setAction: (action: GalleryAction) => void;
+ setShowOptions: (show: boolean) => void;
+ xyOffset: XyOffset;
+}
+const TabletOptions = ({
+ fileInfo,
+ numOptions,
+ openUp = false,
+ setAction,
+ setShowOptions,
+ xyOffset,
+}: Props) => {
+ const theme = useTheme();
+ const styles = getStyleSheet(theme);
+
+ const toggleOverlay = useCallback(() => {
+ setShowOptions(false);
+ }, []);
+
+ const overlayStyle = useMemo(() => ({
+ marginTop: openUp ? 0 : openDownMargin,
+ top: xyOffset?.y ? xyOffset.y - (openUp ? ITEM_HEIGHT * numOptions : 0) : 0,
+ right: xyOffset?.x,
+ }), [numOptions, openUp, xyOffset]);
+
+ return (
+
+
+
+ );
+};
+
+export default TabletOptions;
diff --git a/app/screens/home/search/results/file_options/toasts.tsx b/app/screens/home/search/results/file_options/toasts.tsx
new file mode 100644
index 0000000000..7aad769a3d
--- /dev/null
+++ b/app/screens/home/search/results/file_options/toasts.tsx
@@ -0,0 +1,46 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React from 'react';
+
+import CopyPublicLink from '@screens/gallery/footer/copy_public_link';
+import DownloadWithAction from '@screens/gallery/footer/download_with_action';
+
+import type {GalleryAction, GalleryItemType} from '@typings/screens/gallery';
+
+type Props = {
+ action: GalleryAction;
+ fileInfo: FileInfo | undefined;
+ setAction: (action: GalleryAction) => void;
+}
+const Toasts = ({
+ action,
+ fileInfo,
+ setAction,
+}: Props) => {
+ const galleryItem = {...fileInfo, type: 'image'} as GalleryItemType;
+
+ switch (action) {
+ case 'downloading':
+ return (
+
+ );
+ case 'copying':
+ return (
+
+ );
+
+ default:
+ return null;
+ }
+};
+
+export default Toasts;
diff --git a/app/screens/home/search/results/file_result.tsx b/app/screens/home/search/results/file_result.tsx
new file mode 100644
index 0000000000..9125c504d0
--- /dev/null
+++ b/app/screens/home/search/results/file_result.tsx
@@ -0,0 +1,122 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useCallback, useRef, useState} from 'react';
+import {Dimensions, StyleSheet, View} from 'react-native';
+
+import File from '@components/files/file';
+import {useIsTablet} from '@hooks/device';
+import {GalleryAction} from '@typings/screens/gallery';
+import {getViewPortWidth} from '@utils/images';
+
+import TabletOptions from './file_options/tablet_options';
+
+export type XyOffset = {x: number; y: number} | undefined;
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ marginHorizontal: 20,
+ },
+});
+
+type Props = {
+ canDownloadFiles: boolean;
+ channelName: string | undefined;
+ fileInfo: FileInfo;
+ index: number;
+ isSingleImage: boolean;
+ numOptions: number;
+ onOptionsPress: (finfo: FileInfo) => void;
+ onPress: (idx: number) => void;
+ publicLinkEnabled: boolean;
+ setAction: (action: GalleryAction) => void;
+ updateFileForGallery: (idx: number, file: FileInfo) => void;
+}
+
+const galleryIdentifier = 'search-files-location';
+
+const FileResult = ({
+ canDownloadFiles,
+ channelName,
+ fileInfo,
+ index,
+ isSingleImage,
+ numOptions,
+ onOptionsPress,
+ onPress,
+ publicLinkEnabled,
+ setAction,
+ updateFileForGallery,
+}: Props) => {
+ const elementsRef = useRef(null);
+ const isTablet = useIsTablet();
+ const isReplyPost = false;
+
+ const [showOptions, setShowOptions] = useState(false);
+ const [openUp, setOpenUp] = useState(false);
+ const [xyOffset, setXYoffset] = useState(undefined);
+ const {height} = Dimensions.get('window');
+
+ const fileRef = useCallback((element: View) => {
+ if (showOptions) {
+ elementsRef.current = element;
+ elementsRef?.current?.measureInWindow((x, y) => {
+ setOpenUp((y > height / 2));
+ setXYoffset({x, y});
+ });
+ }
+ }, [elementsRef, showOptions]);
+
+ const handleOptionsPress = useCallback((fInfo: FileInfo) => {
+ setShowOptions(true);
+ onOptionsPress(fInfo);
+ }, []);
+
+ const handleSetAction = useCallback((action: GalleryAction) => {
+ setAction(action);
+ if (showOptions && action !== 'none') {
+ setShowOptions(false);
+ }
+ }, [setAction, showOptions]);
+
+ return (
+ <>
+
+
+
+ {isTablet && showOptions && xyOffset &&
+
+ }
+ >
+ );
+};
+
+export default FileResult;
diff --git a/app/screens/home/search/results/file_results.tsx b/app/screens/home/search/results/file_results.tsx
index ec01da4b2d..5e22bf3d26 100644
--- a/app/screens/home/search/results/file_results.tsx
+++ b/app/screens/home/search/results/file_results.tsx
@@ -1,44 +1,39 @@
// 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 React, {useCallback, useMemo, useState} from 'react';
+import {FlatList, ListRenderItemInfo, StyleProp, ViewStyle} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
-import File from '@components/files/file';
+import {useIsTablet} from '@app/hooks/device';
+import {useImageAttachments} from '@app/hooks/files';
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 {GalleryAction} from '@typings/screens/gallery';
import {isImage, isVideo} from '@utils/file';
-import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery';
-import {bottomSheetSnapPoint} from '@utils/helpers';
-import {getViewPortWidth} from '@utils/images';
+import {
+ getChannelNamesWithID,
+ getFileInfosIndexes,
+ getNumberFileMenuOptions,
+ getOrderedFileInfos,
+ getOrderedGalleryItems,
+} from '@utils/files';
+import {openGalleryAtIndex} from '@utils/gallery';
import {TabTypes} from '@utils/search';
import {preventDoubleTap} from '@utils/tap';
-import FileOptions from './file_options';
-import {HEADER_HEIGHT} from './file_options/header';
+import {showMobileOptionsBottomSheet} from './file_options/mobile_options';
+import Toasts from './file_options/toasts';
+import FileResult from './file_result';
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;
+ publicLinkEnabled: boolean;
searchValue: string;
}
@@ -48,121 +43,76 @@ const FileResults = ({
canDownloadFiles,
fileChannels,
fileInfos,
- publicLinkEnabled,
paddingTop,
+ publicLinkEnabled,
searchValue,
}: Props) => {
const theme = useTheme();
- const isTablet = useIsTablet();
const insets = useSafeAreaInsets();
- const [lastViewedIndex, setLastViewedIndex] = useState(undefined);
- const containerStyle = useMemo(() => ({top: fileInfos.length ? 8 : 0}), [fileInfos]);
+ const isTablet = useIsTablet();
+
+ const [action, setAction] = useState('none');
+ const [lastViewedFileInfo, setLastViewedFileInfo] = useState(undefined);
+
+ const containerStyle = useMemo(() => ([paddingTop, {top: fileInfos.length ? 8 : 0}]), [fileInfos, paddingTop]);
+ const numOptions = getNumberFileMenuOptions(canDownloadFiles, publicLinkEnabled);
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 filesForGallery = imageAttachments.concat(nonImageAttachments);
- 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 channelNames = useMemo(() => getChannelNamesWithID(fileChannels), [fileChannels]);
+ const orderedFileInfos = useMemo(() => getOrderedFileInfos(filesForGallery), []);
+ const fileInfosIndexes = useMemo(() => getFileInfosIndexes(orderedFileInfos), []);
+ const orderedGalleryItems = useMemo(() => getOrderedGalleryItems(orderedFileInfos), []);
- 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 = () => (
-
- );
- 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 onPreviewPress = useCallback(preventDoubleTap((idx: number) => {
+ openGalleryAtIndex(galleryIdentifier, idx, orderedGalleryItems);
+ }), [orderedGalleryItems]);
const updateFileForGallery = (idx: number, file: FileInfo) => {
'worklet';
- orderedFilesForGallery[idx] = file;
+ orderedFileInfos[idx] = file;
};
- const renderItem = useCallback(({item}: ListRenderItemInfo) => {
- const container: StyleProp = fileInfos.length > 1 ? styles.container : undefined;
- const isSingleImage = orderedFilesForGallery.length === 1 && (isImage(orderedFilesForGallery[0]) || isVideo(orderedFilesForGallery[0]));
- const isReplyPost = false;
+ const onOptionsPress = useCallback((fInfo: FileInfo) => {
+ setLastViewedFileInfo(fInfo);
+ if (!isTablet) {
+ showMobileOptionsBottomSheet({
+ fileInfo: fInfo,
+ insets,
+ numOptions,
+ setAction,
+ theme,
+ });
+ }
+ }, [insets, isTablet, numOptions, theme]);
+
+ const renderItem = useCallback(({item}: ListRenderItemInfo) => {
+ const isSingleImage = orderedFileInfos.length === 1 && (isImage(orderedFileInfos[0]) || isVideo(orderedFileInfos[0]));
return (
-
-
-
+
);
}, [
- (orderedFilesForGallery.length === 1) && orderedFilesForGallery[0].mime_type,
+ (orderedFileInfos.length === 1) && orderedFileInfos[0].mime_type,
canDownloadFiles,
channelNames,
- fileInfos.length > 1,
- filesForGalleryIndexes,
- handleOptionsPress,
- handlePreviewPress,
- isTablet,
+ fileInfosIndexes,
+ onPreviewPress,
+ setAction,
publicLinkEnabled,
- theme,
]);
const noResults = useMemo(() => (
@@ -173,23 +123,30 @@ const FileResults = ({
), [searchValue]);
return (
-
+ <>
+
+
+ >
);
};
diff --git a/app/screens/home/search/results/results.tsx b/app/screens/home/search/results/results.tsx
index 6bf6fbba82..5971ec699c 100644
--- a/app/screens/home/search/results/results.tsx
+++ b/app/screens/home/search/results/results.tsx
@@ -33,7 +33,6 @@ const getStyles = (dimensions: ScaledSize) => {
flex: 1,
width: dimensions.width,
},
-
});
};
@@ -106,8 +105,8 @@ const Results = ({
canDownloadFiles={canDownloadFiles}
fileChannels={fileChannels}
fileInfos={fileInfos}
- publicLinkEnabled={publicLinkEnabled}
paddingTop={paddingTop}
+ publicLinkEnabled={publicLinkEnabled}
searchValue={searchValue}
/>
diff --git a/app/utils/files.tsx b/app/utils/files.tsx
new file mode 100644
index 0000000000..8c625ee6d5
--- /dev/null
+++ b/app/utils/files.tsx
@@ -0,0 +1,44 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {ChannelModel} from '@app/database/models/server';
+import {fileToGalleryItem} from '@utils/gallery';
+
+export const getNumberFileMenuOptions = (canDownloadFiles: boolean, publicLinkEnabled: boolean) => {
+ let numberItems = 1;
+ numberItems += canDownloadFiles ? 1 : 0;
+ numberItems += publicLinkEnabled ? 1 : 0;
+ return numberItems;
+};
+
+// return an object with key:value of channelID:channelDisplayName
+export const getChannelNamesWithID = (fileChannels: ChannelModel[]) => {
+ return fileChannels.reduce<{[id: string]: string | undefined}>((acc, v) => {
+ acc[v.id] = v.displayName;
+ return acc;
+ }, {});
+};
+
+// return array of fileInfos (image and non-image) sorted by create_at date
+export const getOrderedFileInfos = (fileInfos: FileInfo[]) => {
+ return fileInfos.sort((a: FileInfo, b: FileInfo) => {
+ return (b.create_at || 0) - (a.create_at || 0);
+ });
+};
+
+// returns object with keys of fileInfo.id and value of the ordered index from
+// orderedFilesForGallery
+export const getFileInfosIndexes = (orderedFilesForGallery: FileInfo[]) => {
+ return orderedFilesForGallery.reduce<{[id: string]: number | undefined}>((acc, v, idx) => {
+ if (v.id) {
+ acc[v.id] = idx;
+ }
+ return acc;
+ }, {});
+};
+
+// return ordered FileInfo[] converted to GalleryItemType[]
+export const getOrderedGalleryItems = (orderedFileInfos: FileInfo[]) => {
+ return orderedFileInfos.map((f) => fileToGalleryItem(f, f.user_id));
+};
+