diff --git a/src/core/components/infinite-loading/infinite-loading.ts b/src/core/components/infinite-loading/infinite-loading.ts index ab0a4b7fa..87e2125b7 100644 --- a/src/core/components/infinite-loading/infinite-loading.ts +++ b/src/core/components/infinite-loading/infinite-loading.ts @@ -22,7 +22,7 @@ const THRESHOLD = .15; // % of the scroll element height that must be close to t * Component to show a infinite loading trigger and spinner while more data is being loaded. * * Usage: - * + * */ @Component({ selector: 'core-infinite-loading', diff --git a/src/core/features/course/components/course-format/course-format.html b/src/core/features/course/components/course-format/course-format.html index 2e1f6aeb5..a98e46850 100644 --- a/src/core/features/course/components/course-format/course-format.html +++ b/src/core/features/course/components/course-format/course-format.html @@ -21,7 +21,7 @@
- + diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index 3c432a9c1..341f3d215 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -70,7 +70,7 @@ import { CoreBlockSideBlocksComponent } from '@features/block/components/side-bl }) export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { - static readonly LOAD_MORE_ACTIVITIES = 20; // How many activities should load each time showMoreActivities is called. + static readonly LOAD_MORE_ACTIVITIES = 10; // How many activities should load each time showMoreActivities is called. @Input() course!: CoreCourseAnyCourseData; // The course to render. @Input() sections: CoreCourseSectionToDisplay[] = []; // List of course sections. @@ -89,7 +89,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { allSectionsComponent?: Type; canLoadMore = false; - showSectionId = 0; + lastShownSectionIndex = 0; data: Record = {}; // Data to pass to the components. courseIndexTour: CoreUserTourDirectiveOptions = { id: 'course-index', @@ -514,8 +514,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } else { this.previousSection = undefined; this.nextSection = undefined; - this.canLoadMore = false; - this.showSectionId = 0; + this.lastShownSectionIndex = -1; this.showMoreActivities(); } @@ -594,40 +593,22 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. */ showMoreActivities(infiniteComplete?: () => void): void { - this.canLoadMore = false; - - const sections = this.sections || []; let modulesLoaded = 0; - let i: number; - for (i = this.showSectionId + 1; i < sections.length; i++) { - if (!sections[i].hasContent || !sections[i].modules) { + while (this.lastShownSectionIndex < this.sections.length - 1 && + modulesLoaded < CoreCourseFormatComponent.LOAD_MORE_ACTIVITIES) { + this.lastShownSectionIndex++; + + if (!this.sections[this.lastShownSectionIndex].hasContent || !this.sections[this.lastShownSectionIndex].modules) { continue; } - modulesLoaded += sections[i].modules.reduce((total, module) => - !CoreCourseHelper.isModuleStealth(module, sections[i]) ? total + 1 : total, 0); - - if (modulesLoaded >= CoreCourseFormatComponent.LOAD_MORE_ACTIVITIES) { - break; - } + modulesLoaded += this.sections[this.lastShownSectionIndex].modules.reduce((total, module) => + !CoreCourseHelper.isModuleStealth(module, this.sections[this.lastShownSectionIndex]) ? total + 1 : total, 0); } - this.showSectionId = i; - this.canLoadMore = i < sections.length; + this.canLoadMore = this.lastShownSectionIndex < this.sections.length - 1; - if (this.canLoadMore) { - // Check if any of the following sections have any content. - let thereAreMore = false; - for (i++; i < sections.length; i++) { - if (sections[i].hasContent && sections[i].modules && sections[i].modules?.length > 0) { - thereAreMore = true; - break; - } - } - this.canLoadMore = thereAreMore; - } - - infiniteComplete && infiniteComplete(); + infiniteComplete?.(); } /** diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html index 209ee9b14..ea67ff1c2 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -28,7 +28,7 @@ {{ 'core.course.hiddenfromstudents' | translate }} - + {{ 'core.course.hiddenoncoursepage' | translate }}
@@ -80,14 +80,14 @@ -
-
+
@@ -95,7 +95,7 @@
-
diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts index cccd63e81..439b3dab6 100644 --- a/src/core/features/course/components/module/module.ts +++ b/src/core/features/course/components/module/module.ts @@ -50,6 +50,11 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { @Input() showActivityDates = false; // Whether to show activity dates. @Input() showCompletionConditions = false; // Whether to show activity completion conditions. @Input() showLegacyCompletion?: boolean; // Whether to show module completion in the old format. + @Input() showCompletion = true; // Whether to show module completion. + @Input() showAvailability = true; // Whether to show module availability. + @Input() showExtra = true; // Whether to show extra badges. + @Input() showDownloadStatus = true; // Whether to show download status. + @Input() showIndentation = true; // Whether to show indentation @Input() isLastViewed = false; // Whether it's the last module viewed in a course. @Output() completionChanged = new EventEmitter(); // Notify when module completion changes. @HostBinding('class.indented') indented = false; @@ -70,14 +75,24 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { */ async ngOnInit(): Promise { const site = CoreSites.getRequiredCurrentSite(); - const enableIndentation = await CoreCourse.isCourseIndentationEnabled(site, this.module.course); - this.indented = enableIndentation && this.module.indent > 0; + if (this.showIndentation && this.module.indent > 0) { + this.indented = await CoreCourse.isCourseIndentationEnabled(site, this.module.course); + } else { + this.indented = false; + } this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname, this.module.modplural); - this.showLegacyCompletion = this.showLegacyCompletion ?? - CoreConstants.CONFIG.uselegacycompletion ?? - !site.isVersionGreaterEqualThan('3.11'); - this.checkShowCompletion(); + if (this.showCompletion) { + this.showLegacyCompletion = this.showLegacyCompletion ?? + CoreConstants.CONFIG.uselegacycompletion ?? + !site.isVersionGreaterEqualThan('3.11'); + this.checkShowCompletion(); + } else { + this.showLegacyCompletion = false; + this.showCompletionConditions = false; + this.showManualCompletion = false; + this.hasCompletion = false; + } if (!this.module.handlerData) { return; @@ -86,7 +101,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title; this.moduleHasView = CoreCourse.moduleHasView(this.module); - if (this.module.handlerData?.showDownloadButton) { + if (this.showDownloadStatus && this.module.handlerData.showDownloadButton) { const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.module.course); this.updateModuleStatus(status); diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index 31d6a537b..88345545b 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -298,7 +298,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon await this.loadData(true, true); } finally { // Do not call doRefresh on the format component if the refresher is defined in the format component - // to prevent an inifinite loop. + // to prevent an infinite loop. if (this.displayRefresher && this.formatComponent) { await CoreUtils.ignoreErrors(this.formatComponent.doRefresh(refresher)); } diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.html b/src/core/features/course/pages/list-mod-type/list-mod-type.html index edf62be15..92d8c00f1 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.html +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.html @@ -17,12 +17,24 @@ - - - - + + + + +

+ + +

+
+
+ + + +
+ diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.ts b/src/core/features/course/pages/list-mod-type/list-mod-type.ts index c27314dcc..d10977796 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.ts +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.ts @@ -34,10 +34,14 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; }) export class CoreCourseListModTypePage implements OnInit { + private static readonly PAGE_LENGTH = 10; // How many activities should load each time showMoreActivities is called. + sections: CoreCourseSection[] = []; title = ''; loaded = false; - courseId?: number; + courseId = 0; + canLoadMore = false; + lastShownSectionIndex = -1; protected modName?: string; protected archetypes: Record = {}; // To speed up the check of modules. @@ -64,9 +68,16 @@ export class CoreCourseListModTypePage implements OnInit { * @inheritdoc */ async ngOnInit(): Promise { - this.title = CoreNavigator.getRouteParam('title') || ''; - this.courseId = CoreNavigator.getRouteNumberParam('courseId'); - this.modName = CoreNavigator.getRouteParam('modName'); + try { + this.title = CoreNavigator.getRouteParam('title') || ''; + this.courseId = CoreNavigator.getRequiredRouteParam('courseId'); + this.modName = CoreNavigator.getRequiredRouteParam('modName'); + } catch (error) { + CoreDomUtils.showErrorModal(error); + CoreNavigator.back(); + + return; + } try { await this.fetchData(); @@ -77,8 +88,6 @@ export class CoreCourseListModTypePage implements OnInit { /** * Fetches the data. - * - * @returns Resolved when done. */ protected async fetchData(): Promise { if (!this.courseId) { @@ -90,12 +99,14 @@ export class CoreCourseListModTypePage implements OnInit { let sections = await CoreCourse.getSections(this.courseId, false, true); sections = sections.filter((section) => { - if (!section.modules) { + if (!section.modules.length || section.hiddenbynumsections) { return false; } section.modules = section.modules.filter((mod) => { - if (!CoreCourseHelper.canUserViewModule(mod, section) || !CoreCourse.moduleHasView(mod)) { + if (!CoreCourseHelper.canUserViewModule(mod, section) || + !CoreCourse.moduleHasView(mod) || + mod.visibleoncoursepage === 0) { // Ignore this module. return false; } @@ -110,11 +121,11 @@ export class CoreCourseListModTypePage implements OnInit { ); } - if (this.archetypes[mod.modname] == CoreConstants.MOD_ARCHETYPE_RESOURCE) { + if (this.archetypes[mod.modname] === CoreConstants.MOD_ARCHETYPE_RESOURCE) { return true; } - } else if (mod.modname == this.modName) { + } else if (mod.modname === this.modName) { return true; } }); @@ -125,19 +136,39 @@ export class CoreCourseListModTypePage implements OnInit { const result = await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId); this.sections = result.sections; + + this.lastShownSectionIndex = -1; + this.showMoreActivities(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error getting data'); } } + /** + * Show more activities. + * + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + */ + showMoreActivities(infiniteComplete?: () => void): void { + let modulesLoaded = 0; + while (this.lastShownSectionIndex < this.sections.length - 1 && modulesLoaded < CoreCourseListModTypePage.PAGE_LENGTH) { + this.lastShownSectionIndex++; + + modulesLoaded += this.sections[this.lastShownSectionIndex].modules.length; + } + + this.canLoadMore = this.lastShownSectionIndex < this.sections.length - 1; + + infiniteComplete?.(); + } + /** * Refresh the data. * * @param refresher Refresher. - * @returns Promise resolved when done. */ async refreshData(refresher: IonRefresher): Promise { - await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.courseId || 0)); + await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.courseId)); try { await this.fetchData(); diff --git a/src/core/features/course/pages/module-preview/module-preview.html b/src/core/features/course/pages/module-preview/module-preview.html index c2656b26b..7df03de9d 100644 --- a/src/core/features/course/pages/module-preview/module-preview.html +++ b/src/core/features/course/pages/module-preview/module-preview.html @@ -40,7 +40,7 @@ {{ 'core.course.hiddenfromstudents' | translate }} - + {{ 'core.course.hiddenoncoursepage' | translate }}
diff --git a/src/core/features/course/tests/behat/basic_usage-311.feature b/src/core/features/course/tests/behat/basic_usage-311.feature index 99b642baf..32644ce60 100755 --- a/src/core/features/course/tests/behat/basic_usage-311.feature +++ b/src/core/features/course/tests/behat/basic_usage-311.feature @@ -85,20 +85,20 @@ Feature: Test basic usage of one course in app And I press "OK" in the app And I wait loading to finish in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app Scenario: Guest access Given I entered the course "Course 1" as "teacher1" in the app @@ -122,16 +122,16 @@ Feature: Test basic usage of one course in app When I press "View course" "ion-button" in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app diff --git a/src/core/features/course/tests/behat/basic_usage.feature b/src/core/features/course/tests/behat/basic_usage.feature index 8cb9e184d..c1e9904cd 100755 --- a/src/core/features/course/tests/behat/basic_usage.feature +++ b/src/core/features/course/tests/behat/basic_usage.feature @@ -83,25 +83,33 @@ Feature: Test basic usage of one course in app Scenario: View course contents When I entered the course "Course 1" as "student1" in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app When I set "page-core-course-index .core-course-thumb" styles to "background" "lightblue" And I set "page-core-course-index .core-course-thumb img" styles to "display" "none" Then the UI should match the snapshot + # Test infinite scroll on course + When I scroll to "Test workshop name" in the app + Then I should find "Web links" in the app + And I should find "Test glossary" in the app + + # Test Collapsible header + And the UI should match the snapshot + When I press "Choice course 1" in the app Then the header should be "Choice course 1" in the app @@ -161,20 +169,20 @@ Feature: Test basic usage of one course in app Scenario: View section contents When I entered the course "Course 1" as "student1" in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app When I press "Course index" in the app And I press "General" in the app @@ -337,20 +345,20 @@ Feature: Test basic usage of one course in app Scenario: Navigation between sections using the bottom arrows When I entered the course "Course 1" as "student1" in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app When I press "Course index" in the app And I press "General" in the app @@ -425,20 +433,20 @@ Feature: Test basic usage of one course in app And I press "OK" in the app And I wait loading to finish in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app Scenario: View blocks on drawer Given the following "blocks" exist: @@ -447,20 +455,20 @@ Feature: Test basic usage of one course in app | activity_modules | Course | C1 | course-view-* | site-pre | | And I entered the course "Course 1" as "student1" in the app Then the header should be "Course 1" in the app + And I should find "Test forum name" in the app + And I should find "Test wiki name" in the app And I should find "Choice course 1" in the app And I should find "assignment" in the app - And I should find "Test forum name" in the app - And I should find "Test chat name" in the app - And I should find "Web links" in the app And I should find "Test external name" in the app - And I should find "Test feedback name" in the app - And I should find "Test glossary" in the app - And I should find "Quiz 1" in the app And I should find "Test survey name" in the app - And I should find "Test wiki name" in the app - And I should find "Test lesson name" in the app + And I should find "Test chat name" in the app + And I should find "Quiz 1" in the app And I should find "Test scorm name" in the app + And I should find "Test feedback name" in the app + And I should find "Test lesson name" in the app And I should find "Test workshop name" in the app + And I should not find "Web links" in the app + And I should not find "Test glossary" in the app Then I press "Open block drawer" in the app And I should find "HTML title test" in the app And I should find "body test" in the app diff --git a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png index 431c0a806..12ebfcb86 100644 Binary files a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png and b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png differ diff --git a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png new file mode 100644 index 000000000..ddc6ce75b Binary files /dev/null and b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png differ diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 99d13131a..18d615a3a 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1898,10 +1898,12 @@ ion-popover { &.md { margin-top: 2px; margin-bottom: 2px; - } - // Never show backdrop on popovers - ion-backdrop { - background: transparent; + + // Never show backdrop on popovers on Android + // @todo Apply box shadow on ios and make it transparent too. The main problem is the box arrow. + ion-backdrop { + background: transparent; + } } }