MOBILE-3833 course: Display data ASAP in course downloads

main
Dani Palou 2022-03-24 17:29:58 +01:00
parent 9c2116c33b
commit 1a07331396
4 changed files with 49 additions and 29 deletions

View File

@ -1453,6 +1453,7 @@
"core.block.tour_navigation_dashboard_content": "tool_usertours", "core.block.tour_navigation_dashboard_content": "tool_usertours",
"core.block.tour_navigation_dashboard_title": "tool_usertours", "core.block.tour_navigation_dashboard_title": "tool_usertours",
"core.browser": "local_moodlemobileapp", "core.browser": "local_moodlemobileapp",
"core.calculating": "local_moodlemobileapp",
"core.cancel": "moodle", "core.cancel": "moodle",
"core.cannotconnect": "local_moodlemobileapp", "core.cannotconnect": "local_moodlemobileapp",
"core.cannotconnecttrouble": "local_moodlemobileapp", "core.cannotconnecttrouble": "local_moodlemobileapp",
@ -1673,6 +1674,7 @@
"core.editor.underline": "atto_underline/pluginname", "core.editor.underline": "atto_underline/pluginname",
"core.editor.unorderedlist": "atto_unorderedlist/pluginname", "core.editor.unorderedlist": "atto_unorderedlist/pluginname",
"core.emptysplit": "local_moodlemobileapp", "core.emptysplit": "local_moodlemobileapp",
"core.endonesteptour": "tool_usertours",
"core.error": "moodle", "core.error": "moodle",
"core.errorchangecompletion": "local_moodlemobileapp", "core.errorchangecompletion": "local_moodlemobileapp",
"core.errordeletefile": "local_moodlemobileapp", "core.errordeletefile": "local_moodlemobileapp",
@ -2342,7 +2344,6 @@
"core.usernotfullysetup": "error", "core.usernotfullysetup": "error",
"core.users": "moodle", "core.users": "moodle",
"core.usersuspended": "tool_reportbuilder", "core.usersuspended": "tool_reportbuilder",
"core.endonesteptour": "tool_usertours",
"core.view": "moodle", "core.view": "moodle",
"core.viewcode": "local_moodlemobileapp", "core.viewcode": "local_moodlemobileapp",
"core.vieweditor": "local_moodlemobileapp", "core.vieweditor": "local_moodlemobileapp",

View File

