From b31b0764a5834fdab0ff80aacf15f52f1de84e6a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 15 Nov 2021 09:03:58 +0100 Subject: [PATCH] MOBILE-3909 calendar: Allow setting reminders for offline events --- src/addons/calendar/pages/day/day.page.ts | 7 +- .../pages/edit-event/edit-event.page.ts | 5 +- src/addons/calendar/pages/event/event.html | 4 +- src/addons/calendar/pages/event/event.page.ts | 209 ++++++++++++------ src/addons/calendar/pages/index/index.page.ts | 7 +- .../calendar/services/calendar-helper.ts | 2 + src/addons/calendar/services/calendar-sync.ts | 7 +- src/addons/calendar/services/calendar.ts | 12 +- 8 files changed, 166 insertions(+), 87 deletions(-) diff --git a/src/addons/calendar/pages/day/day.page.ts b/src/addons/calendar/pages/day/day.page.ts index 8e6d707eb..19963cb62 100644 --- a/src/addons/calendar/pages/day/day.page.ts +++ b/src/addons/calendar/pages/day/day.page.ts @@ -521,12 +521,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * @param eventId Event to load. */ gotoEvent(eventId: number): void { - if (eventId < 0) { - // It's an offline event, go to the edit page. - this.openEdit(eventId); - } else { - CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); - } + CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); } /** diff --git a/src/addons/calendar/pages/edit-event/edit-event.page.ts b/src/addons/calendar/pages/edit-event/edit-event.page.ts index e16c88b09..1ea01493d 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.page.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.page.ts @@ -564,7 +564,10 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { if (event) { CoreEvents.trigger( AddonCalendarProvider.NEW_EVENT_EVENT, - { eventId: event.id }, + { + eventId: event.id, + oldEventId: this.eventId, + }, this.currentSite.getId(), ); } else { diff --git a/src/addons/calendar/pages/event/event.html b/src/addons/calendar/pages/event/event.html index 3874350f4..e2279bd11 100644 --- a/src/addons/calendar/pages/event/event.html +++ b/src/addons/calendar/pages/event/event.html @@ -21,8 +21,8 @@ [priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"> - + { - if (data && data.eventId == this.eventId) { + if (data && data.eventId === this.eventId) { + this.eventLoaded = false; + this.refreshEvent(true, false); + } + }, this.currentSiteId); + + // Listen for event created. If user edits the data of a new offline event or it's sent to server, this event is triggered. + this.newEventObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { + if (this.eventId < 0 && data && (data.eventId === this.eventId || data.oldEventId === this.eventId)) { + this.eventId = data.eventId; this.eventLoaded = false; this.refreshEvent(true, false); } @@ -173,53 +183,22 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { * @return Promise resolved when done. */ async fetchEvent(sync = false, showErrors = false): Promise { - let deleted = false; - this.isOnline = CoreApp.isOnline(); if (sync) { - // Try to synchronize offline events. - try { - const result = await AddonCalendarSync.syncEvents(); - if (result.warnings && result.warnings.length) { - CoreDomUtils.showErrorModal(result.warnings[0]); - } + const deleted = await this.syncEvents(showErrors); - if (result.deleted && result.deleted.indexOf(this.eventId) != -1) { - // This event was deleted during the sync. - deleted = true; - } - - if (result.updated) { - // Trigger a manual sync event. - result.source = 'event'; - - CoreEvents.trigger( - AddonCalendarSyncProvider.MANUAL_SYNCED, - result, - this.currentSiteId, - ); - } - } catch (error) { - if (showErrors) { - CoreDomUtils.showErrorModalDefault(error, 'core.errorsync', true); - } + if (deleted) { + return; } } - if (deleted) { - return; - } - try { // Get the event data. - const event = await AddonCalendar.getEventById(this.eventId); - const formattedEvent = await AddonCalendarHelper.formatEventData(event); - this.event = formattedEvent; - - // Load reminders, and re-schedule them if needed (maybe the event time has changed). - this.loadReminders(); - AddonCalendar.scheduleEventsNotifications([this.event]); + if (this.eventId >= 0) { + const event = await AddonCalendar.getEventById(this.eventId); + this.event = await AddonCalendarHelper.formatEventData(event); + } try { const offlineEvent = AddonCalendarHelper.formatOfflineEventData( @@ -228,13 +207,28 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { // There is offline data, apply it. this.hasOffline = true; - - this.event = Object.assign(this.event, offlineEvent); + this.event = Object.assign(this.event || {}, offlineEvent); } catch { // No offline data. this.hasOffline = false; + + if (this.eventId < 0) { + // It's an offline event, but it wasn't found. Shouldn't happen. + CoreDomUtils.showErrorModal('Event not found.'); + CoreNavigator.back(); + + return; + } } + if (!this.event) { + return; // At this point we should always have the event, adding this check to avoid TS errors. + } + + // Load reminders, and re-schedule them if needed (maybe the event time has changed). + this.loadReminders(); + AddonCalendar.scheduleEventsNotifications([this.event]); + // Reset some of the calculated data. this.categoryPath = ''; this.courseName = ''; @@ -253,6 +247,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { } const promises: Promise[] = []; + const event = this.event; const courseId = this.event.courseid; if (courseId != this.siteHomeId) { @@ -266,16 +261,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { // If it's a group event, get the name of the group. if (courseId && this.event.groupid) { - promises.push(CoreGroups.getUserGroupsInCourse(courseId).then((groups) => { - const group = groups.find((group) => group.id == formattedEvent.groupid); - - this.groupName = group ? group.name : ''; - - return; - }).catch(() => { - // Error getting groups, just don't show the group name. - this.groupName = ''; - })); + promises.push(this.loadGroupName(this.event, courseId)); } if (this.event.iscategoryevent && this.event.category) { @@ -290,14 +276,14 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { // Check if event was deleted in offine. promises.push(AddonCalendarOffline.isEventDeleted(this.eventId).then((deleted) => { - formattedEvent.deleted = deleted; + event.deleted = deleted; return; })); // Re-calculate the formatted time so it uses the device date. promises.push(AddonCalendar.getCalendarTimeFormat().then(async (timeFormat) => { - formattedEvent.formattedtime = await AddonCalendar.formatEventTime(formattedEvent, timeFormat); + event.formattedtime = await AddonCalendar.formatEventTime(event, timeFormat); return; })); @@ -311,6 +297,69 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { this.syncIcon = CoreConstants.ICON_SYNC; } + /** + * Sync offline events. + * + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved with boolean: whether event was deleted on sync. + */ + protected async syncEvents(showErrors = false): Promise { + let deleted = false; + + // Try to synchronize offline events. + try { + const result = await AddonCalendarSync.syncEvents(); + if (result.warnings && result.warnings.length) { + CoreDomUtils.showErrorModal(result.warnings[0]); + } + + if (result.deleted && result.deleted.indexOf(this.eventId) != -1) { + // This event was deleted during the sync. + deleted = true; + } else if (this.eventId < 0 && result.offlineIdMap[this.eventId]) { + // Event was created, use the online ID. + this.eventId = result.offlineIdMap[this.eventId]; + } + + if (result.updated) { + // Trigger a manual sync event. + result.source = 'event'; + + CoreEvents.trigger( + AddonCalendarSyncProvider.MANUAL_SYNCED, + result, + this.currentSiteId, + ); + } + } catch (error) { + if (showErrors) { + CoreDomUtils.showErrorModalDefault(error, 'core.errorsync', true); + } + } + + return deleted; + } + + /** + * Load group name. + * + * @param event Event. + * @param courseId Course ID. + * @return Promise resolved when done. + */ + protected async loadGroupName(event: AddonCalendarEventToDisplay, courseId: number): Promise { + try { + const groups = await CoreGroups.getUserGroupsInCourse(courseId); + + const group = groups.find((group) => group.id == event.groupid); + this.groupName = group ? group.name : ''; + + } catch { + // Error getting groups, just don't show the group name. + this.groupName = ''; + } + } + /** * Add a reminder for this event. */ @@ -392,7 +441,9 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { const promises: Promise[] = []; - promises.push(AddonCalendar.invalidateEvent(this.eventId)); + if (this.eventId > 0) { + promises.push(AddonCalendar.invalidateEvent(this.eventId)); + } promises.push(AddonCalendar.invalidateTimeFormat()); await CoreUtils.allPromisesIgnoringErrors(promises); @@ -459,9 +510,14 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { const modal = await CoreDomUtils.showModalLoading('core.sending', true); try { - const sent = await AddonCalendar.deleteEvent(this.event.id, this.event.name, deleteAll); + let onlineEventDeleted = false; + if (this.event.id < 0) { + await AddonCalendarOffline.deleteEvent(this.event.id); + } else { + onlineEventDeleted = await AddonCalendar.deleteEvent(this.event.id, this.event.name, deleteAll); + } - if (sent) { + if (onlineEventDeleted) { // Event deleted, invalidate right days & months. try { await AddonCalendarHelper.refreshAfterChangeEvent(this.event, deleteAll ? this.event.eventcount : 1); @@ -471,12 +527,16 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { } // Trigger an event. - CoreEvents.trigger(AddonCalendarProvider.DELETED_EVENT_EVENT, { - eventId: this.eventId, - sent: sent, - }, CoreSites.getCurrentSiteId()); + if (this.event.id < 0) { + CoreEvents.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, CoreSites.getCurrentSiteId()); + } else { + CoreEvents.trigger(AddonCalendarProvider.DELETED_EVENT_EVENT, { + eventId: this.eventId, + sent: onlineEventDeleted, + }, CoreSites.getCurrentSiteId()); + } - if (sent) { + if (onlineEventDeleted || this.event.id < 0) { CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, 3000); // Event deleted, close the view. @@ -537,11 +597,21 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { // Event was deleted, close the view. CoreNavigator.back(); } else if (data.events && (!isManual || data.source != 'event')) { - const event = data.events.find((ev) => ev.id == this.eventId); + if (this.eventId < 0) { + if (data.offlineIdMap[this.eventId]) { + // Event was created, use the online ID. + this.eventId = data.offlineIdMap[this.eventId]; - if (event) { - this.eventLoaded = false; - this.refreshEvent(); + this.eventLoaded = false; + this.refreshEvent(); + } + } else { + const event = data.events.find((ev) => ev.id == this.eventId); + + if (event) { + this.eventLoaded = false; + this.refreshEvent(); + } } } } @@ -550,10 +620,11 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { * Page destroyed. */ ngOnDestroy(): void { - this.editEventObserver?.off(); - this.syncObserver?.off(); - this.manualSyncObserver?.off(); - this.onlineObserver?.unsubscribe(); + this.editEventObserver.off(); + this.syncObserver.off(); + this.manualSyncObserver.off(); + this.onlineObserver.unsubscribe(); + this.newEventObserver.off(); clearInterval(this.updateCurrentTime); } diff --git a/src/addons/calendar/pages/index/index.page.ts b/src/addons/calendar/pages/index/index.page.ts index 7e4f1eeb2..f2c8fb51b 100644 --- a/src/addons/calendar/pages/index/index.page.ts +++ b/src/addons/calendar/pages/index/index.page.ts @@ -301,12 +301,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { * @param eventId Event to load. */ gotoEvent(eventId: number): void { - if (eventId < 0) { - // It's an offline event, go to the edit page. - this.openEdit(eventId); - } else { - CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); - } + CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); } /** diff --git a/src/addons/calendar/services/calendar-helper.ts b/src/addons/calendar/services/calendar-helper.ts index b1a2081ec..d441b1c1d 100644 --- a/src/addons/calendar/services/calendar-helper.ts +++ b/src/addons/calendar/services/calendar-helper.ts @@ -245,6 +245,8 @@ export class AddonCalendarHelperProvider { format: 1, visible: 1, offline: true, + canedit: event.id < 0, + candelete: event.id < 0, timeduration: 0, }; diff --git a/src/addons/calendar/services/calendar-sync.ts b/src/addons/calendar/services/calendar-sync.ts index b6d4d0cf6..56f3e8a3c 100644 --- a/src/addons/calendar/services/calendar-sync.ts +++ b/src/addons/calendar/services/calendar-sync.ts @@ -124,6 +124,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider 0 ? eventId : 0, data, siteId); + const newEvent = await AddonCalendar.submitEventOnline(eventId, data, siteId); result.updated = true; result.events.push(newEvent); + if (eventId < 0) { + result.offlineIdMap[eventId] = newEvent.id; + } // Add data to invalidate. const numberOfRepetitions = data.repeat ? data.repeats : @@ -298,6 +302,7 @@ export const AddonCalendarSync = makeSingleton(AddonCalendarSyncProvider); export type AddonCalendarSyncEvents = { warnings: string[]; events: AddonCalendarEvent[]; + offlineIdMap: Record; // Map offline ID with online ID for created events. deleted: number[]; toinvalidate: AddonCalendarSyncInvalidateEvent[]; updated: boolean; diff --git a/src/addons/calendar/services/calendar.ts b/src/addons/calendar/services/calendar.ts index d7b0842d1..93780709c 100644 --- a/src/addons/calendar/services/calendar.ts +++ b/src/addons/calendar/services/calendar.ts @@ -1687,7 +1687,7 @@ export class AddonCalendarProvider { /** * Submit an event, either to create it or to edit it. It will fail if offline or cannot connect. * - * @param eventId ID of the event. If undefined/null, create a new event. + * @param eventId ID of the event. If undefined/null or negative number, create a new event. * @param formData Form data. * @param siteId Site ID. If not provided, current site. * @return Promise resolved when done. @@ -1700,7 +1700,7 @@ export class AddonCalendarProvider { const site = await CoreSites.getSite(siteId); // Add data that is "hidden" in web. - formData.id = eventId; + formData.id = eventId > 0 ? eventId : 0; formData.userid = site.getUserId(); formData.visible = 1; formData.instance = 0; @@ -1724,6 +1724,13 @@ export class AddonCalendarProvider { }); } + if (eventId < 0) { + // Offline event has been sent. Change reminders eventid if any. + await CoreUtils.ignoreErrors( + site.getDb().updateRecords(REMINDERS_TABLE, { eventid: result.event.id }, { eventid: eventId }), + ); + } + return result.event; } @@ -2240,6 +2247,7 @@ export type AddonCalendarEventToDisplay = Partial & */ export type AddonCalendarUpdatedEventEvent = { eventId: number; + oldEventId?: number; // Old event ID. Used when an offline event is sent. sent?: boolean; };