From 00ef0fa0a98bc77966a217ee52132463340e5107 Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Tue, 6 Feb 2018 08:43:46 +0100
Subject: [PATCH] MOBILE-2335 book: Show download button in section page

---
 .../mod/book/providers/module-handler.ts      |  1 +
 .../mod/book/providers/prefetch-handler.ts    |  4 +-
 src/core/course/components/format/format.html |  2 +-
 src/core/course/components/module/module.html | 20 +++-
 src/core/course/components/module/module.ts   | 94 ++++++++++++++++++-
 src/core/course/providers/module-delegate.ts  |  8 ++
 6 files changed, 121 insertions(+), 8 deletions(-)

diff --git a/src/addon/mod/book/providers/module-handler.ts b/src/addon/mod/book/providers/module-handler.ts
index 7fc1a41ac..88fc25d7e 100644
--- a/src/addon/mod/book/providers/module-handler.ts
+++ b/src/addon/mod/book/providers/module-handler.ts
@@ -50,6 +50,7 @@ export class AddonModBookModuleHandler implements CoreCourseModuleHandler {
             icon: this.courseProvider.getModuleIconSrc('book'),
             title: module.name,
             class: 'addon-mod_book-handler',
+            showDownloadButton: true,
             action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
                 navCtrl.push('AddonModBookIndexPage', {module: module, courseId: courseId}, options);
             }
diff --git a/src/addon/mod/book/providers/prefetch-handler.ts b/src/addon/mod/book/providers/prefetch-handler.ts
index 90b4fcce8..74bfd0aae 100644
--- a/src/addon/mod/book/providers/prefetch-handler.ts
+++ b/src/addon/mod/book/providers/prefetch-handler.ts
@@ -45,7 +45,9 @@ export class AddonModBookPrefetchHandler extends CoreCourseModulePrefetchHandler
         const promises = [];
 
         promises.push(super.downloadOrPrefetch(module, courseId, prefetch));
-        promises.push(this.bookProvider.getBook(courseId, module.id));
+        promises.push(this.bookProvider.getBook(courseId, module.id).catch(() => {
+            // Ignore errors since this WS isn't available in some Moodle versions.
+        }));
 
         return Promise.all(promises);
     }
diff --git a/src/core/course/components/format/format.html b/src/core/course/components/format/format.html
index 0b1e8dcc8..80df9237c 100644
--- a/src/core/course/components/format/format.html
+++ b/src/core/course/components/format/format.html
@@ -56,7 +56,7 @@
         </ion-item>
 
         <ng-container *ngFor="let module of section.modules">
-            <core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [courseId]="course.id" (completionChanged)="completionChanged.emit()"></core-course-module>
+            <core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [courseId]="course.id" [downloadEnabled]="downloadEnabled" (completionChanged)="completionChanged.emit()"></core-course-module>
         </ng-container>
     </section>
 </ng-template>
diff --git a/src/core/course/components/module/module.html b/src/core/course/components/module/module.html
index 90fc35bb4..d4d63e0e7 100644
--- a/src/core/course/components/module/module.html
+++ b/src/core/course/components/module/module.html
@@ -4,14 +4,28 @@
 
     <core-format-text [text]="module.handlerData.title"></core-format-text>
 
-    <div float-end *ngIf="module.uservisible !== false && ((module.handlerData.buttons && module.handlerData.buttons.length > 0) || spinner || module.completionstatus)" class="buttons core-module-buttons" [ngClass]="{'core-button-completion': module.completionstatus}">
+    <!-- Buttons. -->
+    <div float-end *ngIf="module.uservisible !== false" class="buttons core-module-buttons" [ngClass]="{'core-button-completion': module.completionstatus}">
+        <!-- Module completion. -->
         <core-course-module-completion *ngIf="module.completionstatus" [completion]="module.completionstatus" [moduleName]="module.name" (completionChanged)="completionChanged.emit()"></core-course-module-completion>
 
-        <button ion-button icon-only clear *ngFor="let button of module.handlerData.buttons" [hidden]="button.hidden" (click)="buttonClicked($event, button)" color="dark" class="core-animate-show-hide" [attr.aria-label]="button.label | translate">
+        <!-- Download button. -->
+        <button *ngIf="downloadEnabled && showDownload" [hidden]="spinner || module.handlerData.spinner" ion-button icon-only clear (click)="download($event, false)" color="dark" class="core-animate-show-hide" [attr.aria-label]="'core.download' | translate">
+            <ion-icon name="cloud-download"></ion-icon>
+        </button>
+
+        <!-- Refresh button. -->
+        <button *ngIf="downloadEnabled && showRefresh" [hidden]="spinner || module.handlerData.spinner" ion-button icon-only clear (click)="download($event, true)" color="dark" class="core-animate-show-hide" [attr.aria-label]="'core.refresh' | translate">
+            <ion-icon name="refresh"></ion-icon>
+        </button>
+
+        <!-- Buttons defined by the module handler. -->
+        <button ion-button icon-only clear *ngFor="let button of module.handlerData.buttons" [hidden]="button.hidden || spinner || module.handlerData.spinner" (click)="buttonClicked($event, button)" color="dark" class="core-animate-show-hide" [attr.aria-label]="button.label | translate">
             <ion-icon [name]="button.icon" [ios]="button.iosIcon || ''" [md]="button.mdIcon || ''"></ion-icon>
         </button>
 
