From 5ac62106bf815c6b40d439788a22d71a25951391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 13 Oct 2021 16:18:29 +0200 Subject: [PATCH 01/14] MOBILE-3686 courses: Merge available courses and search courses pages --- .../features/courses/courses-lazy.module.ts | 12 +- .../available-courses/available-courses.html | 19 -- .../available-courses.module.ts | 41 ---- .../available-courses/available-courses.ts | 78 ------- .../courses/pages/dashboard/dashboard.ts | 2 +- .../features/courses/pages/list/list.html | 36 +++ .../search.module.ts => list/list.module.ts} | 8 +- src/core/features/courses/pages/list/list.ts | 211 ++++++++++++++++++ .../courses/pages/my-courses/my-courses.ts | 2 +- .../features/courses/pages/search/search.html | 23 -- .../features/courses/pages/search/search.ts | 100 --------- .../services/handlers/courses-index-link.ts | 7 +- .../features/sitehome/pages/index/index.ts | 4 +- 13 files changed, 263 insertions(+), 280 deletions(-) delete mode 100644 src/core/features/courses/pages/available-courses/available-courses.html delete mode 100644 src/core/features/courses/pages/available-courses/available-courses.module.ts delete mode 100644 src/core/features/courses/pages/available-courses/available-courses.ts create mode 100644 src/core/features/courses/pages/list/list.html rename src/core/features/courses/pages/{search/search.module.ts => list/list.module.ts} (87%) create mode 100644 src/core/features/courses/pages/list/list.ts delete mode 100644 src/core/features/courses/pages/search/search.html delete mode 100644 src/core/features/courses/pages/search/search.ts diff --git a/src/core/features/courses/courses-lazy.module.ts b/src/core/features/courses/courses-lazy.module.ts index 0fa55f3fb..ad6fc7cdf 100644 --- a/src/core/features/courses/courses-lazy.module.ts +++ b/src/core/features/courses/courses-lazy.module.ts @@ -33,16 +33,10 @@ const routes: Routes = [ .then(m => m.CoreCoursesCategoriesPageModule), }, { - path: 'all', + path: 'list', loadChildren: () => - import('./pages/available-courses/available-courses.module') - .then(m => m.CoreCoursesAvailableCoursesPageModule), - }, - { - path: 'search', - loadChildren: () => - import('./pages/search/search.module') - .then(m => m.CoreCoursesSearchPageModule), + import('./pages/list/list.module') + .then(m => m.CoreCoursesListPageModule), }, { path: 'my', diff --git a/src/core/features/courses/pages/available-courses/available-courses.html b/src/core/features/courses/pages/available-courses/available-courses.html deleted file mode 100644 index 07aebce00..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - -

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

-
-
- - - - - - - - - - - diff --git a/src/core/features/courses/pages/available-courses/available-courses.module.ts b/src/core/features/courses/pages/available-courses/available-courses.module.ts deleted file mode 100644 index c272cfa72..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.module.ts +++ /dev/null @@ -1,41 +0,0 @@ -// (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 { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCoursesComponentsModule } from '../../components/components.module'; - -import { CoreCoursesAvailableCoursesPage } from './available-courses'; - -const routes: Routes = [ - { - path: '', - component: CoreCoursesAvailableCoursesPage, - }, -]; - -@NgModule({ - imports: [ - RouterModule.forChild(routes), - CoreSharedModule, - CoreCoursesComponentsModule, - ], - declarations: [ - CoreCoursesAvailableCoursesPage, - ], - exports: [RouterModule], -}) -export class CoreCoursesAvailableCoursesPageModule { } diff --git a/src/core/features/courses/pages/available-courses/available-courses.ts b/src/core/features/courses/pages/available-courses/available-courses.ts deleted file mode 100644 index c7a5b70ef..000000000 --- a/src/core/features/courses/pages/available-courses/available-courses.ts +++ /dev/null @@ -1,78 +0,0 @@ -// (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 { Component, OnInit } from '@angular/core'; -import { IonRefresher } from '@ionic/angular'; - -import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreCourses, CoreCourseSearchedData } from '../../services/courses'; - -/** - * Page that displays available courses in current site. - */ -@Component({ - selector: 'page-core-courses-available-courses', - templateUrl: 'available-courses.html', -}) -export class CoreCoursesAvailableCoursesPage implements OnInit { - - courses: CoreCourseSearchedData[] = []; - coursesLoaded = false; - - /** - * View loaded. - */ - ngOnInit(): void { - this.loadCourses().finally(() => { - this.coursesLoaded = true; - }); - } - - /** - * Load the courses. - * - * @return Promise resolved when done. - */ - protected async loadCourses(): Promise { - const frontpageCourseId = CoreSites.getCurrentSiteHomeId(); - - try { - const courses = await CoreCourses.getCoursesByField(); - - this.courses = courses.filter((course) => course.id != frontpageCourseId); - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); - } - } - - /** - * Refresh the courses. - * - * @param refresher Refresher. - */ - refreshCourses(refresher: IonRefresher): void { - const promises: Promise[] = []; - - promises.push(CoreCourses.invalidateUserCourses()); - promises.push(CoreCourses.invalidateCoursesByField()); - - Promise.all(promises).finally(() => { - this.loadCourses().finally(() => { - refresher?.complete(); - }); - }); - } - -} diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 1c67c49c9..56ef7d56c 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -167,7 +167,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * Go to search courses. */ async openSearch(): Promise { - CoreNavigator.navigateToSitePath('/courses/search'); + CoreNavigator.navigateToSitePath('/courses/list', { params : { mode: 'search' } }); } /** diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html new file mode 100644 index 000000000..3b2471ad6 --- /dev/null +++ b/src/core/features/courses/pages/list/list.html @@ -0,0 +1,36 @@ + + + + + +

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

+
+
+ + + + + + + + + + +

{{ 'core.courses.totalcoursesearchresults' | translate:{$a: searchTotal} }}

+
+
+ + + + + + + + + + + +
+
diff --git a/src/core/features/courses/pages/search/search.module.ts b/src/core/features/courses/pages/list/list.module.ts similarity index 87% rename from src/core/features/courses/pages/search/search.module.ts rename to src/core/features/courses/pages/list/list.module.ts index 497e6f4d1..c0d3a2c36 100644 --- a/src/core/features/courses/pages/search/search.module.ts +++ b/src/core/features/courses/pages/list/list.module.ts @@ -19,12 +19,12 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; import { CoreSearchComponentsModule } from '@features/search/components/components.module'; -import { CoreCoursesSearchPage } from './search'; +import { CoreCoursesListPage } from './list'; const routes: Routes = [ { path: '', - component: CoreCoursesSearchPage, + component: CoreCoursesListPage, }, ]; @@ -36,8 +36,8 @@ const routes: Routes = [ CoreSearchComponentsModule, ], declarations: [ - CoreCoursesSearchPage, + CoreCoursesListPage, ], exports: [RouterModule], }) -export class CoreCoursesSearchPageModule { } +export class CoreCoursesListPageModule { } diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts new file mode 100644 index 000000000..12c0cf317 --- /dev/null +++ b/src/core/features/courses/pages/list/list.ts @@ -0,0 +1,211 @@ +// (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 { Component, OnDestroy, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses'; + +type CoreCoursesListMode = 'search' | 'all'; + +/** + * Page that shows a list of courses. + */ +@Component({ + selector: 'page-core-courses-list', + templateUrl: 'list.html', +}) +export class CoreCoursesListPage implements OnInit, OnDestroy { + + searchEnabled = false; + searchMode = false; + searchCanLoadMore = false; + searchLoadMoreError = false; + searchTotal = 0; + + mode: CoreCoursesListMode = 'all'; + + courses: CoreCourseBasicSearchedData[] = []; + coursesLoaded = false; + + protected currentSiteId: string; + protected frontpageCourseId: number; + protected searchPage = 0; + protected searchText = ''; + protected siteUpdatedObserver: CoreEventObserver; + + constructor() { + this.currentSiteId = CoreSites.getRequiredCurrentSite().getId(); + this.frontpageCourseId = CoreSites.getRequiredCurrentSite().getSiteHomeId(); + + // Refresh the enabled flags if site is updated. + this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + + if (!this.searchEnabled) { + this.searchMode = false; + + this.fetchCourses(); + } + }, this.currentSiteId); + } + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.mode = CoreNavigator.getRouteParam('mode') || this.mode; + + if (this.mode == 'search') { + this.searchMode = true; + } + + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + if (!this.searchEnabled) { + this.searchMode = false; + } + + this.fetchCourses(); + } + + /** + * Load the course list. + * + * @return Promise resolved when done. + */ + protected async fetchCourses(): Promise { + try { + if (this.searchMode && this.searchText) { + await this.search(this.searchText); + } else { + await this.loadAvailableCourses(); + } + } finally { + this.coursesLoaded = true; + } + } + + /** + * Load the courses. + * + * @return Promise resolved when done. + */ + protected async loadAvailableCourses(): Promise { + try { + const courses = await CoreCourses.getCoursesByField(); + + this.courses = courses.filter((course) => course.id != this.frontpageCourseId); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); + } + } + + /** + * Refresh the courses. + * + * @param refresher Refresher. + */ + refreshCourses(refresher: IonRefresher): void { + const promises: Promise[] = []; + + promises.push(CoreCourses.invalidateUserCourses()); + promises.push(CoreCourses.invalidateCoursesByField()); + + Promise.all(promises).finally(() => { + this.fetchCourses().finally(() => { + refresher?.complete(); + }); + }); + } + + /** + * Search a new text. + * + * @param text The text to search. + */ + async search(text: string): Promise { + this.searchMode = true; + this.searchText = text; + this.courses = []; + this.searchPage = 0; + this.searchTotal = 0; + + const modal = await CoreDomUtils.showModalLoading('core.searching', true); + this.searchCourses().finally(() => { + modal.dismiss(); + }); + } + + /** + * Clear search box. + */ + clearSearch(): void { + this.searchText = ''; + this.courses = []; + this.searchPage = 0; + this.searchTotal = 0; + this.searchMode = false; + + this.coursesLoaded = false; + this.fetchCourses(); + } + + /** + * Load more results. + * + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + */ + loadMoreResults(infiniteComplete?: () => void ): void { + this.searchCourses().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Search courses or load the next page of current search. + * + * @return Promise resolved when done. + */ + protected async searchCourses(): Promise { + this.searchLoadMoreError = false; + + try { + const response = await CoreCourses.search(this.searchText, this.searchPage); + + if (this.searchPage === 0) { + this.courses = response.courses; + } else { + this.courses = this.courses.concat(response.courses); + } + this.searchTotal = response.total; + + this.searchPage++; + this.searchCanLoadMore = this.courses.length < this.searchTotal; + } catch (error) { + this.searchLoadMoreError = true; // Set to prevent infinite calls with infinite-loading. + CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.siteUpdatedObserver?.off(); + } + +} diff --git a/src/core/features/courses/pages/my-courses/my-courses.ts b/src/core/features/courses/pages/my-courses/my-courses.ts index 576d7e8df..675933daf 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.ts +++ b/src/core/features/courses/pages/my-courses/my-courses.ts @@ -204,7 +204,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { * Go to search courses. */ openSearch(): void { - CoreNavigator.navigateToSitePath('courses/search'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); } /** diff --git a/src/core/features/courses/pages/search/search.html b/src/core/features/courses/pages/search/search.html deleted file mode 100644 index fb33940a9..000000000 --- a/src/core/features/courses/pages/search/search.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -

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

-
-
- - - - - -

{{ 'core.courses.totalcoursesearchresults' | translate:{$a: total} }}

