From 6b46f48b3ce1ea714b8896ef713278cdaab1fe8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 24 Jan 2022 17:46:36 +0100 Subject: [PATCH] MOBILE-3915 course: Improve course summary page --- scripts/langindex.json | 1 + src/core/classes/page-transition.ts | 3 + .../components/single-activity.scss | 3 + .../components/singleactivity.ts | 1 + .../course/pages/contents/contents.ts | 4 +- src/core/features/course/pages/index/index.ts | 2 +- .../course/pages/preview/preview.html | 92 ++++++------ .../course/pages/preview/preview.page.ts | 141 +++++++++++------- .../course/pages/preview/preview.scss | 64 ++++---- .../features/course/services/course-helper.ts | 23 +-- src/core/features/course/services/course.ts | 15 +- .../course/services/format-delegate.ts | 12 +- .../services/handlers/default-format.ts | 91 +++-------- .../course-list-item/course-list-item.ts | 2 +- .../courses/services/handlers/course-link.ts | 2 +- src/core/lang.json | 1 + upgrade.txt | 1 + 17 files changed, 233 insertions(+), 225 deletions(-) create mode 100644 src/core/features/course/format/singleactivity/components/single-activity.scss diff --git a/scripts/langindex.json b/scripts/langindex.json index 6d97093db..c2d7b6f89 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -2257,6 +2257,7 @@ "core.strftimetime24": "langconfig", "core.submit": "moodle", "core.success": "moodle", + "core.summary": "moodle", "core.tablet": "local_moodlemobileapp", "core.tag.defautltagcoll": "tag", "core.tag.errorareanotsupported": "local_moodlemobileapp", diff --git a/src/core/classes/page-transition.ts b/src/core/classes/page-transition.ts index 4ae2d83c9..4260515e0 100644 --- a/src/core/classes/page-transition.ts +++ b/src/core/classes/page-transition.ts @@ -93,6 +93,7 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp } rootAnimation.addAnimation(enteringContentAnimation); + enteringContentAnimation.beforeAddClass('animating').afterRemoveClass('animating'); if (backDirection) { enteringContentAnimation @@ -214,6 +215,8 @@ export const moodleTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp // setup leaving view if (leavingEl) { const leavingContent = createAnimation(); + leavingContent.beforeAddClass('animating').afterRemoveClass('animating'); + const leavingContentEl = leavingEl.querySelector(':scope > ion-content'); const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar'); const leavingHeaderEls = leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'); diff --git a/src/core/features/course/format/singleactivity/components/single-activity.scss b/src/core/features/course/format/singleactivity/components/single-activity.scss new file mode 100644 index 000000000..1352a5254 --- /dev/null +++ b/src/core/features/course/format/singleactivity/components/single-activity.scss @@ -0,0 +1,3 @@ +:host ::ng-deep .collapsible-title { + display: none; +} diff --git a/src/core/features/course/format/singleactivity/components/singleactivity.ts b/src/core/features/course/format/singleactivity/components/singleactivity.ts index 01da69058..b3d11a9d7 100644 --- a/src/core/features/course/format/singleactivity/components/singleactivity.ts +++ b/src/core/features/course/format/singleactivity/components/singleactivity.ts @@ -31,6 +31,7 @@ import { CoreCourse } from '@features/course/services/course'; @Component({ selector: 'core-course-format-single-activity', templateUrl: 'core-course-format-single-activity.html', + styleUrls: ['single-activity.scss'], }) export class CoreCourseFormatSingleActivityComponent implements OnChanges { diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index a4de288f3..199599a4c 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -384,8 +384,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { */ openCourseSummary(): void { CoreNavigator.navigateToSitePath( - '/course/' + this.course.id + '/preview', - { params: { course: this.course } }, + `/course/${this.course.id}/preview`, + { params: { course: this.course, avoidOpenCourse: true } }, ); } diff --git a/src/core/features/course/pages/index/index.ts b/src/core/features/course/pages/index/index.ts index 2ecf4abab..963df1009 100644 --- a/src/core/features/course/pages/index/index.ts +++ b/src/core/features/course/pages/index/index.ts @@ -249,7 +249,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { } CoreNavigator.navigateToSitePath( - '/course/' + this.course.id + '/preview', + `/course/${this.course.id}/preview`, { params: { course: this.course, avoidOpenCourse: true } }, ); } diff --git a/src/core/features/course/pages/preview/preview.html b/src/core/features/course/pages/preview/preview.html index 4969b265b..4cb9bad62 100644 --- a/src/core/features/course/pages/preview/preview.html +++ b/src/core/features/course/pages/preview/preview.html @@ -5,7 +5,7 @@

- + {{'core.course.coursesummary' | translate}}

@@ -16,44 +16,49 @@
-
+
- - + -

- - -

+

+ + +

{{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }} - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}

+
+ + +
+

+ {{'core.summary' | translate}} +

- - + +

{{ 'core.teachers' | translate }}

- @@ -62,7 +67,7 @@ -
+ @@ -83,7 +88,8 @@ -
+ +

{{ instance.name }}

@@ -92,23 +98,24 @@
-
- - -

{{ 'core.courses.paypalaccepted' | translate }}

-

{{ 'core.paymentinstant' | translate }}

- - {{ 'core.courses.sendpaymentbutton' | translate }} - -
-
- - -

{{ 'core.courses.notenrollable' | translate }}

-
-
- + + +

{{ 'core.courses.paypalaccepted' | translate }}

+

{{ 'core.paymentinstant' | translate }}

+ + {{ 'core.courses.sendpaymentbutton' | translate }} + +
+
+ + +

{{ 'core.courses.notenrollable' | translate }}

+
+
+ + + @@ -116,23 +123,24 @@ [name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status"> - -

{{ 'core.course.downloadcourse' | translate }}

-

{{ 'core.course.refreshcourse' | translate }}

-
-
- + {{ 'core.course.downloadcourse' | translate }} + {{ 'core.course.refreshcourse' | translate }} + + + -

{{ 'core.course' | translate }}

+ {{ 'core.course' | translate }}
-
- + + + -

{{ 'core.openinbrowser' | translate }}

+ {{ 'core.openinbrowser' | translate }}
-
+ +
diff --git a/src/core/features/course/pages/preview/preview.page.ts b/src/core/features/course/pages/preview/preview.page.ts index 923eb7109..8c1f9b9ce 100644 --- a/src/core/features/course/pages/preview/preview.page.ts +++ b/src/core/features/course/pages/preview/preview.page.ts @@ -20,8 +20,8 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { + CoreCourseCustomField, CoreCourseEnrolmentMethod, - CoreCourseGetCoursesData, CoreCourses, CoreCourseSearchedData, CoreCoursesProvider, @@ -34,6 +34,8 @@ import { Translate } from '@singletons'; import { CoreConstants } from '@/core/constants'; import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password'; import { CoreNavigator } from '@services/navigator'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; /** * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. @@ -45,12 +47,13 @@ import { CoreNavigator } from '@services/navigator'; }) export class CoreCoursePreviewPage implements OnInit, OnDestroy { - course?: CoreCourseSearchedData; + course?: CoreCourseSummaryData; isEnrolled = false; canAccessCourse = true; selfEnrolInstances: CoreCourseEnrolmentMethod[] = []; paypalEnabled = false; dataLoaded = false; + avoidOpenCourse = false; prefetchCourseData: CorePrefetchStatusInfo = { icon: '', statusTranslatable: 'core.loading', @@ -64,6 +67,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { courseUrl = ''; courseImageUrl?: string; isMobile: boolean; + progress?: number; protected isGuestEnabled = false; protected useGuestAccess = false; @@ -74,6 +78,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { protected paypalReturnUrl = ''; protected pageDestroyed = false; protected courseStatusObserver?: CoreEventObserver; + protected courseId!: number; constructor( protected zone: NgZone, @@ -84,7 +89,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { if (this.downloadCourseEnabled) { // Listen for status change in course. this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => { - if (data.courseId == this.course!.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { + if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { this.updateCourseStatus(data.status); } }, CoreSites.getCurrentSiteId()); @@ -92,27 +97,25 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { } /** - * View loaded. + * @inheritdoc */ async ngOnInit(): Promise { - this.course = CoreNavigator.getRouteParam('course'); - - if (!this.course) { + try { + this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + } catch (error) { + CoreDomUtils.showErrorModal(error); CoreNavigator.back(); return; } - const currentSite = CoreSites.getCurrentSite(); - const currentSiteUrl = currentSite && currentSite.getURL(); + this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse'); + this.course = CoreNavigator.getRouteParam('course'); - this.paypalEnabled = this.course!.enrollmentmethods?.indexOf('paypal') > -1; - this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/index.php?id=' + this.course!.id); - this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'course/view.php?id=' + this.course!.id); - this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl!, 'enrol/paypal/return.php'); - if (this.course.overviewfiles.length > 0) { - this.courseImageUrl = this.course.overviewfiles[0].fileurl; - } + const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL(); + this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); + this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); + this.paypalReturnUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/paypal/return.php'); try { await this.getCourse(); @@ -120,11 +123,11 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { if (this.downloadCourseEnabled) { // Determine course prefetch icon. - this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.course!.id); + this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId); if (this.prefetchCourseData.loading) { // Course is being downloaded. Get the download promise. - const promise = CoreCourseHelper.getCourseDownloadPromise(this.course!.id); + const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId); if (promise) { // There is a download promise. If it fails, show an error. promise.catch((error) => { @@ -134,7 +137,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { }); } else { // No download, this probably means that the app was closed while downloading. Set previous status. - CoreCourse.setCoursePreviousStatus(this.course!.id); + CoreCourse.setCoursePreviousStatus(this.courseId); } } } @@ -177,13 +180,15 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { this.selfEnrolInstances = []; try { - this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.course!.id); + this.enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(this.courseId); this.enrolmentMethods.forEach((method) => { if (method.type === 'self') { this.selfEnrolInstances.push(method); } else if (method.type === 'guest') { this.isGuestEnabled = true; + } else if (method.type === 'paypal') { + this.paypalEnabled = true; } }); } catch (error) { @@ -191,22 +196,17 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { } try { - let course: CoreEnrolledCourseData | CoreCourseGetCoursesData; - // Check if user is enrolled in the course. try { - course = await CoreCourses.getUserCourse(this.course!.id); + this.course = await CoreCourses.getUserCourse(this.courseId); this.isEnrolled = true; } catch { // The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course. this.isEnrolled = false; - - course = await CoreCourses.getCourse(this.course!.id); + this.course = await CoreCourses.getCourse(this.courseId); } // Success retrieving the course, we can assume the user has permissions to view it. - this.course!.fullname = course.fullname || this.course!.fullname; - this.course!.summary = course.summary || this.course!.summary; this.canAccessCourse = true; this.useGuestAccess = false; } catch { @@ -219,14 +219,37 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { } } - if (!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) { - try { - const course = await CoreCourses.getCourseByField('id', this.course!.id); + if (this.course && 'overviewfiles' in this.course && this.course.overviewfiles?.length) { + this.courseImageUrl = this.course.overviewfiles[0].fileurl; + } - this.course!.customfields = course.customfields; - } catch { - // Ignore errors. + try { + const courseByField = await CoreCourses.getCourseByField('id', this.courseId); + if (this.course) { + this.course.customfields = courseByField.customfields; + this.course.contacts = courseByField.contacts; + this.course.displayname = courseByField.displayname; + this.course.categoryname = courseByField.categoryname; + this.course.overviewfiles = courseByField.overviewfiles; + } else { + this.course = courseByField; } + + this.paypalEnabled = !this.isEnrolled && courseByField.enrollmentmethods?.indexOf('paypal') > -1; + + } catch { + // Ignore errors. + } + + if (!this.course || + !('progress' in this.course) || + typeof this.course.progress !== 'number' || + this.course.progress < 0 || + this.course.completionusertracked === false + ) { + this.progress = undefined; + } else { + this.progress = this.course.progress; } this.dataLoaded = true; @@ -234,13 +257,15 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { /** * Open the course. + * + * @param replaceCurrentPage If current place should be replaced in the navigation stack. */ - openCourse(): void { - if (!this.canAccessCourse) { + openCourse(replaceCurrentPage = false): void { + if (!this.canAccessCourse || !this.course || this.avoidOpenCourse) { return; } - CoreCourseHelper.openCourse(this.course!, { isGuest: this.useGuestAccess }); + CoreCourseHelper.openCourse(this.course, { params: { isGuest: this.useGuestAccess }, replace: replaceCurrentPage }); } /** @@ -279,7 +304,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { }; // Open the enrolment page in InAppBrowser. - const window = await CoreSites.getCurrentSite()!.openInAppWithAutoLogin(this.enrolUrl); + const window = await CoreSites.getRequiredCurrentSite().openInAppWithAutoLogin(this.enrolUrl); // Observe loaded pages in the InAppBrowser to check if the enrol process has ended. const inAppLoadSubscription = window.on('loadstart').subscribe((event) => { @@ -319,7 +344,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { const modal = await CoreDomUtils.showModalLoading('core.loading', true); try { - await CoreCourses.selfEnrol(this.course!.id, password, instanceId); + await CoreCourses.selfEnrol(this.courseId, password, instanceId); // Close modal and refresh data. this.isEnrolled = true; @@ -331,13 +356,13 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { await this.refreshData().finally(() => { // My courses have been updated, trigger event. CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, { - courseId: this.course!.id, + courseId: this.courseId, course: this.course, action: CoreCoursesProvider.ACTION_ENROL, }, CoreSites.getCurrentSiteId()); }); - this.openCourse(); + this.openCourse(true); modal?.dismiss(); } catch (error) { @@ -378,12 +403,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { const promises: Promise[] = []; promises.push(CoreCourses.invalidateUserCourses()); - promises.push(CoreCourses.invalidateCourse(this.course!.id)); - promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.course!.id)); - promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.course!.id)); - if (CoreSites.getCurrentSite() && !CoreSites.getCurrentSite()!.isVersionGreaterEqualThan('3.7')) { - promises.push(CoreCourses.invalidateCoursesByField('id', this.course!.id)); - } + promises.push(CoreCourses.invalidateCourse(this.courseId)); + promises.push(CoreCourses.invalidateCourseEnrolmentMethods(this.courseId)); + promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(this.courseId)); + promises.push(CoreCourses.invalidateCoursesByField('id', this.courseId)); if (this.guestInstanceId) { promises.push(CoreCourses.invalidateCourseGuestEnrolmentInfo(this.guestInstanceId)); } @@ -419,14 +442,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { } // Check if user is enrolled in the course. - try { - CoreCourses.invalidateUserCourses(); - } catch { - // Ignore errors. - } + await CoreUtils.ignoreErrors(CoreCourses.invalidateUserCourses()); try { - await CoreCourses.getUserCourse(this.course!.id); + await CoreCourses.getUserCourse(this.courseId); } catch { // Not enrolled, wait a bit and try again. if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) { @@ -451,7 +470,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { */ async prefetchCourse(): Promise { try { - await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, { + await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course as CoreEnrolledCourseData, { isGuest: this.useGuestAccess, }); } catch (error) { @@ -462,14 +481,20 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { } /** - * Page destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.pageDestroyed = true; - - if (this.courseStatusObserver) { - this.courseStatusObserver.off(); - } + this.courseStatusObserver?.off(); } } + +type CoreCourseSummaryData = CoreCourseWithImageAndColor & (CoreEnrolledCourseData | CoreCourseSearchedData) & { + contacts?: { // Contact users. + id: number; // Contact user id. + fullname: string; // Contact user fullname. + }[]; + customfields?: CoreCourseCustomField[]; // Custom fields and associated values. + categoryname?: string; // Category name. +}; diff --git a/src/core/features/course/pages/preview/preview.scss b/src/core/features/course/pages/preview/preview.scss index 161f3647a..8f159c333 100644 --- a/src/core/features/course/pages/preview/preview.scss +++ b/src/core/features/course/pages/preview/preview.scss @@ -1,41 +1,43 @@ :host { - --scroll-factor: 0.5; - --translate-z: calc(-2 * var(--scroll-factor))px; - --scale: calc(1 + var(--scroll-factor) * 2); + ion-content:not(.animating) { + &::part(scroll) { + perspective: 1px; + perspective-origin: center top; + transform-style: preserve-3d; + } - perspective: 1px; - perspective-origin: center top; - transform-style: preserve-3d; + .core-course-thumb { + transform-origin: center top; - // @todo This parallax effect caused the image to be scaled during page transitions, - // and in some devices it seems like the problem persisted even after the transition. - // We should decide whether we want to keep this parallax or not, and if we do fix - // the problem or find an alternative implementation. For now, it's disabled. + --scroll-factor: 0.5; + --translate-z: calc(-2 * var(--scroll-factor))px; + --scale: calc(1 + var(--scroll-factor) * 2); + + /** + * Calculated with scroll-factor: 0.5; + * translate-z: -2 * $scroll-factor px; + * scale: 1 + $scroll-factor * 2; + */ + transform: translateZ(-1px) scale(2); + } + } + + .core-course-thumb-parallax-content { + transform: translateZ(0); + -webkit-filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); + filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); + } + + .core-course-thumb-parallax { + height: 40vw; + max-height: 35vh; + z-index: -1; + overflow: hidden; + } - // .core-course-thumb-parallax-content { - // transform: translateZ(0); - // -webkit-filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); - // filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow))); - // } - // .core-course-thumb-parallax { - // height: 40vw; - // max-height: 35vh; - // z-index: -1; - // overflow: hidden; - // } .core-course-thumb { overflow: hidden; text-align: center; - cursor: pointer; - pointer-events: auto; - transform-origin: center top; - - /** - * Calculated with scroll-factor: 0.5; - * translate-z: -2 * $scroll-factor px; - * scale: 1 + $scroll-factor * 2; - */ - // transform: translateZ(-1px) scale(2); } diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 737a405ae..56dee5790 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -68,7 +68,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreSiteHome } from '@features/sitehome/services/sitehome'; -import { CoreNavigator } from '@services/navigator'; +import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home'; import { CoreStatusWithWarningsWSResponse } from '@services/ws'; @@ -1178,7 +1178,7 @@ export class CoreCourseHelperProvider { modal?.dismiss(); - return this.openCourse(course, params, siteId); + return this.openCourse(course, { params , siteId }); } /** @@ -2020,20 +2020,25 @@ export class CoreCourseHelperProvider { * they will see the result immediately. * * @param course Course to open - * @param params Params to pass to the course page. - * @param siteId Site ID. If not defined, current site. + * @param navOptions Navigation options that includes params to pass to the page. * @return Promise resolved when done. */ - async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params, siteId?: string): Promise { + async openCourse( + course: CoreCourseAnyCourseData | { id: number }, + navOptions?: CoreNavigationOptions & { siteId?: string }, + ): Promise { + const siteId = navOptions?.siteId; if (!siteId || siteId == CoreSites.getCurrentSiteId()) { // Current site, we can open the course. - return CoreCourse.openCourse(course, params); + return CoreCourse.openCourse(course, navOptions); } else { // We need to load the site first. - params = params || {}; - Object.assign(params, { course: course }); + navOptions = navOptions || {}; - await CoreNavigator.navigateToSitePath(`course/${course.id}`, { siteId, params }); + navOptions.params = navOptions.params || {}; + Object.assign(navOptions.params, { course: course }); + + await CoreNavigator.navigateToSitePath(`course/${course.id}`, navOptions); } } diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 001452497..fa30f2991 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -43,7 +43,7 @@ import { CoreCourseLogCronHandler } from './handlers/log-cron'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync'; import { CoreTagItem } from '@features/tag/services/tag'; -import { CoreNavigator } from '@services/navigator'; +import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreCourseModuleDelegate } from './module-delegate'; const ROOT_CACHE_KEY = 'mmCourse:'; @@ -1177,10 +1177,13 @@ export class CoreCourseProvider { * This function must be in here instead of course helper to prevent circular dependencies. * * @param course Course to open - * @param params Other params to pass to the course page. + * @param navOptions Navigation options that includes params to pass to the page. * @return Promise resolved when done. */ - async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params): Promise { + async openCourse( + course: CoreCourseAnyCourseData | { id: number }, + navOptions?: CoreNavigationOptions, + ): Promise { const loading = await CoreDomUtils.showModalLoading(); // Wait for site plugins to be fetched. @@ -1197,7 +1200,7 @@ export class CoreCourseProvider { if (!format || !CoreSitePlugins.sitePluginPromiseExists(`format_${format}`)) { // No custom format plugin. We don't need to wait for anything. loading.dismiss(); - await CoreCourseFormatDelegate.openCourse( course, params); + await CoreCourseFormatDelegate.openCourse( course, navOptions); return; } @@ -1208,7 +1211,7 @@ export class CoreCourseProvider { // The format loaded successfully, but the handlers wont be registered until all site plugins have loaded. if (CoreSitePlugins.sitePluginsFinishedLoading) { - return CoreCourseFormatDelegate.openCourse( course, params); + return CoreCourseFormatDelegate.openCourse( course, navOptions); } // Wait for plugins to be loaded. @@ -1217,7 +1220,7 @@ export class CoreCourseProvider { const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => { observer?.off(); - CoreCourseFormatDelegate.openCourse( course, params) + CoreCourseFormatDelegate.openCourse( course, navOptions) .then(deferred.resolve).catch(deferred.reject); }); diff --git a/src/core/features/course/services/format-delegate.ts b/src/core/features/course/services/format-delegate.ts index 967903d58..4bfb14bc7 100644 --- a/src/core/features/course/services/format-delegate.ts +++ b/src/core/features/course/services/format-delegate.ts @@ -13,10 +13,10 @@ // limitations under the License. import { Injectable, Type } from '@angular/core'; -import { Params } from '@angular/router'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; +import { CoreNavigationOptions } from '@services/navigator'; import { makeSingleton } from '@singletons'; import { CoreCourseWSSection } from './course'; import { CoreCourseSection } from './course-helper'; @@ -100,10 +100,10 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Your page should include the course handlers using CoreCoursesDelegate. * * @param course The course to open. It should contain a "format" attribute. - * @param params Params to pass to the course page. + * @param navOptions Navigation options that includes params to pass to the page. * @return Promise resolved when done. */ - openCourse?(course: CoreCourseAnyCourseData, params?: Params): Promise; + openCourse?(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise; /** * Return the Component to use to display the course format instead of using the default one. @@ -323,11 +323,11 @@ export class CoreCourseFormatDelegateService extends CoreDelegate { - await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, params]); + async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise { + await this.executeFunctionOnEnabled(course.format || '', 'openCourse', [course, navOptions]); } /** diff --git a/src/core/features/course/services/handlers/default-format.ts b/src/core/features/course/services/handlers/default-format.ts index 4ef53ca4b..d7254f194 100644 --- a/src/core/features/course/services/handlers/default-format.ts +++ b/src/core/features/course/services/handlers/default-format.ts @@ -13,12 +13,9 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { Params } from '@angular/router'; - import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses'; -import { CoreNavigator } from '@services/navigator'; +import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreUtils } from '@services/utils/utils'; -import { CoreCourseWSSection } from '../course'; import { CoreCourseSection } from '../course-helper'; import { CoreCourseFormatHandler } from '../format-delegate'; @@ -32,19 +29,14 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { format = 'default'; /** - * Whether or not the handler is enabled on a site level. - * - * @return Promise resolved with true if enabled. + * @inheritdoc */ async isEnabled(): Promise { return true; } /** - * Get the title to use in course page. - * - * @param course The course. - * @return Title. + * @inheritdoc */ getCourseTitle(course: CoreCourseAnyCourseData): string { if (course.displayname) { @@ -57,57 +49,35 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { } /** - * Whether it allows seeing all sections at the same time. Defaults to true. - * - * @param course The course to check. - * @return Whether it can view all sections. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - canViewAllSections(course: CoreCourseAnyCourseData): boolean { + canViewAllSections(): boolean { return true; } /** - * Whether the option blocks should be displayed. Defaults to true. - * - * @param course The course to check. - * @return Whether it can display blocks. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - displayBlocks(course: CoreCourseAnyCourseData): boolean { + displayBlocks(): boolean { return true; } /** - * Whether the default section selector should be displayed. Defaults to true. - * - * @param course The course to check. - * @return Whether the default section selector should be displayed. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - displaySectionSelector(course: CoreCourseAnyCourseData): boolean { + displaySectionSelector(): boolean { return true; } /** - * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, - * and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true. - * - * @param course The course to check. - * @param sections List of course sections. - * @return Whether the refresher should be displayed. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): boolean { + displayRefresher(): boolean { return true; } /** - * Given a list of sections, get the "current" section that should be displayed first. - * - * @param course The course to get the title. - * @param sections List of sections. - * @return Current section (or promise resolved with current section). + * @inheritdoc */ async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise { let marker: number | undefined; @@ -137,48 +107,33 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { } /** - * Invalidate the data required to load the course format. - * - * @param course The course to get the title. - * @param sections List of sections. - * @return Promise resolved when the data is invalidated. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async invalidateData(course: CoreCourseAnyCourseData, sections: CoreCourseWSSection[]): Promise { + async invalidateData(course: CoreCourseAnyCourseData): Promise { await CoreCourses.invalidateCoursesByField('id', course.id); } /** - * Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened. - * Implement it only if you want to create your own page to display the course. In general it's better to use the method - * getCourseFormatComponent because it will display the course handlers at the top. - * Your page should include the course handlers using CoreCoursesDelegate. - * - * @param course The course to open. It should contain a "format" attribute. - * @param params Params to pass to the course page. - * @return Promise resolved when done. + * @inheritdoc */ - async openCourse(course: CoreCourseAnyCourseData, params?: Params): Promise { - params = params || {}; - Object.assign(params, { course: course }); + async openCourse(course: CoreCourseAnyCourseData, navOptions?: CoreNavigationOptions): Promise { + navOptions = navOptions || {}; + + navOptions.params = navOptions.params || {}; + Object.assign(navOptions.params, { course: course }); // Don't return the .push promise, we don't want to display a loading modal during the page transition. const currentTab = CoreNavigator.getCurrentMainMenuTab(); const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`); const deepPath = '/deep'.repeat(routeDepth); - CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, { params }); + CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, navOptions); } /** - * Whether the view should be refreshed when completion changes. If your course format doesn't display - * activity completion then you should return false. - * - * @param course The course. - * @return Whether course view should be refreshed when an activity completion changes. + * @inheritdoc */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise { + async shouldRefreshWhenCompletionChanges(): Promise { return true; } diff --git a/src/core/features/courses/components/course-list-item/course-list-item.ts b/src/core/features/courses/components/course-list-item/course-list-item.ts index bf9d4419f..5a5f5fa24 100644 --- a/src/core/features/courses/components/course-list-item/course-list-item.ts +++ b/src/core/features/courses/components/course-list-item/course-list-item.ts @@ -171,7 +171,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On CoreCourseHelper.openCourse(this.course); } else { CoreNavigator.navigateToSitePath( - '/course/' + this.course.id + '/preview', + `/course/${this.course.id}/preview`, { params: { course: this.course } }, ); } diff --git a/src/core/features/courses/services/handlers/course-link.ts b/src/core/features/courses/services/handlers/course-link.ts index 5e1800ddb..5c7347844 100644 --- a/src/core/features/courses/services/handlers/course-link.ts +++ b/src/core/features/courses/services/handlers/course-link.ts @@ -156,7 +156,7 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler modal.dismiss(); // Now open the course. - CoreCourseHelper.openCourse(course, pageParams); + CoreCourseHelper.openCourse(course, { params: pageParams }); } /** diff --git a/src/core/lang.json b/src/core/lang.json index 6690dc4bb..624038e0b 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -301,6 +301,7 @@ "strftimetime24": "%H:%M", "submit": "Submit", "success": "Success", + "summary": "Summary", "tablet": "Tablet", "teachers": "Teachers", "thereisdatatosync": "There are offline {{$a}} to be synchronised.", diff --git a/upgrade.txt b/upgrade.txt index 79ccced1d..e55234e0d 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -15,6 +15,7 @@ information provided here is intended especially for developers. The function CoreUserDelegate.getProfileHandlersFor must now receive a context + contextId instead of a courseId. The user handler function isEnabledForCourse is now called isEnabledForContext and receives a context + contextId instead of a courseId. Some user handler's functions have also changed to accept context + contextId instead of a courseId: isEnabledForUser, getDisplayData, action. +- CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page. === 3.9.5 ===