MOBILE-2308 calendar: List events page

main
Pau Ferrer Ocaña 2017-12-22 09:09:47 +01:00
parent f6083227b4
commit a9a63e5668
11 changed files with 779 additions and 13 deletions

View File

@ -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
]
})

View File

@ -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>

View File

@ -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 {}

View File

@ -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();
}
}

View File

@ -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);
});
}
}

View File

@ -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;
};
}

View File

@ -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 + "%"]);
}
/**

View File

@ -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,

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
});