From e740fe4205e1767da235f4d4f912ae200d9dc568 Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Wed, 26 Jun 2019 08:16:12 +0200
Subject: [PATCH 1/5] MOBILE-3087 calendar: Support editing calendar events
---
scripts/langindex.json | 1 +
.../calendar/pages/edit-event/edit-event.html | 2 +-
.../calendar/pages/edit-event/edit-event.ts | 61 ++++--
src/addon/calendar/pages/event/event.html | 16 +-
src/addon/calendar/pages/event/event.ts | 197 ++++++++++++++++--
src/addon/calendar/pages/list/list.html | 2 +-
src/addon/calendar/pages/list/list.ts | 42 +++-
src/addon/calendar/providers/calendar-sync.ts | 12 +-
src/addon/calendar/providers/calendar.ts | 17 +-
src/assets/lang/en.json | 1 +
src/lang/en.json | 1 +
src/providers/utils/utils.ts | 4 +-
12 files changed, 292 insertions(+), 64 deletions(-)
diff --git a/scripts/langindex.json b/scripts/langindex.json
index dc86f5c89..6103e27f7 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -1484,6 +1484,7 @@
"core.image": "local_moodlemobileapp",
"core.imageviewer": "local_moodlemobileapp",
"core.info": "moodle",
+ "core.invalidformdata": "error",
"core.ios": "local_moodlemobileapp",
"core.labelsep": "langconfig",
"core.lastaccess": "moodle",
diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html
index 0cf401536..96edde1b9 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.html
+++ b/src/addon/calendar/pages/edit-event/edit-event.html
@@ -134,7 +134,7 @@
-
+
diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts
index bf394c205..e800800c2 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.ts
+++ b/src/addon/calendar/pages/edit-event/edit-event.ts
@@ -162,7 +162,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
}
if (this.eventId && !refresh) {
- // If editing an event, get offline data. Wait for sync first.
+ // Editing an event, get the event data. Wait for sync first.
promises.push(this.calendarSync.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(() => {
// Do not block if the scope is already destroyed.
@@ -170,29 +170,38 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
- // Get the event data if there's any.
+ // Get the event offline data if there's any.
return this.calendarOffline.getEvent(this.eventId).then((event) => {
this.hasOffline = true;
- // Load the data in the form.
- this.eventForm.controls.name.setValue(event.name);
- this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
- this.eventForm.controls.eventtype.setValue(event.eventtype);
- this.eventForm.controls.categoryid.setValue(event.categoryid || '');
- this.eventForm.controls.courseid.setValue(event.courseid || '');
- this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
- this.eventForm.controls.groupid.setValue(event.groupid || '');
- this.eventForm.controls.description.setValue(event.description);
- this.eventForm.controls.location.setValue(event.location);
- this.eventForm.controls.duration.setValue(event.duration);
- this.eventForm.controls.timedurationuntil.setValue(
- this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
- this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
- this.eventForm.controls.repeat.setValue(!!event.repeat);
- this.eventForm.controls.repeats.setValue(event.repeats || '1');
+ return event;
}).catch(() => {
// No offline data.
this.hasOffline = false;
+
+ if (this.eventId > 0) {
+ // It's an online event. get its data from server.
+ return this.calendarProvider.getEventById(this.eventId);
+ }
+ }).then((event) => {
+ if (event) {
+ // Load the data in the form.
+ this.eventForm.controls.name.setValue(event.name);
+ this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
+ this.eventForm.controls.eventtype.setValue(event.eventtype);
+ this.eventForm.controls.categoryid.setValue(event.categoryid || '');
+ this.eventForm.controls.courseid.setValue(event.courseid || '');
+ this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
+ this.eventForm.controls.groupid.setValue(event.groupid || '');
+ this.eventForm.controls.description.setValue(event.description);
+ this.eventForm.controls.location.setValue(event.location);
+ this.eventForm.controls.duration.setValue(event.duration);
+ this.eventForm.controls.timedurationuntil.setValue(
+ this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
+ this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
+ this.eventForm.controls.repeat.setValue(!!event.repeat);
+ this.eventForm.controls.repeats.setValue(event.repeats || '1');
+ }
});
}));
}
@@ -379,7 +388,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
}
// Send the data.
- const modal = this.domUtils.showModalLoading('core.sending');
+ const modal = this.domUtils.showModalLoading('core.sending', true);
this.calendarProvider.submitEvent(this.eventId, data).then((result) => {
this.returnToList(result.event);
@@ -399,13 +408,21 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
// Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy.
this.unblockSync();
- if (event) {
+ if (this.eventId > 0) {
+ // Editing an event.
const data: any = {
event: event
};
- this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId());
+ this.eventsProvider.trigger(AddonCalendarProvider.EDIT_EVENT_EVENT, data, this.currentSite.getId());
} else {
- this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, this.currentSite.getId());
+ if (event) {
+ const data: any = {
+ event: event
+ };
+ this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId());
+ } else {
+ this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, {}, this.currentSite.getId());
+ }
}
if (this.svComponent && this.svComponent.isOn()) {
diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html
index d823697c6..93e5bd285 100644
--- a/src/addon/calendar/pages/event/event.html
+++ b/src/addon/calendar/pages/event/event.html
@@ -1,13 +1,27 @@
+
+
+
+
+
+
+
+
+
-
+
+
+
+ {{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendarevent' | translate} }}
+
+
diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts
index 77d4bce09..6fb0a16d1 100644
--- a/src/addon/calendar/pages/event/event.ts
+++ b/src/addon/calendar/pages/event/event.ts
@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, ViewChild } from '@angular/core';
-import { IonicPage, Content, NavParams } from 'ionic-angular';
+import { Component, ViewChild, Optional, OnDestroy, NgZone } from '@angular/core';
+import { IonicPage, Content, NavParams, NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
+import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
+import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
+import { CoreAppProvider } from '@providers/app';
+import { CoreEventsProvider } from '@providers/events';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreSitesProvider } from '@providers/sites';
@@ -25,6 +29,8 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreGroupsProvider } from '@providers/groups';
+import { CoreSplitViewComponent } from '@components/split-view/split-view';
+import { Network } from '@ionic-native/network';
/**
* Page that displays a single calendar event.
@@ -34,11 +40,17 @@ import { CoreGroupsProvider } from '@providers/groups';
selector: 'page-addon-calendar-event',
templateUrl: 'event.html',
})
-export class AddonCalendarEventPage {
+export class AddonCalendarEventPage implements OnDestroy {
@ViewChild(Content) content: Content;
protected eventId;
protected siteHomeId: number;
+ protected editEventObserver: any;
+ protected syncObserver: any;
+ protected manualSyncObserver: any;
+ protected onlineObserver: any;
+ protected currentSiteId: string;
+
eventLoaded: boolean;
notificationFormat: string;
notificationMin: string;
@@ -55,17 +67,31 @@ export class AddonCalendarEventPage {
currentTime: number;
defaultTime: number;
reminders: any[];
+ canEdit = false;
+ hasOffline = false;
+ isOnline = false;
+ syncIcon: string; // Sync icon.
+ isSplitViewOn = false;
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider,
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider,
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
- private groupsProvider: CoreGroupsProvider) {
+ private groupsProvider: CoreGroupsProvider, @Optional() private svComponent: CoreSplitViewComponent,
+ private navCtrl: NavController, private eventsProvider: CoreEventsProvider, network: Network, zone: NgZone,
+ private calendarSync: AddonCalendarSyncProvider, private appProvider: CoreAppProvider,
+ private calendarOffline: AddonCalendarOfflineProvider) {
this.eventId = navParams.get('id');
this.notificationsEnabled = localNotificationsProvider.isAvailable();
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
+ this.currentSiteId = sitesProvider.getCurrentSiteId();
+ this.isSplitViewOn = this.svComponent && this.svComponent.isOn();
+
+ // Check if site supports editing. No need to check allowed types, event.canedit already does it.
+ this.canEdit = this.calendarProvider.canEditEventsInSite();
+
if (this.notificationsEnabled) {
this.calendarProvider.getEventReminders(this.eventId).then((reminders) => {
this.reminders = reminders;
@@ -79,34 +105,105 @@ export class AddonCalendarEventPage {
this.notificationFormat = this.timeUtils.fixFormatForDatetime(this.timeUtils.convertPHPToMoment(
this.translate.instant('core.strftimedatetime')));
}
+
+ // Listen for event edited. If current event is edited, reload the data.
+ this.editEventObserver = eventsProvider.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => {
+ if (data && data.event && data.event.id == this.eventId) {
+ this.eventLoaded = false;
+ this.refreshEvent(true, false);
+ }
+ }, this.currentSiteId);
+
+ // Refresh data if this calendar event is synchronized automatically.
+ this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, this.checkSyncResult.bind(this, false),
+ this.currentSiteId);
+
+ // Refresh data if calendar events are synchronized manually but not by this page.
+ this.manualSyncObserver = eventsProvider.on(AddonCalendarSyncProvider.MANUAL_SYNCED, this.checkSyncResult.bind(this, true),
+ this.currentSiteId);
+
+ // Refresh online status when changes.
+ this.onlineObserver = network.onchange().subscribe((online) => {
+ // Execute the callback in the Angular zone, so change detection doesn't stop working.
+ zone.run(() => {
+ this.isOnline = online;
+ });
+ });
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
- this.fetchEvent().finally(() => {
- this.eventLoaded = true;
- });
+ this.syncIcon = 'spinner';
+
+ this.fetchEvent();
}
/**
* Fetches the event and updates the view.
*
+ * @param {boolean} [sync] Whether it should try to synchronize offline events.
+ * @param {boolean} [showErrors] Whether to show sync errors to the user.
* @return {Promise} Promise resolved when done.
*/
- fetchEvent(): Promise {
+ fetchEvent(sync?: boolean, showErrors?: boolean): Promise {
const currentSite = this.sitesProvider.getCurrentSite(),
canGetById = this.calendarProvider.isGetEventByIdAvailable();
let promise;
- if (canGetById) {
- promise = this.calendarProvider.getEventById(this.eventId);
+ this.isOnline = this.appProvider.isOnline();
+
+ if (sync) {
+ // Try to synchronize offline events.
+ promise = this.calendarSync.syncEvents().then((result) => {
+ if (result.warnings && result.warnings.length) {
+ this.domUtils.showErrorModal(result.warnings[0]);
+ }
+
+ if (result.updated) {
+ // Trigger a manual sync event.
+ result.source = 'event';
+
+ this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
+ }
+ }).catch((error) => {
+ if (showErrors) {
+ this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
+ }
+ });
} else {
- promise = this.calendarProvider.getEvent(this.eventId);
+ promise = Promise.resolve();
}
- return promise.then((event) => {
+ return promise.then(() => {
+ const promises = [];
+
+ // Get the event data.
+ if (canGetById) {
+ promises.push(this.calendarProvider.getEventById(this.eventId));
+ } else {
+ promises.push(this.calendarProvider.getEvent(this.eventId));
+ }
+
+ // Get offline data.
+ promises.push(this.calendarOffline.getEvent(this.eventId).catch(() => {
+ // No offline data.
+ }));
+
+ return Promise.all(promises).then((results) => {
+ if (results[1]) {
+ // There is offline data, apply it.
+ this.hasOffline = true;
+ Object.assign(results[0], results[1]);
+ } else {
+ this.hasOffline = false;
+ }
+
+ return results[0];
+ });
+
+ }).then((event) => {
const promises = [];
this.calendarHelper.formatEventData(event);
@@ -196,6 +293,9 @@ export class AddonCalendarEventPage {
return Promise.all(promises);
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true);
+ }).finally(() => {
+ this.eventLoaded = true;
+ this.syncIcon = 'sync';
});
}
@@ -246,16 +346,77 @@ export class AddonCalendarEventPage {
});
}
+ /**
+ * Refresh the data.
+ *
+ * @param {any} [refresher] Refresher.
+ * @param {Function} [done] Function to call when done.
+ * @param {boolean} [showErrors] Whether to show sync errors to the user.
+ * @return {Promise} Promise resolved when done.
+ */
+ doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise {
+ if (this.eventLoaded) {
+ return this.refreshEvent(true, showErrors).finally(() => {
+ refresher && refresher.complete();
+ done && done();
+ });
+ }
+
+ return Promise.resolve();
+ }
+
/**
* Refresh the event.
*
- * @param {any} refresher Refresher.
+ * @param {boolean} [sync] Whether it should try to synchronize offline events.
+ * @param {boolean} [showErrors] Whether to show sync errors to the user.
+ * @return {Promise} Promise resolved when done.
*/
- refreshEvent(refresher: any): void {
- this.calendarProvider.invalidateEvent(this.eventId).finally(() => {
- this.fetchEvent().finally(() => {
- refresher.complete();
- });
+ refreshEvent(sync?: boolean, showErrors?: boolean): Promise {
+ this.syncIcon = 'spinner';
+
+ return this.calendarProvider.invalidateEvent(this.eventId).catch(() => {
+ // Ignore errors.
+ }).then(() => {
+ return this.fetchEvent(sync, showErrors);
});
}
+
+ /**
+ * Open the page to edit the event.
+ */
+ openEdit(): void {
+ // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
+ const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
+ navCtrl.push('AddonCalendarEditEventPage', {eventId: this.eventId});
+ }
+
+ /**
+ * Check the result of an automatic sync or a manual sync not done by this page.
+ *
+ * @param {boolean} isManual Whether it's a manual sync.
+ * @param {any} data Sync result.
+ */
+ protected checkSyncResult(isManual: boolean, data: any): void {
+ if (data && data.events && (!isManual || data.source != 'event')) {
+ const event = data.events.find((ev) => {
+ return ev.id == this.eventId;
+ });
+
+ if (event) {
+ this.eventLoaded = false;
+ this.refreshEvent();
+ }
+ }
+ }
+
+ /**
+ * Page destroyed.
+ */
+ ngOnDestroy(): void {
+ this.editEventObserver && this.editEventObserver.off();
+ this.syncObserver && this.syncObserver.off();
+ this.manualSyncObserver && this.manualSyncObserver.off();
+ this.onlineObserver && this.onlineObserver.unsubscribe();
+ }
}
diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html
index 0204b9acf..57563190d 100644
--- a/src/addon/calendar/pages/list/list.html
+++ b/src/addon/calendar/pages/list/list.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts
index 1a57c4c0b..3418f0514 100644
--- a/src/addon/calendar/pages/list/list.ts
+++ b/src/addon/calendar/pages/list/list.ts
@@ -59,8 +59,11 @@ export class AddonCalendarListPage implements OnDestroy {
protected preSelectedCourseId: number;
protected newEventObserver: any;
protected discardedObserver: any;
+ protected editEventObserver: any;
protected syncObserver: any;
+ protected manualSyncObserver: any;
protected onlineObserver: any;
+ protected currentSiteId: string;
courses: any[];
eventsLoaded = false;
@@ -80,7 +83,7 @@ export class AddonCalendarListPage implements OnDestroy {
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
- private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, zone: NgZone,
+ private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, zone: NgZone,
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
@@ -88,12 +91,13 @@ export class AddonCalendarListPage implements OnDestroy {
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
this.notificationsEnabled = localNotificationsProvider.isAvailable();
+ this.currentSiteId = sitesProvider.getCurrentSiteId();
if (this.notificationsEnabled) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
calendarProvider.scheduleEventsNotifications(this.events);
- }, sitesProvider.getCurrentSiteId());
+ }, this.currentSiteId);
}
this.eventId = navParams.get('eventId') || false;
@@ -116,7 +120,7 @@ export class AddonCalendarListPage implements OnDestroy {
}
});
}
- }, sitesProvider.getCurrentSiteId());
+ }, this.currentSiteId);
// Listen for new event discarded event. When it does, reload the data.
this.discardedObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
@@ -127,13 +131,29 @@ export class AddonCalendarListPage implements OnDestroy {
this.eventsLoaded = false;
this.refreshEvents(true, false);
- }, sitesProvider.getCurrentSiteId());
+ }, this.currentSiteId);
+
+ // Listen for events edited. When an event is edited, reload the data.
+ this.editEventObserver = eventsProvider.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => {
+ if (data && data.event) {
+ this.eventsLoaded = false;
+ this.refreshEvents(true, false);
+ }
+ }, this.currentSiteId);
// Refresh data if calendar events are synchronized automatically.
this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
this.eventsLoaded = false;
this.refreshEvents();
- }, sitesProvider.getCurrentSiteId());
+ }, this.currentSiteId);
+
+ // Refresh data if calendar events are synchronized manually but not by this page.
+ this.manualSyncObserver = eventsProvider.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => {
+ if (data && data.source != 'list') {
+ this.eventsLoaded = false;
+ this.refreshEvents();
+ }
+ }, this.currentSiteId);
// Refresh online status when changes.
this.onlineObserver = network.onchange().subscribe((online) => {
@@ -187,9 +207,9 @@ export class AddonCalendarListPage implements OnDestroy {
if (result.updated) {
// Trigger a manual sync event.
- this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, {
- source: 'list'
- }, this.sitesProvider.getCurrentSiteId());
+ result.source = 'list';
+
+ this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
}
}).catch((error) => {
if (showErrors) {
@@ -530,6 +550,8 @@ export class AddonCalendarListPage implements OnDestroy {
* @param {number} [eventId] Event ID to edit.
*/
openEdit(eventId?: number): void {
+ this.eventId = undefined;
+
const params: any = {};
if (eventId) {
@@ -574,7 +596,9 @@ export class AddonCalendarListPage implements OnDestroy {
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
this.newEventObserver && this.newEventObserver.off();
this.discardedObserver && this.discardedObserver.off();
+ this.editEventObserver && this.editEventObserver.off();
this.syncObserver && this.syncObserver.off();
- this.onlineObserver && this.onlineObserver.off();
+ this.manualSyncObserver && this.manualSyncObserver.off();
+ this.onlineObserver && this.onlineObserver.unsubscribe();
}
}
diff --git a/src/addon/calendar/providers/calendar-sync.ts b/src/addon/calendar/providers/calendar-sync.ts
index cb9eb4b63..834d1ac34 100644
--- a/src/addon/calendar/providers/calendar-sync.ts
+++ b/src/addon/calendar/providers/calendar-sync.ts
@@ -37,8 +37,6 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
static MANUAL_SYNCED = 'addon_calendar_manual_synced';
static SYNC_ID = 'calendar';
- protected componentTranslate: string;
-
constructor(translate: TranslateService,
appProvider: CoreAppProvider,
courseProvider: CoreCourseProvider,
@@ -54,8 +52,6 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
super('AddonCalendarSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils);
-
- this.componentTranslate = this.translate.instant('addon.calendar.calendarevent');
}
/**
@@ -66,7 +62,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
* @return {Promise} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllEvents(siteId?: string, force?: boolean): Promise {
- return this.syncOnSites('all calendars', this.syncAllEventsFunc.bind(this), [force], siteId);
+ return this.syncOnSites('all calendar events', this.syncAllEventsFunc.bind(this), [force], siteId);
}
/**
@@ -77,6 +73,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
* @return {Promise} Promise resolved if sync is successful, rejected if sync fails.
*/
protected syncAllEventsFunc(siteId: string, force?: boolean): Promise {
+
const promise = force ? this.syncEvents(siteId) : this.syncEventsIfNeeded(siteId);
return promise.then((result) => {
@@ -196,7 +193,8 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
if (this.syncProvider.isBlocked(AddonCalendarProvider.COMPONENT, event.id, siteId)) {
this.logger.debug('Cannot sync event ' + event.name + ' because it is blocked.');
- return Promise.reject(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
+ return Promise.reject(this.translate.instant('core.errorsyncblocked',
+ {$a: this.translate.instant('addon.calendar.calendarevent')}));
}
// Try to send the data.
@@ -216,7 +214,7 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
return this.calendarOffline.deleteEvent(event.id, siteId).then(() => {
// Event deleted, add a warning.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
- component: this.componentTranslate,
+ component: this.translate.instant('addon.calendar.calendarevent'),
name: event.name,
error: this.textUtils.getErrorMessageFromError(error)
}));
diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts
index 6e5bc965b..0b9af1e6b 100644
--- a/src/addon/calendar/providers/calendar.ts
+++ b/src/addon/calendar/providers/calendar.ts
@@ -27,6 +27,7 @@ import { CoreConfigProvider } from '@providers/config';
import { ILocalNotification } from '@ionic-native/local-notifications';
import { SQLiteDB } from '@classes/sqlitedb';
import { AddonCalendarOfflineProvider } from './calendar-offline';
+import { TranslateService } from '@ngx-translate/core';
/**
* Service to handle calendar events.
@@ -40,6 +41,7 @@ export class AddonCalendarProvider {
static DEFAULT_NOTIFICATION_TIME = 60;
static NEW_EVENT_EVENT = 'addon_calendar_new_event';
static NEW_EVENT_DISCARDED_EVENT = 'addon_calendar_new_event_discarded';
+ static EDIT_EVENT_EVENT = 'addon_calendar_edit_event';
static TYPE_CATEGORY = 'category';
static TYPE_COURSE = 'course';
static TYPE_GROUP = 'group';
@@ -218,7 +220,7 @@ export class AddonCalendarProvider {
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider,
private utils: CoreUtilsProvider, private calendarOffline: AddonCalendarOfflineProvider,
- private appProvider: CoreAppProvider) {
+ private appProvider: CoreAppProvider, private translate: TranslateService) {
this.logger = logger.getInstance('AddonCalendarProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema);
}
@@ -980,7 +982,12 @@ export class AddonCalendarProvider {
formData.userid = site.getUserId();
formData.visible = 1;
formData.instance = 0;
- formData['_qf__core_calendar_local_event_forms_create'] = 1;
+
+ if (eventId > 0) {
+ formData['_qf__core_calendar_local_event_forms_update'] = 1;
+ } else {
+ formData['_qf__core_calendar_local_event_forms_create'] = 1;
+ }
const params = {
formdata: this.utils.objectToGetParams(formData)
@@ -988,7 +995,11 @@ export class AddonCalendarProvider {
return site.write('core_calendar_submit_create_update_form', params).then((result) => {
if (result.validationerror) {
- return Promise.reject(this.utils.createFakeWSError(''));
+ // Simulate a WS error.
+ return Promise.reject({
+ message: this.translate.instant('core.invalidformdata'),
+ errorcode: 'validationerror'
+ });
}
return result.event;
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index a7884b46f..e8a1c8227 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -1484,6 +1484,7 @@
"core.image": "Image",
"core.imageviewer": "Image viewer",
"core.info": "Information",
+ "core.invalidformdata": "Incorrect form data",
"core.ios": "iOS",
"core.labelsep": ":",
"core.lastaccess": "Last access",
diff --git a/src/lang/en.json b/src/lang/en.json
index b5c79cb94..4ec6a60a4 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -117,6 +117,7 @@
"image": "Image",
"imageviewer": "Image viewer",
"info": "Information",
+ "invalidformdata": "Incorrect form data",
"ios": "iOS",
"labelsep": ":",
"lastaccess": "Last access",
diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts
index 0f9f71b3b..95cd5cee1 100644
--- a/src/providers/utils/utils.ts
+++ b/src/providers/utils/utils.ts
@@ -1058,7 +1058,7 @@ export class CoreUtilsProvider {
* Convert an object to a format of GET param. E.g.: {a: 1, b: 2} -> a=1&b=2
*
* @param {any} object Object to convert.
- * @param {boolean} [removeEmpty=true] Whether to remove params whose value is empty/null/undefined.
+ * @param {boolean} [removeEmpty=true] Whether to remove params whose value is null/undefined.
* @return {string} GET params.
*/
objectToGetParams(object: any, removeEmpty: boolean = true): string {
@@ -1070,7 +1070,7 @@ export class CoreUtilsProvider {
for (const name in flattened) {
let value = flattened[name];
- if (removeEmpty && (value === null || typeof value == 'undefined' || value === '')) {
+ if (removeEmpty && (value === null || typeof value == 'undefined')) {
continue;
}
From 089c56b56bd75135d323e3b0d00eb3ae87628d35 Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Wed, 26 Jun 2019 15:19:27 +0200
Subject: [PATCH 2/5] MOBILE-3087 calendar: Fix 'lost' events when loading more
events
When loading more events, it could happen that some events weren't displayed because the timestart was recalculated using the time the request was made. E.g. if I loaded the first events and, 2 minutes later, I loaded more events, there were 2 minutes where we didn't get events.
---
src/addon/calendar/pages/list/list.ts | 9 +++++++--
src/addon/calendar/providers/calendar.ts | 19 +++++++++++--------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts
index 3418f0514..029234a74 100644
--- a/src/addon/calendar/pages/list/list.ts
+++ b/src/addon/calendar/pages/list/list.ts
@@ -21,6 +21,7 @@ import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
@@ -43,6 +44,7 @@ export class AddonCalendarListPage implements OnDestroy {
@ViewChild(Content) content: Content;
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
+ protected initialTime = 0;
protected daysLoaded = 0;
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
protected categoriesRetrieved = false;
@@ -87,7 +89,7 @@ export class AddonCalendarListPage implements OnDestroy {
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
- network: Network) {
+ network: Network, private timeUtils: CoreTimeUtilsProvider) {
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
this.notificationsEnabled = localNotificationsProvider.isAvailable();
@@ -192,6 +194,7 @@ export class AddonCalendarListPage implements OnDestroy {
* @return {Promise} Promise resolved when done.
*/
fetchData(refresh?: boolean, sync?: boolean, showErrors?: boolean): Promise {
+ this.initialTime = this.timeUtils.timestamp();
this.daysLoaded = 0;
this.emptyEventsTimes = 0;
this.isOnline = this.appProvider.isOnline();
@@ -269,7 +272,9 @@ export class AddonCalendarListPage implements OnDestroy {
fetchEvents(refresh?: boolean): Promise {
this.loadMoreError = false;
- return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
+ return this.calendarProvider.getEventsList(this.initialTime, this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL)
+ .then((events) => {
+
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
if (events.length === 0) {
this.emptyEventsTimes++;
diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts
index 0b9af1e6b..c04ebaa2a 100644
--- a/src/addon/calendar/providers/calendar.ts
+++ b/src/addon/calendar/providers/calendar.ts
@@ -537,16 +537,20 @@ export class AddonCalendarProvider {
* Get the events in a certain period. The period is calculated like this:
* start time: now + daysToStart
* end time: start time + daysInterval
- * E.g. using provider.getEventsList(30, 30) is going to get the events starting after 30 days from now
+ * E.g. using provider.getEventsList(undefined, 30, 30) is going to get the events starting after 30 days from now
* and ending before 60 days from now.
*
- * @param {number} [daysToStart=0] Number of days from now to start getting events.
+ * @param {number} [initialTime] Timestamp when the first fetch was done. If not defined, current time.
+ * @param {number} [daysToStart=0] Number of days from now to start getting events.
* @param {number} [daysInterval=30] Number of days between timestart and timeend.
* @param {string} [siteId] Site to get the events from. If not defined, use current site.
* @return {Promise} Promise to be resolved when the participants are retrieved.
*/
- getEventsList(daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string)
- : Promise {
+ getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL,
+ siteId?: string): Promise {
+
+ initialTime = initialTime || this.timeUtils.timestamp();
+
return this.sitesProvider.getSite(siteId).then((site) => {
siteId = site.getId();
const promises = [];
@@ -561,9 +565,8 @@ export class AddonCalendarProvider {
}));
return Promise.all(promises).then(() => {
- const now = this.timeUtils.timestamp(),
- start = now + (CoreConstants.SECONDS_DAY * daysToStart),
- end = start + (CoreConstants.SECONDS_DAY * daysInterval),
+ const start = initialTime + (CoreConstants.SECONDS_DAY * daysToStart),
+ end = start + (CoreConstants.SECONDS_DAY * daysInterval) - 1,
data = {
options: {
userevents: 1,
@@ -733,7 +736,7 @@ export class AddonCalendarProvider {
return this.isDisabled(siteId).then((disabled) => {
if (!disabled) {
// Get first events.
- return this.getEventsList(undefined, undefined, siteId).then((events) => {
+ return this.getEventsList(undefined, undefined, undefined, siteId).then((events) => {
return this.scheduleEventsNotifications(events, siteId);
});
}
From 649ad7a1a2cc67de0eb97f8964992615f1c9691d Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Thu, 27 Jun 2019 10:29:24 +0200
Subject: [PATCH 3/5] MOBILE-3087 calendar: Display offline events in their
right position
---
.../calendar/pages/edit-event/edit-event.html | 2 +-
.../calendar/pages/edit-event/edit-event.ts | 11 +-
src/addon/calendar/pages/list/list.html | 18 +--
src/addon/calendar/pages/list/list.ts | 106 ++++++++++++++----
src/addon/calendar/providers/calendar.ts | 1 +
5 files changed, 100 insertions(+), 38 deletions(-)
diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html
index 96edde1b9..7330b752e 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.html
+++ b/src/addon/calendar/pages/edit-event/edit-event.html
@@ -9,7 +9,7 @@
-
+
+
+ {{ 'core.notsent' | translate }}
+
diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts
index 029234a74..e3ce88a44 100644
--- a/src/addon/calendar/pages/list/list.ts
+++ b/src/addon/calendar/pages/list/list.ts
@@ -31,6 +31,7 @@ import { CoreAppProvider } from '@providers/app';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import * as moment from 'moment';
import { Network } from '@ionic-native/network';
+import { CoreConstants } from '@core/constants';
/**
* Page that displays the list of calendar events.
@@ -69,7 +70,8 @@ export class AddonCalendarListPage implements OnDestroy {
courses: any[];
eventsLoaded = false;
- events = [];
+ events = []; // Events (both online and offline).
+ onlineEvents = [];
offlineEvents = [];
notificationsEnabled = false;
filteredEvents = [];
@@ -98,7 +100,7 @@ export class AddonCalendarListPage implements OnDestroy {
if (this.notificationsEnabled) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
- calendarProvider.scheduleEventsNotifications(this.events);
+ calendarProvider.scheduleEventsNotifications(this.onlineEvents);
}, this.currentSiteId);
}
@@ -179,8 +181,12 @@ export class AddonCalendarListPage implements OnDestroy {
this.fetchData(false, true, false).then(() => {
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
- // Take first and load it.
- this.gotoEvent(this.events[0].id);
+ // Take first online event and load it. If no online event, load the first offline.
+ if (this.onlineEvents[0]) {
+ this.gotoEvent(this.onlineEvents[0].id);
+ } else {
+ this.gotoEvent(this.offlineEvents[0].id);
+ }
}
});
}
@@ -252,8 +258,11 @@ export class AddonCalendarListPage implements OnDestroy {
this.hasOffline = !!events.length;
// Format data and sort by timestart.
- events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
- this.offlineEvents = events.sort((a, b) => a.timestart - b.timestart);
+ events.forEach((event) => {
+ event.offline = true;
+ this.calendarHelper.formatEventData(event);
+ });
+ this.offlineEvents = this.sortEvents(events);
}));
return Promise.all(promises);
@@ -273,39 +282,37 @@ export class AddonCalendarListPage implements OnDestroy {
this.loadMoreError = false;
return this.calendarProvider.getEventsList(this.initialTime, this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL)
- .then((events) => {
+ .then((onlineEvents) => {
- this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
- if (events.length === 0) {
+ if (onlineEvents.length === 0) {
this.emptyEventsTimes++;
if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
this.canLoadMore = false;
if (refresh) {
- this.events = [];
+ this.onlineEvents = [];
this.filteredEvents = [];
+ this.events = this.offlineEvents;
}
} else {
// No events returned, load next events.
+ this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
+
return this.fetchEvents();
}
} else {
- events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
+ onlineEvents.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
- // Sort the events by timestart, they're ordered by id.
- events.sort((a, b) => {
- if (a.timestart == b.timestart) {
- return a.timeduration - b.timeduration;
- }
+ // Get the merged events of this period.
+ const events = this.mergeEvents(onlineEvents);
- return a.timestart - b.timestart;
- });
-
- this.getCategories = this.shouldLoadCategories(events);
+ this.getCategories = this.shouldLoadCategories(onlineEvents);
if (refresh) {
+ this.onlineEvents = onlineEvents;
this.events = events;
} else {
// Filter events with same ID. Repeated events are returned once per WS call, show them only once.
+ this.onlineEvents = this.utils.mergeArraysWithoutDuplicates(this.onlineEvents, onlineEvents, 'id');
this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id');
}
this.filteredEvents = this.getFilteredEvents();
@@ -318,7 +325,9 @@ export class AddonCalendarListPage implements OnDestroy {
this.canLoadMore = true;
// Schedule notifications for the events retrieved (might have new events).
- this.calendarProvider.scheduleEventsNotifications(this.events);
+ this.calendarProvider.scheduleEventsNotifications(this.onlineEvents);
+
+ this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
}
// Resize the content so infinite loading is able to calculate if it should load more items or not.
@@ -441,6 +450,61 @@ export class AddonCalendarListPage implements OnDestroy {
});
}
+ /**
+ * Merge a period of online events with the offline events of that period.
+ *
+ * @param {any[]} onlineEvents Online events.
+ * @return {any[]} Merged events.
+ */
+ protected mergeEvents(onlineEvents: any[]): any[] {
+ if (!this.offlineEvents || !this.offlineEvents.length) {
+ // No offline events, nothing to merge.
+ return onlineEvents;
+ }
+
+ const start = this.initialTime + (CoreConstants.SECONDS_DAY * this.daysLoaded),
+ end = start + (CoreConstants.SECONDS_DAY * AddonCalendarProvider.DAYS_INTERVAL) - 1;
+
+ // First of all, remove the online events that were modified in offline.
+ let result = onlineEvents.filter((event) => {
+ const offlineEvent = this.offlineEvents.find((ev) => {
+ return ev.id == event.id;
+ });
+
+ return !offlineEvent;
+ });
+
+ // Now get the offline events that belong to this period.
+ const periodOfflineEvents = this.offlineEvents.filter((event) => {
+ if (this.daysLoaded == 0 && event.timestart < start) {
+ // Display offline events that are previous to current time to allow editing them.
+ return true;
+ }
+
+ return (event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end;
+ });
+
+ // Merge both arrays and sort them.
+ result = result.concat(periodOfflineEvents);
+
+ return this.sortEvents(result);
+ }
+
+ /**
+ * Sort events by timestart.
+ *
+ * @param {any[]} events List to sort.
+ */
+ protected sortEvents(events: any[]): any[] {
+ return events.sort((a, b) => {
+ if (a.timestart == b.timestart) {
+ return a.timeduration - b.timeduration;
+ }
+
+ return a.timestart - b.timestart;
+ });
+ }
+
/**
* Refresh the data.
*
diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts
index c04ebaa2a..bfd3a03df 100644
--- a/src/addon/calendar/providers/calendar.ts
+++ b/src/addon/calendar/providers/calendar.ts
@@ -591,6 +591,7 @@ export class AddonCalendarProvider {
const preSets = {
cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval),
getCacheUsingCacheKey: true,
+ uniqueCacheKey: true,
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
From 12ce9f63b51bd2e735880a39b8eef0908395a407 Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Thu, 27 Jun 2019 12:34:08 +0200
Subject: [PATCH 4/5] MOBILE-3087 calendar: Support repeateditall setting
---
scripts/langindex.json | 4 ++
src/addon/calendar/lang/en.json | 4 ++
.../calendar/pages/edit-event/edit-event.html | 39 +++++++++++++------
.../calendar/pages/edit-event/edit-event.scss | 2 +-
.../calendar/pages/edit-event/edit-event.ts | 38 ++++++++++++++----
.../calendar/providers/calendar-offline.ts | 10 +++++
src/assets/lang/en.json | 4 ++
7 files changed, 81 insertions(+), 20 deletions(-)
diff --git a/scripts/langindex.json b/scripts/langindex.json
index 6103e27f7..75ad66948 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -96,6 +96,7 @@
"addon.calendar.errorloadevents": "local_moodlemobileapp",
"addon.calendar.eventduration": "calendar",
"addon.calendar.eventendtime": "calendar",
+ "addon.calendar.eventkind": "calendar",
"addon.calendar.eventname": "calendar",
"addon.calendar.eventstarttime": "calendar",
"addon.calendar.eventtype": "calendar",
@@ -106,6 +107,9 @@
"addon.calendar.noevents": "local_moodlemobileapp",
"addon.calendar.nopermissiontoupdatecalendar": "error",
"addon.calendar.reminders": "local_moodlemobileapp",
+ "addon.calendar.repeatedevents": "calendar",
+ "addon.calendar.repeateditall": "calendar",
+ "addon.calendar.repeateditthis": "calendar",
"addon.calendar.repeatevent": "calendar",
"addon.calendar.repeatweeksl": "calendar",
"addon.calendar.setnewreminder": "local_moodlemobileapp",
diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json
index a7a531836..d5cf329a2 100644
--- a/src/addon/calendar/lang/en.json
+++ b/src/addon/calendar/lang/en.json
@@ -12,6 +12,7 @@
"errorloadevents": "Error loading events.",
"eventduration": "Duration",
"eventendtime": "End time",
+ "eventkind": "Type of event",
"eventname": "Event title",
"eventstarttime": "Start time",
"eventtype": "Event type",
@@ -22,6 +23,9 @@
"noevents": "There are no events",
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
"reminders": "Reminders",
+ "repeatedevents": "Repeated events",
+ "repeateditall": "Also apply changes to the other {{$a}} events in this repeat series",
+ "repeateditthis": "Apply changes to this event only",
"repeatevent": "Repeat this event",
"repeatweeksl": "Repeat weekly, creating altogether",
"setnewreminder": "Set a new reminder",
diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html
index 7330b752e..f57a84915 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.html
+++ b/src/addon/calendar/pages/edit-event/edit-event.html
@@ -26,7 +26,7 @@
- {{ 'addon.calendar.eventtype' | translate }}
+ {{ 'addon.calendar.eventkind' | translate }}
{{ type.name | translate }}
@@ -96,8 +96,8 @@
-
-
{{ 'addon.calendar.eventduration' | translate }}
+
+ {{ 'addon.calendar.eventduration' | translate }}
{{ 'addon.calendar.durationnone' | translate }}
@@ -118,15 +118,30 @@
-
-
- {{ 'addon.calendar.repeatevent' | translate }}
-
-
-
- {{ 'addon.calendar.repeatweeksl' | translate }}
-
-
+
+
+
+ {{ 'addon.calendar.repeatevent' | translate }}
+
+
+
+ {{ 'addon.calendar.repeatweeksl' | translate }}
+
+
+
+
+
+
+ {{ 'addon.calendar.repeatedevents' | translate }}
+
+ {{ 'addon.calendar.repeateditall' | translate:{$a: event.othereventscount} }}
+
+
+
+ {{ 'addon.calendar.repeateditthis' | translate }}
+
+
+
diff --git a/src/addon/calendar/pages/edit-event/edit-event.scss b/src/addon/calendar/pages/edit-event/edit-event.scss
index 3c43c635e..6426ce3f1 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.scss
+++ b/src/addon/calendar/pages/edit-event/edit-event.scss
@@ -1,5 +1,5 @@
ion-app.app-root page-addon-calendar-edit-event {
- .addon-calendar-duration-container ion-item:not(.addon-calendar-duration-title) {
+ .addon-calendar-radio-container ion-item:not(.addon-calendar-radio-title) {
&.item-ios {
@include padding-horizontal($item-ios-padding-start * 2, null);
diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts
index 0017a81ea..97049a034 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.ts
+++ b/src/addon/calendar/pages/edit-event/edit-event.ts
@@ -57,6 +57,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
courseGroupSet = false;
advanced = false;
errors: any;
+ event: any; // The event object (when editing an event).
// Form variables.
eventForm: FormGroup;
@@ -72,6 +73,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
protected showAll: boolean;
protected isDestroyed = false;
protected error = false;
+ protected gotEventData = false;
constructor(navParams: NavParams,
private navCtrl: NavController,
@@ -126,6 +128,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.eventForm.addControl('timedurationminutes', this.fb.control(''));
this.eventForm.addControl('repeat', this.fb.control(false));
this.eventForm.addControl('repeats', this.fb.control('1'));
+ this.eventForm.addControl('repeateditall', this.fb.control(1));
}
/**
@@ -164,7 +167,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
return Promise.reject(this.translate.instant('addon.calendar.nopermissiontoupdatecalendar'));
}
- if (this.eventId && !refresh) {
+ if (this.eventId && !this.gotEventData) {
// Editing an event, get the event data. Wait for sync first.
promises.push(this.calendarSync.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(() => {
@@ -173,20 +176,35 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
}
+ const promises = [];
+
// Get the event offline data if there's any.
- return this.calendarOffline.getEvent(this.eventId).then((event) => {
+ promises.push(this.calendarOffline.getEvent(this.eventId).then((event) => {
this.hasOffline = true;
return event;
}).catch(() => {
// No offline data.
this.hasOffline = false;
+ }));
+
+ if (this.eventId > 0) {
+ // It's an online event. get its data from server.
+ promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => {
+ this.event = event;
+ if (event && event.repeatid) {
+ event.othereventscount = event.eventcount ? event.eventcount - 1 : '';
+ }
+
+ return event;
+ }));
+ }
+
+ return Promise.all(promises).then((result) => {
+ this.gotEventData = true;
+
+ const event = result[0] || result[1]; // Use offline data first.
- if (this.eventId > 0) {
- // It's an online event. get its data from server.
- return this.calendarProvider.getEventById(this.eventId);
- }
- }).then((event) => {
if (event) {
// Load the data in the form.
this.eventForm.controls.name.setValue(event.name);
@@ -204,6 +222,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
this.eventForm.controls.repeat.setValue(!!event.repeat);
this.eventForm.controls.repeats.setValue(event.repeats || '1');
+ this.eventForm.controls.repeateditall.setValue(event.repeateditall || 1);
}
});
}));
@@ -394,6 +413,11 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
data.repeats = formData.repeats;
}
+ if (this.event && this.event.repeatid) {
+ data.repeatid = this.event.repeatid;
+ data.repeateditall = formData.repeateditall;
+ }
+
// Send the data.
const modal = this.domUtils.showModalLoading('core.sending', true);
diff --git a/src/addon/calendar/providers/calendar-offline.ts b/src/addon/calendar/providers/calendar-offline.ts
index 833ca7d4b..a929113bc 100644
--- a/src/addon/calendar/providers/calendar-offline.ts
+++ b/src/addon/calendar/providers/calendar-offline.ts
@@ -94,6 +94,14 @@ export class AddonCalendarOfflineProvider {
name: 'repeats',
type: 'TEXT',
},
+ {
+ name: 'repeatid',
+ type: 'INTEGER',
+ },
+ {
+ name: 'repeateditall',
+ type: 'INTEGER',
+ },
{
name: 'userid',
type: 'INTEGER',
@@ -202,6 +210,8 @@ export class AddonCalendarOfflineProvider {
timedurationminutes: data.timedurationminutes,
repeat: data.repeat ? 1 : 0,
repeats: data.repeats,
+ repeatid: data.repeatid,
+ repeateditall: data.repeateditall ? 1 : 0,
timecreated: timeCreated,
userid: site.getUserId()
};
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index e8a1c8227..63974e503 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -96,6 +96,7 @@
"addon.calendar.errorloadevents": "Error loading events.",
"addon.calendar.eventduration": "Duration",
"addon.calendar.eventendtime": "End time",
+ "addon.calendar.eventkind": "Type of event",
"addon.calendar.eventname": "Event title",
"addon.calendar.eventstarttime": "Start time",
"addon.calendar.eventtype": "Event type",
@@ -106,6 +107,9 @@
"addon.calendar.noevents": "There are no events",
"addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event",
"addon.calendar.reminders": "Reminders",
+ "addon.calendar.repeatedevents": "Repeated events",
+ "addon.calendar.repeateditall": "Also apply changes to the other {{$a}} events in this repeat series",
+ "addon.calendar.repeateditthis": "Apply changes to this event only",
"addon.calendar.repeatevent": "Repeat this event",
"addon.calendar.repeatweeksl": "Repeat weekly, creating altogether",
"addon.calendar.setnewreminder": "Set a new reminder",
From a9d62274c9489d342ddc4d8225d182b7234ab25f Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Wed, 3 Jul 2019 15:04:05 +0200
Subject: [PATCH 5/5] MOBILE-3087 calendar: Fix issues when editing existing
events
---
.../calendar/pages/edit-event/edit-event.html | 6 +-
.../calendar/pages/edit-event/edit-event.ts | 119 ++++++++++++++----
src/addon/calendar/pages/event/event.html | 4 +-
3 files changed, 98 insertions(+), 31 deletions(-)
diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html
index f57a84915..9d79a7e5d 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.html
+++ b/src/addon/calendar/pages/edit-event/edit-event.html
@@ -1,6 +1,6 @@
-
+ {{ title | translate }}
@@ -44,7 +44,7 @@
{{ 'core.course' | translate }}
-
+ {{ course.fullname }}
@@ -54,7 +54,7 @@
{{ 'core.course' | translate }}
-
+ {{ course.fullname }}
diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts
index 97049a034..edda98102 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.ts
+++ b/src/addon/calendar/pages/edit-event/edit-event.ts
@@ -21,6 +21,7 @@ import { CoreGroupsProvider } from '@providers/groups';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@@ -79,6 +80,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
private navCtrl: NavController,
private translate: TranslateService,
private domUtils: CoreDomUtilsProvider,
+ private textUtils: CoreTextUtilsProvider,
private timeUtils: CoreTimeUtilsProvider,
private eventsProvider: CoreEventsProvider,
private groupsProvider: CoreGroupsProvider,
@@ -207,22 +209,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
if (event) {
// Load the data in the form.
- this.eventForm.controls.name.setValue(event.name);
- this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
- this.eventForm.controls.eventtype.setValue(event.eventtype);
- this.eventForm.controls.categoryid.setValue(event.categoryid || '');
- this.eventForm.controls.courseid.setValue(event.courseid || '');
- this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
- this.eventForm.controls.groupid.setValue(event.groupid || '');
- this.eventForm.controls.description.setValue(event.description);
- this.eventForm.controls.location.setValue(event.location);
- this.eventForm.controls.duration.setValue(event.duration);
- this.eventForm.controls.timedurationuntil.setValue(
- this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
- this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
- this.eventForm.controls.repeat.setValue(!!event.repeat);
- this.eventForm.controls.repeats.setValue(event.repeats || '1');
- this.eventForm.controls.repeateditall.setValue(event.repeateditall || 1);
+ return this.loadEventData(event, !!result[0]);
}
});
}));
@@ -251,12 +238,24 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
});
}
- // Sort courses by name.
- this.courses = courses.sort((a, b) => {
- const compareA = a.fullname.toLowerCase(),
- compareB = b.fullname.toLowerCase();
+ // Format the name of the courses.
+ const subPromises = [];
+ courses.forEach((course) => {
+ subPromises.push(this.textUtils.formatText(course.fullname).then((text) => {
+ course.fullname = text;
+ }).catch(() => {
+ // Ignore errors.
+ }));
+ });
- return compareA.localeCompare(compareB);
+ return Promise.all(subPromises).then(() => {
+ // Sort courses by name.
+ this.courses = courses.sort((a, b) => {
+ const compareA = a.fullname.toLowerCase(),
+ compareB = b.fullname.toLowerCase();
+
+ return compareA.localeCompare(compareB);
+ });
});
}));
}
@@ -285,6 +284,61 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
});
}
+ /**
+ * Load an event data into the form.
+ *
+ * @param {any} event Event data.
+ * @param {boolean} isOffline Whether the data is from offline or not.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected loadEventData(event: any, isOffline: boolean): Promise {
+ const courseId = event.course ? event.course.id : event.courseid;
+
+ this.eventForm.controls.name.setValue(event.name);
+ this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
+ this.eventForm.controls.eventtype.setValue(event.eventtype);
+ this.eventForm.controls.categoryid.setValue(event.categoryid || '');
+ this.eventForm.controls.courseid.setValue(courseId || '');
+ this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || courseId || '');
+ this.eventForm.controls.groupid.setValue(event.groupid || '');
+ this.eventForm.controls.description.setValue(event.description);
+ this.eventForm.controls.location.setValue(event.location);
+
+ if (isOffline) {
+ // It's an offline event, use the data as it is.
+ this.eventForm.controls.duration.setValue(event.duration);
+ this.eventForm.controls.timedurationuntil.setValue(
+ this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
+ this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
+ this.eventForm.controls.repeat.setValue(!!event.repeat);
+ this.eventForm.controls.repeats.setValue(event.repeats || '1');
+ this.eventForm.controls.repeateditall.setValue(event.repeateditall || 1);
+ } else {
+ // Online event, we'll have to calculate the data.
+
+ if (event.timeduration > 0) {
+ this.eventForm.controls.duration.setValue(1);
+ this.eventForm.controls.timedurationuntil.setValue(this.timeUtils.toDatetimeFormat(
+ (event.timestart + event.timeduration) * 1000));
+ } else {
+ // No duration.
+ this.eventForm.controls.duration.setValue(0);
+ this.eventForm.controls.timedurationuntil.setValue(this.timeUtils.toDatetimeFormat());
+ }
+
+ this.eventForm.controls.timedurationminutes.setValue('');
+ this.eventForm.controls.repeat.setValue(!!event.repeatid);
+ this.eventForm.controls.repeats.setValue(event.eventcount || '1');
+ this.eventForm.controls.repeateditall.setValue(1);
+ }
+
+ if (event.eventtype == 'group' && courseId) {
+ return this.loadGroups(courseId);
+ }
+
+ return Promise.resolve();
+ }
+
/**
* Pull to refresh.
*
@@ -327,20 +381,33 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
}
const modal = this.domUtils.showModalLoading();
- this.loadingGroups = true;
- this.groupsProvider.getUserGroupsInCourse(courseId).then((groups) => {
- this.groups = groups;
- this.courseGroupSet = true;
+ this.loadGroups(courseId).then(() => {
this.groupControl.setValue('');
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error getting data.');
}).finally(() => {
- this.loadingGroups = false;
modal.dismiss();
});
}
+ /**
+ * Load groups of a certain course.
+ *
+ * @param {number} courseId Course ID.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected loadGroups(courseId: number): Promise {
+ this.loadingGroups = true;
+
+ return this.groupsProvider.getUserGroupsInCourse(courseId).then((groups) => {
+ this.groups = groups;
+ this.courseGroupSet = true;
+ }).finally(() => {
+ this.loadingGroups = false;
+ });
+ }
+
/**
* Show or hide advanced form fields.
*/
diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html
index 93e5bd285..2608581ae 100644
--- a/src/addon/calendar/pages/event/event.html
+++ b/src/addon/calendar/pages/event/event.html
@@ -40,10 +40,10 @@
{{ 'core.course' | translate}}
-
+
{{ 'core.group' | translate}}
{{ groupName }}
-
+
{{ 'core.category' | translate}}