diff --git a/scripts/langindex.json b/scripts/langindex.json
index 3ab3ca3a4..feedd73b5 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -1106,6 +1106,7 @@
"addon.storagemanager.deletecourses": "local_moodlemobileapp",
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
"addon.storagemanager.info": "local_moodlemobileapp",
+ "addon.storagemanager.managecoursestorage": "local_moodlemobileapp",
"addon.storagemanager.managestorage": "local_moodlemobileapp",
"addon.storagemanager.storageused": "local_moodlemobileapp",
"assets.countries.AD": "countries",
diff --git a/src/addons/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html b/src/addons/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html
index c650ba61e..de45bf3af 100644
--- a/src/addons/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html
+++ b/src/addons/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html
@@ -12,7 +12,6 @@
-
+
diff --git a/src/addons/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts b/src/addons/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts
index c0aad05ef..d550062d7 100644
--- a/src/addons/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts
+++ b/src/addons/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts
@@ -91,7 +91,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
const items = config.frontpageloggedin.split(',');
const hasNewsItem = items.find((item) => parseInt(item, 10) == FrontPageItemNames['NEWS_ITEMS']);
- const result = CoreCourseHelper.addHandlerDataForModules(
+ const result = await CoreCourseHelper.addHandlerDataForModules(
[mainMenuBlock],
this.siteHomeId,
undefined,
diff --git a/src/addons/storagemanager/lang.json b/src/addons/storagemanager/lang.json
index 8efc60a30..389a65e62 100644
--- a/src/addons/storagemanager/lang.json
+++ b/src/addons/storagemanager/lang.json
@@ -4,5 +4,6 @@
"deletedatafrom": "Offload data from {{name}}",
"info": "Files stored on your device make the app work faster and enable the app to be used offline. You can safely offload files if you need to free up storage space.",
"managestorage": "Manage storage",
+ "managecoursestorage": "Manage course storage",
"storageused": "File storage used:"
}
diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html
index 42371c995..4f1b55179 100644
--- a/src/addons/storagemanager/pages/course-storage/course-storage.html
+++ b/src/addons/storagemanager/pages/course-storage/course-storage.html
@@ -4,7 +4,7 @@
- {{ 'addon.storagemanager.managestorage' | translate }}
+ {{ 'addon.storagemanager.managecoursestorage' | translate }}
@@ -12,54 +12,90 @@
- {{ course.displayname }}
- {{ course.fullname }}
+ {{ title }}
{{ 'addon.storagemanager.info' | translate }}
{{ 'addon.storagemanager.storageused' | translate }}
+ {{ totalSize | coreBytesToSize }}
- {{ totalSize | coreBytesToSize }}
+
+
+
+ {{ prefetchCourseData.statusTranslatable | translate }}
+
- 0" class="section">
+ 0">
{{ section.name }}
+ 0">
+ {{ section.totalSize | coreBytesToSize }}
+
+
+
+
+
+
- {{ section.totalSize | coreBytesToSize }}
-
-
-
-
+
- 0">
- 0">
+
{{ module.name }}
+ 0">
+ {{ module.totalSize | coreBytesToSize }}
+
- {{ module.totalSize | coreBytesToSize }}
-
-
-
-
+
+
+ 0">
+
+
+
+
+
+
diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.scss b/src/addons/storagemanager/pages/course-storage/course-storage.scss
index 4e413a248..5e459df34 100644
--- a/src/addons/storagemanager/pages/course-storage/course-storage.scss
+++ b/src/addons/storagemanager/pages/course-storage/course-storage.scss
@@ -9,3 +9,12 @@
font-size: 1.2rem;
}
}
+
+.storage-buttons {
+ display: flex;
+ align-items: center;
+}
+
+ion-item {
+ --inner-padding-end: 0px;
+}
diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts
index 80e28b258..3abf047de 100644
--- a/src/addons/storagemanager/pages/course-storage/course-storage.ts
+++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts
@@ -13,37 +13,77 @@
// limitations under the License.
import { CoreConstants } from '@/core/constants';
-import { Component, OnInit } from '@angular/core';
-import { CoreCourse } from '@features/course/services/course';
-import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
-import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
-import { CoreEnrolledCourseData } from '@features/courses/services/courses';
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
+import {
+ CoreCourseHelper,
+ CoreCourseModuleData,
+ CoreCourseSectionWithStatus,
+ CorePrefetchStatusInfo,
+} from '@features/course/services/course-helper';
+import {
+ CoreCourseModulePrefetchDelegate,
+ CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
+import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
import { CoreNavigator } from '@services/navigator';
+import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
+import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
+import { CoreEventObserver, CoreEvents } from '@singletons/events';
/**
* Page that displays the amount of file storage used by each activity on the course, and allows
- * the user to delete these files.
+ * the user to prefecth and delete this data.
*/
@Component({
selector: 'page-addon-storagemanager-course-storage',
templateUrl: 'course-storage.html',
styleUrls: ['course-storage.scss'],
})
-export class AddonStorageManagerCourseStoragePage implements OnInit {
+export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
- course!: CoreEnrolledCourseData;
+ course?: CoreEnrolledCourseData;
+ courseId!: number;
+ title = '';
loaded = false;
sections: AddonStorageManagerCourseSection[] = [];
totalSize = 0;
+ downloadEnabled = false;
+ downloadCourseEnabled = false;
+
+ prefetchCourseData: CorePrefetchStatusInfo = {
+ icon: CoreConstants.ICON_LOADING,
+ statusTranslatable: 'core.course.downloadcourse',
+ status: '',
+ loading: true,
+ };
+
+ protected siteUpdatedObserver?: CoreEventObserver;
+ protected courseStatusObserver?: CoreEventObserver;
+ protected sectionStatusObserver?: CoreEventObserver;
+ protected moduleStatusObserver?: CoreEventObserver;
+ protected isDestroyed = false;
+ protected isGuest = false;
+
+ constructor() {
+ // Refresh the enabled flags if site is updated.
+ this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
+ this.downloadCourseEnabled = !!this.course && !CoreCourses.isDownloadCourseDisabledInSite();
+ this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
+
+ this.initCoursePrefetch();
+ this.initModulePrefetch();
+ }, CoreSites.getCurrentSiteId());
+ }
+
/**
- * View loaded.
+ * @inheritdoc
*/
async ngOnInit(): Promise {
try {
- this.course = CoreNavigator.getRequiredRouteParam('course');
+ this.courseId = CoreNavigator.getRequiredRouteParam('courseId');
} catch (error) {
CoreDomUtils.showErrorModal(error);
@@ -52,16 +92,160 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
return;
}
- const sections = await CoreCourse.getSections(this.course.id, false, true);
- this.sections = CoreCourseHelper.addHandlerDataForModules(sections, this.course.id).sections;
+ this.course = CoreNavigator.getRouteParam('course');
+ this.title = this.course?.displayname ?? this.course?.fullname ?? '';
+ if (!this.title && this.courseId == CoreSites.getCurrentSiteHomeId()) {
+ this.title = Translate.instant('core.sitehome.sitehome');
+ }
+ this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
+
+ this.downloadCourseEnabled = !!this.course && !CoreCourses.isDownloadCourseDisabledInSite();
+ this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
+
+ const sections = await CoreCourse.getSections(this.courseId, false, true);
+ this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
+ .map((section) => ({ ...section, totalSize: 0 }));
+
+ await Promise.all([
+ this.loadSizes(),
+ this.initCoursePrefetch(),
+ this.initModulePrefetch(),
+ ]);
+
+ this.loaded = true;
+ }
+
+ /**
+ * Init course prefetch information.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async initCoursePrefetch(): Promise {
+ if (!this.downloadCourseEnabled || this.courseStatusObserver) {
+ return;
+ }
+
+ // Listen for changes in course status.
+ this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
+ if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
+ this.updateCourseStatus(data.status);
+ }
+ }, CoreSites.getCurrentSiteId());
+
+ // Determine the course prefetch status.
+ await this.determineCoursePrefetchIcon();
+
+ if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) {
+ return;
+ }
+
+ // Course is being downloaded. Get the download promise.
+ const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId);
+ if (promise) {
+ // There is a download promise. Show an error if it fails.
+ 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.
+ const status = await CoreCourse.setCoursePreviousStatus(this.courseId);
+
+ this.updateCourseStatus(status);
+ }
+ }
+
+ /**
+ * Init module prefetch information.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async initModulePrefetch(): Promise {
+ if (!this.downloadEnabled || this.sectionStatusObserver) {
+ return;
+ }
+
+ // Listen for section status changes.
+ this.sectionStatusObserver = CoreEvents.on(
+ CoreEvents.SECTION_STATUS_CHANGED,
+ async (data) => {
+ if (!this.downloadEnabled || !this.sections.length || !data.sectionId || data.courseId != this.courseId) {
+ return;
+ }
+
+ // Check if the affected section is being downloaded.
+ // If so, we don't update section status because it'll already be updated when the download finishes.
+ const downloadId = CoreCourseHelper.getSectionDownloadId({ id: data.sectionId });
+ if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
+ return;
+ }
+
+ // Get the affected section.
+ const section = this.sections.find(section => section.id == data.sectionId);
+ if (!section) {
+ return;
+ }
+
+ // Recalculate the status.
+ await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false);
+
+ if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
+ // All the modules are now downloading, set a download all promise.
+ this.prefecthSection(section);
+ }
+ },
+ CoreSites.getCurrentSiteId(),
+ );
+
+ // The download status of a section might have been changed from within a module page.
+ CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
+
+ this.sections.forEach((section) => {
+ section.modules.forEach((module) => {
+ if (module.handlerData?.showDownloadButton) {
+
+ module.spinner = true;
+ // Listen for changes on this module status, even if download isn't enabled.
+ module.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
+ this.calculateModuleStatus(module);
+ }
+ });
+ });
+
+ this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
+ let module: AddonStorageManagerModule | undefined;
+
+ this.sections.some((section) => {
+ module = section.modules.find((module) =>
+ module.id == data.componentId && module.prefetchHandler && data.component == module.prefetchHandler?.component);
+
+ return !!module;
+ });
+
+ if (!module) {
+ return;
+ }
+
+ // Call determineModuleStatus to get the right status to display.
+ const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(module, data.status);
+
+ // Update the status.
+ this.updateModuleStatus(module, status);
+ }, CoreSites.getCurrentSiteId());
+ }
+
+ /**
+ * Init section, course and modules sizes.
+ */
+ protected async loadSizes(): Promise {
this.totalSize = 0;
const promises: Promise[] = [];
this.sections.forEach((section) => {
section.totalSize = 0;
section.modules.forEach((module) => {
- module.parentSection = section;
module.totalSize = 0;
// Note: This function only gets the size for modules which are downloadable.
@@ -71,11 +255,11 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
// 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.course.id).then((size) => {
+ 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;
+ section.totalSize += size;
this.totalSize += size;
}
@@ -86,8 +270,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
});
await Promise.all(promises);
- this.loaded = true;
+ // Mark course as not downloaded if course size is 0.
if (this.totalSize == 0) {
this.markCourseAsNotDownloaded();
}
@@ -146,15 +330,16 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
}
});
- this.deleteModules(modules);
+ this.deleteModules(modules, section);
}
/**
* The user has requested a delete for a module's data
*
* @param module Module details
+ * @param section Section the module belongs to.
*/
- async deleteForModule(module: AddonStorageManagerModule): Promise {
+ async deleteForModule(module: AddonStorageManagerModule, section: AddonStorageManagerCourseSection): Promise {
if (module.totalSize === 0) {
return;
}
@@ -169,25 +354,29 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
return;
}
- this.deleteModules([module]);
+ this.deleteModules([module], section);
}
/**
* Deletes the specified modules, showing the loading overlay while it happens.
*
* @param modules Modules to delete
+ * @param section Section the modules belong to.
* @return Promise Once deleting has finished
*/
- protected async deleteModules(modules: AddonStorageManagerModule[]): Promise {
+ protected async deleteModules(modules: AddonStorageManagerModule[], section?: AddonStorageManagerCourseSection): Promise {
const modal = await CoreDomUtils.showModalLoading();
const promises: Promise[] = [];
modules.forEach((module) => {
// Remove the files.
- const promise = CoreCourseHelper.removeModuleStoredData(module, this.course.id).then(() => {
+ const promise = CoreCourseHelper.removeModuleStoredData(module, this.courseId).then(() => {
+ const moduleSize = module.totalSize || 0;
// When the files and cache are removed, update the size.
- module.parentSection!.totalSize! -= module.totalSize!;
- this.totalSize -= module.totalSize!;
+ if (section) {
+ section.totalSize -= moduleSize;
+ }
+ this.totalSize -= moduleSize;
module.totalSize = 0;
return;
@@ -203,13 +392,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
} finally {
modal.dismiss();
- // @TODO This is a workaround that should be more specific solving MOBILE-3305.
- // Also should take into account all modules are not downloaded.
-
- // Mark course as not downloaded if course size is 0.
- if (this.totalSize == 0) {
- this.markCourseAsNotDownloaded();
- }
+ await this.loadSizes();
+ CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
}
}
@@ -221,17 +405,201 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
// Also should take into account all modules are not downloaded.
// Check after MOBILE-3188 is integrated.
- CoreCourse.setCourseStatus(this.course.id, CoreConstants.NOT_DOWNLOADED);
+ CoreCourse.setCourseStatus(this.courseId, CoreConstants.NOT_DOWNLOADED);
+ }
+
+ /**
+ * Calculate the status of sections.
+ *
+ * @param refresh If refresh or not.
+ */
+ protected calculateSectionsStatus(refresh?: boolean): void {
+ if (!this.sections) {
+ return;
+ }
+
+ CoreUtils.ignoreErrors(CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, refresh));
+ }
+
+ /**
+ * Confirm and prefetch a section. If the section is "all sections", prefetch all the sections.
+ *
+ * @param section Section to download.
+ * @param refresh Refresh clicked (not used).
+ */
+ async prefecthSection(section: AddonStorageManagerCourseSection): Promise {
+ section.isCalculating = true;
+
+ try {
+ await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, section, this.sections);
+
+ try {
+ await CoreCourseHelper.prefetchSection(section, this.courseId, this.sections);
+
+ } catch (error) {
+ if (!this.isDestroyed) {
+ CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
+ }
+ } finally {
+ await this.loadSizes();
+ }
+ } catch (error) {
+ // User cancelled or there was an error calculating the size.
+ if (!this.isDestroyed && error) {
+ CoreDomUtils.showErrorModal(error);
+
+ return;
+ }
+ } finally {
+ section.isCalculating = false;
+ }
+ }
+
+ /**
+ * Download the module.
+ *
+ * @param module Module to prefetch.
+ * @param refresh Whether it's refreshing.
+ * @return Promise resolved when done.
+ */
+ async prefetchModule(
+ module: AddonStorageManagerModule,
+ refresh = false,
+ ): Promise {
+ if (!module.prefetchHandler) {
+ return;
+ }
+
+ // Show spinner since this operation might take a while.
+ module.spinner = true;
+
+ try {
+ // Get download size to ask for confirm if it's high.
+ const size = await module.prefetchHandler.getDownloadSize(module, module.course, true);
+
+ await CoreCourseHelper.prefetchModule(module.prefetchHandler, module, size, module.course, refresh);
+
+ CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
+ } catch (error) {
+ if (!this.isDestroyed) {
+ CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
+ }
+ } finally {
+ module.spinner = false;
+
+ await this.loadSizes();
+ }
+ }
+
+ /**
+ * Show download buttons according to module status.
+ *
+ * @param module Module to update.
+ * @param status Module status.
+ */
+ protected updateModuleStatus(module: AddonStorageManagerModule, status: string): void {
+ if (!status) {
+ return;
+ }
+
+ module.spinner = false;
+ module.downloadStatus = status;
+
+ module.handlerData?.updateStatus?.(status);
+ }
+
+ /**
+ * Calculate and show module status.
+ *
+ * @param module Module to update.
+ * @return Promise resolved when done.
+ */
+ protected async calculateModuleStatus(module: AddonStorageManagerModule): Promise {
+ if (!module) {
+ return;
+ }
+
+ const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId);
+
+ this.updateModuleStatus(module, status);
+ }
+
+ /**
+ * Determines the prefetch icon of the course.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async determineCoursePrefetchIcon(): Promise {
+ this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
+ }
+
+ /**
+ * 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 whole course.
+ */
+ async prefetchCourse(): Promise {
+ if (!this.course) {
+ return;
+ }
+
+ try {
+ await CoreCourseHelper.confirmAndPrefetchCourse(
+ this.prefetchCourseData,
+ this.course,
+ {
+ sections: this.sections,
+ isGuest: this.isGuest,
+ },
+ );
+ } catch (error) {
+ if (this.isDestroyed) {
+ return;
+ }
+
+ CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ ngOnDestroy(): void {
+ this.courseStatusObserver?.off();
+ this.sectionStatusObserver?.off();
+ this.moduleStatusObserver?.off();
+ this.siteUpdatedObserver?.off();
+
+ this.sections.forEach((section) => {
+ section.modules.forEach((module) => {
+ module.handlerData?.onDestroy?.();
+ });
+ });
+ this.isDestroyed = true;
}
}
-type AddonStorageManagerCourseSection = Omit & {
- totalSize?: number;
+type AddonStorageManagerCourseSection = Omit & {
+ totalSize: number;
modules: AddonStorageManagerModule[];
};
type AddonStorageManagerModule = CoreCourseModuleData & {
- parentSection?: AddonStorageManagerCourseSection;
totalSize?: number;
+ prefetchHandler?: CoreCourseModulePrefetchHandler;
+ spinner?: boolean;
+ downloadStatus?: string;
};
diff --git a/src/addons/storagemanager/services/handlers/course-menu.ts b/src/addons/storagemanager/services/handlers/course-menu.ts
index 3d9cc77d1..14111d513 100644
--- a/src/addons/storagemanager/services/handlers/course-menu.ts
+++ b/src/addons/storagemanager/services/handlers/course-menu.ts
@@ -49,7 +49,7 @@ export class AddonStorageManagerCourseMenuHandlerService implements CoreCourseOp
): CoreCourseOptionsMenuHandlerData {
return {
icon: 'fas-archive',
- title: 'addon.storagemanager.managestorage',
+ title: 'addon.storagemanager.managecoursestorage',
page: 'storage/' + course.id,
class: 'addon-storagemanager-coursemenu-handler',
};
diff --git a/src/core/components/progress-bar/core-progress-bar.html b/src/core/components/progress-bar/core-progress-bar.html
index 6d62b02e0..7178621c7 100644
--- a/src/core/components/progress-bar/core-progress-bar.html
+++ b/src/core/components/progress-bar/core-progress-bar.html
@@ -7,3 +7,5 @@
{{ 'core.percentagenumber' | translate: {$a: text} }}
+
+
diff --git a/src/core/components/progress-bar/progress-bar.scss b/src/core/components/progress-bar/progress-bar.scss
index d50a1f078..41584f3f8 100644
--- a/src/core/components/progress-bar/progress-bar.scss
+++ b/src/core/components/progress-bar/progress-bar.scss
@@ -35,4 +35,11 @@
border-radius: 0;
}
}
+
+ ion-progress-bar {
+ --progress-background: var(--color);
+ height: var(--height);
+ margin-top: calc((var(--line-height) - var(--height)) /2);
+ margin-bottom: calc((var(--line-height) - var(--height)) /2);
+ }
}
diff --git a/src/core/components/progress-bar/progress-bar.ts b/src/core/components/progress-bar/progress-bar.ts
index ae6f86fcc..1b2e1fd56 100644
--- a/src/core/components/progress-bar/progress-bar.ts
+++ b/src/core/components/progress-bar/progress-bar.ts
@@ -30,7 +30,7 @@ import { DomSanitizer, Translate } from '@singletons';
})
export class CoreProgressBarComponent implements OnChanges {
- @Input() progress!: number | string; // Percentage from 0 to 100.
+ @Input() progress!: number | string; // Percentage from 0 to 100. Negative number will show an indeterminate progress bar.
@Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
@Input() a11yText?: string; // Accessibility text to read before the percentage.
@Input() ariaDescribedBy?: string; // ID of the element that described the progress, if any.
diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
index 71f3e4898..bd59a030a 100644
--- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
+++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts
@@ -27,7 +27,6 @@ import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks';
export class CoreBlockSideBlocksButtonComponent {
@Input() courseId!: number;
- @Input() downloadEnabled = false;
/**
* Open side blocks.
@@ -37,7 +36,6 @@ export class CoreBlockSideBlocksButtonComponent {
component: CoreBlockSideBlocksComponent,
componentProps: {
courseId: this.courseId,
- downloadEnabled: this.downloadEnabled,
},
});
}
diff --git a/src/core/features/block/components/side-blocks/side-blocks.html b/src/core/features/block/components/side-blocks/side-blocks.html
index 4b08c5b01..198cb21d2 100644
--- a/src/core/features/block/components/side-blocks/side-blocks.html
+++ b/src/core/features/block/components/side-blocks/side-blocks.html
@@ -17,8 +17,7 @@
0">
-
+
diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts
index 0f817a4a0..b44b32557 100644
--- a/src/core/features/block/components/side-blocks/side-blocks.ts
+++ b/src/core/features/block/components/side-blocks/side-blocks.ts
@@ -33,7 +33,6 @@ import { CoreCoursesDashboard } from '@features/courses/services/dashboard';
export class CoreBlockSideBlocksComponent implements OnInit {
@Input() courseId?: number;
- @Input() downloadEnabled = false;
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList;
diff --git a/src/core/features/course/components/format/core-course-format.html b/src/core/features/course/components/format/core-course-format.html
index e4b211c67..7e549a3c2 100644
--- a/src/core/features/course/components/format/core-course-format.html
+++ b/src/core/features/course/components/format/core-course-format.html
@@ -11,10 +11,8 @@
-
+ class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row">
{{ 'core.course.sections' | translate }}
-
-
-
+ (selectedSection.availabilityinfo || selectedSection.visible === 0))" lines="none" class="core-format-progress-list">
@@ -103,8 +99,7 @@
-
+
@@ -114,7 +109,7 @@
+ [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
@@ -133,8 +128,6 @@
-
-
@@ -145,28 +138,10 @@
-
-
-
-
-
-
- 0 && section.count < section.total" role="progressbar"
- [attr.aria-valuemax]="section.total" [attr.aria-valuenow]="section.count"
- [attr.aria-valuetext]="'core.course.downloadsectionprogressdescription' | translate:section">
- {{section.count}} / {{section.total}}
-
-
-
-
-
-
diff --git a/src/core/features/course/components/format/format.scss b/src/core/features/course/components/format/format.scss
index 46ca0f415..76335d842 100644
--- a/src/core/features/course/components/format/format.scss
+++ b/src/core/features/course/components/format/format.scss
@@ -55,21 +55,6 @@
}
}
- .core-section-download {
- core-combobox {
- max-width: calc(100% - 64px);
- }
- .core-button-spinner {
- display: flex;
- align-items: center;
- @include margin-horizontal(10px);
-
- ion-badge.core-course-download-courses-progress {
- @include margin(null, 12px, null, null);
- }
- }
- }
-
.core-course-section-nav-buttons {
display: flex;
justify-content: flex-end;
diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts
index 683eb061e..7d93a2084 100644
--- a/src/core/features/course/components/format/format.ts
+++ b/src/core/features/course/components/format/format.ts
@@ -27,7 +27,6 @@ import {
ElementRef,
} from '@angular/core';
import { ModalOptions } from '@ionic/core';
-import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
@@ -37,17 +36,14 @@ import {
CoreCourseProvider,
} from '@features/course/services/course';
import {
- CoreCourseHelper,
CoreCourseModuleData,
CoreCourseModuleCompletionData,
CoreCourseSection,
- CoreCourseSectionWithStatus,
} from '@features/course/services/course-helper';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { IonContent, IonRefresher } from '@ionic/angular';
import { CoreUtils } from '@services/utils/utils';
-import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
import { CoreBlockHelper } from '@features/block/services/block-helper';
@@ -71,8 +67,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
static readonly LOAD_MORE_ACTIVITIES = 20; // How many activities should load each time showMoreActivities is called.
@Input() course?: CoreCourseAnyCourseData; // The course to render.
- @Input() sections?: CoreCourseSectionWithStatus[]; // List of course sections. The status will be calculated in this component.
- @Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
+ @Input() sections?: CoreCourseSection[]; // List of course sections.
@Input() initialSectionId?: number; // The section to load first (by ID).
@Input() initialSectionNumber?: number; // The section to load first (by number).
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
@@ -108,7 +103,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
componentProps: {},
};
- protected sectionStatusObserver?: CoreEventObserver;
protected selectTabObserver?: CoreEventObserver;
protected lastCourseFormat?: string;
protected sectionSelectorExpanded = false;
@@ -125,38 +119,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
* Component being initialized.
*/
ngOnInit(): void {
- // Listen for section status changes.
- this.sectionStatusObserver = CoreEvents.on(
- CoreEvents.SECTION_STATUS_CHANGED,
- async (data) => {
- if (!this.downloadEnabled || !this.sections?.length || !data.sectionId || data.courseId != this.course?.id) {
- return;
- }
-
- // Check if the affected section is being downloaded.
- // If so, we don't update section status because it'll already be updated when the download finishes.
- const downloadId = CoreCourseHelper.getSectionDownloadId({ id: data.sectionId });
- if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
- return;
- }
-
- // Get the affected section.
- const section = this.sections.find(section => section.id == data.sectionId);
- if (!section) {
- return;
- }
-
- // Recalculate the status.
- await CoreCourseHelper.calculateSectionStatus(section, this.course.id, false);
-
- if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
- // All the modules are now downloading, set a download all promise.
- this.prefetch(section);
- }
- },
- CoreSites.getCurrentSiteId(),
- );
-
// Listen for select course tab events to select the right section if needed.
this.selectTabObserver = CoreEvents.on(CoreEvents.SELECT_COURSE_TAB, (data) => {
if (data.name) {
@@ -205,10 +167,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
this.treatSections(this.sections);
}
-
- if (this.downloadEnabled && (changes.downloadEnabled || changes.sections)) {
- this.calculateSectionsStatus(false);
- }
}
/**
@@ -219,7 +177,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.data.sections = this.sections;
this.data.initialSectionId = this.initialSectionId;
this.data.initialSectionNumber = this.initialSectionNumber;
- this.data.downloadEnabled = this.downloadEnabled;
this.data.moduleId = this.moduleId;
this.data.completionChanged = this.completionChanged;
}
@@ -394,9 +351,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.canLoadMore = false;
this.showSectionId = 0;
this.showMoreActivities();
- if (this.downloadEnabled) {
- this.calculateSectionsStatus(false);
- }
}
if (this.moduleId && previousValue === undefined) {
@@ -432,62 +386,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
return section1 && section2 ? section1.id === section2.id : section1 === section2;
}
- /**
- * Calculate the status of sections.
- *
- * @param refresh If refresh or not.
- */
- protected calculateSectionsStatus(refresh?: boolean): void {
- if (!this.sections || !this.course) {
- return;
- }
-
- CoreUtils.ignoreErrors(CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, refresh));
- }
-
- /**
- * Confirm and prefetch a section. If the section is "all sections", prefetch all the sections.
- *
- * @param section Section to download.
- * @param refresh Refresh clicked (not used).
- */
- async prefetch(section: CoreCourseSectionWithStatus): Promise {
- section.isCalculating = true;
-
- try {
- await CoreCourseHelper.confirmDownloadSizeSection(this.course!.id, section, this.sections);
-
- await this.prefetchSection(section, true);
- } catch (error) {
- // User cancelled or there was an error calculating the size.
- if (error) {
- CoreDomUtils.showErrorModal(error);
- }
- } finally {
- section.isCalculating = false;
- }
- }
-
- /**
- * Prefetch a section.
- *
- * @param section The section to download.
- * @param manual Whether the prefetch was started manually or it was automatically started because all modules
- * are being downloaded.
- */
- protected async prefetchSection(section: CoreCourseSectionWithStatus, manual?: boolean): Promise {
- try {
- await CoreCourseHelper.prefetchSection(section, this.course!.id, this.sections);
- } catch (error) {
- // Don't show error message if it's an automatic download.
- if (!manual) {
- return;
- }
-
- CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
- }
- }
-
/**
* Refresh the data.
*
@@ -580,7 +478,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
* Component destroyed.
*/
ngOnDestroy(): void {
- this.sectionStatusObserver && this.sectionStatusObserver.off();
this.selectTabObserver && this.selectTabObserver.off();
}
@@ -591,17 +488,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.dynamicComponents?.forEach((component) => {
component.callComponentFunction('ionViewDidEnter');
});
-
- if (!this.downloadEnabled || !this.course || !this.sections) {
- return;
- }
-
- // The download status of a section might have been changed from within a module page.
- if (this.selectedSection && this.selectedSection.id !== CoreCourseProvider.ALL_SECTIONS_ID) {
- CoreCourseHelper.calculateSectionStatus(this.selectedSection, this.course.id, false, false);
- } else {
- CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, false, false);
- }
}
/**
@@ -653,17 +539,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.updateProgress();
}
- /**
- * Recalculate the download status of each section, in response to a module being downloaded.
- */
- onModuleStatusChange(): void {
- if (!this.downloadEnabled || !this.sections || !this.course) {
- return;
- }
-
- CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, false, false);
- }
-
/**
* Update course progress.
*/
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 65637562c..fc9db736c 100644
--- a/src/core/features/course/components/module/core-course-module.html
+++ b/src/core/features/course/components/module/core-course-module.html
@@ -15,7 +15,7 @@
+ [courseId]="module.course" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated">
{{ 'core.restricted' | translate }}
+ [courseId]="module.course" class="ion-text-wrap">
@@ -48,13 +48,9 @@