-        <ion-spinner *ngIf="module.handlerData.spinner" class="core-animate-show-hide"></ion-spinner>
+        <!-- Spinner. -->
+        <ion-spinner *ngIf="spinner || module.handlerData.spinner" class="core-animate-show-hide"></ion-spinner>
     </div>
 
     <div *ngIf="module.visible === 0 || module.availabilityinfo">
diff --git a/src/core/course/components/module/module.ts b/src/core/course/components/module/module.ts
index b450093b1..15a66286b 100644
--- a/src/core/course/components/module/module.ts
+++ b/src/core/course/components/module/module.ts
@@ -12,9 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
 import { NavController } from 'ionic-angular';
+import { CoreEventsProvider } from '../../../../providers/events';
+import { CoreSitesProvider } from '../../../../providers/sites';
+import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
+import { CoreCourseHelperProvider } from '../../providers/helper';
 import { CoreCourseModuleHandlerButton } from '../../providers/module-delegate';
+import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler } from '../../providers/module-prefetch-delegate';
+import { CoreConstants } from '../../../constants';
 
 /**
  * Component to display a module entry in a list of modules.
@@ -27,12 +33,43 @@ import { CoreCourseModuleHandlerButton } from '../../providers/module-delegate';
     selector: 'core-course-module',
     templateUrl: 'module.html'
 })
-export class CoreCourseModuleComponent implements OnInit {
+export class CoreCourseModuleComponent implements OnInit, OnDestroy {
     @Input() module: any; // The module to render.
     @Input() courseId: number; // The course the module belongs to.
+    @Input('downloadEnabled') set enabled(value: boolean) {
+        this.downloadEnabled = value;
+
+        if (this.module.handlerData.showDownloadButton && this.downloadEnabled && !this.statusObserver) {
+            // First time that the download is enabled. Initialize the data.
+            this.spinner = true; // Show spinner while calculating the status.
+
+            this.prefetchHandler = this.prefetchDelegate.getPrefetchHandlerFor(this.module);
+
+            // Get current status to decide which icon should be shown.
+            this.prefetchDelegate.getModuleStatus(this.module, this.courseId).then(this.showStatus.bind(this));
+
+            // Listen for changes on this module status.
+            this.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
+                if (data.componentId === this.module.id && this.prefetchHandler &&
+                        data.component === this.prefetchHandler.component) {
+                    this.showStatus(data.status);
+                }
+            }, this.sitesProvider.getCurrentSiteId());
+        }
+    }
     @Output() completionChanged?: EventEmitter<void>; // Will emit an event when the module completion changes.
 
-    constructor(private navCtrl: NavController) {
+    showDownload: boolean; // Whether to display the download button.
+    showRefresh: boolean; // Whether to display the refresh button.
+    spinner: boolean; // Whether to display a spinner.
+    downloadEnabled: boolean; // Whether the download of sections and modules is enabled.
+
+    protected prefetchHandler: CoreCourseModulePrefetchHandler;
+    protected statusObserver;
+
+    constructor(protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
+            protected domUtils: CoreDomUtilsProvider, protected courseHelper: CoreCourseHelperProvider,
+            protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider) {
         this.completionChanged = new EventEmitter();
     }
 
@@ -68,4 +105,55 @@ export class CoreCourseModuleComponent implements OnInit {
             button.action(event, this.navCtrl, this.module, this.courseId);
         }
     }
+
+    /**
+     * Download the module.
+     *
+     * @param {Event} event Click event.
+     * @param {boolean} refresh Whether it's refreshing.
+     */
+    download(event: Event, refresh: boolean): void {
+        event.preventDefault();
+        event.stopPropagation();
+
+        if (!this.prefetchHandler) {
+            return;
+        }
+
+        // Show spinner since this operation might take a while.
+        this.spinner = true;
+
+        // Get download size to ask for confirm if it's high.
+        this.prefetchHandler.getDownloadSize(module, this.courseId).then((size) => {
+            this.courseHelper.prefetchModule(this.prefetchHandler, this.module, size, this.courseId, refresh).catch((error) => {
+                // Error or cancelled.
+                this.spinner = false;
+            });
+        }).catch((error) => {
+            // Error getting download size, hide spinner.
+            this.spinner = false;
+            this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
+        });
+    }
+
+    /**
+     * Show download buttons according to module status.
+     *
+     * @param {string} status Module status.
+     */
+    protected showStatus(status: string): void {
+        if (status) {
+            this.spinner = status === CoreConstants.DOWNLOADING;
+            this.showDownload = status === CoreConstants.NOT_DOWNLOADED;
+            this.showRefresh = status === CoreConstants.OUTDATED ||
+                (!this.prefetchDelegate.canCheckUpdates() && status === CoreConstants.DOWNLOADED);
+        }
+    }
+
+    /**
+     * Component destroyed.
+     */
+    ngOnDestroy(): void {
+        this.statusObserver && this.statusObserver.off();
+    }
 }
diff --git a/src/core/course/providers/module-delegate.ts b/src/core/course/providers/module-delegate.ts
index 1fbc7e2be..1a2e8c057 100644
--- a/src/core/course/providers/module-delegate.ts
+++ b/src/core/course/providers/module-delegate.ts
@@ -68,6 +68,14 @@ export interface CoreCourseModuleHandlerData {
      */
     class?: string;
 
+    /**
+     * Whether to display a button to download/refresh the module if it's downloadable.
+     * If it's set to true, the app will show a download/refresh button when needed and will handle the download of the
+     * module using CoreCourseModulePrefetchDelegate.
+     * @type {boolean}
+     */
+    showDownloadButton?: boolean;
+
     /**
      * The buttons to display in the module item.
      * @type {CoreCourseModuleHandlerButton[]}