diff --git a/scripts/langindex.json b/scripts/langindex.json index b41c53462..35e016443 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", @@ -1490,6 +1494,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/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 0cf401536..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 }} @@ -9,7 +9,7 @@ -
+

{{ 'addon.calendar.eventname' | translate }}

@@ -26,7 +26,7 @@ -

{{ 'addon.calendar.eventtype' | translate }}

+

{{ 'addon.calendar.eventkind' | translate }}

{{ type.name | translate }} @@ -44,7 +44,7 @@

{{ 'core.course' | translate }}

- + {{ course.fullname }}
@@ -54,7 +54,7 @@

{{ 'core.course' | translate }}

- + {{ course.fullname }}
@@ -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 }} + + +
@@ -134,7 +149,7 @@ - + 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 bf394c205..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'; @@ -57,6 +58,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; @@ -71,11 +73,14 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { protected types: any; // Object with the supported types. protected showAll: boolean; protected isDestroyed = false; + protected error = false; + protected gotEventData = false; constructor(navParams: NavParams, private navCtrl: NavController, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, + private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, private groupsProvider: CoreGroupsProvider, @@ -125,6 +130,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)); } /** @@ -146,6 +152,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { protected fetchData(refresh?: boolean): Promise { let accessInfo; + this.error = false; + // Get access info. return this.calendarProvider.getAccessInformation(this.courseId).then((info) => { accessInfo = info; @@ -161,8 +169,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { return Promise.reject(this.translate.instant('addon.calendar.nopermissiontoupdatecalendar')); } - if (this.eventId && !refresh) { - // If editing an event, get offline data. Wait for sync first. + 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(() => { // Do not block if the scope is already destroyed. @@ -170,29 +178,39 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId); } - // Get the event data if there's any. - return this.calendarOffline.getEvent(this.eventId).then((event) => { + const promises = []; + + // Get the event offline data if there's any. + promises.push(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. + 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 (event) { + // Load the data in the form. + return this.loadEventData(event, !!result[0]); + } }); })); } @@ -220,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); + }); }); })); } @@ -245,11 +275,70 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'Error getting data.'); - this.originalData = null; // Avoid asking for confirmation. - this.navCtrl.pop(); + this.error = true; + + if (!this.svComponent || !this.svComponent.isOn()) { + this.originalData = null; // Avoid asking for confirmation. + this.navCtrl.pop(); + } }); } + /** + * 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. * @@ -292,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. */ @@ -378,8 +480,13 @@ 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'); + const modal = this.domUtils.showModalLoading('core.sending', true); this.calendarProvider.submitEvent(this.eventId, data).then((result) => { this.returnToList(result.event); @@ -399,13 +506,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..2608581ae 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} }} + + @@ -26,10 +40,10 @@

{{ 'core.course' | translate}}

- +

{{ 'core.group' | translate}}

{{ groupName }}

-
+

{{ 'core.category' | 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..a224599f7 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -7,7 +7,7 @@ - + @@ -26,20 +26,6 @@ - - - {{ 'core.notsent' | translate }} -
- -

-

- {{ event.timestart * 1000 | coreFormatDate: "strftimedatetimeshort" }} - - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimedatetimeshort" }} -

-
- - - @@ -54,6 +40,10 @@ - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimetime" }} - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimedatetimeshort" }}

+ + + {{ 'core.notsent' | translate }} +
diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 1a57c4c0b..e3ce88a44 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'; @@ -30,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. @@ -43,6 +45,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; @@ -59,12 +62,16 @@ 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; - events = []; + events = []; // Events (both online and offline). + onlineEvents = []; offlineEvents = []; notificationsEnabled = false; filteredEvents = []; @@ -80,20 +87,21 @@ 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, - network: Network) { + network: Network, private timeUtils: CoreTimeUtilsProvider) { 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()); + calendarProvider.scheduleEventsNotifications(this.onlineEvents); + }, this.currentSiteId); } this.eventId = navParams.get('eventId') || false; @@ -116,7 +124,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 +135,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) => { @@ -157,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); + } } }); } @@ -172,6 +200,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(); @@ -187,9 +216,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) { @@ -229,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); @@ -249,38 +281,38 @@ export class AddonCalendarListPage implements OnDestroy { fetchEvents(refresh?: boolean): Promise { this.loadMoreError = false; - return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => { - this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL; - if (events.length === 0) { + return this.calendarProvider.getEventsList(this.initialTime, this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL) + .then((onlineEvents) => { + + 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(); @@ -293,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. @@ -416,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. * @@ -530,6 +619,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 +665,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-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/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..bfd3a03df 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); } @@ -535,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 = []; @@ -559,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, @@ -586,6 +591,7 @@ export class AddonCalendarProvider { const preSets = { cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval), getCacheUsingCacheKey: true, + uniqueCacheKey: true, updateFrequency: CoreSite.FREQUENCY_SOMETIMES }; @@ -731,7 +737,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); }); } @@ -980,7 +986,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 +999,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 f29201b76..12b6ecacf 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", @@ -1492,6 +1496,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 7dd93bcf8..82744f363 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -115,6 +115,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 a0c352bfd..306c71525 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -1085,7 +1085,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 { @@ -1097,7 +1097,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; }