-
- - - -
- -
diff --git a/src/core/features/courses/pages/search/search.ts b/src/core/features/courses/pages/search/search.ts deleted file mode 100644 index 651aee76f..000000000 --- a/src/core/features/courses/pages/search/search.ts +++ /dev/null @@ -1,100 +0,0 @@ -// (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 { Component } from '@angular/core'; -import { CoreDomUtils } from '@services/utils/dom'; -import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses'; - -/** - * Page that allows searching for courses. - */ -@Component({ - selector: 'page-core-courses-search', - templateUrl: 'search.html', -}) -export class CoreCoursesSearchPage { - - total = 0; - courses: CoreCourseBasicSearchedData[] = []; - canLoadMore = false; - loadMoreError = false; - - protected page = 0; - protected currentSearch = ''; - - /** - * Search a new text. - * - * @param text The text to search. - */ - async search(text: string): Promise { - this.currentSearch = text; - this.courses = []; - this.page = 0; - this.total = 0; - - const modal = await CoreDomUtils.showModalLoading('core.searching', true); - this.searchCourses().finally(() => { - modal.dismiss(); - }); - } - - /** - * Clear search box. - */ - clearSearch(): void { - this.currentSearch = ''; - this.courses = []; - this.page = 0; - this.total = 0; - } - - /** - * Load more results. - * - * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. - */ - loadMoreResults(infiniteComplete?: () => void ): void { - this.searchCourses().finally(() => { - infiniteComplete && infiniteComplete(); - }); - } - - /** - * Search courses or load the next page of current search. - * - * @return Promise resolved when done. - */ - protected async searchCourses(): Promise { - this.loadMoreError = false; - - try { - const response = await CoreCourses.search(this.currentSearch, this.page); - - if (this.page === 0) { - this.courses = response.courses; - } else { - this.courses = this.courses.concat(response.courses); - } - this.total = response.total; - - this.page++; - this.canLoadMore = this.courses.length < this.total; - } catch (error) { - this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); - } - } - -} diff --git a/src/core/features/courses/services/handlers/courses-index-link.ts b/src/core/features/courses/services/handlers/courses-index-link.ts index 500287239..2ff9c3f92 100644 --- a/src/core/features/courses/services/handlers/courses-index-link.ts +++ b/src/core/features/courses/services/handlers/courses-index-link.ts @@ -42,14 +42,17 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB return [{ action: (siteId): void => { let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME; + const pageParams: Params = {}; if (params.categoryid) { pageName += '/categories/' + params.categoryid; } else { - pageName += '/all'; + pageName += '/list'; + pageParams.mode = 'all'; } - CoreNavigator.navigateToSitePath(pageName, { siteId }); + + CoreNavigator.navigateToSitePath(pageName, { params: pageParams, siteId }); }, }]; } diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 7f59dae6d..fd1796d53 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -218,14 +218,14 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * Go to search courses. */ openSearch(): void { - CoreNavigator.navigateToSitePath('courses/search'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); } /** * Go to available courses. */ openAvailableCourses(): void { - CoreNavigator.navigateToSitePath('courses/all'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'all' } }); } /** From 18f20dc12e30700ad684f9c15f5b55c726ea040b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 13 Oct 2021 15:24:20 +0200 Subject: [PATCH 02/14] MOBILE-3686 ux: Add toggle options to context menu items --- src/addons/messages/pages/discussion/discussion.html | 4 ++-- .../messages/pages/discussion/discussion.page.ts | 6 ++++-- src/core/components/context-menu/context-menu-item.ts | 2 ++ .../context-menu/core-context-menu-popover.html | 11 ++++++++--- src/core/features/course/pages/contents/contents.html | 4 ++-- src/core/features/course/pages/contents/contents.ts | 8 ++++---- .../features/courses/pages/dashboard/dashboard.html | 4 ++-- .../features/courses/pages/dashboard/dashboard.ts | 9 --------- src/core/features/sitehome/pages/index/index.html | 4 ++-- src/core/features/sitehome/pages/index/index.ts | 9 --------- 10 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/addons/messages/pages/discussion/discussion.html b/src/addons/messages/pages/discussion/discussion.html index 0ac439fa6..94fa726af 100644 --- a/src/addons/messages/pages/discussion/discussion.html +++ b/src/addons/messages/pages/discussion/discussion.html @@ -44,8 +44,8 @@ 'addon.messages.muteconversation') | translate" (action)="changeMute($event)" [closeOnClick]="false" [iconAction]="muteIcon"> + [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete(!showDelete)" + iconAction="toggle" [toggle]="showDelete"> diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index bb9689cd0..c99f24161 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -1265,9 +1265,11 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView /** * Toggles delete state. + * + * @param enabled Wether to enable or disable show delete toggle. */ - toggleDelete(): void { - this.showDelete = !this.showDelete; + toggleDelete(enable: boolean): void { + this.showDelete = enable; } /** diff --git a/src/core/components/context-menu/context-menu-item.ts b/src/core/components/context-menu/context-menu-item.ts index fd1c2fc33..4129ef889 100644 --- a/src/core/components/context-menu/context-menu-item.ts +++ b/src/core/components/context-menu/context-menu-item.ts @@ -38,6 +38,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange @Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item. @Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click. // If is "spinner" an spinner will be shown. + // If is "toggle" an toggle switch will be shown. // If no icon or spinner is selected, no action or link will work. // If href but no iconAction is provided arrow-right will be used. @Input() iconSlash?: boolean; // Display a red slash over the icon. @@ -52,6 +53,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange @Input() badgeA11yText?: string; // Description for the badge, if needed. @Input() hidden?: boolean; // Whether the item should be hidden. @Input() showBrowserWarning = true; // Whether to show a warning before opening browser (for links). Defaults to true. + @Input() toggle = false; // Whether the toggle is on or off. @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked. @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked. diff --git a/src/core/components/context-menu/core-context-menu-popover.html b/src/core/components/context-menu/core-context-menu-popover.html index 1c23ddabf..4baeb6c85 100644 --- a/src/core/components/context-menu/core-context-menu-popover.html +++ b/src/core/components/context-menu/core-context-menu-popover.html @@ -11,11 +11,16 @@

- + - + + + + + {{item.badge}} diff --git a/src/core/features/course/pages/contents/contents.html b/src/core/features/course/pages/contents/contents.html index 13ad9ebba..938d20ba6 100644 --- a/src/core/features/course/pages/contents/contents.html +++ b/src/core/features/course/pages/contents/contents.html @@ -1,7 +1,7 @@ - + + [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload(!downloadEnabled)" + iconAction="toggle" [toggle]="downloadEnabled"> diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 56ef7d56c..f895f177b 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -40,7 +40,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { downloadEnabled = false; downloadCourseEnabled = false; downloadCoursesEnabled = false; - downloadEnabledIcon = 'far-square'; userId?: number; blocks: Partial[] = []; loaded = false; @@ -138,13 +137,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { }); } - /** - * Toggle download enabled. - */ - toggleDownload(): void { - this.switchDownload(!this.downloadEnabled); - } - /** * Convenience function to switch download enabled. * @@ -152,7 +144,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { */ protected switchDownload(enable: boolean): void { this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; - this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square'; CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled }); } diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index 9bc017b46..574e26e14 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -4,8 +4,8 @@ + [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload(!downloadEnabled)" + iconAction="toggle" [toggle]="downloadEnabled"> diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index fd1796d53..5216e6973 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -53,7 +53,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { downloadEnabled = false; downloadCourseEnabled = false; downloadCoursesEnabled = false; - downloadEnabledIcon = 'far-square'; newsForumModule?: NewsForum; protected updateSiteObserver?: CoreEventObserver; @@ -189,13 +188,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { }); } - /** - * Toggle download enabled. - */ - toggleDownload(): void { - this.switchDownload(!this.downloadEnabled); - } - /** * Convenience function to switch download enabled. * @@ -203,7 +195,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { */ protected switchDownload(enable: boolean): void { this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; - this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square'; CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled }); } From 8acb8b74e283f4334ddfc4f1a1d39afe70caf4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 13 Oct 2021 16:17:40 +0200 Subject: [PATCH 03/14] MOBILE-3686 courses: Merge list courses and my courses page --- scripts/langindex.json | 1 + .../core-courses-course-list-item.html | 8 +- .../course-list-item/course-list-item.ts | 34 +-- .../features/courses/courses-lazy.module.ts | 8 +- src/core/features/courses/courses.module.ts | 2 +- src/core/features/courses/lang.json | 1 + .../features/courses/pages/list/list.html | 14 +- src/core/features/courses/pages/list/list.ts | 101 +++++++- .../courses/pages/my-courses/my-courses.html | 54 ----- .../pages/my-courses/my-courses.module.ts | 40 ---- .../courses/pages/my-courses/my-courses.ts | 219 ------------------ src/core/features/courses/services/courses.ts | 12 + .../features/sitehome/pages/index/index.ts | 2 +- 13 files changed, 147 insertions(+), 349 deletions(-) delete mode 100644 src/core/features/courses/pages/my-courses/my-courses.html delete mode 100644 src/core/features/courses/pages/my-courses/my-courses.module.ts delete mode 100644 src/core/features/courses/pages/my-courses/my-courses.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 0ec207051..9cd4ae19c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1551,6 +1551,7 @@ "core.courses.selfenrolment": "local_moodlemobileapp", "core.courses.sendpaymentbutton": "enrol_paypal", "core.courses.show": "block_myoverview", + "core.courses.showonlyenrolled": "local_moodlemobileapp", "core.courses.therearecourses": "moodle", "core.courses.totalcoursesearchresults": "local_moodlemobileapp", "core.currentdevice": "local_moodlemobileapp", diff --git a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html index aead6199e..0d81bc533 100644 --- a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html +++ b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html @@ -6,6 +6,10 @@ +

+ + +

@@ -19,10 +23,6 @@

-

- - -

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 e7778782f..e4248e0e3 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 @@ -15,8 +15,11 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreNavigator } from '@services/navigator'; -import { CoreCourses, CoreCourseSearchedData } from '../../services/courses'; -import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses-helper'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreCourseListItem, CoreCourses } from '../../services/courses'; +import { CoreCoursesHelper } from '../../services/courses-helper'; /** * This directive is meant to display an item for a list of courses. @@ -32,10 +35,7 @@ import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/c }) export class CoreCoursesCourseListItemComponent implements OnInit { - @Input() course!: CoreCourseSearchedData & CoreCourseWithImageAndColor & { - completionusertracked?: boolean; // If the user is completion tracked. - progress?: number | null; // Progress percentage. - }; // The course to render. + @Input() course!: CoreCourseListItem; // The course to render. icons: CoreCoursesEnrolmentIcons[] = []; isEnrolled = false; @@ -46,15 +46,21 @@ export class CoreCoursesCourseListItemComponent implements OnInit { async ngOnInit(): Promise { CoreCoursesHelper.loadCourseColorAndImage(this.course); - // Check if the user is enrolled in the course. - try { - const course = await CoreCourses.getUserCourse(this.course.id); - this.course.progress = course.progress; - this.course.completionusertracked = course.completionusertracked; + this.isEnrolled = this.course.progress !== undefined; - this.isEnrolled = true; - } catch { - this.isEnrolled = false; + if (!this.isEnrolled) { + try { + const course = await CoreCourses.getUserCourse(this.course.id); + this.course.progress = course.progress; + this.course.completionusertracked = course.completionusertracked; + + this.isEnrolled = true; + } catch { + this.isEnrolled = false; + } + } + + if (!this.isEnrolled) { this.icons = []; this.course.enrollmentmethods.forEach((instance) => { diff --git a/src/core/features/courses/courses-lazy.module.ts b/src/core/features/courses/courses-lazy.module.ts index ad6fc7cdf..cfffc2db4 100644 --- a/src/core/features/courses/courses-lazy.module.ts +++ b/src/core/features/courses/courses-lazy.module.ts @@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { path: '', - redirectTo: 'my', + redirectTo: 'list', pathMatch: 'full', }, { @@ -38,12 +38,6 @@ const routes: Routes = [ import('./pages/list/list.module') .then(m => m.CoreCoursesListPageModule), }, - { - path: 'my', - loadChildren: () => - import('./pages/my-courses/my-courses.module') - .then(m => m.CoreCoursesMyCoursesPageModule), - }, ]; @NgModule({ diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index b07d4bc2b..25129ac4a 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -44,7 +44,7 @@ const mainMenuHomeChildrenRoutes: Routes = [ }, { path: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME, - loadChildren: () => import('./pages/my-courses/my-courses.module').then(m => m.CoreCoursesMyCoursesPageModule), + loadChildren: () => import('./pages/list/list.module').then(m => m.CoreCoursesListPageModule), }, ]; diff --git a/src/core/features/courses/lang.json b/src/core/features/courses/lang.json index 668442a99..47924a0bd 100644 --- a/src/core/features/courses/lang.json +++ b/src/core/features/courses/lang.json @@ -40,6 +40,7 @@ "selfenrolment": "Self enrolment", "sendpaymentbutton": "Send payment via PayPal", "show": "Restore to view", + "showonlyenrolled": "Show only my courses", "therearecourses": "There are {{$a}} courses", "totalcoursesearchresults": "Total courses: {{$a}}" } diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html index 3b2471ad6..fe2dbc491 100644 --- a/src/core/features/courses/pages/list/list.html +++ b/src/core/features/courses/pages/list/list.html @@ -3,7 +3,19 @@ -

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

