From 604e866943fa1124469edc0154f65a16c8966c43 Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Thu, 24 Mar 2022 15:11:13 +0100 Subject: [PATCH 1/8] MOBILE-3833 data: Fix unable to change group for new entries --- src/addons/mod/bigbluebuttonbn/components/index/index.html | 4 ++-- src/addons/mod/data/pages/edit/edit.html | 4 ++-- src/addons/mod/data/pages/edit/edit.ts | 6 ++++-- .../feedback/components/index/addon-mod-feedback-index.html | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.html b/src/addons/mod/bigbluebuttonbn/components/index/index.html index 8003d0166..98308626b 100644 --- a/src/addons/mod/bigbluebuttonbn/components/index/index.html +++ b/src/addons/mod/bigbluebuttonbn/components/index/index.html @@ -23,8 +23,8 @@ <ion-item class="ion-text-wrap core-group-selector"> <ion-label id="addon-bigbluebuttonbn-groupslabel"> - <ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container> - <ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ng-container> </ion-label> <ion-select [(ngModel)]="groupId" (ionChange)="groupChanged()" aria-labelledby="addon-bigbluebuttonbn-groupslabel" interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}"> diff --git a/src/addons/mod/data/pages/edit/edit.html b/src/addons/mod/data/pages/edit/edit.html index 4b04f9b68..875d160f0 100644 --- a/src/addons/mod/data/pages/edit/edit.html +++ b/src/addons/mod/data/pages/edit/edit.html @@ -20,8 +20,8 @@ <core-loading [hideUntil]="loaded"> <ion-item class="ion-text-wrap core-group-selector" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> <ion-label id="addon-data-groupslabel"> - <ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsvisible' | translate }}</ng-container> - <ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsseparate' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ng-container> </ion-label> <ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel" interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}"> diff --git a/src/addons/mod/data/pages/edit/edit.ts b/src/addons/mod/data/pages/edit/edit.ts index 53a73c691..208d9de72 100644 --- a/src/addons/mod/data/pages/edit/edit.ts +++ b/src/addons/mod/data/pages/edit/edit.ts @@ -163,8 +163,10 @@ export class AddonModDataEditPage implements OnInit { const entry = await AddonModDataHelper.fetchEntry(this.database, this.fieldsArray, this.entryId || 0); this.entry = entry.entry; - // Load correct group. - this.selectedGroup = this.entry.groupid; + if (this.entryId) { + // Load correct group. + this.selectedGroup = this.entry.groupid; + } // Check permissions when adding a new entry or offline entry. if (!this.isEditing) { diff --git a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html index 8a72abe19..eb378ee03 100644 --- a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html @@ -61,8 +61,8 @@ <ion-list *ngIf="access && access.canviewanalysis && !access.isempty"> <ion-item class="ion-text-wrap core-group-selector" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)"> <ion-label id="addon-feedback-groupslabel"> - <ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container> - <ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ng-container> + <ng-container *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ng-container> </ion-label> <ion-select [(ngModel)]="group" (ionChange)="setGroup(group)" aria-labelledby="addon-feedback-groupslabel" interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}"> From 9c2116c33b2d9a5f0b004482ce32c9437f9f37dd Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Thu, 24 Mar 2022 16:08:16 +0100 Subject: [PATCH 2/8] MOBILE-3833 data: Hide All participants with visible groups --- src/addons/mod/data/components/index/index.ts | 6 ++++++ src/addons/mod/data/pages/edit/edit.ts | 6 ++++++ src/addons/mod/data/pages/entry/entry.ts | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/src/addons/mod/data/components/index/index.ts b/src/addons/mod/data/components/index/index.ts index 4708f24c7..bb4b1b8dc 100644 --- a/src/addons/mod/data/components/index/index.ts +++ b/src/addons/mod/data/components/index/index.ts @@ -218,6 +218,12 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp } this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule); + if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) { + // There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it. + this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0); + this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id; + } + this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo); this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, { diff --git a/src/addons/mod/data/pages/edit/edit.ts b/src/addons/mod/data/pages/edit/edit.ts index 208d9de72..52abfab1e 100644 --- a/src/addons/mod/data/pages/edit/edit.ts +++ b/src/addons/mod/data/pages/edit/edit.ts @@ -174,6 +174,12 @@ export class AddonModDataEditPage implements OnInit { if (refresh) { this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule); + if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) { + // There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it. + this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0); + this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id; + } + this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo); this.initialSelectedGroup = this.selectedGroup; } diff --git a/src/addons/mod/data/pages/entry/entry.ts b/src/addons/mod/data/pages/entry/entry.ts index 648726a62..6bc442857 100644 --- a/src/addons/mod/data/pages/entry/entry.ts +++ b/src/addons/mod/data/pages/entry/entry.ts @@ -174,6 +174,12 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { this.access = await AddonModData.getDatabaseAccessInformation(this.database.id, { cmId: this.moduleId }); this.groupInfo = await CoreGroups.getActivityGroupInfo(this.database.coursemodule); + if (this.groupInfo.visibleGroups && this.groupInfo.groups?.length) { + // There is a bug in Moodle with All participants and visible groups (MOBILE-3597). Remove it. + this.groupInfo.groups = this.groupInfo.groups.filter(group => group.id !== 0); + this.groupInfo.defaultGroupId = this.groupInfo.groups[0].id; + } + this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo); const actions = AddonModDataHelper.getActions(this.database, this.access, this.entry!); From 1a0733139679f16421a1f64ae9b3898077db52ed Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Thu, 24 Mar 2022 17:29:58 +0100 Subject: [PATCH 3/8] MOBILE-3833 course: Display data ASAP in course downloads --- scripts/langindex.json | 3 +- .../pages/course-storage/course-storage.html | 31 ++++++++----- .../pages/course-storage/course-storage.ts | 43 +++++++++++-------- src/core/lang.json | 1 + 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index ec7f4e8eb..8221281fa 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1453,6 +1453,7 @@ "core.block.tour_navigation_dashboard_content": "tool_usertours", "core.block.tour_navigation_dashboard_title": "tool_usertours", "core.browser": "local_moodlemobileapp", + "core.calculating": "local_moodlemobileapp", "core.cancel": "moodle", "core.cannotconnect": "local_moodlemobileapp", "core.cannotconnecttrouble": "local_moodlemobileapp", @@ -1673,6 +1674,7 @@ "core.editor.underline": "atto_underline/pluginname", "core.editor.unorderedlist": "atto_unorderedlist/pluginname", "core.emptysplit": "local_moodlemobileapp", + "core.endonesteptour": "tool_usertours", "core.error": "moodle", "core.errorchangecompletion": "local_moodlemobileapp", "core.errordeletefile": "local_moodlemobileapp", @@ -2342,7 +2344,6 @@ "core.usernotfullysetup": "error", "core.users": "moodle", "core.usersuspended": "tool_reportbuilder", - "core.endonesteptour": "tool_usertours", "core.view": "moodle", "core.viewcode": "local_moodlemobileapp", "core.vieweditor": "local_moodlemobileapp", diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html index b6353dd6c..b36d1e85d 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.html +++ b/src/addons/storagemanager/pages/course-storage/course-storage.html @@ -24,15 +24,18 @@ <ion-label> <p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p> </ion-label> - <ion-badge color="light" slot="end">{{ totalSize | coreBytesToSize }} + <ion-badge color="light" slot="end"> + <ng-container *ngIf="sizeLoaded">{{ totalSize | coreBytesToSize }}</ng-container> + <ng-container *ngIf="!sizeLoaded">{{ 'core.calculating' | translate }}</ng-container> </ion-badge> </ion-item> - <ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin"> + <ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin" + [disabled]="prefetchCourseData.loading"> <ion-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon> <ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner> {{ prefetchCourseData.statusTranslatable | translate }} </ion-button> - <ion-button *ngIf="totalSize > 0" (click)="deleteForCourse()" expand="block" color="danger" + <ion-button *ngIf="sizeLoaded && totalSize > 0" (click)="deleteForCourse()" expand="block" color="danger" class="ion-no-margin ion-margin-top"> <ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: title }"> @@ -52,18 +55,21 @@ </core-format-text> </p> <ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'" - *ngIf="section.totalSize > 0"> + *ngIf="section.sizeLoaded && section.totalSize > 0"> <ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded" [attr.aria-label]="'core.downloaded' | translate"> </ion-icon>{{ section.totalSize | coreBytesToSize }} </ion-badge> + <ion-badge color="light" *ngIf="!section.sizeLoaded"> + {{ 'core.calculating' | translate }} + </ion-badge> <!-- Download progress. --> <p *ngIf="downloadEnabled && section.isDownloading"> <core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total"> </core-progress-bar> </p> </ion-label> - <div class="storage-buttons" slot="end" *ngIf="section.totalSize > 0 || downloadEnabled"> + <div class="storage-buttons" slot="end" *ngIf="(section.sizeLoaded && section.totalSize > 0) || downloadEnabled"> <div *ngIf="downloadEnabled" slot="end" class="core-button-spinner"> <core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded" [status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)" @@ -77,7 +83,8 @@ {{section.count}} / {{section.total}} </ion-badge> </div> - <ion-button (click)="deleteForSection(section)" *ngIf="section.totalSize > 0" color="danger" fill="clear"> + <ion-button (click)="deleteForSection(section)" *ngIf="section.sizeLoaded && section.totalSize > 0" + color="danger" fill="clear"> <ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }"> </ion-icon> @@ -87,7 +94,8 @@ </ion-card-header> <ion-card-content> <ng-container *ngFor="let module of section.modules"> - <ion-item class="ion-no-padding core-course-storage-activity" *ngIf="downloadEnabled || module.totalSize > 0"> + <ion-item class="ion-no-padding core-course-storage-activity" + *ngIf="downloadEnabled || (module.sizeLoaded && module.totalSize > 0)"> <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" [modname]="module.modname" [componentId]="module.instance"> </core-mod-icon> @@ -98,11 +106,14 @@ </core-format-text> </h3> <ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" - *ngIf="module.totalSize > 0"> + *ngIf="module.sizeLoaded && module.totalSize > 0"> <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded" [attr.aria-label]="'core.downloaded' | translate"> </ion-icon>{{ module.totalSize | coreBytesToSize }} </ion-badge> + <ion-badge color="light" *ngIf="!module.sizeLoaded"> + {{ 'core.calculating' | translate }} + </ion-badge> </ion-label> <div class="storage-buttons" slot="end"> @@ -111,8 +122,8 @@ [canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner" (action)="prefetchModule(module, section)"> </core-download-refresh> - <ion-button fill="clear" (click)="deleteForModule(module, section)" *ngIf="module.totalSize > 0" - color="danger"> + <ion-button fill="clear" (click)="deleteForModule(module, section)" + *ngIf="module.sizeLoaded && module.totalSize > 0" color="danger"> <ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> </ion-icon> diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts index aa14b5269..5c3f371cb 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts @@ -48,6 +48,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { loaded = false; sections: AddonStorageManagerCourseSection[] = []; totalSize = 0; + sizeLoaded = false; downloadEnabled = false; downloadCourseEnabled = false; @@ -107,13 +108,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections .map((section) => ({ ...section, totalSize: 0 })); + this.loaded = true; + await Promise.all([ this.loadSizes(), this.initCoursePrefetch(), this.initModulePrefetch(), ]); - - this.loaded = true; } /** @@ -241,12 +242,15 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { */ protected async loadSizes(): Promise<void> { this.totalSize = 0; + this.sizeLoaded = false; - const promises: Promise<void>[] = []; - this.sections.forEach((section) => { + await Promise.all(this.sections.map(async (section) => { section.totalSize = 0; - section.modules.forEach((module) => { + section.sizeLoaded = false; + + await Promise.all(section.modules.map(async (module) => { module.totalSize = 0; + module.sizeLoaded = false; // Note: This function only gets the size for modules which are downloadable. // For other modules it always returns 0, even if they have downloaded some files. @@ -255,21 +259,22 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { // But these aren't necessarily consistent, for example mod_frog vs mmaModFrog. // There is nothing enforcing correct values. // Most modules which have large files are downloadable, so I think this is sufficient. - const promise = CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId).then((size) => { - // There are some cases where the return from this is not a valid number. - if (!isNaN(size)) { - module.totalSize = Number(size); - section.totalSize += size; - this.totalSize += size; - } + const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId); - return; - }); - promises.push(promise); - }); - }); + // There are some cases where the return from this is not a valid number. + if (!isNaN(size)) { + module.totalSize = Number(size); + section.totalSize += size; + this.totalSize += size; + } - await Promise.all(promises); + module.sizeLoaded = true; + })); + + section.sizeLoaded = true; + })); + + this.sizeLoaded = true; // Mark course as not downloaded if course size is 0. if (this.totalSize == 0) { @@ -608,11 +613,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { totalSize: number; + sizeLoaded?: boolean; modules: AddonStorageManagerModule[]; }; type AddonStorageManagerModule = CoreCourseModuleData & { totalSize?: number; + sizeLoaded?: boolean; prefetchHandler?: CoreCourseModulePrefetchHandler; spinner?: boolean; downloadStatus?: string; diff --git a/src/core/lang.json b/src/core/lang.json index 8dd623b4e..a66c1a6a3 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -13,6 +13,7 @@ "areyousure": "Are you sure?", "back": "Back", "browser": "Browser", + "calculating": "Calculating", "cancel": "Cancel", "cannotconnect": "Cannot connect", "cannotconnecttrouble": "We're having trouble connecting to your site.", From 37af8e3c69b4338a874fe39fe8bb5fd85c5e39a2 Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Mon, 28 Mar 2022 12:54:14 +0200 Subject: [PATCH 4/8] MOBILE-3833 course: Improve performance after download or delete --- .../pages/course-storage/course-storage.html | 23 ++--- .../pages/course-storage/course-storage.ts | 91 ++++++++++++++----- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html index b36d1e85d..57b45f33c 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.html +++ b/src/addons/storagemanager/pages/course-storage/course-storage.html @@ -25,8 +25,8 @@ <p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p> </ion-label> <ion-badge color="light" slot="end"> - <ng-container *ngIf="sizeLoaded">{{ totalSize | coreBytesToSize }}</ng-container> - <ng-container *ngIf="!sizeLoaded">{{ 'core.calculating' | translate }}</ng-container> + <ng-container *ngIf="!calculatingSize">{{ totalSize | coreBytesToSize }}</ng-container> + <ng-container *ngIf="calculatingSize">{{ 'core.calculating' | translate }}</ng-container> </ion-badge> </ion-item> <ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin" @@ -35,7 +35,7 @@ <ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner> {{ prefetchCourseData.statusTranslatable | translate }} </ion-button> - <ion-button *ngIf="sizeLoaded && totalSize > 0" (click)="deleteForCourse()" expand="block" color="danger" + <ion-button [disabled]="calculatingSize || totalSize <= 0" (click)="deleteForCourse()" expand="block" color="danger" class="ion-no-margin ion-margin-top"> <ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: title }"> @@ -55,12 +55,12 @@ </core-format-text> </p> <ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'" - *ngIf="section.sizeLoaded && section.totalSize > 0"> + *ngIf="!section.calculatingSize && section.totalSize > 0"> <ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded" [attr.aria-label]="'core.downloaded' | translate"> </ion-icon>{{ section.totalSize | coreBytesToSize }} </ion-badge> - <ion-badge color="light" *ngIf="!section.sizeLoaded"> + <ion-badge color="light" *ngIf="section.calculatingSize"> {{ 'core.calculating' | translate }} </ion-badge> <!-- Download progress. --> @@ -69,7 +69,8 @@ </core-progress-bar> </p> </ion-label> - <div class="storage-buttons" slot="end" *ngIf="(section.sizeLoaded && section.totalSize > 0) || downloadEnabled"> + <div class="storage-buttons" slot="end" + *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled"> <div *ngIf="downloadEnabled" slot="end" class="core-button-spinner"> <core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded" [status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)" @@ -83,7 +84,7 @@ {{section.count}} / {{section.total}} </ion-badge> </div> - <ion-button (click)="deleteForSection(section)" *ngIf="section.sizeLoaded && section.totalSize > 0" + <ion-button (click)="deleteForSection(section)" *ngIf="!section.calculatingSize && section.totalSize > 0" color="danger" fill="clear"> <ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }"> @@ -95,7 +96,7 @@ <ion-card-content> <ng-container *ngFor="let module of section.modules"> <ion-item class="ion-no-padding core-course-storage-activity" - *ngIf="downloadEnabled || (module.sizeLoaded && module.totalSize > 0)"> + *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" [modname]="module.modname" [componentId]="module.instance"> </core-mod-icon> @@ -106,12 +107,12 @@ </core-format-text> </h3> <ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" - *ngIf="module.sizeLoaded && module.totalSize > 0"> + *ngIf="!module.calculatingSize && module.totalSize > 0"> <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded" [attr.aria-label]="'core.downloaded' | translate"> </ion-icon>{{ module.totalSize | coreBytesToSize }} </ion-badge> - <ion-badge color="light" *ngIf="!module.sizeLoaded"> + <ion-badge color="light" *ngIf="module.calculatingSize"> {{ 'core.calculating' | translate }} </ion-badge> </ion-label> @@ -123,7 +124,7 @@ (action)="prefetchModule(module, section)"> </core-download-refresh> <ion-button fill="clear" (click)="deleteForModule(module, section)" - *ngIf="module.sizeLoaded && module.totalSize > 0" color="danger"> + *ngIf="!module.calculatingSize && module.totalSize > 0" color="danger"> <ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> </ion-icon> diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts index 5c3f371cb..39c3d42d0 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts @@ -48,7 +48,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { loaded = false; sections: AddonStorageManagerCourseSection[] = []; totalSize = 0; - sizeLoaded = false; + calculatingSize = true; downloadEnabled = false; downloadCourseEnabled = false; @@ -106,12 +106,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { const sections = await CoreCourse.getSections(this.courseId, false, true); this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections - .map((section) => ({ ...section, totalSize: 0 })); + .map(section => ({ + ...section, + totalSize: 0, + calculatingSize: true, + modules: section.modules.map(module => ({ + ...module, + calculatingSize: true, + })), + })); this.loaded = true; await Promise.all([ - this.loadSizes(), + this.initSizes(), this.initCoursePrefetch(), this.initModulePrefetch(), ]); @@ -240,18 +248,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * Init section, course and modules sizes. */ - protected async loadSizes(): Promise<void> { - this.totalSize = 0; - this.sizeLoaded = false; - + protected async initSizes(): Promise<void> { await Promise.all(this.sections.map(async (section) => { - section.totalSize = 0; - section.sizeLoaded = false; - await Promise.all(section.modules.map(async (module) => { - module.totalSize = 0; - module.sizeLoaded = false; - // Note: This function only gets the size for modules which are downloadable. // For other modules it always returns 0, even if they have downloaded some files. // However there is no 100% reliable way to actually track the files in this case. @@ -268,13 +267,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { this.totalSize += size; } - module.sizeLoaded = true; + module.calculatingSize = false; })); - section.sizeLoaded = true; + section.calculatingSize = false; })); - this.sizeLoaded = true; + this.calculatingSize = false; // Mark course as not downloaded if course size is 0. if (this.totalSize == 0) { @@ -282,6 +281,56 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } } + /** + * Update the sizes of some modules. + * + * @param modules Modules. + * @param section Section the modules belong to. + * @return Promise resolved when done. + */ + protected async updateModulesSizes( + modules: AddonStorageManagerModule[], + section?: AddonStorageManagerCourseSection, + ): Promise<void> { + this.calculatingSize = true; + + await Promise.all(modules.map(async (module) => { + if (module.calculatingSize) { + return; + } + + module.calculatingSize = true; + + if (!section) { + section = this.sections.find((section) => section.modules.some((mod) => mod.id === module.id)); + if (section) { + section.calculatingSize = true; + } + } + + try { + const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId); + + const diff = (isNaN(size) ? 0 : size) - (module.totalSize ?? 0); + + module.totalSize = Number(size); + this.totalSize += diff; + if (section) { + section.totalSize += diff; + } + } catch { + // Ignore errors, it shouldn't happen. + } finally { + module.calculatingSize = false; + } + })); + + this.calculatingSize = false; + if (section) { + section.calculatingSize = false; + } + } + /** * The user has requested a delete for the whole course data. * @@ -406,7 +455,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } finally { modal.dismiss(); - await this.loadSizes(); + await this.updateModulesSizes(modules, section); CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false); } } @@ -455,7 +504,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true); } } finally { - await this.loadSizes(); + await this.updateModulesSizes(section.modules, section); } } catch (error) { // User cancelled or there was an error calculating the size. @@ -501,7 +550,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } finally { module.spinner = false; - await this.loadSizes(); + await this.updateModulesSizes([module]); } } @@ -613,13 +662,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { totalSize: number; - sizeLoaded?: boolean; + calculatingSize: boolean; modules: AddonStorageManagerModule[]; }; type AddonStorageManagerModule = CoreCourseModuleData & { totalSize?: number; - sizeLoaded?: boolean; + calculatingSize: boolean; prefetchHandler?: CoreCourseModulePrefetchHandler; spinner?: boolean; downloadStatus?: string; From 0298273fc4c62b88b8473790d64cbaf976d6d246 Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Tue, 29 Mar 2022 12:39:18 +0200 Subject: [PATCH 5/8] MOBILE-3833 course: Collapse sections in downloads page --- .../pages/course-storage/course-storage.html | 84 ++++++++++--------- .../pages/course-storage/course-storage.ts | 30 ++++++- .../course-format/course-format.html | 5 ++ .../components/course-format/course-format.ts | 64 +++++++++----- .../components/course-index/course-index.html | 56 +++++++------ .../course/pages/contents/contents.html | 5 -- .../course/pages/contents/contents.ts | 8 -- 7 files changed, 152 insertions(+), 100 deletions(-) diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html index 57b45f33c..2df7b3282 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.html +++ b/src/addons/storagemanager/pages/course-storage/course-storage.html @@ -47,7 +47,13 @@ <ng-container *ngFor="let section of sections"> <ion-card class="section" *ngIf="section.modules.length > 0"> <ion-card-header> - <ion-item class="ion-no-padding" lines="full"> + <ion-item class="ion-no-padding" [lines]="section.expanded ? 'full' : 'none'" button detail="false" + (click)="toggleExpand($event, section)" [class.core-course-storage-section-expanded]="section.expanded" + [attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate" + [attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-storage-section-' + section.id"> + <ion-icon name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon" + [class.expandable-status-icon-expanded]="section.expanded"> + </ion-icon> <ion-label> <p class="item-heading ion-text-wrap"> <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="section.course" @@ -93,44 +99,46 @@ </div> </ion-item> </ion-card-header> - <ion-card-content> - <ng-container *ngFor="let module of section.modules"> - <ion-item class="ion-no-padding core-course-storage-activity" - *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> - <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" - [modname]="module.modname" [componentId]="module.instance"> - </core-mod-icon> - <ion-label class="ion-text-wrap"> - <h3 class="{{module.handlerData!.class}} addon-storagemanager-module-size"> - <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module" - [contextInstanceId]="module.id" [adaptImg]="false"> - </core-format-text> - </h3> - <ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" - *ngIf="!module.calculatingSize && module.totalSize > 0"> - <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded" - [attr.aria-label]="'core.downloaded' | translate"> - </ion-icon>{{ module.totalSize | coreBytesToSize }} - </ion-badge> - <ion-badge color="light" *ngIf="module.calculatingSize"> - {{ 'core.calculating' | translate }} - </ion-badge> - </ion-label> + <ion-card-content id="core-course-storage-section-{{section.id}}"> + <ng-container *ngIf="section.expanded"> + <ng-container *ngFor="let module of section.modules"> + <ion-item class="ion-no-padding core-course-storage-activity" + *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> + <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" + [modname]="module.modname" [componentId]="module.instance"> + </core-mod-icon> + <ion-label class="ion-text-wrap"> + <h3 class="{{module.handlerData!.class}} addon-storagemanager-module-size"> + <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module" + [contextInstanceId]="module.id" [adaptImg]="false"> + </core-format-text> + </h3> + <ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" + *ngIf="!module.calculatingSize && module.totalSize > 0"> + <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded" + [attr.aria-label]="'core.downloaded' | translate"> + </ion-icon>{{ module.totalSize | coreBytesToSize }} + </ion-badge> + <ion-badge color="light" *ngIf="module.calculatingSize"> + {{ 'core.calculating' | translate }} + </ion-badge> + </ion-label> - <div class="storage-buttons" slot="end"> - <core-download-refresh *ngIf="downloadEnabled && module.handlerData?.showDownloadButton && - module.downloadStatus != statusDownloaded" [status]="module.downloadStatus" [enabled]="true" - [canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner" - (action)="prefetchModule(module, section)"> - </core-download-refresh> - <ion-button fill="clear" (click)="deleteForModule(module, section)" - *ngIf="!module.calculatingSize && module.totalSize > 0" color="danger"> - <ion-icon name="fas-trash" slot="icon-only" - [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> - </ion-icon> - </ion-button> - </div> - </ion-item> + <div class="storage-buttons" slot="end"> + <core-download-refresh *ngIf="downloadEnabled && module.handlerData?.showDownloadButton && + module.downloadStatus != statusDownloaded" [status]="module.downloadStatus" [enabled]="true" + [canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner" + (action)="prefetchModule(module, section)"> + </core-download-refresh> + <ion-button fill="clear" (click)="deleteForModule(module, section)" + *ngIf="!module.calculatingSize && module.totalSize > 0" color="danger"> + <ion-icon name="fas-trash" slot="icon-only" + [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> + </ion-icon> + </ion-button> + </div> + </ion-item> + </ng-container> </ng-container> </ion-card-content> </ion-card> diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts index 39c3d42d0..6aa817913 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreConstants } from '@/core/constants'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; import { CoreCourseHelper, @@ -30,6 +30,7 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; +import { CoreDom } from '@singletons/dom'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; /** @@ -62,6 +63,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { statusDownloaded = CoreConstants.DOWNLOADED; + protected initialSectionId?: number; protected siteUpdatedObserver?: CoreEventObserver; protected courseStatusObserver?: CoreEventObserver; protected sectionStatusObserver?: CoreEventObserver; @@ -69,7 +71,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { protected isDestroyed = false; protected isGuest = false; - constructor() { + constructor(protected elementRef: ElementRef) { // Refresh the enabled flags if site is updated. this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); @@ -100,16 +102,19 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest'); + this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId'); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled(); - const sections = await CoreCourse.getSections(this.courseId, false, true); + const sections = (await CoreCourse.getSections(this.courseId, false, true)) + .filter((section) => !CoreCourseHelper.isSectionStealth(section)); this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections .map(section => ({ ...section, totalSize: 0, calculatingSize: true, + expanded: section.id === this.initialSectionId, modules: section.modules.map(module => ({ ...module, calculatingSize: true, @@ -118,6 +123,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { this.loaded = true; + CoreDom.scrollToElement( + this.elementRef.nativeElement, + '.core-course-storage-section-expanded', + { addYAxis: -10 }, + ); + await Promise.all([ this.initSizes(), this.initCoursePrefetch(), @@ -641,6 +652,18 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } } + /** + * Toggle expand status. + * + * @param event Event object. + * @param section Section to expand / collapse. + */ + toggleExpand(event: Event, section: AddonStorageManagerCourseSection): void { + section.expanded = !section.expanded; + event.stopPropagation(); + event.preventDefault(); + } + /** * @inheritdoc */ @@ -663,6 +686,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { totalSize: number; calculatingSize: boolean; + expanded: boolean; modules: AddonStorageManagerModule[]; }; 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 2d8cd3391..4031d1654 100644 --- a/src/core/features/course/components/course-format/course-format.html +++ b/src/core/features/course/components/course-format/course-format.html @@ -1,3 +1,8 @@ +<core-navbar-buttons slot="end" prepend> + <ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate"> + <ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon> + </ion-button> +</core-navbar-buttons> <core-dynamic-component [component]="courseFormatComponent" [data]="data"> <!-- Default course format. --> <core-loading [hideUntil]="loaded"> 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 dca3d5443..c1bee68f6 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -391,29 +391,38 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { ); } + /** + * Get selected section ID. If viewing all sections, use current scrolled section. + * + * @return Section ID, undefined if not found. + */ + protected async getSelectedSectionId(): Promise<number | undefined> { + if (this.selectedSection?.id !== this.allSectionsId) { + return this.selectedSection?.id; + } + + // Check current scrolled section. + const allSectionElements: NodeListOf<HTMLElement> = + this.elementRef.nativeElement.querySelectorAll('section.core-course-module-list-wrapper'); + + const scroll = await this.content.getScrollElement(); + const containerTop = scroll.getBoundingClientRect().top; + + const element = Array.from(allSectionElements).find((element) => { + const position = element.getBoundingClientRect(); + + // The bottom is inside the container or lower. + return position.bottom >= containerTop; + }); + + return Number(element?.getAttribute('id')) || undefined; + } + /** * Display the course index modal. */ async openCourseIndex(): Promise<void> { - let selectedId = this.selectedSection?.id; - - if (selectedId == this.allSectionsId) { - // Check current scrolled section. - const allSectionElements: NodeListOf<HTMLElement> = - this.elementRef.nativeElement.querySelectorAll('section.section-wrapper'); - - const scroll = await this.content.getScrollElement(); - const containerTop = scroll.getBoundingClientRect().top; - - const element = Array.from(allSectionElements).find((element) => { - const position = element.getBoundingClientRect(); - - // The bottom is inside the container or lower. - return position.bottom >= containerTop; - }); - - selectedId = Number(element?.getAttribute('id')) || undefined; - } + const selectedId = await this.getSelectedSectionId(); const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({ component: CoreCourseCourseIndexComponent, @@ -453,6 +462,23 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.moduleId = data.moduleId; } + /** + * Open course downloads page. + */ + async gotoCourseDownloads(): Promise<void> { + const selectedId = await this.getSelectedSectionId(); + + CoreNavigator.navigateToSitePath( + `storage/${this.course.id}`, + { + params: { + title: this.course.fullname, + sectionId: selectedId, + }, + }, + ); + } + /** * Function called when selected section changes. * diff --git a/src/core/features/course/components/course-index/course-index.html b/src/core/features/course/components/course-index/course-index.html index 892a63767..8de92a89b 100644 --- a/src/core/features/course/components/course-index/course-index.html +++ b/src/core/features/course/components/course-index/course-index.html @@ -48,34 +48,36 @@ <ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted" [attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon> </ion-item> - <ng-container *ngIf="section.expanded"> - <ng-container *ngFor="let module of section.modules"> - <ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted" - (click)="selectSectionOrModule($event, section.id, module.id)" button> - <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" - slot="start" aria-hidden="true"></ion-icon> - <ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0" - slot="start" [attr.aria-label]="'core.course.todo' | translate"> - </ion-icon> - <ion-icon class="completioninfo completion_complete" name="fas-circle" - *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start" - [attr.aria-label]="'core.course.done' | translate"> - </ion-icon> - <ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3" - color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate"> - </ion-icon> - <ion-label> - <p class="item-heading"> - <core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id" - [courseId]="module.course"> - </core-format-text> - </p> - </ion-label> - <ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted" - [attr.aria-label]="'core.restricted' | translate"></ion-icon> - </ion-item> + <div id="core-course-index-section-{{section.id}}"> + <ng-container *ngIf="section.expanded"> + <ng-container *ngFor="let module of section.modules"> + <ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted" + (click)="selectSectionOrModule($event, section.id, module.id)" button> + <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" + slot="start" aria-hidden="true"></ion-icon> + <ion-icon class="completioninfo completion_incomplete" name="far-circle" + *ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate"> + </ion-icon> + <ion-icon class="completioninfo completion_complete" name="fas-circle" + *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start" + [attr.aria-label]="'core.course.done' | translate"> + </ion-icon> + <ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3" + color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate"> + </ion-icon> + <ion-label> + <p class="item-heading"> + <core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id" + [courseId]="module.course"> + </core-format-text> + </p> + </ion-label> + <ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted" + [attr.aria-label]="'core.restricted' | translate"></ion-icon> + </ion-item> + </ng-container> </ng-container> - </ng-container> + </div> </ng-container> </ng-container> </ion-list> diff --git a/src/core/features/course/pages/contents/contents.html b/src/core/features/course/pages/contents/contents.html index bc3f404f8..2d0eeb1db 100644 --- a/src/core/features/course/pages/contents/contents.html +++ b/src/core/features/course/pages/contents/contents.html @@ -1,8 +1,3 @@ -<core-navbar-buttons slot="end" prepend> - <ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate"> - <ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon> - </ion-button> -</core-navbar-buttons> <ion-content> <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)"> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index 81953a209..7413a0444 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -366,14 +366,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { } } - gotoCourseDownloads(): void { - CoreNavigator.navigateToSitePath( - `storage/${this.course.id}`, - { params: { title: this.course.fullname } }, - ); - - } - /** * @inheritdoc */ From ca25ad0420e8bd1281f7ac9b9d727f1b8369578b Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Tue, 29 Mar 2022 13:22:52 +0200 Subject: [PATCH 6/8] MOBILE-3833 config: Remove unused globalization plugin --- config.xml | 5 ----- package-lock.json | 18 ------------------ package.json | 4 +--- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/config.xml b/config.xml index bfcacc07b..da7166bf5 100644 --- a/config.xml +++ b/config.xml @@ -130,11 +130,6 @@ <param name="android-package" value="org.apache.cordova.geolocation.Geolocation" /> </feature> </config-file> - <config-file parent="/*" target="res/xml/config.xml"> - <feature name="Globalization"> - <param name="android-package" value="org.apache.cordova.globalization.Globalization" /> - </feature> - </config-file> <config-file parent="/*" target="res/xml/config.xml"> <feature name="InAppBrowser"> <param name="android-package" value="org.apache.cordova.inappbrowser.InAppBrowser" /> diff --git a/package-lock.json b/package-lock.json index 40f640b46..a73545b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,6 @@ "cordova-plugin-file": "6.0.2", "cordova-plugin-file-opener2": "3.0.5", "cordova-plugin-geolocation": "4.1.0", - "cordova-plugin-globalization": "1.11.0", "cordova-plugin-ionic-keyboard": "2.2.0", "cordova-plugin-media": "5.0.4", "cordova-plugin-media-capture": "3.0.3", @@ -11389,18 +11388,6 @@ } } }, - "node_modules/cordova-plugin-globalization": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-globalization/-/cordova-plugin-globalization-1.11.0.tgz", - "integrity": "sha1-6sMVgQAphJOvowvolA5pj2HvvP4=", - "engines": { - "cordovaDependencies": { - "2.0.0": { - "cordova": ">100" - } - } - } - }, "node_modules/cordova-plugin-ionic-keyboard": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", @@ -40148,11 +40135,6 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-4.1.0.tgz", "integrity": "sha512-y5io/P10xGMxSn2KEqfv/fExK47eA1pmSonJdmDqDsaSADV9JpgdPx0mUSA08+5pzma/OS9R0LoODeDPx7Jvjg==" }, - "cordova-plugin-globalization": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-globalization/-/cordova-plugin-globalization-1.11.0.tgz", - "integrity": "sha1-6sMVgQAphJOvowvolA5pj2HvvP4=" - }, "cordova-plugin-ionic-keyboard": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", diff --git a/package.json b/package.json index 295ce081c..767ed5d13 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "cordova-plugin-file": "6.0.2", "cordova-plugin-file-opener2": "3.0.5", "cordova-plugin-geolocation": "4.1.0", - "cordova-plugin-globalization": "1.11.0", "cordova-plugin-ionic-keyboard": "2.2.0", "cordova-plugin-media": "5.0.4", "cordova-plugin-media-capture": "3.0.3", @@ -236,7 +235,6 @@ "ANDROIDX_VERSION": "1.0.0", "ANDROIDX_APPCOMPAT_VERSION": "1.3.1" }, - "cordova-plugin-globalization": {}, "@moodlehq/cordova-plugin-file-transfer": {}, "cordova-plugin-prevent-override": {}, "cordova-plugin-androidx-adapter": {} @@ -245,4 +243,4 @@ "optionalDependencies": { "keytar": "7.2.0" } -} +} \ No newline at end of file From 6dadd7b9a64697ad7e63841a520164facd477317 Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Wed, 30 Mar 2022 09:38:33 +0200 Subject: [PATCH 7/8] MOBILE-3833 core: Fix handle links with URL scheme --- .../features/contentlinks/services/contentlinks-helper.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/features/contentlinks/services/contentlinks-helper.ts b/src/core/features/contentlinks/services/contentlinks-helper.ts index 8c5ccd06c..e4d6379dd 100644 --- a/src/core/features/contentlinks/services/contentlinks-helper.ts +++ b/src/core/features/contentlinks/services/contentlinks-helper.ts @@ -21,6 +21,7 @@ import { makeSingleton, Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { Params } from '@angular/router'; import { CoreContentLinksChooseSiteModalComponent } from '../components/choose-site-modal/choose-site-modal'; +import { CoreCustomURLSchemes } from '@services/urlschemes'; /** * Service that provides some features regarding content links. @@ -138,6 +139,12 @@ export class CoreContentLinksHelperProvider { openBrowserRoot?: boolean, ): Promise<boolean> { try { + if (CoreCustomURLSchemes.isCustomURL(url)) { + await CoreCustomURLSchemes.handleCustomURL(url); + + return true; + } + if (checkRoot) { const data = await CoreSites.isStoredRootURL(url, username); From 9884ceb6b34164c770ba5d3e59332b5cfda8522f Mon Sep 17 00:00:00 2001 From: Dani Palou <dani@moodle.com> Date: Wed, 30 Mar 2022 09:38:55 +0200 Subject: [PATCH 8/8] MOBILE-3833 ios: Fix handle iframe links in iOS --- src/core/initializers/inject-ios-scripts.ts | 3 ++- src/core/services/utils/iframe.ts | 19 +++---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/core/initializers/inject-ios-scripts.ts b/src/core/initializers/inject-ios-scripts.ts index 64b26993d..cfd717666 100644 --- a/src/core/initializers/inject-ios-scripts.ts +++ b/src/core/initializers/inject-ios-scripts.ts @@ -17,10 +17,11 @@ import { CoreIframeUtils } from '@services/utils/iframe'; import { Platform } from '@singletons'; export default async function(): Promise<void> { + await Platform.ready(); + if (!CoreApp.isIOS() || !('WKUserScript' in window)) { return; } - await Platform.ready(); CoreIframeUtils.injectiOSScripts(window); } diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 1faf9308e..08745cb44 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -446,26 +446,13 @@ export class CoreIframeUtilsProvider { } else { element.setAttribute('src', url); } - } else if (CoreUrlUtils.isLocalFileUrl(url)) { - // It's a local file. - const filename = url.substring(url.lastIndexOf('/') + 1); - - if (!CoreFileHelper.isOpenableInApp({ filename })) { - try { - await CoreFileHelper.showConfirmOpenUnsupportedFile(); - } catch (error) { - return; // Cancelled, stop. - } - } - + } else { try { - await CoreUtils.openFile(url); + // It's an external link or a local file, check if it can be opened in the app. + await CoreWindow.open(url, name); } catch (error) { CoreDomUtils.showErrorModal(error); } - } else { - // It's an external link, check if it can be opened in the app. - await CoreWindow.open(url, name); } }