feat: 新增对第三方日历的支持 - #32
Conversation
# Conflicts: # services/calendar-sync.ts
There was a problem hiding this comment.
Pull request overview
该 PR 为“课程同步到系统日历”功能新增了对第三方/云日历的写入支持,并通过新增日历选择器与更稳健的同步/清理逻辑来解决 Issue #23(Google Calendar 等第三方日历导入/同步体验问题)。
Changes:
- 新增可写日历列表获取与“选择同步日历”的 UI(支持本地日历 + 第三方日历多选)。
- 重构日历同步流程:支持指定目标日历 ID、外部事件 ID 持久化与删除、按周重复事件以减少写入量。
- 新增设置项持久化
syncedCalendarIds,并在自动同步与删除流程中使用/维护该配置。
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| store/settings.ts | 新增并持久化 syncedCalendarIds 及其 setter,用于记录用户选择的同步目标日历。 |
| services/calendar-sync.ts | 核心同步逻辑重构:支持目标日历选择、外部事件 ID 清理、重复规则写入,并新增可写日历查询与清理 API。 |
| lib/i18n/locales/zh.json | 新增日历选择器与移除确认相关文案,并调整“系统日历”措辞为更通用的“日历”。 |
| lib/i18n/locales/en.json | 同步补充英文文案,匹配新选择器与移除确认流程。 |
| app/onboarding.tsx | 关闭日历同步时增加失败提示(并改为基于返回结果决定是否关同步开关)。 |
| app/(pages)/settings/calendar.tsx | 新增“选择同步日历”的 BottomSheet、移除确认 ConfirmSheet,以及同步/移除流程与状态管理。 |
| app/_layout.tsx | 自动同步时带上已选择的目标日历 IDs;无课程数据时清理已同步数据。 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export async function getWritableCalendars(): Promise<CalendarInfo[]> { | ||
| const calendars = await getCalendars(EntityTypes.EVENT); | ||
| return calendars | ||
| .filter( | ||
| (calendar) => | ||
| !isAppCalendar(calendar) && | ||
| calendar.allowsModifications && | ||
| (Platform.OS !== "android" || calendar.isPrimary), | ||
| ) | ||
| .map(toCalendarInfo); | ||
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d4ba1bfb1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const failedOldEventIds = await deleteEventIds(oldExternalEventIds); | ||
| setStoredEventIds([...failedOldEventIds, ...createdExternalIds]); |
There was a problem hiding this comment.
Preserve old events when replacement writes fail
When a resync writes at least one event but other writes fail, this unconditionally deletes every previously tracked external event. For example, if the user targets both the local and a cloud calendar and only the local writes succeed, count is nonzero, so all existing cloud events are removed even though none of their replacements were created; the operation then reports only partial success while the cloud schedule disappears. Preserve old events per target until that target's complete replacement has succeeded.
Useful? React with 👍 / 👎.
| try { | ||
| event = await ExpoCalendarEvent.get(id); | ||
| } catch { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Retain IDs when event lookup fails
If ExpoCalendarEvent.get(id) rejects for a transient provider or cloud-calendar error, this path treats the event as already absent and drops its ID from persistent tracking. A subsequent remove can therefore report success and disable sync while the course event remains in the user's external calendar with no way for the app to find it again. Only discard an ID for a confirmed not-found result; retain it for other lookup failures.
Useful? React with 👍 / 👎.
| for (const calendar of staleAppCalendars) { | ||
| await deleteCalendarSafe(calendar); | ||
| } | ||
| setStoredCalendarId(newAppCalendar?.id ?? null); |
There was a problem hiding this comment.
Abort replacement when an iOS calendar cannot be removed
On iOS, if deleting a stale app calendar fails, the return value is ignored and the stored ID is immediately replaced with the new calendar's ID. Because isAppCalendar now recognizes iOS calendars only by that single stored ID, the undeleted old calendar becomes permanently untracked, leaving duplicate course entries that later sync or removal operations cannot clean up. Handle a failed deletion before overwriting the stored ID.
Useful? React with 👍 / 👎.
6d4ba1b to
c84260c
Compare
Co-authored-by: zhxycn <admin@owo.cab>
fix #23