MOBILE-2308 calendar: List events page
parent
f6083227b4
commit
a9a63e5668
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AddonCalendarProvider } from './providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||
import { AddonCalendarMainMenuHandler } from './providers/handlers';
|
||||
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
||||
|
||||
|
@ -24,6 +25,7 @@ import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
|||
],
|
||||
providers: [
|
||||
AddonCalendarProvider,
|
||||
AddonCalendarHelperProvider,
|
||||
AddonCalendarMainMenuHandler
|
||||
]
|
||||
})
|
||||
|
|
|
@ -1,7 +1,37 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate">
|
||||
<ion-icon name="funnel"></ion-icon>
|
||||
</button>
|
||||
<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>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="eventsLoaded" (ionRefresh)="refreshEvents($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="eventsLoaded" class="mm-loading-center">
|
||||
<!-- @todo: Split view. -->
|
||||
<core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="calendar" [message]="'addon.calendar.noevents' | translate">
|
||||
</core-empty-box>
|
||||
|
||||
<ion-list *ngIf="filteredEvents && filteredEvents.length">
|
||||
<a ion-item text-wrap *ngFor="let event of filteredEvents" [title]="event.name">
|
||||
<!-- mm-split-view-link="site.calendar-event({id: event.id})" -->
|
||||
<img *ngIf="event.moduleicon" src="{{event.moduleicon}}" item-start>
|
||||
<ion-icon *ngIf="!event.moduleicon" name="{{event.icon}}" item-start></ion-icon>
|
||||
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||
<p>{{ event.timestart | coreToLocaleString }}</p>
|
||||
</a>
|
||||
</ion-list>
|
||||
|
||||
<ion-infinite-scroll [enabled]="canLoadMore" (ionInfinite)="$event.waitFor(fetchEvents())">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
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 { AddonCalendarListPage } from './list';
|
||||
|
||||
@NgModule({
|
||||
|
@ -22,8 +25,11 @@ import { AddonCalendarListPage } from './list';
|
|||
AddonCalendarListPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
IonicPageModule.forChild(AddonCalendarListPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonCalendarListPagePageModule {}
|
||||
export class AddonCalendarListPageModule {}
|
||||
|
|
|
@ -12,12 +12,21 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
//import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { Component, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { IonicPage, Content, PopoverController, NavParams, NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { CoreCoursesProvider } from '../../../../core/courses/providers/courses';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Page that displays the list of courses the user is enrolled in.
|
||||
* Page that displays the list of calendar events.
|
||||
*/
|
||||
@IonicPage()
|
||||
@Component({
|
||||
|
@ -25,20 +34,277 @@ import { IonicPage } from 'ionic-angular';
|
|||
templateUrl: 'list.html',
|
||||
})
|
||||
export class AddonCalendarListPage implements OnDestroy {
|
||||
eventsLoaded = false;
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
constructor() {}
|
||||
protected daysLoaded = 0;
|
||||
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;
|
||||
|
||||
courses: any[];
|
||||
eventsLoaded = false;
|
||||
events = [];
|
||||
notificationsEnabled = false;
|
||||
filteredEvents = [];
|
||||
eventToLoad = 1;
|
||||
canLoadMore = false;
|
||||
filter = {
|
||||
course: this.allCourses
|
||||
};
|
||||
|
||||
constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams,
|
||||
private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
|
||||
private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController,
|
||||
private eventsProvider: CoreEventsProvider, private navCtrl: NavController) {
|
||||
|
||||
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||
this.notificationsEnabled = localNotificationsProvider.isAvailable();
|
||||
if (this.notificationsEnabled) {
|
||||
// Re-schedule events if default time changes.
|
||||
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||
calendarProvider.scheduleEventsNotifications(this.events);
|
||||
});
|
||||
}
|
||||
|
||||
// @TODO: Split view once single event is done.
|
||||
// let eventId = navParams.get('eventid') || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad() {
|
||||
this.fetchData().then(() => {
|
||||
|
||||
}).finally(() => {
|
||||
this.eventsLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the data required for the view.
|
||||
*
|
||||
* @param {boolean} refresh Empty events array first.
|
||||
*/
|
||||
fetchData(refresh = false) {
|
||||
this.daysLoaded = 0;
|
||||
this.emptyEventsTimes = 0;
|
||||
|
||||
// Load courses for the popover.
|
||||
return this.coursesProvider.getUserCourses(false).then((courses) => {
|
||||
// Add "All courses".
|
||||
courses.unshift(this.allCourses);
|
||||
this.courses = courses;
|
||||
return this.fetchEvents(refresh);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the events and updates the view.
|
||||
*
|
||||
* @param {boolean} refresh Empty events array first.
|
||||
*/
|
||||
fetchEvents(refresh = false) {
|
||||
return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => {
|
||||
this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL;
|
||||
if (events.length === 0) {
|
||||
this.emptyEventsTimes++;
|
||||
if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
|
||||
this.canLoadMore = false;
|
||||
if (refresh) {
|
||||
this.events = [];
|
||||
this.filteredEvents = [];
|
||||
}
|
||||
} else {
|
||||
// No events returned, load next events.
|
||||
return this.fetchEvents();
|
||||
}
|
||||
} else {
|
||||
// Sort the events by timestart, they're ordered by id.
|
||||
events.sort((a, b) => {
|
||||
return a.timestart - b.timestart;
|
||||
});
|
||||
|
||||
events.forEach(this.calendarHelper.formatEventData);
|
||||
this.getCategories = this.shouldLoadCategories(events);
|
||||
|
||||
if (refresh) {
|
||||
this.events = events;
|
||||
} else {
|
||||
// Filter events with same ID. Repeated events are returned once per WS call, show them only once.
|
||||
this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id');
|
||||
}
|
||||
this.filteredEvents = this.getFilteredEvents();
|
||||
this.canLoadMore = true;
|
||||
|
||||
// Schedule notifications for the events retrieved (might have new events).
|
||||
this.calendarProvider.scheduleEventsNotifications(this.events);
|
||||
}
|
||||
|
||||
// Resize the content so infinite loading is able to calculate if it should load more items or not.
|
||||
// @TODO: Infinite loading is not working if content is not high enough.
|
||||
this.content.resize();
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
|
||||
this.canLoadMore = false; // Set to false to prevent infinite calls with infinite-loading.
|
||||
}).then(() => {
|
||||
// Success retrieving events. Get categories if needed.
|
||||
if (this.getCategories) {
|
||||
this.getCategories = false;
|
||||
return this.loadCategories();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filtered events.
|
||||
*/
|
||||
protected getFilteredEvents() {
|
||||
if (this.filter.course.id == -1) {
|
||||
// No filter, display everything.
|
||||
return this.events;
|
||||
}
|
||||
|
||||
return this.events.filter(this.shouldDisplayEvent.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event should be displayed based on the filter.
|
||||
*
|
||||
* @param {any} event Event object.
|
||||
*/
|
||||
protected shouldDisplayEvent(event: any) {
|
||||
if (event.eventtype == 'user' || event.eventtype == 'site') {
|
||||
// User or site event, display it.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.eventtype == 'category') {
|
||||
if (!event.categoryid || !Object.keys(this.categories).length) {
|
||||
// We can't tell if the course belongs to the category, display them all.
|
||||
return true;
|
||||
}
|
||||
if (event.categoryid == this.filter.course.category) {
|
||||
// The event is in the same category as the course, display it.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check parent categories.
|
||||
let category = this.categories[this.filter.course.category];
|
||||
while (category) {
|
||||
if (!category.parent) {
|
||||
// Category doesn't have parent, stop.
|
||||
break;
|
||||
}
|
||||
|
||||
if (event.categoryid == category.parent) {
|
||||
return true;
|
||||
}
|
||||
category = this.categories[category.parent];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show the event if it is from site home or if it matches the selected course.
|
||||
return event.courseid === this.siteHomeId || event.courseid == this.filter.course.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the current state should load categories or not.
|
||||
* @param {any[]} events Events to parse.
|
||||
* @return {boolean} True if categories should be loaded.
|
||||
*/
|
||||
protected shouldLoadCategories(events: any[]) : boolean {
|
||||
if (this.categoriesRetrieved || this.getCategories) {
|
||||
// Use previous value
|
||||
return this.getCategories;
|
||||
}
|
||||
|
||||
// Categories not loaded yet. We should get them if there's any category event.
|
||||
let found = events.some(event => event.categoryid != 'undefined' && event.categoryid > 0);
|
||||
return found || this.getCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load categories to be able to filter events.
|
||||
*/
|
||||
protected loadCategories() {
|
||||
return this.coursesProvider.getCategories(0, true).then((cats) => {
|
||||
this.categoriesRetrieved = true;
|
||||
this.categories = {};
|
||||
// Index categories by ID.
|
||||
cats.forEach(function(category) {
|
||||
this.categories[category.id] = category;
|
||||
});
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the events.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshEvents(refresher: any) {
|
||||
let promises = [];
|
||||
|
||||
promises.push(this.calendarProvider.invalidateEventsList(this.courses));
|
||||
|
||||
if (this.categoriesRetrieved) {
|
||||
promises.push(this.coursesProvider.invalidateCategories(0, true));
|
||||
this.categoriesRetrieved = false;
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchData(true).finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the context menu.
|
||||
*
|
||||
* @param {MouseEvent} event Event.
|
||||
*/
|
||||
openCourseFilter(event: MouseEvent) : void {
|
||||
let popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {courses: this.courses,
|
||||
courseId: this.filter.course.id});
|
||||
popover.onDidDismiss(course => {
|
||||
if (course) {
|
||||
this.filter.course = course;
|
||||
this.content.scrollToTop();
|
||||
this.filteredEvents = this.getFilteredEvents();
|
||||
}
|
||||
});
|
||||
popover.present({
|
||||
ev: event
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open calendar events settings.
|
||||
*/
|
||||
openSettings() {
|
||||
// @TODO: Check the settings page name.
|
||||
this.navCtrl.push('AddonCalendarSettingsPage');
|
||||
};
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off && this.obsDefaultTimeChange.off();
|
||||
}
|
||||
}
|
|
@ -16,16 +16,253 @@ import { Injectable } from '@angular/core';
|
|||
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreSite } from '../../../classes/site';
|
||||
import { CoreCoursesProvider } from '../../../core/courses/providers/courses';
|
||||
import { CoreTimeUtilsProvider } from '../../../providers/utils/time';
|
||||
import { CoreGroupsProvider } from '../../../providers/groups';
|
||||
import { CoreConstants } from '../../../core/constants';
|
||||
import { CoreLocalNotificationsProvider } from '../../../providers/local-notifications';
|
||||
import { CoreConfigProvider } from '../../../providers/config';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding lists of courses and categories.
|
||||
* Service to handle calendar events.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonCalendarProvider {
|
||||
public static DAYS_INTERVAL = 30;
|
||||
public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||
protected static DEFAULT_NOTIFICATION_TIME_SETTING = 'AddonCalendarDefaultNotifTime';
|
||||
protected static DEFAULT_NOTIFICATION_TIME = 60;
|
||||
protected static COMPONENT = 'AddonCalendarEvents';
|
||||
|
||||
// Variables for database.
|
||||
protected static EVENTS_TABLE = 'calendar_events'; // Queue of files to download.
|
||||
protected static tablesSchema = [
|
||||
{
|
||||
name: AddonCalendarProvider.EVENTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'notificationtime',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'eventtype',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'timestart',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'timeduration',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'categoryid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'groupid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'instance',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'modulename',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'repeatid',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
protected logger;
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider,
|
||||
private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) {
|
||||
this.logger = logger.getInstance('AddonCalendarProvider');
|
||||
this.sitesProvider.createTablesFromSchema(AddonCalendarProvider.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured default notification time.
|
||||
*
|
||||
* @param {string} [siteId] ID of the site. If not defined, use current site.
|
||||
* @return {Promise<number>} Promise resolved with the default time.
|
||||
*/
|
||||
getDefaultNotificationTime(siteId?: string) : Promise<number> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||
return this.configProvider.get(key, AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a calendar event from local Db.
|
||||
*
|
||||
* @param {number} id Event ID.
|
||||
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved when the event data is retrieved.
|
||||
*/
|
||||
getEventFromLocalDb(id: number, siteId?: string) : Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecord(AddonCalendarProvider.EVENTS_TABLE, {id: id});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event notification time. Always returns number of minutes (0 if disabled).
|
||||
*
|
||||
* @param {number} id Event ID.
|
||||
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||
* @return {Promise<number>} Event notification time in minutes. 0 if disabled.
|
||||
*/
|
||||
getEventNotificationTime(id: number, siteId?: string) : Promise<number> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.getEventNotificationTimeOption(id, siteId).then((time: number) => {
|
||||
if (time == -1) {
|
||||
return this.getDefaultNotificationTime(siteId);
|
||||
}
|
||||
return time;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event notification time for options. Returns -1 for default time.
|
||||
*
|
||||
* @param {number} id Event ID.
|
||||
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||
* @return {Promise<number>} Promise with wvent notification time in minutes. 0 if disabled, -1 if default time.
|
||||
*/
|
||||
getEventNotificationTimeOption(id: number, siteId?: string) : Promise<number> {
|
||||
return this.getEventFromLocalDb(id, siteId).then((e) => {
|
||||
return e.notificationtime || -1;
|
||||
}).catch(() => {
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events in a certain period. The period is calculated like this:
|
||||
* start time: now + daysToStart
|
||||
* end time: start time + daysInterval
|
||||
* E.g. using provider.getEventsList(30, 30) is going to get the events starting after 30 days from now
|
||||
* and ending before 60 days from now.
|
||||
*
|
||||
* @param {number} [daysToStart=0] Number of days from now to start getting events.
|
||||
* @param {number} [daysInterval=30] Number of days between timestart and timeend.
|
||||
* @param {string} [siteId] Site to get the events from. If not defined, use current site.
|
||||
* @return {Promise<any[]>} Promise to be resolved when the participants are retrieved.
|
||||
*/
|
||||
getEventsList(daysToStart = 0, daysInterval=AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
siteId = site.getId();
|
||||
|
||||
return this.coursesProvider.getUserCourses(false, siteId).then((courses) => {
|
||||
courses.push({id: site.getSiteHomeId()}); // Add front page.
|
||||
|
||||
return this.groupsProvider.getUserGroups(courses, siteId).then((groups) => {
|
||||
let now = this.timeUtils.timestamp(),
|
||||
start = now + (CoreConstants.secondsDay * daysToStart),
|
||||
end = start + (CoreConstants.secondsDay * daysInterval);
|
||||
|
||||
// The core_calendar_get_calendar_events needs all the current user courses and groups.
|
||||
let data = {
|
||||
"options[userevents]": 1,
|
||||
"options[siteevents]": 1,
|
||||
"options[timestart]": start,
|
||||
"options[timeend]": end
|
||||
};
|
||||
|
||||
courses.forEach((course, index) => {
|
||||
data["events[courseids][" + index + "]"] = course.id;
|
||||
});
|
||||
|
||||
groups.forEach((group, index) => {
|
||||
data["events[groupids][" + index + "]"] = group.id;
|
||||
});
|
||||
|
||||
// We need to retrieve cached data using cache key because we have timestamp in the params.
|
||||
let preSets = {
|
||||
cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval),
|
||||
getCacheUsingCacheKey: true
|
||||
};
|
||||
|
||||
return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => {
|
||||
this.storeEventsInLocalDB(response.events, siteId);
|
||||
return response.events;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for events list WS calls.
|
||||
*
|
||||
* @param {number} daysToStart Number of days from now to start getting events.
|
||||
* @param {number} daysInterval Number of days between timestart and timeend.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getEventsListCacheKey(daysToStart: number, daysInterval: number) : string {
|
||||
return this.getRootCacheKey() + 'eventslist:' + daysToStart + ':' + daysInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root cache key for the WS calls related to this provider.
|
||||
*
|
||||
* @return {string} Root cache key.
|
||||
*/
|
||||
protected getRootCacheKey() : string {
|
||||
return 'mmaCalendar:';
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates events list and all the single events and related info.
|
||||
*
|
||||
* @param {any[]} courses List of courses or course ids.
|
||||
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved when the list is invalidated.
|
||||
*/
|
||||
invalidateEventsList(courses: any[], siteId?: string) {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
siteId = site.getId();
|
||||
|
||||
let promises = [];
|
||||
|
||||
promises.push(this.coursesProvider.invalidateUserCourses(siteId));
|
||||
promises.push(this.groupsProvider.invalidateUserGroups(courses, siteId));
|
||||
promises.push(site.invalidateWsCacheForKeyStartingWith(this.getRootCacheKey()));
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,4 +276,122 @@ export class AddonCalendarProvider {
|
|||
return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an event notification. If time is 0, cancel scheduled notification if any.
|
||||
* If local notification plugin is not enabled, resolve the promise.
|
||||
*
|
||||
* @param {any} event Event to schedule.
|
||||
* @param {number} time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start".
|
||||
* @param {string} [siteId] Site ID the event belongs to. If not defined, use current site.
|
||||
* @return {Promise<void>} Promise resolved when the notification is scheduled.
|
||||
*/
|
||||
scheduleEventNotification(event: any, time: number, siteId?: string) : Promise<void> {
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (time === 0) {
|
||||
// Cancel if it was scheduled.
|
||||
return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, siteId);
|
||||
}
|
||||
|
||||
// If time is -1, get event default time.
|
||||
let promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time);
|
||||
|
||||
return promise.then((time) => {
|
||||
let timeend = (event.timestart + event.timeduration) * 1000;
|
||||
if (timeend <= new Date().getTime()) {
|
||||
// The event has finished already, don't schedule it.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let dateTriggered = new Date((event.timestart - (time * 60)) * 1000),
|
||||
startDate = new Date(event.timestart * 1000),
|
||||
notification = {
|
||||
id: event.id,
|
||||
title: event.name,
|
||||
text: startDate.toLocaleString(),
|
||||
at: dateTriggered,
|
||||
data: {
|
||||
eventid: event.id,
|
||||
siteid: siteId
|
||||
}
|
||||
};
|
||||
|
||||
return this.localNotificationsProvider.schedule(notification, AddonCalendarProvider.COMPONENT, siteId);
|
||||
});
|
||||
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Schedules the notifications for a list of events.
|
||||
* If an event notification time is 0, cancel its scheduled notification (if any).
|
||||
* If local notification plugin is not enabled, resolve the promise.
|
||||
*
|
||||
* @param {any[]} events Events to schedule.
|
||||
* @param {string} [siteId] ID of the site the events belong to. If not defined, use current site.
|
||||
* @return {Promise<any[]>} Promise resolved when all the notifications have been scheduled.
|
||||
*/
|
||||
scheduleEventsNotifications(events: any[], siteId?: string) : Promise<any[]> {
|
||||
var promises = [];
|
||||
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
events.forEach((e) => {
|
||||
promises.push(this.getEventNotificationTime(e.id, siteId).then((time) => {
|
||||
return this.scheduleEventNotification(e, time, siteId);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store events in local DB.
|
||||
*
|
||||
* @param {any[]} events Events to store.
|
||||
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||
* @return {Promise<any[]>} Promise resolved when the events are stored.
|
||||
*/
|
||||
protected storeEventsInLocalDB(events: any[], siteId?: string) : Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
siteId = site.getId();
|
||||
|
||||
let promises = [],
|
||||
db = site.getDb();
|
||||
|
||||
events.forEach((event) => {
|
||||
// Don't override event notification time if the user configured it.
|
||||
promises.push(this.getEventFromLocalDb(event.id, siteId).catch(() => {
|
||||
// Event not stored, return empty object.
|
||||
return {};
|
||||
}).then((e) => {
|
||||
let eventRecord = {
|
||||
id: event.id,
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
eventtype: event.eventtype,
|
||||
courseid: event.courseid,
|
||||
timestart: event.timestart,
|
||||
timeduration: event.timeduration,
|
||||
categoryid: event.categoryid,
|
||||
groupid: event.groupid,
|
||||
instance: event.instance,
|
||||
modulename: event.modulename,
|
||||
timemodified: event.timemodified,
|
||||
repeatid: event.repeatid,
|
||||
notificationtime: e.notificationtime || -1
|
||||
};
|
||||
|
||||
return db.insertOrUpdateRecord(AddonCalendarProvider.EVENTS_TABLE, eventRecord, {id: eventRecord.id});
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// (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 { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
//import { CoreCourseProvider } from '../../../core/course/providers/course';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding lists of courses and categories.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonCalendarHelperProvider {
|
||||
protected logger;
|
||||
|
||||
private static eventicons = {
|
||||
'course': 'ionic',
|
||||
'group': 'people',
|
||||
'site': 'globe',
|
||||
'user': 'person',
|
||||
'category': 'albums'
|
||||
};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
|
||||
this.logger = logger.getInstance('AddonCalendarHelperProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to format some event data to be rendered.
|
||||
*
|
||||
* @param {any} e Event to format.
|
||||
*/
|
||||
formatEventData(e: any) {
|
||||
let icon = AddonCalendarHelperProvider.eventicons[e.eventtype] || false;
|
||||
if (!icon) {
|
||||
// @TODO: It's a module event.
|
||||
//icon = this.courseProvider.getModuleIconSrc(e.modulename);
|
||||
e.moduleicon = icon;
|
||||
}
|
||||
e.icon = icon;
|
||||
};
|
||||
}
|
|
@ -802,8 +802,8 @@ export class CoreSite {
|
|||
}
|
||||
|
||||
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
||||
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?%';
|
||||
return this.db.execute(sql, [key]);
|
||||
let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?';
|
||||
return this.db.execute(sql, [key + "%"]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,6 +29,7 @@ import { CoreFileComponent } from './file/file';
|
|||
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||
import { CoreCoursePickerMenuPopoverComponent } from './course-picker-menu/course-picker-menu-popover';
|
||||
import { CoreChronoComponent } from './chrono/chrono';
|
||||
import { CoreLocalFileComponent } from './local-file/local-file';
|
||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
|
@ -47,12 +48,14 @@ import { CoreSitePickerComponent } from './site-picker/site-picker';
|
|||
CoreContextMenuComponent,
|
||||
CoreContextMenuItemComponent,
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreCoursePickerMenuPopoverComponent,
|
||||
CoreChronoComponent,
|
||||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreCoursePickerMenuPopoverComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<ion-list radio-group [(ngModel)]="courseId">
|
||||
<ion-item text-wrap *ngFor="let course of courses" >
|
||||
<ion-label><core-format-text [text]="course.fullname"></core-format-text></ion-label>
|
||||
<ion-radio value="{{course.id}}" (ionSelect)="coursePicked($event, course)" ></ion-radio>
|
||||
</ion-item>
|
||||
</ion-list>
|
|
@ -0,0 +1,45 @@
|
|||
// (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 { Component } from '@angular/core';
|
||||
import { NavParams, ViewController } from 'ionic-angular';
|
||||
|
||||
/**
|
||||
* Component to display a list of courses.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-course-picker-menu-popover',
|
||||
templateUrl: 'course-picker-menu-popover.html'
|
||||
})
|
||||
export class CoreCoursePickerMenuPopoverComponent {
|
||||
courses: any[];
|
||||
courseId = -1;
|
||||
|
||||
constructor(private navParams: NavParams, private viewCtrl: ViewController) {
|
||||
this.courses = navParams.get('courses') || [];
|
||||
this.courseId = navParams.get('courseId') || -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a course is clicked.
|
||||
*
|
||||
* @param {Event} event Click event.
|
||||
* @param {any} course Course object clicked.
|
||||
* @return {boolean} Return true if success, false if error.
|
||||
*/
|
||||
coursePicked(event: Event, course: any) : boolean {
|
||||
this.viewCtrl.dismiss(course);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -739,7 +739,7 @@ export class CoreSitesProvider {
|
|||
* @param {number} [siteId] The site ID. If not defined, current site (if available).
|
||||
* @return {Promise} Promise resolved with site home ID.
|
||||
*/
|
||||
getSiteHomeId(siteId: string) : Promise<number> {
|
||||
getSiteHomeId(siteId?: string) : Promise<number> {
|
||||
return this.getSite(siteId).then((site) => {
|
||||
return site.getSiteHomeId();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue