MOBILE-3686 courses: Filter my courses and add download to categories

main
Pau Ferrer Ocaña 2021-10-14 10:10:30 +02:00
parent ad45289f4b
commit 264309593f
2 changed files with 127 additions and 29 deletions

View File

@ -7,6 +7,16 @@
<core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id"> <core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id">
</core-format-text> </core-format-text>
</h1> </h1>
<ion-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload(!downloadEnabled)"
iconAction="toggle" [toggle]="downloadEnabled"></core-context-menu-item>
<core-context-menu-item *ngIf="myCoursesEnabled" [priority]="900"
[content]="'core.courses.showonlyenrolled' | translate" (action)="toggleEnrolled(!showOnlyEnrolled)"
iconAction="toggle" [toggle]="showOnlyEnrolled"></core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
@ -17,22 +27,18 @@
<ion-item *ngIf="currentCategory" class="ion-text-wrap"> <ion-item *ngIf="currentCategory" class="ion-text-wrap">
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon> <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
<ion-label> <ion-label>
<h2> <p class="item-heading">
<core-format-text [text]="currentCategory!.name" contextLevel="coursecat" <core-format-text [text]="currentCategory.name" contextLevel="coursecat"
[contextInstanceId]="currentCategory!.id"></core-format-text> [contextInstanceId]="currentCategory.id"></core-format-text>
</h2> </p>
</ion-label> <p *ngIf="currentCategory.description">
</ion-item> <core-format-text [text]="currentCategory.description" maxHeight="60" contextLevel="coursecat"
<ion-item class="ion-text-wrap" *ngIf="currentCategory && currentCategory!.description"> [contextInstanceId]="currentCategory.id"></core-format-text>
<ion-label> </p>
<h2>
<core-format-text [text]="currentCategory!.description" maxHeight="60" contextLevel="coursecat"
[contextInstanceId]="currentCategory!.id"></core-format-text>
</h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
<div *ngIf="categories.length > 0"> <ng-container *ngIf="categories.length > 0">
<ion-item-divider> <ion-item-divider>
<ion-label> <ion-label>
<h2>{{ 'core.courses.categories' | translate }}</h2> <h2>{{ 'core.courses.categories' | translate }}</h2>
@ -48,22 +54,24 @@
</core-format-text> </core-format-text>
</h2> </h2>
</ion-label> </ion-label>
<ion-badge slot="end" *ngIf="category.coursecount > 0" color="light"> <ion-badge slot="end" *ngIf="!showOnlyEnrolled && category.coursecount > 0" color="light">
<span aria-hidden="true">{{ category.coursecount }}</span> <span aria-hidden="true">{{ category.coursecount }}</span>
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span> <span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
</section> </section>
</div> </ng-container>
<div *ngIf="courses.length > 0"> <ng-container *ngIf="courses.length > 0">
<ion-item-divider> <ion-item-divider>
<ion-label> <ion-label>
<h2>{{ 'core.courses.courses' | translate }}</h2> <h2 *ngIf="!showOnlyEnrolled">{{ 'core.courses.courses' | translate }}</h2>
<h2 *ngIf="showOnlyEnrolled">{{ 'core.courses.mycourses' | translate }}</h2>
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item> <core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
</div> </core-courses-course-list-item>
</ng-container>
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap" <core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
[message]="'core.courses.nocoursesyet' | translate"> [message]="'core.courses.nocoursesyet' | translate">
</core-empty-box> </core-empty-box>

View File

@ -12,14 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { IonRefresher } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreCategoryData, CoreCourses, CoreCourseSearchedData } from '../../services/courses'; import { CoreCategoryData, CoreCourseListItem, CoreCourses, CoreCoursesProvider } from '../../services/courses';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
/** /**
* Page that displays a list of categories and the courses in the current category if any. * Page that displays a list of categories and the courses in the current category if any.
@ -28,25 +29,64 @@ import { CoreNavigator } from '@services/navigator';
selector: 'page-core-courses-categories', selector: 'page-core-courses-categories',
templateUrl: 'categories.html', templateUrl: 'categories.html',
}) })
export class CoreCoursesCategoriesPage implements OnInit { export class CoreCoursesCategoriesPage implements OnInit, OnDestroy {
title: string; title: string;
currentCategory?: CoreCategoryData; currentCategory?: CoreCategoryData;
categories: CoreCategoryData[] = []; categories: CoreCategoryData[] = [];
courses: CoreCourseSearchedData[] = []; courses: CoreCourseListItem[] = [];
categoriesLoaded = false; categoriesLoaded = false;
myCoursesEnabled = true;
showOnlyEnrolled = false;
downloadEnabled = false;
downloadCourseEnabled = false;
downloadCoursesEnabled = false;
protected categoryCourses: CoreCourseListItem[] = [];
protected currentSiteId: string;
protected categoryId = 0; protected categoryId = 0;
protected myCoursesObserver: CoreEventObserver;
protected siteUpdatedObserver: CoreEventObserver;
protected isDestroyed = false;
constructor() { constructor() {
this.title = Translate.instant('core.courses.categories'); this.title = Translate.instant('core.courses.categories');
this.currentSiteId = CoreSites.getRequiredCurrentSite().getId();
// Update list if user enrols in a course.
this.myCoursesObserver = CoreEvents.on(
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
(data) => {
if (data.action == CoreCoursesProvider.ACTION_ENROL) {
this.fetchCategories();
}
},
this.currentSiteId,
);
// Refresh the enabled flags if site is updated.
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite();
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
}, this.currentSiteId);
} }
/** /**
* View loaded. * @inheritdoc
*/ */
ngOnInit(): void { ngOnInit(): void {
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0; this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
this.showOnlyEnrolled = CoreNavigator.getRouteBooleanParam('enrolled') || this.showOnlyEnrolled;
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite();
this.fetchCategories().finally(() => { this.fetchCategories().finally(() => {
this.categoriesLoaded = true; this.categoriesLoaded = true;
@ -87,13 +127,14 @@ export class CoreCoursesCategoriesPage implements OnInit {
this.title = this.currentCategory.name; this.title = this.currentCategory.name;
try { try {
this.courses = await CoreCourses.getCoursesByField('category', this.categoryId); this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId);
await this.toggleEnrolled(this.showOnlyEnrolled);
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
} }
} }
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true); !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
} }
} }
@ -108,7 +149,7 @@ export class CoreCoursesCategoriesPage implements OnInit {
promises.push(CoreCourses.invalidateUserCourses()); promises.push(CoreCourses.invalidateUserCourses());
promises.push(CoreCourses.invalidateCategories(this.categoryId, true)); promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId)); promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
promises.push(CoreSites.getCurrentSite()!.invalidateConfig()); promises.push(CoreSites.getRequiredCurrentSite().invalidateConfig());
Promise.all(promises).finally(() => { Promise.all(promises).finally(() => {
this.fetchCategories().finally(() => { this.fetchCategories().finally(() => {
@ -123,7 +164,56 @@ export class CoreCoursesCategoriesPage implements OnInit {
* @param categoryId Category Id. * @param categoryId Category Id.
*/ */
openCategory(categoryId: number): void { openCategory(categoryId: number): void {
CoreNavigator.navigateToSitePath('courses/categories/' + categoryId); CoreNavigator.navigateToSitePath(
'courses/categories/' + categoryId,
{ params: {
enrolled: this.showOnlyEnrolled,
} },
);
}
/**
* Toggle show only my courses.
*
* @param enable If enable or disable.
*/
async toggleEnrolled(enable: boolean): Promise<void> {
this.showOnlyEnrolled = enable;
if (!this.showOnlyEnrolled) {
this.courses = this.categoryCourses;
} else {
await Promise.all(this.categoryCourses.map(async (course) => {
const isEnrolled = course.progress !== undefined;
if (!isEnrolled) {
try {
const userCourse = await CoreCourses.getUserCourse(course.id);
course.progress = userCourse.progress;
course.completionusertracked = userCourse.completionusertracked;
} catch {
// Ignore errors.
}
}
}));
this.courses = this.categoryCourses.filter((course) => 'progress' in course);
}
}
/**
* Toggle download enabled.
*/
toggleDownload(enabled: boolean): void {
this.downloadEnabled = enabled;
}
/**
* @inheritdoc
*/
ngOnDestroy(): void {
this.myCoursesObserver?.off();
this.siteUpdatedObserver?.off();
this.isDestroyed = true;
} }
} }