From 9aa07fbab0e58dff03cb44c5d840b0788a0bd7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 20 Dec 2017 15:36:51 +0100 Subject: [PATCH 01/10] MOBILE-2308 gulp: Adapt gulp language files creation --- gulpfile.js | 60 ++++++++++++++++----------------- src/providers/utils/mimetype.ts | 4 +-- src/providers/utils/utils.ts | 6 ++-- tslint.json | 10 +++++- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 2296ffaa8..01a2deb13 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -59,39 +59,39 @@ function treatMergedData(data) { var mergedOrdered = {}; for (var filepath in data) { + var pathSplit = filepath.split('/'); - if (filepath.indexOf('lang/') === 0 || filepath.indexOf('core/lang') === 0) { + pathSplit.pop(); - addProperties(merged, data[filepath], 'core.'); + switch (pathSplit[0]) { + case 'lang': + prefix = 'core'; + break; + case 'core': + if (pathSplit[1] == 'lang') { + // Not used right now. + prefix = 'core'; + } else { + prefix = 'core.' + pathSplit[1]; + } + break; + case 'addon': + // Remove final item 'lang'. + pathSplit.pop(); + // Remove first item 'addon'. + pathSplit.shift(); - } else if (filepath.indexOf('core/') === 0) { - - var componentName = filepath.replace('core/', ''); - componentName = componentName.substr(0, componentName.indexOf('/')); - addProperties(merged, data[filepath], 'core.'+componentName+'.'); - - } else if (filepath.indexOf('addons') === 0) { - - var split = filepath.split('/'), - pluginName = split[1], - index = 2; - - // Check if it's a subplugin. If so, we'll use plugin_subfolder_subfolder2_... - // E.g. 'mod_assign_feedback_comments'. - while (split[index] && split[index] != 'lang') { - pluginName = pluginName + '_' + split[index]; - index++; - } - addProperties(merged, data[filepath], 'mma.'+pluginName+'.'); - - } else if (filepath.indexOf('assets/countries') === 0) { - - addProperties(merged, data[filepath], 'core.country-'); - - } else if (filepath.indexOf('assets/mimetypes') === 0) { - - addProperties(merged, data[filepath], 'core.mimetype-'); + // For subplugins. We'll use plugin_subfolder_subfolder2_... + // E.g. 'mod_assign_feedback_comments'. + prefix = 'addon.' + pathSplit.join('_'); + break; + case 'assets': + prefix = 'assets.' + pathSplit[1]; + break; + } + if (prefix) { + addProperties(merged, data[filepath], prefix + '.'); } } @@ -181,7 +181,7 @@ var appLangFiles = ['ar.json', 'bg.json', 'ca.json', 'cs.json', 'da.json', 'de.j lang: [ './src/lang/', './src/core/**/lang/', - './src/addons/**/lang/', + './src/addon/**/lang/', './src/assets/countries/', './src/assets/mimetypes/' ], diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index 589c14b32..096365aaf 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -323,7 +323,7 @@ export class CoreMimetypeUtilsProvider { let filename = '', mimetype = '', extension = '', - langPrefix = 'core.mimetype-'; + langPrefix = 'assets.mimetypes.'; if (typeof obj == 'object' && typeof obj.file == 'function') { // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable. @@ -422,7 +422,7 @@ export class CoreMimetypeUtilsProvider { * @return {string} Translated name. */ getTranslatedGroupName(name: string) : string { - let key = 'core.mimetype-group:' + name, + let key = 'assets.mimetypes.group:' + name, translated = this.translate.instant(key); return translated != key ? translated : name; } diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index d75334f2d..4160cbca8 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -552,7 +552,7 @@ export class CoreUtilsProvider { * @return {string} Country name. If the country is not found, return the country code. */ getCountryName(code: string) : string { - let countryKey = 'core.country-' + code, + let countryKey = 'assets.countries.' + code, countryName = this.translate.instant(countryKey); return countryName !== countryKey ? countryName : code; @@ -580,8 +580,8 @@ export class CoreUtilsProvider { let countries = {}; for (let name in table) { - if (name.indexOf('core.country-') === 0) { - let code = name.replace('core.country-', ''); + if (name.indexOf('assets.countries.') === 0) { + let code = name.replace('assets.countries.', ''); countries[code] = table[name]; } } diff --git a/tslint.json b/tslint.json index dd8e8d887..7cecb9d18 100644 --- a/tslint.json +++ b/tslint.json @@ -3,7 +3,15 @@ "no-duplicate-variable": true, "no-unused-variable": [ true - ] + ], + "max-line-length": { + "options": [132] + }, + }, + "jsRules": { + "max-line-length": { + "options": [132] + } }, "rulesDirectory": [ "node_modules/tslint-eslint-rules/dist/rules" From f6083227b49a535b4d9e6caa91dc9808d43cf580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 20 Dec 2017 12:23:12 +0100 Subject: [PATCH 02/10] MOBILE-2308 calendar: Add calendar to main menu --- src/addon/calendar/calendar.module.ts | 34 +++++++++++++ src/addon/calendar/lang/en.json | 4 ++ src/addon/calendar/pages/list/list.html | 7 +++ src/addon/calendar/pages/list/list.module.ts | 29 +++++++++++ src/addon/calendar/pages/list/list.scss | 3 ++ src/addon/calendar/pages/list/list.ts | 44 +++++++++++++++++ src/addon/calendar/providers/calendar.ts | 42 ++++++++++++++++ src/addon/calendar/providers/handlers.ts | 52 ++++++++++++++++++++ src/app/app.module.ts | 5 +- 9 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/addon/calendar/calendar.module.ts create mode 100644 src/addon/calendar/lang/en.json create mode 100644 src/addon/calendar/pages/list/list.html create mode 100644 src/addon/calendar/pages/list/list.module.ts create mode 100644 src/addon/calendar/pages/list/list.scss create mode 100644 src/addon/calendar/pages/list/list.ts create mode 100644 src/addon/calendar/providers/calendar.ts create mode 100644 src/addon/calendar/providers/handlers.ts diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts new file mode 100644 index 000000000..38c4411d0 --- /dev/null +++ b/src/addon/calendar/calendar.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonCalendarProvider } from './providers/calendar'; +import { AddonCalendarMainMenuHandler } from './providers/handlers'; +import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + AddonCalendarProvider, + AddonCalendarMainMenuHandler + ] +}) +export class AddonCalendarModule { + constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler) { + mainMenuDelegate.registerHandler(calendarHandler); + } +} \ No newline at end of file diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json new file mode 100644 index 000000000..474af9c02 --- /dev/null +++ b/src/addon/calendar/lang/en.json @@ -0,0 +1,4 @@ +{ + "calendar": "Calendar", + "calendarevents": "Calendar events" +} \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html new file mode 100644 index 000000000..6b26736f8 --- /dev/null +++ b/src/addon/calendar/pages/list/list.html @@ -0,0 +1,7 @@ + + + {{ 'addon.calendar.calendarevents' | translate }} + + + + diff --git a/src/addon/calendar/pages/list/list.module.ts b/src/addon/calendar/pages/list/list.module.ts new file mode 100644 index 000000000..44505a53f --- /dev/null +++ b/src/addon/calendar/pages/list/list.module.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonCalendarListPage } from './list'; + +@NgModule({ + declarations: [ + AddonCalendarListPage, + ], + imports: [ + IonicPageModule.forChild(AddonCalendarListPage), + TranslateModule.forChild() + ], +}) +export class AddonCalendarListPagePageModule {} diff --git a/src/addon/calendar/pages/list/list.scss b/src/addon/calendar/pages/list/list.scss new file mode 100644 index 000000000..345d0d21c --- /dev/null +++ b/src/addon/calendar/pages/list/list.scss @@ -0,0 +1,3 @@ +page-addon-calendar-list { + +} \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts new file mode 100644 index 000000000..2b6bec034 --- /dev/null +++ b/src/addon/calendar/pages/list/list.ts @@ -0,0 +1,44 @@ +// (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, OnDestroy } from '@angular/core'; +import { IonicPage } from 'ionic-angular'; +//import { AddonCalendarProvider } from '../../providers/calendar'; + +/** + * Page that displays the list of courses the user is enrolled in. + */ +@IonicPage() +@Component({ + selector: 'page-addon-calendar-list', + templateUrl: 'list.html', +}) +export class AddonCalendarListPage implements OnDestroy { + eventsLoaded = false; + + constructor() {} + + /** + * View loaded. + */ + ionViewDidLoad() { + + } + + /** + * Page destroyed. + */ + ngOnDestroy() { + } +} \ No newline at end of file diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts new file mode 100644 index 000000000..291232b9c --- /dev/null +++ b/src/addon/calendar/providers/calendar.ts @@ -0,0 +1,42 @@ +// (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 { CoreSite } from '../../../classes/site'; + +/** + * Service that provides some features regarding lists of courses and categories. + */ +@Injectable() +export class AddonCalendarProvider { + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { + this.logger = logger.getInstance('AddonCalendarProvider'); + } + + /** + * Check if Calendar is disabled in a certain site. + * + * @param {CoreSite} [site] Site. If not defined, use current site. + * @return {boolean} Whether it's disabled. + */ + isCalendarDisabledInSite(site?: CoreSite) : boolean { + site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar'); + } + +} diff --git a/src/addon/calendar/providers/handlers.ts b/src/addon/calendar/providers/handlers.ts new file mode 100644 index 000000000..c4d71c6c2 --- /dev/null +++ b/src/addon/calendar/providers/handlers.ts @@ -0,0 +1,52 @@ +// (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 { AddonCalendarProvider } from './calendar'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { + name = 'mmaCalendar'; + priority = 400; + + constructor(private calendarProvider: AddonCalendarProvider) {} + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean|Promise { + let isDisabled = this.calendarProvider.isCalendarDisabledInSite(); + return !isDisabled; + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'calendar', + title: 'addon.calendar.calendar', + page: 'AddonCalendarListPage', + class: 'mma-calendar-handler' + }; + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 20b958fd6..807ed7af9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -55,7 +55,7 @@ import { CoreMainMenuModule } from '../core/mainmenu/mainmenu.module'; import { CoreCoursesModule } from '../core/courses/courses.module'; import { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.module'; import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module'; - +import { AddonCalendarModule } from '../addon/calendar/calendar.module'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient) { @@ -86,7 +86,8 @@ export function createTranslateLoader(http: HttpClient) { CoreCoursesModule, CoreFileUploaderModule, CoreSharedFilesModule, - CoreComponentsModule + CoreComponentsModule, + AddonCalendarModule ], bootstrap: [IonicApp], entryComponents: [ From a9a63e5668f3c4ffd2e1f48ce848cd432b864fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 22 Dec 2017 09:09:47 +0100 Subject: [PATCH 03/10] MOBILE-2308 calendar: List events page --- src/addon/calendar/calendar.module.ts | 2 + src/addon/calendar/pages/list/list.html | 30 ++ src/addon/calendar/pages/list/list.module.ts | 8 +- src/addon/calendar/pages/list/list.ts | 278 +++++++++++++- src/addon/calendar/providers/calendar.ts | 359 +++++++++++++++++- src/addon/calendar/providers/helper.ts | 53 +++ src/classes/site.ts | 4 +- src/components/components.module.ts | 5 +- .../course-picker-menu-popover.html | 6 + .../course-picker-menu-popover.ts | 45 +++ src/providers/sites.ts | 2 +- 11 files changed, 779 insertions(+), 13 deletions(-) create mode 100644 src/addon/calendar/providers/helper.ts create mode 100644 src/components/course-picker-menu/course-picker-menu-popover.html create mode 100644 src/components/course-picker-menu/course-picker-menu-popover.ts diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index 38c4411d0..ac20c15e7 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -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 ] }) diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 6b26736f8..3ad253f5a 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -1,7 +1,37 @@ {{ 'addon.calendar.calendarevents' | translate }} + + + + + + + + + + + + + + + + + + + +

+

{{ event.timestart | coreToLocaleString }}

+
+
+ + + + +
diff --git a/src/addon/calendar/pages/list/list.module.ts b/src/addon/calendar/pages/list/list.module.ts index 44505a53f..9335a8061 100644 --- a/src/addon/calendar/pages/list/list.module.ts +++ b/src/addon/calendar/pages/list/list.module.ts @@ -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 {} diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 2b6bec034..5212688ea 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -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(); } } \ No newline at end of file diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 291232b9c..d1a8445ec 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -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} Promise resolved with the default time. + */ + getDefaultNotificationTime(siteId?: string) : Promise { + 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} Promise resolved when the event data is retrieved. + */ + getEventFromLocalDb(id: number, siteId?: string) : Promise { + 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} Event notification time in minutes. 0 if disabled. + */ + getEventNotificationTime(id: number, siteId?: string) : Promise { + 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} Promise with wvent notification time in minutes. 0 if disabled, -1 if default time. + */ + getEventNotificationTimeOption(id: number, siteId?: string) : Promise { + 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} Promise to be resolved when the participants are retrieved. + */ + getEventsList(daysToStart = 0, daysInterval=AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise { + 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} 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} Promise resolved when the notification is scheduled. + */ + scheduleEventNotification(event: any, time: number, siteId?: string) : Promise { + 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} Promise resolved when all the notifications have been scheduled. + */ + scheduleEventsNotifications(events: any[], siteId?: string) : Promise { + 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} Promise resolved when the events are stored. + */ + protected storeEventsInLocalDB(events: any[], siteId?: string) : Promise { + 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); + }); + } } diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts new file mode 100644 index 000000000..f8ec7450b --- /dev/null +++ b/src/addon/calendar/providers/helper.ts @@ -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; + }; +} diff --git a/src/classes/site.ts b/src/classes/site.ts index b4645cfeb..fea101fa3 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -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 + "%"]); } /** diff --git a/src/components/components.module.ts b/src/components/components.module.ts index a3d1ec8ba..6a37a8dae 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -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, diff --git a/src/components/course-picker-menu/course-picker-menu-popover.html b/src/components/course-picker-menu/course-picker-menu-popover.html new file mode 100644 index 000000000..6eea11d5e --- /dev/null +++ b/src/components/course-picker-menu/course-picker-menu-popover.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/course-picker-menu/course-picker-menu-popover.ts b/src/components/course-picker-menu/course-picker-menu-popover.ts new file mode 100644 index 000000000..e654664dc --- /dev/null +++ b/src/components/course-picker-menu/course-picker-menu-popover.ts @@ -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; + } +} diff --git a/src/providers/sites.ts b/src/providers/sites.ts index c3fbb5ed3..aaba7ee3d 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -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 { + getSiteHomeId(siteId?: string) : Promise { return this.getSite(siteId).then((site) => { return site.getSiteHomeId(); }); From 6f671fe39fd7c21dbd737b7c2a4db245f9b052a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 28 Dec 2017 12:55:54 +0100 Subject: [PATCH 04/10] MOBILE-2308 calendar: Settings page --- src/addon/calendar/lang/en.json | 5 +- src/addon/calendar/pages/list/list.ts | 5 +- .../calendar/pages/settings/settings.html | 22 ++++++++ .../pages/settings/settings.module.ts | 31 ++++++++++++ .../calendar/pages/settings/settings.scss | 3 ++ src/addon/calendar/pages/settings/settings.ts | 50 +++++++++++++++++++ src/addon/calendar/providers/calendar.ts | 14 ++++++ src/app/app.ios.scss | 5 +- src/app/app.md.scss | 8 ++- src/app/app.scss | 6 +-- src/app/app.wp.scss | 4 ++ src/providers/events.ts | 29 +++++++++-- 12 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 src/addon/calendar/pages/settings/settings.html create mode 100644 src/addon/calendar/pages/settings/settings.module.ts create mode 100644 src/addon/calendar/pages/settings/settings.scss create mode 100644 src/addon/calendar/pages/settings/settings.ts diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json index 474af9c02..4bb5a7893 100644 --- a/src/addon/calendar/lang/en.json +++ b/src/addon/calendar/lang/en.json @@ -1,4 +1,7 @@ { "calendar": "Calendar", - "calendarevents": "Calendar events" + "calendarevents": "Calendar events", + "defaultnotificationtime": "Default notification time", + "errorloadevents": "Error loading events.", + "noevents": "There are no events" } \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 5212688ea..e49e4d2f9 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -67,12 +67,12 @@ export class AddonCalendarListPage implements OnDestroy { private eventsProvider: CoreEventsProvider, private navCtrl: NavController) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); - this.notificationsEnabled = localNotificationsProvider.isAvailable(); + this.notificationsEnabled = true;//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); - }); + }, sitesProvider.getCurrentSiteId()); } // @TODO: Split view once single event is done. @@ -297,7 +297,6 @@ export class AddonCalendarListPage implements OnDestroy { * Open calendar events settings. */ openSettings() { - // @TODO: Check the settings page name. this.navCtrl.push('AddonCalendarSettingsPage'); }; diff --git a/src/addon/calendar/pages/settings/settings.html b/src/addon/calendar/pages/settings/settings.html new file mode 100644 index 000000000..b32c35dcf --- /dev/null +++ b/src/addon/calendar/pages/settings/settings.html @@ -0,0 +1,22 @@ + + + {{ 'core.settings.settings' | translate }} + + + + + + {{ 'addon.calendar.defaultnotificationtime' | translate }} + + {{ 'core.settings.disabled' | translate }} + {{ 600 | coreDuration }} + {{ 1800 | coreDuration }} + {{ 3600 | coreDuration }} + {{ 7200 | coreDuration }} + {{ 21600 | coreDuration }} + {{ 43200 | coreDuration }} + {{ 86400 | coreDuration }} + + + + diff --git a/src/addon/calendar/pages/settings/settings.module.ts b/src/addon/calendar/pages/settings/settings.module.ts new file mode 100644 index 000000000..59067baa6 --- /dev/null +++ b/src/addon/calendar/pages/settings/settings.module.ts @@ -0,0 +1,31 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonCalendarSettingsPage } from './settings'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; + +@NgModule({ + declarations: [ + AddonCalendarSettingsPage, + ], + imports: [ + CorePipesModule, + IonicPageModule.forChild(AddonCalendarSettingsPage), + TranslateModule.forChild() + ], +}) +export class AddonCalendarSettingsPageModule {} diff --git a/src/addon/calendar/pages/settings/settings.scss b/src/addon/calendar/pages/settings/settings.scss new file mode 100644 index 000000000..1f837a1e7 --- /dev/null +++ b/src/addon/calendar/pages/settings/settings.scss @@ -0,0 +1,3 @@ +page-addon-calendar-settings { + +} \ No newline at end of file diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts new file mode 100644 index 000000000..5e1be133c --- /dev/null +++ b/src/addon/calendar/pages/settings/settings.ts @@ -0,0 +1,50 @@ +// (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 { IonicPage } from 'ionic-angular'; +import { AddonCalendarProvider } from '../../providers/calendar'; +import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreSitesProvider } from '../../../../providers/sites'; + +/** + * Page that displays the list of calendar events. + */ +@IonicPage() +@Component({ + selector: 'page-addon-calendar-settings', + templateUrl: 'settings.html', +}) +export class AddonCalendarSettingsPage { + + defaultTime = 0; + + constructor(private calendarProvider: AddonCalendarProvider, private eventsProvider: CoreEventsProvider, + private sitesProvider: CoreSitesProvider) {} + + /** + * View loaded. + */ + ionViewDidLoad() { + this.calendarProvider.getDefaultNotificationTime().then((time) => { + this.defaultTime = time; + }); + } + + updateDefaultTime(newTime) { + this.calendarProvider.setDefaultNotificationTime(newTime); + this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, {time: newTime}, + this.sitesProvider.getCurrentSiteId()); + }; +} \ No newline at end of file diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index d1a8445ec..bb5b0a4ec 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -350,6 +350,20 @@ export class AddonCalendarProvider { return Promise.all(promises); } + /** + * Set the default notification time. + * + * @param {number} time New default time. + * @param {string} [siteId] ID of the site. If not defined, use current site. + * @return {Promise} Promise resolved when stored. + */ + setDefaultNotificationTime(time: number, siteId?: string) : Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + let key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + return this.configProvider.set(key, time); + } + /** * Store events in local DB. * diff --git a/src/app/app.ios.scss b/src/app/app.ios.scss index 829e70af8..84a20c0c0 100644 --- a/src/app/app.ios.scss +++ b/src/app/app.ios.scss @@ -24,6 +24,9 @@ } } +.bar-buttons core-context-menu .button-clear-ios { + color: $toolbar-ios-button-color; +} // Highlights inside the input element. @if ($core-text-input-ios-show-highlight) { @@ -87,4 +90,4 @@ @include ios-input-highlight($text-input-ios-highlight-color-invalid); } } -} \ No newline at end of file +} diff --git a/src/app/app.md.scss b/src/app/app.md.scss index c62a2141f..4aa56ca6d 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -12,8 +12,12 @@ height: calc(100% - #{($card-md-margin-end + $card-md-margin-start)}); } +.bar-buttons core-context-menu .button-clear-md { + color: $toolbar-md-button-color; +} + // Highlights inside the input element. -@if ($core-text-input-md-show-highlight) { +@if ($mm-text-input-md-show-highlight) { .card-md, .list-md { // In order to get a 2px border we need to add an inset // box-shadow 1px (this is to avoid the div resizing) @@ -75,4 +79,4 @@ @include md-input-highlight($text-input-md-highlight-color-invalid); } } -} \ No newline at end of file +} diff --git a/src/app/app.scss b/src/app/app.scss index 8c06237ba..9f15a904b 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -153,16 +153,16 @@ core-format-text[maxHeight], *[core-format-text][maxHeight] { display: none; } - &:not(.core-shortened) { + &:not(.mm-shortened) { max-height: none !important; } - &.core-shortened { + &.mm-shortened { color: $gray-darker; overflow: hidden; min-height: 50px; - .core-show-more { + .mm-show-more { color: color($colors, dark); text-align: right; font-size: 14px; diff --git a/src/app/app.wp.scss b/src/app/app.wp.scss index c0544152c..fd5175023 100644 --- a/src/app/app.wp.scss +++ b/src/app/app.wp.scss @@ -11,3 +11,7 @@ .col[align-self-stretch] .card-wp { height: calc(100% - #{($card-wp-margin-end + $card-wp-margin-start)}); } + +.bar-buttons core-context-menu .button-clear-wp { + color: $toolbar-wp-button-color; +} diff --git a/src/providers/events.ts b/src/providers/events.ts index e9066b00f..198cb6a68 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -64,9 +64,10 @@ export class CoreEventsProvider { * * @param {string} eventName Name of the event to listen to. * @param {Function} callBack Function to call when the event is triggered. + * @param {string} [siteId] Site where to trigger the event. Undefined won't check the site. * @return {CoreEventObserver} Observer to stop listening. */ - on(eventName: string, callBack: (value: any) => void) : CoreEventObserver { + on(eventName: string, callBack: (value: any) => void, siteId?: string) : CoreEventObserver { // If it's a unique event and has been triggered already, call the callBack. // We don't need to create an observer because the event won't be triggered again. if (this.uniqueEvents[eventName]) { @@ -84,7 +85,11 @@ export class CoreEventsProvider { this.observables[eventName] = new Subject(); } - let subscription = this.observables[eventName].subscribe(callBack); + let subscription = this.observables[eventName].subscribe((value: any) => { + if (!siteId || value.siteId == siteId) { + callBack(value); + } + }); // Create and return a CoreEventObserver. return { @@ -100,10 +105,17 @@ export class CoreEventsProvider { * * @param {string} event Name of the event to trigger. * @param {any} [data] Data to pass to the observers. + * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. */ - trigger(eventName: string, data?: any) : void { + trigger(eventName: string, data?: any, siteId?: string) : void { this.logger.debug(`Event '${eventName}' triggered.`); if (this.observables[eventName]) { + if (siteId) { + if (!data) { + data = {}; + } + data.siteId = siteId; + } this.observables[eventName].next(data); } } @@ -113,12 +125,21 @@ export class CoreEventsProvider { * * @param {string} event Name of the event to trigger. * @param {any} data Data to pass to the observers. + * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. */ - triggerUnique(eventName: string, data: any) : void { + triggerUnique(eventName: string, data: any, siteId?: string) : void { if (this.uniqueEvents[eventName]) { this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`); } else { this.logger.debug(`Unique event '${eventName}' triggered.`); + + if (siteId) { + if (!data) { + data = {}; + } + data.siteId = siteId; + } + // Store the data so it can be passed to observers that register from now on. this.uniqueEvents[eventName] = { data: data From 3ef367db762365f3d884506ae1c2dacf95dcf3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 28 Dec 2017 13:25:09 +0100 Subject: [PATCH 05/10] MOBILE-2308 events: Adapt events to new functions --- src/classes/site.ts | 10 +++++----- .../pages/course-preview/course-preview.ts | 2 +- src/core/courses/pages/my-courses/my-courses.ts | 16 ++++++---------- src/core/login/pages/credentials/credentials.ts | 9 ++------- src/core/login/providers/helper.ts | 3 +-- src/core/mainmenu/pages/more/more.ts | 8 ++------ src/providers/filepool.ts | 3 +-- src/providers/sites.ts | 12 ++++++------ 8 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index fea101fa3..553a1cf19 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -504,27 +504,27 @@ export class CoreSite { } // Session expired, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {siteId: this.id}); + this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, this.id); // Change error message. We'll try to get data from cache. error.message = this.translate.instant('core.lostconnection'); } else if (error.errorcode === 'userdeleted') { // User deleted, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {siteId: this.id, params: data}); + this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {params: data}, this.id); error.message = this.translate.instant('core.userdeleted'); return Promise.reject(error); } else if (error.errorcode === 'forcepasswordchangenotice') { // Password Change Forced, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {siteId: this.id}); + this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {}, this.id); error.message = this.translate.instant('core.forcepasswordchangenotice'); return Promise.reject(error); } else if (error.errorcode === 'usernotfullysetup') { // User not fully setup, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {siteId: this.id}); + this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {}, this.id); error.message = this.translate.instant('core.usernotfullysetup'); return Promise.reject(error); } else if (error.errorcode === 'sitepolicynotagreed') { // Site policy not agreed, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {siteId: this.id}); + this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {}, this.id); error.message = this.translate.instant('core.sitepolicynotagreederror'); return Promise.reject(error); } else if (error.errorcode === 'dmlwriteexception' && this.textUtils.hasUnicodeData(data)) { diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index 1076440cd..0789790c9 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -307,7 +307,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { this.refreshData().finally(() => { // My courses have been updated, trigger event. this.eventsProvider.trigger( - CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {siteId: this.sitesProvider.getCurrentSiteId()}); + CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId()); }); }); }).catch((error) => { diff --git a/src/core/courses/pages/my-courses/my-courses.ts b/src/core/courses/pages/my-courses/my-courses.ts index ea3941f05..3259c9d47 100644 --- a/src/core/courses/pages/my-courses/my-courses.ts +++ b/src/core/courses/pages/my-courses/my-courses.ts @@ -53,17 +53,13 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { this.coursesLoaded = true; }); - this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => { - if (data.siteId == this.sitesProvider.getCurrentSiteId()) { - this.fetchCourses(); - } - }); + this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => { + this.fetchCourses(); + }, this.sitesProvider.getCurrentSiteId()); - this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => { - if (data.siteId == this.sitesProvider.getCurrentSiteId()) { - this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); - } - }); + this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); + }, this.sitesProvider.getCurrentSiteId()); } /** diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index 8d0dc4138..ceb996031 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -84,10 +84,7 @@ export class CoreLoginCredentialsPage { */ ionViewDidLeave() { this.viewLeft = true; - this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, { - siteId: this.siteId, - config: this.siteConfig - }); + this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {config: this.siteConfig}, this.siteId); } /** @@ -146,9 +143,7 @@ export class CoreLoginCredentialsPage { if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; - this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, { - config: this.siteConfig - }); + this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {config: this.siteConfig}); } } else { this.siteName = null; diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 7ffc71213..08caa097c 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -574,10 +574,9 @@ export class CoreLoginHelperProvider { if (site.isLoggedOut()) { this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, { - siteId: site.getId(), pageName: pageName, params: params - }); + }, site.getId()); return true; } return false; diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index 0f0b95ff8..1f2fa4f28 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -45,12 +45,8 @@ export class CoreMainMenuMorePage implements OnDestroy { private navCtrl: NavController, private mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider) { this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this)); - this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => { - if (sitesProvider.getCurrentSiteId() == data.siteId) { - this.loadSiteInfo(); - } - }); - + this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this), + sitesProvider.getCurrentSiteId()); this.loadSiteInfo(); } diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 3a122ace8..a576880ee 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -2692,12 +2692,11 @@ export class CoreFilepoolProvider { */ protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string|number) : void { const data = { - siteid: siteId, component: component, componentId: this.fixComponentId(componentId), status: status } - this.eventsProvider.trigger(CoreEventsProvider.PACKAGE_STATUS_CHANGED, data); + this.eventsProvider.trigger(CoreEventsProvider.PACKAGE_STATUS_CHANGED, data, siteId); } /** diff --git a/src/providers/sites.ts b/src/providers/sites.ts index aaba7ee3d..e912ef2bf 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -398,7 +398,7 @@ export class CoreSitesProvider { this.sites[siteId] = candidateSite; // Store session. this.login(siteId); - this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, siteId); + this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, {}, siteId); if (this.siteTablesSchemas.length) { // Create tables in the site's database. @@ -558,7 +558,7 @@ export class CoreSitesProvider { // Check if local_mobile was installed to Moodle. return site.checkIfLocalMobileInstalledAndNotUsed().then(() => { // Local mobile was added. Throw invalid session to force reconnect and create a new token. - this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {siteId: siteId}); + this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, siteId); }, () => { // Update site info. We don't block the UI. this.updateSiteInfo(siteId); @@ -622,7 +622,7 @@ export class CoreSitesProvider { // DB remove shouldn't fail, but we'll go ahead even if it does. return site.deleteFolder(); }).then(() => { - this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site); + this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId); }); }); }); @@ -801,7 +801,7 @@ export class CoreSitesProvider { siteId: siteId }; return this.appDB.insertOrUpdateRecord(this.CURRENT_SITE_TABLE, entry, {id: 1}).then(() => { - this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {siteId: siteId}); + this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId); }); } @@ -829,7 +829,7 @@ export class CoreSitesProvider { promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, {id: 1})); return Promise.all(promises).finally(() => { - this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {siteId: siteId}); + this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {}, siteId); }); } @@ -936,7 +936,7 @@ export class CoreSitesProvider { } return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}).finally(() => { - this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {siteId: siteId}); + this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {}, siteId); }); }); }); From d4ddfc74a1f59ef886927c7a5803dd3740e7b11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 28 Dec 2017 16:10:26 +0100 Subject: [PATCH 06/10] MOBILE-2308 calendar: Single event page --- src/addon/calendar/lang/en.json | 15 +- src/addon/calendar/pages/event/event.html | 57 +++++++ .../calendar/pages/event/event.module.ts | 35 +++++ src/addon/calendar/pages/event/event.scss | 3 + src/addon/calendar/pages/event/event.ts | 142 ++++++++++++++++++ src/addon/calendar/pages/list/list.html | 6 +- src/addon/calendar/pages/list/list.ts | 31 +++- src/addon/calendar/pages/settings/settings.ts | 2 +- src/addon/calendar/providers/calendar.ts | 80 +++++++++- src/addon/calendar/providers/helper.ts | 13 +- src/providers/app.ts | 12 +- 11 files changed, 373 insertions(+), 23 deletions(-) create mode 100644 src/addon/calendar/pages/event/event.html create mode 100644 src/addon/calendar/pages/event/event.module.ts create mode 100644 src/addon/calendar/pages/event/event.scss create mode 100644 src/addon/calendar/pages/event/event.ts diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json index 4bb5a7893..63e4b17ec 100644 --- a/src/addon/calendar/lang/en.json +++ b/src/addon/calendar/lang/en.json @@ -2,6 +2,19 @@ "calendar": "Calendar", "calendarevents": "Calendar events", "defaultnotificationtime": "Default notification time", + "errorloadevent": "Error loading event.", "errorloadevents": "Error loading events.", - "noevents": "There are no events" + "eventendtime": "End time", + "eventstarttime": "Start time", + "noevents": "There are no events", + "notifications": "Notifications", + "typeclose": "Close event", + "typecourse": "Course event", + "typecategory": "Category event", + "typedue": "Due event", + "typegradingdue": "Grading due event", + "typegroup": "Group event", + "typeopen": "Open event", + "typesite": "Site event", + "typeuser": "User event" } \ No newline at end of file diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html new file mode 100644 index 000000000..61451a1cb --- /dev/null +++ b/src/addon/calendar/pages/event/event.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + +

{{ 'addon.calendar.eventstarttime' | translate}}

+

{{ event.timestart | coreToLocaleString }}

+
+ +

{{ 'addon.calendar.eventendtime' | translate}}

+

{{ (event.timestart + event.timeduration) |  coreToLocaleString }}

+
+ +

{{ 'core.course' | translate}}

+

+
+ + {{event.moduleName}} + + +

+ +

+
+
+
+ + + + {{ 'addon.calendar.notifications' | translate }} + + {{ 'core.defaultvalue' | translate :{$a: defaultTimeReadable} }} + {{ 'core.settings.disabled' | translate }} + {{ 600 | coreDuration }} + {{ 1800 | coreDuration }} + {{ 3600 | coreDuration }} + {{ 7200 | coreDuration }} + {{ 21600 | coreDuration }} + {{ 43200 | coreDuration }} + {{ 86400 | coreDuration }} + + + +
+
diff --git a/src/addon/calendar/pages/event/event.module.ts b/src/addon/calendar/pages/event/event.module.ts new file mode 100644 index 000000000..7731c3b2d --- /dev/null +++ b/src/addon/calendar/pages/event/event.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCalendarEventPage } from './event'; + +@NgModule({ + declarations: [ + AddonCalendarEventPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCalendarEventPage), + TranslateModule.forChild() + ], +}) +export class AddonCalendarEventPageModule {} diff --git a/src/addon/calendar/pages/event/event.scss b/src/addon/calendar/pages/event/event.scss new file mode 100644 index 000000000..345d0d21c --- /dev/null +++ b/src/addon/calendar/pages/event/event.scss @@ -0,0 +1,3 @@ +page-addon-calendar-list { + +} \ No newline at end of file diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts new file mode 100644 index 000000000..e0c102ca7 --- /dev/null +++ b/src/addon/calendar/pages/event/event.ts @@ -0,0 +1,142 @@ +// (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, ViewChild } from '@angular/core'; +import { IonicPage, Content, NavParams } 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 { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications'; +//import { CoreCourseProvider } from '../../../core/course/providers/course'; +import * as moment from 'moment'; + +/** + * Page that displays a single calendar event. + */ +@IonicPage() +@Component({ + selector: 'page-addon-calendar-event', + templateUrl: 'event.html', +}) +export class AddonCalendarEventPage { + @ViewChild(Content) content: Content; + + protected eventId; + protected siteHomeId: number; + eventLoaded: boolean; + notificationTime: number; + defaultTimeReadable: string; + event = {}; + title: string; + courseName: string; + notificationsEnabled = false; + + constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams, + private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, + private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, + private localNotificationsProvider: CoreLocalNotificationsProvider/*, private courseProvider: CoreCourseProvider*/) { + + this.eventId = navParams.get('id'); + this.notificationsEnabled = localNotificationsProvider.isAvailable(); + this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); + if (this.notificationsEnabled) { + this.calendarProvider.getEventNotificationTimeOption(this.eventId).then((notificationTime) => { + this.notificationTime = notificationTime; + }); + + this.calendarProvider.getDefaultNotificationTime().then((defaultTime) => { + if (defaultTime === 0) { + // Disabled by default. + this.defaultTimeReadable = this.translate.instant('core.settings.disabled'); + } else { + this.defaultTimeReadable = moment.duration(defaultTime * 60 * 1000).humanize(); + } + }); + } + } + + /** + * View loaded. + */ + ionViewDidLoad() { + this.fetchEvent().finally(() => { + this.eventLoaded = true; + }); + } + + updateNotificationTime() { + if (!isNaN(this.notificationTime) && this.event && this.event.id) { + this.calendarProvider.updateNotificationTime(this.event, this.notificationTime); + } + } + + /** + * Fetches the event and updates the view. + * + * @param {boolean} refresh Empty events array first. + */ + fetchEvent() { + return this.calendarProvider.getEvent(this.eventId).then((event) => { + this.calendarHelper.formatEventData(event); + this.event = event; + + // Guess event title. + let title = this.translate.instant('addon.calendar.type' + event.eventtype); + if (event.moduleIcon) { + // @todo: It's a module event, translate the module name to the current language. + let name = "" //this.courseProvider.translateModuleName(event.modulename); + if (name.indexOf('core.mod_') === -1) { + event.moduleName = name; + } + if (title == 'addon.calendar.type' + event.eventtype) { + title = this.translate.instant('core.mod_'+ event.modulename + '.' + event.eventtype); + + if (title == 'core.mod_'+ event.modulename + '.' + event.eventtype) { + title = name; + } + } + } else { + if (title == 'addon.calendar.type' + event.eventtype) { + title = event.name; + } + } + this.title = title; + + if (event.courseid != this.siteHomeId) { + // It's a course event, retrieve the course name. + return this.coursesProvider.getUserCourse(event.courseid, true).then((course) => { + this.courseName = course.fullname; + }); + } + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevent', true); + }); + } + + /** + * Refresh the event. + * + * @param {any} refresher Refresher. + */ + refreshEvent(refresher: any) { + this.calendarProvider.invalidateEvent(this.eventId).finally(() => { + this.fetchEvent().finally(() => { + refresher.complete(); + }); + }); + } +} \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 3ad253f5a..9052d8bb1 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -21,10 +21,10 @@ - + - - + +