@ -24,15 +24,18 @@
<ion-label> <ion-label>
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p> <p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p>
</ion-label> </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-badge>
</ion-item> </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-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon>
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner> <ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
{{ prefetchCourseData.statusTranslatable | translate }} {{ prefetchCourseData.statusTranslatable | translate }}
</ion-button> </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"> class="ion-no-margin ion-margin-top">
<ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: <ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate:
{ name: title }"> { name: title }">
@ -52,18 +55,21 @@
</core-format-text> </core-format-text>
</p> </p>
<ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'" <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" <ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded"
[attr.aria-label]="'core.downloaded' | translate"> [attr.aria-label]="'core.downloaded' | translate">
</ion-icon>{{ section.totalSize | coreBytesToSize }} </ion-icon>{{ section.totalSize | coreBytesToSize }}
</ion-badge> </ion-badge>
<ion-badge color="light" *ngIf="!section.sizeLoaded">
{{ 'core.calculating' | translate }}
</ion-badge>
<!-- Download progress. --> <!-- Download progress. -->
<p *ngIf="downloadEnabled && section.isDownloading"> <p *ngIf="downloadEnabled && section.isDownloading">
<core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total"> <core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total">
</core-progress-bar> </core-progress-bar>
</p> </p>
</ion-label> </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"> <div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
<core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded" <core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus != statusDownloaded"
[status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)" [status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)"
@ -77,7 +83,8 @@
{{section.count}} / {{section.total}} {{section.count}} / {{section.total}}
</ion-badge> </ion-badge>
</div> </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" <ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }"> [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
</ion-icon> </ion-icon>
@ -87,7 +94,8 @@
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content>
<ng-container *ngFor="let module of section.modules"> <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" <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
[modname]="module.modname" [componentId]="module.instance"> [modname]="module.modname" [componentId]="module.instance">
</core-mod-icon> </core-mod-icon>
@ -98,11 +106,14 @@
</core-format-text> </core-format-text>
</h3> </h3>
<ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" <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" <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded"
[attr.aria-label]="'core.downloaded' | translate"> [attr.aria-label]="'core.downloaded' | translate">
</ion-icon>{{ module.totalSize | coreBytesToSize }} </ion-icon>{{ module.totalSize | coreBytesToSize }}
</ion-badge> </ion-badge>
<ion-badge color="light" *ngIf="!module.sizeLoaded">
{{ 'core.calculating' | translate }}
</ion-badge>
</ion-label> </ion-label>
<div class="storage-buttons" slot="end"> <div class="storage-buttons" slot="end">
@ -111,8 +122,8 @@
[canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner" [canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner"
(action)="prefetchModule(module, section)"> (action)="prefetchModule(module, section)">
</core-download-refresh> </core-download-refresh>
<ion-button fill="clear" (click)="deleteForModule(module, section)" *ngIf="module.totalSize > 0" <ion-button fill="clear" (click)="deleteForModule(module, section)"
color="danger"> *ngIf="module.sizeLoaded && module.totalSize > 0" color="danger">
<ion-icon name="fas-trash" slot="icon-only" <ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
</ion-icon> </ion-icon>

View File

@ -48,6 +48,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
loaded = false; loaded = false;
sections: AddonStorageManagerCourseSection[] = []; sections: AddonStorageManagerCourseSection[] = [];
totalSize = 0; totalSize = 0;
sizeLoaded = false;
downloadEnabled = false; downloadEnabled = false;
downloadCourseEnabled = false; downloadCourseEnabled = false;
@ -107,13 +108,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
.map((section) => ({ ...section, totalSize: 0 })); .map((section) => ({ ...section, totalSize: 0 }));
this.loaded = true;
await Promise.all([ await Promise.all([
this.loadSizes(), this.loadSizes(),
this.initCoursePrefetch(), this.initCoursePrefetch(),
this.initModulePrefetch(), this.initModulePrefetch(),
]); ]);
this.loaded = true;
} }
/** /**
@ -241,12 +242,15 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
*/ */
protected async loadSizes(): Promise<void> { protected async loadSizes(): Promise<void> {
this.totalSize = 0; this.totalSize = 0;
this.sizeLoaded = false;
const promises: Promise<void>[] = []; await Promise.all(this.sections.map(async (section) => {
this.sections.forEach((section) => {
section.totalSize = 0; section.totalSize = 0;
section.modules.forEach((module) => { section.sizeLoaded = false;
await Promise.all(section.modules.map(async (module) => {
module.totalSize = 0; module.totalSize = 0;
module.sizeLoaded = false;
// Note: This function only gets the size for modules which are downloadable. // 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. // 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. // But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
// There is nothing enforcing correct values. // There is nothing enforcing correct values.
// Most modules which have large files are downloadable, so I think this is sufficient. // Most modules which have large files are downloadable, so I think this is sufficient.
const promise = CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId).then((size) => { const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
// 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;
}
return; // There are some cases where the return from this is not a valid number.
}); if (!isNaN(size)) {
promises.push(promise); 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. // Mark course as not downloaded if course size is 0.
if (this.totalSize == 0) { if (this.totalSize == 0) {
@ -608,11 +613,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
totalSize: number; totalSize: number;
sizeLoaded?: boolean;
modules: AddonStorageManagerModule[]; modules: AddonStorageManagerModule[];
}; };
type AddonStorageManagerModule = CoreCourseModuleData & { type AddonStorageManagerModule = CoreCourseModuleData & {
totalSize?: number; totalSize?: number;
sizeLoaded?: boolean;
prefetchHandler?: CoreCourseModulePrefetchHandler; prefetchHandler?: CoreCourseModulePrefetchHandler;
spinner?: boolean; spinner?: boolean;
downloadStatus?: string; downloadStatus?: string;

View File

@ -13,6 +13,7 @@
"areyousure": "Are you sure?", "areyousure": "Are you sure?",
"back": "Back", "back": "Back",
"browser": "Browser", "browser": "Browser",
"calculating": "Calculating",
"cancel": "Cancel", "cancel": "Cancel",
"cannotconnect": "Cannot connect", "cannotconnect": "Cannot connect",
"cannotconnecttrouble": "We're having trouble connecting to your site.", "cannotconnecttrouble": "We're having trouble connecting to your site.",