diff --git a/client-new/src/components/filecollection/FileList.tsx b/client-new/src/components/filecollection/FileList.tsx index d3b6a46..4512908 100644 --- a/client-new/src/components/filecollection/FileList.tsx +++ b/client-new/src/components/filecollection/FileList.tsx @@ -1,39 +1,78 @@ import { IFile } from '@/interfaces/IFile'; -import { Text, View } from 'native-base'; +import { moderateScale, verticalScale } from '@/utils/FontSizeUtils'; +import { HStack, Text, VStack, View } from 'native-base'; + import React from 'react'; import { heightPercentageToDP as h } from 'react-native-responsive-screen'; -import FileRow from './FileRow'; + import NoTaskIcon from '../icons/NoTaskIcon'; -import { moderateScale, verticalScale } from '@/utils/FontSizeUtils'; +import FileRow from './FileRow'; type FileListProps = { files: IFile[]; orderBy?: string; reverse?: boolean; + gallery?: boolean; + refetch: Function; }; -const FileList: React.FC = ({ files, orderBy, reverse }: FileListProps) => { +const FileList: React.FC = ({ + files, + orderBy, + reverse, + gallery, + refetch +}: FileListProps) => { console.log('FileListProps', { orderBy, reverse }); let sortedFiles = [...files]; if (orderBy === 'name') { - sortedFiles = sortedFiles.sort((a, b) => a.file_name.localeCompare(b.file_name)); + sortedFiles = sortedFiles.sort((a, b) => + a.file_name.localeCompare(b.file_name) + ); } else if (orderBy === 'date') { - sortedFiles = sortedFiles.sort((a, b) => (a.updated_at > b.updated_at ? -1 : 1)); + sortedFiles = sortedFiles.sort((a, b) => + a.updated_at > b.updated_at ? -1 : 1 + ); } else if (orderBy === 'size') { - sortedFiles = sortedFiles.sort((a, b) => (a.file_size > b.file_size ? -1 : 1)); + sortedFiles = sortedFiles.sort((a, b) => + a.file_size > b.file_size ? -1 : 1 + ); } if (reverse) { sortedFiles.reverse(); } + const itemsPerRow = 2; + + // Split the items into rows of 2 + const rows = Array.from({ length: Math.ceil(sortedFiles.length / itemsPerRow) }, (_, index) => + sortedFiles.slice(index * itemsPerRow, (index + 1) * itemsPerRow) + ); + return ( - {sortedFiles.length === 0 && } - {sortedFiles.map((file) => ( - - ))} + {gallery ? // Use HStack for horizontal layout if gallery is true + + {rows.map((row, rowIndex) => ( + + {row.map((item, itemIndex) => ( + + + + ))} + + ))} + + : ( + + {sortedFiles.length === 0 && } + {sortedFiles.map((file) => ( + + ))} + + )} ); }; @@ -42,11 +81,7 @@ export default FileList; const NoTasks = () => { return ( - + { No files found. - ) -} \ No newline at end of file + ); +}; diff --git a/client-new/src/components/filecollection/FileRow.tsx b/client-new/src/components/filecollection/FileRow.tsx index c318559..ce49f7b 100644 --- a/client-new/src/components/filecollection/FileRow.tsx +++ b/client-new/src/components/filecollection/FileRow.tsx @@ -1,13 +1,13 @@ import { IFile } from '@/interfaces/IFile'; -import { fetchFileURL } from '@/services/FileService'; +import { deleteFile, fetchFileURL } from '@/services/FileService'; import { ConvertFileSize } from '@/utils/FileUtils'; import { useQuery } from '@tanstack/react-query'; +import * as FileSystem from 'expo-file-system'; import { Text, View } from 'native-base'; -import React from 'react'; +import React, { useState } from 'react'; import { Alert, Linking, Pressable } from 'react-native'; import FileViewer from 'react-native-file-viewer'; -import * as FileSystem from 'expo-file-system'; import { heightPercentageToDP as h, widthPercentageToDP as w @@ -20,24 +20,24 @@ import OpenLinkButton from '../reusable/OpenLinkButton'; type FileRowProps = { file: IFile; + refetch: Function; }; -const FileRow: React.FC = ({ file }) => { +const FileRow: React.FC = ({ file, refetch }) => { const size = ConvertFileSize(file.file_size); const lastDotIndex = file.file_name.lastIndexOf('.'); const fileName = file.file_name.substring(0, lastDotIndex); const fileExtension = file.file_name.substring(lastDotIndex + 1); const truncatedName = - fileName.length > 50 - ? fileName.substring(0, 50) + '...' - : fileName; + fileName.length > 50 ? fileName.substring(0, 50) + '...' : fileName; const handlePress = async () => { const url = await fetchFileURL(file.id); - const downloadResumable = FileSystem.createDownloadResumable(url, - FileSystem.cacheDirectory + file.file_name, + const downloadResumable = FileSystem.createDownloadResumable( + url, + FileSystem.cacheDirectory + file.file_name ); try { @@ -45,52 +45,62 @@ const FileRow: React.FC = ({ file }) => { console.log('Finished downloading to ', uri); FileViewer.open(uri, { showOpenWithDialog: true, - showAppsSuggestions: true, + showAppsSuggestions: true }); } catch (e) { console.error(e); } + }; - } + const handleDelete = async () => { + const result = await deleteFile(file.id); + if (result != 200) { + console.log('Did not successfully delete the file'); + } + }; // Example of setup fileOptions const fileOptions = (fileId: number) => { - Alert.alert( - 'File Options', - 'What would you like to do with this file?', - [ - { - text: 'Cancel', - style: 'cancel', - onPress: () => console.log('Cancel Pressed') - }, - { - text: 'Download', - style: 'default', - onPress: () => { - handlePress(); - } - }, - { - text: 'Open in Browser', - style: 'default', - onPress: async () => { - const url = await fetchFileURL(file.id); - Linking.openURL(url); - } - }, - { - text: 'Share', - style: 'default', - onPress: () => console.log('Share Pressed') - }, - { - text: 'Delete', - style: 'destructive', - onPress: () => console.log('Delete Pressed') + Alert.alert('File Options', 'What would you like to do with this file?', [ + { + text: 'Cancel', + style: 'cancel', + onPress: () => console.log('Cancel Pressed') + }, + { + text: 'Download', + style: 'default', + onPress: () => { + handlePress(); } - ] - ); + }, + { + text: 'Open in Browser', + style: 'default', + onPress: async () => { + const url = await fetchFileURL(file.id); + Linking.openURL(url); + } + }, + { + text: 'Share', + style: 'default', + onPress: async () => { + // Doesn't work on simulator + const url = await fetchFileURL(file.id); + Linking.openURL(`mailto:subject=SendMail&body=${url}`); + } + }, + { + text: 'Delete', + style: 'destructive', + onPress: async () => { + await handleDelete(); + refetch(); + } + // console.log('Delete Pressed') + } + ]); }; return ( @@ -110,14 +120,19 @@ const FileRow: React.FC = ({ file }) => { marginTop={h('1%')} > {truncatedName} - {fileExtension} ∙ {size} + + {fileExtension} ∙ {size} + - fileOptions(file.id)} style={{ transform: [{ rotate: '90deg' }] }} > + fileOptions(file.id)} + style={{ transform: [{ rotate: '90deg' }] }} + > diff --git a/client-new/src/components/reusable/Rectangle.tsx b/client-new/src/components/reusable/Rectangle.tsx new file mode 100644 index 0000000..4dd90b0 --- /dev/null +++ b/client-new/src/components/reusable/Rectangle.tsx @@ -0,0 +1,19 @@ +import { View } from 'native-base'; + +import React from 'react'; + +export default function Rectangle() { + return ( + <> + + + ); +} \ No newline at end of file diff --git a/client-new/src/screens/app/FileCollectionScreen.tsx b/client-new/src/screens/app/FileCollectionScreen.tsx index 7765f1b..c59d246 100644 --- a/client-new/src/screens/app/FileCollectionScreen.tsx +++ b/client-new/src/screens/app/FileCollectionScreen.tsx @@ -1,9 +1,9 @@ - // import * as DocumentPicker from 'expo-document-picker'; -import * as FileSystem from 'expo-file-system'; -import * as DocumentPicker from 'expo-document-picker'; import FileList from '@/components/filecollection/FileList'; +import NoTaskIcon from '@/components/icons/NoTaskIcon'; +import ActivityLoader from '@/components/reusable/ActivityLoader'; import LegacyWordmark from '@/components/reusable/LegacyWordmark'; +import Rectangle from '@/components/reusable/Rectangle'; import ScreenBody from '@/components/reusable/ScreenBody'; import SearchBar from '@/components/reusable/SearchBar'; import TaskTagGrid from '@/components/reusable/TaskTagGrid'; @@ -12,18 +12,29 @@ import { IFile } from '@/interfaces/IFile'; import { fetchUserFilesList, uploadFile } from '@/services/FileService'; import { moderateScale, verticalScale } from '@/utils/FontSizeUtils'; import { useMutation, useQuery } from '@tanstack/react-query'; +import * as DocumentPicker from 'expo-document-picker'; +import * as FileSystem from 'expo-file-system'; import Fuse from 'fuse.js'; -import { AddIcon, Button, ChevronDownIcon, Icon, Pressable, ScrollView, Text, View } from 'native-base'; +import { + AddIcon, + Button, + ChevronDownIcon, + Icon, + Pressable, + ScrollView, + Square, + Text, + View +} from 'native-base'; +import { background } from 'native-base/lib/typescript/theme/styled-system'; + import React, { useRef, useState } from 'react'; -import { Alert, RefreshControl } from 'react-native'; +import { Alert, RefreshControl, TouchableOpacity, ViewStyle } from 'react-native'; import { heightPercentageToDP as h, widthPercentageToDP as w } from 'react-native-responsive-screen'; import { SafeAreaView } from 'react-native-safe-area-context'; -import NoTaskIcon from '@/components/icons/NoTaskIcon'; -import ActivityLoader from '@/components/reusable/ActivityLoader'; - export default function FileCollectionScreen() { const { user } = useUser(); @@ -33,6 +44,7 @@ export default function FileCollectionScreen() { const [open, setOpen] = useState(false); const [orderBy, setOrderBy] = useState('name'); const [reverse, setReverse] = useState(false); + const [gallery, setGallery] = useState(false); const { isPending, @@ -42,11 +54,10 @@ export default function FileCollectionScreen() { } = useQuery({ queryKey: ['userfiles', user?.id, selectedTags], queryFn: () => fetchUserFilesList(user.id, selectedTags), - staleTime: 1000 * 60 * 5, // 5 minutes + staleTime: 1000 * 60 * 5 // 5 minutes }); console.log('Query Key:', ['userFiles', user?.id, selectedTags]); - const filterFiles = (files: IFile[], keys: string[]): IFile[] => { if (search.length > 0) { const options = { @@ -76,7 +87,7 @@ export default function FileCollectionScreen() { }, onError: (error) => { Alert.alert('Error uploading file', error.message); - }, + } }); } } catch (err) { @@ -85,11 +96,20 @@ export default function FileCollectionScreen() { }; const sendFile = useMutation({ - mutationFn: async (file: DocumentPicker.DocumentPickerAsset) => await uploadFile(file, user.id) - }) + mutationFn: async (file: DocumentPicker.DocumentPickerAsset) => + await uploadFile(file, user.id) + }); + + const handlePress = () => { + setGallery(!gallery); + }; + return ( - + - @@ -167,14 +186,46 @@ export default function FileCollectionScreen() { pressfunc={setSelectedTags} /> - + {isPending && } {error && Error: {error.message}} - {filteredFiles && } + {filteredFiles && ( + + )} {sendFile.isPending && } + + + + {gallery ? : } + + + + ); } @@ -186,7 +237,12 @@ type AllFilesSelectionProps = { setReverse: React.Dispatch>; }; -const AllFilesSelection = ({ orderBy, setOrderBy, reverse, setReverse }: AllFilesSelectionProps) => { +const AllFilesSelection = ({ + orderBy, + setOrderBy, + reverse, + setReverse +}: AllFilesSelectionProps) => { const [rotated, setRotated] = useState(false); // name -> size -> date @@ -198,11 +254,11 @@ const AllFilesSelection = ({ orderBy, setOrderBy, reverse, setReverse }: AllFile } else { setOrderBy('name'); } - } + }; const handleReverse = () => { setReverse(!reverse); - } + }; return ( - {orderBy === 'name' ? 'Name' : orderBy === 'name-reversed' ? 'Name' : orderBy === 'size' ? 'Size' : 'Last Modified'} + {orderBy === 'name' + ? 'Name' + : orderBy === 'name-reversed' + ? 'Name' + : orderBy === 'size' + ? 'Size' + : 'Last Modified'} - { - handleReverse(); - }} style={{ transform: [{ rotate: reverse ? '180deg' : '0deg' }] }}> + { + handleReverse(); + }} + style={{ transform: [{ rotate: reverse ? '180deg' : '0deg' }] }} + > - ) + ); +}; + +const Grid = () => { + + return ( + + + + + + + + + + + + + ); +}; + +const Rows = () => { + return ( + + + + + + ); } diff --git a/client-new/src/services/FileService.ts b/client-new/src/services/FileService.ts index 9db66f8..b65fcc6 100644 --- a/client-new/src/services/FileService.ts +++ b/client-new/src/services/FileService.ts @@ -94,3 +94,19 @@ export const createFile = async ( console.log('Error:', error); } }; + +/** + * Deletes a file url from the server + * @param fileId the id of the file to delete + * @returns the response status + */ +export const deleteFile = async (fileId: number) => { + try { + const response = await axios.delete(`${API_BASE_URL}/files/${fileId}`); + console.log(response.status) + return response.status; + } catch (error) { + console.log('Error deleting the file', error); + throw new Error('Error deleting the file'); + } +}