Skip to content

Commit 8081b90

Browse files
committed
fix(ios): widget could not get more than 2 classes
1 parent 583268f commit 8081b90

8 files changed

Lines changed: 210 additions & 85 deletions

File tree

app.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
"image": "./src/assets/icon.png",
6767
"prerendered": false
6868
},
69+
"old": {
70+
"image": "./src/assets/icons/icon_old.png",
71+
"prerendered": false
72+
},
6973
"magnet": {
7074
"image": "./src/assets/icons/icon_magnet.png",
7175
"prerendered": false

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "unicenotes",
33
"main": "expo-router/entry",
4-
"version": "3.0.0",
4+
"version": "3.1.0",
55
"scripts": {
66
"start": "expo start --dev-client",
77
"android": "expo run:android",

src/app/home.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import BottomSheet, {
1313
} from '@gorhom/bottom-sheet';
1414
import { useRouter } from 'expo-router';
1515
import React, { useCallback, useEffect, useRef, useState } from 'react';
16-
import { StyleSheet, View } from 'react-native';
16+
import { StyleSheet, View, Platform } from 'react-native';
1717
import {
1818
ActivityIndicator,
1919
Avatar,
@@ -63,6 +63,7 @@ export default function HomeScreen() {
6363

6464
useEffect(() => {
6565
getNextEvent('normal');
66+
if (Platform.OS === 'ios') pushWidgetTimeline();
6667

6768
// check in app context if update modal has been shown, if not show it and set it to true
6869
if (!updateModalShown) {
@@ -88,27 +89,37 @@ export default function HomeScreen() {
8889
const result = await edtService.getNextEvent(adeid ?? 'demo');
8990
setNextEvent(result);
9091
setNextEventLoaded(true);
91-
try {
92-
const courses = await edtService.getNextTwoCourses(adeid ?? 'demo');
93-
NextClassWidgetInstance.updateSnapshot({ courses });
94-
} catch {
95-
// Widget non disponible en mode dev ou non configuré
96-
console.warn('NextClassWidget: Impossible de récupérer les prochains cours pour le widget');
97-
}
9892
}
9993
}
10094

10195
async function getMyCal() {
10296
haptics('medium');
10397
setSelectable(false);
10498
setLoading(true);
99+
105100
const cal = await edtService.getEDT(adeid ?? 'demo');
106101
setCalendar(cal);
102+
if (Platform.OS === 'ios') {
103+
try {
104+
NextClassWidgetInstance.updateTimeline(edtService.buildWidgetTimeline(cal));
105+
} catch { }
106+
}
107+
107108
setSelectable(true);
108109
setLoading(false);
109110
router.push('/show-edt');
110111
}
111112

113+
async function pushWidgetTimeline() {
114+
try {
115+
const events = await edtService.getEDT(adeid ?? 'demo');
116+
const timeline = edtService.buildWidgetTimeline(events);
117+
NextClassWidgetInstance.updateTimeline(timeline);
118+
} catch {
119+
// widget non configuré ou ADE indisponible
120+
}
121+
}
122+
112123
async function showUpdateModal() {
113124
// wait 1 second before showing the update modal
114125
await new Promise(resolve => setTimeout(resolve, 1500));

src/app/icon-config.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ export default function IconConfigScreen() {
2727
haptics('medium');
2828
if (!__DEV__) {
2929
setAppIcon(value);
30-
} else {
31-
console.log("Changement d'icône : " + value);
3230
}
3331
}
3432

@@ -62,6 +60,14 @@ export default function IconConfigScreen() {
6260
>
6361
Par défaut
6462
</Chip>
63+
<Chip
64+
style={{ ...chipStyle, borderRadius: 0, marginTop: 1 }}
65+
textStyle={{ paddingVertical: 8 }}
66+
avatar={<Image style={{ width: 24, height: 24 }} source={require('../assets/icons/icon_old.png')} />}
67+
onPress={() => changeIconHome('old')}
68+
>
69+
Old Style
70+
</Chip>
6571
<Chip
6672
style={{ ...chipStyle, borderRadius: 0, marginTop: 1 }}
6773
textStyle={{ paddingVertical: 8 }}

src/services/edt.ts

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { File, Paths } from 'expo-file-system';
22
import ICAL from 'ical.js';
3-
import type { CalendarEvent, NextEvent, WidgetClass } from '../types';
3+
import type { CalendarEvent, NextClassWidgetProps, NextEvent, WidgetClass } from '../types';
44
import { stringToColour } from '../utils/color';
55

66
interface ICALComponent {
@@ -142,6 +142,41 @@ export class EDT {
142142
return { summary: next.summary, location: next.location };
143143
}
144144

145+
async getNextEvent(adeid: string): Promise<NextEvent> {
146+
const icalData = await this.fetchEDT(adeid);
147+
if (!icalData) {
148+
return { summary: 'ADE Indisponible', location: 'Impossible de récupérer le cours.' };
149+
}
150+
const events = this.parseICal(icalData);
151+
return this.findNextEvent(events);
152+
}
153+
154+
private convertToCalendarEvents(events: ICALEvent[]): CalendarEvent[] {
155+
return events.map((e, index) => ({
156+
id: e.uid ?? String(index),
157+
start: { dateTime: e.startDate.toJSDate().toISOString() },
158+
end: { dateTime: e.endDate.toJSDate().toISOString() },
159+
title: e.summary ?? '',
160+
subtitle: e.description ?? '',
161+
description: e.location ?? '',
162+
color: stringToColour(e.summary ?? ''),
163+
}));
164+
}
165+
166+
async getEDT(adeid: string): Promise<CalendarEvent[]> {
167+
if (!adeid || adeid === 'demo') return [];
168+
169+
const icalData = await this.fetchEDT(adeid);
170+
if (!icalData) return getCalendarFromCache();
171+
const events = this.parseICal(icalData);
172+
const calEvents = this.convertToCalendarEvents(events);
173+
try {
174+
CALENDAR_FILE.write(JSON.stringify(calEvents));
175+
} catch { }
176+
return calEvents;
177+
}
178+
179+
// Widget
145180
findNextTwoCourses(events: ICALEvent[]): WidgetClass[] {
146181
const now = new Date();
147182
const windowStart = new Date(now.getTime() - 15 * 60 * 1000);
@@ -174,36 +209,61 @@ export class EDT {
174209
return this.findNextTwoCourses(events);
175210
}
176211

177-
async getNextEvent(adeid: string): Promise<NextEvent> {
178-
const icalData = await this.fetchEDT(adeid);
179-
if (!icalData) {
180-
return { summary: 'ADE Indisponible', location: 'Impossible de récupérer le cours.' };
212+
buildWidgetTimeline(
213+
events: CalendarEvent[],
214+
): Array<{ date: Date; props: NextClassWidgetProps }> {
215+
if (events.length === 0) {
216+
return [
217+
{
218+
date: new Date(),
219+
props: { courses: [], configured: false },
220+
},
221+
];
181222
}
182-
const events = this.parseICal(icalData);
183-
return this.findNextEvent(events);
184-
}
185223

186-
private convertToCalendarEvents(events: ICALEvent[]): CalendarEvent[] {
187-
return events.map((e, index) => ({
188-
id: e.uid ?? String(index),
189-
start: { dateTime: e.startDate.toJSDate().toISOString() },
190-
end: { dateTime: e.endDate.toJSDate().toISOString() },
191-
title: e.summary ?? '',
192-
subtitle: e.description ?? '',
193-
description: e.location ?? '',
194-
color: stringToColour(e.summary ?? ''),
195-
}));
196-
}
224+
const now = new Date();
225+
const cutoff = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 jours
197226

198-
async getEDT(adeid: string): Promise<CalendarEvent[]> {
199-
const icalData = await this.fetchEDT(adeid);
200-
if (!icalData) return getCalendarFromCache();
201-
const events = this.parseICal(icalData);
202-
const calEvents = this.convertToCalendarEvents(events);
203-
try {
204-
CALENDAR_FILE.write(JSON.stringify(calEvents));
205-
} catch { }
206-
return calEvents;
227+
const pad = (n: number) => String(n).padStart(2, '0');
228+
const fmt = (d: Date) => `${pad(d.getHours())}:${pad(d.getMinutes())}`;
229+
230+
// Tous les cours futurs triés par heure de début
231+
const allFuture = events
232+
.filter((e) => new Date(e.end.dateTime) > now)
233+
.sort(
234+
(a, b) =>
235+
new Date(a.start.dateTime).getTime() - new Date(b.start.dateTime).getTime(),
236+
);
237+
238+
// Les 2 prochains cours non encore terminés à partir d'un instant donné
239+
const getCoursesAt = (from: Date): WidgetClass[] =>
240+
allFuture
241+
.filter((e) => new Date(e.end.dateTime) > from)
242+
.slice(0, 2)
243+
.map((e) => ({
244+
title: e.title,
245+
room: e.description, // description = location dans CalendarEvent
246+
startTime: fmt(new Date(e.start.dateTime)),
247+
endTime: fmt(new Date(e.end.dateTime)),
248+
}));
249+
250+
const seen = new Set<number>();
251+
const timeline: Array<{ date: Date; props: NextClassWidgetProps }> = [];
252+
253+
seen.add(now.getTime());
254+
timeline.push({ date: now, props: { courses: getCoursesAt(now), configured: true } });
255+
256+
// Une entrée à chaque fin de cours dans les 24h
257+
for (const event of allFuture) {
258+
const endDate = new Date(event.end.dateTime);
259+
const ms = endDate.getTime();
260+
if (endDate <= cutoff && !seen.has(ms)) {
261+
seen.add(ms);
262+
timeline.push({ date: endDate, props: { courses: getCoursesAt(endDate), configured: true } });
263+
}
264+
}
265+
266+
return timeline;
207267
}
208268
}
209269

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface WidgetClass {
2727

2828
export interface NextClassWidgetProps {
2929
courses: WidgetClass[];
30+
configured?: boolean;
3031
}
3132

3233
export type HapticIntensity =

src/utils/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ import { haptics } from './haptics';
44
export async function handleURL(url: string): Promise<void> {
55
haptics('selection');
66
await WebBrowser.openBrowserAsync(url);
7-
}
7+
}

0 commit comments

Comments
 (0)