Open Kgeek33 opened 2 weeks ago
Essaye de faire passer le linter / typecheck ma PR a été merge... Je suis en train de tester mais ça charge dans le vide expo go là
Ah nan ma PR est pas merge mb
non ta pr n'est pas passé, mais t'as des erreurs de typages dans ta pr 🤣
non ta pr n'est pas passé, mais t'as des erreurs de typages dans ta pr 🤣
Nan elle est clean ma PR
il y a 50 min, vince a fait un commit sur ta pr qui fait que t'as des erreurs de typages désormais
oh nan j'vais pas vu pourquoi
Il a corrigé des bugs mais j'ai pas vu en détail pourquoi des erreurs de typage
Perso je trouve qu'il y'a beaucoup trop d'indicateur hors connexion et c'est moche, de plus entre Messages et Notes c'est pas centré pareil.
Je pense que l'on peut enlever l'indicateur rouge et juste garder le header "Flûte"/"Catastrophe" sur chaque page, je trouve ça moins agressif...
De plus, les chats ne sont pas disponibles hors connexion
ok je corrige ça 👍, je laisse le wifi en rouge sur les devoirs ou pas ?
Ah oui, j'ai oublié de changer le texte pour le chat 😅
ok je corrige ça 👍, je laisse le wifi en rouge sur les devoirs ou pas ?
Je ne pense pas que ça soit utile! Rajoute juste le header Flûte! ça suffit imo
Sinon very cool la pr 😄
ok je fais ça haha merci 😃
@imyanice c'est bon pour moi j'ajoute sur l'emploi du temps et tout est parfait
Tout est bon pour moi, à vous de review :)
Voici mon review qualitatif, t'as un problème de rendu et les chats ne sont pas hors ligne (je crois que c'est qq chose que tu as rajouté ?)
D'acc! Super!! Je te mets une vidéo si ça t'aide pour debug le truc https://github.com/user-attachments/assets/9973687e-9446-44cb-b3f5-52ff3b2cbdfc
Seules les notes ont l'air de fonctionner
Mdrr je l'avais pas vu venir le défilement de la banderole dans les devoirs 🤣🤣
@imyanice c'est bon pour moi !
Y'a toujours des erreurs de rendu :)
Il faudrait que ça défile avec le reste, là ce n'est pas le cas
J'aurai bien aimé que ça défile mais le problème, c'est que tous les devoirs sont affichés via le composant <FlatList>
À la limite, j'pensais a ça. Afficher une bulle sur le header indiquant "Mode hors connexion" En orange, la où ça devrait être placé
Comme le Semaine 11, mais marqué comme "Hors connexion" avec une couleur rouge (ou non)
genre comme ça par exemple pour les devoirs :
Rouge | En fonction du système |
---|---|
import { NativeItem, NativeList, NativeListHeader, NativeText } from "@/components/Global/NativeComponents";
import { useCurrentAccount } from "@/stores/account";
import { useHomeworkStore } from "@/stores/homework";
import { useTheme } from "@react-navigation/native";
import React, { useRef, useState, useCallback, useEffect, useMemo } from "react";
import { toggleHomeworkState, updateHomeworkForWeekInCache } from "@/services/homework";
import {
View,
Text,
FlatList,
Dimensions,
Button,
ScrollView,
RefreshControl,
StyleSheet,
ActivityIndicator,
TextInput,
ListRenderItem
} from "react-native";
import { dateToEpochWeekNumber, epochWNToDate } from "@/utils/epochWeekNumber";
import HomeworksNoHomeworksItem from "./Atoms/NoHomeworks";
import HomeworkItem from "./Atoms/Item";
import { PressableScale } from "react-native-pressable-scale";
import { TouchableOpacity } from "react-native-gesture-handler";
import { Book, Check, CheckCircle, CheckCircle2, CheckSquare, ChevronLeft, ChevronRight, CircleDashed, CircleDotDashed, Search, WifiOff, X } from "lucide-react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { BlurView } from "expo-blur";
import Reanimated, { Easing, FadeIn, FadeInLeft, FadeInRight, FadeInUp, FadeOut, FadeOutDown, FadeOutLeft, FadeOutRight, FadeOutUp, FlipInXDown, LinearTransition, ZoomIn, ZoomOut } from "react-native-reanimated";
import { animPapillon } from "@/utils/ui/animations";
import PapillonSpinner from "@/components/Global/PapillonSpinner";
import AnimatedNumber from "@/components/Global/AnimatedNumber";
import { LinearGradient } from "expo-linear-gradient";
import * as Haptics from "expo-haptics";
import MissingItem from "@/components/Global/MissingItem";
import { PapillonModernHeader } from "@/components/Global/PapillonModernHeader";
import {Homework} from "@/services/shared/Homework";
import {Account} from "@/stores/account/types";
import {Screen} from "@/router/helpers/types";
import {NativeSyntheticEvent} from "react-native/Libraries/Types/CoreEventTypes";
import {NativeScrollEvent, ScrollViewProps} from "react-native/Libraries/Components/ScrollView/ScrollView";
import {SearchBar} from "react-native-screens";
import NetInfo from "@react-native-community/netinfo";
import { getErrorTitle } from "@/utils/format/get_papillon_error_title";
type HomeworksPageProps = {
index: number;
isActive: boolean;
loaded: boolean;
homeworks: Record<number, Homework[]>;
account: Account;
updateHomeworks: () => Promise<void>;
loading: boolean;
getDayName: (date: string | number | Date) => string;
};
const formatDate = (date: string | number | Date): string => {
return new Date(date).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long"
});
};
const WeekView: Screen<"Homeworks"> = ({ route, navigation }) => {
const flatListRef: React.MutableRefObject<FlatList> = useRef(null) as any as React.MutableRefObject<FlatList>;
const { width } = Dimensions.get("window");
const finalWidth = width - (width > 600 ? (
320 > width * 0.35 ? width * 0.35 :
320
) : 0);
const insets = useSafeAreaInsets();
const outsideNav = route.params?.outsideNav;
const theme = useTheme();
const account = useCurrentAccount(store => store.account!);
const homeworks = useHomeworkStore(store => store.homeworks);
// @ts-expect-error
let firstDate = account?.instance?.instance?.firstDate || null;
if (!firstDate) {
firstDate = new Date();
firstDate.setMonth(8);
firstDate.setDate(1);
}
const firstDateEpoch = dateToEpochWeekNumber(firstDate);
// Function to get the current week number since epoch
const getCurrentWeekNumber = () => {
const now = new Date();
now.setHours(0, 0, 0, 0);
const start = new Date(1970, 0, 0);
start.setHours(0, 0, 0, 0);
const diff = now.getTime() - start.getTime();
const oneWeek = 1000 * 60 * 60 * 24 * 7;
return Math.floor(diff / oneWeek) + 1;
};
const currentWeek = getCurrentWeekNumber();
const [data, setData] = useState(Array.from({ length: 100 }, (_, i) => currentWeek - 50 + i));
const [selectedWeek, setSelectedWeek] = useState(currentWeek);
const [direction, setDirection] = useState<"left" | "right">("right");
const [oldSelectedWeek, setOldSelectedWeek] = useState(selectedWeek);
const [hideDone, setHideDone] = useState(false);
const getItemLayout = useCallback((_: any, index: number) => ({
length: finalWidth,
offset: finalWidth * index,
index,
}), [width]);
const keyExtractor = useCallback((item: any) => item.toString(), []);
const getDayName = (date: string | number | Date): string => {
const days = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];
return days[new Date(date).getDay()];
};
const errorTitle = useMemo(() => getErrorTitle(), []);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [loadedWeeks, setLoadedWeeks] = useState<number[]>([]);
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
return NetInfo.addEventListener(state => {
setIsOnline(state.isConnected ?? false);
});
}, []);
const updateHomeworks = useCallback(async (force = false, showRefreshing = true, showLoading = true) => {
if(!account) return;
if (!force && loadedWeeks.includes(selectedWeek)) {
return;
}
if (showRefreshing) {
setRefreshing(true);
}
if (showLoading) {
setLoading(true);
}
console.log("[Homeworks]: updating cache...", selectedWeek, epochWNToDate(selectedWeek));
updateHomeworkForWeekInCache(account, epochWNToDate(selectedWeek))
.then(() => {
console.log("[Homeworks]: updated cache !", epochWNToDate(selectedWeek));
setLoading(false);
setRefreshing(false);
setLoadedWeeks(prev => [...prev, selectedWeek]);
});
}, [account, selectedWeek, loadedWeeks]);
// on page change, load the homeworks
useEffect(() => {
if (selectedWeek > oldSelectedWeek) {
setDirection("right");
} else if (selectedWeek < oldSelectedWeek) {
setDirection("left");
}
setTimeout(() => {
setOldSelectedWeek(selectedWeek);
updateHomeworks(false, false);
}, 0);
}, [selectedWeek]);
const [searchTerms, setSearchTerms] = useState("");
const renderWeek: ListRenderItem<number> = ({ item }) => {
const homeworksInWeek = homeworks[item] ?? [];
const sortedHomework = homeworksInWeek.sort((a, b) => new Date(a.due).getTime() - new Date(b.due).getTime());
const groupedHomework = sortedHomework.reduce((acc, curr) => {
const dayName = getDayName(curr.due);
const formattedDate = formatDate(curr.due);
const day = `${dayName} ${formattedDate}`;
if (!acc[day]) {
acc[day] = [curr];
} else {
acc[day].push(curr);
}
// filter homeworks by search terms
if (searchTerms.length > 0) {
acc[day] = acc[day].filter(homework => {
const content = homework.content.toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
const subject = homework.subject.toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
return content.includes(searchTerms.toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "")) ||
subject.includes(searchTerms.toLowerCase().trim().normalize("NFD").replace(/[\u0300-\u036f]/g, ""));
});
}
// if hideDone is enabled, filter out the done homeworks
if (hideDone) {
acc[day] = acc[day].filter(homework => !homework.done);
}
// remove all empty days
if (acc[day].length === 0) {
delete acc[day];
}
return acc;
}, {} as Record<string, Homework[]>);
// Moved completed homework to the bottom of the day
const sortedGroupedHomework = Object.keys(groupedHomework).reduce((acc, day) => {
acc[day] = groupedHomework[day].sort((a, b) => {
if (a.done === b.done) {
return 0; // Keep the current order if both are either completed or not completed
}
return a.done ? 1 : -1; // Unfinished at the top, finished at the bottom
});
return acc;
}, {} as Record<string, Homework[]>);
return (
<ScrollView
style={{ width: finalWidth, height: "100%" }}
contentContainerStyle={{
padding: 16,
paddingTop: outsideNav ? 72 : insets.top + 56,
}}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={() => updateHomeworks(true)}
progressViewOffset={outsideNav ? 72 : insets.top + 56}
/>
}
>
{!isOnline &&
<Reanimated.View
entering={FlipInXDown.springify().mass(1).damping(20).stiffness(300)}
exiting={FadeOutUp.springify().mass(1).damping(20).stiffness(300)}
layout={animPapillon(LinearTransition)}
style={{
backgroundColor: theme.colors.background,
}}
>
<NativeList inline>
<NativeItem icon={<WifiOff />}>
<NativeText variant="title" style={{ paddingVertical: 2, marginBottom: -4 }}>
{errorTitle.label} {errorTitle.emoji}
</NativeText>
<NativeText variant="subtitle">
Vous êtes hors ligne. Les données affichées peuvent être obsolètes.
</NativeText>
</NativeItem>
</NativeList>
</Reanimated.View>
}
{groupedHomework && Object.keys(groupedHomework).map((day, index) => (
<Reanimated.View
key={day}
entering={animPapillon(FadeInUp)}
exiting={animPapillon(FadeOutDown)}
layout={animPapillon(LinearTransition)}
>
<NativeListHeader animated label={day} />
<NativeList animated>
{groupedHomework[day].map((homework, idx) => (
<HomeworkItem
key={homework.id}
index={idx}
navigation={navigation}
total={groupedHomework[day].length}
homework={homework}
onDonePressHandler={async () => {
await toggleHomeworkState(account, homework);
await updateHomeworks(true, false, false);
}}
/>
))}
</NativeList>
</Reanimated.View>
))}
{groupedHomework && Object.keys(groupedHomework).length === 0 &&
<Reanimated.View
style={{
marginTop: 24,
width: "100%",
}}
layout={animPapillon(LinearTransition)}
key={searchTerms + hideDone}
>
{searchTerms.length > 0 ?
<MissingItem
emoji="🔍"
title="Aucun résultat"
description="Aucun devoir ne correspond à votre recherche."
/>
:
hideDone ?
<MissingItem
emoji="🌴"
title="Il ne reste rien à faire"
description="Il n'y a aucun devoir non terminé pour cette semaine."
/>
:
<MissingItem
emoji="📚"
title="Aucun devoir"
description="Il n'y a aucun devoir pour cette semaine."
/>}
</Reanimated.View>
}
</ScrollView>
);
};
const onEndReached = () => {
const lastWeek = data[data.length - 1];
const newWeeks = Array.from({ length: 50 }, (_, i) => lastWeek + i + 1);
setData(prevData => [...prevData, ...newWeeks]);
};
const onStartReached = () => {
const firstWeek = data[0];
const newWeeks = Array.from({ length: 50 }, (_, i) => firstWeek - 50 + i);
setData(prevData => [...newWeeks, ...prevData]);
flatListRef.current?.scrollToIndex({ index: 50, animated: false });
};
const onScroll: ScrollViewProps["onScroll"] = useCallback(({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
if (nativeEvent.contentOffset.x < finalWidth) {
onStartReached();
}
// Update selected week based on scroll position
const index = Math.round(nativeEvent.contentOffset.x / finalWidth);
setSelectedWeek(data[index]);
}, [finalWidth, data]);
const onMomentumScrollEnd: ScrollViewProps["onMomentumScrollEnd"] = useCallback(({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
const index = Math.round(nativeEvent.contentOffset.x / finalWidth);
setSelectedWeek(data[index]);
}, [finalWidth, data]);
const goToWeek = useCallback((weekNumber: number) => {
const index = data.findIndex(week => week === weekNumber);
if (index !== -1) {
// @ts-expect-error
const currentIndex = Math.round(flatListRef.current?.contentOffset?.x / finalWidth) || 0;
const distance = Math.abs(index - currentIndex);
const animated = distance <= 10; // Animate if the distance is 10 weeks or less
flatListRef.current?.scrollToIndex({ index, animated });
setSelectedWeek(weekNumber);
} else {
// If the week is not in the current data, update the data and scroll
const newData = Array.from({ length: 100 }, (_, i) => weekNumber - 50 + i);
setData(newData);
// Use a timeout to ensure the FlatList has updated before scrolling
setTimeout(() => {
flatListRef.current?.scrollToIndex({ index: 50, animated: false });
setSelectedWeek(weekNumber);
}, 0);
}
}, [data, finalWidth]);
const [showPickerButtons, setShowPickerButtons] = useState(false);
const [searchHasFocus, setSearchHasFocus] = useState(false);
const SearchRef: React.MutableRefObject<TextInput> = useRef(null) as any as React.MutableRefObject<TextInput>;
return (
<View>
<PapillonModernHeader outsideNav={outsideNav}>
{showPickerButtons && !searchHasFocus &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
entering={animPapillon(ZoomIn)}
exiting={animPapillon(ZoomOut)}
>
<PressableScale
onPress={() => goToWeek(selectedWeek - 1)}
activeScale={0.8}
>
<BlurView
style={[styles.weekButton, {
backgroundColor: theme.colors.primary + 16,
}]}
tint={theme.dark ? "dark" : "light"}
>
<ChevronLeft
size={24}
color={theme.colors.primary}
strokeWidth={2.5}
/>
</BlurView>
</PressableScale>
</Reanimated.View>
}
{!searchHasFocus &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
entering={animPapillon(FadeIn).delay(100)}
exiting={animPapillon(FadeOutLeft)}
>
<PressableScale
style={[styles.weekPickerContainer]}
onPress={() => setShowPickerButtons(!showPickerButtons)}
onLongPress={() => {
setHideDone(!hideDone);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
}}
delayLongPress={200}
>
<Reanimated.View
layout={animPapillon(LinearTransition)}
style={[{
backgroundColor:
showPickerButtons ? theme.colors.primary + 16 :
theme.colors.text + 16,
overflow: "hidden",
borderRadius: 80,
}]}
>
<BlurView
style={[styles.weekPicker, {
backgroundColor: "transparent",
}]}
tint={theme.dark ? "dark" : "light"}
>
{showPickerButtons && !loading &&
<Reanimated.View
entering={animPapillon(FadeIn)}
exiting={animPapillon(FadeOut)}
style={{
marginRight: 2,
}}
>
<Book
color={showPickerButtons ? theme.colors.primary : theme.colors.text}
size={18}
strokeWidth={2.6}
/>
</Reanimated.View>
}
{!showPickerButtons && hideDone &&
<Reanimated.View
entering={animPapillon(ZoomIn)}
exiting={animPapillon(FadeOut)}
style={{
marginRight: 2,
}}
>
<CircleDashed
color={showPickerButtons ? theme.colors.primary : theme.colors.text}
size={18}
strokeWidth={3}
opacity={0.7}
/>
</Reanimated.View>
}
<Reanimated.Text style={[styles.weekPickerText, styles.weekPickerTextIntl,
{
color: showPickerButtons ? theme.colors.primary : theme.colors.text,
}
]}
layout={animPapillon(LinearTransition)}
>
{width > 370 ? "Semaine" : "sem."}
</Reanimated.Text>
<Reanimated.View
layout={animPapillon(LinearTransition)}
>
<AnimatedNumber
value={((selectedWeek - firstDateEpoch % 52) % 52 + 1).toString()}
style={[styles.weekPickerText, styles.weekPickerTextNbr,
{
color: showPickerButtons ? theme.colors.primary : theme.colors.text,
}
]}
/>
</Reanimated.View>
{isOnline && loading && (
<PapillonSpinner
size={18}
color={
showPickerButtons
? theme.colors.primary
: theme.colors.text
}
strokeWidth={2.8}
entering={animPapillon(ZoomIn)}
exiting={animPapillon(ZoomOut)}
style={{
marginLeft: 5,
}}
/>
)}
</BlurView>
</Reanimated.View>
</PressableScale>
</Reanimated.View>
}
{showPickerButtons && !searchHasFocus &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
entering={animPapillon(ZoomIn).delay(100)}
exiting={animPapillon(FadeOutLeft)}
>
<PressableScale
onPress={() => goToWeek(selectedWeek + 1)}
activeScale={0.8}
>
<BlurView
style={[styles.weekButton, {
backgroundColor: theme.colors.primary + 16,
}]}
tint={theme.dark ? "dark" : "light"}
>
<ChevronRight
size={24}
color={theme.colors.primary}
strokeWidth={2.5}
/>
</BlurView>
</PressableScale>
</Reanimated.View>
}
{showPickerButtons && !searchHasFocus &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
style={{
flex: 1
}}
/>
}
{showPickerButtons && !searchHasFocus && width > 330 &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
entering={animPapillon(FadeInLeft).delay(100)}
exiting={animPapillon(FadeOutLeft)}
style={{
alignItems: "center",
justifyContent: "center",
backgroundColor: hideDone ? theme.colors.primary : theme.colors.background + "ff",
borderColor: theme.colors.border + "dd",
borderWidth: 1,
borderRadius: 800,
height: 40,
width: showPickerButtons ? 40 : null,
minWidth: showPickerButtons ? 40 : null,
maxWidth: showPickerButtons ? 40 : null,
gap: 4,
shadowColor: "#00000022",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.6,
shadowRadius: 4,
}}
>
<TouchableOpacity
onPress={() => {
setHideDone(!hideDone);
}}
>
<CheckSquare
size={20}
color={hideDone ? "#fff" : theme.colors.text}
strokeWidth={2.5}
opacity={hideDone ? 1 : 0.7}
/>
</TouchableOpacity>
</Reanimated.View>
}
<Reanimated.View
layout={
LinearTransition.duration(250).easing(Easing.bezier(0.5, 0, 0, 1).factory())
}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
flex: 1,
backgroundColor: theme.colors.background + "ff",
borderColor: theme.colors.border + "dd",
borderWidth: 1,
borderRadius: 800,
paddingHorizontal: 14,
height: 40,
width: showPickerButtons ? 40 : null,
minWidth: showPickerButtons ? 40 : null,
maxWidth: showPickerButtons ? 40 : null,
gap: 4,
shadowColor: "#00000022",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.6,
shadowRadius: 4,
}}
>
<TouchableOpacity
onPress={() => {
setShowPickerButtons(false);
setTimeout(() => {
// #TODO : change timeout method or duration
SearchRef.current?.focus();
}, 20);
}}
>
<Search
size={20}
color={theme.colors.text}
strokeWidth={2.5}
opacity={0.7}
/>
</TouchableOpacity>
{!showPickerButtons &&
<Reanimated.View
layout={animPapillon(LinearTransition)}
style={{
flex: 1,
height: "100%",
overflow: "hidden",
borderRadius: 80,
}}
entering={FadeIn.duration(250).delay(20)}
exiting={FadeOut.duration(100)}
>
<TextInput
placeholder={
(hideDone && !searchHasFocus) ? "Non terminé" :
"Rechercher"
}
value={searchTerms}
onChangeText={setSearchTerms}
placeholderTextColor={theme.colors.text + "80"}
style={{
color: theme.colors.text,
padding: 8,
borderRadius: 80,
fontFamily: "medium",
fontSize: 16.5,
flex: 1,
}}
onFocus={() => setSearchHasFocus(true)}
onBlur={() => setSearchHasFocus(false)}
ref={SearchRef}
/>
</Reanimated.View>
}
{searchTerms.length > 0 && searchHasFocus &&
<TouchableOpacity
onPress={() => {
setSearchTerms("");
}}
>
<Reanimated.View
layout={animPapillon(LinearTransition)}
entering={FadeIn.duration(100)}
exiting={FadeOut.duration(100)}
>
<X
size={20}
color={theme.colors.text}
strokeWidth={2.5}
opacity={0.7}
/>
</Reanimated.View>
</TouchableOpacity>
}
</Reanimated.View>
</PapillonModernHeader>
<FlatList
ref={flatListRef}
data={data}
renderItem={renderWeek}
keyExtractor={keyExtractor}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
initialNumToRender={3}
maxToRenderPerBatch={5}
windowSize={5}
getItemLayout={getItemLayout}
onEndReached={onEndReached}
onEndReachedThreshold={0.1}
onScroll={onScroll}
onMomentumScrollEnd={onMomentumScrollEnd}
scrollEventThrottle={16}
initialScrollIndex={50}
style={{
height: "100%",
}}
/>
</View>
);
};
const styles = StyleSheet.create({
header: {
paddingHorizontal: 16,
paddingVertical: 8,
position: "absolute",
top: 0,
left: 0,
},
weekPickerContainer: {},
weekPicker: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 20,
height: 40,
borderRadius: 80,
gap: 6,
backgroundColor: "rgba(0, 0, 0, 0.05)",
alignSelf: "flex-start",
overflow: "hidden",
},
weekPickerText: {
zIndex: 10000,
},
weekPickerTextIntl: {
fontSize: 14.5,
fontFamily: "medium",
opacity: 0.7,
},
weekPickerTextNbr: {
fontSize: 16.5,
fontFamily: "semibold",
marginTop: -1.5,
},
weekButton: {
overflow: "hidden",
borderRadius: 80,
height: 38,
width: 38,
justifyContent: "center",
alignItems: "center",
},
});
export default WeekView;
c'est ça qui permet d'afficher les devoirs
<FlatList
ref={flatListRef}
data={data}
renderItem={renderWeek}
keyExtractor={keyExtractor}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
initialNumToRender={3}
maxToRenderPerBatch={5}
windowSize={5}
getItemLayout={getItemLayout}
onEndReached={onEndReached}
onEndReachedThreshold={0.1}
onScroll={onScroll}
onMomentumScrollEnd={onMomentumScrollEnd}
scrollEventThrottle={16}
initialScrollIndex={50}
style={{
height: "100%",
}}
/>
c'est ça qui permet d'afficher les devoirs
<FlatList ref={flatListRef} data={data} renderItem={renderWeek} keyExtractor={keyExtractor} horizontal pagingEnabled showsHorizontalScrollIndicator={false} initialNumToRender={3} maxToRenderPerBatch={5} windowSize={5} getItemLayout={getItemLayout} onEndReached={onEndReached} onEndReachedThreshold={0.1} onScroll={onScroll} onMomentumScrollEnd={onMomentumScrollEnd} scrollEventThrottle={16} initialScrollIndex={50} style={{ height: "100%", }} />
Ce que je t'ai envoyé fonctionne, FlatList prend RenderWeek qui permet de render la semaine. C'est donc là que je mets mon bazar, l'edt arrive
T'en penses quoi de ça ? https://github.com/PapillonApp/Papillon/pull/346#issuecomment-2466910593 On a envoyé le message en même temps, t'as pas du le voir
Comme tu veux, mais maintenant que c'est fait et fix je vois pas l'intérêt lol https://github.com/user-attachments/assets/b2714681-202e-443b-8a0c-caacabec69fe
Papillon/src/views/account/Lessons/Atoms/Page.tsx
import { NativeItem, NativeList, NativeText } from "@/components/Global/NativeComponents";
import { useTheme } from "@react-navigation/native";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { ActivityIndicator, Image, Platform, RefreshControl as RNRefreshControl, ScrollView, Text, View } from "react-native";
import { TimetableItem } from "./Item";
import { createNativeWrapper } from "react-native-gesture-handler";
import Reanimated, {
FadeInDown,
FadeOut,
FadeOutUp,
FlipInXDown,
LinearTransition
} from "react-native-reanimated";
import NetInfo from "@react-native-community/netinfo";
import { Activity, Sofa, Utensils, WifiOff } from "lucide-react-native";
import LessonsNoCourseItem from "./NoCourse";
import { Timetable, TimetableClass } from "@/services/shared/Timetable";
import { animPapillon } from "@/utils/ui/animations";
import LessonsLoading from "./Loading";
import MissingItem from "@/components/Global/MissingItem";
import { getErrorTitle } from "@/utils/format/get_papillon_error_title";
const RefreshControl = createNativeWrapper(RNRefreshControl, {
disallowInterruption: true,
shouldCancelWhenOutside: false,
});
const lz = (num: number) => (num < 10 ? `0${num}` : num);
const getDuration = (minutes: number): string => {
const durationHours = Math.floor(minutes / 60);
const durationRemainingMinutes = minutes % 60;
return `${durationHours} h ${lz(durationRemainingMinutes)} min`;
};
interface PageProps {
current: boolean
date: Date
day: TimetableClass[]
loading: boolean
paddingTop: number
refreshAction: () => unknown
weekExists: boolean
}
export const Page = ({ day, date, current, paddingTop, refreshAction, loading, weekExists }: PageProps) => {
const errorTitle = useMemo(() => getErrorTitle(), []);
const [isOnline, setIsOnline] = useState(true);
const theme = useTheme();
useEffect(() => {
return NetInfo.addEventListener(state => {
setIsOnline(state.isConnected ?? false);
});
}, []);
return (
<ScrollView
style={{
flex: 1,
width: "100%",
height: "100%",
}}
showsVerticalScrollIndicator={false}
contentContainerStyle={{
paddingTop: paddingTop
}}
refreshControl={
<RefreshControl
refreshing={loading}
onRefresh={refreshAction}
progressViewOffset={paddingTop}
/>
}
>
{current &&
<View
style={{
paddingHorizontal: 10,
paddingVertical: 10,
gap: 10,
width: "100%"
}}
>
{!isOnline &&
<Reanimated.View
entering={FlipInXDown.springify().mass(1).damping(20).stiffness(300)}
exiting={FadeOutUp.springify().mass(1).damping(20).stiffness(300)}
layout={animPapillon(LinearTransition)}
style={{
backgroundColor: theme.colors.background,
}}
>
<NativeList inline>
<NativeItem icon={<WifiOff />}>
<NativeText variant="title" style={{ paddingVertical: 2, marginBottom: -4 }}>
{errorTitle.label} {errorTitle.emoji}
</NativeText>
<NativeText variant="subtitle">
Vous êtes hors ligne. Les données affichées peuvent être obsolètes.
</NativeText>
</NativeItem>
</NativeList>
</Reanimated.View>
}
{day && day.length > 0 && day[0].type !== "vacation" && day.map((item, i) => (
<View key={item.startTimestamp + i.toString()} style={{ gap: 10 }}>
<TimetableItem key={item.startTimestamp} item={item} index={i} />
{day[i + 1] &&
day[i + 1].startTimestamp - item.endTimestamp > 1740000 && (
<SeparatorCourse
i={i}
start={item.endTimestamp}
end={day[i + 1].startTimestamp}
/>
)}
</View>
))}
</View>
}
{loading && day.length == 0 && (
<Reanimated.View
style={{
padding: 26,
}}
entering={animPapillon(FadeInDown)}
exiting={animPapillon(FadeOutUp).delay(100)}
>
<LessonsLoading />
</Reanimated.View>
)}
{day && day.length === 0 && current && !loading && (
weekExists && (new Date(date).getDay() == 6 || new Date(date).getDay() == 0) ? (
<MissingItem
emoji="🌴"
title="C'est le week-end !"
description="Profitez de votre week-end, il n'y a pas de cours aujourd'hui."
entering={animPapillon(FadeInDown)}
exiting={animPapillon(FadeOut)}
/>
) : (
<MissingItem
emoji="📆"
title="Pas de cours aujourd'hui"
description="Aucun cours n'est prévu pour aujourd'hui."
entering={animPapillon(FadeInDown)}
exiting={animPapillon(FadeOut)}
/>
)
)}
{day.length === 1 && current && !loading && (day[0].type === "vacation" ? <MissingItem
emoji="🏝️"
title="C'est les vacances !"
description="Profitez de vos vacances, à bientôt."
entering={animPapillon(FadeInDown)}
exiting={animPapillon(FadeOut)}
/>: <></>
)}
</ScrollView>
);
};
const SeparatorCourse: React.FC<{
i: number
start: number
end: number
}> = ({ i, start, end }) => {
const { colors } = useTheme();
const startHours = new Date(start).getHours();
return (
<Reanimated.View
style={{
borderRadius: 10,
backgroundColor: colors.card,
borderColor: colors.text + "33",
borderWidth: 0.5,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 1,
elevation: 1,
marginLeft: 70,
}}
entering={
Platform.OS === "ios" ?
FadeInDown.delay(50 * i)
.springify()
.mass(1)
.damping(20)
.stiffness(300)
: void 0
}
exiting={Platform.OS === "ios" ? FadeOut.duration(300) : void 0}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 10,
borderRadius: 10,
gap: 10,
overflow: "hidden",
backgroundColor: colors.text + "11",
}}
>
<Image
source={require("../../../../../assets/images/mask_course.png")}
resizeMode='cover'
tintColor={colors.text}
style={{
position: "absolute",
top: "-15%",
left: "-20%",
width: "200%",
height: "300%",
opacity: 0.05,
}}
/>
{startHours > 11 &&
startHours < 14 ? (
<Utensils size={20} color={colors.text} />
) : (
<Sofa size={20} color={colors.text} />
)}
<Text
numberOfLines={1}
style={{
flex: 1,
fontFamily: "semibold",
fontSize: 16,
color: colors.text,
}}
>
{startHours > 11 &&
startHours < 14
? "Pause méridienne"
: "Pas de cours"}
</Text>
<Text
numberOfLines={1}
style={{
fontFamily: "medium",
fontSize: 15,
opacity: 0.5,
color: colors.text,
}}
>
{getDuration(
Math.round((end - start) / 60000)
)}
</Text>
</View>
</Reanimated.View>
);
};
super merci, j'intègre ça dans ma pr :)
T'as pas enlevé le code ici, sinon à part ça lgtm!
y'a pas mal de pr assez sympa en terme de fonctionnalités mais les review sont pauvres, c'est dommage parce que ça traine et on peut pas avancer
Je corrige ça tout à l'heure @imyanice @LeGeek01 je suis d'accord, c'est pour ça qu'on travaille avec @Louis-htmlcss pour un workflow avec qr code, comme ça (et j'espère surtout), on devrait gagner énormément de temps
c bon @imyanice
y'a pas mal de pr assez sympa en terme de fonctionnalités mais les review sont pauvres, c'est dommage parce que ça traine et on peut pas avancer
Ils sont vraiment incompétents les gens de Papillon dis donc
Sur certains points, oui, très clairement, notamment le manque de communication, point que j'ai souligné et rabaché et qui m'a coûté ma place au sein de l'équipe, et il n'y a pas d'amélioration.
Donc oui, à un moment, ça devient chiant.
est ce que l'interface ressemble au screen qui ont été envoyé précédemment ?
Sur certains points, oui, très clairement, notamment le manque de communication, point que j'ai souligné et rabaché et qui m'a coûté ma place au sein de l'équipe, et il n'y a pas d'amélioration.
Donc oui, à un moment, ça devient chiant.
Ca marche
@tryon-dev je mettrai des captures tout à l'heure, j'ai oublié d'en mettre comme elle était pas encore prête
@tryon-dev je mettrai des captures tout à l'heure, j'ai oublié d'en mettre comme elle était pas encore prête
Encore un avantage a notre pr qui permet une preview expo
@tryon-dev j'ai importé des vidéos et des captures dans la description de la pr :) Déso si les vidéos sont pas directement intégrés dans le tableau
Participation de @imyanice dans cette PR
🚀 Nouvelle Pull Request
Proposez vos modifications pour améliorer Papillon
Informations importantes
Merci de vous référer à la documentation sur la contribution si vous avez des questions à propos des pull requests (https://gitbook.getpapillon.xyz/organisation/outils-internes/github)
Checklist d'avant pull request
Veuillez cocher toutes les cases applicables en remplaçant [ ] par [x].
TODO
(aka des annotations pour du code manquant) dans vos modificationsChangelogs proposés
L'application est disponible à 100% hors connexion ! En fonction de la page, soit est affiché une icône
WifiOff
, soitReanimated.View
avec un titre au hasard et un texte disant que l'utilisateur est en mode hors connexionIssues en lien
Informations supplémentaires
Captures d'écran/Vidéos