From 1d39a3ed2816d8d6556c1a1cf08c836db7833ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 3 Feb 2021 14:47:02 +0100 Subject: [PATCH 1/4] MOBILE-3660 courses: Implement missing handlers --- .../services/handlers/push-click.ts | 8 +- src/core/features/course/services/course.ts | 2 +- src/core/features/courses/courses.module.ts | 12 + .../courses/pages/dashboard/dashboard.ts | 1 + src/core/features/courses/services/courses.ts | 2 - .../courses/services/handlers/course-link.ts | 342 ++++++++++++++++++ .../services/handlers/courses-index-link.ts | 65 ++++ .../services/handlers/dashboard-home.ts | 31 +- .../services/handlers/dashboard-link.ts | 57 +++ .../services/handlers/enrol-push-click.ts | 91 +++++ .../services/handlers/my-courses-home.ts | 7 + .../services/handlers/request-push-click.ts | 103 ++++++ .../mainmenu/services/handlers/mainmenu.ts | 12 +- 13 files changed, 717 insertions(+), 16 deletions(-) create mode 100644 src/core/features/courses/services/handlers/course-link.ts create mode 100644 src/core/features/courses/services/handlers/courses-index-link.ts create mode 100644 src/core/features/courses/services/handlers/dashboard-link.ts create mode 100644 src/core/features/courses/services/handlers/enrol-push-click.ts create mode 100644 src/core/features/courses/services/handlers/request-push-click.ts diff --git a/src/addons/notifications/services/handlers/push-click.ts b/src/addons/notifications/services/handlers/push-click.ts index 8b8e00723..32985b0b2 100644 --- a/src/addons/notifications/services/handlers/push-click.ts +++ b/src/addons/notifications/services/handlers/push-click.ts @@ -41,7 +41,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification The notification to check. * @return Whether the notification click is handled by this handler */ - async handles(notification: NotificationData): Promise { + async handles(notification: AddonNotificationsNotificationData): Promise { if (!notification.moodlecomponent) { // The notification doesn't come from Moodle. Handle it. return true; @@ -63,7 +63,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification Notification to mark. * @return Promise resolved when done. */ - protected async markAsRead(notification: NotificationData): Promise { + protected async markAsRead(notification: AddonNotificationsNotificationData): Promise { const notifId = notification.savedmessageid || notification.id; if (!notifId) { @@ -81,7 +81,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification The notification to check. * @return Promise resolved when done. */ - async handleClick(notification: NotificationData): Promise { + async handleClick(notification: AddonNotificationsNotificationData): Promise { if (notification.customdata?.extendedtext) { // Display the text in a modal. @@ -133,7 +133,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi export class AddonNotificationsPushClickHandler extends makeSingleton(AddonNotificationsPushClickHandlerService) {} -type NotificationData = CorePushNotificationsNotificationBasicData & { +type AddonNotificationsNotificationData = CorePushNotificationsNotificationBasicData & { contexturl?: string; // URL related to the notification. savedmessageid?: number; // Notification ID (optional). id?: number; // Notification ID (optional). diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index b0f743abe..25f2fab6f 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -162,7 +162,7 @@ export class CoreCourseProvider { * @return Whether the current view is a certain course. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - currentViewIsCourse(navCtrl: any, courseId: number): boolean { + currentViewIsCourse(courseId: number): boolean { // @todo implement return false; } diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index e47bfb9f7..52a5e34c8 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -14,12 +14,19 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; +import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate'; +import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; +import { CoreCoursesCourseLinkHandler } from './services/handlers/course-link'; +import { CoreCoursesIndexLinkHandler } from './services/handlers/courses-index-link'; import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home'; +import { CoreCoursesDashboardLinkHandler } from './services/handlers/dashboard-link'; +import { CoreCoursesEnrolPushClickHandler } from './services/handlers/enrol-push-click'; import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses-home'; +import { CoreCoursesRequestPushClickHandler } from './services/handlers/request-push-click'; const mainMenuHomeChildrenRoutes: Routes = [ { @@ -59,6 +66,11 @@ const mainMenuHomeSiblingRoutes: Routes = [ useFactory: () => () => { CoreMainMenuHomeDelegate.instance.registerHandler(CoreDashboardHomeHandler.instance); CoreMainMenuHomeDelegate.instance.registerHandler(CoreCoursesMyCoursesHomeHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesCourseLinkHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesIndexLinkHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesDashboardLinkHandler.instance); + CorePushNotificationsDelegate.instance.registerClickHandler(CoreCoursesEnrolPushClickHandler.instance); + CorePushNotificationsDelegate.instance.registerClickHandler(CoreCoursesRequestPushClickHandler.instance); }, }, ], diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index f32c3b666..26514f819 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -161,6 +161,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * Open page to manage courses storage. */ manageCoursesStorage(): void { + // AddonStorageManagerCoursesStoragePage // @todo this.navCtrl.navigateForward(['/main/home/courses/storage']); } diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index 54e64f496..84ce04a4f 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -1168,8 +1168,6 @@ export class CoreCourses extends makeSingleton(CoreCoursesProvider) {} /** * Data sent to the EVENT_MY_COURSES_UPDATED. - * - * @todo course type. */ export type CoreCoursesMyCoursesUpdatedEventData = { action: string; // Action performed. diff --git a/src/core/features/courses/services/handlers/course-link.ts b/src/core/features/courses/services/handlers/course-link.ts new file mode 100644 index 000000000..bd58a16bf --- /dev/null +++ b/src/core/features/courses/services/handlers/course-link.ts @@ -0,0 +1,342 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreCourse } from '@features/course/services/course'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CoreCourseAnyCourseData, CoreCourses, CoreCoursesProvider, CoreEnrolledCourseData } from '../courses'; +import { CoreLogger } from '@singletons/logger'; +import { makeSingleton, Translate } from '@singletons'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { Params } from '@angular/router'; +import { CoreError } from '@classes/errors/error'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreIonLoadingElement } from '@classes/ion-loading'; + +/** + * Handler to treat links to course view or enrol (except site home). + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesCourseLinkHandler'; + pattern = /((\/enrol\/index\.php)|(\/course\/enrol\.php)|(\/course\/view\.php)).*([?&]id=\d+)/; + + protected waitStart = 0; + protected logger: CoreLogger; + + constructor() { + super(); + + this.logger = CoreLogger.getInstance('CoreCoursesCourseLinkHandler'); + } + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. + */ + getActions( + siteIds: string[], + url: string, + params: Params, + ): CoreContentLinksAction[] | Promise { + const courseId = parseInt(params.id, 10); + const sectionId = params.sectionid ? parseInt(params.sectionid, 10) : null; + const pageParams: Params = { + sectionId: sectionId || null, + }; + let sectionNumber = typeof params.section != 'undefined' ? parseInt(params.section, 10) : NaN; + + if (!sectionId && !sectionNumber) { + // Check if the URL has a hash to navigate to the section. + const matches = url.match(/#section-(\d+)/); + if (matches && matches[1]) { + sectionNumber = parseInt(matches[1], 10); + } + } + + if (!isNaN(sectionNumber)) { + pageParams.sectionNumber = sectionNumber; + } + + return [{ + action: (siteId): void => { + siteId = siteId || CoreSites.instance.getCurrentSiteId(); + if (siteId == CoreSites.instance.getCurrentSiteId()) { + // Check if we already are in the course index page. + if (CoreCourse.instance.currentViewIsCourse(courseId)) { + // Current view is this course, just select the contents tab. + CoreCourse.instance.selectCourseTab('', pageParams); + + return; + } else { + this.actionEnrol(courseId, url, pageParams).catch(() => { + // Ignore errors. + }); + } + } else { + // Make the course the new history root (to avoid "loops" in history). + CoreCourseHelper.instance.getAndOpenCourse(courseId, pageParams, siteId); + } + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + async isEnabled(siteId: string, url: string, params: Params, courseId?: number): Promise { + courseId = parseInt(params.id, 10); + + if (!courseId) { + return false; + } + + // Get the course id of Site Home. + return CoreSites.instance.getSiteHomeId(siteId).then((siteHomeId) => courseId != siteHomeId); + } + + /** + * Action to perform when an enrol link is clicked. + * + * @param courseId Course ID. + * @param url Treated URL. + * @param pageParams Params to send to the new page. + * @return Promise resolved when done. + */ + protected async actionEnrol(courseId: number, url: string, pageParams: Params): Promise { + const modal = await CoreDomUtils.instance.showModalLoading(); + let course: CoreCourseAnyCourseData | { id: number } | undefined; + + // Check if user is enrolled in the course. + try { + course = await CoreCourses.instance.getUserCourse(courseId); + } catch { + course = await this.checkSelfUserCanSelfEnrolOrAccess(courseId, url, modal); + } + + // Check if we need to retrieve the course. + if (!course) { + try { + const data = await CoreCourseHelper.instance.getCourse(courseId); + course = data.course; + } catch { + // Cannot get course, return a "fake". + course = { id: courseId }; + } + } + + modal.dismiss(); + + // Now open the course. + CoreCourseHelper.instance.openCourse(course, pageParams); + } + + /** + * Check if the user can self enrol or access the course. + * + * @param courseId Course ID. + * @param url Treated URL. + * @param modal Modal, to dismiss when needed. + * @return The course after self enrolling or undefined if the user has access but is not enrolled. + */ + protected checkSelfUserCanSelfEnrolOrAccess( + courseId: number, + url: string, + modal: CoreIonLoadingElement, + ): Promise { + // User is not enrolled in the course. Check if can self enrol. + return this.canSelfEnrol(courseId).then(async () => { + modal.dismiss(); + + const isEnrolUrl = !!url.match(/(\/enrol\/index\.php)|(\/course\/enrol\.php)/); + + // The user can self enrol. If it's not a enrolment URL we'll ask for confirmation. + if (!isEnrolUrl) { + try { + await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.courses.confirmselfenrol')); + } catch { + // User cancelled. Check if the user can view the course contents (guest access or similar). + await CoreCourse.instance.getSections(courseId, false, true); + + return; + } + } + + // Enrol URL or user confirmed. + try { + return this.selfEnrol(courseId); + } catch (error) { + if (error) { + CoreDomUtils.instance.showErrorModal(error); + } + + throw error; + } + }, async (error) => { + // Can't self enrol. Check if the user can view the course contents (guest access or similar). + try { + await CoreCourse.instance.getSections(courseId, false, true); + } catch { + // Error. Show error message and allow the user to open the link in browser. + modal.dismiss(); + + if (error) { + error = CoreTextUtils.instance.getErrorMessageFromError(error) || error; + } + if (!error) { + error = Translate.instance.instant('core.courses.notenroled'); + } + + const body = CoreTextUtils.instance.buildSeveralParagraphsMessage( + [error, Translate.instance.instant('core.confirmopeninbrowser')], + ); + + try { + await CoreDomUtils.instance.showConfirm(body); + + CoreSites.instance.getCurrentSite()?.openInBrowserWithAutoLogin(url); + } catch { + // User cancelled. + }; + + throw error; + } + + return undefined; + }); + } + + /** + * Check if a user can be "automatically" self enrolled in a course. + * + * @param courseId Course ID. + * @return Promise resolved if user can be enrolled in a course, rejected otherwise. + */ + protected async canSelfEnrol(courseId: number): Promise { + // Check that the course has self enrolment enabled. + + const methods = await CoreCourses.instance.getCourseEnrolmentMethods(courseId); + let isSelfEnrolEnabled = false; + let instances = 0; + methods.forEach((method) => { + if (method.type == 'self' && method.status) { + isSelfEnrolEnabled = true; + instances++; + } + }); + + if (!isSelfEnrolEnabled || instances != 1) { + // Self enrol not enabled or more than one instance. + throw new CoreError('Self enrol not enabled in course'); + } + } + + /** + * Try to self enrol a user in a course. + * + * @param courseId Course ID. + * @param password Password. + * @return Promise resolved when the user is enrolled, rejected otherwise. + */ + protected async selfEnrol(courseId: number, password?: string): Promise { + const modal = await CoreDomUtils.instance.showModalLoading(); + + try { + await CoreCourses.instance.selfEnrol(courseId, password); + + // Success self enrolling the user, invalidate the courses list. + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses()); + + try { + // Sometimes the list of enrolled courses takes a while to be updated. Wait for it. + return this.waitForEnrolled(courseId, true); + } finally { + modal.dismiss(); + } + } catch (error) { + modal.dismiss(); + + if (error && error.code === CoreCoursesProvider.ENROL_INVALID_KEY) { + // Invalid password. Allow the user to input password. + const title = Translate.instance.instant('core.courses.selfenrolment'); + const body = ' '; // Empty message. + const placeholder = Translate.instance.instant('core.courses.password'); + + if (typeof password != 'undefined') { + // The user attempted a password. Show an error message. + CoreDomUtils.instance.showErrorModal(error); + } + + password = await CoreDomUtils.instance.showPrompt(body, title, placeholder); + + return this.selfEnrol(courseId, password); + } else { + throw error; + } + } + } + + /** + * Wait for the user to be enrolled in a course. + * + * @param courseId The course ID. + * @param first If it's the first call (true) or it's a recursive call (false). + * @return Promise resolved when enrolled or timeout. + */ + protected async waitForEnrolled(courseId: number, first?: boolean): Promise { + if (first) { + this.waitStart = Date.now(); + } + + // Check if user is enrolled in the course. + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses()); + try { + return CoreCourses.instance.getUserCourse(courseId); + } catch { + + // Not enrolled, wait a bit and try again. + if (Date.now() - this.waitStart > 60000) { + // Max time reached, stop. + return; + } + + return new Promise((resolve, reject): void => { + setTimeout(() => { + this.waitForEnrolled(courseId) + .then(resolve).catch(reject); + }, 5000); + }); + } + } + +} + +export class CoreCoursesCourseLinkHandler extends makeSingleton(CoreCoursesCourseLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/courses-index-link.ts b/src/core/features/courses/services/handlers/courses-index-link.ts new file mode 100644 index 000000000..52179a846 --- /dev/null +++ b/src/core/features/courses/services/handlers/courses-index-link.ts @@ -0,0 +1,65 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Params } from '@angular/router'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; +import { CoreCoursesMyCoursesHomeHandlerService } from './my-courses-home'; + +/** + * Handler to treat links to course index (list of courses). + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesIndexLinkHandler'; + featureName = 'CoreMainMenuDelegate_CoreCourses'; + pattern = /\/course\/?(index\.php.*)?$/; + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @return List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { + return [{ + action: (siteId): void => { + let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME; + + if (CoreCourses.instance.isGetCoursesByFieldAvailable()) { + if (params.categoryid) { + pageName += '/categories/' + params.categoryid; + } else { + pageName += '/all'; + } + } else { + // By default, go to My Courses. + pageName += '/my'; + } + + CoreNavigator.instance.navigateToSitePath(pageName, { siteId }); + }, + }]; + } + +} + +export class CoreCoursesIndexLinkHandler extends makeSingleton(CoreCoursesIndexLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/dashboard-home.ts b/src/core/features/courses/services/handlers/dashboard-home.ts index e3a3edb1c..b4f4e8ce0 100644 --- a/src/core/features/courses/services/handlers/dashboard-home.ts +++ b/src/core/features/courses/services/handlers/dashboard-home.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline'; import { Injectable } from '@angular/core'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; @@ -45,9 +46,35 @@ export class CoreDashboardHomeHandlerService implements CoreMainMenuHomeHandler * @return Whether or not the handler is enabled on a site level. */ async isEnabledForSite(siteId?: string): Promise { - const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); + const promises: Promise[] = []; + let blocksEnabled = false; + let dashboardAvailable = false; - return CoreBlockDelegate.instance.hasSupportedBlock(blocks); + // Check if blocks and 3.6 dashboard is enabled. + promises.push(CoreBlockDelegate.instance.areBlocksDisabled(siteId).then((disabled) => { + blocksEnabled = !disabled; + + return; + })); + + promises.push(CoreCoursesDashboard.instance.isAvailable().then((available) => { + dashboardAvailable = available; + + return; + })); + + await Promise.all(promises); + + if (dashboardAvailable && blocksEnabled) { + const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); + + return CoreBlockDelegate.instance.hasSupportedBlock(blocks); + } + + // Check if my overview is enabled. If it's enabled we will fake enabled blocks. + const timelineEnabled = await AddonBlockTimeline.instance.isAvailable(); + + return timelineEnabled && blocksEnabled; } /** diff --git a/src/core/features/courses/services/handlers/dashboard-link.ts b/src/core/features/courses/services/handlers/dashboard-link.ts new file mode 100644 index 000000000..31c643165 --- /dev/null +++ b/src/core/features/courses/services/handlers/dashboard-link.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './dashboard-home'; + +/** + * Handler to treat links to my overview. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesDashboardLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesMyOverviewLinkHandler'; + pattern = /\/my\/?$/; + + /** + * Get the list of actions for a link (url). + * + * @return List of (or promise resolved with list of) actions. + */ + getActions(): CoreContentLinksAction[] | Promise { + return [{ + action: (siteId): void => { + // Use redirect to select the tab. + CoreNavigator.instance.navigateToSitePath(CoreDashboardHomeHandlerService.PAGE_NAME, { siteId }); + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * + * @param siteId The site ID. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string): boolean | Promise { + return CoreDashboardHomeHandler.instance.isEnabledForSite(siteId); + } + +} + +export class CoreCoursesDashboardLinkHandler extends makeSingleton(CoreCoursesDashboardLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/enrol-push-click.ts b/src/core/features/courses/services/handlers/enrol-push-click.ts new file mode 100644 index 000000000..e141ad109 --- /dev/null +++ b/src/core/features/courses/services/handlers/enrol-push-click.ts @@ -0,0 +1,91 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Params } from '@angular/router'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; +import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; +import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; + +/** + * Handler for enrol push notifications clicks. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotificationsClickHandler { + + name = 'CoreCoursesEnrolPushClickHandler'; + priority = 200; + + /** + * Check if a notification click is handled by this handler. + * + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler + */ + async handles(notification: CorePushNotificationsNotificationBasicData): Promise { + return CoreUtils.instance.isTrueOrOne(notification.notif) && notification.moodlecomponent?.indexOf('enrol_') === 0 && + notification.name == 'expiry_notification'; + } + + /** + * Handle the notification click. + * + * @param notification The notification to check. + * @return Promise resolved when done. + */ + async handleClick(notification: CoreCoursesEnrolNotificationData): Promise { + const courseId = notification.courseid; + + const modal = await CoreDomUtils.instance.showModalLoading(); + + try { + const result = await CoreCourseHelper.instance.getCourse(courseId, notification.site); + + const params: Params = { + course: result.course, + }; + let page: string; + + if (notification.contexturl?.indexOf('user/index.php') != -1) { + // Open the participants tab. + page = 'course'; + params.selectedTab = 'user_participants'; // @todo: Set this when participants is done. + } else if (result.enrolled) { + // User is still enrolled, open the course. + page = 'course'; + } else { + // User not enrolled anymore, open the preview page. + page = 'courses/preview';; + } + + await CoreNavigator.instance.navigateToSitePath(page, { params, siteId: notification.site }); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting course.'); + } finally { + modal.dismiss(); + } + } + +} + +export class CoreCoursesEnrolPushClickHandler extends makeSingleton(CoreCoursesEnrolPushClickHandlerService) {} + +type CoreCoursesEnrolNotificationData = CorePushNotificationsNotificationBasicData & { + courseid: number; // Course ID related to the notification. + contexturl?: string; // Context URL related to the notification. +}; diff --git a/src/core/features/courses/services/handlers/my-courses-home.ts b/src/core/features/courses/services/handlers/my-courses-home.ts index 43796c803..4cfd9a9a4 100644 --- a/src/core/features/courses/services/handlers/my-courses-home.ts +++ b/src/core/features/courses/services/handlers/my-courses-home.ts @@ -17,6 +17,7 @@ import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; import { CoreSiteHome } from '@features/sitehome/services/sitehome'; import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; import { CoreCoursesDashboard } from '../dashboard'; /** @@ -46,6 +47,12 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH * @return Whether or not the handler is enabled on a site level. */ async isEnabledForSite(siteId?: string): Promise { + const disabled = await CoreCourses.instance.isMyCoursesDisabled(siteId); + + if (disabled) { + return false; + } + const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); return !CoreBlockDelegate.instance.hasSupportedBlock(blocks)&& !CoreSiteHome.instance.isAvailable(siteId); diff --git a/src/core/features/courses/services/handlers/request-push-click.ts b/src/core/features/courses/services/handlers/request-push-click.ts new file mode 100644 index 000000000..237573dbd --- /dev/null +++ b/src/core/features/courses/services/handlers/request-push-click.ts @@ -0,0 +1,103 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Params } from '@angular/router'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; +import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; + + +/** + * Handler for course request push notifications clicks. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesRequestPushClickHandlerService implements CorePushNotificationsClickHandler { + + name = 'CoreCoursesRequestPushClickHandler'; + priority = 200; + + /** + * Check if a notification click is handled by this handler. + * + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler + */ + async handles(notification: CorePushNotificationsNotificationBasicData): Promise { + // Don't support 'courserequestrejected', that way the app will open the notifications page. + return CoreUtils.instance.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' && + (notification.name == 'courserequested' || notification.name == 'courserequestapproved'); + } + + /** + * Handle the notification click. + * + * @param notification The notification to check. + * @return Promise resolved when done. + */ + async handleClick(notification: CoreCoursesRequestNotificationData): Promise { + const courseId = notification.courseid; + + if (notification.name == 'courserequested') { + // Feature not supported in the app, open in browser. + const site = await CoreSites.instance.getSite(notification.site); + const url = CoreTextUtils.instance.concatenatePaths(site.getURL(), 'course/pending.php'); + + await site.openInBrowserWithAutoLogin(url); + + return; + } + + // Open the course. + const modal = await CoreDomUtils.instance.showModalLoading(); + + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses(notification.site)); + + try { + const result = await CoreCourseHelper.instance.getCourse(courseId, notification.site); + const params: Params = { + course: result.course, + }; + let page: string; + + if (result.enrolled) { + // User is still enrolled, open the course. + page = 'course'; + } else { + // User not enrolled (shouldn't happen), open the preview page. + page = 'courses/preview'; + } + + await CoreNavigator.instance.navigateToSitePath(page, { params, siteId: notification.site }); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting course.'); + } finally { + modal.dismiss(); + } + } + +} + +export class CoreCoursesRequestPushClickHandler extends makeSingleton(CoreCoursesRequestPushClickHandlerService) {} + +type CoreCoursesRequestNotificationData = CorePushNotificationsNotificationBasicData & { + courseid: number; // Course ID related to the notification. +}; diff --git a/src/core/features/mainmenu/services/handlers/mainmenu.ts b/src/core/features/mainmenu/services/handlers/mainmenu.ts index a530dafa0..17226d49c 100644 --- a/src/core/features/mainmenu/services/handlers/mainmenu.ts +++ b/src/core/features/mainmenu/services/handlers/mainmenu.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { makeSingleton } from '@singletons'; +import { CoreMainMenuHomeDelegate } from '../home-delegate'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../mainmenu-delegate'; /** @@ -24,7 +25,7 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { static readonly PAGE_NAME = 'home'; - name = 'CoreMainMenuHome'; + name = 'CoreHome'; priority = 1100; /** @@ -39,13 +40,10 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { /** * Check if the handler is enabled on a certain site. * - * @param siteId Site ID. If not defined, current site. * @return Whether or not the handler is enabled on a site level. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async isEnabledForSite(siteId?: string): Promise { - // @todo - return true; + async isEnabledForSite(): Promise { + return CoreMainMenuHomeDelegate.instance.getHandlers().length > 0; } /** @@ -55,7 +53,7 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { */ getDisplayData(): CoreMainMenuHandlerData { return { - icon: 'fa-home', + icon: 'fas-home', title: 'core.mainmenu.home', page: CoreMainMenuHomeHandlerService.PAGE_NAME, // @todo: subPage? The page can change due to core-tabs. From 66f64c02dcc1cebba4d8f715b743e730184b0523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 4 Feb 2021 14:39:32 +0100 Subject: [PATCH 2/4] MOBILE-3660 core: Remove and mark some unused params --- src/addons/messages/pages/settings/settings.page.ts | 1 - src/addons/messages/pages/settings/settings.scss | 10 ---------- .../contentlinks/services/contentlinks-helper.ts | 4 ++-- .../features/course/classes/main-resource-component.ts | 3 ++- src/core/features/grades/services/grades-helper.ts | 7 ++----- .../features/grades/services/handlers/user-link.ts | 4 ++-- src/core/features/login/services/login-helper.ts | 5 ++--- src/core/singletons/window.ts | 10 +++++----- 8 files changed, 15 insertions(+), 29 deletions(-) delete mode 100644 src/addons/messages/pages/settings/settings.scss diff --git a/src/addons/messages/pages/settings/settings.page.ts b/src/addons/messages/pages/settings/settings.page.ts index 4e540d70d..0fe82855e 100644 --- a/src/addons/messages/pages/settings/settings.page.ts +++ b/src/addons/messages/pages/settings/settings.page.ts @@ -34,7 +34,6 @@ import { IonRefresher } from '@ionic/angular'; @Component({ selector: 'page-addon-messages-settings', templateUrl: 'settings.html', - styleUrls: ['settings.scss'], }) export class AddonMessagesSettingsPage implements OnInit, OnDestroy { diff --git a/src/addons/messages/pages/settings/settings.scss b/src/addons/messages/pages/settings/settings.scss deleted file mode 100644 index b0fc90bb0..000000000 --- a/src/addons/messages/pages/settings/settings.scss +++ /dev/null @@ -1,10 +0,0 @@ -:host { - .list-header { - margin-bottom: 0; - border-top: 0; - } - - .toggle { - display: inline-block; - } -} diff --git a/src/core/features/contentlinks/services/contentlinks-helper.ts b/src/core/features/contentlinks/services/contentlinks-helper.ts index a198a1fa9..f0d19002b 100644 --- a/src/core/features/contentlinks/services/contentlinks-helper.ts +++ b/src/core/features/contentlinks/services/contentlinks-helper.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { NavController } from '@ionic/angular'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate'; @@ -83,6 +82,7 @@ export class CoreContentLinksHelperProvider { * Goes to a certain page in a certain site. If the site is current site it will perform a regular navigation, * otherwise it will 'redirect' to the other site. * + * @param navCtrlUnused Deprecated param. * @param pageName Name of the page to go. * @param pageParams Params to send to the page. * @param siteId Site ID. If not defined, current site. @@ -90,7 +90,7 @@ export class CoreContentLinksHelperProvider { * @return Promise resolved when done. * @deprecated since 3.9.5. Use CoreNavigator.navigateToSitePath instead. */ - async goInSite(navCtrl: NavController, pageName: string, pageParams: Params, siteId?: string): Promise { + async goInSite(navCtrlUnused: unknown, pageName: string, pageParams: Params, siteId?: string): Promise { await CoreNavigator.instance.navigateToSitePath(pageName, { params: pageParams, siteId }); } diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index 9e802aea1..7d838a22e 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -223,7 +223,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, * Go to blog posts. */ async gotoBlog(): Promise { - // @todo return this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id }); + // const params: Params = { cmId: this.module?.id }; + // @todo return CoreNavigator.instance.navigateToSitePath('AddonBlogEntriesPage', { params }); } /** diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts index 0bb36e7ee..5fa221650 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { NavController } from '@ionic/angular'; import { CoreLogger } from '@singletons/logger'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; @@ -440,7 +439,6 @@ export class CoreGradesHelperProvider { * @param courseId Course ID to view. * @param userId User to view. If not defined, current user. * @param moduleId Module to view. If not defined, view all course grades. - * @param navCtrl NavController to use. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ @@ -448,11 +446,10 @@ export class CoreGradesHelperProvider { courseId: number, userId?: number, moduleId?: number, - navCtrl?: NavController, siteId?: string, ): Promise { const modal = await CoreDomUtils.instance.showModalLoading(); - let currentUserId; + let currentUserId: number; try { const site = await CoreSites.instance.getSite(siteId); @@ -504,7 +501,7 @@ export class CoreGradesHelperProvider { } // View own grades. Check if we already are in the course index page. - if (CoreCourse.instance.currentViewIsCourse(navCtrl, courseId)) { + if (CoreCourse.instance.currentViewIsCourse(courseId)) { // Current view is this course, just select the grades tab. CoreCourse.instance.selectCourseTab('CoreGrades'); diff --git a/src/core/features/grades/services/handlers/user-link.ts b/src/core/features/grades/services/handlers/user-link.ts index e6a3116d5..96ddd29e8 100644 --- a/src/core/features/grades/services/handlers/user-link.ts +++ b/src/core/features/grades/services/handlers/user-link.ts @@ -50,11 +50,11 @@ export class CoreGradesUserLinkHandlerService extends CoreContentLinksHandlerBas data = data || {}; return [{ - action: (siteId, navCtrl?): void => { + action: (siteId): void => { const userId = params.userid && parseInt(params.userid, 10); const moduleId = data?.cmid && parseInt(data.cmid, 10) || undefined; - CoreGradesHelper.instance.goToGrades(courseId!, userId, moduleId, navCtrl, siteId); + CoreGradesHelper.instance.goToGrades(courseId!, userId, moduleId, siteId); }, }]; } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index d8dbf4db6..0004cb0ac 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -14,7 +14,6 @@ import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; -import { NavController } from '@ionic/angular'; import { Md5 } from 'ts-md5/dist/md5'; import { CoreApp, CoreStoreConfig } from '@services/app'; @@ -447,7 +446,7 @@ export class CoreLoginHelperProvider { /** * Go to the initial page of a site depending on 'userhomepage' setting. * - * @param navCtrl NavController to use. Defaults to app root NavController. + * @param navCtrlUnused Deprecated param. * @param page Name of the page to load after loading the main page. * @param params Params to pass to the page. * @param options Navigation options. @@ -456,7 +455,7 @@ export class CoreLoginHelperProvider { * @deprecated since 3.9.5. Use CoreNavigator.navigateToSiteHome or CoreNavigator.navigateToSitePath instead. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - async goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: any, url?: string): Promise { + async goToSiteInitialPage(navCtrlUnused?: unknown, page?: string, params?: any, options?: any, url?: string): Promise { await CoreNavigator.instance.navigateToSiteHome({ ...options, params: { diff --git a/src/core/singletons/window.ts b/src/core/singletons/window.ts index 593cbcea6..6932347a8 100644 --- a/src/core/singletons/window.ts +++ b/src/core/singletons/window.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; import { NavController } from '@ionic/angular'; import { CoreFileHelper } from '@services/file-helper'; @@ -21,6 +22,8 @@ import { CoreUtils } from '@services/utils/utils'; /** * Options for the open function. + * + * @deprecated since 3.9.5 */ export type CoreWindowOpenOptions = { /** @@ -46,10 +49,9 @@ export class CoreWindow { * * @param url URL to open. * @param name Name of the browsing context into which to load the URL. - * @param options Other options. * @return Promise resolved when done. */ - static async open(url: string, name?: string, options?: CoreWindowOpenOptions): Promise { + static async open(url: string, name?: string): Promise { if (CoreUrlUtils.instance.isLocalFileUrl(url)) { const filename = url.substr(url.lastIndexOf('/') + 1); @@ -64,13 +66,11 @@ export class CoreWindow { await CoreUtils.instance.openFile(url); } else { let treated = false; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options = options || {}; if (name != '_system') { // Check if it can be opened in the app. treated = false; - // @todo await CoreContentLinksHelper.instance.handleLink(url, undefined, options.navCtrl, true, true); + await CoreContentLinksHelper.instance.handleLink(url, undefined, true, true); } if (!treated) { From 67e3021e54cde527749c9e8b4859a5ce7b3240b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 4 Feb 2021 15:13:59 +0100 Subject: [PATCH 3/4] MOBILE-3660 core: Create and use popover controller singleton --- src/addons/calendar/pages/day/day.page.ts | 10 ++++------ src/addons/calendar/pages/index/index.page.ts | 7 +++---- src/addons/calendar/pages/list/list.page.ts | 10 ++++------ .../context-menu/context-menu-popover.ts | 6 +++--- src/core/components/context-menu/context-menu.ts | 6 ++---- .../course-options-menu/course-options-menu.ts | 8 ++------ .../components/course-progress/course-progress.ts | 9 ++------- .../features/courses/services/courses-helper.ts | 15 --------------- src/core/singletons/index.ts | 2 ++ 9 files changed, 22 insertions(+), 51 deletions(-) diff --git a/src/addons/calendar/pages/day/day.page.ts b/src/addons/calendar/pages/day/day.page.ts index d6b4cd8d6..80d7657bd 100644 --- a/src/addons/calendar/pages/day/day.page.ts +++ b/src/addons/calendar/pages/day/day.page.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy } from '@angular/core'; -import { PopoverController, IonRefresher } from '@ionic/angular'; +import { IonRefresher } from '@ionic/angular'; import { CoreApp } from '@services/app'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreLocalNotifications } from '@services/local-notifications'; @@ -35,7 +35,7 @@ import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import moment from 'moment'; -import { Network, NgZone } from '@singletons'; +import { Network, NgZone, PopoverController } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { Params } from '@angular/router'; import { Subscription } from 'rxjs'; @@ -100,9 +100,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { category: true, }; - constructor( - private popoverCtrl: PopoverController, - ) { + constructor() { this.currentSiteId = CoreSites.instance.getCurrentSiteId(); if (CoreLocalNotifications.instance.isAvailable()) { @@ -537,7 +535,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * @param event Event. */ async openFilter(event: MouseEvent): Promise { - const popover = await this.popoverCtrl.create({ + const popover = await PopoverController.instance.create({ component: AddonCalendarFilterPopoverComponent, componentProps: { courses: this.courses, diff --git a/src/addons/calendar/pages/index/index.page.ts b/src/addons/calendar/pages/index/index.page.ts index fbe1e8319..c5fdf8e5f 100644 --- a/src/addons/calendar/pages/index/index.page.ts +++ b/src/addons/calendar/pages/index/index.page.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { PopoverController, IonRefresher } from '@ionic/angular'; +import { IonRefresher } from '@ionic/angular'; import { CoreApp } from '@services/app'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; @@ -23,7 +23,7 @@ import { AddonCalendar, AddonCalendarProvider, AddonCalendarUpdatedEventEvent } import { AddonCalendarOffline } from '../../services/calendar-offline'; import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync'; import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper'; -import { Network, NgZone } from '@singletons'; +import { Network, NgZone, PopoverController } from '@singletons'; import { Subscription } from 'rxjs'; import { CoreEnrolledCourseData } from '@features/courses/services/courses'; import { ActivatedRoute, Params } from '@angular/router'; @@ -83,7 +83,6 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { }; constructor( - protected popoverCtrl: PopoverController, protected route: ActivatedRoute, ) { this.currentSiteId = CoreSites.instance.getCurrentSiteId(); @@ -341,7 +340,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { * @param event Event. */ async openFilter(event: MouseEvent): Promise { - const popover = await this.popoverCtrl.create({ + const popover = await PopoverController.instance.create({ component: AddonCalendarFilterPopoverComponent, componentProps: { courses: this.courses, diff --git a/src/addons/calendar/pages/list/list.page.ts b/src/addons/calendar/pages/list/list.page.ts index a0142b4a0..3e2dbb71c 100644 --- a/src/addons/calendar/pages/list/list.page.ts +++ b/src/addons/calendar/pages/list/list.page.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; -import { PopoverController, IonContent, IonRefresher } from '@ionic/angular'; +import { IonContent, IonRefresher } from '@ionic/angular'; import { AddonCalendarProvider, AddonCalendar, @@ -36,7 +36,7 @@ import { CoreConstants } from '@/core/constants'; import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import { Params } from '@angular/router'; import { Subscription } from 'rxjs'; -import { Network, NgZone } from '@singletons'; +import { Network, NgZone, PopoverController } from '@singletons'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; @@ -101,9 +101,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { category: true, }; - constructor( - private popoverCtrl: PopoverController, - ) { + constructor() { this.siteHomeId = CoreSites.instance.getCurrentSiteHomeId(); this.notificationsEnabled = CoreLocalNotifications.instance.isAvailable(); @@ -624,7 +622,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { * @param event Event. */ async openFilter(event: MouseEvent): Promise { - const popover = await this.popoverCtrl.create({ + const popover = await PopoverController.instance.create({ component: AddonCalendarFilterPopoverComponent, componentProps: { courses: this.courses, diff --git a/src/core/components/context-menu/context-menu-popover.ts b/src/core/components/context-menu/context-menu-popover.ts index 89e3861df..e6e6c5e7e 100644 --- a/src/core/components/context-menu/context-menu-popover.ts +++ b/src/core/components/context-menu/context-menu-popover.ts @@ -13,7 +13,8 @@ // limitations under the License. import { Component } from '@angular/core'; -import { NavParams, PopoverController } from '@ionic/angular'; +import { NavParams } from '@ionic/angular'; +import { PopoverController } from '@singletons'; import { CoreContextMenuItemComponent } from './context-menu-item'; /** @@ -32,7 +33,6 @@ export class CoreContextMenuPopoverComponent { constructor( navParams: NavParams, - protected popoverCtrl: PopoverController, ) { this.title = navParams.get('title'); this.items = navParams.get('items') || []; @@ -43,7 +43,7 @@ export class CoreContextMenuPopoverComponent { * Close the popover. */ closeMenu(item?: CoreContextMenuItemComponent): void { - this.popoverCtrl.dismiss(item); + PopoverController.instance.dismiss(item); } /** diff --git a/src/core/components/context-menu/context-menu.ts b/src/core/components/context-menu/context-menu.ts index bfcacec1f..4deb00e33 100644 --- a/src/core/components/context-menu/context-menu.ts +++ b/src/core/components/context-menu/context-menu.ts @@ -15,10 +15,9 @@ import { Component, Input, OnInit, OnDestroy, ElementRef } from '@angular/core'; import { Subject } from 'rxjs'; import { auditTime } from 'rxjs/operators'; -import { PopoverController } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; -import { Translate } from '@singletons'; +import { PopoverController, Translate } from '@singletons'; import { CoreContextMenuItemComponent } from './context-menu-item'; import { CoreContextMenuPopoverComponent } from './context-menu-popover'; @@ -47,7 +46,6 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { constructor( - protected popoverCtrl: PopoverController, elementRef: ElementRef, ) { // Create the stream and subscribe to it. We ignore successive changes during 250ms. @@ -179,7 +177,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { */ async showContextMenu(event: MouseEvent): Promise { if (!this.expanded) { - const popover = await this.popoverCtrl.create( + const popover = await PopoverController.instance.create( { event, component: CoreContextMenuPopoverComponent, diff --git a/src/core/features/courses/components/course-options-menu/course-options-menu.ts b/src/core/features/courses/components/course-options-menu/course-options-menu.ts index a767b5919..a25ffe83b 100644 --- a/src/core/features/courses/components/course-options-menu/course-options-menu.ts +++ b/src/core/features/courses/components/course-options-menu/course-options-menu.ts @@ -13,10 +13,10 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; -import { PopoverController } from '@ionic/angular'; import { CoreCourses } from '../../services/courses'; import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper'; import { CorePrefetchStatusInfo } from '@features/course/services/course-helper'; +import { PopoverController } from '@singletons'; /** * This component is meant to display a popover with the course options. @@ -32,10 +32,6 @@ export class CoreCoursesCourseOptionsMenuComponent implements OnInit { downloadCourseEnabled = false; - constructor( - protected popoverController: PopoverController, - ) { } - /** * Component being initialized. */ @@ -49,7 +45,7 @@ export class CoreCoursesCourseOptionsMenuComponent implements OnInit { * @param action Action name to take. */ action(action: string): void { - this.popoverController.dismiss(action); + PopoverController.instance.dismiss(action); } } diff --git a/src/core/features/courses/components/course-progress/course-progress.ts b/src/core/features/courses/components/course-progress/course-progress.ts index 54f9755fd..968fe2f80 100644 --- a/src/core/features/courses/components/course-progress/course-progress.ts +++ b/src/core/features/courses/components/course-progress/course-progress.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Component, Input, OnInit, OnDestroy } from '@angular/core'; -import { PopoverController } from '@ionic/angular'; import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -21,7 +20,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreCourses } from '@features/courses/services/courses'; import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; -import { Translate } from '@singletons'; +import { PopoverController, Translate } from '@singletons'; import { CoreConstants } from '@/core/constants'; import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper'; import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu'; @@ -62,10 +61,6 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { protected courseStatusObserver?: CoreEventObserver; protected siteUpdatedObserver?: CoreEventObserver; - constructor( - protected popoverCtrl: PopoverController, - ) { } - /** * Component being initialized. */ @@ -204,7 +199,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { e.preventDefault(); e.stopPropagation(); - const popover = await this.popoverCtrl.create({ + const popover = await PopoverController.instance.create({ component: CoreCoursesCourseOptionsMenuComponent, componentProps: { course: this.course, diff --git a/src/core/features/courses/services/courses-helper.ts b/src/core/features/courses/services/courses-helper.ts index 85633923b..b0b826698 100644 --- a/src/core/features/courses/services/courses-helper.ts +++ b/src/core/features/courses/services/courses-helper.ts @@ -13,14 +13,12 @@ // limitations under the License. import { Injectable } from '@angular/core'; -// import { PopoverController } from '@ionic/angular'; import { CoreUtils } from '@services/utils/utils'; import { CoreSites } from '@services/sites'; import { CoreCourses, CoreCourseSearchedData, CoreCourseUserAdminOrNavOptionIndexed, CoreEnrolledCourseData } from './courses'; import { makeSingleton, Translate } from '@singletons'; import { CoreWSExternalFile } from '@services/ws'; import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion'; -// import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover'; /** * Helper to gather some common courses functions. @@ -276,19 +274,6 @@ export class CoreCoursesHelperProvider { })); } - /** - * Show a context menu to select a course, and return the courseId and categoryId of the selected course (-1 for all courses). - * Returns an empty object if popover closed without picking a course. - * - * @param event Click event. - * @param courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover. - * @param courseId The course to select at start. - * @return Promise resolved with the course ID and category ID. - */ - async selectCourse(): Promise { - // @todo params and logic - } - } export class CoreCoursesHelper extends makeSingleton(CoreCoursesHelperProvider) { } diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index 3353eab15..057183385 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -25,6 +25,7 @@ import { GestureController as GestureControllerService, ActionSheetController as ActionSheetControllerService, NavController as NavControllerService, + PopoverController as PopoverControllerService, } from '@ionic/angular'; import { Badge as BadgeService } from '@ionic-native/badge/ngx'; @@ -148,6 +149,7 @@ export class ActionSheetController extends makeSingleton(ActionSheetControllerSe export class AlertController extends makeSingleton(AlertControllerService) {} export class LoadingController extends makeSingleton(LoadingControllerService) {} export class ModalController extends makeSingleton(ModalControllerService) {} +export class PopoverController extends makeSingleton(PopoverControllerService) {} export class ToastController extends makeSingleton(ToastControllerService) {} export class GestureController extends makeSingleton(GestureControllerService) {} export class ApplicationInit extends makeSingleton(ApplicationInitStatus) {} From f42ed2f054ca0955ecf3d1bed86f5216fbc774d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 4 Feb 2021 15:51:03 +0100 Subject: [PATCH 4/4] MOBILE-3660 github: Improve duplicate class detection --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 50b42d8f4..92b23af69 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -18,4 +18,4 @@ jobs: - run: npx tslint -c ionic-migration.json -p tsconfig.json - run: npm run test:ci - run: npm run build:prod - - run: result=$(find src -type f -iname '*.html' -exec grep -E 'class="[^"]+"[^>]+class="' {} \; | wc -l); test $result -eq 0 + - run: result=$(find src -type f -iname '*.html' -exec sh -c 'cat {} | tr "\n" " " | grep -Eo "class=\"[^\"]+\"[^>]+class=\"" ' \; | wc -l); test $result -eq 0