diff --git a/src/components/Home/AccountSwitcher.tsx b/src/components/Home/AccountSwitcher.tsx index ab3ba3a9c..16a803367 100644 --- a/src/components/Home/AccountSwitcher.tsx +++ b/src/components/Home/AccountSwitcher.tsx @@ -4,8 +4,8 @@ import { useTheme } from "@react-navigation/native"; import { ChevronDown } from "lucide-react-native"; import Reanimated, { - interpolateColor, - LinearTransition, useAnimatedStyle, + LinearTransition, + SharedValue, ZoomIn, ZoomOut, } from "react-native-reanimated"; @@ -14,8 +14,8 @@ import { useCurrentAccount } from "@/stores/account"; import { defaultProfilePicture } from "@/utils/ui/default-profile-picture"; import PapillonSpinner from "../Global/PapillonSpinner"; import { animPapillon } from "@/utils/ui/animations"; -import Animated from "react-native-reanimated"; import { BlurView } from "expo-blur"; +import Animated from "react-native-reanimated"; const ReanimatedBlurView = Reanimated.createAnimatedComponent(BlurView); @@ -23,7 +23,7 @@ const AccountSwitcher: React.FC<{ small?: boolean, opened?: boolean, modalOpen?: boolean, - translationY?: Reanimated.SharedValue, + translationY?: SharedValue, loading?: boolean, }> = ({ small, opened, modalOpen, translationY, loading }) => { const theme = useTheme(); @@ -33,51 +33,12 @@ const AccountSwitcher: React.FC<{ const shouldHideName = account.personalization.hideNameOnHomeScreen || false; const shouldHidePicture = account.personalization.hideProfilePicOnHomeScreen || false; - - const borderAnimatedStyle = useAnimatedStyle(() => ({ - borderWidth: 1, - borderRadius: 80, - borderColor: interpolateColor( - translationY?.value || 0, // Should think to pass a default value - [200, 251], - ["#ffffff50", colors.border], - ), - backgroundColor: interpolateColor( - translationY?.value || 0, // Should think to pass a default value - [200, 251], - ["#ffffff30", "transparent"], - ), - })); - - const textAnimatedStyle = useAnimatedStyle(() => ({ - color: interpolateColor( - translationY?.value || 0, // Should think to pass a default value - [200, 251], - ["#FFF", colors.text], - ), - fontSize: 16, - fontFamily: "semibold", - maxWidth: 140, - })); - - const AnimatedChevronDown = Animated.createAnimatedComponent(ChevronDown); - const iconAnimatedStyle = useAnimatedStyle(() => ({ - color: interpolateColor( - translationY?.value || 0, // Should think to pass a default value - [200, 251], - ["#FFF", colors.text], - ), - marginLeft: -6, - })); return ( ); diff --git a/src/router/screens/account/index.tsx b/src/router/screens/account/index.tsx index 608a3acf3..9ea2f3ed0 100644 --- a/src/router/screens/account/index.tsx +++ b/src/router/screens/account/index.tsx @@ -7,7 +7,6 @@ import NewsScreen from "@/views/account/News/News"; import Grades from "@/views/account/Grades/Grades"; import Attendance from "@/views/account/Attendance/Attendance"; import Messages from "@/views/account/Chat/Messages"; -import PlaceholderScreen from "@/views/account/Home/PlaceholderScreen"; export const screens = [ createScreen("Home", () => , { diff --git a/src/views/account/Home/Elements/AttendanceElement.tsx b/src/views/account/Home/Elements/AttendanceElement.tsx index 559d1cdad..a327acec8 100644 --- a/src/views/account/Home/Elements/AttendanceElement.tsx +++ b/src/views/account/Home/Elements/AttendanceElement.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useEffect } from "react"; -import { NativeListHeader } from "@/components/Global/NativeComponents"; +import { NativeItem, NativeList, NativeListHeader } from "@/components/Global/NativeComponents"; import { updateGradesPeriodsInCache } from "@/services/grades"; import { useCurrentAccount } from "@/stores/account"; import { useAttendanceStore } from "@/stores/attendance"; @@ -10,6 +10,8 @@ import RedirectButton from "@/components/Home/RedirectButton"; import { PapillonNavigation } from "@/router/refs"; import { log } from "@/utils/logger/logger"; import type { Attendance } from "@/services/shared/Attendance"; +import MissingItem from "@/components/Global/MissingItem"; +import { FadeInDown, FadeOut } from "react-native-reanimated"; interface AttendanceElementProps { @@ -89,7 +91,28 @@ const AttendanceElement: React.FC = ({ onImportance }) = }; if (!totalMissed || totalMissed.absences.length === 0) { - return null; + return ( + + + + + + ); } return ( diff --git a/src/views/account/Home/Elements/GradesElement.tsx b/src/views/account/Home/Elements/GradesElement.tsx index 22daa9b37..3d0a2a916 100644 --- a/src/views/account/Home/Elements/GradesElement.tsx +++ b/src/views/account/Home/Elements/GradesElement.tsx @@ -1,4 +1,4 @@ -import { NativeList, NativeListHeader } from "@/components/Global/NativeComponents"; +import { NativeItem, NativeList, NativeListHeader } from "@/components/Global/NativeComponents"; import { PapillonNavigation } from "@/router/refs"; import { updateGradesAndAveragesInCache, updateGradesPeriodsInCache } from "@/services/grades"; import { useCurrentAccount } from "@/stores/account"; @@ -7,6 +7,8 @@ import React, { useEffect, useState } from "react"; import GradeItem from "../../Grades/Subject/GradeItem"; import type { Grade } from "@/services/shared/Grade"; import RedirectButton from "@/components/Home/RedirectButton"; +import MissingItem from "@/components/Global/MissingItem"; +import { FadeInDown, FadeOut } from "react-native-reanimated"; interface GradesElementProps { onImportance: (value: number) => unknown @@ -69,7 +71,27 @@ const GradesElement: React.FC = ({ onImportance }) => { }, [grades]); if (!grades || lastThreeGrades.length === 0) { - return null; + return ( + + + + + + ); } return ( @@ -90,7 +112,7 @@ const GradesElement: React.FC = ({ onImportance }) => { navigation={PapillonNavigation.current} index={index} totalItems={lastThreeGrades.length} - allGrades={[]} + allGrades={grades[defaultPeriod] || []} /> ))} diff --git a/src/views/account/Home/Elements/HomeworksElement.tsx b/src/views/account/Home/Elements/HomeworksElement.tsx index 71ef9ddf2..57f132b1c 100644 --- a/src/views/account/Home/Elements/HomeworksElement.tsx +++ b/src/views/account/Home/Elements/HomeworksElement.tsx @@ -1,4 +1,4 @@ -import { NativeList, NativeListHeader } from "@/components/Global/NativeComponents"; +import { NativeItem, NativeList, NativeListHeader } from "@/components/Global/NativeComponents"; import { useCurrentAccount } from "@/stores/account"; import React, { useCallback, useEffect, useMemo } from "react"; import { useHomeworkStore } from "@/stores/homework"; @@ -11,6 +11,8 @@ import RedirectButton from "@/components/Home/RedirectButton"; import { dateToEpochWeekNumber } from "@/utils/epochWeekNumber"; import {NativeStackNavigationProp} from "@react-navigation/native-stack"; import {RouteParameters} from "@/router/helpers/types"; +import { FadeInDown, FadeOut } from "react-native-reanimated"; +import MissingItem from "@/components/Global/MissingItem"; interface HomeworksElementProps { onImportance: (value: number) => unknown @@ -58,23 +60,37 @@ const HomeworksElement: React.FC = ({ navigation, onImpor [account, updateHomeworks] ); - if ( - !homeworks[dateToEpochWeekNumber(actualDay)]?.filter( - (hw) => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime - ) && - !homeworks[dateToEpochWeekNumber(actualDay) + 1]?.filter( - (hw) => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime - ) - ) { - return null; - } - const startTime = Date.now() / 1000; // Convertir en millisecondes - const endTime = startTime + 7 * 24 * 60 * 60 * 1000; // Ajouter 7 jours en millisecondes + const startTime = Date.now() / 1000; + const endTime = startTime + 7 * 24 * 60 * 60 * 1000; + + const hwSemaineActuelle = homeworks[dateToEpochWeekNumber(actualDay)]?.filter( + (hw) => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime + ); + const hwSemaineProchaine = homeworks[dateToEpochWeekNumber(actualDay) + 1]?.filter( + (hw) => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime + ); - const hwFinalList = homeworks[dateToEpochWeekNumber(actualDay)]?.filter(hw => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime); - if(hwFinalList.length === 0) { - return null; + if ( + (!hwSemaineActuelle && !hwSemaineProchaine) || + (hwSemaineActuelle.length === 0 && hwSemaineProchaine.length === 0) + ) { + return ( + + + + + + ); } return ( @@ -85,7 +101,7 @@ const HomeworksElement: React.FC = ({ navigation, onImpor )} /> - {hwFinalList.map((hw, index) => ( + {hwSemaineActuelle.map((hw, index) => ( = ({ navigation, onImpor }} /> ))} - {new Date().getDay() >= 2 && homeworks[dateToEpochWeekNumber(actualDay) + 1]?.filter(hw => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime).map((hw, index) => ( + {new Date().getDay() >= 2 && hwSemaineProchaine.map((hw, index) => ( = ({ onImportance }) => > diff --git a/src/views/account/Home/Home.tsx b/src/views/account/Home/Home.tsx index 8b4cfbc98..0ab4e50a3 100644 --- a/src/views/account/Home/Home.tsx +++ b/src/views/account/Home/Home.tsx @@ -36,15 +36,13 @@ import type {Screen} from "@/router/helpers/types"; import {useCurrentAccount} from "@/stores/account"; import getCorners from "@/utils/ui/corner-radius"; import {useIsFocused, useTheme} from "@react-navigation/native"; -import React, {useCallback, useMemo, useState} from "react"; +import React, { useMemo, useState } from "react"; import { Dimensions, - Platform, - RefreshControl, StatusBar, View } from "react-native"; -import Reanimated from "react-native-reanimated"; +import Reanimated, { interpolateColor } from "react-native-reanimated"; import Animated, { Extrapolation, interpolate, @@ -57,7 +55,6 @@ import AccountSwitcher from "@/components/Home/AccountSwitcher"; import ContextMenu from "@/components/Home/AccountSwitcherContextMenu"; import Header from "@/components/Home/Header"; import {useBottomTabBarHeight} from "@react-navigation/bottom-tabs"; -import * as Haptics from "expo-haptics"; import ModalContent from "@/views/account/Home/ModalContent"; import {AnimatedScrollView} from "react-native-reanimated/lib/typescript/reanimated2/component/ScrollView"; @@ -72,102 +69,143 @@ const Home: Screen<"HomeScreen"> = ({ navigation }) => { let account = useCurrentAccount(store => store.account!); - const [shouldOpenContextMenu, setShouldOpenContextMenu] = useState(false); const [modalOpen, setModalOpen] = useState(false); - const [modalFull, setModalFull] = useState(false); + const [scrool, setScrool] = useState(false); - const [canHaptics, setCanHaptics] = useState(true); const [refreshing, setRefreshing] = useState(false); - const openAccSwitcher = useCallback(() => { - setShouldOpenContextMenu(false); - setTimeout(() => { - setShouldOpenContextMenu(true); - }, 150); - }, []); - const windowHeight = Dimensions.get("window").height; const tabbarHeight = useBottomTabBarHeight(); - const widgetAnimatedStyle = useAnimatedStyle(() => ({ - paddingTop: insets.top, - opacity: interpolate( + const backgroundAnimatedStyle = useAnimatedStyle(() => { + const isModalFullyOpen = scrollOffset.value >= 265; + const backgroundColor = !modalOpen + ? interpolateColor(scrollOffset.value, [0, 195], [colors.primary, colors.primary]) + : interpolateColor(scrollOffset.value, [0, 265], [colors.primary, colors.card]); + + return { + backgroundColor: isModalFullyOpen ? colors.card : backgroundColor, + }; + }); + + const widgetAnimatedStyle = useAnimatedStyle(() => { + if (modalOpen) { + return { + transform: [{ scale: 1 }], + opacity: 1, + }; + } + + const translateY = interpolate( scrollOffset.value, - [0, 265 + insets.top], - [1, 0], + [0, 265], + [0, 0], Extrapolation.CLAMP - ), - transform: [ - { translateY: scrollOffset.value }, - { scale: interpolate( - scrollOffset.value, - [0, 265], - [1, 0.9], - Extrapolation.CLAMP - )}, - ] - })); - - const modalAnimatedStyle = useAnimatedStyle(() => ({ - borderCurve: "continuous", - borderTopLeftRadius: interpolate( + ); + const scale = interpolate( scrollOffset.value, - [0, 100, 265 + insets.top - 1, 265 + insets.top], - [12, 12, corners, 0], + [0, 265], + [1, 0.95], Extrapolation.CLAMP - ), - borderTopRightRadius: interpolate( + ); + const opacity = interpolate( scrollOffset.value, - [0, 100, 265 + insets.top - 1, 265 + insets.top], - [12, 12, corners, 0], + [0, 200, 265], + [1, 1, 0], Extrapolation.CLAMP - ), + ); - shadowColor: "#000", - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.2, - shadowRadius: 10, + return { + paddingTop: insets.top, + opacity, + transform: [{ translateY }, { scale }], + }; + }); - flex: 1, - minHeight: windowHeight - tabbarHeight - 8, - backgroundColor: colors.card, - overflow: "hidden", - transform: [ - {scale: interpolate( + const modalAnimatedStyle = useAnimatedStyle(() => { + const isScrolling = scrollOffset.value > 0; + + const borderRadius = isScrolling + ? 0 + : interpolate( scrollOffset.value, - [0, 200, (260 + insets.top) - 40, 260 + insets.top], - [1, 0.95, 0.95, 1], + [0, 100, 265 + insets.top - 1, 265 + insets.top], + [12, 12, corners, 0], Extrapolation.CLAMP - )}, - {translateY: interpolate( + ); + + const scale = isScrolling + ? 1 + : interpolate( scrollOffset.value, - [-1000, 0, 125, 265 ], - [-1000, 0, 105, 0], + [0, 200, 260 + insets.top - 40, 260 + insets.top], + [1, 0.95, 0.95, 1], Extrapolation.CLAMP - )} - ], - })); + ); - const navigationBarAnimatedStyle = useAnimatedStyle(() => ({ - position: "absolute", - top: scrollOffset.value - 270 - insets.top, - left: 0, - right: 0, - height: interpolate( + const translateY = interpolate( scrollOffset.value, - [125, 265], - [0, insets.top + 60], + [-1000, 0, 125, 265], + [-1000, 0, 105, 0], Extrapolation.CLAMP - ), - zIndex: 100, - backgroundColor: colors.background, - borderColor: colors.border, - borderBottomWidth: 0.5, - })); + ); + + return { + flex: 1, + minHeight: windowHeight - tabbarHeight - 8, + backgroundColor: colors.card, + overflow: "hidden", + borderTopLeftRadius: borderRadius, + borderTopRightRadius: borderRadius, + transform: [{ scale }, { translateY }], + shadowColor: "#000", + shadowOpacity: 0.2, + shadowRadius: 10, + }; + }); + + const modalIndicatorAnimatedStyle = useAnimatedStyle(() => { + const isScrolling = scrollOffset.value > 0; + + return { + position: "absolute", + top: 10, + left: "50%", + transform: [ + { + translateX: isScrolling + ? -2 + : interpolate( + scrollOffset.value, + [125, 200], + [-25, -2], + Extrapolation.CLAMP + ), + }, + ], + width: isScrolling + ? 4 + : interpolate( + scrollOffset.value, + [125, 200], + [50, 4], + Extrapolation.CLAMP + ), + height: 4, + backgroundColor: colors.text + "20", + zIndex: 100, + borderRadius: 5, + opacity: isScrolling + ? 0 + : interpolate( + scrollOffset.value, + [125, 180, 200], + [1, 0.5, 0], + Extrapolation.CLAMP + ), + }; + }); const modalContentAnimatedStyle = useAnimatedStyle(() => ({ paddingHorizontal: 16, @@ -184,117 +222,70 @@ const Home: Screen<"HomeScreen"> = ({ navigation }) => { ] })); - const modalIndicatorAnimatedStyle = useAnimatedStyle(() => ({ - position: "absolute", - top: 10, - left: "50%", - transform: [ - {translateX: interpolate( - scrollOffset.value, - [125, 200], - [-25, -2], - Extrapolation.CLAMP - )} - ], - width: interpolate( - scrollOffset.value, - [125, 200], - [50, 4], - Extrapolation.CLAMP - ), - height: 4, - backgroundColor: colors.text + "20", - zIndex: 100, - borderRadius: 5, - opacity: interpolate( - scrollOffset.value, - [125, 180, 200], - [1, 0.5, 0], - Extrapolation.CLAMP - ), - })); - - const scrollViewAnimatedStyle = useAnimatedStyle(() => ({ - flex: 1, - backgroundColor: scrollOffset.value > 265 + insets.top ? colors.card : colors.primary, - })); - return ( - - {!modalOpen && focused && ( - - )} - - - - { - if (e.nativeEvent.contentOffset.y < 265 + insets.top && modalOpen) { - scrollRef.current?.scrollTo({ y: 0, animated: true }); - } - }} - onScroll={(e) => { - if (e.nativeEvent.contentOffset.y > 125 && canHaptics) { - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - setCanHaptics(false); - } else if (e.nativeEvent.contentOffset.y < 125 && !canHaptics) { - setCanHaptics(true); - } - - setModalOpen(e.nativeEvent.contentOffset.y >= 195 + insets.top); - setModalFull(e.nativeEvent.contentOffset.y >= 265 + insets.top); - }} - refreshControl={ setRefreshing(true)} - style={{zIndex: 100}} - progressViewOffset={285 + insets.top} - />} - showsVerticalScrollIndicator={false} - > - + + {!modalOpen && focused && ( + + )} + -
- + + { + if (modalOpen && e.nativeEvent.contentOffset.y < 275 + insets.top) { + scrollRef.current?.scrollTo({ y: 0, animated: true }); + setModalOpen(false); + } else if (!modalOpen && e.nativeEvent.contentOffset.y > 50) { + scrollRef.current?.scrollTo({ y: 275 + insets.top, animated: true }); + setModalOpen(true); + } + setScrool(true); + }} + > + +
+ - - - - setRefreshing(false)} - /> + + + + setRefreshing(false)} + /> + - - - + + + ); }; diff --git a/src/views/account/Home/PlaceholderScreen.tsx b/src/views/account/Home/PlaceholderScreen.tsx deleted file mode 100644 index 334bd5af2..000000000 --- a/src/views/account/Home/PlaceholderScreen.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useTheme } from "@react-navigation/native"; -import React, { useEffect, useLayoutEffect } from "react"; -import { View, Text, StatusBar, TouchableOpacity, Platform } from "react-native"; - -import { defaultTabs } from "@/consts/DefaultTabs"; -import LottieView from "lottie-react-native"; -import { X } from "lucide-react-native"; -import MissingItem from "@/components/Global/MissingItem"; -import TabAnimatedTitle from "@/components/Global/TabAnimatedTitle"; -import {Screen} from "@/router/helpers/types"; - -const PlaceholderScreen: Screen<"Messages" | "Menu"> = ({ route, navigation }) => { - const theme = useTheme(); - - useLayoutEffect(() => { - navigation.setOptions({ - ...TabAnimatedTitle({ route, navigation }), - }); - }, [navigation, route.params, theme.colors.text]); - - const [isFocused, setIsFocused] = React.useState(false); - - useEffect(() => { - const unsubscribe = navigation.addListener("focus", () => { - setIsFocused(true); - }); - - return unsubscribe; - }, [navigation]); - - return ( - - - - ); -}; - -export default PlaceholderScreen;