{{ event.timestart | coreToLocaleString }}

diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index e49e4d2f9..8e473800e 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -24,6 +24,7 @@ import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications'; import { CoreCoursePickerMenuPopoverComponent } from '../../../../components/course-picker-menu/course-picker-menu-popover'; import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreAppProvider } from '../../../../providers/app'; /** * Page that displays the list of calendar events. @@ -48,6 +49,7 @@ export class AddonCalendarListPage implements OnDestroy { protected categories = {}; protected siteHomeId: number; protected obsDefaultTimeChange: any; + protected eventId: number; courses: any[]; eventsLoaded = false; @@ -64,10 +66,10 @@ export class AddonCalendarListPage implements OnDestroy { 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) { + private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); - this.notificationsEnabled = true;//localNotificationsProvider.isAvailable(); + this.notificationsEnabled = localNotificationsProvider.isAvailable(); if (this.notificationsEnabled) { // Re-schedule events if default time changes. this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => { @@ -75,16 +77,24 @@ export class AddonCalendarListPage implements OnDestroy { }, sitesProvider.getCurrentSiteId()); } - // @TODO: Split view once single event is done. - // let eventId = navParams.get('eventid') || false; + this.eventId = navParams.get('eventid') || false; } /** * View loaded. */ ionViewDidLoad() { - this.fetchData().then(() => { + if (this.eventId && !this.appProvider.isWide()) { + // There is an event to load and it's a phone device, open the event in a new state. + this.gotoEvent(this.eventId); + } + this.fetchData().then(() => { + // @TODO: Split view once single event is done. + if (this.eventId && this.appProvider.isWide()) { + // There is an event to load and it's a phone device, open the event in a new state. + this.gotoEvent(this.eventId); + } }).finally(() => { this.eventsLoaded = true; }); @@ -151,7 +161,7 @@ export class AddonCalendarListPage implements OnDestroy { } // 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. + // @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); @@ -298,7 +308,14 @@ export class AddonCalendarListPage implements OnDestroy { */ openSettings() { this.navCtrl.push('AddonCalendarSettingsPage'); - }; + } + + /** + * Navigate to a particular event. + */ + gotoEvent(eventId) { + this.navCtrl.push('AddonCalendarEventPage', {id: eventId}); + } /** * Page destroyed. diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts index 5e1be133c..10d0deccc 100644 --- a/src/addon/calendar/pages/settings/settings.ts +++ b/src/addon/calendar/pages/settings/settings.ts @@ -19,7 +19,7 @@ import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; /** - * Page that displays the list of calendar events. + * Page that displays the calendar settings. */ @IonicPage() @Component({ diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index bb5b0a4ec..1e9d85ffb 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -124,6 +124,44 @@ export class AddonCalendarProvider { return this.configProvider.get(key, AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME); } + /** + * Get a calendar event. If the server request fails and data is not cached, try to get it from local DB. + * + * @param {number} id Event ID. + * @param {boolean} [refresh] True when we should update the event data. + * @param {string} [siteId] ID of the site. If not defined, use current site. + * @return {Promise} Promise resolved when the event data is retrieved. + */ + getEvent(id: number, siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + let presets = { + cacheKey: this.getEventCacheKey(id) + }, + data = { + "options[userevents]": 0, + "options[siteevents]": 0, + "events[eventids][0]": id + }; + return site.read('core_calendar_get_calendar_events', data, presets).then((response) => { + // The WebService returns all category events. Check the response to search for the event we want. + let event = response.events.find((e) => {return e.id == id}); + return event || this.getEventFromLocalDb(id); + }).catch(() => { + return this.getEventFromLocalDb(id); + }); + }); + } + + /** + * Get cache key for a single event WS call. + * + * @param {number} id Event ID. + * @return {string} Cache key. + */ + protected getEventCacheKey(id: number): string { + return 'mmaCalendar:events:' + id; + } + /** * Get a calendar event from local Db. * @@ -245,14 +283,14 @@ export class AddonCalendarProvider { 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} Promise resolved when the list is invalidated. + * @return {Promise} Promise resolved when the list is invalidated. */ - invalidateEventsList(courses: any[], siteId?: string) { + invalidateEventsList(courses: any[], siteId?: string) : Promise { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); @@ -265,6 +303,19 @@ export class AddonCalendarProvider { }); } + /** + * Invalidates a single event. + * + * @param {number} eventId List of courses or course ids. + * @param {string} [siteId] Site Id. If not defined, use current site. + * @return {Promise} Promise resolved when the list is invalidated. + */ + invalidateEvent(eventId: number, siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getEventCacheKey(eventId)); + }); + } + /** * Check if Calendar is disabled in a certain site. * @@ -408,4 +459,27 @@ export class AddonCalendarProvider { return Promise.all(promises); }); } + + /** + * Updates an event notification time and schedule a new notification. + * + * @param {any} event Event to update its notification time. + * @param {number} time New notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolved when the notification is updated. + */ + updateNotificationTime(event: any, time: number, siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + if (!this.sitesProvider.isLoggedIn()) { + // Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing. + return Promise.reject(null); + } + + event.notificationtime = time; + + return site.getDb().insertOrUpdateRecord(AddonCalendarProvider.EVENTS_TABLE, event, {id: event.id}).then(() => { + return this.scheduleEventNotification(event, time); + }); + }); + } } diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index f8ec7450b..36547ecb1 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -32,7 +32,7 @@ export class AddonCalendarHelperProvider { 'category': 'albums' }; - constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider/*, private courseProvider: CoreCourseProvider*/) { this.logger = logger.getInstance('AddonCalendarHelperProvider'); } @@ -42,12 +42,11 @@ export class AddonCalendarHelperProvider { * @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 = AddonCalendarHelperProvider.eventicons[e.eventtype] || false; + if (!e.icon) { + // @todo: It's a module event. + //e.icon = this.courseProvider.getModuleIconSrc(e.modulename); + e.moduleIcon = e.icon; } - e.icon = icon; }; } diff --git a/src/providers/app.ts b/src/providers/app.ts index 2f343a95e..d0cc6e579 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -184,7 +184,7 @@ export class CoreAppProvider { return online; } - /* + /** * Check if device uses a limited connection. * * @return {boolean} Whether the device uses a limited connection. @@ -200,6 +200,16 @@ export class CoreAppProvider { return limited.indexOf(type) > -1; } + /** + * Check if device is wide enough. It's used i.e. to show split view. + * + * @return {boolean} Whether the device uses a limited connection. + */ + isWide() : boolean { + //@todo Should use media querys like splitpane + return this.platform.is('tablet'); + } + /** * Check if the app is running in a Windows environment. * From 04e3d6d2d1bc48c5161b974b7abae45b1f4780af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 29 Dec 2017 13:47:00 +0100 Subject: [PATCH 07/10] MOBILE-2308 calendar: Init process --- src/addon/calendar/calendar.module.ts | 27 +++++++++++- src/addon/calendar/pages/event/event.html | 2 +- src/addon/calendar/pages/event/event.ts | 2 +- src/addon/calendar/pages/list/list.html | 4 +- src/addon/calendar/pages/list/list.ts | 14 ++++++- src/addon/calendar/providers/calendar.ts | 50 +++++++++++++++++++++-- src/app/app.md.scss | 2 +- 7 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index ac20c15e7..a79aa07dd 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -17,6 +17,9 @@ import { AddonCalendarProvider } from './providers/calendar'; import { AddonCalendarHelperProvider } from './providers/helper'; import { AddonCalendarMainMenuHandler } from './providers/handlers'; import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate'; +import { CoreInitDelegate } from '../../providers/init'; +import { CoreLocalNotificationsProvider } from '../../providers/local-notifications'; +import { CoreLoginHelperProvider } from '../../core/login/providers/helper'; @NgModule({ declarations: [ @@ -30,7 +33,29 @@ import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate'; ] }) export class AddonCalendarModule { - constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler) { + constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler, + initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider, + localNotificationsProvider: CoreLocalNotificationsProvider) { mainMenuDelegate.registerHandler(calendarHandler); + + initDelegate.ready().then(() => { + calendarProvider.scheduleAllSitesEventsNotifications(); + }); + + + localNotificationsProvider.registerClick(AddonCalendarProvider.COMPONENT, (data) => { + if (data.eventid) { + initDelegate.ready().then(() => { + calendarProvider.isDisabled(data.siteId).then(function(disabled) { + if (disabled) { + // The calendar is disabled in the site, don't open it. + return; + } + + loginHelper.redirect('AddonCalendarListPage', {eventid: data.eventid}, data.siteId); + }); + }); + } + }); } } \ No newline at end of file diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index 61451a1cb..f3f20e707 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -7,7 +7,7 @@ - + diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index e0c102ca7..590e3806a 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -40,7 +40,7 @@ export class AddonCalendarEventPage { eventLoaded: boolean; notificationTime: number; defaultTimeReadable: string; - event = {}; + event: any = {}; title: string; courseName: string; notificationsEnabled = false; diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 9052d8bb1..8ecc9754b 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -15,14 +15,14 @@ - + - +

diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 8e473800e..355cb5b89 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -92,8 +92,18 @@ export class AddonCalendarListPage implements OnDestroy { this.fetchData().then(() => { // @TODO: Split view once single event is done. if (this.eventId && this.appProvider.isWide()) { - // There is an event to load and it's a phone device, open the event in a new state. - this.gotoEvent(this.eventId); + // There is an event to load and it's a tablet device. Search the position of the event in the list and load it. + let found = this.events.findIndex((e) => {return e.id == this.eventId}); + + if (found > 0) { + this.eventToLoad = found + 1; + } else { + // Event not found in the list, open it in a new state. Use a $timeout to open the state after the + // split view is loaded. + //$timeout(function() { + this.gotoEvent(this.eventId); + //}); + } } }).finally(() => { this.eventsLoaded = true; diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 1e9d85ffb..d4dbfb03d 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -29,10 +29,10 @@ import { CoreConfigProvider } from '../../../providers/config'; @Injectable() export class AddonCalendarProvider { public static DAYS_INTERVAL = 30; + public static COMPONENT = 'AddonCalendarEvents'; public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent'; - protected static DEFAULT_NOTIFICATION_TIME_SETTING = 'AddonCalendarDefaultNotifTime'; + protected static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime'; protected static DEFAULT_NOTIFICATION_TIME = 60; - protected static COMPONENT = 'AddonCalendarEvents'; // Variables for database. protected static EVENTS_TABLE = 'calendar_events'; // Queue of files to download. @@ -220,7 +220,7 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site to get the events from. If not defined, use current site. * @return {Promise} Promise to be resolved when the participants are retrieved. */ - getEventsList(daysToStart = 0, daysInterval=AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise { + getEventsList(daysToStart = 0, daysInterval = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); @@ -327,6 +327,50 @@ export class AddonCalendarProvider { return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar'); } + /** + * Check if Calendar is disabled in a certain site. + * + * @param {string} [siteId] Site Id. If not defined, use current site. + * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + */ + isDisabled(siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.isCalendarDisabledInSite(site); + }); + } + + + /** + * Get the next events for all the sites and schedules their notifications. + * If an event notification time is 0, cancel its scheduled notification (if any). + * If local notification plugin is not enabled, resolve the promise. + * + * @return {Promise} Promise resolved when all the notifications have been scheduled. + */ + scheduleAllSitesEventsNotifications() : Promise { + if (this.localNotificationsProvider.isAvailable()) { + return this.sitesProvider.getSitesIds().then((siteIds) => { + let promises = []; + + siteIds.forEach((siteId) => { + // Check if calendar is disabled for the site. + promises.push(this.isDisabled(siteId).then((disabled) => { + if (!disabled) { + // Get first events. + return this.getEventsList(undefined, undefined, siteId).then((events) => { + return this.scheduleEventsNotifications(events, siteId); + }); + } + })); + }); + + return Promise.all(promises); + }); + } else { + return Promise.resolve([]); + } + } + /** * Schedules an event notification. If time is 0, cancel scheduled notification if any. * If local notification plugin is not enabled, resolve the promise. diff --git a/src/app/app.md.scss b/src/app/app.md.scss index 4aa56ca6d..c0814c60b 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -17,7 +17,7 @@ } // Highlights inside the input element. -@if ($mm-text-input-md-show-highlight) { +@if ($core-text-input-md-show-highlight) { .card-md, .list-md { // In order to get a 2px border we need to add an inset // box-shadow 1px (this is to avoid the div resizing) From 27c715081cb92b085bfcdbd2cedc4a07b2633813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 10 Jan 2018 16:35:25 +0100 Subject: [PATCH 08/10] MOBILE-2308 splitview: Implement Split view --- src/addon/calendar/pages/list/list.html | 44 +++--- src/addon/calendar/pages/list/list.ts | 27 ++-- src/components/components.module.ts | 3 + .../split-view/placeholder/placeholder.html | 9 ++ .../placeholder/placeholder.module.ts | 20 +++ .../split-view/placeholder/placeholder.scss | 3 + .../split-view/placeholder/placeholder.ts | 16 ++ src/components/split-view/split-view.html | 7 + src/components/split-view/split-view.scss | 39 +++++ src/components/split-view/split-view.ts | 147 ++++++++++++++++++ src/providers/app.ts | 10 -- 11 files changed, 275 insertions(+), 50 deletions(-) create mode 100644 src/components/split-view/placeholder/placeholder.html create mode 100644 src/components/split-view/placeholder/placeholder.module.ts create mode 100644 src/components/split-view/placeholder/placeholder.scss create mode 100644 src/components/split-view/placeholder/placeholder.ts create mode 100644 src/components/split-view/split-view.html create mode 100644 src/components/split-view/split-view.scss create mode 100644 src/components/split-view/split-view.ts diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 8ecc9754b..7cdae7575 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -11,27 +11,27 @@ - - - - - - - - + + + + + + + + - -
- - - -

-

{{ event.timestart | coreToLocaleString }}

-
-
+ + + + +

+

{{ event.timestart | coreToLocaleString }}

+
+
- - - -
- + + + +
+ + \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 355cb5b89..82c8b748f 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -25,6 +25,7 @@ import { CoreLocalNotificationsProvider } from '../../../../providers/local-noti import { CoreCoursePickerMenuPopoverComponent } from '../../../../components/course-picker-menu/course-picker-menu-popover'; import { CoreEventsProvider } from '../../../../providers/events'; import { CoreAppProvider } from '../../../../providers/app'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; /** * Page that displays the list of calendar events. @@ -36,6 +37,7 @@ import { CoreAppProvider } from '../../../../providers/app'; }) export class AddonCalendarListPage implements OnDestroy { @ViewChild(Content) content: Content; + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; protected daysLoaded = 0; protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events. @@ -56,7 +58,6 @@ export class AddonCalendarListPage implements OnDestroy { events = []; notificationsEnabled = false; filteredEvents = []; - eventToLoad = 1; canLoadMore = false; filter = { course: this.allCourses @@ -84,26 +85,15 @@ export class AddonCalendarListPage implements OnDestroy { * View loaded. */ ionViewDidLoad() { - if (this.eventId && !this.appProvider.isWide()) { - // There is an event to load and it's a phone device, open the event in a new state. + if (this.eventId) { + // There is an event to load, open the event in a new state. this.gotoEvent(this.eventId); } this.fetchData().then(() => { - // @TODO: Split view once single event is done. - if (this.eventId && this.appProvider.isWide()) { - // There is an event to load and it's a tablet device. Search the position of the event in the list and load it. - let found = this.events.findIndex((e) => {return e.id == this.eventId}); - - if (found > 0) { - this.eventToLoad = found + 1; - } else { - // Event not found in the list, open it in a new state. Use a $timeout to open the state after the - // split view is loaded. - //$timeout(function() { - this.gotoEvent(this.eventId); - //}); - } + if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) { + // Take first and load it. + this.gotoEvent(this.events[0].id); } }).finally(() => { this.eventsLoaded = true; @@ -324,7 +314,8 @@ export class AddonCalendarListPage implements OnDestroy { * Navigate to a particular event. */ gotoEvent(eventId) { - this.navCtrl.push('AddonCalendarEventPage', {id: eventId}); + this.eventId = eventId; + this.splitviewCtrl.push('AddonCalendarEventPage', {id: eventId}); } /** diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 6a37a8dae..0ce4dbc4d 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -21,6 +21,7 @@ import { CoreLoadingComponent } from './loading/loading'; import { CoreMarkRequiredComponent } from './mark-required/mark-required'; import { CoreInputErrorsComponent } from './input-errors/input-errors'; import { CoreShowPasswordComponent } from './show-password/show-password'; +import { CoreSplitViewComponent } from './split-view/split-view'; import { CoreIframeComponent } from './iframe/iframe'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; @@ -40,6 +41,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreMarkRequiredComponent, CoreInputErrorsComponent, CoreShowPasswordComponent, + CoreSplitViewComponent, CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, @@ -68,6 +70,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreMarkRequiredComponent, CoreInputErrorsComponent, CoreShowPasswordComponent, + CoreSplitViewComponent, CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, diff --git a/src/components/split-view/placeholder/placeholder.html b/src/components/split-view/placeholder/placeholder.html new file mode 100644 index 000000000..52e7e8bcc --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.html @@ -0,0 +1,9 @@ + + +   + + + + + + diff --git a/src/components/split-view/placeholder/placeholder.module.ts b/src/components/split-view/placeholder/placeholder.module.ts new file mode 100644 index 000000000..d3133905f --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { CoreSplitViewPlaceholderPage } from './placeholder'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../components.module'; + +@NgModule({ + declarations: [ + CoreSplitViewPlaceholderPage, + ], + imports: [ + CoreComponentsModule, + IonicPageModule.forChild(CoreSplitViewPlaceholderPage), + TranslateModule.forChild() + ], + exports: [ + CoreSplitViewPlaceholderPage + ] +}) +export class CorePlaceholderPageModule { } diff --git a/src/components/split-view/placeholder/placeholder.scss b/src/components/split-view/placeholder/placeholder.scss new file mode 100644 index 000000000..b854d8617 --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.scss @@ -0,0 +1,3 @@ +core-placeholder { + +} diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts new file mode 100644 index 000000000..da0c7cf36 --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { + IonicPage, + NavController, + NavParams } from 'ionic-angular'; + +@IonicPage() +@Component({ + selector: 'core-placeholder', + templateUrl: 'placeholder.html', +}) +export class CoreSplitViewPlaceholderPage { + + constructor(public navCtrl: NavController, public navParams: NavParams) { } + +} diff --git a/src/components/split-view/split-view.html b/src/components/split-view/split-view.html new file mode 100644 index 000000000..dbea8c69b --- /dev/null +++ b/src/components/split-view/split-view.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/split-view/split-view.scss b/src/components/split-view/split-view.scss new file mode 100644 index 000000000..4383232eb --- /dev/null +++ b/src/components/split-view/split-view.scss @@ -0,0 +1,39 @@ +core-split-view { + ion-menu.split-pane-side { + display: block; + + .menu-inner { + left: 0; + right: 0; + top: 0; + bottom: 0; + -webkit-transform: initial; + transform: initial; + width: 100%; + } + } + + .split-pane-main { + display: none; + } + + .split-pane-visible { + .split-pane-main { + display: block; + } + + .split-pane-side .core-split-item-selected { + background-color: $gray-lighter; + border-left: 5px solid $core-color-light; + &.item-md { + padding-left: $item-md-padding-start - 5px; + } + &.item-ios { + padding-left: $item-ios-padding-start - 5px; + } + &.item-wp { + padding-left: $item-wp-padding-start - 5px; + } + } + } +} \ No newline at end of file diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts new file mode 100644 index 000000000..50295fbb1 --- /dev/null +++ b/src/components/split-view/split-view.ts @@ -0,0 +1,147 @@ +// (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. + +// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo + +import { Component, ViewChild, Injectable, Input, ElementRef, OnInit } from '@angular/core'; +import { NavController, Nav } from 'ionic-angular'; +import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder'; + +/** + * Directive to create a split view layout. + * + * @description + * To init/change the right pane contents (content pane), inject this component in the master page. + * @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + * Then use the push function to load. + * + * Accepts the following params: + * + * @param {string|boolean} [when] When the split-pane should be shown. Can be a CSS media query expression, or a shortcut + * expression. Can also be a boolean expression. Check split-pane component documentation for more information. + * + * Example: + * + * + * + * + */ +@Component({ + selector: 'core-split-view', + templateUrl: 'split-view.html' +}) +export class CoreSplitViewComponent implements OnInit { + // @todo Mix both panels header buttons + + @ViewChild('detailNav') _detailNav: Nav; + @Input() when?: string | boolean = "md"; // + protected _isOn: boolean = false; + protected masterPageName: string = ""; + protected loadDetailPage: any = false; + protected element: HTMLElement; // Current element. + + // Empty placeholder for the 'detail' page. + detailPage: any = null; + + constructor(private _masterNav: NavController, element: ElementRef) { + this.element = element.nativeElement; + } + + /** + * Component being initialized. + */ + ngOnInit() { + // Get the master page name and set an empty page as a placeholder. + this.masterPageName = this._masterNav.getActive().component.name; + this.emptyDetails(); + } + + /** + * Check if both panels are shown. It depends on screen width. + * + * @return {boolean} If split view is enabled. + */ + isOn(): boolean { + return this._isOn; + } + + /** + * Push a page to the navigation stack. It will decide where to load it depending on the size of the screen. + * + * @param {any} page The component class or deeplink name you want to push onto the navigation stack. + * @param {any} params Any NavParams you want to pass along to the next view. + */ + push(page: any, params?: any, element?: HTMLElement) { + if (this._isOn) { + this._detailNav.setRoot(page, params); + } else { + this.loadDetailPage = { + component: page, + data: params + }; + this._masterNav.push(page, params); + } + } + + /** + * Set the details panel to default info. + */ + emptyDetails() { + this.loadDetailPage = false; + this._detailNav.setRoot('CoreSplitViewPlaceholderPage'); + } + + /** + * Splitpanel visibility has changed. + * + * @param {Boolean} isOn If it fits both panels at the same time. + */ + onSplitPaneChanged(isOn) { + this._isOn = isOn; + if (this._masterNav && this._detailNav) { + (isOn) ? this.activateSplitView() : this.deactivateSplitView(); + } + } + + /** + * Enable the split view, show both panels and do some magical navigation. + */ + activateSplitView() { + let currentView = this._masterNav.getActive(), + currentPageName = currentView.component.name; + if (currentPageName != this.masterPageName) { + // CurrentView is a 'Detail' page remove it from the 'master' nav stack. + this._masterNav.pop(); + + // and add it to the 'detail' nav stack. + this._detailNav.setRoot(currentView.component, currentView.data); + } else if (this.loadDetailPage) { + // MasterPage is shown, load the last detail page if found. + this._detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data); + } + this.loadDetailPage = false; + } + + /** + * Disabled the split view, show only one panel and do some magical navigation. + */ + deactivateSplitView() { + let detailView = this._detailNav.getActive(), + currentPageName = detailView.component.name; + if (currentPageName != 'CoreSplitViewPlaceholderPage') { + // Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack. + this._masterNav.push(detailView.component, detailView.data); + } + } +} \ No newline at end of file diff --git a/src/providers/app.ts b/src/providers/app.ts index d0cc6e579..7bc1f36be 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -200,16 +200,6 @@ export class CoreAppProvider { return limited.indexOf(type) > -1; } - /** - * Check if device is wide enough. It's used i.e. to show split view. - * - * @return {boolean} Whether the device uses a limited connection. - */ - isWide() : boolean { - //@todo Should use media querys like splitpane - return this.platform.is('tablet'); - } - /** * Check if the app is running in a Windows environment. * From 2cdeda17ee28318b3f9ae78d9ef39c083ed01853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 11 Jan 2018 16:30:53 +0100 Subject: [PATCH 09/10] MOBILE-2308 core: Add segment name to avoid collisions --- src/addon/calendar/pages/event/event.ts | 2 +- src/addon/calendar/pages/list/list.ts | 2 +- src/addon/calendar/pages/settings/settings.ts | 2 +- src/components/split-view/placeholder/placeholder.ts | 2 +- src/core/courses/pages/available-courses/available-courses.ts | 2 +- src/core/courses/pages/categories/categories.ts | 2 +- src/core/courses/pages/course-preview/course-preview.ts | 2 +- src/core/courses/pages/my-courses/my-courses.ts | 2 +- src/core/courses/pages/my-overview/my-overview.ts | 2 +- src/core/courses/pages/search/search.ts | 2 +- .../courses/pages/self-enrol-password/self-enrol-password.ts | 2 +- src/core/emulator/pages/capture-media/capture-media.ts | 2 +- src/core/login/pages/credentials/credentials.ts | 2 +- src/core/login/pages/email-signup/email-signup.ts | 2 +- src/core/login/pages/forgotten-password/forgotten-password.ts | 2 +- src/core/login/pages/init/init.ts | 2 +- src/core/login/pages/reconnect/reconnect.ts | 2 +- src/core/login/pages/site-error/site-error.ts | 2 +- src/core/login/pages/site-help/site-help.ts | 2 +- src/core/login/pages/site-policy/site-policy.ts | 2 +- src/core/login/pages/site/site.ts | 2 +- src/core/login/pages/sites/sites.ts | 2 +- src/core/mainmenu/pages/menu/menu.ts | 2 +- src/core/mainmenu/pages/more/more.ts | 2 +- src/core/sharedfiles/pages/choose-site/choose-site.ts | 2 +- src/core/sharedfiles/pages/list/list.ts | 2 +- src/core/viewer/pages/iframe/iframe.ts | 2 +- src/core/viewer/pages/text/text.ts | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 590e3806a..47059f76f 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -27,7 +27,7 @@ import * as moment from 'moment'; /** * Page that displays a single calendar event. */ -@IonicPage() +@IonicPage({segment: "addon-calendar-event"}) @Component({ selector: 'page-addon-calendar-event', templateUrl: 'event.html', diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 82c8b748f..1cbaf686a 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -30,7 +30,7 @@ import { CoreSplitViewComponent } from '../../../../components/split-view/split- /** * Page that displays the list of calendar events. */ -@IonicPage() +@IonicPage({segment: "addon-calendar-list"}) @Component({ selector: 'page-addon-calendar-list', templateUrl: 'list.html', diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts index 10d0deccc..a677ea8dd 100644 --- a/src/addon/calendar/pages/settings/settings.ts +++ b/src/addon/calendar/pages/settings/settings.ts @@ -21,7 +21,7 @@ import { CoreSitesProvider } from '../../../../providers/sites'; /** * Page that displays the calendar settings. */ -@IonicPage() +@IonicPage({segment: "addon-calendar-settings"}) @Component({ selector: 'page-addon-calendar-settings', templateUrl: 'settings.html', diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts index da0c7cf36..0f624fc72 100644 --- a/src/components/split-view/placeholder/placeholder.ts +++ b/src/components/split-view/placeholder/placeholder.ts @@ -4,7 +4,7 @@ import { NavController, NavParams } from 'ionic-angular'; -@IonicPage() +@IonicPage({segment: "core-placeholder"}) @Component({ selector: 'core-placeholder', templateUrl: 'placeholder.html', diff --git a/src/core/courses/pages/available-courses/available-courses.ts b/src/core/courses/pages/available-courses/available-courses.ts index 47d71babf..084e2782f 100644 --- a/src/core/courses/pages/available-courses/available-courses.ts +++ b/src/core/courses/pages/available-courses/available-courses.ts @@ -21,7 +21,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that displays available courses in current site. */ -@IonicPage() +@IonicPage({segment: "core-courses-available-courses"}) @Component({ selector: 'page-core-courses-available-courses', templateUrl: 'available-courses.html', diff --git a/src/core/courses/pages/categories/categories.ts b/src/core/courses/pages/categories/categories.ts index 2253fc17a..3c78fa63a 100644 --- a/src/core/courses/pages/categories/categories.ts +++ b/src/core/courses/pages/categories/categories.ts @@ -23,7 +23,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that displays a list of categories and the courses in the current category if any. */ -@IonicPage() +@IonicPage({segment: "core-courses-categories"}) @Component({ selector: 'page-core-courses-categories', templateUrl: 'categories.html', diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index 0789790c9..a1b10ed7c 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -26,7 +26,7 @@ import { CoreCoursesDelegate } from '../../providers/delegate'; /** * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. */ -@IonicPage() +@IonicPage({segment: "core-courses-course-preview"}) @Component({ selector: 'page-core-courses-course-preview', templateUrl: 'course-preview.html', diff --git a/src/core/courses/pages/my-courses/my-courses.ts b/src/core/courses/pages/my-courses/my-courses.ts index 3259c9d47..639302b69 100644 --- a/src/core/courses/pages/my-courses/my-courses.ts +++ b/src/core/courses/pages/my-courses/my-courses.ts @@ -22,7 +22,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that displays the list of courses the user is enrolled in. */ -@IonicPage() +@IonicPage({segment: "core-courses-my-courses"}) @Component({ selector: 'page-core-courses-my-courses', templateUrl: 'my-courses.html', diff --git a/src/core/courses/pages/my-overview/my-overview.ts b/src/core/courses/pages/my-overview/my-overview.ts index 963afe4c5..21a7c423d 100644 --- a/src/core/courses/pages/my-overview/my-overview.ts +++ b/src/core/courses/pages/my-overview/my-overview.ts @@ -22,7 +22,7 @@ import * as moment from 'moment'; /** * Page that displays My Overview. */ -@IonicPage() +@IonicPage({segment: "core-courses-my-overview"}) @Component({ selector: 'page-core-courses-my-overview', templateUrl: 'my-overview.html', diff --git a/src/core/courses/pages/search/search.ts b/src/core/courses/pages/search/search.ts index 654b36cb5..0e7de9151 100644 --- a/src/core/courses/pages/search/search.ts +++ b/src/core/courses/pages/search/search.ts @@ -20,7 +20,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that allows searching for courses. */ -@IonicPage() +@IonicPage({segment: "core-courses-search"}) @Component({ selector: 'page-core-courses-search', templateUrl: 'search.html', diff --git a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts index dcdc404fe..7e9716a43 100644 --- a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts +++ b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts @@ -18,7 +18,7 @@ import { IonicPage, ViewController } from 'ionic-angular'; /** * Page that displays a form to enter a password to self enrol in a course. */ -@IonicPage() +@IonicPage({segment: "core-courses-self-enrol-password"}) @Component({ selector: 'page-core-courses-self-enrol-password', templateUrl: 'self-enrol-password.html', diff --git a/src/core/emulator/pages/capture-media/capture-media.ts b/src/core/emulator/pages/capture-media/capture-media.ts index ba257c29f..e94d15be5 100644 --- a/src/core/emulator/pages/capture-media/capture-media.ts +++ b/src/core/emulator/pages/capture-media/capture-media.ts @@ -22,7 +22,7 @@ import { CoreTimeUtilsProvider } from '../../../../providers/utils/time'; /** * Page to capture media in browser or desktop. */ -@IonicPage() +@IonicPage({segment: "core-emulator-capture-media"}) @Component({ selector: 'page-core-emulator-capture-media', templateUrl: 'capture-media.html', diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index ceb996031..32c939a05 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter the user credentials. */ -@IonicPage() +@IonicPage({segment: "core-login-credentials"}) @Component({ selector: 'page-core-login-credentials', templateUrl: 'credentials.html', diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index 95504c0fe..5311a4a47 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -26,7 +26,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to signup using email. */ -@IonicPage() +@IonicPage({segment: "core-login-email-signup"}) @Component({ selector: 'page-core-login-email-signup', templateUrl: 'email-signup.html', diff --git a/src/core/login/pages/forgotten-password/forgotten-password.ts b/src/core/login/pages/forgotten-password/forgotten-password.ts index 1b812040e..5dc6f1daa 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.ts @@ -22,7 +22,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to recover a forgotten password. */ -@IonicPage() +@IonicPage({segment: "core-login-forgotten-password"}) @Component({ selector: 'page-core-login-forgotten-password', templateUrl: 'forgotten-password.html', diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index b122908f9..7be53c668 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -23,7 +23,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper'; /** * Page that displays a "splash screen" while the app is being initialized. */ -@IonicPage() +@IonicPage({segment: "core-login-init"}) @Component({ selector: 'page-core-login-init', templateUrl: 'init.html', diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index aaa744056..4873c923c 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -23,7 +23,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter the user password to reconnect to a site. */ -@IonicPage() +@IonicPage({segment: "core-login-reconnect"}) @Component({ selector: 'page-core-login-reconnect', templateUrl: 'reconnect.html', diff --git a/src/core/login/pages/site-error/site-error.ts b/src/core/login/pages/site-error/site-error.ts index 6f248056e..f68e085ea 100644 --- a/src/core/login/pages/site-error/site-error.ts +++ b/src/core/login/pages/site-error/site-error.ts @@ -18,7 +18,7 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular'; /** * Component that displays an error when trying to connect to a site. */ -@IonicPage() +@IonicPage({segment: "core-login-site-error"}) @Component({ selector: 'page-core-login-site-error', templateUrl: 'site-error.html', diff --git a/src/core/login/pages/site-help/site-help.ts b/src/core/login/pages/site-help/site-help.ts index a003b28e7..a7d8132d2 100644 --- a/src/core/login/pages/site-help/site-help.ts +++ b/src/core/login/pages/site-help/site-help.ts @@ -18,7 +18,7 @@ import { IonicPage, ViewController } from 'ionic-angular'; /** * Component that displays some help regarding the CoreLoginSitePage. */ -@IonicPage() +@IonicPage({segment: "core-login-site-help"}) @Component({ selector: 'page-core-login-site-help', templateUrl: 'site-help.html', diff --git a/src/core/login/pages/site-policy/site-policy.ts b/src/core/login/pages/site-policy/site-policy.ts index dac709cde..c3c582358 100644 --- a/src/core/login/pages/site-policy/site-policy.ts +++ b/src/core/login/pages/site-policy/site-policy.ts @@ -23,7 +23,7 @@ import { CoreSite } from '../../../../classes/site'; /** * Page to accept a site policy. */ -@IonicPage() +@IonicPage({segment: "core-login-site-policy"}) @Component({ selector: 'page-core-login-site-policy', templateUrl: 'site-policy.html', diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index bd4af2e9b..6615edace 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -24,7 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter or select the site URL to connect to. */ -@IonicPage() +@IonicPage({segment: "core-login-site"}) @Component({ selector: 'page-core-login-site', templateUrl: 'site.html', diff --git a/src/core/login/pages/sites/sites.ts b/src/core/login/pages/sites/sites.ts index 3e6fa76c7..3ae4d56cd 100644 --- a/src/core/login/pages/sites/sites.ts +++ b/src/core/login/pages/sites/sites.ts @@ -24,7 +24,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper'; /** * Page that displays the list of stored sites. */ -@IonicPage() +@IonicPage({segment: "core-login-sites"}) @Component({ selector: 'page-core-login-sites', templateUrl: 'sites.html', diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index fad067dc5..d319918f1 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -22,7 +22,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/d /** * Page that displays the main menu of the app. */ -@IonicPage() +@IonicPage({segment: "core-mainmenu"}) @Component({ selector: 'page-core-mainmenu', templateUrl: 'menu.html', diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index 1f2fa4f28..6ae8863e3 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -22,7 +22,7 @@ import { CoreMainMenuProvider, CoreMainMenuCustomItem } from '../../providers/ma /** * Page that displays the list of main menu options that aren't in the tabs. */ -@IonicPage() +@IonicPage({segment: "core-mainmenu-more"}) @Component({ selector: 'page-core-mainmenu-more', templateUrl: 'more.html', diff --git a/src/core/sharedfiles/pages/choose-site/choose-site.ts b/src/core/sharedfiles/pages/choose-site/choose-site.ts index 68896cc94..20702dc4c 100644 --- a/src/core/sharedfiles/pages/choose-site/choose-site.ts +++ b/src/core/sharedfiles/pages/choose-site/choose-site.ts @@ -22,7 +22,7 @@ import { CoreSharedFilesHelperProvider } from '../../providers/helper'; /** * Modal to display the list of sites to choose one to store a shared file. */ -@IonicPage() +@IonicPage({segment: "core-shared-files-choose-site"}) @Component({ selector: 'page-core-shared-files-choose-site', templateUrl: 'choose-site.html', diff --git a/src/core/sharedfiles/pages/list/list.ts b/src/core/sharedfiles/pages/list/list.ts index dc285979e..92a1d7f34 100644 --- a/src/core/sharedfiles/pages/list/list.ts +++ b/src/core/sharedfiles/pages/list/list.ts @@ -24,7 +24,7 @@ import { CoreSharedFilesProvider } from '../../providers/sharedfiles'; /** * Modal to display the list of shared files. */ -@IonicPage() +@IonicPage({segment: "core-shared-files-list"}) @Component({ selector: 'page-core-shared-files-list', templateUrl: 'list.html', diff --git a/src/core/viewer/pages/iframe/iframe.ts b/src/core/viewer/pages/iframe/iframe.ts index ba67d8d7e..ee598859a 100644 --- a/src/core/viewer/pages/iframe/iframe.ts +++ b/src/core/viewer/pages/iframe/iframe.ts @@ -18,7 +18,7 @@ import { IonicPage, NavParams } from 'ionic-angular'; /** * Page to display a URL in an iframe. */ -@IonicPage() +@IonicPage({segment: "core-viewer-iframe"}) @Component({ selector: 'page-core-viewer-iframe', templateUrl: 'iframe.html', diff --git a/src/core/viewer/pages/text/text.ts b/src/core/viewer/pages/text/text.ts index 94486c69f..dd0d6281d 100644 --- a/src/core/viewer/pages/text/text.ts +++ b/src/core/viewer/pages/text/text.ts @@ -19,7 +19,7 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; /** * Page to render a certain text. If opened as a modal, it will have a button to close the modal. */ -@IonicPage() +@IonicPage({segment: "core-viewer-text"}) @Component({ selector: 'page-core-viewer-text', templateUrl: 'text.html', From 8794a06b9fde316634f03862f888dc63f05c9a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 12 Jan 2018 11:56:18 +0100 Subject: [PATCH 10/10] MOBILE-2308 calendar: Change static declarations and minor --- src/addon/calendar/pages/event/event.scss | 2 +- src/addon/calendar/pages/event/event.ts | 2 - src/addon/calendar/pages/list/list.ts | 12 ++--- src/addon/calendar/providers/calendar.ts | 53 ++++++++++--------- src/addon/calendar/providers/handlers.ts | 4 +- src/addon/calendar/providers/helper.ts | 4 +- src/app/app.scss | 6 +-- .../placeholder/placeholder.module.ts | 16 ++++++ .../split-view/placeholder/placeholder.ts | 23 ++++++-- src/components/split-view/split-view.ts | 34 ++++++------ src/core/courses/providers/handlers.ts | 2 +- 11 files changed, 93 insertions(+), 65 deletions(-) diff --git a/src/addon/calendar/pages/event/event.scss b/src/addon/calendar/pages/event/event.scss index 345d0d21c..c3d04186c 100644 --- a/src/addon/calendar/pages/event/event.scss +++ b/src/addon/calendar/pages/event/event.scss @@ -1,3 +1,3 @@ -page-addon-calendar-list { +page-addon-calendar-event { } \ No newline at end of file diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 47059f76f..a976a1201 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -86,8 +86,6 @@ export class AddonCalendarEventPage { /** * Fetches the event and updates the view. - * - * @param {boolean} refresh Empty events array first. */ fetchEvent() { return this.calendarProvider.getEvent(this.eventId).then((event) => { diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 1cbaf686a..5e6a2fbd1 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -103,7 +103,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Fetch all the data required for the view. * - * @param {boolean} refresh Empty events array first. + * @param {boolean} [refresh] Empty events array first. */ fetchData(refresh = false) { this.daysLoaded = 0; @@ -121,7 +121,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Fetches the events and updates the view. * - * @param {boolean} refresh Empty events array first. + * @param {boolean} [refresh] Empty events array first. */ fetchEvents(refresh = false) { return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => { @@ -144,7 +144,7 @@ export class AddonCalendarListPage implements OnDestroy { return a.timestart - b.timestart; }); - events.forEach(this.calendarHelper.formatEventData); + events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper)); this.getCategories = this.shouldLoadCategories(events); if (refresh) { @@ -253,7 +253,7 @@ export class AddonCalendarListPage implements OnDestroy { this.categoriesRetrieved = true; this.categories = {}; // Index categories by ID. - cats.forEach(function(category) { + cats.forEach((category) => { this.categories[category.id] = category; }); }).catch(() => { @@ -291,7 +291,7 @@ export class AddonCalendarListPage implements OnDestroy { openCourseFilter(event: MouseEvent) : void { let popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {courses: this.courses, courseId: this.filter.course.id}); - popover.onDidDismiss(course => { + popover.onDidDismiss((course) => { if (course) { this.filter.course = course; this.content.scrollToTop(); @@ -322,6 +322,6 @@ export class AddonCalendarListPage implements OnDestroy { * Page destroyed. */ ngOnDestroy() { - this.obsDefaultTimeChange && this.obsDefaultTimeChange.off && this.obsDefaultTimeChange.off(); + this.obsDefaultTimeChange && this.obsDefaultTimeChange.off(); } } \ No newline at end of file diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index d4dbfb03d..886a21670 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -31,14 +31,15 @@ export class AddonCalendarProvider { public static DAYS_INTERVAL = 30; public static COMPONENT = 'AddonCalendarEvents'; public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent'; - protected static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime'; - protected static DEFAULT_NOTIFICATION_TIME = 60; + protected DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime'; + protected ROOT_CACHE_KEY = 'mmaCalendar:'; + protected DEFAULT_NOTIFICATION_TIME = 60; // Variables for database. - protected static EVENTS_TABLE = 'calendar_events'; // Queue of files to download. - protected static tablesSchema = [ + protected EVENTS_TABLE = 'calendar_events'; + protected tablesSchema = [ { - name: AddonCalendarProvider.EVENTS_TABLE, + name: this.EVENTS_TABLE, columns: [ { name: 'id', @@ -108,7 +109,7 @@ export class AddonCalendarProvider { private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider, private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) { this.logger = logger.getInstance('AddonCalendarProvider'); - this.sitesProvider.createTablesFromSchema(AddonCalendarProvider.tablesSchema); + this.sitesProvider.createTablesFromSchema(this.tablesSchema); } /** @@ -120,8 +121,8 @@ export class AddonCalendarProvider { getDefaultNotificationTime(siteId?: string) : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - let key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; - return this.configProvider.get(key, AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME); + let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + return this.configProvider.get(key, this.DEFAULT_NOTIFICATION_TIME); } /** @@ -159,7 +160,7 @@ export class AddonCalendarProvider { * @return {string} Cache key. */ protected getEventCacheKey(id: number): string { - return 'mmaCalendar:events:' + id; + return this.ROOT_CACHE_KEY + 'events:' + id; } /** @@ -171,7 +172,7 @@ export class AddonCalendarProvider { */ getEventFromLocalDb(id: number, siteId?: string) : Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(AddonCalendarProvider.EVENTS_TABLE, {id: id}); + return site.getDb().getRecord(this.EVENTS_TABLE, {id: id}); }); } @@ -263,6 +264,15 @@ export class AddonCalendarProvider { }); } + /** + * Get prefix cache key for events list WS calls. + * + * @return {string} Prefix Cache key. + */ + protected getEventsListPrefixCacheKey() : string { + return this.ROOT_CACHE_KEY + 'eventslist:'; + } + /** * Get cache key for events list WS calls. * @@ -271,16 +281,7 @@ export class AddonCalendarProvider { * @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:'; + return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval; } /** @@ -298,7 +299,7 @@ export class AddonCalendarProvider { promises.push(this.coursesProvider.invalidateUserCourses(siteId)); promises.push(this.groupsProvider.invalidateUserGroups(courses, siteId)); - promises.push(site.invalidateWsCacheForKeyStartingWith(this.getRootCacheKey())); + promises.push(site.invalidateWsCacheForKeyStartingWith(this.getEventsListPrefixCacheKey())); return Promise.all(promises); }); } @@ -393,8 +394,8 @@ export class AddonCalendarProvider { 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()) { + let timeEnd = (event.timestart + event.timeduration) * 1000; + if (timeEnd <= new Date().getTime()) { // The event has finished already, don't schedule it. return Promise.resolve(); } @@ -455,7 +456,7 @@ export class AddonCalendarProvider { setDefaultNotificationTime(time: number, siteId?: string) : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - let key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; return this.configProvider.set(key, time); } @@ -496,7 +497,7 @@ export class AddonCalendarProvider { notificationtime: e.notificationtime || -1 }; - return db.insertOrUpdateRecord(AddonCalendarProvider.EVENTS_TABLE, eventRecord, {id: eventRecord.id}); + return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, {id: eventRecord.id}); })); }); @@ -521,7 +522,7 @@ export class AddonCalendarProvider { event.notificationtime = time; - return site.getDb().insertOrUpdateRecord(AddonCalendarProvider.EVENTS_TABLE, event, {id: event.id}).then(() => { + return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, {id: event.id}).then(() => { return this.scheduleEventNotification(event, time); }); }); diff --git a/src/addon/calendar/providers/handlers.ts b/src/addon/calendar/providers/handlers.ts index c4d71c6c2..0b80de8cd 100644 --- a/src/addon/calendar/providers/handlers.ts +++ b/src/addon/calendar/providers/handlers.ts @@ -21,7 +21,7 @@ import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/main */ @Injectable() export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { - name = 'mmaCalendar'; + name = 'AddonCalendar'; priority = 400; constructor(private calendarProvider: AddonCalendarProvider) {} @@ -46,7 +46,7 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { icon: 'calendar', title: 'addon.calendar.calendar', page: 'AddonCalendarListPage', - class: 'mma-calendar-handler' + class: 'addon-calendar-handler' }; } } diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index 36547ecb1..54076fcd7 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -24,7 +24,7 @@ import { CoreSitesProvider } from '../../../providers/sites'; export class AddonCalendarHelperProvider { protected logger; - private static eventicons = { + private EVENTICONS = { 'course': 'ionic', 'group': 'people', 'site': 'globe', @@ -42,7 +42,7 @@ export class AddonCalendarHelperProvider { * @param {any} e Event to format. */ formatEventData(e: any) { - e.icon = AddonCalendarHelperProvider.eventicons[e.eventtype] || false; + e.icon = this.EVENTICONS[e.eventtype] || false; if (!e.icon) { // @todo: It's a module event. //e.icon = this.courseProvider.getModuleIconSrc(e.modulename); diff --git a/src/app/app.scss b/src/app/app.scss index 9f15a904b..8c06237ba 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -153,16 +153,16 @@ core-format-text[maxHeight], *[core-format-text][maxHeight] { display: none; } - &:not(.mm-shortened) { + &:not(.core-shortened) { max-height: none !important; } - &.mm-shortened { + &.core-shortened { color: $gray-darker; overflow: hidden; min-height: 50px; - .mm-show-more { + .core-show-more { color: color($colors, dark); text-align: right; font-size: 14px; diff --git a/src/components/split-view/placeholder/placeholder.module.ts b/src/components/split-view/placeholder/placeholder.module.ts index d3133905f..d17ef7a88 100644 --- a/src/components/split-view/placeholder/placeholder.module.ts +++ b/src/components/split-view/placeholder/placeholder.module.ts @@ -1,3 +1,19 @@ +// (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. + +// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo + import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { CoreSplitViewPlaceholderPage } from './placeholder'; diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts index 0f624fc72..34233ea46 100644 --- a/src/components/split-view/placeholder/placeholder.ts +++ b/src/components/split-view/placeholder/placeholder.ts @@ -1,8 +1,21 @@ +// (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. + +// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo + import { Component } from '@angular/core'; -import { - IonicPage, - NavController, - NavParams } from 'ionic-angular'; +import { IonicPage } from 'ionic-angular'; @IonicPage({segment: "core-placeholder"}) @Component({ @@ -11,6 +24,6 @@ import { }) export class CoreSplitViewPlaceholderPage { - constructor(public navCtrl: NavController, public navParams: NavParams) { } + constructor() { } } diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index 50295fbb1..b6f20244a 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -44,9 +44,9 @@ import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder'; export class CoreSplitViewComponent implements OnInit { // @todo Mix both panels header buttons - @ViewChild('detailNav') _detailNav: Nav; + @ViewChild('detailNav') detailNav: Nav; @Input() when?: string | boolean = "md"; // - protected _isOn: boolean = false; + protected isEnabled: boolean = false; protected masterPageName: string = ""; protected loadDetailPage: any = false; protected element: HTMLElement; // Current element. @@ -54,7 +54,7 @@ export class CoreSplitViewComponent implements OnInit { // Empty placeholder for the 'detail' page. detailPage: any = null; - constructor(private _masterNav: NavController, element: ElementRef) { + constructor(private masterNav: NavController, element: ElementRef) { this.element = element.nativeElement; } @@ -63,7 +63,7 @@ export class CoreSplitViewComponent implements OnInit { */ ngOnInit() { // Get the master page name and set an empty page as a placeholder. - this.masterPageName = this._masterNav.getActive().component.name; + this.masterPageName = this.masterNav.getActive().component.name; this.emptyDetails(); } @@ -73,7 +73,7 @@ export class CoreSplitViewComponent implements OnInit { * @return {boolean} If split view is enabled. */ isOn(): boolean { - return this._isOn; + return this.isEnabled; } /** @@ -83,14 +83,14 @@ export class CoreSplitViewComponent implements OnInit { * @param {any} params Any NavParams you want to pass along to the next view. */ push(page: any, params?: any, element?: HTMLElement) { - if (this._isOn) { - this._detailNav.setRoot(page, params); + if (this.isEnabled) { + this.detailNav.setRoot(page, params); } else { this.loadDetailPage = { component: page, data: params }; - this._masterNav.push(page, params); + this.masterNav.push(page, params); } } @@ -99,7 +99,7 @@ export class CoreSplitViewComponent implements OnInit { */ emptyDetails() { this.loadDetailPage = false; - this._detailNav.setRoot('CoreSplitViewPlaceholderPage'); + this.detailNav.setRoot('CoreSplitViewPlaceholderPage'); } /** @@ -108,8 +108,8 @@ export class CoreSplitViewComponent implements OnInit { * @param {Boolean} isOn If it fits both panels at the same time. */ onSplitPaneChanged(isOn) { - this._isOn = isOn; - if (this._masterNav && this._detailNav) { + this.isEnabled = isOn; + if (this.masterNav && this.detailNav) { (isOn) ? this.activateSplitView() : this.deactivateSplitView(); } } @@ -118,17 +118,17 @@ export class CoreSplitViewComponent implements OnInit { * Enable the split view, show both panels and do some magical navigation. */ activateSplitView() { - let currentView = this._masterNav.getActive(), + let currentView = this.masterNav.getActive(), currentPageName = currentView.component.name; if (currentPageName != this.masterPageName) { // CurrentView is a 'Detail' page remove it from the 'master' nav stack. - this._masterNav.pop(); + this.masterNav.pop(); // and add it to the 'detail' nav stack. - this._detailNav.setRoot(currentView.component, currentView.data); + this.detailNav.setRoot(currentView.component, currentView.data); } else if (this.loadDetailPage) { // MasterPage is shown, load the last detail page if found. - this._detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data); + this.detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data); } this.loadDetailPage = false; } @@ -137,11 +137,11 @@ export class CoreSplitViewComponent implements OnInit { * Disabled the split view, show only one panel and do some magical navigation. */ deactivateSplitView() { - let detailView = this._detailNav.getActive(), + let detailView = this.detailNav.getActive(), currentPageName = detailView.component.name; if (currentPageName != 'CoreSplitViewPlaceholderPage') { // Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack. - this._masterNav.push(detailView.component, detailView.data); + this.masterNav.push(detailView.component, detailView.data); } } } \ No newline at end of file diff --git a/src/core/courses/providers/handlers.ts b/src/core/courses/providers/handlers.ts index dd3338802..f09078432 100644 --- a/src/core/courses/providers/handlers.ts +++ b/src/core/courses/providers/handlers.ts @@ -22,7 +22,7 @@ import { CoreCoursesMyOverviewProvider } from '../providers/my-overview'; */ @Injectable() export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { - name = 'mmCourses'; + name = 'CoreCourses'; priority = 1100; isOverviewEnabled: boolean;