Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(courses): Allow user to choose default files view (by folders/by date) #531

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
9 changes: 7 additions & 2 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,15 +292,20 @@
"subtitle_plural": "{{count}} files"
},
"courseDirectoryScreen": {
"noResult": "No results found"
"clearSearch": "Clear search",
"emptyRootFolder": "There are no files",
"emptyFolder": "The folder is empty",
"noResult": "No results found",
"navigateRecentFiles": "View recent",
"search": "Search"
},
"courseFileListItem": {
"openFileError": "Cannot open the file.",
"reDownloadFile": "Redownload file"
},
"courseFilesTab": {
"browseFiles": "Browse files",
"empty": "There are no files here",
"empty": "There are no files",
"navigateFolders": "Navigate folders",
"recentSectionTitle": "Recent files",
"title": "Files"
Expand Down
9 changes: 7 additions & 2 deletions assets/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,15 +292,20 @@
"subtitle_plural": "{{count}} file"
},
"courseDirectoryScreen": {
"noResult": "Nessun risultato trovato"
"clearSearch": "Cancella ricerca",
"emptyRootFolder": "Non ci sono file",
"emptyFolder": "La cartella è vuota",
"noResult": "Nessun risultato trovato",
"navigateRecentFiles": "Sfoglia file recenti",
"search": "Cerca"
},
"courseFileListItem": {
"openFileError": "Impossibile aprire il file.",
"reDownloadFile": "Ri-scarica file"
},
"courseFilesTab": {
"browseFiles": "Esplora file",
"empty": "Non ci sono file qui",
"empty": "Non ci sono file",
"navigateFolders": "Sfoglia le cartelle",
"recentSectionTitle": "File recenti",
"title": "Materiale"
Expand Down
3 changes: 3 additions & 0 deletions src/core/contexts/PreferencesContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const editablePreferenceKeys = [
'emailGuideRead',
'placesSearched',
'agendaScreen',
'filesScreen',
'hideGrades',
] as const;

Expand All @@ -34,6 +35,7 @@ export const objectPreferenceKeys = [
'emailGuideRead',
'placesSearched',
'agendaScreen',
'filesScreen',
'hideGrades',
];

Expand Down Expand Up @@ -62,6 +64,7 @@ export interface PreferencesContextBase {
layout: 'weekly' | 'daily';
filters: AgendaTypesFilterState;
};
filesScreen: 'filesView' | 'directoryView';
hideGrades?: boolean;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/providers/PreferencesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const PreferencesProvider = ({ children }: PropsWithChildren) => {
lecture: false,
},
},
filesScreen: 'filesView',
});

const preferencesInitialized = useRef<boolean>(false);
Expand Down
4 changes: 2 additions & 2 deletions src/features/courses/navigation/CourseNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { CourseIndicator } from '../components/CourseIndicator';
import { CourseContext } from '../contexts/CourseContext';
import { CourseFilesCacheProvider } from '../providers/CourseFilesCacheProvider';
import { CourseAssignmentsScreen } from '../screens/CourseAssignmentsScreen';
import { CourseFilesScreen } from '../screens/CourseFilesScreen';
import { CourseInfoScreen } from '../screens/CourseInfoScreen';
import { CourseLecturesScreen } from '../screens/CourseLecturesScreen';
import { CourseNoticesScreen } from '../screens/CourseNoticesScreen';
import { FileNavigator } from './FileNavigator';

type Props = NativeStackScreenProps<TeachingStackParamList, 'Course'>;

