diff --git a/scripts/langindex.json b/scripts/langindex.json index 6377afa61..0cbf1d82e 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -89,8 +89,10 @@ "addon.calendar.calendarevent": "local_moodlemobileapp", "addon.calendar.calendarevents": "local_moodlemobileapp", "addon.calendar.calendarreminders": "local_moodlemobileapp", + "addon.calendar.categoryevents": "calendar", "addon.calendar.confirmeventdelete": "calendar", "addon.calendar.confirmeventseriesdelete": "calendar", + "addon.calendar.courseevents": "calendar", "addon.calendar.currentmonth": "local_moodlemobileapp", "addon.calendar.daynext": "calendar", "addon.calendar.dayprev": "calendar", @@ -114,6 +116,7 @@ "addon.calendar.fri": "calendar", "addon.calendar.friday": "calendar", "addon.calendar.gotoactivity": "calendar", + "addon.calendar.groupevents": "calendar", "addon.calendar.invalidtimedurationminutes": "calendar", "addon.calendar.invalidtimedurationuntil": "calendar", "addon.calendar.mon": "calendar", @@ -131,6 +134,7 @@ "addon.calendar.sat": "calendar", "addon.calendar.saturday": "calendar", "addon.calendar.setnewreminder": "local_moodlemobileapp", + "addon.calendar.siteevents": "calendar", "addon.calendar.sun": "calendar", "addon.calendar.sunday": "calendar", "addon.calendar.thu": "calendar", @@ -149,6 +153,7 @@ "addon.calendar.typesite": "calendar", "addon.calendar.typeuser": "calendar", "addon.calendar.upcomingevents": "calendar", + "addon.calendar.userevents": "calendar", "addon.calendar.wed": "calendar", "addon.calendar.wednesday": "calendar", "addon.calendar.when": "calendar", @@ -1519,6 +1524,7 @@ "core.fileuploader.uploading": "local_moodlemobileapp", "core.fileuploader.uploadingperc": "local_moodlemobileapp", "core.fileuploader.video": "local_moodlemobileapp", + "core.filter": "moodle", "core.folder": "moodle", "core.forcepasswordchangenotice": "moodle", "core.fulllistofcourses": "moodle", diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index 37f7f2017..06a8bbf6c 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -27,6 +27,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreLoginHelperProvider } from '@core/login/providers/helper'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { AddonCalendarComponentsModule } from './components/components.module'; // List of providers (without handlers). export const ADDON_CALENDAR_PROVIDERS: any[] = [ @@ -40,6 +41,7 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [ declarations: [ ], imports: [ + AddonCalendarComponentsModule, ], providers: [ AddonCalendarProvider, diff --git a/src/addon/calendar/components/calendar/calendar.scss b/src/addon/calendar/components/calendar/calendar.scss index 17c3fd478..d04e2091b 100644 --- a/src/addon/calendar/components/calendar/calendar.scss +++ b/src/addon/calendar/components/calendar/calendar.scss @@ -10,7 +10,8 @@ $calendar-border-color: $gray !default; ion-app.app-root page-addon-calendar-list, ion-app.app-root page-addon-calendar-day, -ion-app.app-root addon-calendar-upcoming-events { +ion-app.app-root addon-calendar-upcoming-events, +ion-app.app-root addon-calendar-filter-popover { .item.addon-calendar-event { > .icon { @@ -49,6 +50,26 @@ ion-app.app-root addon-calendar-upcoming-events { } } +ion-app.app-root addon-calendar-filter-popover .item.addon-calendar-event { + ion-icon[item-start] + .item-inner { + @include margin-horizontal(8px, 0); + } + + > .icon { + width: 28px; + height: 28px; + line-height: 28px; + font-size: 20px; + + &.fa { + font-size: 16px; + &::before { + width: 1.8em; + } + } + } +} + ion-app.app-root addon-calendar-calendar { .addon-calendar-navigation { diff --git a/src/addon/calendar/components/calendar/calendar.ts b/src/addon/calendar/components/calendar/calendar.ts index 05ca83e1d..55a871937 100644 --- a/src/addon/calendar/components/calendar/calendar.ts +++ b/src/addon/calendar/components/calendar/calendar.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core'; +import { Component, OnDestroy, OnInit, Input, OnChanges, DoCheck, SimpleChange, Output, EventEmitter, + KeyValueDiffers } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; @@ -32,11 +33,10 @@ import { CoreAppProvider } from '@providers/app'; selector: 'addon-calendar-calendar', templateUrl: 'addon-calendar-calendar.html', }) -export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDestroy { +export class AddonCalendarCalendarComponent implements OnInit, OnChanges, DoCheck, OnDestroy { @Input() initialYear: number | string; // Initial year to load. @Input() initialMonth: number | string; // Initial month to load. - @Input() courseId: number | string; - @Input() categoryId: number | string; // Category ID the course belongs to. + @Input() filter: any; // Filter to apply. @Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true. @Input() displayNavButtons?: string | boolean; // Whether to display nav buttons created by this component. Defaults to true. @Output() onEventClicked = new EventEmitter(); @@ -59,6 +59,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest protected offlineEditedEventsIds = []; // IDs of events edited in offline. protected deletedEvents = []; // Events deleted in offline. protected currentTime: number; + protected differ: any; // To detect changes in the data input. // Observers. protected undeleteEventObserver: any; @@ -67,6 +68,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest constructor(eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, localNotificationsProvider: CoreLocalNotificationsProvider, + differs: KeyValueDiffers, private calendarProvider: AddonCalendarProvider, private calendarHelper: AddonCalendarHelperProvider, private calendarOffline: AddonCalendarOfflineProvider, @@ -102,6 +104,8 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest } } }, this.currentSiteId); + + this.differ = differs.find([]).create(); } /** @@ -125,9 +129,18 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest this.canNavigate = typeof this.canNavigate == 'undefined' ? true : this.utils.isTrueOrOne(this.canNavigate); this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true : this.utils.isTrueOrOne(this.displayNavButtons); + } - if ((changes.courseId || changes.categoryId) && this.weeks) { - this.filterEvents(); + /** + * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). + */ + ngDoCheck(): void { + if (this.weeks) { + // Check if there's any change in the filter object. + const changes = this.differ.diff(this.filter); + if (changes) { + this.filterEvents(); + } } } @@ -260,21 +273,12 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest } /** - * Filter events to only display events belonging to a certain course. + * Filter events based on the filter popover. */ filterEvents(): void { - const courseId = this.courseId ? Number(this.courseId) : undefined, - categoryId = this.categoryId ? Number(this.categoryId) : undefined; - this.weeks.forEach((week) => { week.days.forEach((day) => { - if (!courseId || courseId < 0) { - day.filteredEvents = day.events; - } else { - day.filteredEvents = day.events.filter((event) => { - return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories); - }); - } + day.filteredEvents = this.calendarHelper.getFilteredEvents(day.events, this.filter, this.categories); // Re-calculate some properties. this.calendarHelper.calculateDayData(day, day.filteredEvents); diff --git a/src/addon/calendar/components/components.module.ts b/src/addon/calendar/components/components.module.ts index 45656098d..dacc846b9 100644 --- a/src/addon/calendar/components/components.module.ts +++ b/src/addon/calendar/components/components.module.ts @@ -21,11 +21,13 @@ import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { AddonCalendarCalendarComponent } from '../components/calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from '../components/upcoming-events/upcoming-events'; +import { AddonCalendarFilterPopoverComponent } from '../components/filter/filter'; @NgModule({ declarations: [ AddonCalendarCalendarComponent, - AddonCalendarUpcomingEventsComponent + AddonCalendarUpcomingEventsComponent, + AddonCalendarFilterPopoverComponent ], imports: [ CommonModule, @@ -39,7 +41,11 @@ import { AddonCalendarUpcomingEventsComponent } from '../components/upcoming-eve ], exports: [ AddonCalendarCalendarComponent, - AddonCalendarUpcomingEventsComponent + AddonCalendarUpcomingEventsComponent, + AddonCalendarFilterPopoverComponent + ], + entryComponents: [ + AddonCalendarFilterPopoverComponent ] }) export class AddonCalendarComponentsModule {} diff --git a/src/addon/calendar/components/filter/addon-calendar-filter-popover.html b/src/addon/calendar/components/filter/addon-calendar-filter-popover.html new file mode 100644 index 000000000..8de25e0db --- /dev/null +++ b/src/addon/calendar/components/filter/addon-calendar-filter-popover.html @@ -0,0 +1,14 @@ + + + + {{ 'addon.calendar.' + type + 'events' | translate}} + + + + + + + + + + diff --git a/src/addon/calendar/components/filter/filter.ts b/src/addon/calendar/components/filter/filter.ts new file mode 100644 index 000000000..a2f057404 --- /dev/null +++ b/src/addon/calendar/components/filter/filter.ts @@ -0,0 +1,69 @@ +// (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 } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarHelperProvider } from '../../providers/helper'; + +/** + * Component to display a list of courses. + */ +@Component({ + selector: 'addon-calendar-filter-popover', + templateUrl: 'addon-calendar-filter-popover.html' +}) +export class AddonCalendarFilterPopoverComponent { + courses: any[]; + courseId = -1; + + types = {}; + typeIcons = {}; + typeKeys = []; + + constructor(navParams: NavParams, protected eventsProvider: CoreEventsProvider) { + this.courses = navParams.get('courses') || []; + const filter = navParams.get('filter') || {}; + this.courseId = filter.courseId || -1; + + this.typeKeys = AddonCalendarProvider.ALL_TYPES.map((name) => { + this.types[name] = filter[name]; + this.typeIcons[name] = AddonCalendarHelperProvider.EVENTICONS[name]; + + return name; + }); + } + + /** + * Function called when an item is clicked. + * + * @param event Click event. + */ + onChange(event: Event): void { + const filter = this.types; + if (this.courseId > 0) { + const course = this.courses.find((course) => this.courseId == course.id); + filter['courseId'] = course && course.id; + filter['categoryId'] = course && course.category; + } else { + filter['courseId'] = false; + filter['categoryId'] = false; + } + + filter['filtered'] = filter['courseId'] || AddonCalendarProvider.ALL_TYPES.some((name) => !this.types[name]); + + this.eventsProvider.trigger(AddonCalendarProvider.FILTER_CHANGED_EVENT, filter); + } +} diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.ts b/src/addon/calendar/components/upcoming-events/upcoming-events.ts index 4b12725ea..d96c0cf25 100644 --- a/src/addon/calendar/components/upcoming-events/upcoming-events.ts +++ b/src/addon/calendar/components/upcoming-events/upcoming-events.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core'; +import { Component, OnDestroy, OnInit, Input, DoCheck, Output, EventEmitter, KeyValueDiffers } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; @@ -30,9 +30,8 @@ import { CoreConstants } from '@core/constants'; selector: 'addon-calendar-upcoming-events', templateUrl: 'addon-calendar-upcoming-events.html', }) -export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, OnDestroy { - @Input() courseId: number | string; - @Input() categoryId: number | string; // Category ID the course belongs to. +export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, OnDestroy { + @Input() filter: any; // Filter to apply. @Output() onEventClicked = new EventEmitter(); filteredEvents = []; @@ -49,6 +48,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, protected deletedEvents = []; // Events deleted in offline. protected lookAhead: number; protected timeFormat: string; + protected differ: any; // To detect changes in the data input. // Observers. protected undeleteEventObserver: any; @@ -57,6 +57,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, constructor(eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, localNotificationsProvider: CoreLocalNotificationsProvider, + differs: KeyValueDiffers, private calendarProvider: AddonCalendarProvider, private calendarHelper: AddonCalendarHelperProvider, private calendarOffline: AddonCalendarOfflineProvider, @@ -85,6 +86,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, } } }, this.currentSiteId); + + this.differ = differs.find([]).create(); } /** @@ -95,10 +98,12 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, } /** - * Detect changes on input properties. + * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). */ - ngOnChanges(changes: {[name: string]: SimpleChange}): void { - if (changes.courseId || changes.categoryId) { + ngDoCheck(): void { + // Check if there's any change in the filter object. + const changes = this.differ.diff(this.filter); + if (changes) { this.filterEvents(); } } @@ -207,19 +212,10 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, } /** - * Filter events to only display events belonging to a certain course. + * Filter events based on the filter popover. */ - filterEvents(): void { - const courseId = this.courseId ? Number(this.courseId) : undefined, - categoryId = this.categoryId ? Number(this.categoryId) : undefined; - - if (!courseId || courseId < 0) { - this.filteredEvents = this.events; - } else { - this.filteredEvents = this.events.filter((event) => { - return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories); - }); - } + protected filterEvents(): void { + this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } /** diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json index 9c65c4488..8b1748f6b 100644 --- a/src/addon/calendar/lang/en.json +++ b/src/addon/calendar/lang/en.json @@ -4,8 +4,10 @@ "calendarevent": "Calendar event", "calendarevents": "Calendar events", "calendarreminders": "Calendar reminders", + "categoryevents": "Category events", "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?", + "courseevents": "Course events", "currentmonth": "Current Month", "daynext": "Next day", "dayprev": "Previous day", @@ -29,6 +31,7 @@ "fri": "Fri", "friday": "Friday", "gotoactivity": "Go to activity", + "groupevents": "Group events", "invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.", "invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.", "mon": "Mon", @@ -46,6 +49,7 @@ "sat": "Sat", "saturday": "Saturday", "setnewreminder": "Set a new reminder", + "siteevents": "Site events", "sun": "Sun", "sunday": "Sunday", "thu": "Thu", @@ -64,6 +68,7 @@ "typesite": "Site event", "typeuser": "User event", "upcomingevents": "Upcoming events", + "userevents": "User events", "wed": "Wed", "wednesday": "Wednesday", "when": "When", diff --git a/src/addon/calendar/pages/day/day.html b/src/addon/calendar/pages/day/day.html index 913e5ea4e..d81aff79e 100644 --- a/src/addon/calendar/pages/day/day.html +++ b/src/addon/calendar/pages/day/day.html @@ -2,9 +2,9 @@ {{ 'addon.calendar.calendarevents' | translate }} - diff --git a/src/addon/calendar/pages/day/day.ts b/src/addon/calendar/pages/day/day.ts index 3730cd253..bcf71420f 100644 --- a/src/addon/calendar/pages/day/day.ts +++ b/src/addon/calendar/pages/day/day.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy, NgZone } from '@angular/core'; -import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; @@ -26,6 +26,7 @@ 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 { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import { Network } from '@ionic-native/network'; import * as moment from 'moment'; @@ -63,11 +64,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { protected manualSyncObserver: any; protected onlineObserver: any; protected obsDefaultTimeChange: any; + protected filterChangedObserver: any; periodName: string; filteredEvents = []; - courseId: number; - categoryId: number; canCreate = false; courses: any[]; loaded = false; @@ -76,6 +76,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { syncIcon: string; isCurrentDay: boolean; isPastDay: boolean; + filter = {}; constructor(localNotificationsProvider: CoreLocalNotificationsProvider, navParams: NavParams, @@ -92,14 +93,19 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { private eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider, private coursesHelper: CoreCoursesHelperProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, + private popoverCtrl: PopoverController) { const now = new Date(); + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = true; + }); + this.filter['courseId'] = navParams.get('courseId'); + 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(); if (localNotificationsProvider.isAvailable()) { @@ -186,6 +192,17 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + + this.filterEvents(); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -223,9 +240,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { const promises = []; // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter['courseId']).then((data) => { this.courses = data.courses; - this.categoryId = data.categoryId; })); // Get categories. @@ -257,7 +273,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { })); // Check if user can create events. - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { this.canCreate = canEdit; })); @@ -374,16 +390,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } /** - * Filter events to only display events belonging to a certain course. + * Filter events based on the filter popover. */ 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); - }); - } + this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } /** @@ -513,19 +523,14 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * * @param 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; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // 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(); - } + popover.present({ + ev: event }); } @@ -544,8 +549,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.navCtrl.push('AddonCalendarEditEventPage', params); @@ -697,6 +702,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.obsDefaultTimeChange && this.obsDefaultTimeChange.off(); } } diff --git a/src/addon/calendar/pages/index/index.html b/src/addon/calendar/pages/index/index.html index d2d6d38b0..005d68f25 100644 --- a/src/addon/calendar/pages/index/index.html +++ b/src/addon/calendar/pages/index/index.html @@ -2,9 +2,9 @@ {{ (showCalendar ? 'addon.calendar.calendarevents' : 'addon.calendar.upcomingevents') | translate }} - @@ -25,9 +25,9 @@ {{ '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 6ab4a88ba..4fad22e36 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 } from 'ionic-angular'; +import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; @@ -24,6 +24,7 @@ import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events'; +import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { Network } from '@ionic-native/network'; @@ -52,11 +53,10 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { protected syncObserver: any; protected manualSyncObserver: any; protected onlineObserver: any; + protected filterChangedObserver: any; year: number; month: number; - courseId: number; - categoryId: number; canCreate = false; courses: any[]; notificationsEnabled = false; @@ -66,6 +66,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { syncIcon: string; showCalendar = true; loadUpcoming = false; + filter = {}; constructor(localNotificationsProvider: CoreLocalNotificationsProvider, navParams: NavParams, @@ -80,9 +81,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { private calendarSync: AddonCalendarSyncProvider, private eventsProvider: CoreEventsProvider, private coursesHelper: CoreCoursesHelperProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, + private popoverCtrl: PopoverController) { - this.courseId = navParams.get('courseId'); this.eventId = navParams.get('eventId') || false; this.year = navParams.get('year'); this.month = navParams.get('month'); @@ -91,6 +92,11 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.loadUpcoming = !!navParams.get('upcoming'); this.showCalendar = !this.loadUpcoming; + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = true; + }); + this.filter['courseId'] = navParams.get('courseId'); + // 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) { @@ -140,6 +146,15 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { }); }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -203,13 +218,12 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.hasOffline = false; // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter['courseId']).then((data) => { this.courses = data.courses; - this.categoryId = data.categoryId; })); // Check if user can create events. - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { this.canCreate = canEdit; })); @@ -301,8 +315,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { year: data.year }; - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.navCtrl.push('AddonCalendarDayPage', params); @@ -313,17 +327,14 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { * * @param 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; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // 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; - }); - } + popover.present({ + ev: event }); } @@ -338,8 +349,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { if (eventId) { params.eventId = eventId; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.navCtrl.push('AddonCalendarEditEventPage', params); @@ -374,6 +385,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.undeleteEventObserver && this.undeleteEventObserver.off(); this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); } } diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 973184953..9f5e4178f 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -2,9 +2,9 @@ {{ 'addon.calendar.calendarevents' | translate }} - diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 5a0643ae1..68195ddcf 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; -import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; +import { IonicPage, Content, NavParams, NavController, PopoverController } from 'ionic-angular'; import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; @@ -31,6 +31,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; import * as moment from 'moment'; import { Network } from '@ionic-native/network'; import { CoreConstants } from '@core/constants'; +import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; /** * Page that displays the list of calendar events. @@ -51,8 +52,14 @@ export class AddonCalendarListPage implements OnDestroy { protected getCategories = false; protected categories = {}; protected siteHomeId: number; - protected obsDefaultTimeChange: any; protected eventId: number; + protected currentSiteId: string; + protected onlineEvents: AddonCalendarGetEventsEvent[] = []; + protected offlineEvents = []; + protected deletedEvents = []; + + // Observers. + protected obsDefaultTimeChange: any; protected newEventObserver: any; protected discardedObserver: any; protected editEventObserver: any; @@ -61,10 +68,7 @@ export class AddonCalendarListPage implements OnDestroy { protected syncObserver: any; protected manualSyncObserver: any; protected onlineObserver: any; - protected currentSiteId: string; - protected onlineEvents: AddonCalendarGetEventsEvent[] = []; - protected offlineEvents = []; - protected deletedEvents = []; + protected filterChangedObserver: any; courses: any[]; eventsLoaded = false; @@ -73,20 +77,31 @@ export class AddonCalendarListPage implements OnDestroy { filteredEvents: AddonCalendarGetEventsEvent[] = []; canLoadMore = false; loadMoreError = false; - courseId: number; - categoryId: number; canCreate = false; hasOffline = false; isOnline = false; syncIcon: string; // Sync icon. + filter = {}; - 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 coursesHelper: CoreCoursesHelperProvider, - private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider, - private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider, - network: Network, private timeUtils: CoreTimeUtilsProvider) { + constructor( + navParams: NavParams, + sitesProvider: CoreSitesProvider, + network: Network, + zone: NgZone, + localNotificationsProvider: CoreLocalNotificationsProvider, + private calendarProvider: AddonCalendarProvider, + private domUtils: CoreDomUtilsProvider, + private coursesProvider: CoreCoursesProvider, + private utils: CoreUtilsProvider, + private calendarHelper: AddonCalendarHelperProvider, + private coursesHelper: CoreCoursesHelperProvider, + private eventsProvider: CoreEventsProvider, + private navCtrl: NavController, + private appProvider: CoreAppProvider, + private calendarOffline: AddonCalendarOfflineProvider, + private calendarSync: AddonCalendarSyncProvider, + private timeUtils: CoreTimeUtilsProvider, + private popoverCtrl: PopoverController) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); this.notificationsEnabled = localNotificationsProvider.isAvailable(); @@ -100,7 +115,11 @@ export class AddonCalendarListPage implements OnDestroy { } this.eventId = navParams.get('eventId') || false; - this.courseId = navParams.get('courseId'); + + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = true; + }); + this.filter['courseId'] = navParams.get('courseId'); // Listen for events added. When an event is added, reload the data. this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { @@ -198,6 +217,19 @@ export class AddonCalendarListPage implements OnDestroy { } }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + + this.filterEvents(); + + this.domUtils.scrollToTop(this.content); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -274,14 +306,13 @@ export class AddonCalendarListPage implements OnDestroy { this.hasOffline = false; - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { this.canCreate = canEdit; })); // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((result) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter['courseId']).then((result) => { this.courses = result.courses; - this.categoryId = result.categoryId; return this.fetchEvents(refresh); })); @@ -354,7 +385,7 @@ export class AddonCalendarListPage implements OnDestroy { this.onlineEvents = this.utils.mergeArraysWithoutDuplicates(this.onlineEvents, onlineEvents, 'id'); this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id'); } - this.filteredEvents = this.getFilteredEvents(); + this.filterEvents(); // Calculate which evemts need to display the date. this.filteredEvents.forEach((event, index): any => { @@ -398,19 +429,9 @@ export class AddonCalendarListPage implements OnDestroy { } /** - * Get filtered events. - * - * @return Filtered events. */ - protected getFilteredEvents(): AddonCalendarGetEventsEvent[] { - if (!this.courseId) { - // No filter, display everything. - return this.events; - } - - return this.events.filter((event) => { - return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories); - }); + protected filterEvents(): void { + this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } /** @@ -596,21 +617,14 @@ export class AddonCalendarListPage implements OnDestroy { * * @param 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; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // 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(); - - this.domUtils.scrollToTop(this.content); - } + popover.present({ + ev: event }); } @@ -627,8 +641,8 @@ export class AddonCalendarListPage implements OnDestroy { if (eventId) { params.eventId = eventId; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.splitviewCtrl.push('AddonCalendarEditEventPage', params); @@ -687,6 +701,7 @@ export class AddonCalendarListPage implements OnDestroy { this.undeleteEventObserver && this.undeleteEventObserver.off(); this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); } } diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index cb8dd8485..ed3891a00 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -50,11 +50,17 @@ export class AddonCalendarProvider { static EDIT_EVENT_EVENT = 'addon_calendar_edit_event'; static DELETED_EVENT_EVENT = 'addon_calendar_deleted_event'; static UNDELETED_EVENT_EVENT = 'addon_calendar_undeleted_event'; + static FILTER_CHANGED_EVENT = 'addon_calendar_filter_changed_event'; static TYPE_CATEGORY = 'category'; static TYPE_COURSE = 'course'; static TYPE_GROUP = 'group'; static TYPE_SITE = 'site'; static TYPE_USER = 'user'; + static ALL_TYPES = [ AddonCalendarProvider.TYPE_SITE, + AddonCalendarProvider.TYPE_CATEGORY, + AddonCalendarProvider.TYPE_COURSE, + AddonCalendarProvider.TYPE_GROUP, + AddonCalendarProvider.TYPE_USER ]; static CALENDAR_TF_24 = '%H:%M'; // Calendar time in 24 hours format. static CALENDAR_TF_12 = '%I:%M %p'; // Calendar time in 12 hours format. diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index 8ff3e2810..68aa6abe1 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -29,8 +29,8 @@ import * as moment from 'moment'; export class AddonCalendarHelperProvider { protected logger; - protected EVENTICONS = { - course: 'fa-university', + static EVENTICONS = { + course: 'fa-graduation-cap', group: 'people', site: 'globe', user: 'person', @@ -131,7 +131,7 @@ export class AddonCalendarHelperProvider { * @param e Event to format. */ formatEventData(e: any): void { - e.eventIcon = this.EVENTICONS[e.eventtype] || ''; + e.eventIcon = AddonCalendarHelperProvider.EVENTICONS[e.eventtype] || ''; if (!e.eventIcon) { e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename); e.moduleIcon = e.eventIcon; @@ -309,6 +309,37 @@ export class AddonCalendarHelperProvider { return false; } + /** + * Filter events to be shown on the events list. + * + * @param events Events without filtering. + * @param filter Filter from popover. + * @param categories Categories indexed by ID. + * @return Filtered events. + */ + getFilteredEvents(events: any[], filter: any, categories: any): any[] { + // Do not filter. + if (!filter.filtered) { + return events; + } + + const courseId = filter.courseId ? Number(filter.courseId) : undefined; + + if (!courseId || courseId < 0) { + // Filter only by type. + return events.filter((event) => { + return filter[event.formattedType]; + }); + } + + const categoryId = filter.categoryId ? Number(filter.categoryId) : undefined; + + return events.filter((event) => { + return filter[event.formattedType] && + this.shouldDisplayEvent(event, courseId, categoryId, categories); + }); + } + /** * Check if an event should be displayed based on the filter. * @@ -352,8 +383,10 @@ export class AddonCalendarHelperProvider { return false; } + const eventCourse = (event.course && event.course.id) || event.courseid; + // Show the event if it is from site home or if it matches the selected course. - return event.course && (event.course.id == this.sitesProvider.getCurrentSiteHomeId() || event.course.id == courseId); + return eventCourse && (eventCourse == this.sitesProvider.getCurrentSiteHomeId() || eventCourse == courseId); } /** diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0258261e8..885e7adab 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -88,8 +88,10 @@ "addon.calendar.calendarevent": "Calendar event", "addon.calendar.calendarevents": "Calendar events", "addon.calendar.calendarreminders": "Calendar reminders", + "addon.calendar.categoryevents": "Category events", "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.courseevents": "Course events", "addon.calendar.currentmonth": "Current Month", "addon.calendar.daynext": "Next day", "addon.calendar.dayprev": "Previous day", @@ -113,6 +115,7 @@ "addon.calendar.fri": "Fri", "addon.calendar.friday": "Friday", "addon.calendar.gotoactivity": "Go to activity", + "addon.calendar.groupevents": "Group events", "addon.calendar.invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.", "addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.", "addon.calendar.mon": "Mon", @@ -130,6 +133,7 @@ "addon.calendar.sat": "Sat", "addon.calendar.saturday": "Saturday", "addon.calendar.setnewreminder": "Set a new reminder", + "addon.calendar.siteevents": "Site events", "addon.calendar.sun": "Sun", "addon.calendar.sunday": "Sunday", "addon.calendar.thu": "Thu", @@ -148,6 +152,7 @@ "addon.calendar.typesite": "Site event", "addon.calendar.typeuser": "User event", "addon.calendar.upcomingevents": "Upcoming events", + "addon.calendar.userevents": "User events", "addon.calendar.wed": "Wed", "addon.calendar.wednesday": "Wednesday", "addon.calendar.when": "When", @@ -1517,6 +1522,7 @@ "core.fileuploader.uploading": "Uploading", "core.fileuploader.uploadingperc": "Uploading: {{$a}}%", "core.fileuploader.video": "Video", + "core.filter": "Filter", "core.folder": "Folder", "core.forcepasswordchangenotice": "You must change your password to proceed.", "core.fulllistofcourses": "All courses", diff --git a/src/lang/en.json b/src/lang/en.json index 0c900b134..8a1f726e8 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -120,6 +120,7 @@ "invalidformdata": "Incorrect form data", "ios": "iOS", "labelsep": ":", + "filter": "Filter", "lastaccess": "Last access", "lastdownloaded": "Last downloaded", "lastmodified": "Last modified",