diff --git a/App.old.tsx b/App.old.tsx index e376b9b0..fa7f3b90 100644 --- a/App.old.tsx +++ b/App.old.tsx @@ -335,7 +335,7 @@ function InsetSettings() { name="About" component={AboutScreen} options={{ - headerTitle: 'A propos de Papillon', + headerTitle: 'À propos de Papillon', headerBackTitle: 'Préférences', }} /> @@ -344,7 +344,7 @@ function InsetSettings() { component={DonorsScreen} options={{ headerTitle: 'Donateurs', - headerBackTitle: 'A propos', + headerBackTitle: 'À propos', }} /> { console.log('[background fetch] Running background fetch'); - await Promise.all([fetchLessons(), fetchHomeworks(), fetchGrades()]); + await Promise.all([fetchLessons(), fetchHomeworks(), fetchGrades(), fetchCours(), selfReminder()]); return BackgroundFetchResult.NewData; }; diff --git a/fetch/background/BagReminder.ts b/fetch/background/BagReminder.ts new file mode 100644 index 00000000..272e1e3e --- /dev/null +++ b/fetch/background/BagReminder.ts @@ -0,0 +1,67 @@ +import {getContextValues} from '../../utils/AppContext'; +import notifee from '@notifee/react-native'; +import {PapillonLesson} from '../types/timetable'; +import {checkCanNotify, DidNotified, SetNotified} from './Helper'; +import formatCoursName from '../../utils/FormatCoursName'; + +const now = new Date(); + +const fetchCours = async () => { + if (now.getHours() >= 18 && now.getHours() <= 20) { + console.log('[background fetch] Running cours fetch'); + const tomorrow = new Date(now); + tomorrow.setDate(now.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + const tommorowAsString = tomorrow.toISOString().split('T')[0]; + let dataInstance = await getContextValues().dataProvider; + await dataInstance.getTimetable(tomorrow).then(timetable => { + console.log('[background fetch] fetched cours'); + const cours = timetable.filter(cours => { + if (cours.isCancelled) return false; + if (cours.start.split('T')[0] !== tommorowAsString) return false; + return true; + }); + if (cours.length > 0) { + remindBag(cours); + } + }); + } else { + console.log('[background fetch] Skipping cours fetch'); + } +}; + +const remindBag = async (lesson: PapillonLesson[]) => { + const tomorrow = new Date(now); + tomorrow.setDate(now.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + + const canNotify: boolean = await checkCanNotify('notifications_BagReminderEnabled'); + const didNotify: boolean = await DidNotified('hw_' + tomorrow.getTime()); + if (!canNotify || didNotify) return; + var body = ''; + var isFirst = true; + lesson.forEach(cours => { + if (!body.includes(cours.subject)) { + let start = new Date(cours.start); + let end = new Date(cours.end); + if (!isFirst) body += '\n'; + isFirst = false; + body += `${('0' + start.getHours()).slice(-2)}:${('0' + start.getMinutes()).slice(-2)} - ${('0' + end.getHours()).slice(-2)}:${('0' + end.getMinutes()).slice(-2)} • ${formatCoursName(cours.subject.name)}`; + } + }); + await notifee.displayNotification({ + id: 'hw_' + tomorrow.getTime(), + title: '🎒 Il est temps de préparer votre sac pour demain !', + body: body, + android: { + channelId: 'bag-remind', + }, + ios: { + sound: 'papillon_ding.wav', + threadId: 'notifications_BagReminderEnabled', + }, + }); + await SetNotified('hw_' + tomorrow.getTime()); +}; + +export default fetchCours; diff --git a/fetch/background/Grades.ts b/fetch/background/Grades.ts index e3e0963e..d37955b6 100644 --- a/fetch/background/Grades.ts +++ b/fetch/background/Grades.ts @@ -94,23 +94,24 @@ const sendGradesToSharedGroup = async (grades: PapillonGrades) => { }; const notifyGrades = async (grades: PapillonGrades[]) => { - let oldGrades = await AsyncStorage.getItem('oldGrades'); + let oldGradesData = await AsyncStorage.getItem('oldGrades'); const fullGrades = grades.grades; - const avg = await calculateSubjectAverage(fullGrades); - if (oldGrades === null) { + if (oldGradesData === null) { await AsyncStorage.setItem('oldGrades', JSON.stringify(fullGrades)); return true; } - oldGrades = JSON.parse(oldGrades); + const oldGrades = JSON.parse(oldGradesData); if (oldGrades.length === fullGrades.length) { return true; } - // find the difference between the two arrays - const lastGrades = fullGrades.filter((grade) => !oldGrades.includes(grade)); + // make a list of the new grades + const lastGrades = fullGrades.filter((grade) => { + return !oldGrades.some((oldGrade) => oldGrade.subject.name === grade.subject.name && oldGrade.date === grade.date && oldGrade.grade.value.value === grade.grade.value.value); + }); for (let i = 0; i < lastGrades.length; i++) { let lastGrade = lastGrades[i]; @@ -128,7 +129,7 @@ const notifyGrades = async (grades: PapillonGrades[]) => { let bdy = `Vous avez eu ${lastGrade.grade.value.value}/${lastGrade.grade.out_of.value} ${goodGrade ? '! 👏' : ''}`; if(lastGrade.grade.value.value === undefined || lastGrade.grade.value.value === null || lastGrade.grade.value.value < 0) { - bdy = 'Vous n\'avez pas été noté(e) pour ce cours.'; + bdy = 'Vous n\'avez pas été noté(e) pour cette évaluation.'; } notifee.displayNotification({ diff --git a/fetch/background/Helper.ts b/fetch/background/Helper.ts index 1b7cd706..2b48bac2 100644 --- a/fetch/background/Helper.ts +++ b/fetch/background/Helper.ts @@ -26,7 +26,7 @@ const checkCanNotify = async (type = 'notificationsEnabled') => { } console.log('[background fetch] authorized:', authorized, 'canNotify:', canNotify, 'enabled:', enabled); - if(authorized) RegisterNotifChannel() + if(authorized) RegisterNotifChannel(); return authorized && canNotify && enabled; }; @@ -46,7 +46,7 @@ const SetNotified = async (id: string) => { }; const RegisterNotifChannel = async () => { - if(Platform.OS === "ios") return; + if(Platform.OS === 'ios') return; let groups = [ { name: 'Nouvelles données disponibles', @@ -107,10 +107,24 @@ const RegisterNotifChannel = async () => { description: 'Indique quand une nouvelle actualité est disponible', importance: AndroidImportance.HIGH }, - ] - await notifee.createChannelGroups(groups) - await notifee.createChannels(channels) -} + { + name: 'Rappels de faire son sac', + id: 'remind-bag', + groupId: 'remind-group', + description: 'Notification de rappel de faire son sac', + importance: AndroidImportance.DEFAULT + }, + { + name: 'Rappels de self', + id: 'remind-self', + groupId: 'remind-group', + description: 'Notification de rappel de réserver le self', + importance: AndroidImportance.DEFAULT + } + ]; + await notifee.createChannelGroups(groups); + await notifee.createChannels(channels); +}; export { checkCanNotify, diff --git a/fetch/background/Lessons.ts b/fetch/background/Lessons.ts index fb144c10..ed4368d6 100644 --- a/fetch/background/Lessons.ts +++ b/fetch/background/Lessons.ts @@ -66,6 +66,12 @@ const notifyLessons = async (lessons: PapillonLesson[]) => { const canNotify : boolean = await checkCanNotify('notifications_CoursEnabled'); if (!canNotify) return; + // remove all notifications + for (const lesson of lessons) { + const lessonID = (lesson.subject?.name ? lesson.subject.name : '') + new Date(lesson.start).getTime(); + notifee.cancelNotification(lessonID); + } + // get all lessons with status set const lessonsWithStatus = lessons.filter(lesson => lesson.status !== undefined); diff --git a/fetch/background/SelfReminder.ts b/fetch/background/SelfReminder.ts new file mode 100644 index 00000000..171b9494 --- /dev/null +++ b/fetch/background/SelfReminder.ts @@ -0,0 +1,40 @@ +import notifee from '@notifee/react-native'; +import {getContextValues} from '../../utils/AppContext'; +import {checkCanNotify, DidNotified} from './Helper'; + +const SelfReminder = async () => { + console.log('[background fetch] Running self reminder'); + const now = new Date(); + + const canNotify: boolean = await checkCanNotify('notifications_BagReminderEnabled'); + const didNotify: boolean = await DidNotified('hw_' + now.getTime()); + if (!canNotify || didNotify) return; + + if (now.getHours() >= 8 && now.getHours() <= 10) { + let dataInstance = await getContextValues().dataProvider; + await dataInstance.getTimetable(now).then(async (timetable) => { + console.log('[background fetch] fetched cours'); + const cours = timetable.filter(cours => { + if (cours.isCancelled) return false; + if (cours.start.split('T')[0] !== now.toISOString().split('T')[0]) return false; + return true; + }); + if (cours.length > 0) { + await notifee.displayNotification({ + id: 'self_' + now.getTime(), + title: '🍽️ Pense à réserver le self !', + body: 'Il est temps d\'aller réserver le self pour ce midi.', + android: { + channelId: 'self-remind', + }, + ios: { + sound: 'papillon_ding.wav', + threadId: 'notifications_SelfReminderEnabled', + }, + }); + } + }); + } +}; + +export default SelfReminder; diff --git a/ios/Papillon.xcodeproj/project.pbxproj b/ios/Papillon.xcodeproj/project.pbxproj index ce0e10ea..6869cd06 100644 --- a/ios/Papillon.xcodeproj/project.pbxproj +++ b/ios/Papillon.xcodeproj/project.pbxproj @@ -30,52 +30,52 @@ BDA68BB82ABF5ED00034EC6A /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BDA68BB62ABF5ED00034EC6A /* Intents.intentdefinition */; }; BDF0E6BD2B9A9BF000A77575 /* GradeWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF0E6BA2B9A9BF000A77575 /* GradeWidget.swift */; }; BDF0E6BE2B9A9BF000A77575 /* SystemSmallGradeWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF0E6BC2B9A9BF000A77575 /* SystemSmallGradeWidget.swift */; }; - 36C144D0BDCC457F8E5074E5 /* relief-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EEB7B528074B41CABAF6B677 /* relief-Icon-60x60@2x.png */; }; - 031906744EBD40E38AE9D5D7 /* relief-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1F21D6C43CD84CF28564397A /* relief-Icon-60x60@3x.png */; }; - EA4854E06C974276B3C10C48 /* beta-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A07FE353856B499192F66850 /* beta-Icon-60x60@2x.png */; }; - 655EBC0E85964339A2A6E91A /* beta-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C9040B77C4DC4873B63B8C99 /* beta-Icon-60x60@3x.png */; }; - 14294C04C0494BE88E7A921E /* black-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 199812555E3B487CA1087832 /* black-Icon-60x60@2x.png */; }; - 380A675CD38F43FC96933362 /* black-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 824242A819B841F09FFEEEA2 /* black-Icon-60x60@3x.png */; }; - 0B5A64F2820F45489D7E352D /* chip-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F9F34814DC564A2FBBFB3071 /* chip-Icon-60x60@2x.png */; }; - B1D798BD53724A449A615EE4 /* chip-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 50CDEE9DB119497D80332F06 /* chip-Icon-60x60@3x.png */; }; - 35FBB2C3B69C472E85746A0F /* cutted-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B809D7D90B941A9BBA74B62 /* cutted-Icon-60x60@2x.png */; }; - 4861B6974E7D4349B882BCC1 /* cutted-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 95344399564547999E42E509 /* cutted-Icon-60x60@3x.png */; }; - 5D2A5911792C43A3AF344D7C /* classic-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DB72AF7F311E458EA4904E19 /* classic-Icon-60x60@2x.png */; }; - DC0BBD59195F4C89A19DD917 /* classic-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0190905430F04CB4806D5E6B /* classic-Icon-60x60@3x.png */; }; - C6D6E664AFA44DC59472BB72 /* gold-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 81D62E84C6954B139888DACA /* gold-Icon-60x60@2x.png */; }; - A212115E638A41A2B136A958 /* gold-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D24488CD10B49C0BADD10BC /* gold-Icon-60x60@3x.png */; }; - 987E9F26CFBE4CB39BB96EC6 /* gradient-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9C94A061595C4D25BECC55CC /* gradient-Icon-60x60@2x.png */; }; - 63F49A136159431D9C6B2920 /* gradient-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F4DCD942896E4A7FB86897DE /* gradient-Icon-60x60@3x.png */; }; - E679D645138D493993943D4C /* metal-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C57766AA4E754FD89556A892 /* metal-Icon-60x60@2x.png */; }; - 92BCA684A5D34FFCA3E16A3D /* metal-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 08A6C59B86DA47D492719EB0 /* metal-Icon-60x60@3x.png */; }; - C85AEEA6182D4FF08BFF310D /* neon-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8A39597301C240998E2B450D /* neon-Icon-60x60@2x.png */; }; - 60A10F35C4884B3884B997C6 /* neon-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = A77CCD32E68444D698538998 /* neon-Icon-60x60@3x.png */; }; - 88CAE73D3F3B4636BD93B1A8 /* pride-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 160D55F66A544EE18B93B260 /* pride-Icon-60x60@2x.png */; }; - 5B000C80727246BAA64B2EEA /* pride-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B4D791C808EF4B2A8B302902 /* pride-Icon-60x60@3x.png */; }; - A38EBF31062E4271BBA81192 /* purple-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E9623F4B27A344119E4995F5 /* purple-Icon-60x60@2x.png */; }; - DBD22842438A4AA09972FEE7 /* purple-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3C77C0DC85A7472EA5ADC9ED /* purple-Icon-60x60@3x.png */; }; - 46DE351F09774FB0BD33C6E6 /* rayspurple-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1400469A1EB644E2AAC07C22 /* rayspurple-Icon-60x60@2x.png */; }; - 02516804F26D42C482AAD14C /* rayspurple-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 87656C4C91BE4B52AF4B01EC /* rayspurple-Icon-60x60@3x.png */; }; - C2F39DDC4B6D4404BEC390C3 /* rays-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0EF0C6F6FFA147578A9E1013 /* rays-Icon-60x60@2x.png */; }; - 835FBBB8B9754F06AFC132E5 /* rays-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3F0AC4816D644506A5AF6E5B /* rays-Icon-60x60@3x.png */; }; - 0DCC93A0C07B4AD78D219181 /* retro-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 899164250BDE4B63A5CA3D84 /* retro-Icon-60x60@2x.png */; }; - 3A05DEDB06C64BD7AC1337E8 /* retro-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BD739E400BD4911B574F03E /* retro-Icon-60x60@3x.png */; }; - 2ED7771F7C8E4F52946A703E /* sparkles-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C960935E986849DD841B02A4 /* sparkles-Icon-60x60@2x.png */; }; - 226DFB4F33324441AF156B72 /* sparkles-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 33C38D89EA2F46EDA2DC0AC1 /* sparkles-Icon-60x60@3x.png */; }; - E4737C8C06D246208C55DEA4 /* lightgreen-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 27905055C4CD4393A76F209A /* lightgreen-Icon-60x60@2x.png */; }; - 9F6D139A36974CEA9129DE68 /* lightgreen-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 48B9E5B6B0CA4C9AB25355C0 /* lightgreen-Icon-60x60@3x.png */; }; - BCA3354417E64433847A69C1 /* backtoschool-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 66BAE59E93EF41F69CD60EFA /* backtoschool-Icon-60x60@2x.png */; }; - CD1B94E141A94F86B602B6A9 /* backtoschool-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = AC10BF2EF16F45DFAEFE51AD /* backtoschool-Icon-60x60@3x.png */; }; - 4D1ED56951794A98BBD5B561 /* barbie-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B0CBC293AE584565A7E061B6 /* barbie-Icon-60x60@2x.png */; }; - 2DC51E7D6C82472F9026D630 /* barbie-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 192D2C5382E745859DA5A120 /* barbie-Icon-60x60@3x.png */; }; - 12976D90A95B4B97BCF7DA6F /* betterneon-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B3BA1D842BE4FF9A3B6EEF4 /* betterneon-Icon-60x60@2x.png */; }; - 052EA8BAE63946288D031848 /* betterneon-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 38AE114E9B6249DABC1763AD /* betterneon-Icon-60x60@3x.png */; }; - 1931B81670C14C338E43832E /* macos-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EC4BF6274042A1B32B1E /* macos-Icon-60x60@2x.png */; }; - A5A0F4FB0C8A4D47854A206F /* macos-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 495EDDC8A347424696BC9815 /* macos-Icon-60x60@3x.png */; }; - 9D68F9C36F3D4A828A6F4105 /* oldios-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52844129341344C799497601 /* oldios-Icon-60x60@2x.png */; }; - 22907F47E6DE4F1DBE44EAF8 /* oldios-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9A7F32A990024C5FBF9A8BA0 /* oldios-Icon-60x60@3x.png */; }; - 25A3AAB26FAC4432A71F0B99 /* verscinq-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 672B666D2B5B4DC7917A7D1F /* verscinq-Icon-60x60@2x.png */; }; - CD578BFD94A74CB086E9CD62 /* verscinq-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = E9E534C5C88743D58FA12DC3 /* verscinq-Icon-60x60@3x.png */; }; + 29ECE730080B40A580B2E5BF /* relief-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 50689BA322C44A78B5003000 /* relief-Icon-60x60@2x.png */; }; + 0061414B40C248EE93A8C192 /* relief-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 880B7066DF8D43FE8577FA53 /* relief-Icon-60x60@3x.png */; }; + 03835D1F13D54CADBA5A8924 /* beta-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BE99E5DF98BF4AD385F2DC17 /* beta-Icon-60x60@2x.png */; }; + F3397183B1A24982BC935127 /* beta-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 81122D5C9FCC4EB1A18B9929 /* beta-Icon-60x60@3x.png */; }; + D1D4F02E8A064589909F2047 /* black-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5939E1A59F334EC9B42924E2 /* black-Icon-60x60@2x.png */; }; + DBFC7FDCE83C4A68A80A8F5B /* black-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C615CCA9CC764F3F802429A2 /* black-Icon-60x60@3x.png */; }; + 992FF1196B85410DBCB6E332 /* chip-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A5282314B08D4325BACD3648 /* chip-Icon-60x60@2x.png */; }; + D164F444B4C64225A20EF667 /* chip-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7585AD79015A4614A57762F4 /* chip-Icon-60x60@3x.png */; }; + 0C60167BB0FA45168A268FDF /* cutted-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1A88EDFDB674480483DA40BF /* cutted-Icon-60x60@2x.png */; }; + 8BDDF3A1AB124110A64CD40D /* cutted-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 66F2AC249CAA42E5BFFFC0C5 /* cutted-Icon-60x60@3x.png */; }; + 0CBF912DC3DF4AA1ADE67B37 /* classic-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C4904ADC2C33469AA3CBE11D /* classic-Icon-60x60@2x.png */; }; + DD38A6BB8B824FEB8FE4647B /* classic-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2B0038150A3D4E4D98F1FC1F /* classic-Icon-60x60@3x.png */; }; + 5FF98FCCFC22415097405414 /* gold-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 69F1E4CC6A794AE1A7439226 /* gold-Icon-60x60@2x.png */; }; + 7E947167C5124F70AE0969A4 /* gold-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2653BAE651234FC99730FE39 /* gold-Icon-60x60@3x.png */; }; + B61574447ED54320AAEC6E59 /* gradient-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9EEF676419374D9CB0679EB7 /* gradient-Icon-60x60@2x.png */; }; + 7D7B9AA0B0F24AE9A3CC5BA8 /* gradient-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 306537C054DA4E17AB5C44E6 /* gradient-Icon-60x60@3x.png */; }; + A31CA97A96F8445DAA8DADB1 /* metal-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 23B358831EFD4C56984759F6 /* metal-Icon-60x60@2x.png */; }; + B7994D5B42434179BD88B981 /* metal-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B9A5F7F1FAF342FBB906EDEF /* metal-Icon-60x60@3x.png */; }; + DFC1DF7F15B54FF090CB321A /* neon-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 14DDA3D991D14BCD9BA3119D /* neon-Icon-60x60@2x.png */; }; + 8A5CF142090C4F73A147BA66 /* neon-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 41A124E0267247269CFDAE10 /* neon-Icon-60x60@3x.png */; }; + C48B38E02DA549EEA7AF445C /* pride-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 12FC4EE805E040118FE03D2E /* pride-Icon-60x60@2x.png */; }; + 422804F6AE9B44FDAA50766E /* pride-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 58C1A10437C1467E833D2DC9 /* pride-Icon-60x60@3x.png */; }; + BB2476FC6A1742E0A058ECCC /* purple-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 311CDA58D7134E929744D0C5 /* purple-Icon-60x60@2x.png */; }; + 29160BD474874B8BB985975E /* purple-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B26F223078D4EED8F760BB0 /* purple-Icon-60x60@3x.png */; }; + E9764B19D2704BF288A46156 /* rayspurple-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AEB06A01647445E3AA5951EC /* rayspurple-Icon-60x60@2x.png */; }; + F4EDA951C8424C33A8773786 /* rayspurple-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1D71BE31E6754D8AB8D8C1CA /* rayspurple-Icon-60x60@3x.png */; }; + 9BAA5FE2BDF24E0DBFFD65C2 /* rays-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B66D3767798D42B996C5FADD /* rays-Icon-60x60@2x.png */; }; + 42A706D5724E4C4CB01877B6 /* rays-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8A1932A5491641F29D66E283 /* rays-Icon-60x60@3x.png */; }; + 50D7A7391DE64D708978EE86 /* retro-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0AE46211C8D34BBB88297290 /* retro-Icon-60x60@2x.png */; }; + EBBA8A49801C4F69847359BC /* retro-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4F54853B4B51436EBD20249E /* retro-Icon-60x60@3x.png */; }; + 1B0CB18130DB45B7B04D969C /* sparkles-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A3EDBC827C2D4CFC9ADCC9ED /* sparkles-Icon-60x60@2x.png */; }; + DAAC83EA7DEA4B89B149BF44 /* sparkles-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1F1537A00BD147D2A7C81787 /* sparkles-Icon-60x60@3x.png */; }; + 0A32EDE680FA4F51B0DEA26D /* lightgreen-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8F09700A18C54F11A1D0DA60 /* lightgreen-Icon-60x60@2x.png */; }; + E7E9E3CCE10841199C9886B7 /* lightgreen-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B2CD9A47830B44578311608C /* lightgreen-Icon-60x60@3x.png */; }; + 96280EC957514B82885B5A04 /* backtoschool-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C2232213665C43639D032D26 /* backtoschool-Icon-60x60@2x.png */; }; + 834C78B45A404B749A88E044 /* backtoschool-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 09F6D2D16BB84BFCB7F82C21 /* backtoschool-Icon-60x60@3x.png */; }; + B083676D70D9489DA5CD1C23 /* barbie-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A322B2C018484A85B0823D46 /* barbie-Icon-60x60@2x.png */; }; + E8A80ED046D24E44971089DF /* barbie-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 08A8C1D127F04A1DBEE7461F /* barbie-Icon-60x60@3x.png */; }; + D109F8224F4648DE924FF9BC /* betterneon-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F68549047B5B4CC18E6CEDBC /* betterneon-Icon-60x60@2x.png */; }; + D57D548201D54CBC8CF216D1 /* betterneon-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B13B28F100B14B1F94120AF5 /* betterneon-Icon-60x60@3x.png */; }; + 2BA75672F2014C50A014E12A /* macos-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CA3921CE24444E5799CACFE8 /* macos-Icon-60x60@2x.png */; }; + 7BD0158BC3364E51BC19489D /* macos-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = FAEEFC30BA4542E3A014852F /* macos-Icon-60x60@3x.png */; }; + B37480F2DF364A54816A823B /* oldios-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EC9970C67C5B480492E3C2C4 /* oldios-Icon-60x60@2x.png */; }; + 13E5744CC0864BC094A3281F /* oldios-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 02B5CD3490F94278A3CC1EF8 /* oldios-Icon-60x60@3x.png */; }; + 1626AB64C5A74864B1641184 /* verscinq-Icon-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8585746E160F4B0C81A1ABA7 /* verscinq-Icon-60x60@2x.png */; }; + 5BACCF11133D4AEABA079508 /* verscinq-Icon-60x60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 34E12304924F49E6B66637D3 /* verscinq-Icon-60x60@3x.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -138,52 +138,52 @@ BDF0E6C02B9AA25400A77575 /* PapillonRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = PapillonRelease.entitlements; path = Papillon/PapillonRelease.entitlements; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Papillon/ExpoModulesProvider.swift"; sourceTree = ""; }; - EEB7B528074B41CABAF6B677 /* relief-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "relief-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/relief-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 1F21D6C43CD84CF28564397A /* relief-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "relief-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/relief-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - A07FE353856B499192F66850 /* beta-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "beta-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/beta-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - C9040B77C4DC4873B63B8C99 /* beta-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "beta-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/beta-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 199812555E3B487CA1087832 /* black-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "black-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/black-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 824242A819B841F09FFEEEA2 /* black-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "black-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/black-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - F9F34814DC564A2FBBFB3071 /* chip-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "chip-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/chip-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 50CDEE9DB119497D80332F06 /* chip-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "chip-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/chip-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 8B809D7D90B941A9BBA74B62 /* cutted-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "cutted-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/cutted-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 95344399564547999E42E509 /* cutted-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "cutted-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/cutted-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - DB72AF7F311E458EA4904E19 /* classic-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "classic-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/classic-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 0190905430F04CB4806D5E6B /* classic-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "classic-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/classic-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 81D62E84C6954B139888DACA /* gold-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "gold-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/gold-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 2D24488CD10B49C0BADD10BC /* gold-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "gold-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/gold-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 9C94A061595C4D25BECC55CC /* gradient-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "gradient-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/gradient-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - F4DCD942896E4A7FB86897DE /* gradient-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "gradient-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/gradient-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - C57766AA4E754FD89556A892 /* metal-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "metal-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/metal-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 08A6C59B86DA47D492719EB0 /* metal-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "metal-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/metal-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 8A39597301C240998E2B450D /* neon-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "neon-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/neon-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - A77CCD32E68444D698538998 /* neon-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "neon-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/neon-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 160D55F66A544EE18B93B260 /* pride-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "pride-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/pride-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - B4D791C808EF4B2A8B302902 /* pride-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "pride-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/pride-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - E9623F4B27A344119E4995F5 /* purple-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "purple-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/purple-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 3C77C0DC85A7472EA5ADC9ED /* purple-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "purple-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/purple-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 1400469A1EB644E2AAC07C22 /* rayspurple-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "rayspurple-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/rayspurple-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 87656C4C91BE4B52AF4B01EC /* rayspurple-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "rayspurple-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/rayspurple-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 0EF0C6F6FFA147578A9E1013 /* rays-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "rays-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/rays-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 3F0AC4816D644506A5AF6E5B /* rays-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "rays-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/rays-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 899164250BDE4B63A5CA3D84 /* retro-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "retro-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/retro-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 8BD739E400BD4911B574F03E /* retro-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "retro-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/retro-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - C960935E986849DD841B02A4 /* sparkles-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "sparkles-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/sparkles-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 33C38D89EA2F46EDA2DC0AC1 /* sparkles-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "sparkles-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/sparkles-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 27905055C4CD4393A76F209A /* lightgreen-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "lightgreen-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/lightgreen-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 48B9E5B6B0CA4C9AB25355C0 /* lightgreen-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "lightgreen-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/lightgreen-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 66BAE59E93EF41F69CD60EFA /* backtoschool-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "backtoschool-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/backtoschool-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - AC10BF2EF16F45DFAEFE51AD /* backtoschool-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "backtoschool-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/backtoschool-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - B0CBC293AE584565A7E061B6 /* barbie-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "barbie-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/barbie-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 192D2C5382E745859DA5A120 /* barbie-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "barbie-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/barbie-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 4B3BA1D842BE4FF9A3B6EEF4 /* betterneon-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "betterneon-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/betterneon-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 38AE114E9B6249DABC1763AD /* betterneon-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "betterneon-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/betterneon-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 4B44EC4BF6274042A1B32B1E /* macos-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "macos-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/macos-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 495EDDC8A347424696BC9815 /* macos-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "macos-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/macos-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 52844129341344C799497601 /* oldios-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "oldios-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/oldios-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 9A7F32A990024C5FBF9A8BA0 /* oldios-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "oldios-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/oldios-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - 672B666D2B5B4DC7917A7D1F /* verscinq-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "verscinq-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/verscinq-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; - E9E534C5C88743D58FA12DC3 /* verscinq-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "verscinq-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/verscinq-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 50689BA322C44A78B5003000 /* relief-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "relief-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/relief-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 880B7066DF8D43FE8577FA53 /* relief-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "relief-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/relief-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + BE99E5DF98BF4AD385F2DC17 /* beta-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "beta-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/beta-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 81122D5C9FCC4EB1A18B9929 /* beta-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "beta-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/beta-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 5939E1A59F334EC9B42924E2 /* black-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "black-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/black-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + C615CCA9CC764F3F802429A2 /* black-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "black-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/black-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + A5282314B08D4325BACD3648 /* chip-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "chip-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/chip-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 7585AD79015A4614A57762F4 /* chip-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "chip-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/chip-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 1A88EDFDB674480483DA40BF /* cutted-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "cutted-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/cutted-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 66F2AC249CAA42E5BFFFC0C5 /* cutted-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "cutted-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/cutted-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + C4904ADC2C33469AA3CBE11D /* classic-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "classic-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/classic-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 2B0038150A3D4E4D98F1FC1F /* classic-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "classic-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/classic-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 69F1E4CC6A794AE1A7439226 /* gold-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "gold-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/gold-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 2653BAE651234FC99730FE39 /* gold-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "gold-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/gold-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 9EEF676419374D9CB0679EB7 /* gradient-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "gradient-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/gradient-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 306537C054DA4E17AB5C44E6 /* gradient-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "gradient-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/gradient-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 23B358831EFD4C56984759F6 /* metal-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "metal-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/metal-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + B9A5F7F1FAF342FBB906EDEF /* metal-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "metal-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/metal-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 14DDA3D991D14BCD9BA3119D /* neon-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "neon-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/neon-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 41A124E0267247269CFDAE10 /* neon-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "neon-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/neon-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 12FC4EE805E040118FE03D2E /* pride-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "pride-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/pride-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 58C1A10437C1467E833D2DC9 /* pride-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "pride-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/pride-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 311CDA58D7134E929744D0C5 /* purple-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "purple-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/purple-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 4B26F223078D4EED8F760BB0 /* purple-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "purple-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/purple-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + AEB06A01647445E3AA5951EC /* rayspurple-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "rayspurple-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/rayspurple-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 1D71BE31E6754D8AB8D8C1CA /* rayspurple-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "rayspurple-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/rayspurple-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + B66D3767798D42B996C5FADD /* rays-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "rays-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/rays-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 8A1932A5491641F29D66E283 /* rays-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "rays-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/rays-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 0AE46211C8D34BBB88297290 /* retro-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "retro-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/retro-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 4F54853B4B51436EBD20249E /* retro-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "retro-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/retro-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + A3EDBC827C2D4CFC9ADCC9ED /* sparkles-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "sparkles-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/sparkles-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 1F1537A00BD147D2A7C81787 /* sparkles-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "sparkles-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/sparkles-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 8F09700A18C54F11A1D0DA60 /* lightgreen-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "lightgreen-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/lightgreen-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + B2CD9A47830B44578311608C /* lightgreen-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "lightgreen-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/lightgreen-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + C2232213665C43639D032D26 /* backtoschool-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "backtoschool-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/backtoschool-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 09F6D2D16BB84BFCB7F82C21 /* backtoschool-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "backtoschool-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/backtoschool-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + A322B2C018484A85B0823D46 /* barbie-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "barbie-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/barbie-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 08A8C1D127F04A1DBEE7461F /* barbie-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "barbie-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/barbie-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + F68549047B5B4CC18E6CEDBC /* betterneon-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "betterneon-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/betterneon-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + B13B28F100B14B1F94120AF5 /* betterneon-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "betterneon-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/betterneon-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + CA3921CE24444E5799CACFE8 /* macos-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "macos-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/macos-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + FAEEFC30BA4542E3A014852F /* macos-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "macos-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/macos-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + EC9970C67C5B480492E3C2C4 /* oldios-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "oldios-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/oldios-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 02B5CD3490F94278A3CC1EF8 /* oldios-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "oldios-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/oldios-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 8585746E160F4B0C81A1ABA7 /* verscinq-Icon-60x60@2x.png */ = {isa = PBXFileReference; name = "verscinq-Icon-60x60@2x.png"; path = "Papillon/DynamicAppIcons/verscinq-Icon-60x60@2x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; + 34E12304924F49E6B66637D3 /* verscinq-Icon-60x60@3x.png */ = {isa = PBXFileReference; name = "verscinq-Icon-60x60@3x.png"; path = "Papillon/DynamicAppIcons/verscinq-Icon-60x60@3x.png"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -283,52 +283,52 @@ B2BD97BA2E1449C181B5DC8A /* DynamicAppIcons */ = { isa = PBXGroup; children = ( - EEB7B528074B41CABAF6B677 /* relief-Icon-60x60@2x.png */, - 1F21D6C43CD84CF28564397A /* relief-Icon-60x60@3x.png */, - A07FE353856B499192F66850 /* beta-Icon-60x60@2x.png */, - C9040B77C4DC4873B63B8C99 /* beta-Icon-60x60@3x.png */, - 199812555E3B487CA1087832 /* black-Icon-60x60@2x.png */, - 824242A819B841F09FFEEEA2 /* black-Icon-60x60@3x.png */, - F9F34814DC564A2FBBFB3071 /* chip-Icon-60x60@2x.png */, - 50CDEE9DB119497D80332F06 /* chip-Icon-60x60@3x.png */, - 8B809D7D90B941A9BBA74B62 /* cutted-Icon-60x60@2x.png */, - 95344399564547999E42E509 /* cutted-Icon-60x60@3x.png */, - DB72AF7F311E458EA4904E19 /* classic-Icon-60x60@2x.png */, - 0190905430F04CB4806D5E6B /* classic-Icon-60x60@3x.png */, - 81D62E84C6954B139888DACA /* gold-Icon-60x60@2x.png */, - 2D24488CD10B49C0BADD10BC /* gold-Icon-60x60@3x.png */, - 9C94A061595C4D25BECC55CC /* gradient-Icon-60x60@2x.png */, - F4DCD942896E4A7FB86897DE /* gradient-Icon-60x60@3x.png */, - C57766AA4E754FD89556A892 /* metal-Icon-60x60@2x.png */, - 08A6C59B86DA47D492719EB0 /* metal-Icon-60x60@3x.png */, - 8A39597301C240998E2B450D /* neon-Icon-60x60@2x.png */, - A77CCD32E68444D698538998 /* neon-Icon-60x60@3x.png */, - 160D55F66A544EE18B93B260 /* pride-Icon-60x60@2x.png */, - B4D791C808EF4B2A8B302902 /* pride-Icon-60x60@3x.png */, - E9623F4B27A344119E4995F5 /* purple-Icon-60x60@2x.png */, - 3C77C0DC85A7472EA5ADC9ED /* purple-Icon-60x60@3x.png */, - 1400469A1EB644E2AAC07C22 /* rayspurple-Icon-60x60@2x.png */, - 87656C4C91BE4B52AF4B01EC /* rayspurple-Icon-60x60@3x.png */, - 0EF0C6F6FFA147578A9E1013 /* rays-Icon-60x60@2x.png */, - 3F0AC4816D644506A5AF6E5B /* rays-Icon-60x60@3x.png */, - 899164250BDE4B63A5CA3D84 /* retro-Icon-60x60@2x.png */, - 8BD739E400BD4911B574F03E /* retro-Icon-60x60@3x.png */, - C960935E986849DD841B02A4 /* sparkles-Icon-60x60@2x.png */, - 33C38D89EA2F46EDA2DC0AC1 /* sparkles-Icon-60x60@3x.png */, - 27905055C4CD4393A76F209A /* lightgreen-Icon-60x60@2x.png */, - 48B9E5B6B0CA4C9AB25355C0 /* lightgreen-Icon-60x60@3x.png */, - 66BAE59E93EF41F69CD60EFA /* backtoschool-Icon-60x60@2x.png */, - AC10BF2EF16F45DFAEFE51AD /* backtoschool-Icon-60x60@3x.png */, - B0CBC293AE584565A7E061B6 /* barbie-Icon-60x60@2x.png */, - 192D2C5382E745859DA5A120 /* barbie-Icon-60x60@3x.png */, - 4B3BA1D842BE4FF9A3B6EEF4 /* betterneon-Icon-60x60@2x.png */, - 38AE114E9B6249DABC1763AD /* betterneon-Icon-60x60@3x.png */, - 4B44EC4BF6274042A1B32B1E /* macos-Icon-60x60@2x.png */, - 495EDDC8A347424696BC9815 /* macos-Icon-60x60@3x.png */, - 52844129341344C799497601 /* oldios-Icon-60x60@2x.png */, - 9A7F32A990024C5FBF9A8BA0 /* oldios-Icon-60x60@3x.png */, - 672B666D2B5B4DC7917A7D1F /* verscinq-Icon-60x60@2x.png */, - E9E534C5C88743D58FA12DC3 /* verscinq-Icon-60x60@3x.png */, + 50689BA322C44A78B5003000 /* relief-Icon-60x60@2x.png */, + 880B7066DF8D43FE8577FA53 /* relief-Icon-60x60@3x.png */, + BE99E5DF98BF4AD385F2DC17 /* beta-Icon-60x60@2x.png */, + 81122D5C9FCC4EB1A18B9929 /* beta-Icon-60x60@3x.png */, + 5939E1A59F334EC9B42924E2 /* black-Icon-60x60@2x.png */, + C615CCA9CC764F3F802429A2 /* black-Icon-60x60@3x.png */, + A5282314B08D4325BACD3648 /* chip-Icon-60x60@2x.png */, + 7585AD79015A4614A57762F4 /* chip-Icon-60x60@3x.png */, + 1A88EDFDB674480483DA40BF /* cutted-Icon-60x60@2x.png */, + 66F2AC249CAA42E5BFFFC0C5 /* cutted-Icon-60x60@3x.png */, + C4904ADC2C33469AA3CBE11D /* classic-Icon-60x60@2x.png */, + 2B0038150A3D4E4D98F1FC1F /* classic-Icon-60x60@3x.png */, + 69F1E4CC6A794AE1A7439226 /* gold-Icon-60x60@2x.png */, + 2653BAE651234FC99730FE39 /* gold-Icon-60x60@3x.png */, + 9EEF676419374D9CB0679EB7 /* gradient-Icon-60x60@2x.png */, + 306537C054DA4E17AB5C44E6 /* gradient-Icon-60x60@3x.png */, + 23B358831EFD4C56984759F6 /* metal-Icon-60x60@2x.png */, + B9A5F7F1FAF342FBB906EDEF /* metal-Icon-60x60@3x.png */, + 14DDA3D991D14BCD9BA3119D /* neon-Icon-60x60@2x.png */, + 41A124E0267247269CFDAE10 /* neon-Icon-60x60@3x.png */, + 12FC4EE805E040118FE03D2E /* pride-Icon-60x60@2x.png */, + 58C1A10437C1467E833D2DC9 /* pride-Icon-60x60@3x.png */, + 311CDA58D7134E929744D0C5 /* purple-Icon-60x60@2x.png */, + 4B26F223078D4EED8F760BB0 /* purple-Icon-60x60@3x.png */, + AEB06A01647445E3AA5951EC /* rayspurple-Icon-60x60@2x.png */, + 1D71BE31E6754D8AB8D8C1CA /* rayspurple-Icon-60x60@3x.png */, + B66D3767798D42B996C5FADD /* rays-Icon-60x60@2x.png */, + 8A1932A5491641F29D66E283 /* rays-Icon-60x60@3x.png */, + 0AE46211C8D34BBB88297290 /* retro-Icon-60x60@2x.png */, + 4F54853B4B51436EBD20249E /* retro-Icon-60x60@3x.png */, + A3EDBC827C2D4CFC9ADCC9ED /* sparkles-Icon-60x60@2x.png */, + 1F1537A00BD147D2A7C81787 /* sparkles-Icon-60x60@3x.png */, + 8F09700A18C54F11A1D0DA60 /* lightgreen-Icon-60x60@2x.png */, + B2CD9A47830B44578311608C /* lightgreen-Icon-60x60@3x.png */, + C2232213665C43639D032D26 /* backtoschool-Icon-60x60@2x.png */, + 09F6D2D16BB84BFCB7F82C21 /* backtoschool-Icon-60x60@3x.png */, + A322B2C018484A85B0823D46 /* barbie-Icon-60x60@2x.png */, + 08A8C1D127F04A1DBEE7461F /* barbie-Icon-60x60@3x.png */, + F68549047B5B4CC18E6CEDBC /* betterneon-Icon-60x60@2x.png */, + B13B28F100B14B1F94120AF5 /* betterneon-Icon-60x60@3x.png */, + CA3921CE24444E5799CACFE8 /* macos-Icon-60x60@2x.png */, + FAEEFC30BA4542E3A014852F /* macos-Icon-60x60@3x.png */, + EC9970C67C5B480492E3C2C4 /* oldios-Icon-60x60@2x.png */, + 02B5CD3490F94278A3CC1EF8 /* oldios-Icon-60x60@3x.png */, + 8585746E160F4B0C81A1ABA7 /* verscinq-Icon-60x60@2x.png */, + 34E12304924F49E6B66637D3 /* verscinq-Icon-60x60@3x.png */, ); name = DynamicAppIcons; sourceTree = ""; @@ -501,52 +501,52 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, 47E295F32B9B75FF0014C672 /* papillon_ding.wav in Resources */, - 36C144D0BDCC457F8E5074E5 /* relief-Icon-60x60@2x.png in Resources */, - 031906744EBD40E38AE9D5D7 /* relief-Icon-60x60@3x.png in Resources */, - EA4854E06C974276B3C10C48 /* beta-Icon-60x60@2x.png in Resources */, - 655EBC0E85964339A2A6E91A /* beta-Icon-60x60@3x.png in Resources */, - 14294C04C0494BE88E7A921E /* black-Icon-60x60@2x.png in Resources */, - 380A675CD38F43FC96933362 /* black-Icon-60x60@3x.png in Resources */, - 0B5A64F2820F45489D7E352D /* chip-Icon-60x60@2x.png in Resources */, - B1D798BD53724A449A615EE4 /* chip-Icon-60x60@3x.png in Resources */, - 35FBB2C3B69C472E85746A0F /* cutted-Icon-60x60@2x.png in Resources */, - 4861B6974E7D4349B882BCC1 /* cutted-Icon-60x60@3x.png in Resources */, - 5D2A5911792C43A3AF344D7C /* classic-Icon-60x60@2x.png in Resources */, - DC0BBD59195F4C89A19DD917 /* classic-Icon-60x60@3x.png in Resources */, - C6D6E664AFA44DC59472BB72 /* gold-Icon-60x60@2x.png in Resources */, - A212115E638A41A2B136A958 /* gold-Icon-60x60@3x.png in Resources */, - 987E9F26CFBE4CB39BB96EC6 /* gradient-Icon-60x60@2x.png in Resources */, - 63F49A136159431D9C6B2920 /* gradient-Icon-60x60@3x.png in Resources */, - E679D645138D493993943D4C /* metal-Icon-60x60@2x.png in Resources */, - 92BCA684A5D34FFCA3E16A3D /* metal-Icon-60x60@3x.png in Resources */, - C85AEEA6182D4FF08BFF310D /* neon-Icon-60x60@2x.png in Resources */, - 60A10F35C4884B3884B997C6 /* neon-Icon-60x60@3x.png in Resources */, - 88CAE73D3F3B4636BD93B1A8 /* pride-Icon-60x60@2x.png in Resources */, - 5B000C80727246BAA64B2EEA /* pride-Icon-60x60@3x.png in Resources */, - A38EBF31062E4271BBA81192 /* purple-Icon-60x60@2x.png in Resources */, - DBD22842438A4AA09972FEE7 /* purple-Icon-60x60@3x.png in Resources */, - 46DE351F09774FB0BD33C6E6 /* rayspurple-Icon-60x60@2x.png in Resources */, - 02516804F26D42C482AAD14C /* rayspurple-Icon-60x60@3x.png in Resources */, - C2F39DDC4B6D4404BEC390C3 /* rays-Icon-60x60@2x.png in Resources */, - 835FBBB8B9754F06AFC132E5 /* rays-Icon-60x60@3x.png in Resources */, - 0DCC93A0C07B4AD78D219181 /* retro-Icon-60x60@2x.png in Resources */, - 3A05DEDB06C64BD7AC1337E8 /* retro-Icon-60x60@3x.png in Resources */, - 2ED7771F7C8E4F52946A703E /* sparkles-Icon-60x60@2x.png in Resources */, - 226DFB4F33324441AF156B72 /* sparkles-Icon-60x60@3x.png in Resources */, - E4737C8C06D246208C55DEA4 /* lightgreen-Icon-60x60@2x.png in Resources */, - 9F6D139A36974CEA9129DE68 /* lightgreen-Icon-60x60@3x.png in Resources */, - BCA3354417E64433847A69C1 /* backtoschool-Icon-60x60@2x.png in Resources */, - CD1B94E141A94F86B602B6A9 /* backtoschool-Icon-60x60@3x.png in Resources */, - 4D1ED56951794A98BBD5B561 /* barbie-Icon-60x60@2x.png in Resources */, - 2DC51E7D6C82472F9026D630 /* barbie-Icon-60x60@3x.png in Resources */, - 12976D90A95B4B97BCF7DA6F /* betterneon-Icon-60x60@2x.png in Resources */, - 052EA8BAE63946288D031848 /* betterneon-Icon-60x60@3x.png in Resources */, - 1931B81670C14C338E43832E /* macos-Icon-60x60@2x.png in Resources */, - A5A0F4FB0C8A4D47854A206F /* macos-Icon-60x60@3x.png in Resources */, - 9D68F9C36F3D4A828A6F4105 /* oldios-Icon-60x60@2x.png in Resources */, - 22907F47E6DE4F1DBE44EAF8 /* oldios-Icon-60x60@3x.png in Resources */, - 25A3AAB26FAC4432A71F0B99 /* verscinq-Icon-60x60@2x.png in Resources */, - CD578BFD94A74CB086E9CD62 /* verscinq-Icon-60x60@3x.png in Resources */, + 29ECE730080B40A580B2E5BF /* relief-Icon-60x60@2x.png in Resources */, + 0061414B40C248EE93A8C192 /* relief-Icon-60x60@3x.png in Resources */, + 03835D1F13D54CADBA5A8924 /* beta-Icon-60x60@2x.png in Resources */, + F3397183B1A24982BC935127 /* beta-Icon-60x60@3x.png in Resources */, + D1D4F02E8A064589909F2047 /* black-Icon-60x60@2x.png in Resources */, + DBFC7FDCE83C4A68A80A8F5B /* black-Icon-60x60@3x.png in Resources */, + 992FF1196B85410DBCB6E332 /* chip-Icon-60x60@2x.png in Resources */, + D164F444B4C64225A20EF667 /* chip-Icon-60x60@3x.png in Resources */, + 0C60167BB0FA45168A268FDF /* cutted-Icon-60x60@2x.png in Resources */, + 8BDDF3A1AB124110A64CD40D /* cutted-Icon-60x60@3x.png in Resources */, + 0CBF912DC3DF4AA1ADE67B37 /* classic-Icon-60x60@2x.png in Resources */, + DD38A6BB8B824FEB8FE4647B /* classic-Icon-60x60@3x.png in Resources */, + 5FF98FCCFC22415097405414 /* gold-Icon-60x60@2x.png in Resources */, + 7E947167C5124F70AE0969A4 /* gold-Icon-60x60@3x.png in Resources */, + B61574447ED54320AAEC6E59 /* gradient-Icon-60x60@2x.png in Resources */, + 7D7B9AA0B0F24AE9A3CC5BA8 /* gradient-Icon-60x60@3x.png in Resources */, + A31CA97A96F8445DAA8DADB1 /* metal-Icon-60x60@2x.png in Resources */, + B7994D5B42434179BD88B981 /* metal-Icon-60x60@3x.png in Resources */, + DFC1DF7F15B54FF090CB321A /* neon-Icon-60x60@2x.png in Resources */, + 8A5CF142090C4F73A147BA66 /* neon-Icon-60x60@3x.png in Resources */, + C48B38E02DA549EEA7AF445C /* pride-Icon-60x60@2x.png in Resources */, + 422804F6AE9B44FDAA50766E /* pride-Icon-60x60@3x.png in Resources */, + BB2476FC6A1742E0A058ECCC /* purple-Icon-60x60@2x.png in Resources */, + 29160BD474874B8BB985975E /* purple-Icon-60x60@3x.png in Resources */, + E9764B19D2704BF288A46156 /* rayspurple-Icon-60x60@2x.png in Resources */, + F4EDA951C8424C33A8773786 /* rayspurple-Icon-60x60@3x.png in Resources */, + 9BAA5FE2BDF24E0DBFFD65C2 /* rays-Icon-60x60@2x.png in Resources */, + 42A706D5724E4C4CB01877B6 /* rays-Icon-60x60@3x.png in Resources */, + 50D7A7391DE64D708978EE86 /* retro-Icon-60x60@2x.png in Resources */, + EBBA8A49801C4F69847359BC /* retro-Icon-60x60@3x.png in Resources */, + 1B0CB18130DB45B7B04D969C /* sparkles-Icon-60x60@2x.png in Resources */, + DAAC83EA7DEA4B89B149BF44 /* sparkles-Icon-60x60@3x.png in Resources */, + 0A32EDE680FA4F51B0DEA26D /* lightgreen-Icon-60x60@2x.png in Resources */, + E7E9E3CCE10841199C9886B7 /* lightgreen-Icon-60x60@3x.png in Resources */, + 96280EC957514B82885B5A04 /* backtoschool-Icon-60x60@2x.png in Resources */, + 834C78B45A404B749A88E044 /* backtoschool-Icon-60x60@3x.png in Resources */, + B083676D70D9489DA5CD1C23 /* barbie-Icon-60x60@2x.png in Resources */, + E8A80ED046D24E44971089DF /* barbie-Icon-60x60@3x.png in Resources */, + D109F8224F4648DE924FF9BC /* betterneon-Icon-60x60@2x.png in Resources */, + D57D548201D54CBC8CF216D1 /* betterneon-Icon-60x60@3x.png in Resources */, + 2BA75672F2014C50A014E12A /* macos-Icon-60x60@2x.png in Resources */, + 7BD0158BC3364E51BC19489D /* macos-Icon-60x60@3x.png in Resources */, + B37480F2DF364A54816A823B /* oldios-Icon-60x60@2x.png in Resources */, + 13E5744CC0864BC094A3281F /* oldios-Icon-60x60@3x.png in Resources */, + 1626AB64C5A74864B1641184 /* verscinq-Icon-60x60@2x.png in Resources */, + 5BACCF11133D4AEABA079508 /* verscinq-Icon-60x60@3x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Papillon.xcodeproj/xcshareddata/xcschemes/PapillonWidgetExtension.xcscheme b/ios/Papillon.xcodeproj/xcshareddata/xcschemes/PapillonWidgetExtension.xcscheme index c293e4b9..ccb357be 100644 --- a/ios/Papillon.xcodeproj/xcshareddata/xcschemes/PapillonWidgetExtension.xcscheme +++ b/ios/Papillon.xcodeproj/xcshareddata/xcschemes/PapillonWidgetExtension.xcscheme @@ -100,7 +100,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/stacks/SettingsStack.tsx b/stacks/SettingsStack.tsx index cacc73fa..88e92884 100644 --- a/stacks/SettingsStack.tsx +++ b/stacks/SettingsStack.tsx @@ -19,7 +19,7 @@ import AdjustmentsScreen from '../views/Settings/AdjustmentsScreen'; import HeaderSelectScreen from '../views/Settings/HeaderSelectScreen'; import PaymentScreen from '../views/Settings/PaymentScreen'; import NotificationsScreen from '../views/Settings/NotificationsScreen'; -import ConsentScreen from '../views/NewAuthStack/ConsentScreen'; +import ConsentScreenWithoutAcceptation from '../views/ConsentScreenWithoutAcceptation'; function InsetSettings() { const UIColors = GetUIColors(); @@ -92,7 +92,7 @@ function InsetSettings() { name="About" component={AboutScreen} options={{ - headerTitle: 'A propos de Papillon', + headerTitle: 'À propos de Papillon', }} /> diff --git a/views/ConsentScreenWithoutAcceptation.tsx b/views/ConsentScreenWithoutAcceptation.tsx new file mode 100644 index 00000000..f2334c1a --- /dev/null +++ b/views/ConsentScreenWithoutAcceptation.tsx @@ -0,0 +1,104 @@ +import React, { useState, useEffect } from 'react'; +import { Alert, View, StatusBar, ScrollView, useWindowDimensions, Text, Button, TouchableOpacity, Platform } from 'react-native'; + +import GetUIColors from '../utils/GetUIColors'; +import { licenceFile } from './NewAuthStack/LicenceFile'; + +import * as Haptics from 'expo-haptics'; + +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import RenderHtml from 'react-native-render-html'; + +import { AlertTriangle, Check } from 'lucide-react-native'; + +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const ConsentScreenWithoutAcceptation = ({ navigation }: { navigation: any }) => { + const UIColors = GetUIColors(); + const { width } = useWindowDimensions(); + const insets = useSafeAreaInsets(); + + const defaultHTMLTextProps = {}; + + const [canAccept, setCanAccept] = useState(false); + + // header right button + useEffect(() => { + navigation.setOptions({ + headerTitle: Platform.OS ==='ios' ? ' ' : 'Termes & conditions', + headerLeft: Platform.OS ==='ios' ? () => ( + + Termes & conditions + + ) : () => null, + }); + }, [canAccept, UIColors]); + + // haptic when can accept + useEffect(() => { + if (canAccept) { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } + }, [canAccept]); + + return ( + { + if (nativeEvent.layoutMeasurement.height + nativeEvent.contentOffset.y >= nativeEvent.contentSize.height - 50) { + setCanAccept(true); + } + }} + > + { Platform.OS === 'ios' ? ( + + ) : ( + + ) } + + + + + + + + + ); +}; + +export default ConsentScreenWithoutAcceptation; diff --git a/views/ConversationsScreen.tsx b/views/ConversationsScreen.tsx index 3cf12980..e111d30a 100644 --- a/views/ConversationsScreen.tsx +++ b/views/ConversationsScreen.tsx @@ -25,7 +25,7 @@ import NativeList from '../components/NativeList'; import NativeItem from '../components/NativeItem'; import NativeText from '../components/NativeText'; -import { Plus } from 'lucide-react-native'; +import { MessageCircle, Plus, Search } from 'lucide-react-native'; import type { PapillonDiscussion } from '../fetch/types/discussions'; function ConversationsScreen({ navigation }: { @@ -96,7 +96,7 @@ function ConversationsScreen({ navigation }: { // Add search functionality and new conversation button. useLayoutEffect(() => { navigation.setOptions({ - headerTitle: 'Conversations', + headerTitle: 'Messages', headerBackTitle: 'Retour', headerTintColor: UIColors.text, headerShadowVisible: Platform.OS == 'ios', @@ -155,6 +155,7 @@ function ConversationsScreen({ navigation }: { {!loading && (conversations && conversations.length === 0) && ( } title="Aucune conversation" subtitle="Vous n'avez eu aucune conversation." /> @@ -162,6 +163,7 @@ function ConversationsScreen({ navigation }: { {!loading && (conversations && conversations.length > 0) && (filteredConversations && filteredConversations.length === 0) && ( } title="Aucune conversation trouvée" subtitle="Utilisez d'autres mots clés, ceux que vous avez rentrer ne donnent rien." /> diff --git a/views/Cours/LessonScreen.tsx b/views/Cours/LessonScreen.tsx index 1cff243d..ba95e57a 100644 --- a/views/Cours/LessonScreen.tsx +++ b/views/Cours/LessonScreen.tsx @@ -5,12 +5,10 @@ import { ScrollView, StatusBar, Platform, - Alert + Alert, } from 'react-native'; import { useTheme, Text } from 'react-native-paper'; import GetUIColors from '../../utils/GetUIColors'; -import PapillonInsetHeader from '../../components/PapillonInsetHeader'; -import getClosestGradeEmoji from '../../utils/EmojiCoursName'; import formatCoursName from '../../utils/FormatCoursName'; import { getSavedCourseColor } from '../../utils/ColorCoursName'; @@ -26,12 +24,9 @@ import { Hourglass, Clock8, Users, - Palette, - Bell, ClipboardList, BookOpen, File as FileLucide, - Library, TextSelect, } from 'lucide-react-native'; @@ -61,7 +56,10 @@ function LessonScreen({ route, navigation }) { // main color const mainColor = theme.dark ? '#ffffff' : '#444444'; - const color = getSavedCourseColor(lesson.subject.name, lesson.background_color); + const color = getSavedCourseColor( + lesson.subject.name, + lesson.background_color + ); // calculate length of lesson const start = new Date(lesson.start); @@ -96,228 +94,228 @@ function LessonScreen({ route, navigation }) { React.useLayoutEffect(() => { navigation.setOptions({ headerTitle: () => ( - - - {formatCoursName(lesson.subject.name)} - - - - {(lesson.rooms.length > 0 && !lesson.rooms[0].toLowerCase().includes('salle') ? 'salle ' : '') + (lesson.rooms.length > 0 ? lesson.rooms.join(', ') : 'inconnue') + ' - '} - - - {lesson.status?.toLowerCase() || lengthString + ' de cours'} - - - + + + {formatCoursName(lesson.subject.name)} + + + + {(lesson.rooms.length > 0 && + !lesson.rooms[0].toLowerCase().includes('salle') + ? 'salle ' + : '') + + (lesson.rooms.length > 0 + ? lesson.rooms.join(', ') + : 'inconnue') + + ' - '} + + + {lesson.status?.toLowerCase() || lengthString + ' de cours'} + + + ), }); }, [navigation, theme, lesson.subject.name, UIColors]); return ( - - - { Platform.OS === 'ios' ? ( - + + {Platform.OS === 'ios' ? ( + ) : ( - + )} - - - - } - > + + + }> - Salle{lesson.rooms.length > 1 ? 's' : ''} de cours + Salle{lesson.rooms.length > 1 ? 's' : ''} de cours {lesson.rooms.join(', ') || 'Non spécifié'} - } - > + }> - Professeur{lesson.teachers.length > 1 ? 's' : ''} + Professeur{lesson.teachers.length > 1 ? 's' : ''} {lesson.teachers.join(', ') || 'Non spécifié'} - { lesson.group_names && lesson.group_names.length > 0 ? ( - } - > + {lesson.group_names && lesson.group_names.length > 0 ? ( + }> - Groupe{lesson.group_names.length > 1 ? 's' : ''} + Groupe{lesson.group_names.length > 1 ? 's' : ''} {lesson.group_names.join(', ') || 'Non spécifié'} - ) : } + ) : ( + + )} - { lesson.status ? ( - + {lesson.status ? ( + } + leading={ + + } backgroundColor={lesson.is_cancelled ? '#B42828' : null} > - - Statut du cours + + Statut du cours - + {lesson.status} - { lesson.memo ? ( - } - > - - Commentaire - - - {lesson.memo} - + {lesson.memo ? ( + }> + Commentaire + {lesson.memo} - ) : } + ) : ( + + )} - ) : null } + ) : null} - - } - > - - Durée du cours - - - {lengthString} - + + }> + Durée du cours + {lengthString} - } - > - - Date du cours - - - {dateCours} - + }> + Date du cours + {dateCours} - } - > - - Début du cours - - - {startStr} - + }> + Début du cours + {startStr} - } - > - - Fin du cours - - - {endStr} - + }> + Fin du cours + {endStr} - { lesson.content && lesson.content.description ? ( - - } - > - - Titre - + {lesson.content && lesson.content.description ? ( + + }> + Titre {lesson.content.title || 'Non spécifié'} - } - > - - Description - + }> + Description {lesson.content.description || 'Non spécifié'} - ) : } - - { lesson.content && lesson.content.files && lesson.content.files.length > 0 ? ( - - { lesson.content.files.map((file, index) => ( - } - onPress={() => { - if (file.url.startsWith('http')) { - openURL(file.url); - } - else { - Alert.alert( - 'Impossible d\'ouvrir le fichier', - 'Le fichier n\'est pas disponible en ligne.', - [ - { - text: 'OK', - style: 'cancel', - }, - ], - { cancelable: true } - ); - } - }} - > - - {file.name} - - - {file.url} - - - )) } - - ) : } + ) : ( + + )} - + {lesson.content && + lesson.content.files && + lesson.content.files.length > 0 ? ( + + {lesson.content.files.map((file, index) => ( + } + onPress={() => { + if (file.url.startsWith('http')) { + openURL(file.url); + } else { + Alert.alert( + 'Impossible d\'ouvrir le fichier', + 'Le fichier n\'est pas disponible en ligne.', + [ + { + text: 'OK', + style: 'cancel', + }, + ], + { cancelable: true } + ); + } + }} + > + + {file.name} + + + {file.url} + + + ))} + + ) : ( + + )} + ); } const styles = StyleSheet.create({ container: { - flex: 1 + flex: 1, }, headerEmojiContainer: { diff --git a/views/DevoirsScreen.tsx b/views/DevoirsScreen.tsx index 89979219..9d27450a 100644 --- a/views/DevoirsScreen.tsx +++ b/views/DevoirsScreen.tsx @@ -26,7 +26,8 @@ import { convert as convertHTML } from 'html-to-text'; import { File, Plus, - ExternalLink + ExternalLink, + FileUp } from 'lucide-react-native'; import { getSavedCourseColor } from '../utils/ColorCoursName'; @@ -36,6 +37,8 @@ import GetUIColors from '../utils/GetUIColors'; import { useAppContext } from '../utils/AppContext'; import NativeText from '../components/NativeText'; +import {PronoteApiHomeworkReturnType } from 'pawnote'; + import * as WebBrowser from 'expo-web-browser'; import type { PapillonHomework } from '../fetch/types/homework'; import { BlurView } from 'expo-blur'; @@ -312,6 +315,7 @@ function Hwitem({ homework, openURL, navigation }: { }, []); if (!homework) return; + return ( + + + + } + trailing={ + homework.return && homework.return.type == PronoteApiHomeworkReturnType.FILE_UPLOAD && ( + + ) } onPress={() => { navigation.navigate('Devoir', { homeworkLocalID: homework.localID }); @@ -363,12 +374,15 @@ function Hwitem({ homework, openURL, navigation }: { {homework.subject.name.toUpperCase()} + + + + + + {convertHTML(homework.description.replace('\n', ' '), { wordwrap: 130 })} + - - {convertHTML(homework.description.replace('\n', ' '), { wordwrap: 130 })} - - {homework.attachments.map((file, index) => ( ( - Platform.OS === 'ios' ? ( - navigation.goBack()} style={styles.iosBack}> - - - ) : null + Platform.OS === 'ios' ? ( + Platform.isPad ? ( + navigation.goBack()} style={styles.iosBack}> + + + ) : ( + navigation.goBack()} style={styles.iosBack}> + + + )) : null ), }); }, [navigation, grade]); @@ -194,6 +191,9 @@ function GradeView({ route, navigation }) { .{valueBottom} + + /{grade.grade.out_of.value} + )} @@ -209,9 +209,6 @@ function GradeView({ route, navigation }) { )} )} - - /{grade.grade.out_of.value} - - - {parseFloat( - (grade.grade.value.value / grade.grade.out_of.value) * 20 - ).toFixed(2)} - - /20 + + {grade.grade.value.significant === true && (<> + {grade.grade.value.type[0] == '1' ? ( + + Abs. + + ) : ( + + N.not + + )} + )} + + {grade.grade.value.significant === false && (<> + + {parseFloat( + (grade.grade.value.value / grade.grade.out_of.value) * 20 + ).toFixed(2)} + + /20 + )} } > @@ -350,7 +362,11 @@ function GradeView({ route, navigation }) { } trailing={ - avgInfluence > 0 ? ( + avgInfluence == 0 ? ( + + {parseFloat(avgInfluence)} pts + + ) : avgInfluence > 0 ? ( + {parseFloat(avgInfluence).toFixed(2)} pts @@ -390,7 +406,11 @@ function GradeView({ route, navigation }) { } trailing={ - avgPercentInfluence > 0 ? ( + avgPercentInfluence == 0 ? ( + + {parseFloat(avgPercentInfluence)} % + + ) : avgPercentInfluence > 0 ? ( + {parseFloat(avgPercentInfluence).toFixed(2)} % @@ -420,7 +440,11 @@ function GradeView({ route, navigation }) { } trailing={ - (grade.grade.value.value - grade.grade.average.value).toFixed(2) > 0 ? ( + isNaN((grade.grade.value.value - grade.grade.average.value).toFixed(2)) || (grade.grade.value.value - grade.grade.average.value).toFixed(2) == 0 ? ( + + 0 pts + + ) : (grade.grade.value.value - grade.grade.average.value).toFixed(2) > 0 ? ( + {(grade.grade.value.value - grade.grade.average.value).toFixed(2)} pts diff --git a/views/GradesScreen.tsx b/views/GradesScreen.tsx index 72b68c29..e751df9c 100644 --- a/views/GradesScreen.tsx +++ b/views/GradesScreen.tsx @@ -1,1707 +1,1128 @@ -import React, { useEffect } from 'react'; -import { - Animated, - View, - ScrollView, - StyleSheet, - StatusBar, - Platform, - Pressable, - TouchableOpacity, - RefreshControl, - Modal, - Dimensions, -} from 'react-native'; -import { useTheme, Text } from 'react-native-paper'; +import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import { Animated, ActivityIndicator, StatusBar, View, Dimensions, StyleSheet, ScrollView, TouchableOpacity, RefreshControl, Easing, Platform, Pressable } from 'react-native'; -import { SFSymbol } from 'react-native-sfsymbols'; +// Custom imports +import GetUIColors from '../utils/GetUIColors'; +import { useAppContext } from '../utils/AppContext'; import PapillonInsetHeader from '../components/PapillonInsetHeader'; +import { SFSymbol } from 'react-native-sfsymbols'; -import AlertBottomSheet from '../interface/AlertBottomSheet'; - -import { BlurView } from 'expo-blur'; -import { ContextMenuButton} from 'react-native-ios-context-menu'; +import NativeList from '../components/NativeList'; +import NativeItem from '../components/NativeItem'; +import NativeText from '../components/NativeText'; -import LineChart from 'react-native-simple-line-chart'; +import { getSavedCourseColor } from '../utils/ColorCoursName'; +import getClosestGradeEmoji from '../utils/EmojiCoursName'; +import formatCoursName from '../utils/FormatCoursName'; -import { BarChart3, Users2, TrendingDown, TrendingUp, Info, AlertTriangle } from 'lucide-react-native'; +import { useActionSheet } from '@expo/react-native-action-sheet'; -import { PressableScale } from 'react-native-pressable-scale'; +// Icons +import { Users2, File, TrendingDown, TrendingUp, AlertTriangle, MoreVertical } from 'lucide-react-native'; +import { Stats } from '../interface/icons/PapillonIcons'; -import { useActionSheet } from '@expo/react-native-action-sheet'; +// Plugins +import { ContextMenuButton } from 'react-native-ios-context-menu'; +import LineChart from 'react-native-simple-line-chart'; +import { BlurView } from 'expo-blur'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { PressableScale } from 'react-native-pressable-scale'; -import { getSavedCourseColor } from '../utils/ColorCoursName'; -import getClosestGradeEmoji from '../utils/EmojiCoursName'; -import formatCoursName from '../utils/FormatCoursName'; -import GetUIColors from '../utils/GetUIColors'; -import { useAppContext } from '../utils/AppContext'; -import NativeList from '../components/NativeList'; -import NativeItem from '../components/NativeItem'; -import NativeText from '../components/NativeText'; +// Interfaces +interface UIaverage { + name: string, + value: number, + icon: React.ReactElement, +} + +export interface GradeSettings { + scale: number, +} + +interface PapillonAveragesOverTime { + date: Date, + value: number, +} -import * as StoreReview from 'expo-store-review'; +// Pawnote import { PapillonPeriod } from '../fetch/types/period'; import { PapillonGrades, PapillonGradesViewAverages } from '../fetch/types/grades'; -import { PapillonSubject } from '../fetch/types/subject'; -import { PronoteApiGradeType } from 'pawnote'; -import { formatPapillonGradeValue } from '../utils/grades/format'; -function GradesScreen({ navigation }) { - const theme = useTheme(); +import { calculateSubjectAverage } from '../utils/grades/averages'; +import PapillonLoading from '../components/PapillonLoading'; + +const GradesScreen = ({ navigation }: { + navigation: any // TODO +}) => { const UIColors = GetUIColors(); const appContext = useAppContext(); const insets = useSafeAreaInsets(); const { showActionSheetWithOptions } = useActionSheet(); - - const [state, setState] = React.useState<{ - latestGrades: PapillonGrades['grades'], - averagesData: PapillonGradesViewAverages | null, - subjectsList: PapillonSubject[], - avgChartData: Array<{ x: number, y: number }>, - hasSimulatedGrades: boolean, - calculatedClassAvg: boolean, - calculatedAvg: boolean, - }>({ - latestGrades: [], - averagesData: null, - subjectsList: [], - avgChartData: [], - hasSimulatedGrades: false, - calculatedClassAvg: false, - calculatedAvg: false, + + // Data + const [isLoading, setIsLoading] = React.useState(false); + const [isRefreshing, setIsRefreshing] = React.useState(false); + const [periods, setPeriods] = React.useState([]); + const [selectedPeriod, setSelectedPeriod] = React.useState(null); + const [grades, setGrades] = React.useState([]); + const [allGrades, setAllGrades] = React.useState(null); + const [latestGrades, setLatestGrades] = React.useState(null); + const [averages, setAverages] = React.useState({} as PapillonGradesViewAverages); + const [pronoteStudentAverage, setPronoteStudentAverage] = React.useState(null); + const [pronoteClassAverage, setPronoteClassAverage] = React.useState(null); + const [averagesOverTime, setAveragesOverTime] = React.useState([]); + const [classAveragesOverTime, setClassAveragesOverTime] = React.useState([]); + const [chartLines, setChartLines] = React.useState(null); + const [chartPoint, setChartPoint] = React.useState(null); + const [openedSettings, setOpenedSettings] = React.useState(true); + + // Constants + const [gradeSettings, setGradeSettings] = React.useState({ + scale: 20, }); - - // No need to be reactive, so we just write it as is. - let allGradesNonReactive: PapillonGrades['grades'] = []; - const [moyReelleAlert, setMoyReelleAlert] = React.useState(false); - const [moyClasseReelleAlert, setClasseReelleAlert] = React.useState(false); - const [moyClasseBasseAlert, setClasseBasseAlert] = React.useState(false); - const [moyClasseHauteAlert, setClasseHauteAlert] = React.useState(false); + // Update gradeSettings when focused + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + AsyncStorage.getItem('gradeSettings').then((value) => { + if (value) { + setGradeSettings(JSON.parse(value)); + } + }); - const [isLoading, setIsLoading] = React.useState(false); - const [isHeadLoading, setHeadLoading] = React.useState(false); + if (openedSettings) setOpenedSettings(false); + }); - const [selectedCourse, setSelectedCourse] = React.useState(null); - const [courseModalVisible, setCourseModalVisible] = React.useState(false); + return unsubscribe; + }, [navigation, gradeSettings, openedSettings]); + // UI arrays + const [UIaverage, setUIaverage] = React.useState([]); - const [gradeOpened, setGradeOpened] = React.useState(false); - const [selectedPeriod, setSelectedPeriod] = React.useState(null); - - const [periods, setPeriods] = React.useState([]); + // Update UIaverage when averages change + React.useEffect(() => { + setUIaverage([ + { + name: 'Moyenne groupe', + value: pronoteClassAverage ? pronoteClassAverage : (averages.group || 0), + icon: , + }, + { + name: 'Moyenne max', + value: averages.max || 0, + icon: , + }, + { + name: 'Moyenne min', + value: averages.min || 0, + icon: , + }, + ]); + }, [averages, UIColors.text, pronoteClassAverage]); - const yOffset = new Animated.Value(0); + // Update chartLines when averagesOverTime change + React.useEffect(() => { + const createLineData = (averages: PapillonAveragesOverTime[]) => averages.map(average => ({ + x: new Date(average.date).getTime(), + y: average.value, + })); + + const linesSettings = { + lineColor: UIColors.primary, + curve: 'monotone', + endPointConfig: { + color: UIColors.primary, + radius: 8, + animated: true, + }, + lineWidth: 3, + activePointConfig: { + color: UIColors.primary, + borderColor: UIColors.element, + radius: 7, + borderWidth: 0, + showVerticalLine: true, + verticalLineColor: UIColors.text, + verticalLineOpacity: 0.2, + verticalLineWidth: 1.5, + } + }; - const scrollHandler = Animated.event( - [{ nativeEvent: { contentOffset: { y: yOffset } } }], - { useNativeDriver: false } - ); + const studentLinesData = createLineData(averagesOverTime); + const classLinesData = createLineData(classAveragesOverTime); - const headerOpacity = yOffset.interpolate({ - inputRange: [-75, -60], - outputRange: [0, 1], - extrapolate: 'clamp', - }); + const lines = [ + { + ...linesSettings, + key: 'student2', + data: studentLinesData, + }, + { + ...linesSettings, + key: 'class', + lineColor: UIColors.border, + lineWidth: 2, + trailingOpacity: 0, + endPointConfig: { + radius: 0, + animated: false, + color: 'transparent', + }, + activePointConfig: { + radius: 0, + borderWidth: 0, + showVerticalLine: false, + color: 'transparent', + borderColor: 'transparent', + }, + data: classLinesData, + }, + { + ...linesSettings, + key: 'student', + data: studentLinesData, + } + ]; + + setChartLines(lines); + }, [averagesOverTime, classAveragesOverTime, UIColors.text, UIColors.border, UIColors.primary, UIColors.element]); - const sum = (arr: number[]) => arr.reduce((a,b) => a+b, 0); + async function getPeriodsFromAPI (): Promise { + return appContext.dataProvider!.getUser().then((user) => { + const periods = user.periodes.grades; - function calculateSubjectAverage (grades: PapillonGrades['grades']): PapillonSubject['averages'] { - let totalGradesInSubject = 0; + setPeriods(periods); + const currentPeriod = periods.find((period) => period.actual)!; - type Values = Array<[value: number, outOf: number]>; + setSelectedPeriod(currentPeriod.name); + return currentPeriod; + }); + } - const studentValues: Values = []; - const classValues: Values = []; - const minValues: Values = []; - const maxValues: Values = []; + function getGradesFromAPI (force = false, periodName = selectedPeriod): Promise { + if (!isRefreshing) { + setIsLoading(true); + } - for (let i = 0; i < grades.length; i++) { - const grade = grades[i].grade; + try { + if (appContext.dataProvider && periodName) { + return appContext.dataProvider.getGrades(periodName, force).then((grades) => { + if (grades) { + return parseGrades(grades); + } + else { + return Promise.reject('No grades'); + } + }); + } + } catch (error) { + console.error(error); + } finally { + setIsLoading(false); + setIsRefreshing(false); + } + } - let studentValue: number | undefined; - let classValue: number | undefined; - let minValue: number | undefined; - let maxValue: number | undefined; + function addGradesToSubject(grades: PapillonGrades): Promise { + const data = grades.averages.map(average => ({ ...average, grades: [] as PapillonGrades['grades'] })); - // Student's grade value. - if (grade.value.significant) { // TODO: Handle for Skolengo ? - if (grade.value.type === PronoteApiGradeType.AbsentZero || grade.value.type === PronoteApiGradeType.UnreturnedZero) { - studentValue = 0; - } - } else studentValue = grade.value.value; + grades.grades.forEach((grade) => { + const subject = data.find((subject) => subject.subject.id === grade.subject.id); + if (subject) { + subject.grades.push(grade); + grade.background_color = subject.color; + } + }); - // Class' grade value. - if (grade.average.significant) { // TODO: Handle for Skolengo ? - if (grade.average.type === PronoteApiGradeType.AbsentZero || grade.average.type === PronoteApiGradeType.UnreturnedZero) { - classValue = 0; - } - } else classValue = grade.average.value; - - // Minimum grade value. - if (grade.min.significant) { // TODO: Handle for Skolengo ? - if (grade.min.type === PronoteApiGradeType.AbsentZero || grade.min.type === PronoteApiGradeType.UnreturnedZero) { - minValue = 0; - } - } else minValue = grade.min.value; - - // Maximum grade value. - if (grade.max.significant) { // TODO: Handle for Skolengo ? - if (grade.max.type === PronoteApiGradeType.AbsentZero || grade.max.type === PronoteApiGradeType.UnreturnedZero) { - maxValue = 0; - } - } else maxValue = grade.max.value; + data.forEach((subject) => { + subject.grades.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }); - // Useless grade, go to next grade. - if (grade.coefficient === 0) continue; - if (grade.out_of.significant) continue; + data.sort((a, b) => a.subject.name.localeCompare(b.subject.name)); - totalGradesInSubject += 1; - const outOf = grade.out_of.value * grade.coefficient; - - if (typeof studentValue !== 'undefined') { - studentValues.push([studentValue * grade.coefficient, outOf]); - } + if(grades.class_overall_average?.value > 0) { + setPronoteClassAverage(grades.class_overall_average.value); + } - if (typeof classValue !== 'undefined') { - classValues.push([classValue * grade.coefficient, outOf]); - } + if(grades.overall_average?.value > 0) { + setPronoteStudentAverage(grades.overall_average.value); + } - if (typeof minValue !== 'undefined') { - minValues.push([minValue * grade.coefficient, outOf]); - } + setGrades(data); + setAllGrades(grades.grades); - if (typeof maxValue !== 'undefined') { - maxValues.push([maxValue * grade.coefficient, outOf]); - } - } + const latest = [...grades.grades].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 10); + setLatestGrades(latest); + } - if (totalGradesInSubject === 0) { + // Calculate averages over time + async function calculateAveragesOverTime (grades: PapillonGrades[], type= 'value'): Promise { + // map grades to data with date and average value + const data = await Promise.all(grades.map(async (grade, i) => { + const gradesUntil = grades.slice(0, i + 1); + const average = await calculateSubjectAverage(gradesUntil, type, gradeSettings.scale); return { - average: -1, - class_average: -1, - min: -1, - max: -1, - out_of: 20, + date: new Date(grade.date), + value: average, }; - } + })); - const student = sum(studentValues.map(v => v[0])) / sum(studentValues.map(v => v[1])) * 20; - const classAverage = sum(classValues.map(v => v[0])) / sum(classValues.map(v => v[1])) * 20; - const min = sum(minValues.map(v => v[0])) / sum(minValues.map(v => v[1])) * 20; - const max = sum(maxValues.map(v => v[0])) / sum(maxValues.map(v => v[1])) * 20; - - return { - average: student, - class_average: classAverage, - min: min, - max: max, - out_of: 20, - }; - } + // sort by date + data.sort((a, b) => a.date.getTime() - b.date.getTime()); - function calculateAveragesFromGrades (grades: PapillonGrades['grades']): PapillonSubject['averages'] { - const subjects: PapillonSubject[] = []; - - // 1. Read subjects from grades. - grades.forEach((grade) => { - const subjectIndex = subjects.findIndex(({ subject }) => subject.id === grade.subject.id); - if (subjectIndex !== -1) { // found, push to grades of this subject. - subjects[subjectIndex].grades.push(grade); - } else { // insert the subject in the array. - subjects.push({ - name: grade.subject.name, - subject: grade.subject, - grades: [grade], - averages: { // default values before we get to step 2. - average: -1, - class_average: -1, - min: -1, - max: -1, - out_of: 20, - }, - }); - } - }); + return data; + } - // 2. Calculate averages for each subject. - subjects.forEach((subject) => { - subject.averages = calculateSubjectAverage(subject.grades); + // Estimate averages + async function estimatedStudentAverages (grades: PapillonGrades): Promise { + const [student, group, max, min] = await Promise.all([ + calculateSubjectAverage(grades, 'value', gradeSettings.scale), + calculateSubjectAverage(grades, 'average', gradeSettings.scale), + calculateSubjectAverage(grades, 'max', gradeSettings.scale), + calculateSubjectAverage(grades, 'min', gradeSettings.scale) + ]); + + setAverages({ + student, + group, + max, + min, }); + } - // 3. Calculate averages of all subjects - let student = 0; - let classAverage = 0; - let min = 0; - let max = 0; - - let count = 0; + // Estimate averages over time + async function estimateAveragesOverTime (grades: PapillonGrades): Promise { + const [averagesOverTime, classAveragesOverTime] = await Promise.all([ + calculateAveragesOverTime(grades, 'value'), + calculateAveragesOverTime(grades, 'average') + ]); + setAveragesOverTime(averagesOverTime); + setClassAveragesOverTime(classAveragesOverTime); + } - for (let i = 0; i < subjects.length; i++) { - if (subjects[i].averages.average === -1) continue; + // Parse grades + async function parseGrades (grades: PapillonGrades): Promise { + addGradesToSubject(grades); + estimatedStudentAverages(grades?.grades); + estimateAveragesOverTime(grades?.grades); + } - student += subjects[i].averages.average; - classAverage += subjects[i].averages.class_average; - min += subjects[i].averages.min; - max += subjects[i].averages.max; + function androidPeriodChangePicker () { + const options = periods.map((item) => item.name); + options.push('Annuler'); + const cancelButtonIndex = options.length - 1; - count += 1; - } + showActionSheetWithOptions( + { + options, + cancelButtonIndex : cancelButtonIndex, + tintColor: UIColors.primary, + }, + (buttonIndex) => { + if (typeof buttonIndex !== 'undefined' && buttonIndex !== cancelButtonIndex) { + setSelectedPeriod(periods[buttonIndex].name); + } + } + ); + } - student = student / count; - classAverage = classAverage / count; - min = min / count; - max = max / count; + // On mount + React.useEffect(() => { getPeriodsFromAPI(); }, []); + React.useEffect(() => { getGradesFromAPI(false, selectedPeriod); }, [selectedPeriod]); - if (isNaN(student)) { - student = 0; - } - if (isNaN(classAverage)) { - classAverage = 0; - } - if (isNaN(min)) { - min = 0; - } - if (isNaN(max)) { - max = 0; - } + // Header animation + const yOffset = new Animated.Value(0); - return { - average: student, - class_average: classAverage, - min: min, - max: max, - out_of: 20, - }; - } + const scrollHandler = Animated.event( + [{ nativeEvent: { contentOffset: { y: yOffset } } }], + { useNativeDriver: false } + ); - // Add buttons to navigation header. + const headerOpacity = yOffset.interpolate({ + inputRange: [-40, 0], + outputRange: [0, 1], + extrapolate: 'clamp', + }); + + const HeaderRight = ({ + navigation, + periods, + selectedPeriod, + UIColors, + isLoading, + androidPeriodChangePicker, + setOpenedSettings + }: { + navigation: any, + periods: PapillonPeriod[] + selectedPeriod: string | null + }) => { + const menuConfig = useMemo(() => ({ + menuTitle: '', + menuItems: periods.map((period) => ({ + actionKey: period.name, + actionTitle: period.name, + menuState: selectedPeriod === period.name ? 'on' : 'off', + })), + }), [periods, selectedPeriod]); + + return ( + + { isLoading && ( + + )} + { + if (nativeEvent.actionKey === selectedPeriod) return; + setSelectedPeriod(nativeEvent.actionKey); + }} + > + { + if (Platform.OS !== 'ios') { + androidPeriodChangePicker(); + } + }} + > + + {selectedPeriod} + + + + { + navigation.navigate('GradesSettings'); + setOpenedSettings(true); + }} + > + + + + ); + }; + + // Change header title React.useLayoutEffect(() => { navigation.setOptions({ - headerLeft: Platform.OS === 'ios' && () => ( - } - title="Notes" - color="#A84700" - /> - ), - headerTitle: Platform.OS === 'ios' ? '' : 'Notes', + headerTitle : 'Notes', + headerRight: () => , + headerShadowVisible: false, headerTransparent: Platform.OS === 'ios' ? true : false, headerStyle: Platform.OS === 'android' ? { backgroundColor: UIColors.background, elevation: 0, } : undefined, - headerBackground: Platform.OS === 'ios' ? () => ( + }); + }, [navigation, periods, selectedPeriod, UIColors, headerOpacity, setOpenedSettings, showActionSheetWithOptions]); + + return ( + <> + { Platform.OS === 'ios' && ( - ) : undefined, - headerRight: () => ( - - { - return { - actionKey: period.name, - actionTitle: period.name, - menuState : selectedPeriod?.name === period.name ? 'on' : 'off', - };² - }), - }} - onPressMenuItem={({nativeEvent}) => { - setSelectedPeriod(periods.find((period) => period.name === nativeEvent.actionKey) ?? null); + )} + { + setIsRefreshing(true); getGradesFromAPI(true); }} - > - { - if (Platform.OS !== 'ios') { - openPeriodSelectionSheet(); - } - }} - style={styles.periodButtonContainer} - > - - {selectedPeriod?.name || ''} - - - - - {/* navigation.navigate('ModalGradesSimulator')} - > - - */} - - ), - }); - }, [navigation, selectedPeriod, isLoading, UIColors]); - - const openPeriodSelectionSheet = (): void => { - const options = periods.map((period) => period.name); - options.push('Annuler'); - - showActionSheetWithOptions( - { - title: 'Changer de période', - message: 'Sélectionnez la période de votre choix', - options, - tintColor: UIColors.primary, - cancelButtonIndex: options.length - 1, - containerStyle: { - paddingBottom: insets.bottom, - backgroundColor: UIColors.elementHigh, - }, - textStyle: { - color: UIColors.text, - }, - titleTextStyle: { - color: UIColors.text, - fontWeight: 'bold', - }, - messageTextStyle: { - color: UIColors.text, - }, - }, - (selectedIndex) => { - // If the selected was "Annuler", then do nothing. - if (selectedIndex === options.length - 1) return; - // Make sure we're using a number. - if (typeof selectedIndex !== 'number') return; - - const selectedPeriod = periods[selectedIndex]; - setSelectedPeriod(selectedPeriod); - getGradesFromAPI(true); - } - ); - }; - - /** - * Read periods from the API. - * - * Defines the following states : - * - periods (setPeriods) - * - remainingPeriods (setRemainingPeriods) - NOTE: Unused ? - * - selectedPeriod (setSelectedPeriod) - */ - async function getPeriodsFromAPI (): Promise { - console.log('get periods ?'); - const allPeriods = await appContext.dataProvider!.getPeriods(); - const firstPeriod = allPeriods[0]; // TODO: Define `actual` on the connector. + /> + } + > + - let periods: PapillonPeriod[] = []; - periods = allPeriods; + {grades.length === 0 && ( + } + /> + )} - setPeriods(periods); - - // TODO: Select current by default. - setSelectedPeriod(firstPeriod); - return firstPeriod; - } + { averages.student && averages.student > 0 && ( + + )} - function calculateAverage (grades: PapillonGrades['grades'], isClass: boolean): number { - let sumOfGrades = 0; - let sumOfCoefficients = 0; - - for (const { grade } of grades) { - if (!grade.value.significant && !grade.average.significant && !grade.out_of.significant) { - if (isClass) { - const correctedClassValue = grade.average.value / grade.out_of.value * 20; - sumOfGrades += correctedClassValue * grade.coefficient; - } else { - const correctedValue = grade.value.value / grade.out_of.value * 20; - sumOfGrades += correctedValue * grade.coefficient; - } - - sumOfCoefficients += grade.coefficient; - } - } + { latestGrades && latestGrades.length > 0 && ( + + )} - return sumOfGrades / sumOfCoefficients; - } + { Platform.OS === 'android' && ( + + )} - async function parseGrades (overview: PapillonGrades): Promise { - const subjects: PapillonSubject[] = []; + { averages.student && averages.student > 0 && ( + + )} - /** - * Whether the user set custom grades. - * We store this to know if the average displayed is real or not. - */ - let hasCustomGrades = false; + - // Add custom grades in the list, if exist. - const storedCustomGrades = await AsyncStorage.getItem('custom-grades'); - if (storedCustomGrades) { - const customGrades = JSON.parse(storedCustomGrades) as PapillonGrades['grades']; - hasCustomGrades = customGrades.length > 0; + - for (const grade of customGrades) { - const newGrade: PapillonGrades['grades'][number] = { - ...grade, - isSimulated: true, - }; - - // We don't add the grade if it already exists (!== -1) - const alreadyExistsIndex = overview.grades.findIndex((item) => item.id === grade.id); - if (alreadyExistsIndex !== -1) continue; - - overview.grades.push(newGrade); - } + + + ); +}; - // Update averages value of subjects, due to custom grades. - for (const grade of customGrades) { - const subject = overview.averages.find((average) => average.subject.name === grade.subject.name); - if (!subject) continue; - - const filteredGrades = overview.grades.filter((grade) => grade.subject.name === subject.subject.name); - subject.average = { significant: false, value: calculateAverage(filteredGrades, false) }; - subject.class_average = { significant: false, value: calculateAverage(filteredGrades, true) }; - } - } +const LatestGradesList = React.memo(({ isLoading, grades, allGrades, gradeSettings, navigation }) => { + const UIColors = GetUIColors(null, 'ios'); - allGradesNonReactive = overview.grades; - - allGradesNonReactive.forEach((grade) => { - const subjectIndex = subjects.findIndex((subject) => subject.id === grade.subject.id); - if (subjectIndex !== -1) { - subjects[subjectIndex].grades.push(grade); - } else { - subjects.push({ - id : grade.subject.id, - name: grade.subject.name, - parsedName: { - name: grade.subject.name.split(' > ')[0], - sub: grade.subject.name.split(' > ').length > 0 ? grade.subject.name.split(' > ')[1] : void 0, - }, - - subject: grade.subject, - grades: [grade], - - // Default values, will be replaced below. - averages: { - average: -1, - class_average: -1, - max: -1, - min: -1, - out_of: -1 - }, - }); + const showGrade = useCallback((grade) => { + navigation.navigate('Grade', { + grade, + allGrades: allGrades, + }); + }, [allGrades, navigation]); + + return (<> + + + + + + + {grades.map((grade, index) => ( + showGrade(grade)} + key={index} + style={[ + subjectStyles.smallGrade, + { + backgroundColor: UIColors.element, + }, + Platform.OS === 'android' && { + borderColor: UIColors.border, + borderWidth: 0.5, + shadowColor: '#00000055', + elevation: 3, + } + ]} + > + + + + {getClosestGradeEmoji(grade.subject.name)} + + + {formatCoursName(grade.subject.name)} + + + + + + {grade.description || 'Aucune description'} + + + {new Date(grade.date).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric', year: 'numeric' })} + + + + { grade.grade.value?.value ? ( + + {((grade.grade.value?.value / grade.grade.out_of.value) * gradeSettings.scale).toFixed(2)} + + ) : ( + + N.not + + )} + + /{gradeSettings.scale.toFixed(0)} + + + + + ))} + + + ); +}); + +const GradesList = React.memo(({ grades, allGrades, gradeSettings, navigation, UIColors }) => { + const showGrade = useCallback((grade) => { + navigation.navigate('Grade', { + grade, + allGrades: allGrades, }); - - overview.averages.forEach((average) => { - const subject = subjects.find((subject) => subject.id === average.subject.id); - if (!subject) return; - - average.color = getSavedCourseColor(average.subject.name.split(' > ')[0], average.color); - subject.averages = { - average: average.average.significant ? -1 : average.average.value, - class_average: average.class_average.significant ? -1 : average.class_average.value, - min: average.min.significant ? -1 : average.min.value, - max: average.max.significant ? -1 : average.max.value, - out_of: average.out_of.significant ? -1 : average.out_of.value, - }; + }, [navigation, allGrades]); - allGradesNonReactive.forEach((grade) => { - if (grade.subject.name === subject.name) { - grade.color = average.color; - } - }); + return ( + + {grades.map((subject, index) => { + const formattedCourseName = formatCoursName(subject.subject.name); + const backgroundColor = getSavedCourseColor(subject.subject.name, subject.color); + const gradeValue = ((subject.average.value / subject.out_of.value) * gradeSettings.scale).toFixed(2); + const gradeScale = gradeSettings.scale.toFixed(0); + + return ( + + + + + {formattedCourseName} + + - subject.grades.forEach((grade) => { - grade.color = average.color; - }); - }); + + + {gradeValue !== 'NaN' ? gradeValue : 'N.éval'} + + + /{gradeScale} + + + + + {subject.grades.map((grade, index) => { + const gradeEmoji = getClosestGradeEmoji(subject.subject.name); + const gradeValue = grade.grade.value?.value?.toFixed(2) || 'N.not'; + const gradeScale = grade.grade.out_of.value.toFixed(0); + const gradeDescription = grade.description || 'Aucune description'; + const gradeDate = new Date(grade.date).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric', year: 'numeric' }); + + return ( + showGrade(grade)} + leading={ + + {gradeEmoji} + + } + trailing={ + + {(grade.subjectFile || grade.correctionFile) && ( + + )} - // Calculate averages from grades. - let averagesCalculation = calculateAveragesFromGrades(allGradesNonReactive); - // Structure averages gotten. - const averagesData: PapillonGradesViewAverages = { - studentAverage: averagesCalculation.average.toFixed(2), - classAverage: averagesCalculation.class_average.toFixed(2), - minAverage: averagesCalculation.min.toFixed(2), - maxAverage: averagesCalculation.max.toFixed(2), - }; - - // Sort subjects. - subjects.sort((a, b) => a.name.localeCompare(b.name)); - // Oldest grades first. - allGradesNonReactive.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - - // For each last grade, calculate average, to create the graph. - const chartData: Array<{ x: number, y: number }> = []; - for (const grade of allGradesNonReactive) { - const currentGradeTime = new Date(grade.date).getTime(); - const gradesBefore = allGradesNonReactive.filter((grade) => new Date(grade.date).getTime() <= currentGradeTime); - let average = calculateAveragesFromGrades(gradesBefore).average; - - // If NaN, set to 0. - if (isNaN(average)) { - average = 0; - } + + + + {gradeValue} + + + /{gradeScale} + + - chartData.push({ - x: currentGradeTime, - y: average - }); - } + { parseFloat(grade.grade.coefficient) !== 1 && ( + + Coeff. {grade.grade.coefficient} + + )} + + + } + > + + {gradeDescription} + + + {gradeDate} + + + ); + })} + + ); + })} + + ); +}); - let isCalculatedClassAvg = true; - if (!overview.class_overall_average.significant && overview.class_overall_average.value.toFixed(2) !== averagesCalculation.class_average.toFixed(2)) { - if (overview.class_overall_average.value > 0 && !hasCustomGrades) { - averagesData.classAverage = overview.class_overall_average.value.toFixed(2); - } else isCalculatedClassAvg = true; - } +const GradesAverageHistory = React.memo(({ isLoading, averages, chartLines, chartPoint, setChartPoint, gradeSettings, pronoteStudentAverage }) => { + const UIColors = GetUIColors(null, 'ios'); + if (chartLines === null || chartLines === undefined) return null; - let isCalculatedAvg = true; - if (!overview.overall_average.significant && overview.overall_average.value.toFixed(2) !== averagesCalculation.average.toFixed(2)) { - if (overview.overall_average.value > 0 && !hasCustomGrades) { - averagesData.studentAverage = overview.overall_average.value.toFixed(2); - } else isCalculatedAvg = true; - } + const [isReal, setIsReal] = useState(false); + const [reevaluated, setReevaluated] = useState(false); + const [currentDate, setCurrentDate] = useState(null); + const [finalAvg, setFinalAvg] = useState(averages.student); - setState({ - // Store only the 10 latest grades. - latestGrades: allGradesNonReactive.slice(-10).reverse(), - averagesData, - subjectsList: subjects, - avgChartData: chartData, - hasSimulatedGrades: hasCustomGrades, - calculatedClassAvg: isCalculatedClassAvg, - calculatedAvg: isCalculatedAvg - }); - } - - /** - * Get grades for the `selectedPeriod` from service API. - * @param force - Whether to forbid using the cache or not. - */ - async function getGradesFromAPI (force = false, period = selectedPeriod): Promise { - setIsLoading(true); - - if (appContext.dataProvider && period) { - const grades = await appContext.dataProvider.getGrades(period.name, force); - const start = performance.now(); - if (grades) await parseGrades(grades); - else { - // TODO: Warn user that cache is missing. - console.warn('CACHE NEEDED !'); - } - console.log('took', performance.now() - start, 'ms'); + useEffect(() => { + if (pronoteStudentAverage) { + setFinalAvg(pronoteStudentAverage); + setIsReal(true); + } else { + setFinalAvg(averages.student); + setIsReal(false); } - - setIsLoading(false); - } + }, [averages, pronoteStudentAverage]); useEffect(() => { - (async () => { - console.log('GradesScreen(onMount): getPeriodsFromAPI'); - const selectedPeriod = await getPeriodsFromAPI(); - console.log('GradesScreen(onMount): getGradesFromAPI ->', selectedPeriod.name); - await getGradesFromAPI(false, selectedPeriod); - })(); - }, []); - - function showGrade (grade: PapillonGrades['grades'][number]): void { - navigation.navigate('Grade', { grade, allGrades: allGradesNonReactive }); - setGradeOpened(true); - } - - // On grade modal close - React.useEffect(() => { - const unsubscribe = navigation.addListener('focus', async () => { - if (gradeOpened) { - if (await StoreReview.hasAction()) { - const triedValue = parseInt((await AsyncStorage.getItem('review-tried')) || '0'); - - if (triedValue >= 5) { - const reviewValue = await AsyncStorage.getItem('review-requested'); - - if (reviewValue) { - // check if date is more than 3 days - const now = new Date(); - const date = new Date(reviewValue); - const diffTime = Math.abs(now.getTime() - date.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - - if (diffDays < 7) return; - } + setReevaluated(false); - console.log('gradeModalClose: Review requested'); - await StoreReview.requestReview(); - await AsyncStorage.setItem('review-requested', new Date().toString()); - } + if (chartPoint) { + setIsReal(false); + setCurrentDate(chartPoint.x); - await AsyncStorage.setItem('review-tried', (triedValue + 1).toString()); - } + if (pronoteStudentAverage) { + const diff = Math.abs(pronoteStudentAverage - averages.student); + const correctedAvg = chartPoint.y - diff; + setFinalAvg(correctedAvg); + setReevaluated(true); + } else { + setFinalAvg(chartPoint.y); + } + } else { + if (pronoteStudentAverage) { + setFinalAvg(pronoteStudentAverage); + setIsReal(true); + } else { + setFinalAvg(averages.student); + setIsReal(false); } - setGradeOpened(false); - }); + setCurrentDate(null); + } + }, [chartPoint]); - return unsubscribe; - }, [navigation, gradeOpened]); + const handlePointFocus = useCallback((point) => { + setChartPoint(point); + }, [setChartPoint]); - const openSubject = (subject: PapillonSubject): void => { - setSelectedCourse(subject); - setCourseModalVisible(true); - }; + const handlePointLoseFocus = useCallback(() => { + setChartPoint(null); + }, [setChartPoint]); return ( - <> - { - setHeadLoading(true); - await getPeriodsFromAPI(); - await getGradesFromAPI(true); - setHeadLoading(false); + + + + {currentDate ? ( + + au {new Date(currentDate).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric' })} + + ) : ( + + Moyenne générale + + )} + + + + + {isReal ? 'Moyenne réelle' : + reevaluated ? 'Estim. réévaluée' : 'Estimation'} + + + + + + {finalAvg.toFixed(2)} + + + + /{gradeSettings.scale.toFixed(0)} + + + + { chartLines[0] && chartLines[0].data.length > 2 ? ( + + - } - onScroll={scrollHandler} - scrollEventThrottle={16} - > - - - } - color='#32AB8E' - visible={moyReelleAlert} - cancelAction={() => { - setMoyReelleAlert(false); - }} - /> - setCourseModalVisible(false)} - presentationStyle='pageSheet' - transparent={false} - > - setCourseModalVisible(false)} + onPointFocus={handlePointFocus} + onPointLoseFocus={handlePointLoseFocus} /> - - {selectedCourse !== null && ( - - - - {formatCoursName(selectedCourse.name)} - - - - - - - - {parseFloat(selectedCourse.averages.average).toFixed(2)} - - - /{selectedCourse.averages.out_of} - - - } - > - - Moyenne élève - - - - - - - - {parseFloat(selectedCourse.averages.class_average).toFixed(2)} - - - /{selectedCourse.averages.out_of} - - - } - > - - Moyenne de classe - - - - - {parseFloat(selectedCourse.averages.min).toFixed(2)} - - - /{selectedCourse.averages.out_of} - - - } - > - - Moyenne min. - - - - - {parseFloat(selectedCourse.averages.max).toFixed(2)} - - - /{selectedCourse.averages.out_of} - - - } - > - - Moyenne max. - - - - - )} - - - - {isLoading && ( - Chargement en cours de vos notes... - )} - - {state.subjectsList.length === 0 && !isLoading && ( - Aucune note à afficher. - )} - - {state.subjectsList.length > 0 && !isLoading && state.avgChartData.length > 0 && state.averagesData && ( - - - - Moyenne générale - - - setMoyReelleAlert(true)} - > - - - {state.calculatedAvg ? - state.hasSimulatedGrades ? 'Simulée' : 'Estimation' - : 'Moyenne réelle'} - - - - - - {state.averagesData?.studentAverage} - - - /20 - - - + + ) : ( + + )} + + ); +}); - { - return ( - - - {new Date(point!.x).toLocaleDateString('fr-FR', { - year: 'numeric', - month: 'short', - day: 'numeric', - })} - - - - - {point!.y.toFixed(2)} - - - /20 - - - - ); - }, - } - ]} - height={100} - width={Dimensions.get('window').width - 28 - 14} - extraConfig={{ - alwaysShowActivePoint: true, - }} - /> - - )} +const GradesAveragesList = ({ isLoading, UIaverage, gradeSettings }) => { + const renderNativeItem = (item, index) => ( + {item.icon}} + trailing={ + + + {item.value > 0 ? item.value.toFixed(2) : 'N.not'} + + + /{gradeSettings.scale.toFixed(0)} + + + } + > + + {item.name} + + {item.description && item.description.trim() !== '' && ( + + {item.description} + + )} + + ); - {state.latestGrades.length > 0 && !isLoading && ( - - - {state.latestGrades.map((grade) => ( - showGrade(grade)} - > - - - {getClosestGradeEmoji(grade.subject.name)} - - - {formatCoursName(grade.subject.name.split(' > ')[0])} - - - - - {grade.description ? ( - - {grade.description} - - ) : ( - - Note en {formatCoursName(grade.subject.name)} - - )} - - - {new Date(grade.date).toLocaleDateString('fr-FR', { - year: 'numeric', - month: 'short', - day: 'numeric', - })} - - - - { grade.isSimulated && ( - - Simulée - - )} - - - - {formatPapillonGradeValue(grade.grade.value)} - - - - /{formatPapillonGradeValue(grade.grade.out_of)} - - - - ))} - - - )} + const renderNativeList = useMemo(() => ( + + {UIaverage.map(renderNativeItem)} + + ), [UIaverage]); + + if (UIaverage.length === 0 && !isLoading) { + return ( + + + + Aucune moyenne à afficher. + + + + ); + } - {state.averagesData && !isLoading && ( - - - - - } - trailing={ - state.calculatedClassAvg ? ( - setClasseReelleAlert(true)} - > - - - ): null} - > - Moy. de classe - - - {state.averagesData?.classAverage} - - /20 - - - } - visible={moyClasseReelleAlert} - cancelButton='Compris !' - cancelAction={() => { - setClasseReelleAlert(false); - }} - /> - - - - } - trailing={ - setClasseBasseAlert(true)} - > - - - } - > - } - color='#32AB8E' - visible={moyClasseBasseAlert} - cancelButton='Compris !' - cancelAction={() => { - setClasseBasseAlert(false); - }} - /> - Moy. la plus faible - - - {state.averagesData?.minAverage} - - /20 - - - - - - } - trailing={ - setClasseHauteAlert(true)} - > - - - } - > - } - color='#32AB8E' - visible={moyClasseHauteAlert} - cancelButton='Compris !' - cancelAction={() => { - setClasseHauteAlert(false); - }} - /> - Moy. la plus élevée - - - {state.averagesData?.maxAverage} - - /20 - - - - )} + return renderNativeList; +}; - {state.subjectsList.length > 0 && !isLoading && ( - - {state.subjectsList.map((subject, index) => ( - - openSubject(subject)} - > - - - {formatCoursName(subject.parsedName.name)} - - { subject.parsedName.sub && ( - - {formatCoursName(subject.parsedName.sub)} - - )} - - - - { - subject.averages.average !== -1 ? subject.averages.average.toFixed(2) : 'Inconnu' - } - - - /{subject.averages.out_of} - - - - {subject.grades.map((grade, i) => ( - showGrade(grade)} - - leading={ - - - {getClosestGradeEmoji(grade.subject.name)} - - - } - - trailing={ - - - {!grade.grade.value.significant ? ( - - {grade.grade.value.value.toFixed(2)} - - ) : grade.grade.value.type === PronoteApiGradeType.Absent ? ( - Abs. - ) : grade.grade.value.type === PronoteApiGradeType.NotGraded ? ( - N.not - ) : ( - ?? - )} - - - /{!grade.grade.out_of.significant ? ( - grade.grade.out_of.value - ) : '??'} - - +const styles = StyleSheet.create({ + chart: { + container: { + margin: 15, + marginTop: 6, + marginBottom: 0, + borderRadius: 12, + borderCurve: 'continuous', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 1, + }, + shadowOpacity: 0.05, + shadowRadius: 2, + }, - {grade.isSimulated && ( - - Simulée - - )} - - } - > - - {grade.description ? ( - - {grade.description} - - ) : ( - subject.parsedName.sub ? ( - subject.parsedName.sub == 'Ecrit' || subject.parsedName.sub == 'Oral' ? ( - - Note d'{formatCoursName(subject.parsedName.sub).toLowerCase()} en {formatCoursName(subject.parsedName.name)} - - ) : ( - - Note de {formatCoursName(subject.parsedName.sub)} en {formatCoursName(subject.parsedName.name)} - - ) - ) : ( - - Note en {formatCoursName(subject.parsedName.name)} - - ) - )} + header: { + container: { + paddingHorizontal: 16, + paddingVertical: 14, + paddingBottom: 6, + }, + title: { + container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: 1, + }, + text: { + fontFamily: 'Papillon-Medium', + } + } + }, - - {new Date(grade.date).toLocaleDateString('fr-FR', { - year: 'numeric', - month: 'short', - day: 'numeric', - })} - - - - Coeff. : {grade.grade.coefficient} - - - - ))} - - ))} - - )} - - - ); -} + avg: { + container: { + flexDirection: 'row', + alignItems: 'flex-end', + gap: 2, + }, + value: { + fontFamily: 'Papillon-Semibold', + fontSize: 26, + }, + out_of: { + fontFamily: 'Papillon-Medium', + fontSize: 16, + } + } + }, +}); -const styles = StyleSheet.create({ +const subjectStyles = StyleSheet.create({ container: { flex: 1, }, - subjectList: { - width: '100%', - paddingHorizontal: 14, - gap: 12, - marginBottom: 14, - }, - - subjectContainer: { - width: '100%', - borderRadius: 14, - borderCurve: 'continuous', - overflow: 'hidden', - }, - subjectNameContainer: { - width: '100%', - height: 44, - overflow: 'hidden', - justifyContent: 'center', - alignItems: 'center', + listItem: { paddingHorizontal: 16, - flexDirection: 'row', - }, - subjectNameGroup: { - flex: 1, display: 'flex', flexDirection: 'row', alignItems: 'center', - gap: 8, - overflow: 'hidden', - marginRight: 10, + justifyContent: 'space-between', }, - subjectName: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - color: '#FFFFFF', + subjectInfoContainer: { flex: 1, - }, - subjectSub: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - color: '#FFFFFF', - - borderColor: '#FFFFFF75', - borderWidth: 1, - - overflow: 'hidden', - - borderRadius: 8, - borderCurve: 'continuous', - - backgroundColor: '#FFFFFF31', - - paddingHorizontal: 6, - paddingVertical: 3, - }, - subjectAverageContainer: { + paddingVertical: 12, flexDirection: 'row', - alignItems: 'flex-end', - gap: 1, + alignItems: 'center', + gap: 12, }, - subjectAverage: { - fontSize: 17, + subjectEmoji: { + color: '#ffffff', fontFamily: 'Papillon-Semibold', - color: '#FFFFFF', + fontSize: 24, }, - subjectAverageOutOf: { - fontSize: 15, + subjectName: { + color: '#ffffff', fontFamily: 'Papillon-Semibold', - color: '#FFFFFF', - opacity: 0.5, - }, - - gradesList: { - width: '100%', - }, - - gradeContainer: { - width: '100%', - }, - gradeUnderContainer: { - width: '100%', - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 14, - gap: 16, - }, - - gradeEmojiContainer: { - width: 36, - height: 36, - borderRadius: 10, - borderCurve: 'continuous', - overflow: 'hidden', - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - }, - gradeEmoji: { - fontSize: 22, - }, - - gradeNameContainer: { + fontSize: 16, flex: 1, - gap: 3, - marginRight: 10, - }, - gradeName: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - }, - gradeDate: { - fontSize: 14, - opacity: 0.5, - }, - gradeCoefficient: { - fontSize: 14, - fontWeight: '500', - opacity: 0.5, }, - - gradeValueContainer: { + gradeContainer: { flexDirection: 'row', alignItems: 'flex-end', - gap: 1, + gap: 0, }, - gradeValue: { - fontSize: 19, + subjectGradeValue: { + color: '#ffffff', fontFamily: 'Papillon-Semibold', - }, - gradeOutOf: { - fontSize: 14, - opacity: 0.5, - }, - - - periodButtonContainer: { - - }, - periodButtonText: { fontSize: 17, - color: '#21826A', - fontFamily: 'Papillon-Semibold', }, - - ListTitle: { - paddingLeft: 14, - marginTop: 18, - fontSize: 15, + subjectGradeScale: { + color: '#ffffff99', fontFamily: 'Papillon-Medium', - opacity: 0.5, - }, - smallListTitle: { - paddingLeft: 28, - marginTop: 18, fontSize: 15, - fontFamily: 'Papillon-Medium', - opacity: 0.5, }, - - smallSubjectList: { - width: '100%', - gap: 12, + gradeItem: { + // Add any additional styling for the grade items here }, - latestGradesList: { - gap: 14, - paddingHorizontal: 14, - paddingBottom: 2, + gradeDescription: { + // Add any additional styling for the grade descriptions here }, - smallGradeContainer: { - borderRadius: 14, - borderCurve: 'continuous', - width: 220, - paddingBottom: 42, - overflow: 'hidden', + gradeEmoji: { + color: '#ffffff', + fontFamily: 'Papillon-Semibold', + fontSize: 24, }, - smallGradeSubjectContainer: { - gap: 12, + inGradeView: { flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 8, - marginBottom: 12, - }, - smallGradeEmoji: { - fontSize: 20, - }, - smallGradeSubject: { - fontSize: 15, - fontFamily: 'Papillon-Semibold', - color: '#FFFFFF', - width: '82%', + gap: 12, }, - smallGradeNameContainer: { - flex: 1, + inGradeContainer : { + flexDirection: 'column', + alignItems: 'flex-end', gap: 3, - marginHorizontal: 16, - }, - smallGradeName: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - }, - smallGradeDate: { - fontSize: 15, - opacity: 0.5, }, - smallGradeValueContainer: { - flexDirection: 'row', - alignItems: 'flex-end', - gap: 1, - - position: 'absolute', - bottom: 14, - left: 16, - }, - smallGradeValue: { - fontSize: 20, - fontFamily: 'Papillon-Semibold', - }, - smallGradeOutOf: { + gradeCoef: { + fontFamily: 'Papillon-Medium', fontSize: 15, opacity: 0.5, }, - averagesList: { - flex: 1, - marginHorizontal: 14, - gap: 8, - }, - - averageContainer: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 14, - gap: 16, - borderRadius: 14, - borderCurve: 'continuous', - overflow: 'hidden', - }, - averagesClassContainer: { - flexDirection: 'row', - gap: 8, - }, - averageTextContainer: { - gap: 0, - }, - averageText: { - fontSize: 15, - opacity: 0.5, - }, - averageValueContainer: { + gradeTextContainer: { flexDirection: 'row', alignItems: 'flex-end', - gap: 1, + gap: 0, }, - averageValue: { - fontSize: 19, + gradeValue: { fontFamily: 'Papillon-Semibold', - }, - averageValueOutOf: { - fontSize: 15, - opacity: 0.5, - }, - - infoText: { - fontSize: 14, - textAlign: 'center', - opacity: 0.5, - marginVertical: 14, - }, - - noGrades: { fontSize: 17, - fontWeight: '400', + }, + gradeScale: { fontFamily: 'Papillon-Medium', + fontSize: 15, opacity: 0.5, - textAlign: 'center', - marginTop: 12, }, - headerTitle: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - }, - - modalContainer: { - height: '60%', - width: '100%', - position: 'absolute', - bottom: 0, - left: 0, - borderTopLeftRadius: 18, - borderTopRightRadius: 18, + smallGrade: { + width: 200, + borderRadius: 12, borderCurve: 'continuous', overflow: 'hidden', }, - modalSubjectNameContainer: { - height: 52, - width: '100%', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 20, - flexDirection: 'row', - borderTopLeftRadius: 18, - borderTopRightRadius: 18, - borderCurve: 'continuous', - }, - - averageChart: { - marginHorizontal: 14, - marginBottom: 14, - paddingHorizontal: 0, - paddingVertical: 14, - paddingBottom: 6, - marginTop: 14, - - shadowColor: '#000000', - shadowOpacity: 0.1, - shadowRadius: 2, - shadowOffset: { - height: 0, - width: 0, - }, - elevation: 1, - - borderRadius: 14, - borderCurve: 'continuous', - }, - - averagesgrClassContainer: { - marginHorizontal: 16, - marginBottom: 8, - }, - - averagegrTitle: { - fontSize: 15, - opacity: 0.5, - marginBottom: 2, - fontFamily: 'Papillon-Medium', - }, - averagegrTitleInfo: { - position: 'absolute', - right: 0, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: 6, - }, + smallGradeData: { + paddingHorizontal: 15, + paddingVertical: 12, + gap: 4, + flex: 1, - averagegrTitleInfoText: { - fontSize: 15, - fontWeight: '500', - fontFamily: 'Papillon-Semibold', + paddingBottom: 38, }, - averagegrValCont: { + smallGradeContainer: { flexDirection: 'row', alignItems: 'flex-end', - gap: 1, + gap: 0, + position: 'absolute', + bottom: 12, + left: 15, }, - averagegrValue: { - fontSize: 26, + smallGradeValue: { fontFamily: 'Papillon-Semibold', - }, - averagegrValueSm: { fontSize: 20, - fontFamily: 'Papillon-Semibold', }, - averagegrOof: { + smallGradeScale: { + fontFamily: 'Papillon-Medium', fontSize: 15, opacity: 0.5, - fontFamily: 'Papillon-Medium', - }, - - activePoint: { - borderRadius: 8, - borderCurve: 'continuous', - overflow: 'hidden', - paddingHorizontal: 10, - paddingVertical: 6, - }, - - grTextWh: { - color: '#FFFFFF', - }, - - rightActions: { - flexDirection: 'row', - alignItems: 'center', - gap: 20, - }, - - addButtonContainer: { - width: 34, - height: 34, - borderRadius: 10, - borderCurve: 'continuous', - overflow: 'hidden', - justifyContent: 'center', - alignItems: 'center', - }, - - smallGradeSimulated: { - fontSize: 16, - fontFamily: 'Papillon-Semibold', - letterSpacing: 0.15, - - borderRadius: 8, - borderCurve: 'continuous', - - borderWidth: 1, - alignSelf: 'flex-start', - - paddingHorizontal: 6, - paddingVertical: 3, - - position: 'absolute', - right: 16, - bottom: 16, - }, - - gradeDataContainer : { - alignItems: 'flex-end', - }, - - gradeSimulated: { - fontSize: 16, - fontFamily: 'Papillon-Semibold', - letterSpacing: 0.15, - - borderRadius: 8, - borderCurve: 'continuous', - - borderWidth: 1, - alignSelf: 'flex-start', - - paddingHorizontal: 6, - paddingVertical: 3, - - marginTop: 4, }, + }); export default GradesScreen; diff --git a/views/GradesScreenNew.tsx b/views/GradesScreenNew.tsx deleted file mode 100644 index e751df9c..00000000 --- a/views/GradesScreenNew.tsx +++ /dev/null @@ -1,1128 +0,0 @@ -import React, { useEffect, useState, useCallback, useMemo } from 'react'; -import { Animated, ActivityIndicator, StatusBar, View, Dimensions, StyleSheet, ScrollView, TouchableOpacity, RefreshControl, Easing, Platform, Pressable } from 'react-native'; - -// Custom imports -import GetUIColors from '../utils/GetUIColors'; -import { useAppContext } from '../utils/AppContext'; -import PapillonInsetHeader from '../components/PapillonInsetHeader'; -import { SFSymbol } from 'react-native-sfsymbols'; - -import NativeList from '../components/NativeList'; -import NativeItem from '../components/NativeItem'; -import NativeText from '../components/NativeText'; - -import { getSavedCourseColor } from '../utils/ColorCoursName'; -import getClosestGradeEmoji from '../utils/EmojiCoursName'; -import formatCoursName from '../utils/FormatCoursName'; - -import { useActionSheet } from '@expo/react-native-action-sheet'; - -// Icons -import { Users2, File, TrendingDown, TrendingUp, AlertTriangle, MoreVertical } from 'lucide-react-native'; -import { Stats } from '../interface/icons/PapillonIcons'; - -// Plugins -import { ContextMenuButton } from 'react-native-ios-context-menu'; -import LineChart from 'react-native-simple-line-chart'; -import { BlurView } from 'expo-blur'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { PressableScale } from 'react-native-pressable-scale'; - - -// Interfaces -interface UIaverage { - name: string, - value: number, - icon: React.ReactElement, -} - -export interface GradeSettings { - scale: number, -} - -interface PapillonAveragesOverTime { - date: Date, - value: number, -} - -// Pawnote -import { PapillonPeriod } from '../fetch/types/period'; -import { PapillonGrades, PapillonGradesViewAverages } from '../fetch/types/grades'; - -import { calculateSubjectAverage } from '../utils/grades/averages'; -import PapillonLoading from '../components/PapillonLoading'; - -const GradesScreen = ({ navigation }: { - navigation: any // TODO -}) => { - const UIColors = GetUIColors(); - const appContext = useAppContext(); - const insets = useSafeAreaInsets(); - const { showActionSheetWithOptions } = useActionSheet(); - - // Data - const [isLoading, setIsLoading] = React.useState(false); - const [isRefreshing, setIsRefreshing] = React.useState(false); - const [periods, setPeriods] = React.useState([]); - const [selectedPeriod, setSelectedPeriod] = React.useState(null); - const [grades, setGrades] = React.useState([]); - const [allGrades, setAllGrades] = React.useState(null); - const [latestGrades, setLatestGrades] = React.useState(null); - const [averages, setAverages] = React.useState({} as PapillonGradesViewAverages); - const [pronoteStudentAverage, setPronoteStudentAverage] = React.useState(null); - const [pronoteClassAverage, setPronoteClassAverage] = React.useState(null); - const [averagesOverTime, setAveragesOverTime] = React.useState([]); - const [classAveragesOverTime, setClassAveragesOverTime] = React.useState([]); - const [chartLines, setChartLines] = React.useState(null); - const [chartPoint, setChartPoint] = React.useState(null); - const [openedSettings, setOpenedSettings] = React.useState(true); - - // Constants - const [gradeSettings, setGradeSettings] = React.useState({ - scale: 20, - }); - - // Update gradeSettings when focused - React.useEffect(() => { - const unsubscribe = navigation.addListener('focus', () => { - AsyncStorage.getItem('gradeSettings').then((value) => { - if (value) { - setGradeSettings(JSON.parse(value)); - } - }); - - if (openedSettings) setOpenedSettings(false); - }); - - return unsubscribe; - }, [navigation, gradeSettings, openedSettings]); - - // UI arrays - const [UIaverage, setUIaverage] = React.useState([]); - - // Update UIaverage when averages change - React.useEffect(() => { - setUIaverage([ - { - name: 'Moyenne groupe', - value: pronoteClassAverage ? pronoteClassAverage : (averages.group || 0), - icon: , - }, - { - name: 'Moyenne max', - value: averages.max || 0, - icon: , - }, - { - name: 'Moyenne min', - value: averages.min || 0, - icon: , - }, - ]); - }, [averages, UIColors.text, pronoteClassAverage]); - - // Update chartLines when averagesOverTime change - React.useEffect(() => { - const createLineData = (averages: PapillonAveragesOverTime[]) => averages.map(average => ({ - x: new Date(average.date).getTime(), - y: average.value, - })); - - const linesSettings = { - lineColor: UIColors.primary, - curve: 'monotone', - endPointConfig: { - color: UIColors.primary, - radius: 8, - animated: true, - }, - lineWidth: 3, - activePointConfig: { - color: UIColors.primary, - borderColor: UIColors.element, - radius: 7, - borderWidth: 0, - showVerticalLine: true, - verticalLineColor: UIColors.text, - verticalLineOpacity: 0.2, - verticalLineWidth: 1.5, - } - }; - - const studentLinesData = createLineData(averagesOverTime); - const classLinesData = createLineData(classAveragesOverTime); - - const lines = [ - { - ...linesSettings, - key: 'student2', - data: studentLinesData, - }, - { - ...linesSettings, - key: 'class', - lineColor: UIColors.border, - lineWidth: 2, - trailingOpacity: 0, - endPointConfig: { - radius: 0, - animated: false, - color: 'transparent', - }, - activePointConfig: { - radius: 0, - borderWidth: 0, - showVerticalLine: false, - color: 'transparent', - borderColor: 'transparent', - }, - data: classLinesData, - }, - { - ...linesSettings, - key: 'student', - data: studentLinesData, - } - ]; - - setChartLines(lines); - }, [averagesOverTime, classAveragesOverTime, UIColors.text, UIColors.border, UIColors.primary, UIColors.element]); - - async function getPeriodsFromAPI (): Promise { - return appContext.dataProvider!.getUser().then((user) => { - const periods = user.periodes.grades; - - setPeriods(periods); - const currentPeriod = periods.find((period) => period.actual)!; - - setSelectedPeriod(currentPeriod.name); - return currentPeriod; - }); - } - - function getGradesFromAPI (force = false, periodName = selectedPeriod): Promise { - if (!isRefreshing) { - setIsLoading(true); - } - - try { - if (appContext.dataProvider && periodName) { - return appContext.dataProvider.getGrades(periodName, force).then((grades) => { - if (grades) { - return parseGrades(grades); - } - else { - return Promise.reject('No grades'); - } - }); - } - } catch (error) { - console.error(error); - } finally { - setIsLoading(false); - setIsRefreshing(false); - } - } - - function addGradesToSubject(grades: PapillonGrades): Promise { - const data = grades.averages.map(average => ({ ...average, grades: [] as PapillonGrades['grades'] })); - - grades.grades.forEach((grade) => { - const subject = data.find((subject) => subject.subject.id === grade.subject.id); - if (subject) { - subject.grades.push(grade); - grade.background_color = subject.color; - } - }); - - data.forEach((subject) => { - subject.grades.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - }); - - data.sort((a, b) => a.subject.name.localeCompare(b.subject.name)); - - if(grades.class_overall_average?.value > 0) { - setPronoteClassAverage(grades.class_overall_average.value); - } - - if(grades.overall_average?.value > 0) { - setPronoteStudentAverage(grades.overall_average.value); - } - - setGrades(data); - setAllGrades(grades.grades); - - const latest = [...grades.grades].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 10); - setLatestGrades(latest); - } - - // Calculate averages over time - async function calculateAveragesOverTime (grades: PapillonGrades[], type= 'value'): Promise { - // map grades to data with date and average value - const data = await Promise.all(grades.map(async (grade, i) => { - const gradesUntil = grades.slice(0, i + 1); - const average = await calculateSubjectAverage(gradesUntil, type, gradeSettings.scale); - return { - date: new Date(grade.date), - value: average, - }; - })); - - // sort by date - data.sort((a, b) => a.date.getTime() - b.date.getTime()); - - return data; - } - - // Estimate averages - async function estimatedStudentAverages (grades: PapillonGrades): Promise { - const [student, group, max, min] = await Promise.all([ - calculateSubjectAverage(grades, 'value', gradeSettings.scale), - calculateSubjectAverage(grades, 'average', gradeSettings.scale), - calculateSubjectAverage(grades, 'max', gradeSettings.scale), - calculateSubjectAverage(grades, 'min', gradeSettings.scale) - ]); - - setAverages({ - student, - group, - max, - min, - }); - } - - // Estimate averages over time - async function estimateAveragesOverTime (grades: PapillonGrades): Promise { - const [averagesOverTime, classAveragesOverTime] = await Promise.all([ - calculateAveragesOverTime(grades, 'value'), - calculateAveragesOverTime(grades, 'average') - ]); - setAveragesOverTime(averagesOverTime); - setClassAveragesOverTime(classAveragesOverTime); - } - - // Parse grades - async function parseGrades (grades: PapillonGrades): Promise { - addGradesToSubject(grades); - estimatedStudentAverages(grades?.grades); - estimateAveragesOverTime(grades?.grades); - } - - function androidPeriodChangePicker () { - const options = periods.map((item) => item.name); - options.push('Annuler'); - const cancelButtonIndex = options.length - 1; - - showActionSheetWithOptions( - { - options, - cancelButtonIndex : cancelButtonIndex, - tintColor: UIColors.primary, - }, - (buttonIndex) => { - if (typeof buttonIndex !== 'undefined' && buttonIndex !== cancelButtonIndex) { - setSelectedPeriod(periods[buttonIndex].name); - } - } - ); - } - - // On mount - React.useEffect(() => { getPeriodsFromAPI(); }, []); - React.useEffect(() => { getGradesFromAPI(false, selectedPeriod); }, [selectedPeriod]); - - // Header animation - const yOffset = new Animated.Value(0); - - const scrollHandler = Animated.event( - [{ nativeEvent: { contentOffset: { y: yOffset } } }], - { useNativeDriver: false } - ); - - const headerOpacity = yOffset.interpolate({ - inputRange: [-40, 0], - outputRange: [0, 1], - extrapolate: 'clamp', - }); - - const HeaderRight = ({ - navigation, - periods, - selectedPeriod, - UIColors, - isLoading, - androidPeriodChangePicker, - setOpenedSettings - }: { - navigation: any, - periods: PapillonPeriod[] - selectedPeriod: string | null - }) => { - const menuConfig = useMemo(() => ({ - menuTitle: '', - menuItems: periods.map((period) => ({ - actionKey: period.name, - actionTitle: period.name, - menuState: selectedPeriod === period.name ? 'on' : 'off', - })), - }), [periods, selectedPeriod]); - - return ( - - { isLoading && ( - - )} - { - if (nativeEvent.actionKey === selectedPeriod) return; - setSelectedPeriod(nativeEvent.actionKey); - }} - > - { - if (Platform.OS !== 'ios') { - androidPeriodChangePicker(); - } - }} - > - - {selectedPeriod} - - - - { - navigation.navigate('GradesSettings'); - setOpenedSettings(true); - }} - > - - - - ); - }; - - // Change header title - React.useLayoutEffect(() => { - navigation.setOptions({ - headerTitle : 'Notes', - headerRight: () => , - headerShadowVisible: false, - headerTransparent: Platform.OS === 'ios' ? true : false, - headerStyle: Platform.OS === 'android' ? { - backgroundColor: UIColors.background, - elevation: 0, - } : undefined, - }); - }, [navigation, periods, selectedPeriod, UIColors, headerOpacity, setOpenedSettings, showActionSheetWithOptions]); - - return ( - <> - { Platform.OS === 'ios' && ( - - - - )} - { - setIsRefreshing(true); - getGradesFromAPI(true); - }} - /> - } - > - - - {grades.length === 0 && ( - } - /> - )} - - { averages.student && averages.student > 0 && ( - - )} - - { latestGrades && latestGrades.length > 0 && ( - - )} - - { Platform.OS === 'android' && ( - - )} - - { averages.student && averages.student > 0 && ( - - )} - - - - - - - - ); -}; - -const LatestGradesList = React.memo(({ isLoading, grades, allGrades, gradeSettings, navigation }) => { - const UIColors = GetUIColors(null, 'ios'); - - const showGrade = useCallback((grade) => { - navigation.navigate('Grade', { - grade, - allGrades: allGrades, - }); - }, [allGrades, navigation]); - - return (<> - - - - - - - {grades.map((grade, index) => ( - showGrade(grade)} - key={index} - style={[ - subjectStyles.smallGrade, - { - backgroundColor: UIColors.element, - }, - Platform.OS === 'android' && { - borderColor: UIColors.border, - borderWidth: 0.5, - shadowColor: '#00000055', - elevation: 3, - } - ]} - > - - - - {getClosestGradeEmoji(grade.subject.name)} - - - {formatCoursName(grade.subject.name)} - - - - - - {grade.description || 'Aucune description'} - - - {new Date(grade.date).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric', year: 'numeric' })} - - - - { grade.grade.value?.value ? ( - - {((grade.grade.value?.value / grade.grade.out_of.value) * gradeSettings.scale).toFixed(2)} - - ) : ( - - N.not - - )} - - /{gradeSettings.scale.toFixed(0)} - - - - - ))} - - - ); -}); - -const GradesList = React.memo(({ grades, allGrades, gradeSettings, navigation, UIColors }) => { - const showGrade = useCallback((grade) => { - navigation.navigate('Grade', { - grade, - allGrades: allGrades, - }); - }, [navigation, allGrades]); - - return ( - - {grades.map((subject, index) => { - const formattedCourseName = formatCoursName(subject.subject.name); - const backgroundColor = getSavedCourseColor(subject.subject.name, subject.color); - const gradeValue = ((subject.average.value / subject.out_of.value) * gradeSettings.scale).toFixed(2); - const gradeScale = gradeSettings.scale.toFixed(0); - - return ( - - - - - {formattedCourseName} - - - - - - {gradeValue !== 'NaN' ? gradeValue : 'N.éval'} - - - /{gradeScale} - - - - - {subject.grades.map((grade, index) => { - const gradeEmoji = getClosestGradeEmoji(subject.subject.name); - const gradeValue = grade.grade.value?.value?.toFixed(2) || 'N.not'; - const gradeScale = grade.grade.out_of.value.toFixed(0); - const gradeDescription = grade.description || 'Aucune description'; - const gradeDate = new Date(grade.date).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric', year: 'numeric' }); - - return ( - showGrade(grade)} - leading={ - - {gradeEmoji} - - } - trailing={ - - {(grade.subjectFile || grade.correctionFile) && ( - - )} - - - - - {gradeValue} - - - /{gradeScale} - - - - { parseFloat(grade.grade.coefficient) !== 1 && ( - - Coeff. {grade.grade.coefficient} - - )} - - - } - > - - {gradeDescription} - - - {gradeDate} - - - ); - })} - - ); - })} - - ); -}); - -const GradesAverageHistory = React.memo(({ isLoading, averages, chartLines, chartPoint, setChartPoint, gradeSettings, pronoteStudentAverage }) => { - const UIColors = GetUIColors(null, 'ios'); - if (chartLines === null || chartLines === undefined) return null; - - const [isReal, setIsReal] = useState(false); - const [reevaluated, setReevaluated] = useState(false); - const [currentDate, setCurrentDate] = useState(null); - const [finalAvg, setFinalAvg] = useState(averages.student); - - useEffect(() => { - if (pronoteStudentAverage) { - setFinalAvg(pronoteStudentAverage); - setIsReal(true); - } else { - setFinalAvg(averages.student); - setIsReal(false); - } - }, [averages, pronoteStudentAverage]); - - useEffect(() => { - setReevaluated(false); - - if (chartPoint) { - setIsReal(false); - setCurrentDate(chartPoint.x); - - if (pronoteStudentAverage) { - const diff = Math.abs(pronoteStudentAverage - averages.student); - const correctedAvg = chartPoint.y - diff; - setFinalAvg(correctedAvg); - setReevaluated(true); - } else { - setFinalAvg(chartPoint.y); - } - } else { - if (pronoteStudentAverage) { - setFinalAvg(pronoteStudentAverage); - setIsReal(true); - } else { - setFinalAvg(averages.student); - setIsReal(false); - } - - setCurrentDate(null); - } - }, [chartPoint]); - - const handlePointFocus = useCallback((point) => { - setChartPoint(point); - }, [setChartPoint]); - - const handlePointLoseFocus = useCallback(() => { - setChartPoint(null); - }, [setChartPoint]); - - return ( - - - - {currentDate ? ( - - au {new Date(currentDate).toLocaleDateString('fr-FR', { month: 'long', day: 'numeric' })} - - ) : ( - - Moyenne générale - - )} - - - - - {isReal ? 'Moyenne réelle' : - reevaluated ? 'Estim. réévaluée' : 'Estimation'} - - - - - - {finalAvg.toFixed(2)} - - - - /{gradeSettings.scale.toFixed(0)} - - - - { chartLines[0] && chartLines[0].data.length > 2 ? ( - - - - ) : ( - - )} - - ); -}); - -const GradesAveragesList = ({ isLoading, UIaverage, gradeSettings }) => { - const renderNativeItem = (item, index) => ( - {item.icon}} - trailing={ - - - {item.value > 0 ? item.value.toFixed(2) : 'N.not'} - - - /{gradeSettings.scale.toFixed(0)} - - - } - > - - {item.name} - - {item.description && item.description.trim() !== '' && ( - - {item.description} - - )} - - ); - - const renderNativeList = useMemo(() => ( - - {UIaverage.map(renderNativeItem)} - - ), [UIaverage]); - - if (UIaverage.length === 0 && !isLoading) { - return ( - - - - Aucune moyenne à afficher. - - - - ); - } - - return renderNativeList; -}; - -const styles = StyleSheet.create({ - chart: { - container: { - margin: 15, - marginTop: 6, - marginBottom: 0, - borderRadius: 12, - borderCurve: 'continuous', - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 1, - }, - shadowOpacity: 0.05, - shadowRadius: 2, - }, - - header: { - container: { - paddingHorizontal: 16, - paddingVertical: 14, - paddingBottom: 6, - }, - title: { - container: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: 1, - }, - text: { - fontFamily: 'Papillon-Medium', - } - } - }, - - avg: { - container: { - flexDirection: 'row', - alignItems: 'flex-end', - gap: 2, - }, - value: { - fontFamily: 'Papillon-Semibold', - fontSize: 26, - }, - out_of: { - fontFamily: 'Papillon-Medium', - fontSize: 16, - } - } - }, -}); - -const subjectStyles = StyleSheet.create({ - container: { - flex: 1, - }, - listItem: { - paddingHorizontal: 16, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - subjectInfoContainer: { - flex: 1, - paddingVertical: 12, - flexDirection: 'row', - alignItems: 'center', - gap: 12, - }, - subjectEmoji: { - color: '#ffffff', - fontFamily: 'Papillon-Semibold', - fontSize: 24, - }, - subjectName: { - color: '#ffffff', - fontFamily: 'Papillon-Semibold', - fontSize: 16, - flex: 1, - }, - gradeContainer: { - flexDirection: 'row', - alignItems: 'flex-end', - gap: 0, - }, - subjectGradeValue: { - color: '#ffffff', - fontFamily: 'Papillon-Semibold', - fontSize: 17, - }, - subjectGradeScale: { - color: '#ffffff99', - fontFamily: 'Papillon-Medium', - fontSize: 15, - }, - gradeItem: { - // Add any additional styling for the grade items here - }, - gradeDescription: { - // Add any additional styling for the grade descriptions here - }, - - gradeEmoji: { - color: '#ffffff', - fontFamily: 'Papillon-Semibold', - fontSize: 24, - }, - - inGradeView: { - flexDirection: 'row', - alignItems: 'center', - gap: 12, - }, - - inGradeContainer : { - flexDirection: 'column', - alignItems: 'flex-end', - gap: 3, - }, - - gradeCoef: { - fontFamily: 'Papillon-Medium', - fontSize: 15, - opacity: 0.5, - }, - - gradeTextContainer: { - flexDirection: 'row', - alignItems: 'flex-end', - gap: 0, - }, - gradeValue: { - fontFamily: 'Papillon-Semibold', - fontSize: 17, - }, - gradeScale: { - fontFamily: 'Papillon-Medium', - fontSize: 15, - opacity: 0.5, - }, - - smallGrade: { - width: 200, - borderRadius: 12, - borderCurve: 'continuous', - overflow: 'hidden', - }, - - smallGradeData: { - paddingHorizontal: 15, - paddingVertical: 12, - gap: 4, - flex: 1, - - paddingBottom: 38, - }, - - smallGradeContainer: { - flexDirection: 'row', - alignItems: 'flex-end', - gap: 0, - position: 'absolute', - bottom: 12, - left: 15, - }, - - smallGradeValue: { - fontFamily: 'Papillon-Semibold', - fontSize: 20, - }, - - smallGradeScale: { - fontFamily: 'Papillon-Medium', - fontSize: 15, - opacity: 0.5, - }, - -}); - -export default GradesScreen; diff --git a/views/NewAuthStack/Pronote/NGPronoteWebviewLogin.tsx b/views/NewAuthStack/Pronote/NGPronoteWebviewLogin.tsx index 8cf48766..ba545677 100644 --- a/views/NewAuthStack/Pronote/NGPronoteWebviewLogin.tsx +++ b/views/NewAuthStack/Pronote/NGPronoteWebviewLogin.tsx @@ -292,6 +292,8 @@ const NGPronoteWebviewLogin = ({ route, navigation }: { opacity: showWebView ? 1 : 0, }} + setSupportMultipleWindows={false} + onMessage={async ({ nativeEvent }) => { const message = JSON.parse(nativeEvent.data); diff --git a/views/NewAuthStack/SelectService.tsx b/views/NewAuthStack/SelectService.tsx index fc09172f..e3d3f787 100644 --- a/views/NewAuthStack/SelectService.tsx +++ b/views/NewAuthStack/SelectService.tsx @@ -203,8 +203,8 @@ const SelectService = ({ navigation }) => { visible={edAlertVisible} setVisible={setEdAlertVisible} icon={} - title="EcoleDirecte" - subtitle="EcoleDirecte n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard." + title={apiResponse[serviceOptions[selectedService]?.company]?.title} + subtitle={apiResponse[serviceOptions[selectedService]?.company]?.content} cancelAction={() => setEdAlertVisible(false)} /> @@ -213,8 +213,8 @@ const SelectService = ({ navigation }) => { visible={skolengoAlertVisible} setVisible={setSkolengoAlertVisible} icon={} - title="Skolengo" - subtitle="Skolengo n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard." + title={apiResponse[serviceOptions[selectedService]?.company]?.title} + subtitle={apiResponse[serviceOptions[selectedService]?.company]?.content} cancelAction={() => setSkolengoAlertVisible(false)} /> diff --git a/views/Settings/AboutScreen.tsx b/views/Settings/AboutScreen.tsx index c2bb2781..393d8012 100644 --- a/views/Settings/AboutScreen.tsx +++ b/views/Settings/AboutScreen.tsx @@ -257,7 +257,7 @@ function AboutScreen({ navigation }) { { - navigation.navigate('ConsentScreen'); + navigation.navigate('ConsentScreenWithoutAcceptation'); }} chevron > diff --git a/views/Settings/NotificationsScreen.js b/views/Settings/NotificationsScreen.js index 1dd005de..57724647 100644 --- a/views/Settings/NotificationsScreen.js +++ b/views/Settings/NotificationsScreen.js @@ -20,7 +20,7 @@ import notifee, { AuthorizationStatus } from '@notifee/react-native'; import NativeList from '../../components/NativeList'; import NativeItem from '../../components/NativeItem'; import NativeText from '../../components/NativeText'; -import { Calendar, CalendarClock, CheckCircle, TrendingUp } from 'lucide-react-native'; +import {Backpack, BaggageClaim, Calendar, CalendarClock, CheckCircle, TrendingUp, Utensils} from 'lucide-react-native'; function NotificationsScreen({ navigation }) { const UIColors = GetUIColors(); @@ -31,6 +31,8 @@ function NotificationsScreen({ navigation }) { 'notifications_CoursEnabled': true, 'notifications_DevoirsEnabled': true, 'notifications_NotesEnabled': true, + 'notifications_BagReminderEnabled': false, + 'notifications_SelfReminderEnabled': false, }); const checkPermissions = async () => { @@ -204,6 +206,51 @@ function NotificationsScreen({ navigation }) { + + + + } + trailing={ + toggleNotification('notifications_BagReminderEnabled')} + value={notificationSettings.notifications_BagReminderEnabled} + /> + } + > + + Faire son sac + + + Vous rappel de préparer votre sac lorsque la journée actuel contient des cours. + + + + } + trailing={ + toggleNotification('notifications_SelfReminderEnabled')} + value={notificationSettings.notifications_SelfReminderEnabled} + /> + } + > + + Réserver le self + + + Vous rappel de réserver votre repas lorsque la journée suivante contient des cours. + + + ) : ( diff --git a/views/Settings/SettingsScreen.tsx b/views/Settings/SettingsScreen.tsx index 94ef8699..992bf9a5 100644 --- a/views/Settings/SettingsScreen.tsx +++ b/views/Settings/SettingsScreen.tsx @@ -4,7 +4,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import * as Haptics from 'expo-haptics'; import { unsetBackgroundFetch } from '../../fetch/BackgroundFetch'; -import { LogOut, RefreshCw, RotateCw, Server, Trash2 } from 'lucide-react-native'; +import { LogOut, RefreshCw, RotateCw, Trash2 } from 'lucide-react-native'; import { showMessage } from 'react-native-flash-message'; import { revokeAsync } from 'expo-auth-session'; diff --git a/views/SettingsScreen.ios.tsx b/views/SettingsScreen.ios.tsx index 5ddb8ec9..36ec6676 100644 --- a/views/SettingsScreen.ios.tsx +++ b/views/SettingsScreen.ios.tsx @@ -320,7 +320,7 @@ function NewSettings({ navigation }: { onPress={() => navigation.navigate('About')} > - A propos + À propos Papillon version {packageJson.version} {packageJson.canal}