+

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

+

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

+ + + + + + + diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts index 12c0cf317..7d0174397 100644 --- a/src/core/features/courses/pages/list/list.ts +++ b/src/core/features/courses/pages/list/list.ts @@ -13,14 +13,15 @@ // limitations under the License. import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfo } from '@features/courses/services/courses-helper'; import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses'; +import { CoreCourseBasicSearchedData, CoreCourses, CoreCoursesProvider } from '../../services/courses'; -type CoreCoursesListMode = 'search' | 'all'; +type CoreCoursesListMode = 'search' | 'all' | 'my'; /** * Page that shows a list of courses. @@ -31,31 +32,60 @@ type CoreCoursesListMode = 'search' | 'all'; }) export class CoreCoursesListPage implements OnInit, OnDestroy { + downloadAllCoursesEnabled = false; + searchEnabled = false; + myCoursesEnabled = true; searchMode = false; searchCanLoadMore = false; searchLoadMoreError = false; searchTotal = 0; - mode: CoreCoursesListMode = 'all'; + downloadEnabled = false; + downloadCourseEnabled = false; + downloadCoursesEnabled = false; - courses: CoreCourseBasicSearchedData[] = []; + mode: CoreCoursesListMode = 'my'; + + courses: (CoreCourseBasicSearchedData|CoreEnrolledCourseDataWithExtraInfo)[] = []; coursesLoaded = false; + showOnlyEnrolled = false; + protected currentSiteId: string; protected frontpageCourseId: number; protected searchPage = 0; protected searchText = ''; + protected myCoursesObserver: CoreEventObserver; protected siteUpdatedObserver: CoreEventObserver; + protected courseIds = ''; + protected isDestroyed = false; constructor() { this.currentSiteId = CoreSites.getRequiredCurrentSite().getId(); this.frontpageCourseId = CoreSites.getRequiredCurrentSite().getSiteHomeId(); + // 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.fetchCourses(); + } + }, + + this.currentSiteId, + ); + // Refresh the enabled flags if site is updated. this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); + this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled; if (!this.searchEnabled) { this.searchMode = false; @@ -68,12 +98,25 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { * @inheritdoc */ ngOnInit(): void { + this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); + this.mode = CoreNavigator.getRouteParam('mode') || this.mode; + this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); + if (this.mode == 'my' && !this.myCoursesEnabled) { + this.mode = 'all'; + this.showOnlyEnrolled = false; + } + if (this.mode == 'search') { this.searchMode = true; } + if (this.mode == 'my') { + this.showOnlyEnrolled = true; + } + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); if (!this.searchEnabled) { this.searchMode = false; @@ -91,6 +134,8 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { try { if (this.searchMode && this.searchText) { await this.search(this.searchText); + } else if (this.showOnlyEnrolled) { + await this.loadMyCourses(); } else { await this.loadAvailableCourses(); } @@ -99,6 +144,24 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { } } + /** + * Fetch the user courses. + * + * @return Promise resolved when done. + */ + protected async loadMyCourses(): Promise { + try { + const courses: CoreEnrolledCourseDataWithExtraInfo[] = await CoreCourses.getUserCourses(); + this.courseIds = courses.map((course) => course.id).join(','); + + await CoreCoursesHelper.loadCoursesExtraInfo(courses, true); + + this.courses = courses; + } catch (error) { + !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); + } + } + /** * Load the courses. * @@ -110,7 +173,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.courses = courses.filter((course) => course.id != this.frontpageCourseId); } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); + !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); } } @@ -124,6 +187,9 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { promises.push(CoreCourses.invalidateUserCourses()); promises.push(CoreCourses.invalidateCoursesByField()); + if (this.courseIds) { + promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds)); + } Promise.all(promises).finally(() => { this.fetchCourses().finally(() => { @@ -145,7 +211,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchTotal = 0; const modal = await CoreDomUtils.showModalLoading('core.searching', true); - this.searchCourses().finally(() => { + await this.searchCourses().finally(() => { modal.dismiss(); }); } @@ -184,7 +250,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchLoadMoreError = false; try { - const response = await CoreCourses.search(this.searchText, this.searchPage); + const response = await CoreCourses.search(this.searchText, this.searchPage, undefined, this.showOnlyEnrolled); if (this.searchPage === 0) { this.courses = response.courses; @@ -197,15 +263,34 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchCanLoadMore = this.courses.length < this.searchTotal; } catch (error) { this.searchLoadMoreError = true; // Set to prevent infinite calls with infinite-loading. - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); + !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); } } + /** + * Toggle show only my courses. + */ + toggleEnrolled(enabled: boolean): void { + this.coursesLoaded = false; + this.showOnlyEnrolled = enabled; + + this.fetchCourses(); + } + + /** + * Toggle download enabled. + */ + toggleDownload(enabled: boolean): void { + this.downloadEnabled = enabled; + } + /** * @inheritdoc */ ngOnDestroy(): void { + this.myCoursesObserver?.off(); this.siteUpdatedObserver?.off(); + this.isDestroyed = true; } } diff --git a/src/core/features/courses/pages/my-courses/my-courses.html b/src/core/features/courses/pages/my-courses/my-courses.html deleted file mode 100644 index 2e22ce7ee..000000000 --- a/src/core/features/courses/pages/my-courses/my-courses.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - -

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

- - - - - - - - - - - - {{downloadAllCoursesBadge}} - - - -
-
- - - - - - - - - - - - - - - - - -

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