Expand Down Expand Up @@ -141,7 +141,7 @@ export const CourseNavigator = ({ route, navigation }: Props) => {
/>
<TopTabs.Screen
name="CourseFilesScreen"
component={CourseFilesScreen}
component={FileNavigator}
options={{
title: t('courseFilesTab.title'),
tabBarBadge: () =>
Expand Down
7 changes: 2 additions & 5 deletions src/features/courses/navigation/CourseSharedScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export interface CourseSharedScreensParamList extends ParamListBase {
Course: { id: number; animated?: boolean };
Notice: { noticeId: number; courseId: number };
CoursePreferences: { courseId: number; uniqueShortcode: string };
CourseGuide: { courseId: number };
CourseDirectory: {
courseId: number;
directoryId?: string;
directoryName?: string;
};
CourseGuide: { courseId: number };
CourseVideolecture: {
courseId: number;
lectureId: number;
Expand Down Expand Up @@ -111,13 +111,10 @@ export const CourseSharedScreens = (
<Stack.Screen
name="CourseDirectory"
component={CourseDirectoryScreen}
getId={({ params }) => `${params.directoryId}`}
getId={({ params }) => `${params?.directoryId}`}
options={{
headerBackTitleVisible: false,
headerLargeTitle: false,
headerSearchBarOptions: {
hideWhenScrolling: false,
},
}}
/>

Expand Down
50 changes: 50 additions & 0 deletions src/features/courses/navigation/FileNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useTheme } from '@lib/ui/hooks/useTheme';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import { usePreferencesContext } from '../../../core/contexts/PreferencesContext';
import { useTitlesStyles } from '../../../core/hooks/useTitlesStyles';
import { useCourseContext } from '../contexts/CourseContext';
import { CourseDirectoryScreen } from '../screens/CourseDirectoryScreen';
import { CourseFilesScreen } from '../screens/CourseFilesScreen';

export type FileStackParamList = {
RecentFiles: {
courseId: number;
};
DirectoryFiles: {
courseId: number;
directoryId?: string;
directoryName?: string;
};
};

const Stack = createNativeStackNavigator<FileStackParamList>();
export const FileNavigator = () => {
const theme = useTheme();
const { filesScreen } = usePreferencesContext();
const courseId = useCourseContext();

return (
<Stack.Navigator
id="FileTabNavigator"
screenOptions={{
headerShown: false,
...useTitlesStyles(theme),
}}
initialRouteName={
filesScreen === 'filesView' ? 'RecentFiles' : 'DirectoryFiles'
}
>
<Stack.Screen
name="RecentFiles"
component={CourseFilesScreen}
initialParams={{ courseId: courseId }}
/>
<Stack.Screen
name="DirectoryFiles"
component={CourseDirectoryScreen}
initialParams={{ courseId: courseId }}
/>
</Stack.Navigator>
);
};
102 changes: 92 additions & 10 deletions src/features/courses/screens/CourseDirectoryScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ import { useCallback, useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Platform, StyleSheet } from 'react-native';

import { faFile, faFolderOpen } from '@fortawesome/free-regular-svg-icons';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { CtaButton } from '@lib/ui/components/CtaButton';
import { EmptyState } from '@lib/ui/components/EmptyState';
import { IndentedDivider } from '@lib/ui/components/IndentedDivider';
import { RefreshControl } from '@lib/ui/components/RefreshControl';
import { Row } from '@lib/ui/components/Row';
import { Text } from '@lib/ui/components/Text';
import { TranslucentTextField } from '@lib/ui/components/TranslucentTextField';
import { useStylesheet } from '@lib/ui/hooks/useStylesheet';
import { Theme } from '@lib/ui/types/Theme';
import { CourseDirectory, CourseFileOverview } from '@polito/api-client';
Expand All @@ -14,22 +20,28 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { DateTime } from 'luxon';

import { BottomBarSpacer } from '../../../core/components/BottomBarSpacer';
import { usePreferencesContext } from '../../../core/contexts/PreferencesContext';
import { useSafeAreaSpacing } from '../../../core/hooks/useSafeAreaSpacing';
import {
useGetCourseDirectory,
useGetCourseFilesRecent,
} from '../../../core/queries/courseHooks';
import { GlobalStyles } from '../../../core/styles/GlobalStyles';
import { CourseFileOverviewWithLocation } from '../../../core/types/files';
import { TeachingStackParamList } from '../../teaching/components/TeachingNavigator';
import { CourseDirectoryListItem } from '../components/CourseDirectoryListItem';
import { CourseFileListItem } from '../components/CourseFileListItem';
import { CourseRecentFileListItem } from '../components/CourseRecentFileListItem';
import { CourseContext } from '../contexts/CourseContext';
import { CourseFilesCacheContext } from '../contexts/CourseFilesCacheContext';
import { FileStackParamList } from '../navigation/FileNavigator';
import { CourseFilesCacheProvider } from '../providers/CourseFilesCacheProvider';
import { isDirectory } from '../utils/fs-entry';

type Props = NativeStackScreenProps<TeachingStackParamList, 'CourseDirectory'>;
type Props = NativeStackScreenProps<
TeachingStackParamList & FileStackParamList,
'CourseDirectory' | 'DirectoryFiles'
>;

const FileCacheChecker = () => {
const { refresh } = useContext(CourseFilesCacheContext);
Expand All @@ -51,14 +63,14 @@ export const CourseDirectoryScreen = ({ route, navigation }: Props) => {
const [searchFilter, setSearchFilter] = useState('');
const directoryQuery = useGetCourseDirectory(courseId, directoryId);
const { paddingHorizontal } = useSafeAreaSpacing();
const { updatePreference } = usePreferencesContext();

useEffect(() => {
navigation.setOptions({
headerTitle: directoryName ?? t('common.file_plural'),
headerSearchBarOptions: {
onChangeText: e => setSearchFilter(e.nativeEvent.text),
},
});
if (navigation.getId() !== 'FileTabNavigator') {
navigation.setOptions({
headerTitle: directoryName ?? t('common.file_plural'),
});
}
}, [directoryName, navigation, t]);

directoryQuery.data?.sort((a, b) => {
Expand All @@ -79,6 +91,14 @@ export const CourseDirectoryScreen = ({ route, navigation }: Props) => {
<CourseContext.Provider value={courseId}>
<CourseFilesCacheProvider>
<FileCacheChecker />

{navigation.getId() === 'FileTabNavigator' && (
<CourseSearchBar
searchFilter={searchFilter}
setSearchFilter={setSearchFilter}
/>
)}

{searchFilter ? (
<CourseFileSearchFlatList
courseId={courseId}
Expand Down Expand Up @@ -110,19 +130,73 @@ export const CourseDirectoryScreen = ({ route, navigation }: Props) => {
ios: IndentedDivider,
})}
ListFooterComponent={<BottomBarSpacer />}
ListEmptyComponent={
!directoryQuery.isLoading ? (
<EmptyState
message={
navigation.getId() === 'FileTabNavigator'
? t('courseDirectoryScreen.emptyRootFolder')
: t('courseDirectoryScreen.emptyFolder')
}
icon={faFolderOpen}
/>
) : null
}
/>
)}
</CourseFilesCacheProvider>
{navigation.getId() === 'FileTabNavigator' && (
<CtaButton
title={t('courseDirectoryScreen.navigateRecentFiles')}
icon={faFile}
action={() => {
navigation!.navigate('RecentFiles', { courseId });
updatePreference('filesScreen', 'filesView');
}}
/>
)}
</CourseContext.Provider>
);
};

interface SearchProps {
interface SearchFlatListProps {
courseId: number;
searchFilter: string;
}

const CourseFileSearchFlatList = ({ courseId, searchFilter }: SearchProps) => {
interface SearchBarProps {
searchFilter: string;
setSearchFilter: (search: string) => void;
}

const CourseSearchBar = ({ searchFilter, setSearchFilter }: SearchBarProps) => {
const { paddingHorizontal } = useSafeAreaSpacing();
const styles = useStylesheet(createStyles);
const { t } = useTranslation();

return (
<Row align="center" style={[paddingHorizontal, styles.searchBar]}>
<TranslucentTextField
autoFocus={searchFilter.length !== 0}
autoCorrect={false}
leadingIcon={faSearch}
value={searchFilter}
onChangeText={setSearchFilter}
style={[GlobalStyles.grow, styles.textField]}
label={t('courseDirectoryScreen.search')}
editable={true}
isClearable={!!searchFilter}
onClear={() => setSearchFilter('')}
onClearLabel={t('contactsScreen.clearSearch')}
/>
</Row>
);
};

const CourseFileSearchFlatList = ({
courseId,
searchFilter,
}: SearchFlatListProps) => {
const styles = useStylesheet(createStyles);
const { t } = useTranslation();
const [searchResults, setSearchResults] = useState<
Expand Down Expand Up @@ -174,9 +248,17 @@ const CourseFileSearchFlatList = ({ courseId, searchFilter }: SearchProps) => {
);
};

const createStyles = ({ spacing }: Theme) =>
const createStyles = ({ spacing, shapes, colors }: Theme) =>
StyleSheet.create({
noResultText: {
padding: spacing[4],
},
textField: {
borderRadius: shapes.lg,
},
searchBar: {
paddingBottom: spacing[2],
paddingTop: spacing[2],
backgroundColor: colors.background,
},
});
Loading