From 083ca7cd7ee9b3228443dbe32f71d649820389a8 Mon Sep 17 00:00:00 2001
From: Dani Palou
Date: Fri, 5 Jul 2019 10:30:38 +0200
Subject: [PATCH] MOBILE-3021 calendar: Implement day view
---
scripts/langindex.json | 2 +
.../calendar/addon-calendar-calendar.html | 4 +-
.../components/calendar/calendar.scss | 2 +-
.../calendar/components/calendar/calendar.ts | 10 +
.../upcoming-events/upcoming-events.ts | 2 +-
src/addon/calendar/lang/en.json | 2 +
src/addon/calendar/pages/day/day.html | 71 ++
src/addon/calendar/pages/day/day.module.ts | 35 +
src/addon/calendar/pages/day/day.ts | 607 ++++++++++++++++++
.../calendar/pages/edit-event/edit-event.ts | 4 +-
src/addon/calendar/pages/event/event.ts | 1 -
src/addon/calendar/pages/index/index.html | 2 +-
src/addon/calendar/pages/index/index.ts | 72 +--
src/addon/calendar/pages/list/list.ts | 66 +-
src/addon/calendar/providers/calendar.ts | 99 +++
src/assets/lang/en.json | 2 +
src/core/courses/providers/helper.ts | 74 ++-
17 files changed, 963 insertions(+), 92 deletions(-)
create mode 100644 src/addon/calendar/pages/day/day.html
create mode 100644 src/addon/calendar/pages/day/day.module.ts
create mode 100644 src/addon/calendar/pages/day/day.ts
diff --git a/scripts/langindex.json b/scripts/langindex.json
index 1d2d82d13..d80355727 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -90,6 +90,8 @@
"addon.calendar.calendarreminders": "local_moodlemobileapp",
"addon.calendar.confirmeventdelete": "calendar",
"addon.calendar.confirmeventseriesdelete": "calendar",
+ "addon.calendar.daynext": "calendar",
+ "addon.calendar.dayprev": "calendar",
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
"addon.calendar.deleteallevents": "calendar",
"addon.calendar.deleteevent": "calendar",
diff --git a/src/addon/calendar/components/calendar/addon-calendar-calendar.html b/src/addon/calendar/components/calendar/addon-calendar-calendar.html
index 007100baf..7c607ca63 100644
--- a/src/addon/calendar/components/calendar/addon-calendar-calendar.html
+++ b/src/addon/calendar/components/calendar/addon-calendar-calendar.html
@@ -31,7 +31,7 @@
- {{ day.mday }}
+ {{ day.mday }}
@@ -47,7 +47,7 @@
{{event.name}}
- 4">{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}
+ 4" class="addon-calendar-day-more" (click)="dayClicked(day.mday)">{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}
diff --git a/src/addon/calendar/components/calendar/calendar.scss b/src/addon/calendar/components/calendar/calendar.scss
index c3a20bf9e..af7182c32 100644
--- a/src/addon/calendar/components/calendar/calendar.scss
+++ b/src/addon/calendar/components/calendar/calendar.scss
@@ -20,7 +20,7 @@ ion-app.app-root addon-calendar-calendar {
}
}
- .addon-calendar-event {
+ .addon-calendar-event, .addon-calendar-day-number, .addon-calendar-day-more {
cursor: pointer;
}
diff --git a/src/addon/calendar/components/calendar/calendar.ts b/src/addon/calendar/components/calendar/calendar.ts
index 203db79e8..2b40b8b3f 100644
--- a/src/addon/calendar/components/calendar/calendar.ts
+++ b/src/addon/calendar/components/calendar/calendar.ts
@@ -37,6 +37,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
@Input() categoryId: number | string; // Category ID the course belongs to.
@Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true.
@Output() onEventClicked = new EventEmitter();
+ @Output() onDayClicked = new EventEmitter<{day: number, month: number, year: number}>();
periodName: string;
weekDays: any[];
@@ -288,6 +289,15 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest
this.onEventClicked.emit(event.id);
}
+ /**
+ * A day was clicked.
+ *
+ * @param {number} day Day.
+ */
+ dayClicked(day: number): void {
+ this.onDayClicked.emit({day: day, month: this.month, year: this.year});
+ }
+
/**
* Decrease the current month.
*/
diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.ts b/src/addon/calendar/components/upcoming-events/upcoming-events.ts
index 41751c0d5..f358b5785 100644
--- a/src/addon/calendar/components/upcoming-events/upcoming-events.ts
+++ b/src/addon/calendar/components/upcoming-events/upcoming-events.ts
@@ -34,7 +34,6 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges,
@Input() categoryId: number | string; // Category ID the course belongs to.
@Output() onEventClicked = new EventEmitter();
- events = []; // Events (both online and offline).
filteredEvents = [];
loaded = false;
@@ -43,6 +42,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges,
protected categoriesRetrieved = false;
protected categories = {};
protected currentSiteId: string;
+ protected events = []; // Events (both online and offline).
protected onlineEvents = [];
protected offlineEvents = []; // Offline events.
protected deletedEvents = []; // Events deleted in offline.
diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json
index ba81a9a89..8d19b4559 100644
--- a/src/addon/calendar/lang/en.json
+++ b/src/addon/calendar/lang/en.json
@@ -6,6 +6,8 @@
"calendarreminders": "Calendar reminders",
"confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
"confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
+ "daynext": "Next day",
+ "dayprev": "Previous day",
"defaultnotificationtime": "Default notification time",
"deleteallevents": "Delete all events",
"deleteevent": "Delete event",
diff --git a/src/addon/calendar/pages/day/day.html b/src/addon/calendar/pages/day/day.html
new file mode 100644
index 000000000..e9b48bd52
--- /dev/null
+++ b/src/addon/calendar/pages/day/day.html
@@ -0,0 +1,71 @@
+
+
+ {{ 'addon.calendar.calendarevents' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ periodName }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.hasdatatosync' | translate:{$a: 'core.day' | translate} }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.notsent' | translate }}
+
+
+
+ {{ 'core.deletedoffline' | translate }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/calendar/pages/day/day.module.ts b/src/addon/calendar/pages/day/day.module.ts
new file mode 100644
index 000000000..c4e33dbb9
--- /dev/null
+++ b/src/addon/calendar/pages/day/day.module.ts
@@ -0,0 +1,35 @@
+// (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 { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CorePipesModule } from '@pipes/pipes.module';
+import { AddonCalendarDayPage } from './day';
+
+@NgModule({
+ declarations: [
+ AddonCalendarDayPage,
+ ],
+ imports: [
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ CorePipesModule,
+ IonicPageModule.forChild(AddonCalendarDayPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonCalendarDayPageModule {}
diff --git a/src/addon/calendar/pages/day/day.ts b/src/addon/calendar/pages/day/day.ts
new file mode 100644
index 000000000..924bfa0e7
--- /dev/null
+++ b/src/addon/calendar/pages/day/day.ts
@@ -0,0 +1,607 @@
+// (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 OFx ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, OnInit, OnDestroy, NgZone } from '@angular/core';
+import { IonicPage, NavParams, NavController } from 'ionic-angular';
+import { CoreAppProvider } from '@providers/app';
+import { CoreEventsProvider } from '@providers/events';
+import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreTimeUtilsProvider } from '@providers/utils/time';
+import { AddonCalendarProvider } from '../../providers/calendar';
+import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
+import { AddonCalendarHelperProvider } from '../../providers/helper';
+import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
+import { CoreCoursesProvider } from '@core/courses/providers/courses';
+import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
+import { Network } from '@ionic-native/network';
+import * as moment from 'moment';
+
+/**
+ * Page that displays the calendar events for a certain day.
+ */
+@IonicPage({ segment: 'addon-calendar-day' })
+@Component({
+ selector: 'page-addon-calendar-day',
+ templateUrl: 'day.html',
+})
+export class AddonCalendarDayPage implements OnInit, OnDestroy {
+
+ protected currentSiteId: string;
+ protected year: number;
+ protected month: number;
+ protected day: number;
+ protected categories = {};
+ protected events = []; // Events (both online and offline).
+ protected onlineEvents = [];
+ protected offlineEvents = {}; // Offline events.
+ protected offlineEditedEventsIds = []; // IDs of events edited in offline.
+ protected deletedEvents = []; // Events deleted in offline.
+ protected timeFormat: string;
+ protected currentMoment: moment.Moment;
+
+ // Observers.
+ protected newEventObserver: any;
+ protected discardedObserver: any;
+ protected editEventObserver: any;
+ protected deleteEventObserver: any;
+ protected undeleteEventObserver: any;
+ protected syncObserver: any;
+ protected manualSyncObserver: any;
+ protected onlineObserver: any;
+
+ periodName: string;
+ filteredEvents = [];
+ courseId: number;
+ categoryId: number;
+ canCreate = false;
+ courses: any[];
+ loaded = false;
+ hasOffline = false;
+ isOnline = false;
+ syncIcon: string;
+
+ constructor(localNotificationsProvider: CoreLocalNotificationsProvider,
+ navParams: NavParams,
+ network: Network,
+ zone: NgZone,
+ sitesProvider: CoreSitesProvider,
+ private navCtrl: NavController,
+ private domUtils: CoreDomUtilsProvider,
+ private timeUtils: CoreTimeUtilsProvider,
+ private calendarProvider: AddonCalendarProvider,
+ private calendarOffline: AddonCalendarOfflineProvider,
+ private calendarHelper: AddonCalendarHelperProvider,
+ private calendarSync: AddonCalendarSyncProvider,
+ private eventsProvider: CoreEventsProvider,
+ private coursesProvider: CoreCoursesProvider,
+ private coursesHelper: CoreCoursesHelperProvider,
+ private appProvider: CoreAppProvider) {
+
+ const now = new Date();
+
+ this.year = navParams.get('year') || now.getFullYear();
+ this.month = navParams.get('month') || (now.getMonth() + 1);
+ this.day = navParams.get('day') || now.getDate();
+ this.courseId = navParams.get('courseId');
+ this.currentSiteId = sitesProvider.getCurrentSiteId();
+
+ // Listen for events added. When an event is added, reload the data.
+ this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => {
+ if (data && data.event) {
+ this.loaded = false;
+ this.refreshData(true, false);
+ }
+ }, this.currentSiteId);
+
+ // Listen for new event discarded event. When it does, reload the data.
+ this.discardedObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => {
+ this.loaded = false;
+ this.refreshData(true, false);
+ }, 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.loaded = false;
+ this.refreshData(true, false);
+ }
+ }, this.currentSiteId);
+
+ // Refresh data if calendar events are synchronized automatically.
+ this.syncObserver = eventsProvider.on(AddonCalendarSyncProvider.AUTO_SYNCED, (data) => {
+ this.loaded = false;
+ this.refreshData();
+ }, 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 != 'day' || data.year != this.year || data.month != this.month || data.day != this.day)) {
+ this.loaded = false;
+ this.refreshData();
+ }
+ }, this.currentSiteId);
+
+ // Update the events when an event is deleted.
+ this.deleteEventObserver = eventsProvider.on(AddonCalendarProvider.DELETED_EVENT_EVENT, (data) => {
+ if (data && !data.sent) {
+ // Event was deleted in offline. Just mark it as deleted, no need to refresh.
+ this.hasOffline = this.markAsDeleted(data.eventId, true) || this.hasOffline;
+ this.deletedEvents.push(data.eventId);
+ } else {
+ this.loaded = false;
+ this.refreshData();
+ }
+ }, this.currentSiteId);
+
+ // Listen for events "undeleted" (offline).
+ this.undeleteEventObserver = eventsProvider.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => {
+ if (data && data.eventId) {
+ // Mark it as undeleted, no need to refresh.
+ const found = this.markAsDeleted(data.eventId, false);
+
+ // Remove it from the list of deleted events if it's there.
+ const index = this.deletedEvents.indexOf(data.eventId);
+ if (index != -1) {
+ this.deletedEvents.splice(index, 1);
+ }
+
+ if (found) {
+ // The deleted event belongs to current list. Re-calculate "hasOffline".
+ this.hasOffline = false;
+
+ if (this.events.length != this.onlineEvents.length) {
+ this.hasOffline = true;
+ } else {
+ const event = this.events.find((event) => {
+ return event.deleted || event.offline;
+ });
+
+ this.hasOffline = !!event;
+ }
+ }
+ }
+ }, this.currentSiteId);
+
+ // Refresh online status when changes.
+ this.onlineObserver = network.onchange().subscribe(() => {
+ // Execute the callback in the Angular zone, so change detection doesn't stop working.
+ zone.run(() => {
+ this.isOnline = this.appProvider.isOnline();
+ });
+ });
+ }
+
+ /**
+ * View loaded.
+ */
+ ngOnInit(): void {
+ this.currentMoment = moment().year(this.year).month(this.month - 1).day(this.day);
+
+ this.fetchData(true, false);
+ }
+
+ /**
+ * Fetch all the data required for 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.
+ */
+ fetchData(sync?: boolean, showErrors?: boolean): Promise {
+
+ this.syncIcon = 'spinner';
+ this.isOnline = this.appProvider.isOnline();
+
+ const promise = sync ? this.sync() : Promise.resolve();
+
+ return promise.then(() => {
+ const promises = [];
+
+ // Load courses for the popover.
+ promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => {
+ this.courses = data.courses;
+ this.categoryId = data.categoryId;
+ }));
+
+ // Get categories.
+ promises.push(this.loadCategories());
+
+ // Get offline events.
+ promises.push(this.calendarOffline.getAllEditedEvents().then((events) => {
+ // Format data.
+ events.forEach((event) => {
+ event.offline = true;
+ this.calendarHelper.formatEventData(event);
+ });
+
+ // Classify them by month & day.
+ this.offlineEvents = this.calendarHelper.classifyIntoMonths(events);
+
+ // // Get the IDs of events edited in offline.
+ const filtered = events.filter((event) => {
+ return event.id > 0;
+ });
+ this.offlineEditedEventsIds = filtered.map((event) => {
+ return event.id;
+ });
+ }));
+
+ // Get events deleted in offline.
+ promises.push(this.calendarOffline.getAllDeletedEventsIds().then((ids) => {
+ this.deletedEvents = ids;
+ }));
+
+ // Check if user can create events.
+ promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
+ this.canCreate = canEdit;
+ }));
+
+ // Get user preferences.
+ promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
+ this.timeFormat = value;
+ }));
+
+ return Promise.all(promises);
+ }).then(() => {
+ return this.fetchEvents();
+ }).catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
+ }).finally(() => {
+ this.loaded = true;
+ this.syncIcon = 'sync';
+ });
+ }
+
+ /**
+ * Fetch the events for current day.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ fetchEvents(): Promise {
+ // Don't pass courseId and categoryId, we'll filter them locally.
+ return this.calendarProvider.getDayEvents(this.year, this.month, this.day).then((result) => {
+
+ // Calculate the period name. We don't use the one in result because it's in server's language.
+ this.periodName = this.timeUtils.userDate(new Date(this.year, this.month - 1, this.day).getTime(),
+ 'core.strftimedaydate');
+
+ this.onlineEvents = result.events;
+ this.onlineEvents.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
+
+ // Merge the online events with offline data.
+ this.events = this.mergeEvents();
+
+ // Filter events by course.
+ this.filterEvents();
+
+ // Re-calculate the formatted time so it uses the device date.
+ this.events.forEach((event) => {
+ event.formattedtime = this.calendarProvider.formatEventTime(event, this.timeFormat);
+ });
+ });
+ }
+
+ /**
+ * Merge online events with the offline events of that period.
+ *
+ * @return {any[]} Merged events.
+ */
+ protected mergeEvents(): any[] {
+ this.hasOffline = false;
+
+ if (!this.offlineEditedEventsIds.length && !this.deletedEvents.length) {
+ // No offline events, nothing to merge.
+ return this.onlineEvents;
+ }
+
+ const monthOfflineEvents = this.offlineEvents[this.calendarHelper.getMonthId(this.year, this.month)],
+ dayOfflineEvents = monthOfflineEvents && monthOfflineEvents[this.day];
+ let result = this.onlineEvents;
+
+ if (this.deletedEvents.length) {
+ // Mark as deleted the events that were deleted in offline.
+ result.forEach((event) => {
+ event.deleted = this.deletedEvents.indexOf(event.id) != -1;
+
+ if (event.deleted) {
+ this.hasOffline = true;
+ }
+ });
+ }
+
+ if (this.offlineEditedEventsIds.length) {
+ // Remove the online events that were modified in offline.
+ result = result.filter((event) => {
+ return this.offlineEditedEventsIds.indexOf(event.id) == -1;
+ });
+
+ if (result.length != this.onlineEvents.length) {
+ this.hasOffline = true;
+ }
+ }
+
+ if (dayOfflineEvents && dayOfflineEvents.length) {
+ // Add the offline events (either new or edited).
+ this.hasOffline = true;
+ result = this.sortEvents(result.concat(dayOfflineEvents));
+ }
+
+ return result;
+ }
+
+ /**
+ * Filter events to only display events belonging to a certain course.
+ */
+ protected filterEvents(): void {
+ if (!this.courseId || this.courseId < 0) {
+ this.filteredEvents = this.events;
+ } else {
+ this.filteredEvents = this.events.filter((event) => {
+ return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories);
+ });
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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.loaded) {
+ return this.refreshData(true, showErrors).finally(() => {
+ refresher && refresher.complete();
+ done && done();
+ });
+ }
+
+ return Promise.resolve();
+ }
+
+ /**
+ * Refresh the data.
+ *
+ * @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.
+ */
+ refreshData(sync?: boolean, showErrors?: boolean): Promise {
+ this.syncIcon = 'spinner';
+
+ const promises = [];
+
+ promises.push(this.calendarProvider.invalidateAllowedEventTypes());
+ promises.push(this.calendarProvider.invalidateDayEvents(this.year, this.month, this.day));
+ promises.push(this.coursesProvider.invalidateCategories(0, true));
+ promises.push(this.calendarProvider.invalidateTimeFormat());
+
+ return Promise.all(promises).finally(() => {
+ return this.fetchData(sync, showErrors);
+ });
+ }
+
+ /**
+ * Load categories to be able to filter events.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ protected loadCategories(): Promise {
+ return this.coursesProvider.getCategories(0, true).then((cats) => {
+ this.categories = {};
+
+ // Index categories by ID.
+ cats.forEach((category) => {
+ this.categories[category.id] = category;
+ });
+ }).catch(() => {
+ // Ignore errors.
+ });
+ }
+
+ /**
+ * Try to synchronize offline events.
+ *
+ * @param {boolean} [showErrors] Whether to show sync errors to the user.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected sync(showErrors?: boolean): Promise {
+ return 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 = 'day';
+ result.day = this.day;
+ result.month = this.month;
+ result.year = this.year;
+
+ this.eventsProvider.trigger(AddonCalendarSyncProvider.MANUAL_SYNCED, result, this.currentSiteId);
+ }
+ }).catch((error) => {
+ if (showErrors) {
+ this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
+ }
+ });
+ }
+
+ /**
+ * Navigate to a particular event.
+ *
+ * @param {number} eventId Event to load.
+ */
+ gotoEvent(eventId: number): void {
+ if (eventId < 0) {
+ // It's an offline event, go to the edit page.
+ this.openEdit(eventId);
+ } else {
+ this.navCtrl.push('AddonCalendarEventPage', {
+ id: eventId
+ });
+ }
+ }
+
+ /**
+ * Show the context menu.
+ *
+ * @param {MouseEvent} event Event.
+ */
+ openCourseFilter(event: MouseEvent): void {
+ this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
+ if (typeof result.courseId != 'undefined') {
+ this.courseId = result.courseId > 0 ? result.courseId : undefined;
+ this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
+
+ // Course viewed has changed, check if the user can create events for this course calendar.
+ this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
+ this.canCreate = canEdit;
+ });
+
+ this.filterEvents();
+ }
+ });
+ }
+
+ /**
+ * Open page to create/edit an event.
+ *
+ * @param {number} [eventId] Event ID to edit.
+ */
+ openEdit(eventId?: number): void {
+ const params: any = {};
+
+ if (eventId) {
+ params.eventId = eventId;
+ } else {
+ // It's a new event, set the time.
+ params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000;
+ }
+
+ if (this.courseId) {
+ params.courseId = this.courseId;
+ }
+
+ this.navCtrl.push('AddonCalendarEditEventPage', params);
+ }
+
+ /**
+ * Load next month.
+ */
+ loadNext(): void {
+ this.increaseDay();
+
+ this.loaded = false;
+
+ this.fetchEvents().catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
+ this.decreaseDay();
+ }).finally(() => {
+ this.loaded = true;
+ });
+ }
+
+ /**
+ * Load previous month.
+ */
+ loadPrevious(): void {
+ this.decreaseDay();
+
+ this.loaded = false;
+
+ this.fetchEvents().catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
+ this.increaseDay();
+ }).finally(() => {
+ this.loaded = true;
+ });
+ }
+
+ /**
+ * Decrease the current day.
+ */
+ protected decreaseDay(): void {
+ this.currentMoment.subtract(1, 'day');
+
+ this.year = this.currentMoment.year();
+ this.month = this.currentMoment.month() + 1;
+ this.day = this.currentMoment.date();
+ }
+
+ /**
+ * Increase the current day.
+ */
+ protected increaseDay(): void {
+ this.currentMoment.add(1, 'day');
+
+ this.year = this.currentMoment.year();
+ this.month = this.currentMoment.month() + 1;
+ this.day = this.currentMoment.date();
+ }
+
+ /**
+ * Find an event and mark it as deleted.
+ *
+ * @param {number} eventId Event ID.
+ * @param {boolean} deleted Whether to mark it as deleted or not.
+ * @return {boolean} Whether the event was found.
+ */
+ protected markAsDeleted(eventId: number, deleted: boolean): boolean {
+ const event = this.onlineEvents.find((event) => {
+ return event.id == eventId;
+ });
+
+ if (event) {
+ event.deleted = deleted;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Page destroyed.
+ */
+ ngOnDestroy(): void {
+ this.newEventObserver && this.newEventObserver.off();
+ this.discardedObserver && this.discardedObserver.off();
+ this.editEventObserver && this.editEventObserver.off();
+ this.deleteEventObserver && this.deleteEventObserver.off();
+ this.undeleteEventObserver && this.undeleteEventObserver.off();
+ this.syncObserver && this.syncObserver.off();
+ this.manualSyncObserver && this.manualSyncObserver.off();
+ this.onlineObserver && this.onlineObserver.unsubscribe();
+ }
+}
diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts
index edda98102..93f8b3499 100644
--- a/src/addon/calendar/pages/edit-event/edit-event.ts
+++ b/src/addon/calendar/pages/edit-event/edit-event.ts
@@ -99,6 +99,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.courseId = navParams.get('courseId');
this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent';
+ const timestamp = navParams.get('timestamp');
+
this.currentSite = sitesProvider.getCurrentSite();
this.errors = {
required: this.translate.instant('core.required')
@@ -114,7 +116,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
this.groupControl = this.fb.control('');
this.descriptionControl = this.fb.control('');
- const currentDate = this.timeUtils.toDatetimeFormat();
+ const currentDate = this.timeUtils.toDatetimeFormat(timestamp);
this.eventForm.addControl('name', this.fb.control('', Validators.required));
this.eventForm.addControl('timestart', this.fb.control(currentDate, Validators.required));
diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts
index 9f312a3e8..bfb45499c 100644
--- a/src/addon/calendar/pages/event/event.ts
+++ b/src/addon/calendar/pages/event/event.ts
@@ -493,7 +493,6 @@ export class AddonCalendarEventPage implements OnDestroy {
eventId: this.eventId
}, this.sitesProvider.getCurrentSiteId());
- this.domUtils.showToast('addon.calendar.eventcalendareventdeleted', true, 3000, undefined, false);
this.event.deleted = false;
}).catch((error) => {
diff --git a/src/addon/calendar/pages/index/index.html b/src/addon/calendar/pages/index/index.html
index 120c9dc2c..099892df8 100644
--- a/src/addon/calendar/pages/index/index.html
+++ b/src/addon/calendar/pages/index/index.html
@@ -28,7 +28,7 @@
{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}
-
+
diff --git a/src/addon/calendar/pages/index/index.ts b/src/addon/calendar/pages/index/index.ts
index 396690427..59088156b 100644
--- a/src/addon/calendar/pages/index/index.ts
+++ b/src/addon/calendar/pages/index/index.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, NgZone } from '@angular/core';
-import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular';
+import { IonicPage, NavParams, NavController } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
@@ -25,9 +25,7 @@ import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar';
import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
-import { CoreCoursesProvider } from '@core/courses/providers/courses';
-import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
-import { TranslateService } from '@ngx-translate/core';
+import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
import { Network } from '@ionic-native/network';
/**
@@ -42,11 +40,6 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
@ViewChild(AddonCalendarCalendarComponent) calendarComponent: AddonCalendarCalendarComponent;
@ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent: AddonCalendarUpcomingEventsComponent;
- protected allCourses = {
- id: -1,
- fullname: this.translate.instant('core.fulllistofcourses'),
- category: -1
- };
protected eventId: number;
protected currentSiteId: string;
@@ -83,10 +76,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
private calendarOffline: AddonCalendarOfflineProvider,
private calendarHelper: AddonCalendarHelperProvider,
private calendarSync: AddonCalendarSyncProvider,
- private translate: TranslateService,
private eventsProvider: CoreEventsProvider,
- private coursesProvider: CoreCoursesProvider,
- private popoverCtrl: PopoverController,
+ private coursesHelper: CoreCoursesHelperProvider,
private appProvider: CoreAppProvider) {
this.courseId = navParams.get('courseId');
@@ -206,21 +197,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.hasOffline = false;
// Load courses for the popover.
- promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
- // Add "All courses".
- courses.unshift(this.allCourses);
- this.courses = courses;
-
- if (this.courseId) {
- // Search the course to get the category.
- const course = this.courses.find((course) => {
- return course.id == this.courseId;
- });
-
- if (course) {
- this.categoryId = course.category;
- }
- }
+ promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => {
+ this.courses = data.courses;
+ this.categoryId = data.categoryId;
}));
// Check if user can create events.
@@ -273,9 +252,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
const promises = [];
- promises.push(this.calendarProvider.invalidateAllowedEventTypes().then(() => {
- return this.fetchData();
- }));
+ promises.push(this.calendarProvider.invalidateAllowedEventTypes());
// Refresh the sub-component.
if (this.showCalendar && this.calendarComponent) {
@@ -305,21 +282,35 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
}
}
+ /**
+ * View a certain day.
+ *
+ * @param {any} data Data with the year, month and day.
+ */
+ gotoDay(data: any): void {
+ const params: any = {
+ day: data.day,
+ month: data.month,
+ year: data.year
+ };
+
+ if (this.courseId) {
+ params.courseId = this.courseId;
+ }
+
+ this.navCtrl.push('AddonCalendarDayPage', params);
+ }
+
/**
* Show the context menu.
*
* @param {MouseEvent} event Event.
*/
openCourseFilter(event: MouseEvent): void {
- const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
- courses: this.courses,
- courseId: this.courseId
- });
-
- popover.onDidDismiss((course) => {
- if (course) {
- this.courseId = course.id > 0 ? course.id : undefined;
- this.categoryId = course.id > 0 ? course.category : undefined;
+ this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
+ if (typeof result.courseId != 'undefined') {
+ this.courseId = result.courseId > 0 ? result.courseId : undefined;
+ this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
// Course viewed has changed, check if the user can create events for this course calendar.
this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
@@ -327,9 +318,6 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
});
}
});
- popover.present({
- ev: event
- });
}
/**
diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts
index 00c6657d4..1c7572e12 100644
--- a/src/addon/calendar/pages/list/list.ts
+++ b/src/addon/calendar/pages/list/list.ts
@@ -13,19 +13,18 @@
// limitations under the License.
import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core';
-import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
-import { TranslateService } from '@ngx-translate/core';
+import { IonicPage, Content, NavParams, NavController } from 'ionic-angular';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
+import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
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';
-import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
import { CoreEventsProvider } from '@providers/events';
import { CoreAppProvider } from '@providers/app';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
@@ -50,11 +49,6 @@ export class AddonCalendarListPage implements OnDestroy {
protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
protected categoriesRetrieved = false;
protected getCategories = false;
- protected allCourses = {
- id: -1,
- fullname: this.translate.instant('core.fulllistofcourses'),
- category: -1
- };
protected categories = {};
protected siteHomeId: number;
protected obsDefaultTimeChange: any;
@@ -80,18 +74,17 @@ export class AddonCalendarListPage implements OnDestroy {
filteredEvents = [];
canLoadMore = false;
loadMoreError = false;
- filter = {
- course: this.allCourses
- };
+ courseId: number;
+ categoryId: number;
canCreate = false;
hasOffline = false;
isOnline = false;
syncIcon: string; // Sync icon.
- constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams,
+ constructor(private calendarProvider: AddonCalendarProvider, navParams: NavParams,
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, zone: NgZone,
- localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
+ localNotificationsProvider: CoreLocalNotificationsProvider, private coursesHelper: CoreCoursesHelperProvider,
private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider,
private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider,
network: Network, private timeUtils: CoreTimeUtilsProvider) {
@@ -177,6 +170,7 @@ export class AddonCalendarListPage implements OnDestroy {
if (data && !data.sent) {
// Event was deleted in offline. Just mark it as deleted, no need to refresh.
this.markAsDeleted(data.eventId, true);
+ this.deletedEvents.push(data.eventId);
this.hasOffline = true;
} else {
// Event deleted, clear the details if needed and refresh the view.
@@ -278,19 +272,17 @@ export class AddonCalendarListPage implements OnDestroy {
return promise.then(() => {
const promises = [];
- const courseId = this.filter.course.id != this.allCourses.id ? this.filter.course.id : undefined;
this.hasOffline = false;
- promises.push(this.calendarHelper.canEditEvents(courseId).then((canEdit) => {
+ promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
this.canCreate = canEdit;
}));
// Load courses for the popover.
- promises.push(this.coursesProvider.getUserCourses(false).then((courses) => {
- // Add "All courses".
- courses.unshift(this.allCourses);
- this.courses = courses;
+ promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((result) => {
+ this.courses = result.courses;
+ this.categoryId = result.categoryId;
if (this.preSelectedCourseId) {
this.filter.course = courses.find((course) => {
@@ -418,14 +410,13 @@ export class AddonCalendarListPage implements OnDestroy {
* @return {any[]} Filtered events.
*/
protected getFilteredEvents(): any[] {
- if (this.filter.course.id == -1) {
+ if (!this.courseId) {
// No filter, display everything.
return this.events;
}
return this.events.filter((event) => {
- return this.calendarHelper.shouldDisplayEvent(event, this.filter.course.id, this.filter.course.category,
- this.categories);
+ return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories);
});
}
@@ -613,28 +604,21 @@ export class AddonCalendarListPage implements OnDestroy {
* @param {MouseEvent} event Event.
*/
openCourseFilter(event: MouseEvent): void {
- const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
- courses: this.courses,
- courseId: this.filter.course.id
- });
- popover.onDidDismiss((course) => {
- if (course) {
- this.filter.course = course;
- this.domUtils.scrollToTop(this.content);
+ this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => {
+ if (typeof result.courseId != 'undefined') {
+ this.courseId = result.courseId > 0 ? result.courseId : undefined;
+ this.categoryId = result.courseId > 0 ? result.categoryId : undefined;
+
+ // Course viewed has changed, check if the user can create events for this course calendar.
+ this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => {
+ this.canCreate = canEdit;
+ });
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;
- });
+ this.domUtils.scrollToTop(this.content);
}
});
- popover.present({
- ev: event
- });
}
/**
@@ -650,8 +634,8 @@ export class AddonCalendarListPage implements OnDestroy {
if (eventId) {
params.eventId = eventId;
}
- if (this.filter.course.id != this.allCourses.id) {
- params.courseId = this.filter.course.id;
+ if (this.courseId) {
+ params.courseId = this.courseId;
}
this.splitviewCtrl.push('AddonCalendarEditEventPage', params);
diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts
index c759210db..3bb868034 100644
--- a/src/addon/calendar/providers/calendar.ts
+++ b/src/addon/calendar/providers/calendar.ts
@@ -858,6 +858,79 @@ export class AddonCalendarProvider {
});
}
+ /**
+ * Get calendar events for a certain day.
+ *
+ * @param {number} year Year to get.
+ * @param {number} month Month to get.
+ * @param {number} day Day to get.
+ * @param {number} [courseId] Course to get.
+ * @param {number} [categoryId] Category to get.
+ * @param {string} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved with the response.
+ */
+ getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, siteId?: string): Promise {
+
+ return this.sitesProvider.getSite(siteId).then((site) => {
+
+ const data: any = {
+ year: year,
+ month: month,
+ day: day
+ };
+
+ if (courseId) {
+ data.courseid = courseId;
+ }
+ if (categoryId) {
+ data.categoryid = categoryId;
+ }
+
+ const preSets = {
+ cacheKey: this.getDayEventsCacheKey(year, month, day, courseId, categoryId),
+ updateFrequency: CoreSite.FREQUENCY_SOMETIMES
+ };
+
+ return site.read('core_calendar_get_calendar_day_view', data, preSets);
+ });
+ }
+
+ /**
+ * Get prefix cache key for day events WS calls.
+ *
+ * @return {string} Prefix Cache key.
+ */
+ protected getDayEventsPrefixCacheKey(): string {
+ return this.ROOT_CACHE_KEY + 'day:';
+ }
+
+ /**
+ * Get prefix cache key for a certain day for day events WS calls.
+ *
+ * @param {number} year Year to get.
+ * @param {number} month Month to get.
+ * @param {number} day Day to get.
+ * @return {string} Prefix Cache key.
+ */
+ protected getDayEventsDayPrefixCacheKey(year: number, month: number, day: number): string {
+ return this.getDayEventsPrefixCacheKey() + year + ':' + month + ':' + day + ':';
+ }
+
+ /**
+ * Get cache key for day events WS calls.
+ *
+ * @param {number} year Year to get.
+ * @param {number} month Month to get.
+ * @param {number} day Day to get.
+ * @param {number} [courseId] Course to get.
+ * @param {number} [categoryId] Category to get.
+ * @return {string} Cache key.
+ */
+ protected getDayEventsCacheKey(year: number, month: number, day: number, courseId?: number, categoryId?: number): string {
+ return this.getDayEventsDayPrefixCacheKey(year, month, day) + (courseId ? courseId : '') + ':' +
+ (categoryId ? categoryId : '');
+ }
+
/**
* Get a calendar reminders from local Db.
*
@@ -1120,6 +1193,32 @@ export class AddonCalendarProvider {
});
}
+ /**
+ * Invalidates day events for all days.
+ *
+ * @param {string} [siteId] Site Id. If not defined, use current site.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateAllDayEvents(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKeyStartingWith(this.getDayEventsPrefixCacheKey());
+ });
+ }
+
+ /**
+ * Invalidates day events for a certain day.
+ *
+ * @param {number} year Year.
+ * @param {number} month Month.
+ * @param {number} day Day.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateDayEvents(year: number, month: number, day: number, siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKeyStartingWith(this.getDayEventsDayPrefixCacheKey(year, month, day));
+ });
+ }
+
/**
* Invalidates events list and all the single events and related info.
*
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index 7dae77a70..b78160d84 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -90,6 +90,8 @@
"addon.calendar.calendarreminders": "Calendar reminders",
"addon.calendar.confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?",
"addon.calendar.confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?",
+ "addon.calendar.daynext": "Next day",
+ "addon.calendar.dayprev": "Previous day",
"addon.calendar.defaultnotificationtime": "Default notification time",
"addon.calendar.deleteallevents": "Delete all events",
"addon.calendar.deleteevent": "Delete event",
diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts
index 6ff3ba5cf..abd9634fe 100644
--- a/src/core/courses/providers/helper.ts
+++ b/src/core/courses/providers/helper.ts
@@ -13,9 +13,12 @@
// limitations under the License.
import { Injectable } from '@angular/core';
+import { PopoverController } from 'ionic-angular';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from './courses';
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
+import { TranslateService } from '@ngx-translate/core';
+import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
/**
* Helper to gather some common courses functions.
@@ -23,8 +26,46 @@ import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers
@Injectable()
export class CoreCoursesHelperProvider {
- constructor(private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
- private courseCompletionProvider: AddonCourseCompletionProvider) { }
+ constructor(private coursesProvider: CoreCoursesProvider,
+ private utils: CoreUtilsProvider,
+ private courseCompletionProvider: AddonCourseCompletionProvider,
+ private translate: TranslateService,
+ private popoverCtrl: PopoverController) { }
+
+ /**
+ * Get the courses to display the course picker popover. If a courseId is specified, it will also return its categoryId.
+ *
+ * @param {number} [courseId] Course ID to get the category.
+ * @return {Promise<{courses: any[], categoryId: number}>} Promise resolved with the list of courses and the category.
+ */
+ getCoursesForPopover(courseId?: number): Promise<{courses: any[], categoryId: number}> {
+ return this.coursesProvider.getUserCourses(false).then((courses) => {
+ // Add "All courses".
+ courses.unshift({
+ id: -1,
+ fullname: this.translate.instant('core.fulllistofcourses'),
+ category: -1
+ });
+
+ let categoryId;
+
+ if (courseId) {
+ // Search the course to get the category.
+ const course = courses.find((course) => {
+ return course.id == courseId;
+ });
+
+ if (course) {
+ categoryId = course.category;
+ }
+ }
+
+ return {
+ courses: courses,
+ categoryId: categoryId
+ };
+ });
+ }
/**
* Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field,
@@ -174,4 +215,33 @@ export class CoreCoursesHelperProvider {
});
});
}
+
+ /**
+ * Show a context menu to select a course, and return the courseId and categoryId of the selected course (-1 for all courses).
+ * Returns an empty object if popover closed without picking a course.
+ *
+ * @param {MouseEvent} event Click event.
+ * @param {any[]} courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover.
+ * @param {number} courseId The course to select at start.
+ * @return {Promise<{courseId?: number, categoryId?: number}>} Promise resolved with the course ID and category ID.
+ */
+ selectCourse(event: MouseEvent, courses: any[], courseId: number): Promise<{courseId?: number, categoryId?: number}> {
+ return new Promise((resolve, reject): any => {
+ const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {
+ courses: courses,
+ courseId: courseId
+ });
+
+ popover.onDidDismiss((course) => {
+ if (course) {
+ resolve({courseId: course.id, categoryId: course.category});
+ } else {
+ resolve({});
+ }
+ });
+ popover.present({
+ ev: event
+ });
+ });
+ }
}