MOBILE-1927 calendar: Implement events synchronization
parent
7ccfade21e
commit
98776a9c78
|
@ -84,16 +84,30 @@
|
||||||
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
|
||||||
"addon.blog.siteblogheading": "blog",
|
"addon.blog.siteblogheading": "blog",
|
||||||
"addon.calendar.calendar": "calendar",
|
"addon.calendar.calendar": "calendar",
|
||||||
|
"addon.calendar.calendarevent": "local_moodlemobileapp",
|
||||||
"addon.calendar.calendarevents": "local_moodlemobileapp",
|
"addon.calendar.calendarevents": "local_moodlemobileapp",
|
||||||
"addon.calendar.calendarreminders": "local_moodlemobileapp",
|
"addon.calendar.calendarreminders": "local_moodlemobileapp",
|
||||||
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
|
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.durationminutes": "calendar",
|
||||||
|
"addon.calendar.durationnone": "calendar",
|
||||||
|
"addon.calendar.durationuntil": "calendar",
|
||||||
|
"addon.calendar.editevent": "calendar",
|
||||||
"addon.calendar.errorloadevent": "local_moodlemobileapp",
|
"addon.calendar.errorloadevent": "local_moodlemobileapp",
|
||||||
"addon.calendar.errorloadevents": "local_moodlemobileapp",
|
"addon.calendar.errorloadevents": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.eventduration": "calendar",
|
||||||
"addon.calendar.eventendtime": "calendar",
|
"addon.calendar.eventendtime": "calendar",
|
||||||
|
"addon.calendar.eventname": "calendar",
|
||||||
"addon.calendar.eventstarttime": "calendar",
|
"addon.calendar.eventstarttime": "calendar",
|
||||||
|
"addon.calendar.eventtype": "calendar",
|
||||||
"addon.calendar.gotoactivity": "calendar",
|
"addon.calendar.gotoactivity": "calendar",
|
||||||
|
"addon.calendar.invalidtimedurationminutes": "calendar",
|
||||||
|
"addon.calendar.invalidtimedurationuntil": "calendar",
|
||||||
|
"addon.calendar.newevent": "calendar",
|
||||||
"addon.calendar.noevents": "local_moodlemobileapp",
|
"addon.calendar.noevents": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.nopermissiontoupdatecalendar": "error",
|
||||||
"addon.calendar.reminders": "local_moodlemobileapp",
|
"addon.calendar.reminders": "local_moodlemobileapp",
|
||||||
|
"addon.calendar.repeatevent": "calendar",
|
||||||
|
"addon.calendar.repeatweeksl": "calendar",
|
||||||
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
"addon.calendar.setnewreminder": "local_moodlemobileapp",
|
||||||
"addon.calendar.typecategory": "calendar",
|
"addon.calendar.typecategory": "calendar",
|
||||||
"addon.calendar.typeclose": "calendar",
|
"addon.calendar.typeclose": "calendar",
|
||||||
|
@ -1328,6 +1342,7 @@
|
||||||
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
|
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
|
||||||
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
|
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
|
||||||
"core.coursedetails": "moodle",
|
"core.coursedetails": "moodle",
|
||||||
|
"core.coursenogroups": "local_moodlemobileapp",
|
||||||
"core.courses.addtofavourites": "block_myoverview",
|
"core.courses.addtofavourites": "block_myoverview",
|
||||||
"core.courses.allowguests": "enrol_guest",
|
"core.courses.allowguests": "enrol_guest",
|
||||||
"core.courses.availablecourses": "moodle",
|
"core.courses.availablecourses": "moodle",
|
||||||
|
@ -1457,6 +1472,7 @@
|
||||||
"core.grades.range": "grades",
|
"core.grades.range": "grades",
|
||||||
"core.grades.rank": "grades",
|
"core.grades.rank": "grades",
|
||||||
"core.grades.weight": "grades",
|
"core.grades.weight": "grades",
|
||||||
|
"core.group": "moodle",
|
||||||
"core.groupsseparate": "moodle",
|
"core.groupsseparate": "moodle",
|
||||||
"core.groupsvisible": "moodle",
|
"core.groupsvisible": "moodle",
|
||||||
"core.hasdatatosync": "local_moodlemobileapp",
|
"core.hasdatatosync": "local_moodlemobileapp",
|
||||||
|
@ -1692,6 +1708,9 @@
|
||||||
"core.sec": "moodle",
|
"core.sec": "moodle",
|
||||||
"core.secs": "moodle",
|
"core.secs": "moodle",
|
||||||
"core.seemoredetail": "survey",
|
"core.seemoredetail": "survey",
|
||||||
|
"core.selectacategory": "moodle",
|
||||||
|
"core.selectacourse": "moodle",
|
||||||
|
"core.selectagroup": "moodle",
|
||||||
"core.send": "message",
|
"core.send": "message",
|
||||||
"core.sending": "chat",
|
"core.sending": "chat",
|
||||||
"core.serverconnection": "error",
|
"core.serverconnection": "error",
|
||||||
|
@ -1760,6 +1779,7 @@
|
||||||
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
|
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
|
||||||
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
|
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
|
||||||
"core.show": "moodle",
|
"core.show": "moodle",
|
||||||
|
"core.showless": "form",
|
||||||
"core.showmore": "form",
|
"core.showmore": "form",
|
||||||
"core.site": "moodle",
|
"core.site": "moodle",
|
||||||
"core.sitehome.sitehome": "moodle",
|
"core.sitehome.sitehome": "moodle",
|
||||||
|
@ -1808,6 +1828,7 @@
|
||||||
"core.unlimited": "moodle",
|
"core.unlimited": "moodle",
|
||||||
"core.unzipping": "local_moodlemobileapp",
|
"core.unzipping": "local_moodlemobileapp",
|
||||||
"core.upgraderunning": "error",
|
"core.upgraderunning": "error",
|
||||||
|
"core.user": "moodle",
|
||||||
"core.user.address": "moodle",
|
"core.user.address": "moodle",
|
||||||
"core.user.city": "moodle",
|
"core.user.city": "moodle",
|
||||||
"core.user.contact": "local_moodlemobileapp",
|
"core.user.contact": "local_moodlemobileapp",
|
||||||
|
|
|
@ -16,8 +16,11 @@ import { NgModule } from '@angular/core';
|
||||||
import { AddonCalendarProvider } from './providers/calendar';
|
import { AddonCalendarProvider } from './providers/calendar';
|
||||||
import { AddonCalendarOfflineProvider } from './providers/calendar-offline';
|
import { AddonCalendarOfflineProvider } from './providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from './providers/helper';
|
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from './providers/calendar-sync';
|
||||||
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
||||||
|
import { AddonCalendarSyncCronHandler } from './providers/sync-cron-handler';
|
||||||
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||||
|
import { CoreCronDelegate } from '@providers/cron';
|
||||||
import { CoreInitDelegate } from '@providers/init';
|
import { CoreInitDelegate } from '@providers/init';
|
||||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||||
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
||||||
|
@ -27,7 +30,8 @@ import { CoreUpdateManagerProvider } from '@providers/update-manager';
|
||||||
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
AddonCalendarOfflineProvider,
|
AddonCalendarOfflineProvider,
|
||||||
AddonCalendarHelperProvider
|
AddonCalendarHelperProvider,
|
||||||
|
AddonCalendarSyncProvider
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -39,14 +43,19 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
AddonCalendarOfflineProvider,
|
AddonCalendarOfflineProvider,
|
||||||
AddonCalendarHelperProvider,
|
AddonCalendarHelperProvider,
|
||||||
AddonCalendarMainMenuHandler
|
AddonCalendarSyncProvider,
|
||||||
|
AddonCalendarMainMenuHandler,
|
||||||
|
AddonCalendarSyncCronHandler
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AddonCalendarModule {
|
export class AddonCalendarModule {
|
||||||
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
|
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
|
||||||
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
|
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider) {
|
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider,
|
||||||
|
cronDelegate: CoreCronDelegate, syncHandler: AddonCalendarSyncCronHandler) {
|
||||||
|
|
||||||
mainMenuDelegate.registerHandler(calendarHandler);
|
mainMenuDelegate.registerHandler(calendarHandler);
|
||||||
|
cronDelegate.register(syncHandler);
|
||||||
|
|
||||||
initDelegate.ready().then(() => {
|
initDelegate.ready().then(() => {
|
||||||
calendarProvider.scheduleAllSitesEventsNotifications();
|
calendarProvider.scheduleAllSitesEventsNotifications();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"calendar": "Calendar",
|
"calendar": "Calendar",
|
||||||
|
"calendarevent": "Calendar event",
|
||||||
"calendarevents": "Calendar events",
|
"calendarevents": "Calendar events",
|
||||||
"calendarreminders": "Calendar reminders",
|
"calendarreminders": "Calendar reminders",
|
||||||
"defaultnotificationtime": "Default notification time",
|
"defaultnotificationtime": "Default notification time",
|
||||||
|
|
|
@ -12,13 +12,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, Optional, ViewChild } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core';
|
||||||
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreGroupsProvider } from '@providers/groups';
|
import { CoreGroupsProvider } from '@providers/groups';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
@ -28,6 +29,7 @@ import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-t
|
||||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
import { CoreSite } from '@classes/site';
|
import { CoreSite } from '@classes/site';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +40,7 @@ import { CoreSite } from '@classes/site';
|
||||||
selector: 'page-addon-calendar-edit-event',
|
selector: 'page-addon-calendar-edit-event',
|
||||||
templateUrl: 'edit-event.html',
|
templateUrl: 'edit-event.html',
|
||||||
})
|
})
|
||||||
export class AddonCalendarEditEventPage implements OnInit {
|
export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent;
|
@ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent;
|
||||||
|
|
||||||
|
@ -68,6 +70,7 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
protected currentSite: CoreSite;
|
protected currentSite: CoreSite;
|
||||||
protected types: any; // Object with the supported types.
|
protected types: any; // Object with the supported types.
|
||||||
protected showAll: boolean;
|
protected showAll: boolean;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
|
||||||
constructor(navParams: NavParams,
|
constructor(navParams: NavParams,
|
||||||
private navCtrl: NavController,
|
private navCtrl: NavController,
|
||||||
|
@ -82,7 +85,9 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
private calendarProvider: AddonCalendarProvider,
|
private calendarProvider: AddonCalendarProvider,
|
||||||
private calendarOffline: AddonCalendarOfflineProvider,
|
private calendarOffline: AddonCalendarOfflineProvider,
|
||||||
private calendarHelper: AddonCalendarHelperProvider,
|
private calendarHelper: AddonCalendarHelperProvider,
|
||||||
|
private calendarSync: AddonCalendarSyncProvider,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
|
private syncProvider: CoreSyncProvider,
|
||||||
@Optional() private svComponent: CoreSplitViewComponent) {
|
@Optional() private svComponent: CoreSplitViewComponent) {
|
||||||
|
|
||||||
this.eventId = navParams.get('eventId');
|
this.eventId = navParams.get('eventId');
|
||||||
|
@ -142,10 +147,10 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
let accessInfo;
|
let accessInfo;
|
||||||
|
|
||||||
// Get access info.
|
// Get access info.
|
||||||
return this.calendarProvider.getAccessInformation().then((info) => {
|
return this.calendarProvider.getAccessInformation(this.courseId).then((info) => {
|
||||||
accessInfo = info;
|
accessInfo = info;
|
||||||
|
|
||||||
return this.calendarProvider.getAllowedEventTypes();
|
return this.calendarProvider.getAllowedEventTypes(this.courseId);
|
||||||
}).then((types) => {
|
}).then((types) => {
|
||||||
this.types = types;
|
this.types = types;
|
||||||
|
|
||||||
|
@ -157,29 +162,38 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.eventId && !refresh) {
|
if (this.eventId && !refresh) {
|
||||||
// Get the event data if there's any.
|
// If editing an event, get offline data. Wait for sync first.
|
||||||
promises.push(this.calendarOffline.getEvent(this.eventId).then((event) => {
|
|
||||||
this.hasOffline = true;
|
|
||||||
|
|
||||||
// Load the data in the form.
|
promises.push(this.calendarSync.waitForSync(AddonCalendarSyncProvider.SYNC_ID).then(() => {
|
||||||
this.eventForm.controls.name.setValue(event.name);
|
// Do not block if the scope is already destroyed.
|
||||||
this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
|
if (!this.isDestroyed) {
|
||||||
this.eventForm.controls.eventtype.setValue(event.eventtype);
|
this.syncProvider.blockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
|
||||||
this.eventForm.controls.categoryid.setValue(event.categoryid || '');
|
}
|
||||||
this.eventForm.controls.courseid.setValue(event.courseid || '');
|
|
||||||
this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
|
// Get the event data if there's any.
|
||||||
this.eventForm.controls.groupid.setValue(event.groupid || '');
|
return this.calendarOffline.getEvent(this.eventId).then((event) => {
|
||||||
this.eventForm.controls.description.setValue(event.description);
|
this.hasOffline = true;
|
||||||
this.eventForm.controls.location.setValue(event.location);
|
|
||||||
this.eventForm.controls.duration.setValue(event.duration);
|
// Load the data in the form.
|
||||||
this.eventForm.controls.timedurationuntil.setValue(
|
this.eventForm.controls.name.setValue(event.name);
|
||||||
this.timeUtils.toDatetimeFormat((event.timedurationuntil * 1000) || Date.now()));
|
this.eventForm.controls.timestart.setValue(this.timeUtils.toDatetimeFormat(event.timestart * 1000));
|
||||||
this.eventForm.controls.timedurationminutes.setValue(event.timedurationminutes || '');
|
this.eventForm.controls.eventtype.setValue(event.eventtype);
|
||||||
this.eventForm.controls.repeat.setValue(!!event.repeat);
|
this.eventForm.controls.categoryid.setValue(event.categoryid || '');
|
||||||
this.eventForm.controls.repeats.setValue(event.repeats || '1');
|
this.eventForm.controls.courseid.setValue(event.courseid || '');
|
||||||
}).catch(() => {
|
this.eventForm.controls.groupcourseid.setValue(event.groupcourseid || '');
|
||||||
// No offline data.
|
this.eventForm.controls.groupid.setValue(event.groupid || '');
|
||||||
this.hasOffline = false;
|
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');
|
||||||
|
}).catch(() => {
|
||||||
|
// No offline data.
|
||||||
|
this.hasOffline = false;
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,8 +319,8 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
submit(): void {
|
submit(): void {
|
||||||
// Validate data.
|
// Validate data.
|
||||||
const formData = this.eventForm.value,
|
const formData = this.eventForm.value,
|
||||||
timeStartDate = new Date(formData.timestart),
|
timeStartDate = this.timeUtils.datetimeToDate(formData.timestart),
|
||||||
timeUntilDate = new Date(formData.timedurationuntil),
|
timeUntilDate = this.timeUtils.datetimeToDate(formData.timedurationuntil),
|
||||||
timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10);
|
timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10);
|
||||||
let error;
|
let error;
|
||||||
|
|
||||||
|
@ -382,6 +396,9 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
* @param {number} [event] Event.
|
* @param {number} [event] Event.
|
||||||
*/
|
*/
|
||||||
protected returnToList(event?: any): void {
|
protected returnToList(event?: any): void {
|
||||||
|
// Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy.
|
||||||
|
this.unblockSync();
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
const data: any = {
|
const data: any = {
|
||||||
event: event
|
event: event
|
||||||
|
@ -432,4 +449,18 @@ export class AddonCalendarEditEventPage implements OnInit {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected unblockSync(): void {
|
||||||
|
if (this.eventId) {
|
||||||
|
this.syncProvider.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unblockSync();
|
||||||
|
this.isDestroyed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,14 @@
|
||||||
</button>
|
</button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item>
|
<core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="eventsLoaded && hasOffline && isOnline" [priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="refreshEvents($event)">
|
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="doRefresh($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="eventsLoaded">
|
<core-loading [hideUntil]="eventsLoaded">
|
||||||
|
|
|
@ -12,12 +12,13 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChild, OnDestroy } from '@angular/core';
|
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
|
||||||
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
@ -28,6 +29,7 @@ import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
import { Network } from '@ionic-native/network';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of calendar events.
|
* Page that displays the list of calendar events.
|
||||||
|
@ -57,6 +59,8 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
protected preSelectedCourseId: number;
|
protected preSelectedCourseId: number;
|
||||||
protected newEventObserver: any;
|
protected newEventObserver: any;
|
||||||
protected discardedObserver: any;
|
protected discardedObserver: any;
|
||||||
|
protected syncObserver: any;
|
||||||
|
protected onlineObserver: any;
|
||||||
|
|
||||||
courses: any[];
|
courses: any[];
|
||||||
eventsLoaded = false;
|
eventsLoaded = false;
|
||||||
|
@ -71,13 +75,16 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
};
|
};
|
||||||
canCreate = false;
|
canCreate = false;
|
||||||
hasOffline = false;
|
hasOffline = false;
|
||||||
|
isOnline = false;
|
||||||
|
syncIcon: string; // Sync icon.
|
||||||
|
|
||||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
|
||||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
||||||
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider,
|
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, zone: NgZone,
|
||||||
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
||||||
eventsProvider: CoreEventsProvider, private navCtrl: NavController, appProvider: CoreAppProvider,
|
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
|
||||||
private calendarOffline: AddonCalendarOfflineProvider) {
|
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
|
||||||
|
network: Network) {
|
||||||
|
|
||||||
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||||
|
@ -101,7 +108,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventsLoaded = false;
|
this.eventsLoaded = false;
|
||||||
this.refreshEvents(false).finally(() => {
|
this.refreshEvents(true, false).finally(() => {
|
||||||
|
|
||||||
// In tablet mode try to open the event (only if it's an online event).
|
// In tablet mode try to open the event (only if it's an online event).
|
||||||
if (this.splitviewCtrl.isOn() && data.event.id > 0) {
|
if (this.splitviewCtrl.isOn() && data.event.id > 0) {
|
||||||
|
@ -119,8 +126,22 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventsLoaded = false;
|
this.eventsLoaded = false;
|
||||||
this.refreshEvents(false);
|
this.refreshEvents(true, false);
|
||||||
}, sitesProvider.getCurrentSiteId());
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Refresh data if calendar events are synchronized automatically.
|
||||||
|
this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
|
||||||
|
this.eventsLoaded = false;
|
||||||
|
this.refreshEvents();
|
||||||
|
}, sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,7 +153,9 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
this.gotoEvent(this.eventId);
|
this.gotoEvent(this.eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData().then(() => {
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
|
this.fetchData(false, true, false).then(() => {
|
||||||
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
|
if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) {
|
||||||
// Take first and load it.
|
// Take first and load it.
|
||||||
this.gotoEvent(this.events[0].id);
|
this.gotoEvent(this.events[0].id);
|
||||||
|
@ -144,49 +167,76 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* Fetch all the data required for the view.
|
* Fetch all the data required for the view.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh] Empty events array first.
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
|
* @param {boolean} [sync] Whether it should try to synchronize offline events.
|
||||||
|
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
fetchData(refresh: boolean = false): Promise<any> {
|
fetchData(refresh?: boolean, sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
this.daysLoaded = 0;
|
this.daysLoaded = 0;
|
||||||
this.emptyEventsTimes = 0;
|
this.emptyEventsTimes = 0;
|
||||||
|
this.isOnline = this.appProvider.isOnline();
|
||||||
|
|
||||||
const promises = [];
|
let promise;
|
||||||
|
|
||||||
if (this.calendarProvider.canEditEventsInSite()) {
|
if (sync) {
|
||||||
// Site allows creating events. Check if the user has permissions to do so.
|
// Try to synchronize offline events.
|
||||||
promises.push(this.calendarProvider.getAllowedEventTypes().then((types) => {
|
promise = this.calendarSync.syncEvents().then((result) => {
|
||||||
this.canCreate = Object.keys(types).length > 0;
|
if (result.warnings && result.warnings.length) {
|
||||||
}).catch(() => {
|
this.domUtils.showErrorModal(result.warnings[0]);
|
||||||
this.canCreate = false;
|
}
|
||||||
}));
|
|
||||||
|
if (result.updated) {
|
||||||
|
// Trigger a manual sync event.
|
||||||
|
this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, {
|
||||||
|
source: 'list'
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (showErrors) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load courses for the popover.
|
return promise.then(() => {
|
||||||
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
|
||||||
// Add "All courses".
|
|
||||||
courses.unshift(this.allCourses);
|
|
||||||
this.courses = courses;
|
|
||||||
|
|
||||||
if (this.preSelectedCourseId) {
|
const promises = [];
|
||||||
this.filter.course = courses.find((course) => {
|
const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
|
||||||
return course.id == this.preSelectedCourseId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.fetchEvents(refresh);
|
promises.push(this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
|
||||||
}));
|
this.canCreate = canEdit;
|
||||||
|
}));
|
||||||
|
|
||||||
// Get offline events.
|
// Load courses for the popover.
|
||||||
promises.push(this.calendarOffline.getAllEvents().then((events) => {
|
promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||||
this.hasOffline = !!events.length;
|
// Add "All courses".
|
||||||
|
courses.unshift(this.allCourses);
|
||||||
|
this.courses = courses;
|
||||||
|
|
||||||
// Format data and sort by timestart.
|
if (this.preSelectedCourseId) {
|
||||||
events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
|
this.filter.course = courses.find((course) => {
|
||||||
this.offlineEvents = events.sort((a, b) => a.timestart - b.timestart);
|
return course.id == this.preSelectedCourseId;
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.all(promises).finally(() => {
|
return this.fetchEvents(refresh);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get offline events.
|
||||||
|
promises.push(this.calendarOffline.getAllEvents().then((events) => {
|
||||||
|
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);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}).finally(() => {
|
||||||
this.eventsLoaded = true;
|
this.eventsLoaded = true;
|
||||||
|
this.syncIcon = 'sync';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +246,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
* @param {boolean} [refresh] Empty events array first.
|
* @param {boolean} [refresh] Empty events array first.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
fetchEvents(refresh: boolean = false): Promise<any> {
|
fetchEvents(refresh?: boolean): Promise<any> {
|
||||||
this.loadMoreError = false;
|
this.loadMoreError = false;
|
||||||
|
|
||||||
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
||||||
|
@ -367,12 +417,34 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the events.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
* @param {any} [refresher] Refresher.
|
* @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<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
refreshEvents(refresher?: any): Promise<any> {
|
doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise<any> {
|
||||||
|
if (this.eventsLoaded) {
|
||||||
|
return this.refreshEvents(true, showErrors).finally(() => {
|
||||||
|
refresher && refresher.complete();
|
||||||
|
done && done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the events.
|
||||||
|
*
|
||||||
|
* @param {boolean} [sync] Whether it should try to synchronize offline events.
|
||||||
|
* @param {boolean} [showErrors] Whether to show sync errors to the user.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
refreshEvents(sync?: boolean, showErrors?: boolean): Promise<any> {
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
promises.push(this.calendarProvider.invalidateEventsList());
|
promises.push(this.calendarProvider.invalidateEventsList());
|
||||||
|
@ -384,9 +456,7 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).finally(() => {
|
return Promise.all(promises).finally(() => {
|
||||||
return this.fetchData(true).finally(() => {
|
return this.fetchData(true, sync, showErrors);
|
||||||
refresher && refresher.complete();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,6 +510,13 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
this.domUtils.scrollToTop(this.content);
|
this.domUtils.scrollToTop(this.content);
|
||||||
|
|
||||||
this.filteredEvents = this.getFilteredEvents();
|
this.filteredEvents = this.getFilteredEvents();
|
||||||
|
|
||||||
|
// Course viewed has changed, check if the user can create events for this course calendar.
|
||||||
|
const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
|
||||||
|
|
||||||
|
this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
|
||||||
|
this.canCreate = canEdit;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
popover.present({
|
popover.present({
|
||||||
|
@ -496,5 +573,8 @@ export class AddonCalendarListPage implements OnDestroy {
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
|
||||||
this.newEventObserver && this.newEventObserver.off();
|
this.newEventObserver && this.newEventObserver.off();
|
||||||
|
this.discardedObserver && this.discardedObserver.off();
|
||||||
|
this.syncObserver && this.syncObserver.off();
|
||||||
|
this.onlineObserver && this.onlineObserver.off();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
import { AddonCalendarProvider } from './calendar';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle offline calendar events.
|
* Service to handle offline calendar events.
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { AddonCalendarProvider } from './calendar';
|
||||||
|
import { AddonCalendarOfflineProvider } from './calendar-offline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to sync calendar.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
|
static AUTO_SYNCED = 'addon_calendar_autom_synced';
|
||||||
|
static MANUAL_SYNCED = 'addon_calendar_manual_synced';
|
||||||
|
static SYNC_ID = 'calendar';
|
||||||
|
|
||||||
|
protected componentTranslate: string;
|
||||||
|
|
||||||
|
constructor(translate: TranslateService,
|
||||||
|
appProvider: CoreAppProvider,
|
||||||
|
courseProvider: CoreCourseProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider,
|
||||||
|
loggerProvider: CoreLoggerProvider,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
syncProvider: CoreSyncProvider,
|
||||||
|
textUtils: CoreTextUtilsProvider,
|
||||||
|
timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private utils: CoreUtilsProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider,
|
||||||
|
private calendarOffline: AddonCalendarOfflineProvider) {
|
||||||
|
|
||||||
|
super('AddonCalendarSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
|
||||||
|
timeUtils);
|
||||||
|
|
||||||
|
this.componentTranslate = this.translate.instant('addon.calendar.calendarevent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize all events in a certain site or in all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @param {boolean} [force] Wether to force sync not depending on last execution.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
syncAllEvents(siteId?: string, force?: boolean): Promise<any> {
|
||||||
|
return this.syncOnSites('all calendars', this.syncAllEventsFunc.bind(this), [force], siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync all events on a site.
|
||||||
|
*
|
||||||
|
* @param {string} siteId Site ID to sync.
|
||||||
|
* @param {boolean} [force] Wether to force sync not depending on last execution.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
protected syncAllEventsFunc(siteId: string, force?: boolean): Promise<any> {
|
||||||
|
const promise = force ? this.syncEvents(siteId) : this.syncEventsIfNeeded(siteId);
|
||||||
|
|
||||||
|
return promise.then((result) => {
|
||||||
|
if (result && result.updated) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(AddonCalendarSyncProvider.AUTO_SYNCED, {
|
||||||
|
warnings: result.warnings,
|
||||||
|
events: result.events
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync a site events only if a certain time has passed since the last time.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the events are synced or if it doesn't need to be synced.
|
||||||
|
*/
|
||||||
|
syncEventsIfNeeded(siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.isSyncNeeded(AddonCalendarSyncProvider.SYNC_ID, siteId).then((needed) => {
|
||||||
|
if (needed) {
|
||||||
|
return this.syncEvents(siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize all offline events of a certain site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
syncEvents(siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (this.isSyncing(AddonCalendarSyncProvider.SYNC_ID, siteId)) {
|
||||||
|
// There's already a sync ongoing for this site, return the promise.
|
||||||
|
return this.getOngoingSync(AddonCalendarSyncProvider.SYNC_ID, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug('Try to sync calendar events for site ' + siteId);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
warnings: [],
|
||||||
|
events: [],
|
||||||
|
updated: false
|
||||||
|
};
|
||||||
|
let offlineEvents;
|
||||||
|
|
||||||
|
// Get offline events.
|
||||||
|
const syncPromise = this.calendarOffline.getAllEvents(siteId).catch(() => {
|
||||||
|
// No offline data found, return empty list.
|
||||||
|
return [];
|
||||||
|
}).then((events) => {
|
||||||
|
offlineEvents = events;
|
||||||
|
|
||||||
|
if (!events.length) {
|
||||||
|
// Nothing to sync.
|
||||||
|
return;
|
||||||
|
} else if (!this.appProvider.isOnline()) {
|
||||||
|
// Cannot sync in offline.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
promises.push(this.syncOfflineEvent(event, result, siteId));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
}).then(() => {
|
||||||
|
if (result.updated) {
|
||||||
|
// Data has been sent to server. Now invalidate the WS calls.
|
||||||
|
const promises = [
|
||||||
|
this.calendarProvider.invalidateEventsList(siteId),
|
||||||
|
];
|
||||||
|
|
||||||
|
offlineEvents.forEach((event) => {
|
||||||
|
if (event.id > 0) {
|
||||||
|
// An event was edited, invalidate its data too.
|
||||||
|
promises.push(this.calendarProvider.invalidateEvent(event.id, siteId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Sync finished, set sync time.
|
||||||
|
return this.setSyncTime(AddonCalendarSyncProvider.SYNC_ID, siteId).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
// All done, return the result.
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.addOngoingSync(AddonCalendarSyncProvider.SYNC_ID, syncPromise, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize an offline event.
|
||||||
|
*
|
||||||
|
* @param {any} event The event to sync.
|
||||||
|
* @param {any} result Object where to store the result of the sync.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
protected syncOfflineEvent(event: any, result: any, siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
// Verify that event isn't blocked.
|
||||||
|
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}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to send the data.
|
||||||
|
const data = this.utils.clone(event); // Clone the object because it will be modified in the submit function.
|
||||||
|
|
||||||
|
return this.calendarProvider.submitEventOnline(event.id > 0 ? event.id : undefined, data, siteId).then((newEvent) => {
|
||||||
|
result.updated = true;
|
||||||
|
result.events.push(newEvent);
|
||||||
|
|
||||||
|
// Event sent, delete the offline data.
|
||||||
|
return this.calendarOffline.deleteEvent(event.id, siteId);
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// The WebService has thrown an error, this means that the event cannot be created. Delete it.
|
||||||
|
result.updated = true;
|
||||||
|
|
||||||
|
return this.calendarOffline.deleteEvent(event.id, siteId).then(() => {
|
||||||
|
// Event deleted, add a warning.
|
||||||
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
|
component: this.componentTranslate,
|
||||||
|
name: event.name,
|
||||||
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local error, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,10 +33,33 @@ export class AddonCalendarHelperProvider {
|
||||||
category: 'fa-cubes'
|
category: 'fa-cubes'
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider) {
|
constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider) {
|
||||||
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current user can create/edit events.
|
||||||
|
*
|
||||||
|
* @param {number} [courseId] Course ID. If not defined, site calendar.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean: whether the user can create events.
|
||||||
|
*/
|
||||||
|
canEditEvents(courseId?: number, siteId?: string): Promise<boolean> {
|
||||||
|
return this.calendarProvider.canEditEvents(siteId).then((canEdit) => {
|
||||||
|
if (!canEdit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site allows creating events. Check if the user has permissions to do so.
|
||||||
|
return this.calendarProvider.getAllowedEventTypes(courseId, siteId).then((types) => {
|
||||||
|
return Object.keys(types).length > 0;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to format some event data to be rendered.
|
* Convenience function to format some event data to be rendered.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreCronHandler } from '@providers/cron';
|
||||||
|
import { AddonCalendarSyncProvider } from './calendar-sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization cron handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonCalendarSyncCronHandler implements CoreCronHandler {
|
||||||
|
name = 'AddonCalendarSyncCronHandler';
|
||||||
|
|
||||||
|
constructor(private calendarSync: AddonCalendarSyncProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the process.
|
||||||
|
* Receives the ID of the site affected, undefined for all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||||
|
* @param {boolean} [force] Wether the execution is forced (manual sync).
|
||||||
|
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||||
|
*/
|
||||||
|
execute(siteId?: string, force?: boolean): Promise<any> {
|
||||||
|
return this.calendarSync.syncAllEvents(siteId, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time between consecutive executions.
|
||||||
|
*
|
||||||
|
* @return {number} Time between consecutive executions (in ms).
|
||||||
|
*/
|
||||||
|
getInterval(): number {
|
||||||
|
return this.calendarSync.syncInterval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,6 +84,7 @@
|
||||||
"addon.blog.showonlyyourentries": "Show only your entries",
|
"addon.blog.showonlyyourentries": "Show only your entries",
|
||||||
"addon.blog.siteblogheading": "Site blog",
|
"addon.blog.siteblogheading": "Site blog",
|
||||||
"addon.calendar.calendar": "Calendar",
|
"addon.calendar.calendar": "Calendar",
|
||||||
|
"addon.calendar.calendarevent": "Calendar event",
|
||||||
"addon.calendar.calendarevents": "Calendar events",
|
"addon.calendar.calendarevents": "Calendar events",
|
||||||
"addon.calendar.calendarreminders": "Calendar reminders",
|
"addon.calendar.calendarreminders": "Calendar reminders",
|
||||||
"addon.calendar.defaultnotificationtime": "Default notification time",
|
"addon.calendar.defaultnotificationtime": "Default notification time",
|
||||||
|
@ -1389,7 +1390,6 @@
|
||||||
"core.deleteduser": "Deleted user",
|
"core.deleteduser": "Deleted user",
|
||||||
"core.deleting": "Deleting",
|
"core.deleting": "Deleting",
|
||||||
"core.description": "Description",
|
"core.description": "Description",
|
||||||
"core.dfdatetimeinput": "YYYY-MM-DDThh:mm:ss.SSS",
|
|
||||||
"core.dfdaymonthyear": "MM-DD-YYYY",
|
"core.dfdaymonthyear": "MM-DD-YYYY",
|
||||||
"core.dfdayweekmonth": "ddd, D MMM",
|
"core.dfdayweekmonth": "ddd, D MMM",
|
||||||
"core.dffulldate": "dddd, D MMMM YYYY h[:]mm A",
|
"core.dffulldate": "dddd, D MMMM YYYY h[:]mm A",
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
"deleteduser": "Deleted user",
|
"deleteduser": "Deleted user",
|
||||||
"deleting": "Deleting",
|
"deleting": "Deleting",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"dfdatetimeinput": "YYYY-MM-DDThh:mm:ss.SSS",
|
|
||||||
"dfdaymonthyear": "MM-DD-YYYY",
|
"dfdaymonthyear": "MM-DD-YYYY",
|
||||||
"dfdayweekmonth": "ddd, D MMM",
|
"dfdayweekmonth": "ddd, D MMM",
|
||||||
"dffulldate": "dddd, D MMMM YYYY h[:]mm A",
|
"dffulldate": "dddd, D MMMM YYYY h[:]mm A",
|
||||||
|
|
|
@ -308,7 +308,22 @@ export class CoreTimeUtilsProvider {
|
||||||
toDatetimeFormat(timestamp?: number): string {
|
toDatetimeFormat(timestamp?: number): string {
|
||||||
timestamp = timestamp || Date.now();
|
timestamp = timestamp || Date.now();
|
||||||
|
|
||||||
return this.userDate(timestamp, 'core.dfdatetimeinput', false);
|
return this.userDate(timestamp, 'YYYY-MM-DDTHH:mm:ss.SSS', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the value of a ion-datetime to a Date.
|
||||||
|
*
|
||||||
|
* @param {string} value Value of ion-datetime.
|
||||||
|
* @return {Date} Date.
|
||||||
|
*/
|
||||||
|
datetimeToDate(value: string): Date {
|
||||||
|
if (typeof value == 'string' && value.slice(-1) == 'Z') {
|
||||||
|
// The value shoudln't have the timezone because it causes problems, remove it.
|
||||||
|
value = value.substr(0, value.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue