From 04085d5929aa4ca9f2aeffe0e1794e5dd1fccce1 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 17 Jan 2018 15:00:14 +0100 Subject: [PATCH] MOBILE-2310 courses: Apply download courses --- src/core/course/pages/section/section.ts | 4 + .../course-progress/course-progress.html | 8 +- .../course-progress/course-progress.ts | 83 ++++++++++++++---- .../pages/course-preview/course-preview.html | 17 ++-- .../pages/course-preview/course-preview.ts | 54 +++++++++++- .../courses/pages/my-courses/my-courses.html | 1 + .../courses/pages/my-courses/my-courses.ts | 57 ++++++++++++- .../pages/my-overview/my-overview.html | 14 +++- .../courses/pages/my-overview/my-overview.ts | 84 +++++++++++++++++-- 9 files changed, 277 insertions(+), 45 deletions(-) diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 5326dfe1b..2bf7124e3 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -242,6 +242,10 @@ export class CoreCourseSectionPage implements OnDestroy { // Ignore errors (shouldn't happen). }); } + }).catch((error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } }); } diff --git a/src/core/courses/components/course-progress/course-progress.html b/src/core/courses/components/course-progress/course-progress.html index 08b954147..45fca0e83 100644 --- a/src/core/courses/components/course-progress/course-progress.html +++ b/src/core/courses/components/course-progress/course-progress.html @@ -2,11 +2,11 @@

- + - +

diff --git a/src/core/courses/components/course-progress/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index db705c889..2084e6113 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -12,10 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreCourseFormatDelegate } from '../../../course/providers/format-delegate'; +import { CoreCourseProvider } from '../../../course/providers/course'; +import { CoreCourseHelperProvider } from '../../../course/providers/helper'; /** * This component is meant to display a course for a list of courses with progress. @@ -29,32 +34,52 @@ import { CoreCourseFormatDelegate } from '../../../course/providers/format-deleg selector: 'core-courses-course-progress', templateUrl: 'course-progress.html' }) -export class CoreCoursesCourseProgressComponent implements OnInit { +export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { @Input() course: any; // The course to render. isDownloading: boolean; - - protected obsStatus; - protected downloadText; - protected downloadingText; - protected downloadButton = { - isDownload: true, - className: 'core-download-course', - priority: 1000 + prefetchCourseData = { + prefetchCourseIcon: 'spinner' }; - protected buttons; - constructor(private navCtrl: NavController, private translate: TranslateService, - private courseFormatDelegate: CoreCourseFormatDelegate) { - this.downloadText = this.translate.instant('core.course.downloadcourse'); - this.downloadingText = this.translate.instant('core.downloading'); + protected isDestroyed = false; + protected courseStatusObserver; + + constructor(private navCtrl: NavController, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, + private courseFormatDelegate: CoreCourseFormatDelegate, private domUtils: CoreDomUtilsProvider, + private courseProvider: CoreCourseProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) { + // Listen for status change in course. + this.courseStatusObserver = eventsProvider.on(CoreEventsProvider.COURSE_STATUS_CHANGED, (data) => { + if (data.courseId == this.course.id) { + this.prefetchCourseData.prefetchCourseIcon = this.courseHelper.getCourseStatusIconFromStatus(data.status); + } + }, sitesProvider.getCurrentSiteId()); } /** * Component being initialized. */ ngOnInit() { - // @todo: Handle course prefetch. + // Determine course prefetch icon. + this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => { + this.prefetchCourseData.prefetchCourseIcon = icon; + + if (icon == 'spinner') { + // Course is being downloaded. Get the download promise. + const promise = this.courseHelper.getCourseDownloadPromise(this.course.id); + if (promise) { + // There is a download promise. If it fails, show an error. + promise.catch((error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }); + } else { + // No download, this probably means that the app was closed while downloading. Set previous status. + this.courseProvider.setCoursePreviousStatus(this.course.id); + } + } + }); } /** @@ -64,4 +89,30 @@ export class CoreCoursesCourseProgressComponent implements OnInit { this.courseFormatDelegate.openCourse(this.navCtrl, course); } + /** + * Prefetch the course. + * + * @param {Event} e Click event. + */ + prefetchCourse(e: Event) { + e.preventDefault(); + e.stopPropagation(); + + this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course).catch((error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }) + } + + /** + * Component destroyed. + */ + ngOnDestroy() { + this.isDestroyed = true; + + if (this.courseStatusObserver) { + this.courseStatusObserver.off(); + } + } } diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html index 58db99af7..452b2e79f 100644 --- a/src/core/courses/pages/course-preview/course-preview.html +++ b/src/core/courses/pages/course-preview/course-preview.html @@ -17,22 +17,22 @@

{{course.startdate * 1000 | coreFormatDate:"dfdaymonthyear"}} - {{course.enddate * 1000 | coreFormatDate:"dfdaymonthyear"}}

- + - +

{{ 'core.teachers' | translate }}

{{contact.fullname}}

-
+

{{ instance.name }}

- +

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

{{ 'core.paymentinstant' | translate }}

@@ -40,12 +40,11 @@

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

- +

{{ 'core.course.contents' | translate }}

diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index a1b10ed7c..2f99a0282 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -22,6 +22,8 @@ import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; import { CoreCoursesProvider } from '../../providers/courses'; import { CoreCoursesDelegate } from '../../providers/delegate'; +import { CoreCourseProvider } from '../../../course/providers/course'; +import { CoreCourseHelperProvider } from '../../../course/providers/helper'; /** * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. @@ -40,7 +42,9 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { selfEnrolInstances: any[] = []; paypalEnabled: boolean; dataLoaded: boolean; - prefetchCourseIcon: string; + prefetchCourseData = { + prefetchCourseIcon: 'spinner' + }; protected guestWSAvailable: boolean; protected isGuestEnabled: boolean = false; @@ -55,15 +59,24 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { protected selfEnrolModal: Modal; protected pageDestroyed = false; protected currentInstanceId: number; + protected courseStatusObserver; constructor(private navCtrl: NavController, navParams: NavParams, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, appProvider: CoreAppProvider, private coursesProvider: CoreCoursesProvider, private platform: Platform, private modalCtrl: ModalController, private translate: TranslateService, private eventsProvider: CoreEventsProvider, - private coursesDelegate: CoreCoursesDelegate) { + private coursesDelegate: CoreCoursesDelegate, private courseHelper: CoreCourseHelperProvider, + private courseProvider: CoreCourseProvider) { this.course = navParams.get('course'); this.isMobile = appProvider.isMobile(); this.isDesktop = appProvider.isDesktop(); + + // Listen for status change in course. + this.courseStatusObserver = eventsProvider.on(CoreEventsProvider.COURSE_STATUS_CHANGED, (data) => { + if (data.courseId == this.course.id) { + this.prefetchCourseData.prefetchCourseIcon = this.courseHelper.getCourseStatusIconFromStatus(data.status); + } + }, sitesProvider.getCurrentSiteId()); } /** @@ -88,7 +101,26 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { }); this.getCourse().finally(() => { - // @todo: Prefetch course. + // Determine course prefetch icon. + this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => { + this.prefetchCourseData.prefetchCourseIcon = icon; + + if (icon == 'spinner') { + // Course is being downloaded. Get the download promise. + let promise = this.courseHelper.getCourseDownloadPromise(this.course.id); + if (promise) { + // There is a download promise. If it fails, show an error. + promise.catch((error) => { + if (!this.pageDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }); + } else { + // No download, this probably means that the app was closed while downloading. Set previous status. + this.courseProvider.setCoursePreviousStatus(this.course.id); + } + } + }); }); } @@ -97,6 +129,10 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { */ ngOnDestroy() { this.pageDestroyed = true; + + if (this.courseStatusObserver) { + this.courseStatusObserver.off(); + } } /** @@ -387,4 +423,16 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { }); }); } + + /** + * Prefetch the course. + */ + prefetchCourse() { + this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, undefined, this.course._handlers) + .catch((error) => { + if (!this.pageDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }) + } } diff --git a/src/core/courses/pages/my-courses/my-courses.html b/src/core/courses/pages/my-courses/my-courses.html index 7a2eed8d4..b93f51ee9 100644 --- a/src/core/courses/pages/my-courses/my-courses.html +++ b/src/core/courses/pages/my-courses/my-courses.html @@ -7,6 +7,7 @@ + diff --git a/src/core/courses/pages/my-courses/my-courses.ts b/src/core/courses/pages/my-courses/my-courses.ts index 639302b69..1c19435d0 100644 --- a/src/core/courses/pages/my-courses/my-courses.ts +++ b/src/core/courses/pages/my-courses/my-courses.ts @@ -18,6 +18,7 @@ import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreCoursesProvider } from '../../providers/courses'; +import { CoreCourseHelperProvider } from '../../../course/providers/helper'; /** * Page that displays the list of courses the user is enrolled in. @@ -34,14 +35,16 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { filter = ''; showFilter = false; coursesLoaded = false; + prefetchCoursesData: any = {}; protected prefetchIconInitialized = false; protected myCoursesObserver; protected siteUpdatedObserver; + protected isDestroyed = false; constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider, - private sitesProvider: CoreSitesProvider) {} + private sitesProvider: CoreSitesProvider, private courseHelper: CoreCourseHelperProvider) {} /** * View loaded. @@ -81,7 +84,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { this.filteredCourses = this.courses; this.filter = ''; - // this.initPrefetchCoursesIcon(); + this.initPrefetchCoursesIcon(); }); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true); @@ -139,10 +142,60 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { } } + /** + * Prefetch all the courses. + */ + prefetchCourses() { + let initialIcon = this.prefetchCoursesData.icon; + + this.prefetchCoursesData.icon = 'spinner'; + this.prefetchCoursesData.badge = ''; + return this.courseHelper.confirmAndPrefetchCourses(this.courses, (progress) => { + this.prefetchCoursesData.badge = progress.count + ' / ' + progress.total; + }).then((downloaded) => { + this.prefetchCoursesData.icon = downloaded ? 'ion-android-refresh' : initialIcon; + }, (error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + this.prefetchCoursesData.icon = initialIcon; + } + }).finally(() => { + this.prefetchCoursesData.badge = ''; + }); + } + + /** + * Initialize the prefetch icon for the list of courses. + */ + protected initPrefetchCoursesIcon() { + if (this.prefetchIconInitialized) { + // Already initialized. + return; + } + + this.prefetchIconInitialized = true; + + if (!this.courses || this.courses.length < 2) { + // Not enough courses. + this.prefetchCoursesData.icon = ''; + return; + } + + this.courseHelper.determineCoursesStatus(this.courses).then((status) => { + let icon = this.courseHelper.getCourseStatusIconFromStatus(status); + if (icon == 'spinner') { + // It seems all courses are being downloaded, show a download button instead. + icon = 'cloud-download'; + } + this.prefetchCoursesData.icon = icon; + }); + } + /** * Page destroyed. */ ngOnDestroy() { + this.isDestroyed = true; this.myCoursesObserver && this.myCoursesObserver.off(); this.siteUpdatedObserver && this.siteUpdatedObserver.off(); } diff --git a/src/core/courses/pages/my-overview/my-overview.html b/src/core/courses/pages/my-overview/my-overview.html index b1ac565b5..3090218fc 100644 --- a/src/core/courses/pages/my-overview/my-overview.html +++ b/src/core/courses/pages/my-overview/my-overview.html @@ -46,22 +46,30 @@ +
{{ 'core.courses.inprogress' | translate }} {{ 'core.courses.future' | translate }} {{ 'core.courses.past' | translate }} - + +
+ + + {{prefetchCoursesData[courses.selected].badge}} +
+
+
diff --git a/src/core/courses/pages/my-overview/my-overview.ts b/src/core/courses/pages/my-overview/my-overview.ts index 16f83ec21..fb5183d40 100644 --- a/src/core/courses/pages/my-overview/my-overview.ts +++ b/src/core/courses/pages/my-overview/my-overview.ts @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { IonicPage, NavController } from 'ionic-angular'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreCoursesProvider } from '../../providers/courses'; import { CoreCoursesMyOverviewProvider } from '../../providers/my-overview'; +import { CoreCourseHelperProvider } from '../../../course/providers/helper'; import * as moment from 'moment'; /** @@ -27,7 +28,7 @@ import * as moment from 'moment'; selector: 'page-core-courses-my-overview', templateUrl: 'my-overview.html', }) -export class CoreCoursesMyOverviewPage { +export class CoreCoursesMyOverviewPage implements OnDestroy { tabShown = 'courses'; timeline = { sort: 'sortbydates', @@ -52,21 +53,24 @@ export class CoreCoursesMyOverviewPage { searchEnabled: boolean; filteredCourses: any[]; tabs = []; + prefetchCoursesData = { + inprogress: {}, + past: {}, + future: {} + }; - protected prefetchIconInitialized = false; - protected myCoursesObserver; - protected siteUpdatedObserver; + protected prefetchIconsInitialized = false; + protected isDestroyed; constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, - private domUtils: CoreDomUtilsProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) {} + private domUtils: CoreDomUtilsProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider, + private courseHelper: CoreCourseHelperProvider) {} /** * View loaded. */ ionViewDidLoad() { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); - - // @todo: Course download. } /** @@ -144,6 +148,8 @@ export class CoreCoursesMyOverviewPage { this.courses.filter = ''; this.showFilter = false; this.filteredCourses = this.courses[this.courses.selected]; + + this.initPrefetchCoursesIcons(); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'Error getting my overview data.'); }); @@ -229,6 +235,7 @@ export class CoreCoursesMyOverviewPage { } break; case 'courses': + this.prefetchIconsInitialized = false; return this.fetchMyOverviewCourses(); } }).finally(() => { @@ -315,4 +322,65 @@ export class CoreCoursesMyOverviewPage { selectedChanged() { this.filteredCourses = this.courses[this.courses.selected]; } + + /** + * Prefetch all the shown courses. + */ + prefetchCourses() { + let selected = this.courses.selected, + selectedData = this.prefetchCoursesData[selected], + initialIcon = selectedData.icon; + + selectedData.icon = 'spinner'; + selectedData.badge = ''; + return this.courseHelper.confirmAndPrefetchCourses(this.courses[selected], (progress) => { + selectedData.badge = progress.count + ' / ' + progress.total; + }).then((downloaded) => { + selectedData.icon = downloaded ? 'refresh' : initialIcon; + }, (error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + selectedData.icon = initialIcon; + } + }).finally(() => { + selectedData.badge = ''; + }); + } + + /** + * Initialize the prefetch icon for selected courses. + */ + protected initPrefetchCoursesIcons() { + if (this.prefetchIconsInitialized) { + // Already initialized. + return; + } + + this.prefetchIconsInitialized = true; + + Object.keys(this.prefetchCoursesData).forEach((filter) => { + if (!this.courses[filter] || this.courses[filter].length < 2) { + // Not enough courses. + this.prefetchCoursesData[filter].icon = ''; + return; + } + + this.courseHelper.determineCoursesStatus(this.courses[filter]).then((status) => { + let icon = this.courseHelper.getCourseStatusIconFromStatus(status); + if (icon == 'spinner') { + // It seems all courses are being downloaded, show a download button instead. + icon = 'cloud-download'; + } + this.prefetchCoursesData[filter].icon = icon; + }); + + }); + } + + /** + * Component being destroyed. + */ + ngOnDestroy() { + this.isDestroyed = true; + } }