diff --git a/eslint.config.js b/eslint.config.js index 2649e2820..9c3af5f88 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,14 @@ const unusedImports = require("eslint-plugin-unused-imports"); const typescript = require("@typescript-eslint/parser"); module.exports = [ + { // Ignored directory + ignores: [ + "**/node_modules/", + "**/android/", + "**/ios/", + "**/.*" + ] + }, { // Apply to `cjs`, `.mjs` and `.js` files. files: ["**/*.?([cm])js?(x)"] }, diff --git a/package-lock.json b/package-lock.json index b8cc97927..ef8f26b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.9.26", "@react-navigation/stack": "^6.4.0", - "alise-api": "^0.1.2", + "alise-api": "^0.1.3", "axios": "^1.7.7", "buffer": "^6.0.3", "cal-parser": "^1.0.2", @@ -6475,9 +6475,9 @@ } }, "node_modules/alise-api": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/alise-api/-/alise-api-0.1.2.tgz", - "integrity": "sha512-Uv2fDbeIOYIA8UkmH/UDANvsN3de2J8dPL9ciJf+4/xuu1QHZam1fLXp67GOuyY3OUAiSWDKVAwptXoUfFN9Gw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/alise-api/-/alise-api-0.1.3.tgz", + "integrity": "sha512-kAl4MwE7sj2ieyz+coUppBEoq/QvugVwbbohVQtEDuGWeXXPBqLDWUmAOV5JXrUR7N1KAEjZ28VhTXJJa3VnnA==", "license": "GPL-3.0", "engines": { "node": ">=18" diff --git a/package.json b/package.json index 2fac54713..aa9c657e8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.9.26", "@react-navigation/stack": "^6.4.0", - "alise-api": "^0.1.2", + "alise-api": "^0.1.3", "axios": "^1.7.7", "buffer": "^6.0.3", "cal-parser": "^1.0.2", diff --git a/src/components/Global/AnimatedNumber.tsx b/src/components/Global/AnimatedNumber.tsx index f6aff6614..5780058ea 100644 --- a/src/components/Global/AnimatedNumber.tsx +++ b/src/components/Global/AnimatedNumber.tsx @@ -50,7 +50,7 @@ const AnimatedNumber: React.FC = ({ }, contentContainerStyle]} layout={animPapillon(LinearTransition)} > - {value.toString().split("").map((n, i) => ( + {value.toString().split("").map((n:number, i:number) => ( unknown }[]; +export type PickerDataItem = { index?: number, label: string, icon?: JSX.Element, onPress?: () => unknown }; +export type PickerData = string[] | PickerDataItem[]; interface PapillonPickerProps { children: React.ReactNode data: PickerData - selected?: string + selected?: string | PickerDataItem contentContainerStyle?: StyleProp>> delay?: number, direction?: "left" | "right", @@ -204,4 +205,4 @@ const styles = StyleSheet.create({ }, }); -export default PapillonPicker; +export default PapillonPicker; \ No newline at end of file diff --git a/src/components/Grades/AnimatedEmoji.tsx b/src/components/Grades/AnimatedEmoji.tsx index 7e12350d4..5e3d9d1a4 100644 --- a/src/components/Grades/AnimatedEmoji.tsx +++ b/src/components/Grades/AnimatedEmoji.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { View, Text } from "react-native"; +import { View } from "react-native"; import Animated, { useAnimatedStyle, useSharedValue, diff --git a/src/components/Grades/GradeModal.tsx b/src/components/Grades/GradeModal.tsx index a022b3822..63767a726 100644 --- a/src/components/Grades/GradeModal.tsx +++ b/src/components/Grades/GradeModal.tsx @@ -5,13 +5,11 @@ import { Image, TouchableOpacity, Text, - Platform, Alert } from "react-native"; -import { Download, Trash, Maximize2, Share, Delete } from "lucide-react-native"; +import { Download, Trash, Share } from "lucide-react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { BlurView } from "expo-blur"; -import { ScrollView } from "react-native-gesture-handler"; import * as Sharing from "expo-sharing"; import * as FileSystem from "expo-file-system"; import * as MediaLibrary from "expo-media-library"; @@ -46,7 +44,7 @@ const GradeModal: React.FC = ({ await FileSystem.writeAsStringAsync(fileUri, imageBase64, { encoding: FileSystem.EncodingType.Base64 }); const asset = await MediaLibrary.createAssetAsync(fileUri); await MediaLibrary.createAlbumAsync("Download", asset, false); - Alert.alert("Image sauvegardée", "L'image a été sauvegardée dans votre galerie."); + Alert.alert("Image sauvegardée", "L'image a été sauvegardée dans ta galerie."); } catch (error) { console.error("Failed to save image:", error); } diff --git a/src/components/Settings/ApparenceContainerCard.tsx b/src/components/Settings/ApparenceContainerCard.tsx index 0eeedf514..3dfa0d3c0 100644 --- a/src/components/Settings/ApparenceContainerCard.tsx +++ b/src/components/Settings/ApparenceContainerCard.tsx @@ -23,7 +23,7 @@ const ApparenceContainerCard = () => { Mode d'affichage - Par défaut, Papillon s'adapte à votre thème système. Mais vous pouvez choisir un thème clair ou sombre. + Par défaut, Papillon s'adapte à ton thème système. Mais tu peux choisir un thème clair ou sombre. diff --git a/src/consts/DefaultTabs.ts b/src/consts/DefaultTabs.ts index 0f140931e..b8939a914 100644 --- a/src/consts/DefaultTabs.ts +++ b/src/consts/DefaultTabs.ts @@ -60,7 +60,7 @@ export const defaultTabs = [ { tab: "Evaluation", label: "Compétences", - description: "Vos compétences et évaluations", + description: "Tes compétences et évaluations", icon: require("@/../assets/lottie/tab_evaluations.json"), enabled: true, } diff --git a/src/router/helpers/types.ts b/src/router/helpers/types.ts index f72bb2400..6057d5217 100644 --- a/src/router/helpers/types.ts +++ b/src/router/helpers/types.ts @@ -137,7 +137,7 @@ export type RouteParameters = { Menu?: undefined; RestaurantQrCode: { - QrCodes: string[]; + QrCodes: Array; }; RestaurantHistory: { histories: ReservationHistory[]; diff --git a/src/router/navigator/atoms/MenuItem.tsx b/src/router/navigator/atoms/MenuItem.tsx index 2a9b548ce..ff64a6de5 100644 --- a/src/router/navigator/atoms/MenuItem.tsx +++ b/src/router/navigator/atoms/MenuItem.tsx @@ -1,13 +1,11 @@ import * as React from "react"; -import { useCurrentAccount } from "@/stores/account"; -import { useNavigationBuilder, useTheme } from "@react-navigation/native"; -import { StyleSheet, View, Text, Platform } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useTheme } from "@react-navigation/native"; +import { StyleSheet, Platform } from "react-native"; import LottieView from "lottie-react-native"; import colorsList from "@/utils/data/colors.json"; import { Pressable } from "react-native-gesture-handler"; import * as Haptics from "expo-haptics"; -import Reanimated, { FadeIn, FadeOut, LinearTransition, ZoomIn } from "react-native-reanimated"; +import Reanimated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; import { anim2Papillon } from "@/utils/ui/animations"; const MenuItem: React.FC<{ diff --git a/src/router/navigator/atoms/TabItem.tsx b/src/router/navigator/atoms/TabItem.tsx index 90da917e3..cc8fe2536 100644 --- a/src/router/navigator/atoms/TabItem.tsx +++ b/src/router/navigator/atoms/TabItem.tsx @@ -1,8 +1,6 @@ import * as React from "react"; -import { useCurrentAccount } from "@/stores/account"; -import { useNavigationBuilder, useTheme } from "@react-navigation/native"; -import { StyleSheet, View, Text, Platform } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useTheme } from "@react-navigation/native"; +import { StyleSheet, Platform } from "react-native"; import LottieView from "lottie-react-native"; import colorsList from "@/utils/data/colors.json"; import { Pressable } from "react-native-gesture-handler"; diff --git a/src/router/navigator/menu.tsx b/src/router/navigator/menu.tsx index d12793b8b..12082b86c 100644 --- a/src/router/navigator/menu.tsx +++ b/src/router/navigator/menu.tsx @@ -1,17 +1,13 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import { useCurrentAccount } from "@/stores/account"; import { useNavigationBuilder, useTheme } from "@react-navigation/native"; -import { StyleSheet, View, ScrollView, Platform, Image, Text, StatusBar } from "react-native"; +import { StyleSheet, Platform, Image, Text, StatusBar } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import TabItem from "./atoms/TabItem"; -import Reanimated, { LinearTransition, useSharedValue } from "react-native-reanimated"; +import Reanimated from "react-native-reanimated"; import MenuItem from "./atoms/MenuItem"; -import AccountSwitcher from "@/components/Home/AccountSwitcher"; import ContextMenu from "@/components/Home/AccountSwitcherContextMenu"; -import { NativeText } from "@/components/Global/NativeComponents"; import { defaultProfilePicture } from "@/utils/ui/default-profile-picture"; -import { he } from "date-fns/locale"; const PapillonNavigatorMenu: React.FC, "NavigationContent">> = ({ state, descriptors, navigation }) => { const theme = useTheme(); diff --git a/src/router/navigator/navigator.tsx b/src/router/navigator/navigator.tsx index 027e57dec..888137f44 100644 --- a/src/router/navigator/navigator.tsx +++ b/src/router/navigator/navigator.tsx @@ -1,8 +1,7 @@ import { BottomTabView } from "@react-navigation/bottom-tabs"; import { createNavigatorFactory, TabRouter, useNavigationBuilder } from "@react-navigation/native"; import PapillonNavigatorTabs from "./tabs"; -import { memo, useEffect, useMemo, useState } from "react"; -import { Dimensions, View } from "react-native"; +import { View } from "react-native"; import PapillonNavigatorMenu from "./menu"; import useScreenDimensions from "@/hooks/useScreenDimensions"; diff --git a/src/router/navigator/tabs.tsx b/src/router/navigator/tabs.tsx index a82d23b88..257f2c0a3 100644 --- a/src/router/navigator/tabs.tsx +++ b/src/router/navigator/tabs.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useMemo, useState } from "react"; import { useCurrentAccount } from "@/stores/account"; import { useNavigationBuilder, useTheme } from "@react-navigation/native"; -import { StyleSheet, View, Platform } from "react-native"; +import { StyleSheet, Platform } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import TabItem from "./atoms/TabItem"; -import Reanimated, { LinearTransition } from "react-native-reanimated"; +import Reanimated from "react-native-reanimated"; const PapillonNavigatorTabs: React.FC, "NavigationContent">> = ({ state, descriptors, navigation }) => { const theme = useTheme(); diff --git a/src/services/alise/qrcode.ts b/src/services/alise/qrcode.ts new file mode 100644 index 000000000..80a0ea3ac --- /dev/null +++ b/src/services/alise/qrcode.ts @@ -0,0 +1,6 @@ +import type { AliseAccount } from "@/stores/account/types"; + +export const getQRCode = async (account: AliseAccount): Promise => { + const barcode = await account.authentication.session.getBarcode(); + return barcode ?? null; +}; diff --git a/src/services/ecoledirecte/grades.ts b/src/services/ecoledirecte/grades.ts index a1b8dfd6a..9fc4833cd 100644 --- a/src/services/ecoledirecte/grades.ts +++ b/src/services/ecoledirecte/grades.ts @@ -26,19 +26,19 @@ const decodeGradeValue = ( value: ecoledirecte.GradeValue | undefined, ): GradeValue => { if (typeof value === "undefined") - return { value: null, disabled: true }; + return { value: null, disabled: true, status: null }; switch (value.kind) { case GradeKind.Grade: - return { value: value.points ?? 0, disabled: false }; + return { value: value.points ?? 0, disabled: false, status: null }; case GradeKind.Absent: - return { value: value.points ?? 0, disabled: true, information: GradeInformation.Absent }; + return { value: value.points ?? 0, disabled: true, status: "Abs" }; case GradeKind.Exempted: - return { value: value.points ?? 0, disabled: true, information: GradeInformation.Exempted }; + return { value: value.points ?? 0, disabled: true, status: "Disp" }; case GradeKind.NotGraded: - return { value: value.points ?? 0, disabled: true, information: GradeInformation.NotGraded }; + return { value: value.points ?? 0, disabled: true, status: "N. Not" }; default: - return { value: value.points ?? 0, disabled: true }; + return { value: value.points ?? 0, disabled: true, status: null }; } }; @@ -46,7 +46,7 @@ const getGradeValue = (value: number | string | undefined): GradeValue => { return { disabled: false, value: value ? Number(value) : 0, - information: undefined, + status: null, }; }; diff --git a/src/services/grades.ts b/src/services/grades.ts index 771ef39fb..52add827b 100644 --- a/src/services/grades.ts +++ b/src/services/grades.ts @@ -66,8 +66,8 @@ export async function updateGradesAndAveragesInCache (accoun let grades: Grade[] = []; let averages: AverageOverview = { subjects: [], - overall: { value: null, disabled: true }, - classOverall: { value: null, disabled: true } + overall: { value: null, disabled: true, status: null }, + classOverall: { value: null, disabled: true, status: null} }; try { @@ -100,8 +100,8 @@ export async function updateGradesAndAveragesInCache (accoun grades = []; averages = { subjects: [], - overall: { value: 0, disabled: true }, - classOverall: { value: 0, disabled: true } + overall: { value: 0, disabled: true, status: null }, + classOverall: { value: 0, disabled: true, status: null } }; } diff --git a/src/services/iutlan/grades.ts b/src/services/iutlan/grades.ts index fe7fc3b7e..3ab4c0225 100644 --- a/src/services/iutlan/grades.ts +++ b/src/services/iutlan/grades.ts @@ -34,10 +34,12 @@ export const saveIUTLanGrades = async (account: LocalAccount): Promise<{ classOverall: { value: null, disabled: true, + status: null, }, overall: { value: null, disabled: true, + status: null, }, subjects: [] }; @@ -63,24 +65,29 @@ export const saveIUTLanGrades = async (account: LocalAccount): Promise<{ student: { value: parseFloat(note.note.value), disabled: parsedStudent === null || isNaN(parsedStudent), + status: null, }, min: { value: parseFloat(note.note.min), disabled: parsedMin === null || isNaN(parsedMin), + status: null, }, max: { value: parseFloat(note.note.max), disabled: parsedMax === null || isNaN(parsedMax), + status: null, }, average: { value: parseFloat(note.note.moy), disabled: parsedAverage === null || isNaN(parsedAverage), + status: null, }, id: uuid(), outOf: { value: 20, disabled: false, + status: null, }, description: note.description, timestamp: new Date(note.date).getTime(), @@ -112,24 +119,29 @@ export const saveIUTLanGrades = async (account: LocalAccount): Promise<{ classAverage: { value: classAverage, disabled: false, + status: null, }, color: "#888888", max: { value: max, disabled: false, + status: null, }, subjectName: subject.name, min: { value: min, disabled: false, + status: null, }, average: { value: average, disabled: false, + status: null, }, outOf: { value: 20, disabled: false, + status: null, }, }); }); @@ -144,10 +156,12 @@ export const saveIUTLanGrades = async (account: LocalAccount): Promise<{ classOverall: { value: null, disabled: true, + status: null, }, overall: { value: null, disabled: true, + status: null, }, subjects: [] } diff --git a/src/services/pronote/evaluations.ts b/src/services/pronote/evaluations.ts index c03bc69e9..be1868208 100644 --- a/src/services/pronote/evaluations.ts +++ b/src/services/pronote/evaluations.ts @@ -12,7 +12,7 @@ const getTab = (account: PronoteAccount): pronote.Tab => { const tab = account.instance.user.resources[0].tabs.get(pronote.TabLocation.Evaluations); if (!tab) - throw new Error("Vous n'avez pas accès à l'onglet 'Compétences' dans PRONOTE"); + throw new Error("Tu n'as pas accès à l'onglet 'Compétences' dans PRONOTE"); return tab; }; diff --git a/src/services/pronote/grades.ts b/src/services/pronote/grades.ts index 3c7a38e9a..03df84275 100644 --- a/src/services/pronote/grades.ts +++ b/src/services/pronote/grades.ts @@ -30,27 +30,27 @@ export const getGradesPeriods = (account: PronoteAccount): { periods: Period[], const decodeGradeValue = (value: pronote.GradeValue | undefined): GradeValue => { if (typeof value === "undefined") - return { value: null, disabled: true }; + return { value: null, disabled: true, status: "unknown" }; switch (value.kind) { case pronote.GradeKind.Grade: - return { value: value.points, disabled: false }; + return { value: value.points ?? 0, disabled: false, status: null }; case pronote.GradeKind.Absent: - return { value: null, disabled: true, information: GradeInformation.Absent }; + return { value: null, disabled: true, status: "Abs" }; case pronote.GradeKind.Exempted: - return { value: null, disabled: true, information: GradeInformation.Exempted }; + return { value: null, disabled: true, status: "Disp" }; case pronote.GradeKind.NotGraded: - return { value: null, disabled: true, information: GradeInformation.NotGraded }; + return { value: null, disabled: true, status: "N. Not" }; case pronote.GradeKind.Unfit: - return { value: null, disabled: true, information: GradeInformation.Unfit }; + return { value: null, disabled: true, status: null }; case pronote.GradeKind.Unreturned: - return { value: null, disabled: true, information: GradeInformation.Unreturned }; + return { value: null, disabled: true, status: null }; case pronote.GradeKind.AbsentZero: - return { value: 0, disabled: false, information: GradeInformation.Absent }; + return { value: 0, disabled: false, status: "Abs" }; case pronote.GradeKind.UnreturnedZero: - return { value: 0, disabled: false, information: GradeInformation.Unreturned }; + return { value: 0, disabled: false, status: null }; default: - return { value: null, disabled: true }; + return { value: null, disabled: true, status: null }; } }; diff --git a/src/services/qrcode.ts b/src/services/qrcode.ts index 6019d998e..a73c368af 100644 --- a/src/services/qrcode.ts +++ b/src/services/qrcode.ts @@ -1,6 +1,6 @@ import { AccountService, type ExternalAccount } from "@/stores/account/types"; -export const qrcodeFromExternal = async (account: ExternalAccount): Promise => { +export const qrcodeFromExternal = async (account: ExternalAccount): Promise => { switch (account.service) { case AccountService.Turboself: { const { getQRCode } = await import("./turboself/qrcode"); @@ -14,6 +14,10 @@ export const qrcodeFromExternal = async (account: ExternalAccount): Promise typeof value === "number" ? - { value, disabled: false } - : { value: null, disabled: true }; + { value, disabled: false, status: null} + : { value: null, disabled: true, status: null }; const getSubjectMinMax = (evalSubj: Evaluation): {min: GradeValue, max:GradeValue, outOf: GradeValue} => { const outOf = decodeGradeNumber(evalSubj.scale || SKOLENGO_DEFAULT_SCALE); - if(evalSubj.evaluations.filter(e=>e.evaluationResult.mark !== null && !e.evaluationResult.nonEvaluationReason).length === 0) return {min: { value: null, disabled: true } , max: { value: null, disabled: true }, outOf}; + if(evalSubj.evaluations.filter(e=>e.evaluationResult.mark !== null && !e.evaluationResult.nonEvaluationReason).length === 0) return {min: { value: null, disabled: true, status: null } , max: { value: null, disabled: true, status: null }, outOf}; const [minimum, maximum] = evalSubj.evaluations.filter(e=>e.evaluationResult.mark !== null) .map(e=>((e.evaluationResult.mark!)/(e.scale || SKOLENGO_DEFAULT_SCALE)) * (evalSubj.scale || SKOLENGO_DEFAULT_SCALE)) .reduce(([minAcc, maxAcc], e) => [Math.min(minAcc, e), Math.max(maxAcc, e)], [evalSubj.scale || SKOLENGO_DEFAULT_SCALE, 0]); - return {min: { value: minimum, disabled: false } , max: { value: maximum, disabled: false }, outOf}; + return {min: { value: minimum, disabled: false, status: null } , max: { value: maximum, disabled: false, status: null }, outOf}; }; const getOverall = (evals: Evaluation[]): GradeValue =>{ if(evals.filter(e=>e.average !== null).length === 0) - return { value: null, disabled: true }; + return { value: null, disabled: true, status: null }; const sum = evals.filter(e=>e.average !== null).reduce((acc, e) => acc + (e.average! * (e.coefficient || 1)), 0); const sumCoef = evals.filter(e=>e.average !== null).reduce((acc, e) => acc + (e.coefficient || 1), 0); - return { value: sum / sumCoef, disabled: false }; + return { value: sum / sumCoef, disabled: false, status: null }; }; export const getGradesAndAverages = async (account: SkolengoAccount, periodName: string): Promise<{ @@ -43,8 +43,8 @@ export const getGradesAndAverages = async (account: SkolengoAccount, periodName: const evals = await account.instance.getEvaluation(undefined, period.id); const averages: AverageOverview = { - classOverall: { value: 0, disabled: true }, - overall: { value: 0, disabled: true }, + classOverall: { value: 0, disabled: true, status: null }, + overall: { value: 0, disabled: true, status: null }, subjects: evals.map((s) => ({ classAverage: decodeGradeNumber(s.average), color: s.subject.color || "#888", @@ -74,4 +74,4 @@ export const getGradesAndAverages = async (account: SkolengoAccount, periodName: })); return { averages, grades }; -}; \ No newline at end of file +}; diff --git a/src/utils/data/lesson_formats.json b/src/utils/data/lesson_formats.json index a18e0f323..ac298cc31 100644 --- a/src/utils/data/lesson_formats.json +++ b/src/utils/data/lesson_formats.json @@ -103,7 +103,6 @@ "pretty": "Éducation civique", "formats": { "default": [ - "education civique", "education civique" ] } @@ -125,7 +124,6 @@ "default": [ "education physique et sportive", "ed physique sport", - "education physique et sportive", "eps" ] } @@ -136,7 +134,8 @@ "formats": { "default": [ "education musicale", - "education musicale" + "educ musicale", + "musique" ] } }, @@ -158,7 +157,6 @@ "histoire geographie", "histoire geo", "histoire geograph", - "histoire-geographie", "histoire-geographie" ] } @@ -198,10 +196,11 @@ "pretty": "Mathématiques", "formats": { "default": [ - "mathematiques", "mathematiques", "mathematiques 1ere", - "math 1ere" + "math 1ere", + "math", + "maths" ] } }, @@ -221,7 +220,10 @@ "formats": { "default": [ "physique chimie", - "phys chim" + "physique chim", + "phys chim", + "phys chimie", + "physique-chimie" ] } }, @@ -231,8 +233,7 @@ "formats": { "default": [ "sciences economiques et sociales", - "sc econo sociales", - "sciences economiques et sociales" + "sc econo sociales" ] } }, @@ -242,8 +243,8 @@ "formats": { "default": [ "sciences de la vie et de la terre", - "sciences de la vie et de la terre", - "sciences vie terre" + "sciences vie terre", + "svt" ] } }, @@ -376,5 +377,26 @@ "eco et droit du numerique" ] } + }, + { + "label": "special", + "pretty": "Cours exceptionnel obligatoire", + "formats": { + "default": [ + "action intervention sortie examen" + ] + } + }, + { + "label": "art", + "pretty": "Arts Plastiques", + "formats": { + "default": [ + "arts plastiques", + "arts", + "arts plast", + "arts pla" + ] + } } ] \ No newline at end of file diff --git a/src/utils/format/DateHelper.ts b/src/utils/format/DateHelper.ts index 8886b750f..615e76cef 100644 --- a/src/utils/format/DateHelper.ts +++ b/src/utils/format/DateHelper.ts @@ -1,17 +1,109 @@ -export const timestampToString = (timestamp: number) => { +// First check if Intl.RelativeTimeFormat is available +const isRelativeTimeFormatSupported = + typeof Intl !== "undefined" && + Intl.RelativeTimeFormat && + typeof Intl.RelativeTimeFormat === "function"; + +// Create formatters with fallback +const createFormatter = (options: { + numeric: "always" | "auto"; + style: "long" | "short" | "narrow"; +}) => { + if (isRelativeTimeFormatSupported) { + try { + return new Intl.RelativeTimeFormat("fr", options); + } catch (e) { + console.error("Error creating RelativeTimeFormat:", e); + // Fallback formatting function + return { + format: (value: number, unit: Intl.RelativeTimeFormatUnit) => { + const abs = Math.abs(value); + if (unit === "days") { + return value === 0 + ? "aujourd'hui" + : value === 1 + ? "demain" + : value === -1 + ? "hier" + : `dans ${abs} jours`; + } + if (unit === "months") { + return `dans ${abs} mois`; + } + if (unit === "years") { + return `dans ${abs} ans`; + } + return `dans ${abs} ${unit}`; + }, + }; + } + } + // Fallback if RelativeTimeFormat is not supported + return { + format: (value: number, unit: Intl.RelativeTimeFormatUnit) => { + const abs = Math.abs(value); + return `dans ${abs} ${unit}`; + }, + }; +}; + +const numericDateFormatter = createFormatter({ + numeric: "always", + style: "long", +}); + +const dateFormatter = createFormatter({ + numeric: "auto", + style: "long", +}); + +export const timestampToString = (timestamp: number): string => { + if (!timestamp) { + return "Date invalide"; + } + const date = new Date(timestamp); const today = new Date(); + // Reset hours to avoid time-of-day complications today.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0); - const difference = Math.ceil((date.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); + // Calculate differences + const dateDifference = [ + date.getFullYear() - today.getFullYear(), + date.getMonth() - today.getMonth(), + date.getDate() - today.getDate(), + ]; + + const yearDifference = Math.trunc( + dateDifference[0] + dateDifference[1] / 12 + dateDifference[2] / 365 + ); + + const monthDifference = Math.trunc( + dateDifference[0] * 12 + dateDifference[1] + dateDifference[2] / 30.4 + ); + + const dayDifference = Math.round( + (date.getTime() - today.getTime()) / (1000 * 60 * 60 * 24) + ); + + let formattedDate: string; + + try { + if (yearDifference === 0) { + if (monthDifference === 0) { + formattedDate = dateFormatter.format(dayDifference, "days"); + } else { + formattedDate = numericDateFormatter.format(monthDifference, "months"); + } + } else { + formattedDate = numericDateFormatter.format(yearDifference, "years"); + } - return difference === 0 - ? "Aujourd'hui" - : difference === 1 - ? "Demain" - : difference === 2 - ? "Après-demain" - : `Dans ${difference} jours`; + return formattedDate.charAt(0).toUpperCase() + formattedDate.slice(1); + } catch (error) { + console.error("Error formatting date:", error); + return `dans ${Math.abs(dayDifference)} jours`; + } }; diff --git a/src/views/account/Chat/Messages.tsx b/src/views/account/Chat/Messages.tsx index 0f9fc0465..af76414a8 100644 --- a/src/views/account/Chat/Messages.tsx +++ b/src/views/account/Chat/Messages.tsx @@ -153,14 +153,14 @@ const Discussions: Screen<"Discussions"> = ({ navigation, route }) => { opacity: 0.5, }} > - Vos conversations arrivent... + Tes conversations arrivent... ) : chats.length === 0 ? ( = ({ navigation, route }) => { = ({ navigation }) => { Sujet = ({ navigation }) => { fontFamily: "semibold", color: theme.colors.text, }} - placeholder="Entrez votre texte" + placeholder="Entre ton texte" placeholderTextColor={theme.colors.text + "80"} value={content} multiline={true} @@ -176,8 +176,8 @@ const ChatCreate: Screen<"ChatCreate"> = ({ navigation }) => { console.log("onPress"); if (!subject) { Alert.alert( - "Voulez-vous continuer sans objet ?", - "Vous êtes sur le point de créer une discussion sans objet. Voulez-vous continuer ?", + "Veux-tu continuer sans objet ?", + "Tu es sur le point de créer une discussion sans objet. Veux-tu continuer ?", [ { text: "Annuler", diff --git a/src/views/account/Grades/Document.tsx b/src/views/account/Grades/Document.tsx index 2e6481f20..ba56ada31 100644 --- a/src/views/account/Grades/Document.tsx +++ b/src/views/account/Grades/Document.tsx @@ -7,18 +7,14 @@ import { import { getSubjectData } from "@/services/shared/Subject"; import { useTheme } from "@react-navigation/native"; import React, { useCallback, useEffect, useLayoutEffect, useState } from "react"; -import { Image, ScrollView, Text, View, Platform, TouchableOpacity, Modal } from "react-native"; +import { Image, ScrollView, Text, View, Platform, TouchableOpacity } from "react-native"; import * as StoreReview from "expo-store-review"; import { Asterisk, Calculator, - Download, - Expand, Maximize2, Scale, School, - SmilePlus, - Trash, UserMinus, UserPlus, Users, @@ -29,18 +25,10 @@ import { Screen } from "@/router/helpers/types"; import InsetsBottomView from "@/components/Global/InsetsBottomView"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { useGradesStore } from "@/stores/grades"; -import { LinearGradient } from "expo-linear-gradient"; -import AnimatedEmoji from "@/components/Grades/AnimatedEmoji"; -import GradeModal from "@/components/Grades/GradeModal"; - const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { const { grade, allGrades = [] } = route.params; const theme = useTheme(); - const insets = useSafeAreaInsets(); - const [modalOpen, setModalOpen] = useState(false); - const [isReactionBeingTaken, setIsReactionBeingTaken] = useState(false); const [subjectData, setSubjectData] = useState({ color: "#888888", @@ -49,8 +37,6 @@ const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { }); const [shouldShowReviewOnClose, setShouldShowReviewOnClose] = useState(false); - const currentReel = useGradesStore((state) => state.reels[grade.id]); - const reels = useGradesStore((state) => state.reels); const askForReview = async () => { StoreReview.isAvailableAsync().then((available) => { @@ -60,6 +46,7 @@ const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { }); }; + // on modal closed useEffect(() => { navigation.addListener("beforeRemove", () => { if (shouldShowReviewOnClose) { @@ -149,7 +136,7 @@ const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { : "??", bareme: "/20", }, - ], + ].filter(Boolean), }, { title: "Ma classe", @@ -179,7 +166,7 @@ const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { : "??", bareme: "/" + grade.outOf.value, }, - ], + ].filter(Boolean), }, { title: "Influence", @@ -222,107 +209,52 @@ const GradeDocument: Screen<"GradeDocument"> = ({ route, navigation }) => { classDiff.difference.toFixed(2).replace("-", "") + " pts", }, - ], + ].filter(Boolean), }, - ]; - - const deleteReel = (reelId: string) => { - useGradesStore.setState((store) => { - const updatedReels = { ...store.reels }; - delete updatedReels[reelId]; - return { reels: updatedReels }; - }); - setModalOpen(false); - }; - - const handleFocus = useCallback(() => { - // Si on revient de la page de réaction et qu'on a un reel - if (currentReel && isReactionBeingTaken) { - setModalOpen(true); - setIsReactionBeingTaken(false); - } - }, [currentReel]); - - useEffect(() => { - const unsubscribe = navigation.addListener("focus", handleFocus); - return unsubscribe; - }, [navigation, handleFocus]); + ].filter(list => list.items.length > 0); return ( - - - setModalOpen(false)} - DeleteGrade={() => deleteReel(grade.id)} - /> - - + + - {currentReel ? ( - <> - - - - ) : null} - - - {Platform.OS === "ios" && ( + + {Platform.OS === "ios" && = ({ route, navigation }) => { marginVertical: 8, }} /> - )} - {!reels[grade.id] ? ( - { - setIsReactionBeingTaken(true); - navigation.navigate("GradeReaction", { grade }); - }} - > - - - RÉAGIR - - - ) : ( - setModalOpen(true)} - > - - - )} + } + + + + {subjectData.pretty} + + + {grade.description || "Note sans description"} + + + {new Date(grade.timestamp).toLocaleDateString("fr-FR", { + weekday: "long", + month: "long", + day: "numeric", + })} + + - {subjectData.pretty} - - - {grade.description || "Note sans description"} + {grade.student.disabled ? (grade.student.status === null ? "N. Not" : grade.student.status) : grade.student.value?.toFixed(2)} - {new Date(grade.timestamp).toLocaleDateString("fr-FR", { - weekday: "long", - month: "long", - day: "numeric", - })} + /{grade.outOf.value} - - - - {grade.student.disabled ? "N. not" : grade.student.value?.toFixed(2)} - - - /{grade.outOf.value} - - - - {/* Scrollable Content */} = ({ route, navigation }) => { width: "100%", }} > - - {lists.map((list, index) => ( - - - - {list.items.map( - (item, index) => - item && ( - - + + + + {lists.map((list, index) => ( + + + + + {list.items.map( + (item, index) => + item && ( + - {item.value} - - - {"bareme" in item && ( - - {item.bareme} + + {item.value} - )} - - } - > - {item.title} - {item.description && ( - - {item.description} - - )} - - ) - )} - - - ))} + + {"bareme" in item && ( + + {item.bareme} + + )} + + } + > + {item.title} + + {item.description && ( + + {item.description} + + )} + + ) + )} + + + ))} + diff --git a/src/views/account/Grades/Graph/GradesAverage.tsx b/src/views/account/Grades/Graph/GradesAverage.tsx index d69d95739..f6e96a7d0 100644 --- a/src/views/account/Grades/Graph/GradesAverage.tsx +++ b/src/views/account/Grades/Graph/GradesAverage.tsx @@ -329,26 +329,26 @@ const GradesAverageGraph: React.FC = ({ - Moyenne classe - - {classAvg !== null ? ( - <> - - - /20 - - - ) : ( - Inconnue - )} - - + Moyenne classe + + {classAvg !== null ? ( + <> + + + /20 + + + ) : ( + Inconnue + )} + + {showDetails && maxAvg > 0 && minAvg > 0 ? ( diff --git a/src/views/account/Grades/Latest/LatestGrades.tsx b/src/views/account/Grades/Latest/LatestGrades.tsx index 1cd1cfd71..e75083bb9 100644 --- a/src/views/account/Grades/Latest/LatestGrades.tsx +++ b/src/views/account/Grades/Latest/LatestGrades.tsx @@ -7,6 +7,7 @@ import GradesLatestItem from "./LatestGradesItem"; import { Grade } from "@/services/shared/Grade"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { RouteParameters } from "@/router/helpers/types"; +import * as Haptics from "expo-haptics"; interface GradesLatestListProps { latestGrades: Grade[] @@ -52,6 +53,12 @@ const GradesLatestList = (props: GradesLatestListProps) => { maxToRenderPerBatch={6} initialNumToRender={4} windowSize={3} + snapToAlignment="start" + snapToInterval={240} + decelerationRate="fast" + onScroll={({ nativeEvent }) => { + if (nativeEvent.contentOffset.x % 240 === 0) Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + }} /> diff --git a/src/views/account/Grades/Subject/GradeItem.tsx b/src/views/account/Grades/Subject/GradeItem.tsx index 016c0d9ba..8493eeb9a 100644 --- a/src/views/account/Grades/Subject/GradeItem.tsx +++ b/src/views/account/Grades/Subject/GradeItem.tsx @@ -36,7 +36,7 @@ const GradeItem: React.FC = ({ }); const gradeValue = - typeof grade.student.value === "number" + (typeof grade.student.value === "number" && !isNaN(grade.student.value)) ? grade.student.value.toFixed(2) : "N. not"; diff --git a/src/views/account/Grades/Subject/Subject.tsx b/src/views/account/Grades/Subject/Subject.tsx index 650022f43..b22c62341 100644 --- a/src/views/account/Grades/Subject/Subject.tsx +++ b/src/views/account/Grades/Subject/Subject.tsx @@ -1,11 +1,16 @@ import type { RouteParameters } from "@/router/helpers/types"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import type { Grade, GradesPerSubject } from "@/services/shared/Grade"; -import { NativeListHeader } from "@/components/Global/NativeComponents"; +import { NativeListHeader, NativeText } from "@/components/Global/NativeComponents"; import { animPapillon } from "@/utils/ui/animations"; -import Reanimated, { LinearTransition } from "react-native-reanimated"; -import { FlatList } from "react-native"; +import Reanimated, { FadeInRight, FadeOutLeft, LinearTransition } from "react-native-reanimated"; +import { FlatList, View } from "react-native"; import SubjectItem from "./SubjectList"; +import { useCallback, useMemo, useState } from "react"; +import PapillonPicker, { PickerDataItem } from "@/components/Global/PapillonPicker"; +import { ArrowDownAZ, Calendar, ChevronDown, TrendingUp } from "lucide-react-native"; +import { useTheme } from "@react-navigation/native"; +import PapillonSpinner from "@/components/Global/PapillonSpinner"; interface SubjectProps { allGrades: Grade[] @@ -13,37 +18,144 @@ interface SubjectProps { navigation: NativeStackNavigationProp } +type SortingFunction = (a: GradesPerSubject, b: GradesPerSubject) => number; + +const sortingFunctions: Record = { + 0: (a, b) => a.average.subjectName.localeCompare(b.average.subjectName), + 1: (a, b) => (b.grades[0]?.timestamp || 0) - (a.grades[0]?.timestamp || 0), + 2: (a, b) => (b.average?.average?.value || 0) - (a.average?.average?.value || 0) +}; + +const sortings: PickerDataItem[] = [ + { + label: "Alphabétique", + icon: , + }, + { + label: "Date", + icon: , + }, + { + label: "Moyenne", + icon: , + }, +]; + const Subject: React.FC = ({ gradesPerSubject, navigation, allGrades }) => { - const renderItem = ({ item, index }: { item: GradesPerSubject; index: number }) => ( + const [sorting, setSorting] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const theme = useTheme(); + + // Memoize sorted data + const sortedData = useMemo(() => { + const sortFn = sortingFunctions[sorting]; + return [...gradesPerSubject].sort(sortFn); + }, [gradesPerSubject, sorting]); + + const handleSortingChange = useCallback((item: PickerDataItem) => { + setIsLoading(true); + // Use requestAnimationFrame to prevent UI blocking + requestAnimationFrame(() => { + setSorting(sortings.indexOf(item)); + setIsLoading(false); + }); + }, []); + + const renderItem = useCallback(({ item, index }: { item: GradesPerSubject; index: number }) => ( - ); + ), [navigation, allGrades]); - const ListHeaderComponent = () => ( - - ); + const ListHeaderComponent = useCallback(() => ( + + + + {sortings[sorting].label} + + {isLoading && ( + + )} + + + + )} + /> + ), [sorting, theme.colors.primary, isLoading, handleSortingChange]); + + const keyExtractor = useCallback((item: GradesPerSubject, index: number) => + item.average.subjectName + index, + []); return ( item.average.subjectName + index} - removeClippedSubviews={true} + ListHeaderComponentStyle={{zIndex: 99}} + keyExtractor={keyExtractor} + removeClippedSubviews={false} maxToRenderPerBatch={10} initialNumToRender={8} windowSize={5} + contentContainerStyle={{ + overflow: "visible", + }} + style={{ + overflow: "visible", + }} /> ); diff --git a/src/views/account/Grades/Subject/SubjectList.tsx b/src/views/account/Grades/Subject/SubjectList.tsx index d05981f90..7dc972c49 100644 --- a/src/views/account/Grades/Subject/SubjectList.tsx +++ b/src/views/account/Grades/Subject/SubjectList.tsx @@ -43,11 +43,15 @@ const SubjectItem: React.FC = ({ fetchSubjectData(); }, [subject.average.subjectName]); + if (!subjectData) { + return null; + } + return ( = ({ }} /> )} - keyExtractor={(item) => item.id} + keyExtractor={(item) => { + if (!item.description) { + return item.id + "_" + Math.random(); + } + return item.id; + }} removeClippedSubviews={true} maxToRenderPerBatch={10} initialNumToRender={8} @@ -88,7 +97,7 @@ interface SubjectGradeItemProps { const SubjectGradeItem: React.FC = ({ subject, grade, index, onPress }) => { return ( @@ -135,9 +144,7 @@ const SubjectGradeItem: React.FC = ({ subject, grade, ind fontFamily: "medium", }} > - {grade.student.disabled === true ? "N. not" : (typeof grade.student.value === "number" - ? grade.student.value.toFixed(2) - : "N. not")} + {grade.student.disabled ? (grade.student.status === null ? "N. Not" : grade.student.status) : grade.student.value?.toFixed(2)} = ({ 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..6c2e2ce9e 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 { FadeInDown, FadeOut } from "react-native-reanimated"; +import MissingItem from "@/components/Global/MissingItem"; 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..40953f20c 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,33 @@ 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 hwFinalList = homeworks[dateToEpochWeekNumber(actualDay)]?.filter(hw => hw.due / 1000 >= startTime && hw.due / 1000 <= endTime); + 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 + ) ?? []; - if(hwFinalList.length === 0) { - return null; + if (hwSemaineActuelle.length === 0 && hwSemaineProchaine.length === 0) { + return ( + + + + + + ); } return ( @@ -85,7 +97,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 }) => @@ -171,7 +171,7 @@ const TimetableElement: React.FC = ({ onImportance }) => @@ -188,7 +188,7 @@ const TimetableElement: React.FC = ({ onImportance }) => > diff --git a/src/views/account/Home/Home.tsx b/src/views/account/Home/Home.tsx index 699d56195..7371485af 100644 --- a/src/views/account/Home/Home.tsx +++ b/src/views/account/Home/Home.tsx @@ -113,7 +113,7 @@ const Home: Screen<"HomeScreen"> = ({ navigation }) => { })); const modalAnimatedStyle = useAnimatedStyle(() => ({ - borderCurve: "continuous", + ...(Platform.OS === "android" ? {} : { borderCurve: "continuous" }), borderTopLeftRadius: interpolate( scrollOffset.value, [0, 100, 265 + insets.top - 0.1, 265 + insets.top], @@ -128,10 +128,12 @@ const Home: Screen<"HomeScreen"> = ({ navigation }) => { ), shadowColor: "#000", - shadowOffset: { - width: 0, - height: 2, - }, + ...(Platform.OS === "android" ? {} : { + shadowOffset: { + width: 0, + height: 2, + } + }), shadowOpacity: 0.2, shadowRadius: 10, @@ -250,15 +252,16 @@ const Home: Screen<"HomeScreen"> = ({ navigation }) => { } }} onScroll={(e) => { - if (e.nativeEvent.contentOffset.y > 125 && canHaptics) { + const scrollY = e.nativeEvent.contentOffset.y; + if (scrollY > 125 && canHaptics) { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setCanHaptics(false); - } else if (e.nativeEvent.contentOffset.y < 125 && !canHaptics) { + } else if (scrollY < 125 && !canHaptics) { setCanHaptics(true); } - setModalOpen(e.nativeEvent.contentOffset.y >= 195 + insets.top); - setModalFull(e.nativeEvent.contentOffset.y >= 265 + insets.top); + setModalOpen(scrollY >= 195 + insets.top); + setModalFull(scrollY >= 265 + insets.top); }} refreshControl={ = ({ route, navigation }) => { J'ai lu et pris connaissance - Vous confirmez avoir lu et votre établissement peut en être notifié. + Tu confirmes avoir lu et ton établissement peut en être notifié. diff --git a/src/views/account/Restaurant/Menu.tsx b/src/views/account/Restaurant/Menu.tsx index febc940bf..e9278feb9 100644 --- a/src/views/account/Restaurant/Menu.tsx +++ b/src/views/account/Restaurant/Menu.tsx @@ -6,13 +6,18 @@ import { Switch, Alert, ActivityIndicator, - RefreshControl + RefreshControl, + Text } from "react-native"; import { useTheme } from "@react-navigation/native"; import { AlertTriangle, + ChefHat, Clock2, + CookingPot, + MapPin, QrCode, + Sprout, Utensils, } from "lucide-react-native"; @@ -36,7 +41,7 @@ import { reservationHistoryFromExternal } from "@/services/reservation-history"; import { qrcodeFromExternal } from "@/services/qrcode"; import { ReservationHistory } from "@/services/shared/ReservationHistory"; import { getMenu } from "@/services/menu"; -import type { Menu as PawnoteMenu } from "pawnote"; +import type { FoodAllergen, FoodLabel, Menu as PawnoteMenu } from "pawnote"; import { PapillonHeaderSelector } from "@/components/Global/PapillonModernHeader"; import AnimatedNumber from "@/components/Global/AnimatedNumber"; import { LessonsDateModal } from "../Lessons/LessonsHeader"; @@ -55,9 +60,11 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { const [isRefreshing, setIsRefreshing] = useState(false); const [refreshCount, setRefreshCount] = useState(0); + const currentDate = new Date(); + const [allBalances, setAllBalances] = useState(null); const [allHistories, setAllHistories] = useState(null); - const [allQRCodes, setAllQRCodes] = useState(null); + const [allQRCodes, setAllQRCodes] = useState | null>(null); const [allBookings, setAllBookings] = useState(null); const [currentMenu, setCurrentMenu] = useState(null); const [currentWeek, setCurrentWeek] = useState(0); @@ -78,22 +85,41 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); }; - const updateDatePicker = async (date: Date) => { + const onDatePickerSelect =async (date?: Date) => { + if(!date) { + return; + } + + const newDate = new Date(date); + + newDate.setHours(0, 0, 0, 0); + + if(newDate.valueOf() === pickerDate.valueOf()) { + return; + } + + setPickerDate(newDate); + setMenuLoading(true); + const newWeek = getWeekNumber(date); if (currentWeek !== newWeek) { setCurrentWeek(newWeek); + const allBookings: BookingTerminal[] = []; + for (const account of linkedAccounts) { const bookingsForAccount = await getBookingsAvailableFromExternal(account, newWeek); allBookings.push(...bookingsForAccount); } + setAllBookings(allBookings); } const dailyMenu = account ? await getMenu(account, date).catch(() => null) : null; setCurrentMenu(dailyMenu); + setMenuLoading(false); }; @@ -140,7 +166,7 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { try { const newBalances: Balance[] = []; const newHistories: ReservationHistory[] = []; - const newQRCodes: string[] = []; + const newQRCodes: Array = []; const newBookings: BookingTerminal[] = []; const dailyMenu = account ? await getMenu(account, pickerDate).catch(() => null) : null; @@ -203,6 +229,74 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { return unsub; }, []); + const getLabelIcon = (label: string) => { + switch (label) { + case "Assemblé sur place": + return ; + case "Issu de l'Agriculture Biologique": + return ; + case "Fait maison - Recette du chef": + return ; + case "Produits locaux": + return ; + default: + return null; + } + }; + + const getLabelName = (label: string) => { + switch (label) { + case "Assemblé sur place": + return "Assemblé sur place"; + case "Issu de l'Agriculture Biologique": + return "Agriculture Biologique"; + case "Fait maison - Recette du chef": + return "Fait maison"; + case "Produits locaux": + return "Produits locaux"; + default: + return label; + } + }; + + function renderAllergens (allergens: ReadonlyArray) { + if(allergens.length === 0) { + return null; + } + + return ( + + + + Allergènes : {allergens.map(allergen => allergen.name).join(", ")} + + + ); + } + + function renderLabels (labels: ReadonlyArray) { + if(labels.length === 0) { + return null; + } + + return ( + + {labels.map((label, k) => ( + + {getLabelIcon(label.name)} + + {getLabelName(label.name)} + + + ))} + + ); + } + return ( = ({ route, navigation }) => { ) : ( <> {allBalances?.length === 0 ? ( - + ) : ( <> @@ -271,28 +365,28 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { {(currentMenu || (allBookings && allBookings.some((terminal) => terminal.days.some((day) => day.date?.toDateString() === pickerDate.toDateString())))) && - - setShowDatePicker(true)}> - - - - {pickerDate.toLocaleDateString("fr-FR", { weekday: "long" })} - + + setShowDatePicker(true)}> + + + + {pickerDate.toLocaleDateString("fr-FR", { weekday: "long" })} + + - - - - {pickerDate.toLocaleDateString("fr-FR", { month: "long" })} - - - + + + {pickerDate.toLocaleDateString("fr-FR", { month: "long" })} + + + } - {allBookings && allBookings.some((terminal) => terminal.days.some((day) => day.date?.toDateString() === pickerDate.toDateString())) && ( + {allBookings && pickerDate.getTime() > currentDate.getTime() && allBookings.some((terminal) => terminal.days.some((day) => day.date?.toDateString() === pickerDate.toDateString())) && ( <> @@ -352,18 +446,8 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { {food.name ?? ""} - {food.allergens.length > 0 && ( - - - - Allergènes : {food.allergens.map(allergen => allergen.name).join(", ")} - - - )} + {renderAllergens(food.allergens)} + {renderLabels(food.labels)} ))} @@ -393,18 +477,8 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { {food.name ?? ""} - {food.allergens.length > 0 && ( - - - - Allergènes : {food.allergens.map(allergen => allergen.name).join(", ")} - - - )} + {renderAllergens(food.allergens)} + {renderLabels(food.labels)} ))} @@ -448,14 +522,7 @@ const Menu: Screen<"Menu"> = ({ route, navigation }) => { showDatePicker={showDatePicker} setShowDatePicker={setShowDatePicker} currentDate={pickerDate} - onDateSelect={(date: Date | undefined) => { - if (!date) return; - const newDate = new Date(date); - newDate.setHours(0, 0, 0, 0); - setPickerDate(newDate); - updateDatePicker(newDate); - setShowDatePicker(false); - }} + onDateSelect={onDatePickerSelect} /> )} @@ -471,7 +538,9 @@ const styles = StyleSheet.create({ horizontalList: { marginTop: 10 }, calendarContainer: { flexDirection: "row", justifyContent: "center", alignItems: "center", marginTop: 16, marginBottom: -10, gap: 10 }, weekPickerText: { zIndex: 10000, fontSize: 14.5, fontFamily: "medium", opacity: 0.7 }, - allergensContainer: { display: "flex", flexDirection: "row", alignItems: "center", gap: 5 } + allergensContainer: { display: "flex", flexDirection: "row", alignItems: "center", gap: 5 }, + labelsContainer: { display: "flex", flexDirection: "row", alignItems: "center", gap: 5, marginTop: 4 }, + label: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6 } }); export default Menu; diff --git a/src/views/account/Restaurant/Modals/QrCode.tsx b/src/views/account/Restaurant/Modals/QrCode.tsx index 01d0e8a37..cb01db582 100644 --- a/src/views/account/Restaurant/Modals/QrCode.tsx +++ b/src/views/account/Restaurant/Modals/QrCode.tsx @@ -7,6 +7,7 @@ import { StyleSheet, ScrollView, Platform, AppState, + Image } from "react-native"; import { DeviceMotion } from "expo-sensors"; import { SafeAreaView } from "react-native-safe-area-context"; @@ -170,16 +171,31 @@ const RestaurantQrCode: Screen<"RestaurantQrCode"> = ({ route, navigation }) => scrollEnabled={qrcodes?.length > 1} onScroll={handleScroll} > - { qrcodes && qrcodes?.map((code, index) => ( - - - - ))} + { qrcodes && qrcodes.map((code, index) => { + if (typeof code === "string") { + return ( + + + + ); + } else if (code instanceof Blob) { + const imageUrl = URL.createObjectURL(code); + + return ( + + + + ); + } + })} { qrcodes && qrcodes.length > 1 && ( @@ -216,19 +232,20 @@ const styles = StyleSheet.create({ margin: 5, }, qrCodeContainer: { - height: 300, - width: 300, - borderRadius: 15, - marginTop: 75, - alignSelf: "center", - backgroundColor: "#FFFFFF", + alignItems: "center", + justifyContent: "center", + alignContent: "center", + marginTop: 75 }, qrCodeInnerContainer: { flex: 1, justifyContent: "center", alignItems: "center", width: 300, + padding: 15, + borderRadius: 15, alignSelf: "center", + backgroundColor: "#FFFFFF" }, instructionContainer: { marginTop: 60, @@ -262,6 +279,11 @@ const styles = StyleSheet.create({ inactiveDot: { backgroundColor: "#ffffff25", }, + barcodeImage: { + width: "100%", + height: 50, + resizeMode: "cover", + }, }); export default RestaurantQrCode; diff --git a/src/views/login/IdentityProvider/IdentityProviderSelector.tsx b/src/views/login/IdentityProvider/IdentityProviderSelector.tsx index c3e59e990..7f8d7777a 100644 --- a/src/views/login/IdentityProvider/IdentityProviderSelector.tsx +++ b/src/views/login/IdentityProvider/IdentityProviderSelector.tsx @@ -102,7 +102,7 @@ const IdentityProviderSelector: Screen<"IdentityProviderSelector"> = ({ navigati icon={} > - Les founisseurs d'identité ne fournissent pas de données (calendrier, notes, etc...) mais permettent de vous connecter à l'application. + Les founisseurs d'identité ne fournissent pas de données (calendrier, notes, etc...) mais permettent de te connecter à l'application. diff --git a/src/views/login/IdentityProvider/actions/BackgroundIUTLannion.tsx b/src/views/login/IdentityProvider/actions/BackgroundIUTLannion.tsx index 802567d4c..7e0e6c596 100644 --- a/src/views/login/IdentityProvider/actions/BackgroundIUTLannion.tsx +++ b/src/views/login/IdentityProvider/actions/BackgroundIUTLannion.tsx @@ -1,5 +1,3 @@ -import { NativeText } from "@/components/Global/NativeComponents"; -import PapillonSpinner from "@/components/Global/PapillonSpinner"; import defaultPersonalization from "@/services/local/default-personalization"; import { useAccounts, useCurrentAccount } from "@/stores/account"; import { AccountService, Identity, LocalAccount } from "@/stores/account/types"; @@ -9,8 +7,10 @@ import React from "react"; import { Alert, View } from "react-native"; import { WebView } from "react-native-webview"; import type { Screen } from "@/router/helpers/types"; -import { FadeInDown, FadeOutUp } from "react-native-reanimated"; +import PapillonSpinner from "@/components/Global/PapillonSpinner"; +import { NativeText } from "@/components/Global/NativeComponents"; import { animPapillon } from "@/utils/ui/animations"; +import { FadeInDown, FadeOutUp } from "react-native-reanimated"; const capitalizeFirst = (str: string) => { str = str.toLowerCase(); @@ -216,11 +216,11 @@ const BackgroundIUTLannion: Screen<"BackgroundIUTLannion"> = ({ route, navigatio onLoad={(data) => { const url = data.nativeEvent.url; - if(url.startsWith("https://sso-cas.univ-rennes1.fr//login?")) { + if(url.startsWith("https://sso-cas.univ-rennes.fr//login?")) { injectPassword(); } - if(url.startsWith("https://notes9.iutlan.univ-rennes.fr/") && canExtractJSON) { + if(url.startsWith("https://notes9.iutlan.univ-rennes1.fr/") && canExtractJSON) { redirectToData(); setCanExtractJSON(false); } @@ -232,6 +232,16 @@ const BackgroundIUTLannion: Screen<"BackgroundIUTLannion"> = ({ route, navigatio } }} + onError={(data) => { + console.error(data); + Alert.alert( + "Erreur", + "Impossible de se connecter au portail de l'IUT de Lannion. Vérifie ta connexion internet et réessaye.", + [{ text: "OK", onPress: () => navigation.goBack() }] + ); + navigation.goBack(); + }} + onMessage={(event) => { try { const parsedData = JSON.parse(event.nativeEvent.data); diff --git a/src/views/login/pronote/PronoteInstanceSelector.tsx b/src/views/login/pronote/PronoteInstanceSelector.tsx index c2bfd27df..62158b4d4 100644 --- a/src/views/login/pronote/PronoteInstanceSelector.tsx +++ b/src/views/login/pronote/PronoteInstanceSelector.tsx @@ -8,7 +8,6 @@ import { ActivityIndicator, Keyboard, KeyboardEvent, - Text, } from "react-native"; import pronote from "pawnote"; import Reanimated, { diff --git a/src/views/settings/ExternalAccount/PriceDetectionOnboarding.tsx b/src/views/settings/ExternalAccount/PriceDetectionOnboarding.tsx index 36bc869d0..a22b06d77 100644 --- a/src/views/settings/ExternalAccount/PriceDetectionOnboarding.tsx +++ b/src/views/settings/ExternalAccount/PriceDetectionOnboarding.tsx @@ -1,11 +1,11 @@ -import React, { useState } from "react"; +import React from "react"; import type { Screen } from "@/router/helpers/types"; import { useTheme } from "@react-navigation/native"; import { BellRing } from "lucide-react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Image, View, StyleSheet, Text } from "react-native"; import { NativeText, } from "@/components/Global/NativeComponents"; -import { useAccounts, useCurrentAccount } from "@/stores/account"; +import { useCurrentAccount } from "@/stores/account"; import { TouchableOpacity } from "react-native-gesture-handler"; import BetaIndicator from "@/components/News/Beta"; diff --git a/src/views/settings/ExternalAccount/ServiceSelector.tsx b/src/views/settings/ExternalAccount/ServiceSelector.tsx index 6f751bd9f..6c148cfba 100644 --- a/src/views/settings/ExternalAccount/ServiceSelector.tsx +++ b/src/views/settings/ExternalAccount/ServiceSelector.tsx @@ -10,8 +10,7 @@ import { AccountService } from "@/stores/account/types"; import { useCurrentAccount } from "@/stores/account"; import DuoListPressable from "@/components/FirstInstallation/DuoListPressable"; import ButtonCta from "@/components/FirstInstallation/ButtonCta"; -import { LinearGradient } from "expo-linear-gradient"; - + const ExternalAccountSelector: Screen<"ExternalAccountSelector"> = ({ navigation, route }) => { const theme = useTheme(); const { colors } = theme; diff --git a/src/views/settings/ExternalAccount/TurboselfAccountSelector.tsx b/src/views/settings/ExternalAccount/TurboselfAccountSelector.tsx index f909ccf49..f575a2c5a 100644 --- a/src/views/settings/ExternalAccount/TurboselfAccountSelector.tsx +++ b/src/views/settings/ExternalAccount/TurboselfAccountSelector.tsx @@ -109,7 +109,7 @@ const TurboselfAccountSelector: Screen<"TurboselfAccountSelector"> = ({ navigati textAlign: "center", }} > - Papillon ne donnera jamais vos informations d'authentification à des tiers. + Papillon ne donnera jamais tes informations d'authentification à des tiers. diff --git a/src/views/settings/SettingsApparence.tsx b/src/views/settings/SettingsApparence.tsx index dc017dd94..8e5f95d1d 100644 --- a/src/views/settings/SettingsApparence.tsx +++ b/src/views/settings/SettingsApparence.tsx @@ -10,6 +10,7 @@ import PapillonCheckbox from "@/components/Global/PapillonCheckbox"; import AsyncStorage from "@react-native-async-storage/async-storage"; import Animated, {FadeInDown, FadeOutDown} from "react-native-reanimated"; import ApparenceContainerCard from "@/components/Settings/ApparenceContainerCard"; +import * as Brightness from "expo-brightness"; const SettingsApparence: Screen<"SettingsApparence"> = () => { const theme = useTheme(); @@ -18,6 +19,7 @@ const SettingsApparence: Screen<"SettingsApparence"> = () => { const [selectedTheme, setSelectedTheme] = useState(0); const [hasUserChangedTheme, setHasUserChangedTheme] = useState(false); const [defaultTheme, setDefaultTheme] = useState(0); + const [pressedLightMode, setPressedLightMode] = useState(0); useEffect(() => { AsyncStorage.getItem("theme").then((value) => { @@ -34,6 +36,15 @@ const SettingsApparence: Screen<"SettingsApparence"> = () => { AsyncStorage.setItem("theme", selectedTheme.toString()); }, [selectedTheme]); + useEffect(() => { + if (pressedLightMode === 7) { + Brightness.setBrightnessAsync(1); + } else if (pressedLightMode > 7) { + setPressedLightMode(0); + Brightness.restoreSystemBrightnessAsync(); + } + }, [pressedLightMode]); + return ( = () => { leading={} trailing={ { setSelectedTheme(0); @@ -72,7 +83,7 @@ const SettingsApparence: Screen<"SettingsApparence"> = () => { leading={} trailing={ { setSelectedTheme(1); @@ -80,7 +91,10 @@ const SettingsApparence: Screen<"SettingsApparence"> = () => { style={{marginRight: 5}} /> } - onPress={() => {setSelectedTheme(1);}} + onPress={() => { + setSelectedTheme(1); + setPressedLightMode(pressedLightMode + 1); + }} chevron={false} > Mode clair @@ -90,7 +104,7 @@ const SettingsApparence: Screen<"SettingsApparence"> = () => { leading={} trailing={ { setSelectedTheme(2); diff --git a/src/views/settings/SettingsDevLogs.tsx b/src/views/settings/SettingsDevLogs.tsx index 23174c0af..cc67637be 100644 --- a/src/views/settings/SettingsDevLogs.tsx +++ b/src/views/settings/SettingsDevLogs.tsx @@ -1,5 +1,5 @@ import type { Screen } from "@/router/helpers/types"; -import { ActivityIndicator, ScrollView } from "react-native"; +import { ActivityIndicator, ScrollView, TextInput } from "react-native"; import { NativeIcon, NativeItem, @@ -8,21 +8,36 @@ import { NativeText, } from "@/components/Global/NativeComponents"; import React, { useEffect, useState } from "react"; -import { get_logs, Log, delete_logs } from "@/utils/logger/logger"; +import { + get_logs, + Log, + delete_logs, +} from "@/utils/logger/logger"; import { CircleAlert, CircleX, Code, Delete, + Layers, TriangleAlert, + Moon, + Newspaper, + Calendar, + Folder, } from "lucide-react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { PressableScale } from "react-native-pressable-scale"; -import { FadeInDown, FadeOutUp } from "react-native-reanimated"; +import { + FadeInDown, + FadeOutUp, +} from "react-native-reanimated"; import { animPapillon } from "@/utils/ui/animations"; +import { useTheme } from "@react-navigation/native"; const SettingsDevLogs: Screen<"SettingsDevLogs"> = ({ navigation }) => { + const theme = useTheme(); const [logs, setLogs] = useState([]); + const [searchTerms, setSearchTerms] = useState(""); const insets = useSafeAreaInsets(); const [loading, setLoading] = useState(true); @@ -35,9 +50,7 @@ const SettingsDevLogs: Screen<"SettingsDevLogs"> = ({ navigation }) => { navigation.setOptions({ headerRight: (props) => ( - delete_logs()} - > + delete_logs()}> ), @@ -52,6 +65,22 @@ const SettingsDevLogs: Screen<"SettingsDevLogs"> = ({ navigation }) => { paddingTop: 0, }} > + {loading && ( @@ -60,15 +89,8 @@ const SettingsDevLogs: Screen<"SettingsDevLogs"> = ({ navigation }) => { entering={animPapillon(FadeInDown)} exiting={animPapillon(FadeOutUp)} > - - } - animated - > - - Obtention des logs... - + } animated> + Obtention des logs... Cela peut prendre plusieurs secondes, patiente s'il te plaît. @@ -82,45 +104,69 @@ const SettingsDevLogs: Screen<"SettingsDevLogs"> = ({ navigation }) => { entering={animPapillon(FadeInDown)} exiting={animPapillon(FadeOutUp)} > - {logs.map((log, index) => ( - - ) : log.type === "WARN" ? ( - - ) : log.type === "INFO" ? ( - - ) : ( - - ) - } - color={ - log.type === "ERROR" - ? "#BE0B00" - : log.type === "WARN" - ? "#CF6B0F" - : log.type === "INFO" - ? "#0E7CCB" - : "#AAA" + {logs.slice().reverse().map((log, index) => { + if (log.message.toLowerCase().includes(searchTerms.toLowerCase())) { + return ( + + ) : log.type === "WARN" ? ( + + ) : log.type === "INFO" ? ( + + ) : log.message.startsWith("User navigate into /") ? ( + + ) : log.message === "App in background" ? ( + + ) : log.message.toLowerCase().includes("read") ? ( + + ) : log.message === "[timetable:updateClasses" ? ( + + ) : log.message.toLowerCase().includes("folder") ? ( + + ) : ( + + ) + } + color={ + log.type === "ERROR" + ? "#BE0B00" + : log.type === "WARN" + ? "#CF6B0F" + : log.type === "INFO" + ? "#0E7CCB" + : log.message.startsWith("User navigate into /") + ? "#28B463" + : log.message === "App in background" + ? "#1F618D" + : log.message.toLowerCase().includes("read") + ? "#D4AC02" + : log.message === "[timetable:updateClasses" + ? "#884EA0" + : log.message.toLowerCase().includes("folder") + ? "#CA6F1E" + : "#AAA" + } + style={{ + marginLeft: -6, + }} + /> } - style={{ - marginLeft: -6, - }} - /> - } - > - {log.message} - {log.date} - {log.from} - - ))} + > + {log.message} + {log.date} + {log.from} + + ); + } + return null; + })} - )} ); diff --git a/src/views/settings/SettingsProfile.tsx b/src/views/settings/SettingsProfile.tsx index 7900011d1..c7630b773 100644 --- a/src/views/settings/SettingsProfile.tsx +++ b/src/views/settings/SettingsProfile.tsx @@ -150,9 +150,9 @@ const SettingsProfile: Screen<"SettingsProfile"> = ({ navigation }) => { = ({ navigation }) => { setHideNameOnHomeScreen(!hideNameOnHomeScreen)} + trackColor={{false: theme.colors.border, true: theme.colors.primary}} + thumbColor={theme.colors.background} /> } > @@ -261,6 +263,8 @@ const SettingsProfile: Screen<"SettingsProfile"> = ({ navigation }) => { setHideProfilePicOnHomeScreen(!hideProfilePicOnHomeScreen)} + trackColor={{false: theme.colors.border, true: theme.colors.primary}} + thumbColor={theme.colors.background} /> } > diff --git a/src/views/settings/SettingsReactions.tsx b/src/views/settings/SettingsReactions.tsx index 49400a566..6b691d7f8 100644 --- a/src/views/settings/SettingsReactions.tsx +++ b/src/views/settings/SettingsReactions.tsx @@ -1,11 +1,10 @@ import React from "react"; -import { ScrollView, Text, View } from "react-native"; +import { ScrollView, View } from "react-native"; import type { Screen } from "@/router/helpers/types"; import { useTheme } from "@react-navigation/native"; import { useGradesStore } from "@/stores/grades"; import ReelGallery from "@/components/Settings/ReelGallery"; import MissingItem from "@/components/Global/MissingItem"; -import AnimatedEmoji from "@/components/Grades/AnimatedEmoji"; const SettingsReactions: Screen<"SettingsReactions"> = () => { const theme = useTheme(); diff --git a/src/views/welcome/DevMenu.tsx b/src/views/welcome/DevMenu.tsx index cfd7cd302..d5e4548d5 100644 --- a/src/views/welcome/DevMenu.tsx +++ b/src/views/welcome/DevMenu.tsx @@ -100,13 +100,14 @@ const DevMenu: Screen<"DevMenu"> = ({ navigation }) => { - navigation.navigate("NoteReaction")} > NoteReaction - + */} navigation.navigate("ColorSelector")} diff --git a/src/widgets/Components/LastGrade.tsx b/src/widgets/Components/LastGrade.tsx index b75b5d4fe..747951178 100644 --- a/src/widgets/Components/LastGrade.tsx +++ b/src/widgets/Components/LastGrade.tsx @@ -108,6 +108,10 @@ const LastGradeWidget = forwardRef(({ backgroundColor: subjectColor + "22", borderRadius: 50, padding: 6, + width: 40, + height: 40, + justifyContent: "center", + alignItems: "center", }} > {subjectEmoji} diff --git a/src/widgets/Components/RestaurantQRCode.tsx b/src/widgets/Components/RestaurantQRCode.tsx index 24eca3f40..ecfea4e43 100644 --- a/src/widgets/Components/RestaurantQRCode.tsx +++ b/src/widgets/Components/RestaurantQRCode.tsx @@ -27,7 +27,7 @@ const RestaurantQRCodeWidget = forwardRef(({ const account = useCurrentAccount((store) => store.account); const linkedAccounts = useCurrentAccount(store => store.linkedAccounts); - const [qrcode, setQRCodes] = useState(null); + const [qrcode, setQRCodes] = useState | null>(null); const navigation = useNavigation(); useImperativeHandle(ref, () => ({ @@ -40,7 +40,7 @@ const RestaurantQRCodeWidget = forwardRef(({ void async function () { setHidden(true); setLoading(true); - const qrcodes: string[] = []; + const qrcodes: Array = []; const currentHour = new Date().getHours(); for (const account of linkedAccounts) { if (account.service === AccountService.Turboself || account.service === AccountService.ARD) {