From f6b20e4c8d0b8eac5b1a38a4b730080eaf87db59 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Thu, 6 Mar 2025 15:14:55 +0100 Subject: [PATCH] feat(agenda): added support for restoring visibility of hidden events --- assets/translations/en.json | 4 + assets/translations/it.json | 4 + src/core/components/Checkbox.tsx | 55 ++-- src/features/agenda/screens/LectureScreen.tsx | 4 +- .../navigation/CourseSharedScreens.tsx | 1 - .../courses/screens/CourseHideEventScreen.tsx | 274 ++++++++++++++---- .../screens/CoursePreferencesScreen.tsx | 5 +- src/features/courses/types/Recurrence.ts | 4 + 8 files changed, 265 insertions(+), 86 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 7316d960..a5962e64 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -318,6 +318,10 @@ "courseGuideScreen": { "title": "Course guide" }, + "courseHideEventScreen": { + "button": "Show in Agenda", + "selectItems": "Select all" + }, "courseIconPickerScreen": { "title": "Pick an icon" }, diff --git a/assets/translations/it.json b/assets/translations/it.json index 7e506699..f58eb23c 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -329,6 +329,10 @@ "courseGuideScreen": { "title": "Guida del corso" }, + "courseHideEventScreen": { + "button": "Mostra in Agenda", + "selectItems": "Seleziona tutti" + }, "courseIconPickerScreen": { "title": "Seleziona un'icona" }, diff --git a/src/core/components/Checkbox.tsx b/src/core/components/Checkbox.tsx index 61d5ae06..03795820 100644 --- a/src/core/components/Checkbox.tsx +++ b/src/core/components/Checkbox.tsx @@ -1,6 +1,4 @@ -import { useRef } from 'react'; import { - Animated, StyleProp, StyleSheet, TextStyle, @@ -9,12 +7,14 @@ import { ViewStyle, } from 'react-native'; -import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; +import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'; import { Icon } from '@lib/ui/components/Icon'; import { Text } from '@lib/ui/components/Text'; import { useStylesheet } from '@lib/ui/hooks/useStylesheet'; import { Theme } from '@lib/ui/types/Theme'; +// faSquare, faSquareCheck export const Checkbox = ({ text, onPress, @@ -23,53 +23,55 @@ export const Checkbox = ({ textStyle, checkboxStyle, disable, + dimension = 'default', + icon, }: { - text: string; + text?: string; onPress: () => void; isChecked: boolean; containerStyle?: StyleProp; textStyle?: StyleProp; checkboxStyle?: StyleProp; disable?: boolean; + dimension?: 'default' | 'small'; + icon?: IconDefinition; }) => { const styles = useStylesheet(createStyles); - const animatedWidth = useRef(new Animated.Value(0)).current; - - const startAnimation = () => { - const toValue = isChecked ? 30 : 0; - Animated.timing(animatedWidth, { - toValue: toValue, - duration: 500, - useNativeDriver: false, - }).start(); - }; - return ( { - startAnimation(); onPress(); }} > - - - + {isChecked ? ( + + ) : ( + + )} + {text} @@ -85,16 +87,13 @@ const createStyles = ({ palettes, spacing, fontSizes, dark }: Theme) => marginHorizontal: spacing[5], }, checkbox: { - borderColor: palettes.navy[dark ? '50' : '600'], - borderWidth: 1, - borderRadius: 5, height: 25, width: 25, justifyContent: 'center', alignItems: 'center', }, - checkboxSelected: { - backgroundColor: palettes.navy[dark ? '50' : '600'], + checkboxIcon: { + color: palettes.navy[dark ? '50' : '600'], }, text: { fontSize: fontSizes.sm, diff --git a/src/features/agenda/screens/LectureScreen.tsx b/src/features/agenda/screens/LectureScreen.tsx index fb2f4a1f..182e041b 100644 --- a/src/features/agenda/screens/LectureScreen.tsx +++ b/src/features/agenda/screens/LectureScreen.tsx @@ -13,7 +13,7 @@ import { Row } from '@lib/ui/components/Row'; import { useTheme } from '@lib/ui/hooks/useTheme'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; -import { DateTime } from 'luxon'; +import { DateTime, WeekdayNumbers } from 'luxon'; import { BottomBarSpacer } from '../../../core/components/BottomBarSpacer'; import { EventDetails } from '../../../core/components/EventDetails'; @@ -73,7 +73,7 @@ export const LectureScreen = ({ route, navigation }: Props) => { t('lectureScreen.hideEventAlertMessage', { title: lecture.title, day: DateTime.now() - .set({ weekday: lecture.start.weekday }) + .set({ weekday: lecture.start.weekday as WeekdayNumbers }) .toFormat('cccc'), fromTime: lecture.fromTime, toTime: lecture.toTime, diff --git a/src/features/courses/navigation/CourseSharedScreens.tsx b/src/features/courses/navigation/CourseSharedScreens.tsx index 52b0b235..ca3c3e70 100644 --- a/src/features/courses/navigation/CourseSharedScreens.tsx +++ b/src/features/courses/navigation/CourseSharedScreens.tsx @@ -126,7 +126,6 @@ export const CourseSharedScreens = ( options={{ title: t('common.hiddenEvents'), headerLargeTitle: false, - headerSearchBarOptions: {}, }} /> ; +interface HideEventProps { + key: number; + item: CourseHiddenRecurrence; + updateItemVisibility: ( + element: CourseHiddenRecurrence, + newVisibility: boolean, + ) => void; +} + +const enum CheckboxState { + SELECTED, + NOT_ALL_SELECTED, + UNSELECTED, +} + +const HideEventCard = ({ item, updateItemVisibility }: HideEventProps) => { + const styles = useStylesheet(createStyles); + const { data: place, isLoading: placeLoading } = useGetPlace(item.room); + + const handleVisibilityChange = () => { + updateItemVisibility(item, !item.restoreVisibility); + }; + + const getLongDayTime = (day: number) => { + const dayTime = DateTime.now().set({ weekday: day as WeekdayNumbers }); + const dayName = dayTime.toFormat('cccc'); + return dayName.charAt(0).toUpperCase() + dayName.slice(1); + }; + + return ( + + handleVisibilityChange()} + isChecked={item.restoreVisibility} + containerStyle={styles.checkbox} + /> + + + + {getLongDayTime(item.day) + ' ' + item.start + '-' + item.end} + + + {placeLoading && } + {item.room && place && ( + + {place.room.name} + + )} + + + + ); +}; + export const CourseHideEventScreen = ({ navigation, route }: Props) => { - const { fontSizes } = useTheme(); + const styles = useStylesheet(createStyles); const { courses: coursesPrefs, updatePreference } = usePreferencesContext(); const coursePrefs = useMemo(() => { return coursesPrefs[route.params.uniqueShortcode]; }, [route.params.uniqueShortcode, coursesPrefs]); + const [selectAll, setSelectAll] = useState( + CheckboxState.UNSELECTED, + ); + const [items, setItems] = useState(() => { + return ( + coursesPrefs[route.params.uniqueShortcode].itemsToHideInAgenda?.map( + item => { + return { ...item, restoreVisibility: false }; + }, + ) ?? [] + ); + }); + + const selectAllItem = () => { + setSelectAll(old => { + const newState = + old < 2 ? CheckboxState.UNSELECTED : CheckboxState.SELECTED; + setItems(prevItems => + prevItems.map(item => ({ + ...item, + restoreVisibility: newState === CheckboxState.SELECTED, + })), + ); + return newState; + }); + }; + + const updateItemVisibility = ( + element: CourseHiddenRecurrence, + newVisibility: boolean, + ) => { + setItems(prevItems => + prevItems.map(item => + item === element ? { ...item, restoreVisibility: newVisibility } : item, + ), + ); + }; + + const onPress = () => { + const itemsToDelete = items.filter(item => item.restoreVisibility); // Raccogli gli eventi che devono essere eliminati + + if (itemsToDelete.length > 0) { + updatePreference('courses', { + ...coursesPrefs, + [route.params.uniqueShortcode]: { + ...coursePrefs, + itemsToHideInAgenda: coursePrefs.itemsToHideInAgenda?.filter( + recurrence => + !itemsToDelete.some( + item => + item.start === recurrence.start && + item.end === recurrence.end && + item.day === recurrence.day && + item.room === recurrence.room, + ), + ), + }, + }); + } + + setItems(prevItems => + prevItems.filter( + item => !itemsToDelete.some(deletedItem => deletedItem === item), + ), + ); + }; useEffect(() => { if (!coursePrefs.itemsToHideInAgenda?.length) { @@ -30,50 +156,90 @@ export const CourseHideEventScreen = ({ navigation, route }: Props) => { } }, [coursePrefs.itemsToHideInAgenda, navigation]); + useEffect(() => { + setSelectAll( + items.filter(item => item.restoreVisibility).length === items.length + ? CheckboxState.SELECTED + : items.filter(item => item.restoreVisibility).length > 0 + ? CheckboxState.NOT_ALL_SELECTED + : CheckboxState.UNSELECTED, + ); + }, [items]); + + useEffect(() => { + setItems( + coursePrefs.itemsToHideInAgenda?.map(item => ({ + ...item, + restoreVisibility: false, + })) ?? [], + ); + }, [coursePrefs.itemsToHideInAgenda]); + return ( - - - - {coursesPrefs[route.params.uniqueShortcode].itemsToHideInAgenda?.map( - (item, index) => ( - + + + selectAllItem()} + isChecked={ + CheckboxState.SELECTED === selectAll || + CheckboxState.NOT_ALL_SELECTED === selectAll + } + containerStyle={styles.smallCheckbox} + dimension="small" + text={t('courseHideEventScreen.selectItems')} + textStyle={styles.textSmallCheckbox} + icon={ + CheckboxState.NOT_ALL_SELECTED === selectAll + ? faSquareMinus + : undefined + } + /> + + {items.map((item, index) => ( + } - trailingItem={ - { - updatePreference('courses', { - ...coursesPrefs, - [route.params.uniqueShortcode]: { - ...coursePrefs, - itemsToHideInAgenda: ( - coursePrefs.itemsToHideInAgenda || [] - ).filter( - recurrence => - !( - recurrence.start === item.start && - recurrence.end === item.end && - recurrence.day === item.day && - recurrence.room === item.room - ), - ), - }, - }); - }} - /> - } + item={item} + updateItemVisibility={updateItemVisibility} /> - ), - )} - - - + ))} + + + + + + ); }; + +const createStyles = ({ spacing, fontSizes, palettes, dark }: Theme) => + StyleSheet.create({ + card: { + flex: 1, + marginHorizontal: spacing[2.5], + marginVertical: spacing[2], + padding: spacing[3], + overflow: 'visible', + alignItems: 'center', + }, + cardCol: { + flexShrink: 1, + paddingRight: spacing[5], + }, + title: { + fontSize: fontSizes.md, + flexShrink: 1, + }, + label: { + marginTop: spacing[1.5], + }, + smallCheckbox: { + marginHorizontal: spacing[2.5], + marginTop: spacing[4], + marginBottom: spacing[1.5], + }, + textSmallCheckbox: { + color: palettes.navy[dark ? '50' : '800'], + }, + checkbox: { marginHorizontal: 0 }, + }); diff --git a/src/features/courses/screens/CoursePreferencesScreen.tsx b/src/features/courses/screens/CoursePreferencesScreen.tsx index f9f1d253..00b88b51 100644 --- a/src/features/courses/screens/CoursePreferencesScreen.tsx +++ b/src/features/courses/screens/CoursePreferencesScreen.tsx @@ -11,6 +11,7 @@ import { import { faBell, faBroom, + faCalendarDay, faChevronRight, faFile, faVideoCamera, @@ -289,7 +290,9 @@ export const CoursePreferencesScreen = ({ navigation, route }: Props) => { trailingItem={ } - leadingItem={} + leadingItem={ + + } /> diff --git a/src/features/courses/types/Recurrence.ts b/src/features/courses/types/Recurrence.ts index 4f5bed49..e7cd9f35 100644 --- a/src/features/courses/types/Recurrence.ts +++ b/src/features/courses/types/Recurrence.ts @@ -4,3 +4,7 @@ export type HiddenRecurrence = { end: string; room: string; }; + +export type CourseHiddenRecurrence = HiddenRecurrence & { + restoreVisibility: boolean; +};