-
-
-
diff --git a/src/core/features/courses/pages/my-courses/my-courses.module.ts b/src/core/features/courses/pages/my-courses/my-courses.module.ts deleted file mode 100644 index d07c58ced..000000000 --- a/src/core/features/courses/pages/my-courses/my-courses.module.ts +++ /dev/null @@ -1,40 +0,0 @@ -// (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 { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCoursesMyCoursesPage } from './my-courses'; -import { CoreCoursesComponentsModule } from '../../components/components.module'; - -const routes: Routes = [ - { - path: '', - component: CoreCoursesMyCoursesPage, - }, -]; - -@NgModule({ - imports: [ - RouterModule.forChild(routes), - CoreSharedModule, - CoreCoursesComponentsModule, - ], - declarations: [ - CoreCoursesMyCoursesPage, - ], - exports: [RouterModule], -}) -export class CoreCoursesMyCoursesPageModule { } diff --git a/src/core/features/courses/pages/my-courses/my-courses.ts b/src/core/features/courses/pages/my-courses/my-courses.ts deleted file mode 100644 index 675933daf..000000000 --- a/src/core/features/courses/pages/my-courses/my-courses.ts +++ /dev/null @@ -1,219 +0,0 @@ -// (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 { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { IonSearchbar, IonRefresher } from '@ionic/angular'; -import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; -import { - CoreCoursesProvider, - CoreCourses, -} from '../../services/courses'; -import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper'; -import { CoreCourseHelper } from '@features/course/services/course-helper'; -import { CoreConstants } from '@/core/constants'; -import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; -import { CoreNavigator } from '@services/navigator'; -import { Translate } from '@singletons'; - -/** - * Page that displays the list of courses the user is enrolled in. - */ -@Component({ - selector: 'page-core-courses-my-courses', - templateUrl: 'my-courses.html', -}) -export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { - - @ViewChild(IonSearchbar) searchbar!: IonSearchbar; - - courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = []; - filteredCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = []; - searchEnabled = false; - filter = ''; - showFilter = false; - coursesLoaded = false; - downloadAllCoursesIcon = CoreConstants.ICON_NOT_DOWNLOADED; - downloadAllCoursesLoading = false; - downloadAllCoursesBadge = ''; - downloadAllCoursesEnabled = false; - downloadAllCoursesCount?: number; - downloadAllCoursesTotal?: number; - downloadAllCoursesBadgeA11yText = ''; - - protected myCoursesObserver: CoreEventObserver; - protected siteUpdatedObserver: CoreEventObserver; - protected isDestroyed = false; - protected courseIds = ''; - - constructor() { - // 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.fetchCourses(); - } - }, - - CoreSites.getCurrentSiteId(), - ); - - // Refresh the enabled flags if site is updated. - this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { - this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); - this.downloadAllCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - }, CoreSites.getCurrentSiteId()); - } - - /** - * Component being initialized. - */ - ngOnInit(): void { - this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); - this.downloadAllCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - - this.fetchCourses().finally(() => { - this.coursesLoaded = true; - }); - } - - /** - * Fetch the user courses. - * - * @return Promise resolved when done. - */ - protected async fetchCourses(): Promise { - try { - const courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = await CoreCourses.getUserCourses(); - const courseIds = courses.map((course) => course.id); - - this.courseIds = courseIds.join(','); - - await CoreCoursesHelper.loadCoursesExtraInfo(courses); - - const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds); - courses.forEach((course) => { - course.navOptions = options.navOptions[course.id]; - course.admOptions = options.admOptions[course.id]; - }); - - this.courses = courses; - this.filteredCourses = this.courses; - this.filter = ''; - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); - } - } - - /** - * Refresh the courses. - * - * @param refresher Refresher. - */ - refreshCourses(refresher: IonRefresher): void { - const promises: Promise[] = []; - - promises.push(CoreCourses.invalidateUserCourses()); - promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions()); - if (this.courseIds) { - promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds)); - } - - Promise.all(promises).finally(() => { - this.fetchCourses().finally(() => { - refresher?.complete(); - }); - }); - } - - /** - * Show or hide the filter. - */ - switchFilter(): void { - this.filter = ''; - this.showFilter = !this.showFilter; - this.filteredCourses = this.courses; - if (this.showFilter) { - setTimeout(() => { - this.searchbar.setFocus(); - }, 500); - } - } - - /** - * The filter has changed. - * - * @param Received Event. - */ - filterChanged(event?: Event): void { - const target = event?.target || null; - const newValue = target ? String(target.value).trim().toLowerCase() : null; - if (!newValue || !this.courses) { - this.filteredCourses = this.courses; - } else { - // Use displayname if available, or fullname if not. - if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') { - this.filteredCourses = this.courses.filter((course) => course.displayname!.toLowerCase().indexOf(newValue) > -1); - } else { - this.filteredCourses = this.courses.filter((course) => course.fullname.toLowerCase().indexOf(newValue) > -1); - } - } - } - - /** - * Prefetch all the courses. - * - * @return Promise resolved when done. - */ - async prefetchCourses(): Promise { - this.downloadAllCoursesLoading = true; - - try { - await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, { onProgress: (progress) => { - this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total; - this.downloadAllCoursesBadgeA11yText = - Translate.instant('core.course.downloadcoursesprogressdescription', progress); - this.downloadAllCoursesCount = progress.count; - this.downloadAllCoursesTotal = progress.total; - } }); - } catch (error) { - if (!this.isDestroyed) { - CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); - } - } - - this.downloadAllCoursesBadge = ''; - this.downloadAllCoursesLoading = false; - } - - /** - * Go to search courses. - */ - openSearch(): void { - CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); - } - - /** - * Page destroyed. - */ - ngOnDestroy(): void { - this.isDestroyed = true; - this.myCoursesObserver?.off(); - this.siteUpdatedObserver?.off(); - } - -} diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index e05585344..7b52afce7 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -20,6 +20,7 @@ import { makeSingleton } from '@singletons'; import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { CoreEvents } from '@singletons/events'; import { CoreWSError } from '@classes/errors/wserror'; +import { CoreCourseWithImageAndColor } from './courses-helper'; const ROOT_CACHE_KEY = 'mmCourses:'; @@ -1121,6 +1122,7 @@ export class CoreCoursesProvider { * @param text Text to search. * @param page Page to get. * @param perPage Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE. + * @param limitToEnrolled Limit to enrolled courses. * @param siteId Site ID. If not defined, use current site. * @return Promise resolved with the courses and the total of matches. */ @@ -1128,6 +1130,7 @@ export class CoreCoursesProvider { text: string, page: number = 0, perPage: number = CoreCoursesProvider.SEARCH_PER_PAGE, + limitToEnrolled: boolean = false, siteId?: string, ): Promise<{ total: number; courses: CoreCourseBasicSearchedData[] }> { const site = await CoreSites.getSite(siteId); @@ -1136,6 +1139,7 @@ export class CoreCoursesProvider { criteriavalue: text, page: page, perpage: perPage, + limittoenrolled: limitToEnrolled, }; const preSets: CoreSiteWSPreSets = { getFromCache: false, @@ -1358,6 +1362,14 @@ export type CoreCourseSearchedData = CoreCourseBasicSearchedData & { courseformatoptions?: CoreCourseFormatOption[]; // Additional options for particular course format. }; +/** + * Course to render as list item. + */ +export type CoreCourseListItem = CoreCourseSearchedData & CoreCourseWithImageAndColor & { + completionusertracked?: boolean; // If the user is completion tracked. + progress?: number | null; // Progress percentage. +}; + export type CoreCourseGetCoursesData = CoreEnrolledCourseBasicData & { categoryid: number; // Category id. categorysortorder?: number; // Sort order into the category. diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 5216e6973..dcc3491de 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -223,7 +223,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * Go to my courses. */ openMyCourses(): void { - CoreNavigator.navigateToSitePath('courses/my'); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'my' } }); } /** From ad45289f4bcd88f44e66df310111d12010ba8a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 13 Oct 2021 17:11:47 +0200 Subject: [PATCH 04/14] MOBILE-3686 courses: Add download options to course list item --- .../core-courses-course-list-item.html | 10 ++ .../course-list-item/course-list-item.ts | 112 +++++++++++++++++- .../course-progress/course-progress.ts | 5 +- .../features/courses/pages/list/list.html | 3 +- 4 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html index 0d81bc533..425d8e3f3 100644 --- a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html +++ b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html @@ -34,4 +34,14 @@ slot="end">
+ +
+ +
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 e4248e0e3..6d4da5a97 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 @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit } from '@angular/core'; -import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; +import { CoreCourseProvider, CoreCourse } from '@features/course/services/course'; +import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -33,15 +34,26 @@ import { CoreCoursesHelper } from '../../services/courses-helper'; templateUrl: 'core-courses-course-list-item.html', styleUrls: ['course-list-item.scss'], }) -export class CoreCoursesCourseListItemComponent implements OnInit { +export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, OnChanges { @Input() course!: CoreCourseListItem; // The course to render. + @Input() showDownload = false; // If true, will show download button. + icons: CoreCoursesEnrolmentIcons[] = []; isEnrolled = false; + prefetchCourseData: CorePrefetchStatusInfo = { + icon: '', + statusTranslatable: 'core.loading', + status: '', + loading: true, + }; + + protected courseStatusObserver?: CoreEventObserver; + protected isDestroyed = false; /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise { CoreCoursesHelper.loadCourseColorAndImage(this.course); @@ -55,6 +67,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit { this.course.completionusertracked = course.completionusertracked; this.isEnrolled = true; + + if (this.showDownload) { + this.initPrefetchCourse(); + } } catch { this.isEnrolled = false; } @@ -91,6 +107,15 @@ export class CoreCoursesCourseListItemComponent implements OnInit { } } + /** + * @inheritdoc + */ + ngOnChanges(): void { + if (this.showDownload && this.isEnrolled) { + this.initPrefetchCourse(); + } + } + /** * Open a course. * @@ -107,6 +132,85 @@ export class CoreCoursesCourseListItemComponent implements OnInit { } } + /** + * Initialize prefetch course. + */ + async initPrefetchCourse(): Promise { + if (this.courseStatusObserver !== undefined) { + // Already initialized. + return; + } + + // Listen for status change in course. + this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data: CoreEventCourseStatusChanged) => { + if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { + this.updateCourseStatus(data.status); + } + }, CoreSites.getCurrentSiteId()); + + // Determine course prefetch icon. + const status = await CoreCourse.getCourseStatus(this.course.id); + + this.updateCourseStatus(status); + + if (this.prefetchCourseData.loading) { + // Course is being downloaded. Get the download promise. + const promise = CoreCourseHelper.getCourseDownloadPromise(this.course.id); + if (promise) { + // There is a download promise. If it fails, show an error. + promise.catch((error) => { + if (!this.isDestroyed) { + CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }); + } else { + // No download, this probably means that the app was closed while downloading. Set previous status. + CoreCourse.setCoursePreviousStatus(this.course.id); + } + } + + } + + /** + * Update the course status icon and title. + * + * @param status Status to show. + */ + protected updateCourseStatus(status: string): void { + const statusData = CoreCourseHelper.getCoursePrefetchStatusInfo(status); + + this.prefetchCourseData.status = statusData.status; + this.prefetchCourseData.icon = statusData.icon; + this.prefetchCourseData.statusTranslatable = statusData.statusTranslatable; + this.prefetchCourseData.loading = statusData.loading; + } + + /** + * Prefetch the course. + * + * @param e Click event. + */ + async prefetchCourse(e?: Event): Promise { + e?.preventDefault(); + e?.stopPropagation(); + + try { + await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course); + } catch (error) { + if (!this.isDestroyed) { + CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.courseStatusObserver?.off(); + } + } /** 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 c4892208b..ece9a9b50 100644 --- a/src/core/features/courses/components/course-progress/course-progress.ts +++ b/src/core/features/courses/components/course-progress/course-progress.ts @@ -48,8 +48,6 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On @Input() showAll = false; // If true, will show all actions, options, star and progress. @Input() showDownload = true; // If true, will show download button. Only works if the options menu is not shown. - courseStatus = CoreConstants.NOT_DOWNLOADED; - isDownloading = false; prefetchCourseData: CorePrefetchStatusInfo = { icon: '', statusTranslatable: 'core.loading', @@ -64,6 +62,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On progress = -1; completionUserTracked: boolean | undefined = false; + protected courseStatus = CoreConstants.NOT_DOWNLOADED; protected isDestroyed = false; protected courseStatusObserver?: CoreEventObserver; protected siteUpdatedObserver?: CoreEventObserver; @@ -109,7 +108,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On * Initialize prefetch course. */ async initPrefetchCourse(): Promise { - if (typeof this.courseStatusObserver != 'undefined') { + if (this.courseStatusObserver !== undefined) { // Already initialized. return; } diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html index fe2dbc491..838ce9ae1 100644 --- a/src/core/features/courses/pages/list/list.html +++ b/src/core/features/courses/pages/list/list.html @@ -34,7 +34,8 @@ - + + From 264309593f188968e1caddfdd72763529bda0753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 10:10:30 +0200 Subject: [PATCH 05/14] MOBILE-3686 courses: Filter my courses and add download to categories --- .../courses/pages/categories/categories.html | 46 +++++--- .../courses/pages/categories/categories.ts | 110 ++++++++++++++++-- 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/src/core/features/courses/pages/categories/categories.html b/src/core/features/courses/pages/categories/categories.html index dece679a1..eec8dd4a5 100644 --- a/src/core/features/courses/pages/categories/categories.html +++ b/src/core/features/courses/pages/categories/categories.html @@ -7,6 +7,16 @@ + + + + + + @@ -17,22 +27,18 @@ -

- -

-
-
- - -

- -

+

+ +

+

+ +

-
+

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

@@ -48,22 +54,24 @@
- + {{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }} -
+ -
+ -

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

+

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

+

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

- -
+ + + diff --git a/src/core/features/courses/pages/categories/categories.ts b/src/core/features/courses/pages/categories/categories.ts index df71c1c88..d3de228a0 100644 --- a/src/core/features/courses/pages/categories/categories.ts +++ b/src/core/features/courses/pages/categories/categories.ts @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; 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 { 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. @@ -28,25 +29,64 @@ import { CoreNavigator } from '@services/navigator'; selector: 'page-core-courses-categories', templateUrl: 'categories.html', }) -export class CoreCoursesCategoriesPage implements OnInit { +export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { title: string; currentCategory?: CoreCategoryData; categories: CoreCategoryData[] = []; - courses: CoreCourseSearchedData[] = []; + courses: CoreCourseListItem[] = []; categoriesLoaded = false; + myCoursesEnabled = true; + showOnlyEnrolled = false; + + downloadEnabled = false; + downloadCourseEnabled = false; + downloadCoursesEnabled = false; + + protected categoryCourses: CoreCourseListItem[] = []; + protected currentSiteId: string; protected categoryId = 0; + protected myCoursesObserver: CoreEventObserver; + protected siteUpdatedObserver: CoreEventObserver; + protected isDestroyed = false; constructor() { 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 { 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.categoriesLoaded = true; @@ -87,13 +127,14 @@ export class CoreCoursesCategoriesPage implements OnInit { this.title = this.currentCategory.name; try { - this.courses = await CoreCourses.getCoursesByField('category', this.categoryId); + this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId); + await this.toggleEnrolled(this.showOnlyEnrolled); } catch (error) { - CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); + !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); } } } 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.invalidateCategories(this.categoryId, true)); promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId)); - promises.push(CoreSites.getCurrentSite()!.invalidateConfig()); + promises.push(CoreSites.getRequiredCurrentSite().invalidateConfig()); Promise.all(promises).finally(() => { this.fetchCategories().finally(() => { @@ -123,7 +164,56 @@ export class CoreCoursesCategoriesPage implements OnInit { * @param categoryId Category Id. */ 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 { + 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; } } From 577aa3168f863c0d0ee64a59e2049309db5d9442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 10:36:20 +0200 Subject: [PATCH 06/14] MOBILE-3686 dashboard: Fix dashboard download options --- .../features/courses/pages/dashboard/dashboard.html | 2 +- src/core/features/sitehome/pages/index/index.html | 4 ++-- src/core/features/sitehome/pages/index/index.ts | 10 +--------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html index 394086ca8..a7f25bbee 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.html +++ b/src/core/features/courses/pages/dashboard/dashboard.html @@ -6,7 +6,7 @@ - diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index 574e26e14..64ff7d280 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -3,10 +3,10 @@ - - diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index dcc3491de..b33f2e605 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -51,8 +51,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { currentSite!: CoreSite; searchEnabled = false; downloadEnabled = false; - downloadCourseEnabled = false; - downloadCoursesEnabled = false; newsForumModule?: NewsForum; protected updateSiteObserver?: CoreEventObserver; @@ -62,16 +60,10 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { */ ngOnInit(): void { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); - this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); - this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); // Refresh the enabled flags if site is updated. this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); - this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); - this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - - this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled); }, CoreSites.getCurrentSiteId()); this.currentSite = CoreSites.getRequiredCurrentSite(); @@ -194,7 +186,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * @param enable If enable or disable. */ protected switchDownload(enable: boolean): void { - this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; + this.downloadEnabled = enable; CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled }); } From e789d2628023aca744a6b3d766bdb9fc1a3f9075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 11:02:03 +0200 Subject: [PATCH 07/14] MOBILE-3686 courses: Fix permanency of download options setting --- .../course/pages/contents/contents.ts | 73 +++++++++++++------ .../courses/pages/categories/categories.ts | 20 ++++- .../courses/pages/dashboard/dashboard.html | 8 +- .../courses/pages/dashboard/dashboard.ts | 37 ++++++---- src/core/features/courses/pages/list/list.ts | 26 +++++-- src/core/features/courses/services/courses.ts | 32 ++++++-- .../features/sitehome/pages/index/index.html | 10 +-- .../features/sitehome/pages/index/index.ts | 34 ++++++--- 8 files changed, 171 insertions(+), 69 deletions(-) diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index 7492207ed..2301f3463 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -18,7 +18,7 @@ import { IonContent, IonRefresher } from '@ionic/angular'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; -import { CoreCourses, CoreCourseAnyCourseData } from '@features/courses/services/courses'; +import { CoreCourses, CoreCourseAnyCourseData, CoreCoursesProvider } from '@features/courses/services/courses'; import { CoreCourse, CoreCourseCompletionActivityStatus, @@ -78,14 +78,34 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { protected formatOptions?: Record; protected completionObserver?: CoreEventObserver; protected courseStatusObserver?: CoreEventObserver; + protected siteUpdatedObserver?: CoreEventObserver; + protected downloadEnabledObserver?: CoreEventObserver; protected syncObserver?: CoreEventObserver; protected isDestroyed = false; protected modulesHaveCompletion = false; protected isGuest?: boolean; protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time. + constructor() { + // Refresh the enabled flags if site is updated. + this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { + this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); + + this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() && + CoreCourseFormatDelegate.displayEnableDownload(this.course); + + this.downloadEnabled = this.displayEnableDownload && this.downloadEnabled; + + this.initListeners(); + }, CoreSites.getCurrentSiteId()); + + this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { + this.toggleDownload(data.enabled); + }); + } + /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise { @@ -103,10 +123,12 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { this.moduleId = CoreNavigator.getRouteNumberParam('moduleId'); this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest'); - this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() && + this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() && CoreCourseFormatDelegate.displayEnableDownload(this.course); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); + this.downloadEnabled = this.displayEnableDownload && CoreCourses.getCourseDownloadOptionsEnabled(); + this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => { if (this.modulesHaveCompletion) { CoreUtils.ignoreErrors(CoreCourse.getSections(this.course.id, false, true)); @@ -137,7 +159,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { * @return Promise resolved when done. */ protected async initListeners(): Promise { - if (this.downloadCourseEnabled) { + if (this.downloadCourseEnabled && !this.courseStatusObserver) { // Listen for changes in course status. this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => { if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) { @@ -152,26 +174,30 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { return; } - this.completionObserver = CoreEvents.on( - CoreEvents.COMPLETION_MODULE_VIEWED, - (data) => { - if (data && data.courseId == this.course.id) { - this.refreshAfterCompletionChange(true); + if (!this.completionObserver) { + this.completionObserver = CoreEvents.on( + CoreEvents.COMPLETION_MODULE_VIEWED, + (data) => { + if (data && data.courseId == this.course.id) { + this.refreshAfterCompletionChange(true); + } + }, + ); + } + + if (!this.syncObserver) { + this.syncObserver = CoreEvents.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => { + if (!data || data.courseId != this.course.id) { + return; } - }, - ); - this.syncObserver = CoreEvents.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => { - if (!data || data.courseId != this.course.id) { - return; - } + this.refreshAfterCompletionChange(false); - this.refreshAfterCompletionChange(false); - - if (data.warnings && data.warnings[0]) { - CoreDomUtils.showErrorModal(data.warnings[0]); - } - }); + if (data.warnings && data.warnings[0]) { + CoreDomUtils.showErrorModal(data.warnings[0]); + } + }); + } } /** @@ -472,7 +498,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { * @param enable Whether enable or disable download enabled toggle. */ toggleDownload(enable: boolean): void { - this.downloadEnabled = enable; + this.downloadEnabled = + CoreCourses.setCourseDownloadOptionsEnabled(this.displayEnableDownload && enable); } /** @@ -517,6 +544,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { this.completionObserver?.off(); this.courseStatusObserver?.off(); this.syncObserver?.off(); + this.siteUpdatedObserver?.off(); + this.downloadEnabledObserver?.off(); } /** diff --git a/src/core/features/courses/pages/categories/categories.ts b/src/core/features/courses/pages/categories/categories.ts index d3de228a0..c5969bcae 100644 --- a/src/core/features/courses/pages/categories/categories.ts +++ b/src/core/features/courses/pages/categories/categories.ts @@ -49,6 +49,7 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { protected categoryId = 0; protected myCoursesObserver: CoreEventObserver; protected siteUpdatedObserver: CoreEventObserver; + protected downloadEnabledObserver: CoreEventObserver; protected isDestroyed = false; constructor() { @@ -75,6 +76,10 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled; }, this.currentSiteId); + + this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { + this.toggleDownload(data.enabled); + }); } /** @@ -88,6 +93,9 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); + this.downloadEnabled = + (this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled(); + this.fetchCategories().finally(() => { this.categoriesLoaded = true; }); @@ -202,17 +210,21 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { /** * Toggle download enabled. + * + * @param enable If enable or disable. */ - toggleDownload(enabled: boolean): void { - this.downloadEnabled = enabled; + toggleDownload(enable: boolean): void { + this.downloadEnabled = + CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); } /** * @inheritdoc */ ngOnDestroy(): void { - this.myCoursesObserver?.off(); - this.siteUpdatedObserver?.off(); + this.myCoursesObserver.off(); + this.siteUpdatedObserver.off(); + this.downloadEnabledObserver.off(); this.isDestroyed = true; } diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html index a7f25bbee..a7dc2fc79 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.html +++ b/src/core/features/courses/pages/dashboard/dashboard.html @@ -4,11 +4,11 @@ + [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload(!downloadEnabled)" + iconAction="toggle" [toggle]="downloadEnabled"> + [content]="'addon.storagemanager.managestorage' | translate" + (action)="manageCoursesStorage()" iconAction="fas-archive"> diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index f895f177b..28180ea4e 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -44,25 +44,35 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { blocks: Partial[] = []; loaded = false; - protected updateSiteObserver?: CoreEventObserver; - - /** - * Initialize the component. - */ - ngOnInit(): void { - this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); - this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); - this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); + protected updateSiteObserver: CoreEventObserver; + protected downloadEnabledObserver: CoreEventObserver; + constructor() { // Refresh the enabled flags if site is updated. this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled); + this.switchDownload(this.downloadEnabled); }, CoreSites.getCurrentSiteId()); + this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { + this.switchDownload(data.enabled); + }); + } + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); + + this.downloadEnabled = + (this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled(); + this.loadContent(); } @@ -143,8 +153,8 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * @param enable If enable or disable. */ protected switchDownload(enable: boolean): void { - this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; - CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled }); + this.downloadEnabled = + CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); } /** @@ -165,7 +175,8 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * Component being destroyed. */ ngOnDestroy(): void { - this.updateSiteObserver?.off(); + this.updateSiteObserver.off(); + this.downloadEnabledObserver.off(); } } diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts index 7d0174397..a510d5b5b 100644 --- a/src/core/features/courses/pages/list/list.ts +++ b/src/core/features/courses/pages/list/list.ts @@ -58,6 +58,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { protected searchText = ''; protected myCoursesObserver: CoreEventObserver; protected siteUpdatedObserver: CoreEventObserver; + protected downloadEnabledObserver: CoreEventObserver; protected courseIds = ''; protected isDestroyed = false; @@ -92,6 +93,10 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.fetchCourses(); } }, this.currentSiteId); + + this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { + this.toggleDownload(data.enabled); + }); } /** @@ -101,6 +106,9 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); + this.downloadEnabled = + (this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled(); + this.mode = CoreNavigator.getRouteParam('mode') || this.mode; this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); @@ -269,27 +277,33 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { /** * Toggle show only my courses. + * + * @param enable If enable or disable. */ - toggleEnrolled(enabled: boolean): void { + toggleEnrolled(enable: boolean): void { this.coursesLoaded = false; - this.showOnlyEnrolled = enabled; + this.showOnlyEnrolled = enable; this.fetchCourses(); } /** * Toggle download enabled. + * + * @param enable If enable or disable. */ - toggleDownload(enabled: boolean): void { - this.downloadEnabled = enabled; + toggleDownload(enable: boolean): void { + this.downloadEnabled = + CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); } /** * @inheritdoc */ ngOnDestroy(): void { - this.myCoursesObserver?.off(); - this.siteUpdatedObserver?.off(); + this.myCoursesObserver.off(); + this.siteUpdatedObserver.off(); + this.downloadEnabledObserver.off(); this.isDestroyed = true; } diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index 7b52afce7..9ccd82fbc 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreLogger } from '@singletons/logger'; import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { makeSingleton } from '@singletons'; @@ -63,12 +62,9 @@ export class CoreCoursesProvider { static readonly STATE_HIDDEN = 'hidden'; static readonly STATE_FAVOURITE = 'favourite'; - protected logger: CoreLogger; protected userCoursesIds: { [id: number]: boolean } = {}; // Use an object to make it faster to search. - constructor() { - this.logger = CoreLogger.getInstance('CoreCoursesProvider'); - } + protected downloadOptionsEnabled = false; /** * Whether current site supports getting course options. @@ -1220,6 +1216,32 @@ export class CoreCoursesProvider { return site.write('core_course_set_favourite_courses', params); } + /** + * Get download options enabled option. + * + * @return True if enabled, false otherwise. + */ + getCourseDownloadOptionsEnabled(): boolean { + return this.downloadOptionsEnabled; + } + + /** + * Set trigger and save the download option. + * + * @param enable True to enable, false to disable. + * @return Current status. + */ + setCourseDownloadOptionsEnabled(enable: boolean): boolean { + if (this.downloadOptionsEnabled == enable) { + return enable; + } + + this.downloadOptionsEnabled = enable; + CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: enable }); + + return enable; + } + } export const CoreCourses = makeSingleton(CoreCoursesProvider); diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index 64ff7d280..179a73f10 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -3,12 +3,12 @@ - + + [content]="'addon.storagemanager.managestorage' | translate" + (action)="manageCoursesStorage()" iconAction="fas-archive"> diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index b33f2e605..4f2aff5c5 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -50,22 +50,32 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { siteHomeId = 1; currentSite!: CoreSite; searchEnabled = false; + displayEnableDownload = false; downloadEnabled = false; newsForumModule?: NewsForum; - protected updateSiteObserver?: CoreEventObserver; - - /** - * Page being initialized. - */ - ngOnInit(): void { - this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + protected updateSiteObserver: CoreEventObserver; + protected downloadEnabledObserver: CoreEventObserver; + constructor() { // Refresh the enabled flags if site is updated. this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + + this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled(); }, CoreSites.getCurrentSiteId()); + this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { + this.switchDownload(data.enabled); + }); + } + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); + this.currentSite = CoreSites.getRequiredCurrentSite(); this.siteHomeId = CoreSites.getCurrentSiteHomeId(); @@ -75,6 +85,9 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams); } + this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled(); + this.downloadEnabled = CoreCourses.getCourseDownloadOptionsEnabled(); + this.loadContent().finally(() => { this.dataLoaded = true; }); @@ -186,8 +199,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * @param enable If enable or disable. */ protected switchDownload(enable: boolean): void { - this.downloadEnabled = enable; - CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled }); + this.downloadEnabled = + CoreCourses.setCourseDownloadOptionsEnabled(enable); } /** @@ -229,7 +242,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * Component being destroyed. */ ngOnDestroy(): void { - this.updateSiteObserver?.off(); + this.updateSiteObserver.off(); + this.downloadEnabledObserver.off(); } } From 27ceb961276d3823bc6e37440a41363bbe9c7348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 12:30:19 +0200 Subject: [PATCH 08/14] MOBILE-3686 storage: Add manage storage in site preferences --- .../services/handlers/settings.ts | 52 +++++++++++++++++++ .../storagemanager/storagemanager.module.ts | 5 ++ 2 files changed, 57 insertions(+) create mode 100644 src/addons/storagemanager/services/handlers/settings.ts diff --git a/src/addons/storagemanager/services/handlers/settings.ts b/src/addons/storagemanager/services/handlers/settings.ts new file mode 100644 index 000000000..9bcaf413c --- /dev/null +++ b/src/addons/storagemanager/services/handlers/settings.ts @@ -0,0 +1,52 @@ +// (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 { makeSingleton } from '@singletons'; +import { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate'; + +/** + * Mange storage settings handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonStorageManagerSettingsHandlerService implements CoreSettingsHandler { + + static readonly PAGE_NAME = 'storage'; + + name = 'AddonStorageManager'; + priority = 400; + + /** + * @inheritdoc + */ + async isEnabled(): Promise { + return true; + } + + /** + * @inheritdoc + */ + getDisplayData(): CoreSettingsHandlerData { + return { + icon: 'fas-archive', + title: 'addon.storagemanager.managestorage', + page: AddonStorageManagerSettingsHandlerService.PAGE_NAME, + class: 'addon-storagemanager-settings-handler', + }; + } + +} + +export const AddonStorageManagerSettingsHandler = makeSingleton(AddonStorageManagerSettingsHandlerService); diff --git a/src/addons/storagemanager/storagemanager.module.ts b/src/addons/storagemanager/storagemanager.module.ts index 2a32b1c19..187d66cdd 100644 --- a/src/addons/storagemanager/storagemanager.module.ts +++ b/src/addons/storagemanager/storagemanager.module.ts @@ -17,7 +17,10 @@ import { Routes } from '@angular/router'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing'; +import { CoreSettingsDelegate } from '@features/settings/services/settings-delegate'; import { AddonStorageManagerCourseMenuHandler } from './services/handlers/course-menu'; +import { AddonStorageManagerSettingsHandler } from './services/handlers/settings'; const routes: Routes = [ { @@ -30,6 +33,7 @@ const routes: Routes = [ imports: [ CoreMainMenuTabRoutingModule.forChild(routes), CoreMainMenuRoutingModule.forChild({ children: routes }), + CoreSitePreferencesRoutingModule.forChild(routes), ], exports: [CoreMainMenuRoutingModule], providers: [ @@ -38,6 +42,7 @@ const routes: Routes = [ multi: true, useValue: () => { CoreCourseOptionsDelegate.registerHandler(AddonStorageManagerCourseMenuHandler.instance); + CoreSettingsDelegate.registerHandler(AddonStorageManagerSettingsHandler.instance); }, }, ], From aef7a64db7bb9958c1237566bf8a9eac96bc1211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 13:07:29 +0200 Subject: [PATCH 09/14] MOBILE-3686 courses: Do not check my courses disabled on list --- .../courses/pages/categories/categories.html | 2 +- .../courses/pages/categories/categories.ts | 3 --- .../features/courses/pages/dashboard/dashboard.ts | 4 ++-- src/core/features/courses/pages/list/list.html | 2 +- src/core/features/courses/pages/list/list.ts | 14 ++++---------- src/core/features/courses/services/dashboard.ts | 2 +- src/core/features/sitehome/pages/index/index.ts | 4 ++-- src/core/features/sitehome/services/sitehome.ts | 14 +++----------- 8 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/core/features/courses/pages/categories/categories.html b/src/core/features/courses/pages/categories/categories.html index eec8dd4a5..33fb19de2 100644 --- a/src/core/features/courses/pages/categories/categories.html +++ b/src/core/features/courses/pages/categories/categories.html @@ -12,7 +12,7 @@ - diff --git a/src/core/features/courses/pages/categories/categories.ts b/src/core/features/courses/pages/categories/categories.ts index c5969bcae..34dd3c7a0 100644 --- a/src/core/features/courses/pages/categories/categories.ts +++ b/src/core/features/courses/pages/categories/categories.ts @@ -36,7 +36,6 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { categories: CoreCategoryData[] = []; courses: CoreCourseListItem[] = []; categoriesLoaded = false; - myCoursesEnabled = true; showOnlyEnrolled = false; @@ -72,7 +71,6 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { 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); @@ -91,7 +89,6 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled(); diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 28180ea4e..712f893e5 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -148,11 +148,11 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { } /** - * Convenience function to switch download enabled. + * Switch download enabled. * * @param enable If enable or disable. */ - protected switchDownload(enable: boolean): void { + switchDownload(enable: boolean): void { this.downloadEnabled = CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); } diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html index 838ce9ae1..90593d24f 100644 --- a/src/core/features/courses/pages/list/list.html +++ b/src/core/features/courses/pages/list/list.html @@ -11,7 +11,7 @@ - diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts index a510d5b5b..64edc1eee 100644 --- a/src/core/features/courses/pages/list/list.ts +++ b/src/core/features/courses/pages/list/list.ts @@ -35,7 +35,6 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { downloadAllCoursesEnabled = false; searchEnabled = false; - myCoursesEnabled = true; searchMode = false; searchCanLoadMore = false; searchLoadMoreError = false; @@ -84,7 +83,6 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite(); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled; if (!this.searchEnabled) { @@ -111,12 +109,6 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.mode = CoreNavigator.getRouteParam('mode') || this.mode; - this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite(); - if (this.mode == 'my' && !this.myCoursesEnabled) { - this.mode = 'all'; - this.showOnlyEnrolled = false; - } - if (this.mode == 'search') { this.searchMode = true; } @@ -140,8 +132,10 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { */ protected async fetchCourses(): Promise { try { - if (this.searchMode && this.searchText) { - await this.search(this.searchText); + if (this.searchMode) { + if (this.searchText) { + await this.search(this.searchText); + } } else if (this.showOnlyEnrolled) { await this.loadMyCourses(); } else { diff --git a/src/core/features/courses/services/dashboard.ts b/src/core/features/courses/services/dashboard.ts index 717a43206..d592f2e2d 100644 --- a/src/core/features/courses/services/dashboard.ts +++ b/src/core/features/courses/services/dashboard.ts @@ -107,7 +107,7 @@ export class CoreCoursesDashboardProvider { } /** - * Check if Site Home is disabled in a certain site. + * Check if Dashboard is disabled in a certain site. * * @param site Site. If not defined, use current site. * @return Whether it's disabled. diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 4f2aff5c5..81ecc1c03 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -194,11 +194,11 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { } /** - * Convenience function to switch download enabled. + * Switch download enabled. * * @param enable If enable or disable. */ - protected switchDownload(enable: boolean): void { + switchDownload(enable: boolean): void { this.downloadEnabled = CoreCourses.setCourseDownloadOptionsEnabled(enable); } diff --git a/src/core/features/sitehome/services/sitehome.ts b/src/core/features/sitehome/services/sitehome.ts index 3de9e2ef9..596d144ef 100644 --- a/src/core/features/sitehome/services/sitehome.ts +++ b/src/core/features/sitehome/services/sitehome.ts @@ -169,20 +169,12 @@ export class CoreSiteHomeProvider { // Get number of news items to show. add = !!CoreSites.getCurrentSite()?.getStoredConfig('newsitems'); break; - case FrontPageItemNames['LIST_OF_CATEGORIES']: case FrontPageItemNames['COMBO_LIST']: + itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES']; // Do not break here. + case FrontPageItemNames['LIST_OF_CATEGORIES']: case FrontPageItemNames['LIST_OF_COURSE']: - add = true; - if (itemNumber == FrontPageItemNames['COMBO_LIST']) { - itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES']; - } - break; case FrontPageItemNames['ENROLLED_COURSES']: - if (!CoreCourses.isMyCoursesDisabledInSite()) { - const courses = await CoreCourses.getUserCourses(); - - add = courses.length > 0; - } + add = true; break; case FrontPageItemNames['COURSE_SEARCH_BOX']: add = !CoreCourses.isSearchCoursesDisabledInSite(); From 3bc40c88fe88b143996a184869586adabf6e7554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 13:37:17 +0200 Subject: [PATCH 10/14] MOBILE-3686 courses: Fake pagination on course list to avoid crashing --- .../features/courses/pages/list/list.html | 8 +- src/core/features/courses/pages/list/list.ts | 106 ++++++++++-------- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html index 90593d24f..89108f71e 100644 --- a/src/core/features/courses/pages/list/list.html +++ b/src/core/features/courses/pages/list/list.html @@ -19,7 +19,7 @@ - + @@ -27,7 +27,7 @@ [placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" [autoFocus]="searchMode" searchArea="CoreCoursesSearch"> - +

{{ 'core.courses.totalcoursesearchresults' | translate:{$a: searchTotal} }}

@@ -37,8 +37,10 @@ - + + + diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts index 64edc1eee..9e192dc82 100644 --- a/src/core/features/courses/pages/list/list.ts +++ b/src/core/features/courses/pages/list/list.ts @@ -36,21 +36,22 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { searchEnabled = false; searchMode = false; - searchCanLoadMore = false; - searchLoadMoreError = false; searchTotal = 0; downloadEnabled = false; downloadCourseEnabled = false; downloadCoursesEnabled = false; - mode: CoreCoursesListMode = 'my'; - courses: (CoreCourseBasicSearchedData|CoreEnrolledCourseDataWithExtraInfo)[] = []; - coursesLoaded = false; + loaded = false; + coursesLoaded = 0; + canLoadMore = false; + loadMoreError = false; showOnlyEnrolled = false; + protected loadedCourses: (CoreCourseBasicSearchedData|CoreEnrolledCourseDataWithExtraInfo)[] = []; + protected loadCoursesPerPage = 20; protected currentSiteId: string; protected frontpageCourseId: number; protected searchPage = 0; @@ -107,13 +108,13 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled(); - this.mode = CoreNavigator.getRouteParam('mode') || this.mode; + const mode = CoreNavigator.getRouteParam('mode') || 'my'; - if (this.mode == 'search') { + if (mode == 'search') { this.searchMode = true; } - if (this.mode == 'my') { + if (mode == 'my') { this.showOnlyEnrolled = true; } @@ -136,47 +137,50 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { if (this.searchText) { await this.search(this.searchText); } - } else if (this.showOnlyEnrolled) { - await this.loadMyCourses(); } else { - await this.loadAvailableCourses(); + await this.loadCourses(true); } } finally { - this.coursesLoaded = true; + this.loaded = true; } } /** - * Fetch the user courses. + * Fetch the courses. * + * @param clearTheList If list needs to be reloaded. * @return Promise resolved when done. */ - protected async loadMyCourses(): Promise { + protected async loadCourses(clearTheList = false): Promise { + this.loadMoreError = false; + try { - const courses: CoreEnrolledCourseDataWithExtraInfo[] = await CoreCourses.getUserCourses(); - this.courseIds = courses.map((course) => course.id).join(','); + if (clearTheList) { + if (this.showOnlyEnrolled) { + this.loadedCourses = await CoreCourses.getUserCourses(); + } else { + const courses = await CoreCourses.getCoursesByField(); + this.loadedCourses = courses.filter((course) => course.id != this.frontpageCourseId); + } - await CoreCoursesHelper.loadCoursesExtraInfo(courses, true); + this.coursesLoaded = 0; + this.courses = []; + } - this.courses = courses; + const addCourses = this.loadedCourses.slice(this.coursesLoaded, this.coursesLoaded + this.loadCoursesPerPage); + await CoreCoursesHelper.loadCoursesExtraInfo(addCourses, true); + + this.courses = this.courses.concat(addCourses); + + this.courseIds = this.courses.map((course) => course.id).join(','); + + this.coursesLoaded = this.courses.length; + this.canLoadMore = this.loadedCourses.length > this.courses.length; } catch (error) { + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); } - } - /** - * Load the courses. - * - * @return Promise resolved when done. - */ - protected async loadAvailableCourses(): Promise { - try { - const courses = await CoreCourses.getCoursesByField(); - - this.courses = courses.filter((course) => course.id != this.frontpageCourseId); - } catch (error) { - !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); - } } /** @@ -187,10 +191,16 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { refreshCourses(refresher: IonRefresher): void { const promises: Promise[] = []; - promises.push(CoreCourses.invalidateUserCourses()); - promises.push(CoreCourses.invalidateCoursesByField()); - if (this.courseIds) { - promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds)); + if (!this.searchMode) { + if (this.showOnlyEnrolled) { + promises.push(CoreCourses.invalidateUserCourses()); + } else { + promises.push(CoreCourses.invalidateCoursesByField()); + } + + if (this.courseIds) { + promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds)); + } } Promise.all(promises).finally(() => { @@ -228,19 +238,25 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchTotal = 0; this.searchMode = false; - this.coursesLoaded = false; + this.loaded = false; this.fetchCourses(); } /** - * Load more results. + * Load more courses. * * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. */ - loadMoreResults(infiniteComplete?: () => void ): void { - this.searchCourses().finally(() => { + async loadMoreCourses(infiniteComplete?: () => void ): Promise { + try { + if (this.searchMode) { + await this.searchCourses(); + } else { + await this.loadCourses(); + } + } finally { infiniteComplete && infiniteComplete(); - }); + } } /** @@ -249,7 +265,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { * @return Promise resolved when done. */ protected async searchCourses(): Promise { - this.searchLoadMoreError = false; + this.loadMoreError = false; try { const response = await CoreCourses.search(this.searchText, this.searchPage, undefined, this.showOnlyEnrolled); @@ -262,9 +278,9 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.searchTotal = response.total; this.searchPage++; - this.searchCanLoadMore = this.courses.length < this.searchTotal; + this.canLoadMore = this.courses.length < this.searchTotal; } catch (error) { - this.searchLoadMoreError = true; // Set to prevent infinite calls with infinite-loading. + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true); } } @@ -275,7 +291,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { * @param enable If enable or disable. */ toggleEnrolled(enable: boolean): void { - this.coursesLoaded = false; + this.loaded = false; this.showOnlyEnrolled = enable; this.fetchCourses(); From 6e2bfc5286be8e509d292ba9be3ec025cbce7b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 15:05:53 +0200 Subject: [PATCH 11/14] MOBILE-3686 courses: Move my courses to a main menu tab --- src/core/features/courses/courses.module.ts | 24 +++++++++--- .../services/handlers/courses-index-link.ts | 12 ++---- ...courses-home.ts => my-courses-mainmenu.ts} | 37 ++++++------------- 3 files changed, 33 insertions(+), 40 deletions(-) rename src/core/features/courses/services/handlers/{my-courses-home.ts => my-courses-mainmenu.ts} (60%) diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index 25129ac4a..925858cc0 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -15,9 +15,12 @@ import { APP_INITIALIZER, NgModule, Type } from '@angular/core'; import { Routes } from '@angular/router'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate'; +import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; import { CoreCoursesProvider } from './services/courses'; import { CoreCoursesHelperProvider } from './services/courses-helper'; @@ -28,7 +31,10 @@ import { CoreCoursesIndexLinkHandler } from './services/handlers/courses-index-l 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 { + CoreCoursesMyCoursesHomeHandler, + CoreCoursesMyCoursesMainMenuHandlerService, +} from './services/handlers/my-courses-mainmenu'; import { CoreCoursesRequestPushClickHandler } from './services/handlers/request-push-click'; export const CORE_COURSES_SERVICES: Type[] = [ @@ -42,10 +48,6 @@ const mainMenuHomeChildrenRoutes: Routes = [ path: CoreDashboardHomeHandlerService.PAGE_NAME, loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.CoreCoursesDashboardPageModule), }, - { - path: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME, - loadChildren: () => import('./pages/list/list.module').then(m => m.CoreCoursesListPageModule), - }, ]; const mainMenuHomeSiblingRoutes: Routes = [ @@ -55,20 +57,30 @@ const mainMenuHomeSiblingRoutes: Routes = [ }, ]; +const mainMenuTabRoutes: Routes = [ + { + path: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, + loadChildren: () => import('./pages/list/list.module').then(m => m.CoreCoursesListPageModule), + }, +]; + @NgModule({ imports: [ CoreMainMenuHomeRoutingModule.forChild({ children: mainMenuHomeChildrenRoutes, siblings: mainMenuHomeSiblingRoutes, }), + CoreMainMenuRoutingModule.forChild({ children: mainMenuTabRoutes }), + CoreMainMenuTabRoutingModule.forChild(mainMenuTabRoutes), ], + exports: [CoreMainMenuRoutingModule], providers: [ { provide: APP_INITIALIZER, multi: true, useValue: () => { CoreMainMenuHomeDelegate.registerHandler(CoreDashboardHomeHandler.instance); - CoreMainMenuHomeDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance); + CoreMainMenuDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance); CoreContentLinksDelegate.registerHandler(CoreCoursesCourseLinkHandler.instance); CoreContentLinksDelegate.registerHandler(CoreCoursesIndexLinkHandler.instance); CoreContentLinksDelegate.registerHandler(CoreCoursesDashboardLinkHandler.instance); diff --git a/src/core/features/courses/services/handlers/courses-index-link.ts b/src/core/features/courses/services/handlers/courses-index-link.ts index 2ff9c3f92..4ca26a580 100644 --- a/src/core/features/courses/services/handlers/courses-index-link.ts +++ b/src/core/features/courses/services/handlers/courses-index-link.ts @@ -18,7 +18,7 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; -import { CoreCoursesMyCoursesHomeHandlerService } from './my-courses-home'; +import { CoreCoursesMyCoursesMainMenuHandlerService } from './my-courses-mainmenu'; /** * Handler to treat links to course index (list of courses). @@ -31,17 +31,12 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB 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. + * @inheritdoc */ getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { return [{ action: (siteId): void => { - let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME; + let pageName = CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME; const pageParams: Params = {}; if (params.categoryid) { @@ -51,7 +46,6 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB pageParams.mode = 'all'; } - CoreNavigator.navigateToSitePath(pageName, { params: pageParams, siteId }); }, }]; diff --git a/src/core/features/courses/services/handlers/my-courses-home.ts b/src/core/features/courses/services/handlers/my-courses-mainmenu.ts similarity index 60% rename from src/core/features/courses/services/handlers/my-courses-home.ts rename to src/core/features/courses/services/handlers/my-courses-mainmenu.ts index 3e75bf5a8..3c3991a5c 100644 --- a/src/core/features/courses/services/handlers/my-courses-home.ts +++ b/src/core/features/courses/services/handlers/my-courses-mainmenu.ts @@ -13,39 +13,29 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate'; import { CoreSiteHomeHomeHandler } from '@features/sitehome/services/handlers/sitehome-home'; +import { CoreSites } from '@services/sites'; import { makeSingleton } from '@singletons'; import { CoreCourses } from '../courses'; import { CoreDashboardHomeHandler } from './dashboard-home'; /** - * Handler to add my courses into home page. + * Handler to add my courses into main menu. */ @Injectable({ providedIn: 'root' }) -export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeHandler { +export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler { static readonly PAGE_NAME = 'courses'; name = 'CoreCoursesMyCourses'; - priority = 900; + priority = 850; /** - * Check if the handler is enabled on a site level. - * - * @return Whether or not the handler is enabled on a site level. + * @inheritdoc */ - isEnabled(): Promise { - return this.isEnabledForSite(); - } - - /** - * 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. - */ - async isEnabledForSite(siteId?: string): Promise { + async isEnabled(): Promise { + const siteId = CoreSites.getCurrentSiteId(); const disabled = await CoreCourses.isMyCoursesDisabled(siteId); if (disabled) { @@ -59,20 +49,17 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH } /** - * Returns the data needed to render the handler. - * - * @return Data needed to render the handler. + * @inheritdoc */ - getDisplayData(): CoreMainMenuHomeHandlerToDisplay { + getDisplayData(): CoreMainMenuHandlerData { return { title: 'core.courses.mycourses', - page: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME, + page: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, class: 'core-courses-my-courses-handler', icon: 'fas-graduation-cap', - selectPriority: 900, }; } } -export const CoreCoursesMyCoursesHomeHandler = makeSingleton(CoreCoursesMyCoursesHomeHandlerService); +export const CoreCoursesMyCoursesHomeHandler = makeSingleton(CoreCoursesMyCoursesMainMenuHandlerService); From 233e401a23dd960f975e78a38b46bab2a308bff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Oct 2021 16:41:01 +0200 Subject: [PATCH 12/14] MOBILE-3686 quiz: Text wrap fixes on quiz --- .../mod/quiz/components/index/addon-mod-quiz-index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html index dabe2a7a9..94e4bcc51 100644 --- a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html @@ -212,12 +212,12 @@ - + {{ buttonText | translate }} - {{ 'core.openinbrowser' | translate }} From ef610babd77fa4a5579a1b5877e240d66c5de8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 15 Oct 2021 12:10:29 +0200 Subject: [PATCH 13/14] MOBILE-3686 core: Remove some legacy instace usage on singletons --- src/addons/blog/pages/entries/entries.ts | 2 +- src/addons/mod/scorm/pages/player/player.ts | 10 +++++----- src/app/app.component.ts | 2 +- .../features/siteplugins/classes/call-ws-directive.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/addons/blog/pages/entries/entries.ts b/src/addons/blog/pages/entries/entries.ts index 1e92dcc30..165f1f405 100644 --- a/src/addons/blog/pages/entries/entries.ts +++ b/src/addons/blog/pages/entries/entries.ts @@ -170,7 +170,7 @@ export class AddonBlogEntriesPage implements OnInit { entry.contextInstanceId = entry.userid; } - entry.summary = CoreTextUtils.instance.replacePluginfileUrls(entry.summary, entry.summaryfiles || []); + entry.summary = CoreTextUtils.replacePluginfileUrls(entry.summary, entry.summaryfiles || []); return CoreUser.getProfile(entry.userid, entry.courseid, true).then((user) => { entry.user = user; diff --git a/src/addons/mod/scorm/pages/player/player.ts b/src/addons/mod/scorm/pages/player/player.ts index ad6555c82..9c4bdc9c9 100644 --- a/src/addons/mod/scorm/pages/player/player.ts +++ b/src/addons/mod/scorm/pages/player/player.ts @@ -118,7 +118,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { try { await this.setStartTime(this.currentSco.id); } catch (error) { - CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); } } @@ -198,7 +198,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { try { AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt); } catch (error) { - CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true); + CoreDomUtils.showErrorModalDefault(error, 'core.error', true); } this.refreshToc(); @@ -303,7 +303,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { this.userData = data; this.accessInfo = accessInfo; } catch (error) { - CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); } } @@ -469,7 +469,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { await AddonModScorm.saveTracks(sco.id, this.attempt, tracks, this.scorm, true); } catch (error) { - CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true); + CoreDomUtils.showErrorModalDefault(error, 'core.error', true); } } finally { // Refresh TOC, some prerequisites might have changed. @@ -510,7 +510,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { await this.fetchToc(); } catch (error) { - CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true); } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 84047aeca..8d21b8dfb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -119,7 +119,7 @@ export class AppComponent implements OnInit, AfterViewInit { }); CoreUtils.closeInAppBrowser(); - } else if (CoreApp.instance.isAndroid()) { + } else if (CoreApp.isAndroid()) { // Check if the URL has a custom URL scheme. In Android they need to be opened manually. const urlScheme = CoreUrlUtils.getUrlProtocol(url); if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') { diff --git a/src/core/features/siteplugins/classes/call-ws-directive.ts b/src/core/features/siteplugins/classes/call-ws-directive.ts index 58f9b73ab..53caf6458 100644 --- a/src/core/features/siteplugins/classes/call-ws-directive.ts +++ b/src/core/features/siteplugins/classes/call-ws-directive.ts @@ -124,7 +124,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy { invalidate(): Promise { const params = this.getParamsForWS(); - return CoreSitePlugins.instance.invalidateCallWS(this.name, params, this.preSets); + return CoreSitePlugins.invalidateCallWS(this.name, params, this.preSets); } /** From f5f5fd67816fa3c7e1b2dd83c1c0781a09e30d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 19 Oct 2021 11:00:33 +0200 Subject: [PATCH 14/14] MOBILE-3686 core: Add 2-way data binding to context menu toggles --- .../messages/pages/discussion/discussion.html | 4 ++-- .../pages/discussion/discussion.page.ts | 9 --------- .../context-menu/context-menu-item.ts | 18 ++++++++++++++++- .../context-menu/context-menu-popover.ts | 6 ++++++ .../core-context-menu-popover.html | 16 +++++++-------- .../course/pages/contents/contents.html | 4 ++-- .../course/pages/contents/contents.ts | 9 +++------ .../courses/pages/categories/categories.html | 8 ++++---- .../courses/pages/categories/categories.ts | 19 ++++++------------ .../courses/pages/dashboard/dashboard.html | 4 ++-- .../courses/pages/dashboard/dashboard.ts | 11 ++++------ .../features/courses/pages/list/list.html | 20 +++++++++---------- src/core/features/courses/pages/list/list.ts | 17 +++++----------- src/core/features/courses/services/courses.ts | 7 ++----- .../features/sitehome/pages/index/index.html | 4 ++-- .../features/sitehome/pages/index/index.ts | 9 +++------ 16 files changed, 76 insertions(+), 89 deletions(-) diff --git a/src/addons/messages/pages/discussion/discussion.html b/src/addons/messages/pages/discussion/discussion.html index 94fa726af..f38237550 100644 --- a/src/addons/messages/pages/discussion/discussion.html +++ b/src/addons/messages/pages/discussion/discussion.html @@ -44,8 +44,8 @@ 'addon.messages.muteconversation') | translate" (action)="changeMute($event)" [closeOnClick]="false" [iconAction]="muteIcon"> + [content]="'addon.messages.showdeletemessages' | translate" + iconAction="toggle" [(toggle)]="showDelete"> diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index c99f24161..bdff8c4f9 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -1263,15 +1263,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView return !nextMessage || nextMessage.useridfrom != message.useridfrom || !!nextMessage.showDate; } - /** - * Toggles delete state. - * - * @param enabled Wether to enable or disable show delete toggle. - */ - toggleDelete(enable: boolean): void { - this.showDelete = enable; - } - /** * View info. If it's an individual conversation, go to the user profile. * If it's a group conversation, view info about the group. diff --git a/src/core/components/context-menu/context-menu-item.ts b/src/core/components/context-menu/context-menu-item.ts index 4129ef889..2d6a66587 100644 --- a/src/core/components/context-menu/context-menu-item.ts +++ b/src/core/components/context-menu/context-menu-item.ts @@ -38,7 +38,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange @Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item. @Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click. // If is "spinner" an spinner will be shown. - // If is "toggle" an toggle switch will be shown. + // If is "toggle" a toggle switch will be shown. // If no icon or spinner is selected, no action or link will work. // If href but no iconAction is provided arrow-right will be used. @Input() iconSlash?: boolean; // Display a red slash over the icon. @@ -56,6 +56,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange @Input() toggle = false; // Whether the toggle is on or off. @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked. @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked. + @Output() toggleChange = new EventEmitter();// Will emit an event when toggle changes to enable 2-way data binding. protected hasAction = false; protected destroyed = false; @@ -89,6 +90,21 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange } } + /** + * Toggle changed. + * + * @param event Event. + */ + toggleChanged(event: Event): void { + if (this.toggle === undefined) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + this.toggleChange.emit(this.toggle); + } + /** * Component destroyed. */ diff --git a/src/core/components/context-menu/context-menu-popover.ts b/src/core/components/context-menu/context-menu-popover.ts index 17e6e1885..46461b45f 100644 --- a/src/core/components/context-menu/context-menu-popover.ts +++ b/src/core/components/context-menu/context-menu-popover.ts @@ -55,6 +55,12 @@ export class CoreContextMenuPopoverComponent { * @return Return true if success, false if error. */ itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean { + if (item.iconAction == 'toggle' && !event.defaultPrevented) { + event.preventDefault(); + event.stopPropagation(); + item.toggle = !item.toggle; + } + if (!!item.action && item.action.observers.length > 0) { event.preventDefault(); event.stopPropagation(); diff --git a/src/core/components/context-menu/core-context-menu-popover.html b/src/core/components/context-menu/core-context-menu-popover.html index 4baeb6c85..36091feec 100644 --- a/src/core/components/context-menu/core-context-menu-popover.html +++ b/src/core/components/context-menu/core-context-menu-popover.html @@ -12,14 +12,14 @@

- - - - - + + + + + {{item.badge}} diff --git a/src/core/features/course/pages/contents/contents.html b/src/core/features/course/pages/contents/contents.html index 938d20ba6..1e2ab177a 100644 --- a/src/core/features/course/pages/contents/contents.html +++ b/src/core/features/course/pages/contents/contents.html @@ -1,7 +1,7 @@ - + { - this.toggleDownload(data.enabled); + this.downloadEnabled = this.displayEnableDownload && data.enabled; }); } @@ -494,12 +494,9 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { /** * Toggle download enabled. - * - * @param enable Whether enable or disable download enabled toggle. */ - toggleDownload(enable: boolean): void { - this.downloadEnabled = - CoreCourses.setCourseDownloadOptionsEnabled(this.displayEnableDownload && enable); + toggleDownload(): void { + CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled); } /** diff --git a/src/core/features/courses/pages/categories/categories.html b/src/core/features/courses/pages/categories/categories.html index 33fb19de2..7ecd32f4b 100644 --- a/src/core/features/courses/pages/categories/categories.html +++ b/src/core/features/courses/pages/categories/categories.html @@ -10,11 +10,11 @@ + [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()" + iconAction="toggle" [(toggle)]="downloadEnabled"> + [content]="'core.courses.showonlyenrolled' | translate" (action)="filterEnrolled()" + iconAction="toggle" [(toggle)]="showOnlyEnrolled"> diff --git a/src/core/features/courses/pages/categories/categories.ts b/src/core/features/courses/pages/categories/categories.ts index 34dd3c7a0..3cce72f0a 100644 --- a/src/core/features/courses/pages/categories/categories.ts +++ b/src/core/features/courses/pages/categories/categories.ts @@ -76,7 +76,7 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { }, this.currentSiteId); this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { - this.toggleDownload(data.enabled); + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled; }); } @@ -133,7 +133,7 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { try { this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId); - await this.toggleEnrolled(this.showOnlyEnrolled); + await this.filterEnrolled(); } catch (error) { !this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); } @@ -178,13 +178,9 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { } /** - * Toggle show only my courses. - * - * @param enable If enable or disable. + * Filter my courses or not. */ - async toggleEnrolled(enable: boolean): Promise { - this.showOnlyEnrolled = enable; - + async filterEnrolled(): Promise { if (!this.showOnlyEnrolled) { this.courses = this.categoryCourses; } else { @@ -207,12 +203,9 @@ export class CoreCoursesCategoriesPage implements OnInit, OnDestroy { /** * Toggle download enabled. - * - * @param enable If enable or disable. */ - toggleDownload(enable: boolean): void { - this.downloadEnabled = - CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); + toggleDownload(): void { + CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled); } /** diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html index a7dc2fc79..c4315e7d1 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.html +++ b/src/core/features/courses/pages/dashboard/dashboard.html @@ -4,8 +4,8 @@
+ [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()" + iconAction="toggle" [(toggle)]="downloadEnabled"> diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 712f893e5..5102614cc 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -54,11 +54,11 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - this.switchDownload(this.downloadEnabled); + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled; }, CoreSites.getCurrentSiteId()); this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { - this.switchDownload(data.enabled); + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled; }); } @@ -149,12 +149,9 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { /** * Switch download enabled. - * - * @param enable If enable or disable. */ - switchDownload(enable: boolean): void { - this.downloadEnabled = - CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); + switchDownload(): void { + CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled); } /** diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html index 89108f71e..114a5a78b 100644 --- a/src/core/features/courses/pages/list/list.html +++ b/src/core/features/courses/pages/list/list.html @@ -6,19 +6,19 @@

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

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

- - - - - - + + + + + + diff --git a/src/core/features/courses/pages/list/list.ts b/src/core/features/courses/pages/list/list.ts index 9e192dc82..04d855ffb 100644 --- a/src/core/features/courses/pages/list/list.ts +++ b/src/core/features/courses/pages/list/list.ts @@ -86,7 +86,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled; - if (!this.searchEnabled) { + if (!this.searchEnabled && this.searchMode) { this.searchMode = false; this.fetchCourses(); @@ -94,7 +94,7 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { }, this.currentSiteId); this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { - this.toggleDownload(data.enabled); + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled; }); } @@ -287,24 +287,17 @@ export class CoreCoursesListPage implements OnInit, OnDestroy { /** * Toggle show only my courses. - * - * @param enable If enable or disable. */ - toggleEnrolled(enable: boolean): void { + toggleEnrolled(): void { this.loaded = false; - this.showOnlyEnrolled = enable; - this.fetchCourses(); } /** * Toggle download enabled. - * - * @param enable If enable or disable. */ - toggleDownload(enable: boolean): void { - this.downloadEnabled = - CoreCourses.setCourseDownloadOptionsEnabled((this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable); + toggleDownload(): void { + CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled); } /** diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index 9ccd82fbc..fa9a973c9 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -1229,17 +1229,14 @@ export class CoreCoursesProvider { * Set trigger and save the download option. * * @param enable True to enable, false to disable. - * @return Current status. */ - setCourseDownloadOptionsEnabled(enable: boolean): boolean { + setCourseDownloadOptionsEnabled(enable: boolean): void { if (this.downloadOptionsEnabled == enable) { - return enable; + return; } this.downloadOptionsEnabled = enable; CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: enable }); - - return enable; } } diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index 179a73f10..4dd2ce8d5 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -4,8 +4,8 @@
+ [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()" + iconAction="toggle" [(toggle)]="downloadEnabled"> diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 81ecc1c03..777d6ce9c 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -66,7 +66,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { }, CoreSites.getCurrentSiteId()); this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => { - this.switchDownload(data.enabled); + this.downloadEnabled = data.enabled; }); } @@ -195,12 +195,9 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { /** * Switch download enabled. - * - * @param enable If enable or disable. */ - switchDownload(enable: boolean): void { - this.downloadEnabled = - CoreCourses.setCourseDownloadOptionsEnabled(enable); + switchDownload(): void { + CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled); } /**