From 995a36cc6ea6e415a1fb7f5f8cebc0a241cda38b Mon Sep 17 00:00:00 2001 From: martinmine Date: Thu, 20 Oct 2016 15:03:41 +0200 Subject: [PATCH 001/218] MOBILE-1796 core: Updated to NodeJS v6.9.1 --- .travis.yml | 2 +- gulpfile.js | 4 ++-- ionic.config.json | 10 ++++++++++ ionic.project | 2 +- package.json | 43 +++++++++++++++++++++++++++---------------- upgrade.txt | 7 +++++++ 6 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 ionic.config.json diff --git a/.travis.yml b/.travis.yml index 17681a7f198..a74010f874b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: language: node_js node_js: -- '0.12' +- '6.9.1' before_install: - npm cache clean diff --git a/gulpfile.js b/gulpfile.js index a0f1b312697..ac5aa90a167 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,7 @@ var stripComments = require('gulp-strip-comments'); var removeEmptyLines = require('gulp-remove-empty-lines'); var clipEmptyFiles = require('gulp-clip-empty-files'); var sass = require('gulp-sass'); -var minifyCss = require('gulp-minify-css'); +var cleanCSS = require('gulp-clean-css'); var rename = require('gulp-rename'); var tap = require('gulp-tap'); var fs = require('fs'); @@ -408,7 +408,7 @@ gulp.task('sass', ['sass-build'], function(done) { .pipe(concat('mm.bundle.css')) .pipe(sass()) .pipe(gulp.dest(paths.build)) - .pipe(minifyCss({ + .pipe(cleanCSS({ keepSpecialComments: 0 })) .pipe(rename({ extname: '.min.css' })) diff --git a/ionic.config.json b/ionic.config.json new file mode 100644 index 00000000000..12afec3b87e --- /dev/null +++ b/ionic.config.json @@ -0,0 +1,10 @@ +{ + "name": "moodlemobile2", + "app_id": "", + "watchPatterns": [ + "www/**/*.html", + "www/build/**/*", + "www/index.html", + "!www/lib/**/*" + ] +} \ No newline at end of file diff --git a/ionic.project b/ionic.project index 356bb2cdf09..e31d06752ad 100644 --- a/ionic.project +++ b/ionic.project @@ -1,5 +1,5 @@ { - "name": "mm2", + "name": "moodlemobile2", "app_id": "", "gulpStartupTasks": [ "build", diff --git a/package.json b/package.json index 6e2fac9163a..d73f965c7cc 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,46 @@ { "name": "mm2", "version": "1.0.0", - "description": "mm2: An Ionic project", + "description": "Moodle Mobile 2: The official mobile app for Moodle.", + "repository": { + "type": "git", + "url": "https://github.com/moodlehq/moodlemobile2.git" + }, + "license": "Apache-2.0", + "licenses": [ + { + "type": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], "dependencies": { - "gulp": "^3.5.6", - "gulp-concat": "^2.2.0", - "gulp-minify-css": "^0.3.0", + "gulp": "^3.9.1", + "gulp-clean-css": "^3.0.3", + "gulp-concat": "^2.6.0", "gulp-rename": "^1.2.0", - "gulp-sass": "^1.3.3" + "gulp-sass": "^2.3.2" }, "devDependencies": { - "appium": "^1.4.11", + "appium": "^1.6.0", "bower": "^1.3.3", "gulp-clip-empty-files": "^0.1.1", "gulp-concat-util": "^0.5.2", - "gulp-file": "^0.2.0", - "gulp-insert": "^0.4.0", - "gulp-ng-annotate": "^1.1.0", - "gulp-remove-empty-lines": "0.0.2", + "gulp-file": "^0.3.0", + "gulp-insert": "^0.5.0", + "gulp-ng-annotate": "^2.0.0", + "gulp-remove-empty-lines": "0.0.8", "gulp-slash": "^1.1.3", - "gulp-strip-comments": "^1.0.1", + "gulp-strip-comments": "^2.4.3", "gulp-tap": "^0.1.3", - "gulp-util": "^2.2.14", + "gulp-util": "^3.0.7", "gulp-zip": "^3.2.0", "jasmine": "^2.2.1", "jasmine-core": "^2.2.0", - "karma": "^0.12.31", - "karma-chrome-launcher": "^0.1.7", - "karma-jasmine": "^0.3.5", + "karma": "^1.3.0", + "karma-chrome-launcher": "^2.0.0", + "karma-jasmine": "^1.0.2", "plist": "^1.1.0", - "protractor": "^2.2.0", + "protractor": "^4.0.9", "shelljs": "^0.3.0", "through": "^2.3.6", "wd": "^0.4.0", diff --git a/upgrade.txt b/upgrade.txt index ad79e14400b..ad400b58018 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -1,6 +1,13 @@ This files describes API changes in the Moodle Mobile app, information provided here is intended especially for developers. +=== 3.3 === + + * The project now supports Ionic CLI v2 and Node 6.9. We recommend updating node, npm, Ionic CLI and project dependencies: + npm install -g npm@latest + npm install -g ionic + npm install + === 3.2.1 === * mmaGrades addon has been moved to core with mmGrades name (and derivatives). From bab7084f09194665a3454788e9c9b05b2ac1adb3 Mon Sep 17 00:00:00 2001 From: martinmine Date: Thu, 10 Nov 2016 16:36:38 +0100 Subject: [PATCH 002/218] MOBILE-1877 core: Moved dependencies to devDependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d73f965c7cc..20eedbb8b44 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,15 @@ } ], "dependencies": { - "gulp": "^3.9.1", - "gulp-clean-css": "^3.0.3", - "gulp-concat": "^2.6.0", - "gulp-rename": "^1.2.0", - "gulp-sass": "^2.3.2" }, "devDependencies": { "appium": "^1.6.0", "bower": "^1.3.3", + "gulp": "^3.9.1", + "gulp-clean-css": "^3.0.3", + "gulp-concat": "^2.6.0", + "gulp-rename": "^1.2.0", + "gulp-sass": "^2.3.2", "gulp-clip-empty-files": "^0.1.1", "gulp-concat-util": "^0.5.2", "gulp-file": "^0.3.0", From 85e2233342e65286081607bdac5f48c247c427a6 Mon Sep 17 00:00:00 2001 From: martinmine Date: Tue, 8 Nov 2016 16:19:51 +0100 Subject: [PATCH 003/218] MOBILE-1870 Added npm commands --- gulpfile.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 30 +++++++++++++++++++++++++++--- www/build/empty | 0 3 files changed, 73 insertions(+), 3 deletions(-) delete mode 100644 www/build/empty diff --git a/gulpfile.js b/gulpfile.js index ac5aa90a167..e91b928d246 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,6 +19,7 @@ var gulpSlash = require('gulp-slash'); var ngAnnotate = require('gulp-ng-annotate'); var yargs = require('yargs'); var zip = require('gulp-zip'); +var clean = require('gulp-clean'); // Given a list of paths to search and the path to an addon, return the list of paths to search only inside the addon folder. function getRemoteAddonPaths(paths, pathToAddon) { @@ -894,3 +895,48 @@ gulp.task('remoteaddon', ['remoteaddon-build', 'remoteaddon-sass', 'remoteaddon- done(); }); }); + +// Cleans the development environment by deleting downloaded files and libraries +gulp.task('clean-libs', ['clean-www-libs', 'clean-ionic-platforms', 'clean-e2e-build', 'clean-sass-cache', 'clean-ionic-plugins']); + +// Removes the contents in the /www/lib/ directory +gulp.task('clean-www-libs', function() { + return gulp.src('www/lib/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /platforms directory +gulp.task('clean-ionic-platforms', function() { + return gulp.src('platforms/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /plugins directory +gulp.task('clean-ionic-plugins', function() { + return gulp.src('plugins/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /www/build directory +gulp.task('clean-build', function() { + return gulp.src('www/build/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /e2e/build directory +gulp.task('clean-e2e-build', function() { + return gulp.src('e2e/build/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /.sass-cache directory +gulp.task('clean-sass-cache', function() { + return gulp.src('.sass-cache/', {read: false}) + .pipe(clean()); +}); + +// Removes the contents in the /node-modules directory +gulp.task('clean-node-modules', function() { + return gulp.src('node_modules/', {read: false}) + .pipe(clean()); +}); diff --git a/package.json b/package.json index 20eedbb8b44..553a2c9c0d5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "gulp-concat": "^2.6.0", "gulp-rename": "^1.2.0", "gulp-sass": "^2.3.2", + "gulp-clean": "^0.3.2", "gulp-clip-empty-files": "^0.1.1", "gulp-concat-util": "^0.5.2", "gulp-file": "^0.3.0", @@ -96,7 +97,30 @@ "nl.kingsquare.cordova.background-audio" ], "cordovaPlatforms": [ - "ios", - "android" - ] + { + "platform": "ios", + "version": "4.3.0", + "locator": "ios@4.3.0" + }, + { + "platform": "android", + "version": "6.1.2", + "locator": "android@6.1.2" + } + ], + "scripts": { + "setup": "npm install && ionic state restore && bower install && gulp", + "reinstall": "gulp clean-build && gulp clean-libs && gulp clean-node-modules && npm run-script setup", + "start": "ionic serve", + "clean": "gulp clean-build", + "build.e2e": "gulp e2e-build", + "build.dev": "gulp", + "build.android": "ionic build android", + "build.ios": "ionic build ios", + "ios": "ionic run ios", + "android": "ionic run android", + "serve.e2e": "ionic serve -b -a", + "serve.dev": "ionic serve", + "e2e": "protractor e2e/build/protractor.conf.js" + } } diff --git a/www/build/empty b/www/build/empty deleted file mode 100644 index e69de29bb2d..00000000000 From 0f4dbc3e5f3b7cc281ffe55a52ec1a0f0cc20b28 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 3 Mar 2017 14:39:32 +0100 Subject: [PATCH 004/218] MOBILE-1704 bower: More coherence in dependencies --- bower.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bower.json b/bower.json index 184a2fcfe27..8492720bf2a 100644 --- a/bower.json +++ b/bower.json @@ -3,25 +3,25 @@ "private": "true", "devDependencies": { "ionic": "1.3.2", - "ydn.db": ">=1.0.3", - "ngCordova": ">=0.1.24-alpha", + "ydn.db": "1.0.3", "angular-mocks": "1.5.3", - "angular-md5": "0.1.8", - "angular-translate": "~2.13.0", - "angular-translate-loader-partial": "~2.13.0", "angular-aria": "1.5.3", - "moment": "~2.10.6", - "jszip": "~2.5.0", - "oclazyload": "~1.0.9", - "ckeditor": "=4.5.9", - "angular-ckeditor": "=1.0.3", - "angular-messages": "1.5.3" + "angular-messages": "1.5.3", + "ckeditor": "4.5.9", + "angular-ckeditor": "1.0.3", + "ngCordova": "^0.1.26-alpha", + "angular-md5": "^0.1.8", + "angular-translate": "^2.13.0", + "angular-translate-loader-partial": "^2.13.0", + "moment": "^2.10.6", + "jszip": "^2.5.0", + "oclazyload": "^1.0.9", + "chart.js": "^2.4.0", + "angular-chart.js": "^1.0.2" }, "resolutions": { "angular": "1.5.3" }, "dependencies": { - "chart.js": "^2.4.0", - "angular-chart.js": "^1.0.2" } } From f198d0a956a3740929da30dcd6495955a1b671e3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 7 Mar 2017 15:36:08 +0100 Subject: [PATCH 005/218] MOBILE-1923 styles: Load remote styles in login screen --- www/addons/remotestyles/main.js | 33 ++++++++++++++- .../remotestyles/services/remotestyles.js | 41 +++++++++++++++++++ .../login/controllers/credentials.js | 25 +++++++++-- www/core/components/login/main.js | 2 + www/core/lib/sitesmanager.js | 2 + 5 files changed, 97 insertions(+), 6 deletions(-) diff --git a/www/addons/remotestyles/main.js b/www/addons/remotestyles/main.js index 794e967e55d..180a0bad3d6 100644 --- a/www/addons/remotestyles/main.js +++ b/www/addons/remotestyles/main.js @@ -25,10 +25,23 @@ angular.module('mm.addons.remotestyles', []) }) .run(function($mmEvents, mmCoreEventLogin, mmCoreEventLogout, mmCoreEventSiteAdded, mmCoreEventSiteUpdated, $mmaRemoteStyles, - $mmSite, mmCoreEventSiteDeleted) { + $mmSite, mmCoreEventSiteDeleted, mmCoreLoginSiteCheckedEvent, mmCoreLoginSiteUncheckedEvent) { + var addingSite, + unloadTmpStyles; $mmEvents.on(mmCoreEventSiteAdded, function(siteId) { - $mmaRemoteStyles.addSite(siteId); + addingSite = siteId; + + $mmaRemoteStyles.addSite(siteId).finally(function() { + if (addingSite == siteId) { + addingSite = false; + } + + if (unloadTmpStyles == siteId) { + // This site had some tmp styles loaded, unload them. + $mmaRemoteStyles.unloadTmpStyles(); + } + }); }); $mmEvents.on(mmCoreEventSiteUpdated, function(siteId) { // Load only if current site was updated. @@ -47,4 +60,20 @@ angular.module('mm.addons.remotestyles', []) $mmEvents.on(mmCoreEventSiteDeleted, function(site) { $mmaRemoteStyles.removeSite(site.id); }); + + // Load temporary styles when site config is checked in login. + $mmEvents.on(mmCoreLoginSiteCheckedEvent, function(data) { + $mmaRemoteStyles.loadTmpStyles(data.config.mobilecssurl); + }); + + // Unload temporary styles when site config is "unchecked" in login. + $mmEvents.on(mmCoreLoginSiteUncheckedEvent, function(data) { + if (data.siteid && data.siteid == addingSite) { + // The tmp styles are from a site that is being added. Wait for the site styles to be loaded before removing + // the tmp styles so there is no blink effect. + unloadTmpStyles = data.siteid; + } else { + $mmaRemoteStyles.unloadTmpStyles(); + } + }); }); diff --git a/www/addons/remotestyles/services/remotestyles.js b/www/addons/remotestyles/services/remotestyles.js index edc6f43c157..2a61132928c 100644 --- a/www/addons/remotestyles/services/remotestyles.js +++ b/www/addons/remotestyles/services/remotestyles.js @@ -193,6 +193,35 @@ angular.module('mm.addons.remotestyles') return $q.reject(); }; + /** + * Load styles for a temporary site. These styles aren't prefetched. + * + * @module mm.addons.remotestyles + * @ngdoc method + * @name $mmaRemoteStyles#loadTmpStyles + * @param {String} url URL to get the styles from. + * @return {Void} + */ + self.loadTmpStyles = function(url) { + if (!url) { + return; + } + + return $http.get(url).then(function(response) { + if (typeof response.data == 'string') { + var el = angular.element(''); + el.html(response.data); + angular.element(document.head).append(el); + remoteStylesEls.tmpsite = { + element: el, + hash: '' + }; + } else { + return $q.reject(); + } + }); + }; + /** * Preload the styles of the current site (stored in DB). Please do not use. * @@ -292,5 +321,17 @@ angular.module('mm.addons.remotestyles') }); } + /** + * Unload styles for a temporary site. + * + * @module mm.addons.remotestyles + * @ngdoc method + * @name $mmaRemoteStyles#unloadTmpStyles + * @return {Void} + */ + self.unloadTmpStyles = function() { + return self.removeSite('tmpsite'); + }; + return self; }); diff --git a/www/core/components/login/controllers/credentials.js b/www/core/components/login/controllers/credentials.js index 6be5ba09cdf..2597b6537cb 100644 --- a/www/core/components/login/controllers/credentials.js +++ b/www/core/components/login/controllers/credentials.js @@ -21,8 +21,9 @@ angular.module('mm.core.login') * @ngdoc controller * @name mmLoginCredentialsCtrl */ -.controller('mmLoginCredentialsCtrl', function($scope, $stateParams, $mmSitesManager, $mmUtil, $ionicHistory, $mmApp, - $q, $mmLoginHelper, $mmContentLinksDelegate, $mmContentLinksHelper, $translate) { +.controller('mmLoginCredentialsCtrl', function($scope, $stateParams, $mmSitesManager, $mmUtil, $ionicHistory, $mmApp, $mmEvents, + $q, $mmLoginHelper, $mmContentLinksDelegate, $mmContentLinksHelper, $translate, mmCoreLoginSiteCheckedEvent, + mmCoreLoginSiteUncheckedEvent) { $scope.siteurl = $stateParams.siteurl; $scope.credentials = { @@ -31,7 +32,9 @@ angular.module('mm.core.login') $scope.siteChecked = false; var urlToOpen = $stateParams.urltoopen, - siteConfig = $stateParams.siteconfig; + siteConfig = $stateParams.siteconfig, + eventThrown = false, + siteId; treatSiteConfig(siteConfig); @@ -80,6 +83,13 @@ angular.module('mm.core.login') $scope.logourl = siteConfig.logourl || siteConfig.compactlogourl; $scope.authInstructions = siteConfig.authinstructions || $translate.instant('mm.login.loginsteps'); $scope.canSignup = siteConfig.registerauth == 'email' && !$mmLoginHelper.isEmailSignupDisabled(siteConfig); + + if (!eventThrown && !$scope.$$destroyed) { + eventThrown = true; + $mmEvents.trigger(mmCoreLoginSiteCheckedEvent, { + config: siteConfig + }); + } } else { $scope.sitename = null; $scope.logourl = null; @@ -130,9 +140,10 @@ angular.module('mm.core.login') // Start the authentication process. return $mmSitesManager.getUserToken(siteurl, username, password).then(function(data) { - return $mmSitesManager.newSite(data.siteurl, data.token, data.privatetoken).then(function() { + return $mmSitesManager.newSite(data.siteurl, data.token, data.privatetoken).then(function(id) { delete $scope.credentials; // Delete username and password from the scope. $ionicHistory.nextViewOptions({disableBack: true}); + siteId = id; if (urlToOpen) { // There's a content link to open. @@ -156,4 +167,10 @@ angular.module('mm.core.login') }); }; + $scope.$on('$destroy', function() { + $mmEvents.trigger(mmCoreLoginSiteUncheckedEvent, { + siteid: siteId, + config: siteConfig + }); + }); }); diff --git a/www/core/components/login/main.js b/www/core/components/login/main.js index 1d6f451d2d3..805c2497e0a 100644 --- a/www/core/components/login/main.js +++ b/www/core/components/login/main.js @@ -15,6 +15,8 @@ angular.module('mm.core.login', []) .constant('mmCoreLoginTokenChangePassword', '*changepassword*') // Deprecated. +.constant('mmCoreLoginSiteCheckedEvent', 'mm_login_site_checked') +.constant('mmCoreLoginSiteUncheckedEvent', 'mm_login_site_unchecked') .config(function($stateProvider, $urlRouterProvider, $mmInitDelegateProvider, mmInitDelegateMaxAddonPriority) { diff --git a/www/core/lib/sitesmanager.js b/www/core/lib/sitesmanager.js index ea988a30020..fe6b6e3c2f5 100644 --- a/www/core/lib/sitesmanager.js +++ b/www/core/lib/sitesmanager.js @@ -333,6 +333,8 @@ angular.module('mm.core') // Store session. self.login(siteId); $mmEvents.trigger(mmCoreEventSiteAdded, siteId); + + return siteId; }); } else { return $mmLang.translateAndReject('mm.login.invalidmoodleversion'); From 085f433f156988dabd1a99e8b9781ff4b37c6993 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 15 Mar 2017 09:34:44 +0100 Subject: [PATCH 006/218] MOBILE-2051 resource: Re-download the file even if it's big --- www/addons/mod/resource/services/resource.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/addons/mod/resource/services/resource.js b/www/addons/mod/resource/services/resource.js index 91cd5bafefd..fcba0d29e88 100644 --- a/www/addons/mod/resource/services/resource.js +++ b/www/addons/mod/resource/services/resource.js @@ -238,7 +238,7 @@ angular.module('mm.addons.mod_resource') if (status === mmCoreDownloaded) { // Get the local file URL. - return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod); + return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod, false); } else if (status === mmCoreDownloading) { // Return the online URL. return fixedUrl; @@ -251,7 +251,7 @@ angular.module('mm.addons.mod_resource') return $mmFilepool.shouldDownloadBeforeOpen(fixedUrl, contents[0].filesize).then(function() { // Download and then return the local URL. return $mmFilepool.downloadPackage(siteId, files, component, moduleId, revision, timeMod).then(function() { - return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod); + return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod, false); }); }, function() { // Start the download if in wifi, but return the URL right away so the file is opened. @@ -264,7 +264,7 @@ angular.module('mm.addons.mod_resource') return fixedUrl; } else { // Outdated but offline, so we return the local URL. - return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod); + return $mmFilepool.getUrlByUrl(siteId, url, component, moduleId, timeMod, false); } }); } From 5c84b46bc85f545cc9185095d91d047d7c93c328 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 15 Mar 2017 12:01:11 +0100 Subject: [PATCH 007/218] MOBILE-2064 android: Force min and target SDK --- config.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.xml b/config.xml index f4fa3d6b319..dbb1c9ab999 100644 --- a/config.xml +++ b/config.xml @@ -17,6 +17,8 @@ + + From be39e6543d87e2c5e3d60e6c4ac06342772d4f68 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 21 Feb 2017 13:51:38 +0100 Subject: [PATCH 008/218] MOBILE-2010 lesson: Lesson initial implementation --- www/addons/mod/lesson/controllers/index.js | 112 ++++++++++ www/addons/mod/lesson/main.js | 41 ++++ www/addons/mod/lesson/services/handlers.js | 74 +++++++ www/addons/mod/lesson/services/lesson.js | 229 +++++++++++++++++++++ www/addons/mod/lesson/templates/index.html | 20 ++ www/addons/mod/quiz/main.js | 1 - 6 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 www/addons/mod/lesson/controllers/index.js create mode 100644 www/addons/mod/lesson/main.js create mode 100644 www/addons/mod/lesson/services/handlers.js create mode 100644 www/addons/mod/lesson/services/lesson.js create mode 100644 www/addons/mod/lesson/templates/index.html diff --git a/www/addons/mod/lesson/controllers/index.js b/www/addons/mod/lesson/controllers/index.js new file mode 100644 index 00000000000..87b16d777d3 --- /dev/null +++ b/www/addons/mod/lesson/controllers/index.js @@ -0,0 +1,112 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson') + +/** + * Lesson index controller. + * + * @module mm.addons.mod_lesson + * @ngdoc controller + * @name mmaModLessonIndexCtrl + */ +.controller('mmaModLessonIndexCtrl', function($scope, $stateParams, $mmaModLesson, $mmCourse, $q, $translate, $ionicScrollDelegate, + $mmEvents, $mmText, $mmUtil, $mmCourseHelper, mmaModLessonComponent, $mmApp, mmCoreEventOnlineStatusChanged) { + + var module = $stateParams.module || {}, + courseId = $stateParams.courseid, + lesson, + scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonIndexScroll'), + onlineObserver; + + $scope.title = module.name; + $scope.moduleUrl = module.url; + $scope.refreshIcon = 'spinner'; + $scope.component = mmaModLessonComponent; + $scope.componentId = module.id; + + // Convenience function to get Lesson data. + function fetchLessonData(refresh) { + $scope.isOnline = $mmApp.isOnline(); + + return $mmaModLesson.getLesson(courseId, module.id).then(function(lessonData) { + lesson = lessonData; + $scope.lesson = lesson; + + $scope.now = Date.now(); + $scope.title = lesson.name || $scope.title; + $scope.description = lesson.intro; // Show description only if intro is present. + + return $mmaModLesson.getAccessInformation(lesson.id); + }).then(function(info) { + $scope.preventMessages = info.preventaccessreasons; + }).then(function() { + // All data obtained, now fill the context menu. + $mmCourseHelper.fillContextMenu($scope, module, courseId, refresh, mmaModLessonComponent); + }).catch(function(message) { + if (!refresh && !lesson) { + // Get lesson failed, retry without using cache since it might be a new activity. + return refreshData(); + } + return $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); + }); + } + + // Refreshes data. + function refreshData() { + var promises = []; + + promises.push($mmaModLesson.invalidateLessonData(courseId)); + + return $q.all(promises).finally(function() { + return fetchLessonData(true); + }); + } + + // Fetch the Lesson data. + fetchLessonData().then(function() { + $mmaModLesson.logViewLesson(lesson.id).then(function() { + $mmCourse.checkModuleCompletion(courseId, module.completionstatus); + }); + }).finally(function() { + $scope.lessonLoaded = true; + $scope.refreshIcon = 'ion-refresh'; + }); + + // Pull to refresh. + $scope.refreshLesson = function() { + if ($scope.lessonLoaded) { + $scope.refreshIcon = 'spinner'; + return refreshData().finally(function() { + $scope.refreshIcon = 'ion-refresh'; + $scope.$broadcast('scroll.refreshComplete'); + }); + } + }; + + // Context Menu Description action. + $scope.expandDescription = function() { + $mmText.expandText($translate.instant('mm.core.description'), $scope.description, false, mmaModLessonComponent, module.id); + }; + + // Refresh online status when changes. + onlineObserver = $mmEvents.on(mmCoreEventOnlineStatusChanged, function(online) { + $scope.isOnline = online; + }); + + $scope.$on('$destroy', function() { + onlineObserver && onlineObserver.off && onlineObserver.off(); + }); + +}); diff --git a/www/addons/mod/lesson/main.js b/www/addons/mod/lesson/main.js new file mode 100644 index 00000000000..6ec6d7b92da --- /dev/null +++ b/www/addons/mod/lesson/main.js @@ -0,0 +1,41 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson', ['mm.core']) + +.constant('mmaModLessonComponent', 'mmaModLesson') + +.config(function($stateProvider) { + + $stateProvider + + .state('site.mod_lesson', { + url: '/mod_lesson', + params: { + module: null, + courseid: null + }, + views: { + 'site': { + controller: 'mmaModLessonIndexCtrl', + templateUrl: 'addons/mod/lesson/templates/index.html' + } + } + }); + +}) + +.config(function($mmCourseDelegateProvider) { + $mmCourseDelegateProvider.registerContentHandler('mmaModLesson', 'lesson', '$mmaModLessonHandlers.courseContentHandler'); +}); diff --git a/www/addons/mod/lesson/services/handlers.js b/www/addons/mod/lesson/services/handlers.js new file mode 100644 index 00000000000..9633f81c6fb --- /dev/null +++ b/www/addons/mod/lesson/services/handlers.js @@ -0,0 +1,74 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson') + +/** + * Mod Lesson handlers. + * + * @module mm.addons.mod_lesson + * @ngdoc service + * @name $mmaModLessonHandlers + */ +.factory('$mmaModLessonHandlers', function($mmCourse, $mmaModLesson, $state) { + + var self = {}; + + /** + * Course content handler. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLessonHandlers#courseContentHandler + */ + self.courseContentHandler = function() { + var self = {}; + + /** + * Whether or not the module is enabled for the site. + * + * @return {Boolean} + */ + self.isEnabled = function() { + return $mmaModLesson.isPluginEnabled(); + }; + + /** + * Get the controller. + * + * @param {Object} module The module info. + * @param {Number} courseId The course ID. + * @return {Function} + */ + self.getController = function(module, courseId) { + return function($scope) { + $scope.icon = $mmCourse.getModuleIconSrc('lesson'); + $scope.title = module.name; + $scope.buttons = []; + + $scope.action = function(e) { + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + $state.go('site.mod_lesson', {module: module, courseid: courseId}); + }; + }; + }; + + return self; + }; + + return self; +}); diff --git a/www/addons/mod/lesson/services/lesson.js b/www/addons/mod/lesson/services/lesson.js new file mode 100644 index 00000000000..e012450dc4d --- /dev/null +++ b/www/addons/mod/lesson/services/lesson.js @@ -0,0 +1,229 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson') + +/** + * Lesson service. + * + * @module mm.addons.mod_lesson + * @ngdoc service + * @name $mmaModLesson + */ +.factory('$mmaModLesson', function($log, $mmSitesManager, $q) { + + $log = $log.getInstance('$mmaModLesson'); + + var self = {}; + + /** + * Get cache key for access information WS calls. + * + * @param {Number} lessonId Lesson ID. + * @return {String} Cache key. + */ + function getAccessInformationCacheKey(lessonId) { + return 'mmaModLesson:accessInfo:' + lessonId; + } + + /** + * Get the access information of a certain lesson. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#getAccessInformation + * @param {Number} lessonId Lesson ID. + * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. + * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the access information- + */ + self.getAccessInformation = function(lessonId, forceCache, ignoreCache, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: lessonId + }, + preSets = { + cacheKey: getAccessInformationCacheKey(lessonId) + }; + + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; + } + + return site.read('mod_lesson_get_lesson_access_information', params, preSets); + }); + }; + + /** + * Get cache key for Lesson data WS calls. + * + * @param {Number} courseId Course ID. + * @return {String} Cache key. + */ + function getLessonDataCacheKey(courseId) { + return 'mmaModLesson:lesson:' + courseId; + } + + /** + * Get a Lesson with key=value. If more than one is found, only the first will be returned. + * + * @param {String} siteId Site ID. + * @param {Number} courseId Course ID. + * @param {String} key Name of the property to check. + * @param {Mixed} value Value to search. + * @param {Boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @return {Promise} Promise resolved when the Lesson is retrieved. + */ + function getLesson(siteId, courseId, key, value, forceCache) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + courseids: [courseId] + }, + preSets = { + cacheKey: getLessonDataCacheKey(courseId) + }; + + if (forceCache) { + preSets.omitExpires = true; + } + + return site.read('mod_lesson_get_lessons_by_courses', params, preSets).then(function(response) { + if (response && response.lessons) { + for (var i = 0; i < response.lessons.length; i++) { + var lesson = response.lessons[i]; + if (lesson[key] == value) { + return lesson; + } + } + } + return $q.reject(); + }); + }); + } + + /** + * Get a Lesson by module ID. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#getLesson + * @param {Number} courseId Course ID. + * @param {Number} cmid Course module ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @return {Promise} Promise resolved when the Lesson is retrieved. + */ + self.getLesson = function(courseId, cmid, siteId, forceCache) { + return getLesson(siteId, courseId, 'coursemodule', cmid, forceCache); + }; + + /** + * Get a Lesson by Lesson ID. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#getLessonById + * @param {Number} courseId Course ID. + * @param {Number} id Lesson ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @return {Promise} Promise resolved when the Lesson is retrieved. + */ + self.getLessonById = function(courseId, id, siteId, forceCache) { + return getLesson(siteId, courseId, 'id', id, forceCache); + }; + + /** + * Invalidates Lesson data. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#invalidateAccessInformation + * @param {Number} lessonId Lesson ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + self.invalidateAccessInformation = function(lessonId, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKey(getAccessInformationCacheKey(lessonId)); + }); + }; + + /** + * Invalidates Lesson data. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#invalidateLessonData + * @param {Number} courseId Course ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + self.invalidateLessonData = function(courseId, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKey(getLessonDataCacheKey(courseId)); + }); + }; + + /** + * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the lesson WS are available. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#isPluginEnabled + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + */ + self.isPluginEnabled = function(siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + // All WS were introduced at the same time so checking one is enough. + return site.wsAvailable('mod_lesson_get_lesson_access_information'); + }); + }; + + /** + * Report a lesson as being viewed. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#logViewLesson + * @param {String} id Module ID. + * @param {String} [password] Lesson password (if any). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. + */ + self.logViewLesson = function(id, password, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: id + }; + + if (typeof password != 'undefined') { + params.password = password; + } + + return site.write('mod_lesson_view_lesson', params).then(function(result) { + if (!result.status) { + return $q.reject(); + } + }); + }); + }; + + return self; +}); diff --git a/www/addons/mod/lesson/templates/index.html b/www/addons/mod/lesson/templates/index.html new file mode 100644 index 00000000000..974bb3cee37 --- /dev/null +++ b/www/addons/mod/lesson/templates/index.html @@ -0,0 +1,20 @@ + + {{ title }} + + + + + + + + + + + + +
+ {{ message.message }} +
+
+
+
\ No newline at end of file diff --git a/www/addons/mod/quiz/main.js b/www/addons/mod/quiz/main.js index 7dbe787749c..ffcf9a0193d 100644 --- a/www/addons/mod/quiz/main.js +++ b/www/addons/mod/quiz/main.js @@ -16,7 +16,6 @@ angular.module('mm.addons.mod_quiz', ['mm.core']) .constant('mmaModQuizComponent', 'mmaModQuiz') .constant('mmaModQuizCheckChangesInterval', 5000) -.constant('mmaModQuizComponent', 'mmaModQuiz') .constant('mmaModQuizEventAttemptFinished', 'mma_mod_quiz_attempt_finished') .constant('mmaModQuizEventAutomSynced', 'mma_mod_quiz_autom_synced') .constant('mmaModQuizSyncTime', 300000) // In milliseconds. From bad7d1ebb90896bb9b3daf15d436ee6ee4b1a321 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 6 Mar 2017 09:55:22 +0100 Subject: [PATCH 009/218] MOBILE-2010 lesson: Render lesson first page --- www/addons/mod/lesson/controllers/index.js | 30 ++++- www/addons/mod/lesson/controllers/player.js | 96 ++++++++++++++ www/addons/mod/lesson/lang/en.json | 4 + www/addons/mod/lesson/main.js | 15 +++ www/addons/mod/lesson/services/lesson.js | 138 ++++++++++++++++++++ www/addons/mod/lesson/templates/index.html | 17 +++ www/addons/mod/lesson/templates/player.html | 9 ++ 7 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 www/addons/mod/lesson/controllers/player.js create mode 100644 www/addons/mod/lesson/lang/en.json create mode 100644 www/addons/mod/lesson/templates/player.html diff --git a/www/addons/mod/lesson/controllers/index.js b/www/addons/mod/lesson/controllers/index.js index 87b16d777d3..67c58669df6 100644 --- a/www/addons/mod/lesson/controllers/index.js +++ b/www/addons/mod/lesson/controllers/index.js @@ -22,11 +22,12 @@ angular.module('mm.addons.mod_lesson') * @name mmaModLessonIndexCtrl */ .controller('mmaModLessonIndexCtrl', function($scope, $stateParams, $mmaModLesson, $mmCourse, $q, $translate, $ionicScrollDelegate, - $mmEvents, $mmText, $mmUtil, $mmCourseHelper, mmaModLessonComponent, $mmApp, mmCoreEventOnlineStatusChanged) { + $mmEvents, $mmText, $mmUtil, $mmCourseHelper, mmaModLessonComponent, $mmApp, $state, mmCoreEventOnlineStatusChanged) { var module = $stateParams.module || {}, courseId = $stateParams.courseid, lesson, + accessInfo, scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonIndexScroll'), onlineObserver; @@ -44,13 +45,21 @@ angular.module('mm.addons.mod_lesson') lesson = lessonData; $scope.lesson = lesson; - $scope.now = Date.now(); $scope.title = lesson.name || $scope.title; $scope.description = lesson.intro; // Show description only if intro is present. return $mmaModLesson.getAccessInformation(lesson.id); }).then(function(info) { + accessInfo = info; $scope.preventMessages = info.preventaccessreasons; + if ($scope.preventMessages && $scope.preventMessages.length) { + // Lesson cannot be attempted, stop. + return; + } + + // Check to see if end of lesson was reached and if the user left. + $scope.leftDuringTimed = info.lastpageseen && info.lastpageseen != $mmaModLesson.LESSON_EOL && + info.leftduringtimedsession; }).then(function() { // All data obtained, now fill the context menu. $mmCourseHelper.fillContextMenu($scope, module, courseId, refresh, mmaModLessonComponent); @@ -59,7 +68,9 @@ angular.module('mm.addons.mod_lesson') // Get lesson failed, retry without using cache since it might be a new activity. return refreshData(); } - return $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); + + $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); + return $q.reject(); }); } @@ -68,6 +79,9 @@ angular.module('mm.addons.mod_lesson') var promises = []; promises.push($mmaModLesson.invalidateLessonData(courseId)); + if (lesson) { + promises.push($mmaModLesson.invalidateAccessInformation(lesson.id)); + } return $q.all(promises).finally(function() { return fetchLessonData(true); @@ -84,6 +98,16 @@ angular.module('mm.addons.mod_lesson') $scope.refreshIcon = 'ion-refresh'; }); + // Start the lesson. + $scope.start = function(continueLast) { + var pageId = $scope.leftDuringTimed ? (continueLast ? accessInfo.lastpageseen : accessInfo.firstpageid) : false; + $state.go('site.mod_lesson-player', { + courseid: courseId, + lessonid: lesson.id, + pageid: pageId + }); + }; + // Pull to refresh. $scope.refreshLesson = function() { if ($scope.lessonLoaded) { diff --git a/www/addons/mod/lesson/controllers/player.js b/www/addons/mod/lesson/controllers/player.js new file mode 100644 index 00000000000..6393fb474ac --- /dev/null +++ b/www/addons/mod/lesson/controllers/player.js @@ -0,0 +1,96 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson') + +/** + * Lesson player controller. + * + * @module mm.addons.mod_lesson + * @ngdoc controller + * @name mmaModLessonPlayerCtrl + */ +.controller('mmaModLessonPlayerCtrl', function($scope, $stateParams, $mmaModLesson, $q, $ionicScrollDelegate, $mmUtil, + mmaModLessonComponent, $mmSyncBlock) { + + var lessonId = $stateParams.lessonid, + courseId = $stateParams.courseid, + pageId = $stateParams.pageid, + lesson, + accessInfo, + offline = false, + scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonPlayerScroll'); + + // Block the lesson so it cannot be synced. + $mmSyncBlock.blockOperation(mmaModLessonComponent, lessonId); + + // Block leaving the view, we want to save changes before leaving. + blockData = $mmUtil.blockLeaveView($scope, leavePlayer); + + $scope.component = mmaModLessonComponent; + + // Convenience function to get Lesson data. + function fetchLessonData() { + return $mmaModLesson.getLessonById(courseId, lessonId).then(function(lessonData) { + lesson = lessonData; + $scope.lesson = lesson; + $scope.title = lesson.name; // Temporary title. + + return $mmaModLesson.getAccessInformation(lesson.id, offline, true); + }).then(function(info) { + accessInfo = info; + if (info.preventaccessreasons && info.preventaccessreasons.length) { + // Lesson cannot be attempted, show message and go back. + $mmUtil.showErrorModal(info.preventaccessreasons[0]); + blockData && blockData.back(); + return; + } + + return launchAttempt(pageId); + }).catch(function(message) { + $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); + return $q.reject(); + }); + } + + // Start or continue an attempt. + function launchAttempt(pageId) { + return $mmaModLesson.launchAttempt(lesson.id, undefined, pageId).then(function() { + pageId = pageId || accessInfo.firstpageid; + + return loadPage(pageId); + }); + } + + // Load a certain page. + function loadPage(pageId) { + return $mmaModLesson.getPageData(lesson.id, pageId, undefined, false, offline, true).then(function(data) { + $scope.title = data.page.title; + $scope.pageContent = data.page.contents; + $scope.pageLoaded = true; + }); + } + + // Function called when the user wants to leave the player. Save the attempt before leaving. + function leavePlayer() { + // @todo + return $q.when(); + } + + // Fetch the Lesson data. + fetchLessonData().finally(function() { + $scope.pageLoaded = true; + }); + +}); diff --git a/www/addons/mod/lesson/lang/en.json b/www/addons/mod/lesson/lang/en.json new file mode 100644 index 00000000000..478d2e7c805 --- /dev/null +++ b/www/addons/mod/lesson/lang/en.json @@ -0,0 +1,4 @@ +{ + "startattempt": "Start attempt", + "youhaveseen": "You have seen more than one page of this lesson already.
Do you want to start at the last page you saw?" +} diff --git a/www/addons/mod/lesson/main.js b/www/addons/mod/lesson/main.js index 6ec6d7b92da..6c2e84d69e0 100644 --- a/www/addons/mod/lesson/main.js +++ b/www/addons/mod/lesson/main.js @@ -32,6 +32,21 @@ angular.module('mm.addons.mod_lesson', ['mm.core']) templateUrl: 'addons/mod/lesson/templates/index.html' } } + }) + + .state('site.mod_lesson-player', { + url: '/mod_lesson-player', + params: { + courseid: null, + lessonid: null, + pageid: null + }, + views: { + 'site': { + controller: 'mmaModLessonPlayerCtrl', + templateUrl: 'addons/mod/lesson/templates/player.html' + } + } }); }) diff --git a/www/addons/mod/lesson/services/lesson.js b/www/addons/mod/lesson/services/lesson.js index e012450dc4d..aa225c2042c 100644 --- a/www/addons/mod/lesson/services/lesson.js +++ b/www/addons/mod/lesson/services/lesson.js @@ -27,6 +27,8 @@ angular.module('mm.addons.mod_lesson') var self = {}; + self.LESSON_EOL = -9; + /** * Get cache key for access information WS calls. * @@ -148,6 +150,70 @@ angular.module('mm.addons.mod_lesson') return getLesson(siteId, courseId, 'id', id, forceCache); }; + /** + * Get cache key for get page data WS calls. + * + * @param {Number} lessonId Lesson ID. + * @param {Number} pageId Page ID. + * @return {String} Cache key. + */ + function getPageDataCacheKey(lessonId, pageId) { + return getPageDataCommonCacheKey(lessonId) + ':' + pageId; + } + + /** + * Get common cache key for get page data WS calls. + * + * @param {Number} lessonId Lesson ID. + * @return {String} Cache key. + */ + function getPageDataCommonCacheKey(lessonId) { + return 'mmaModLesson:pageData:' + lessonId; + } + + /** + * Get the access information of a certain lesson. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#getAccessInformation + * @param {Number} lessonId Lesson ID. + * @param {Number} pageId Page ID. + * @param {String} [password] Lesson password (if any). + * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). + * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. + * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the access information- + */ + self.getPageData = function(lessonId, pageId, password, review, forceCache, ignoreCache, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: lessonId, + pageid: pageId + }, + preSets = { + cacheKey: getPageDataCacheKey(lessonId, pageId) + }; + + if (typeof password != 'undefined') { + params.password = password; + } + if (review) { + params.review = true; + } + + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; + } + + return site.read('mod_lesson_get_page_data', params, preSets); + }); + }; + /** * Invalidates Lesson data. * @@ -180,6 +246,39 @@ angular.module('mm.addons.mod_lesson') }); }; + /** + * Invalidates page data for all pages. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#invalidatePageData + * @param {Number} lessonId Lesson ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + self.invalidatePageData = function(lessonId, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKeyStartingWith(getPageDataCommonCacheKey(lessonId)); + }); + }; + + /** + * Invalidates page data for a certain page. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#invalidatePageDataForPage + * @param {Number} lessonId Attempt ID. + * @param {Number} pageId Page ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + self.invalidatePageDataForPage = function(lessonId, pageId, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKey(getPageDataCacheKey(lessonId, pageId)); + }); + }; + /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the lesson WS are available. * @@ -196,6 +295,44 @@ angular.module('mm.addons.mod_lesson') }); }; + /** + * Start or continue an attempt. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#launchAttempt + * @param {String} id Lesson ID. + * @param {String} [password] Lesson password (if any). + * @param {Number} [pageId] Page id to continue from (only when continuing an attempt). + * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. + */ + self.launchAttempt = function(id, password, pageId, review, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: id + }; + + if (typeof password != 'undefined') { + params.password = password; + } + if (typeof pageId == 'number') { + params.pageid = pageId; + } + if (review) { + params.review = true; + } + + return site.write('mod_lesson_launch_attempt', params).then(function(result) { + if (!result.status) { + return $q.reject(); + } + return result; + }); + }); + }; + /** * Report a lesson as being viewed. * @@ -221,6 +358,7 @@ angular.module('mm.addons.mod_lesson') if (!result.status) { return $q.reject(); } + return result; }); }); }; diff --git a/www/addons/mod/lesson/templates/index.html b/www/addons/mod/lesson/templates/index.html index 974bb3cee37..fe484f144a7 100644 --- a/www/addons/mod/lesson/templates/index.html +++ b/www/addons/mod/lesson/templates/index.html @@ -15,6 +15,23 @@
{{ message.message }}
+ +
+
+ +

+
+ + +
+
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/www/addons/mod/lesson/templates/player.html b/www/addons/mod/lesson/templates/player.html new file mode 100644 index 00000000000..0c6b1def76f --- /dev/null +++ b/www/addons/mod/lesson/templates/player.html @@ -0,0 +1,9 @@ + + {{ title }} + + + + {{pageContent}} + + + \ No newline at end of file From 5e83b337352a2d271dc67cb961fee576f980cfd0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 7 Mar 2017 12:05:35 +0100 Subject: [PATCH 010/218] MOBILE-2010 lesson: Show buttons and allow navigating --- www/addons/mod/lesson/controllers/index.js | 38 +++++++++- www/addons/mod/lesson/controllers/player.js | 54 ++++++++++++-- www/addons/mod/lesson/lang/en.json | 2 + www/addons/mod/lesson/services/helper.js | 81 +++++++++++++++++++++ www/addons/mod/lesson/services/lesson.js | 65 ++++++++++++----- www/addons/mod/lesson/templates/player.html | 16 +++- 6 files changed, 225 insertions(+), 31 deletions(-) create mode 100644 www/addons/mod/lesson/services/helper.js diff --git a/www/addons/mod/lesson/controllers/index.js b/www/addons/mod/lesson/controllers/index.js index 67c58669df6..fffaa4fb056 100644 --- a/www/addons/mod/lesson/controllers/index.js +++ b/www/addons/mod/lesson/controllers/index.js @@ -22,13 +22,14 @@ angular.module('mm.addons.mod_lesson') * @name mmaModLessonIndexCtrl */ .controller('mmaModLessonIndexCtrl', function($scope, $stateParams, $mmaModLesson, $mmCourse, $q, $translate, $ionicScrollDelegate, - $mmEvents, $mmText, $mmUtil, $mmCourseHelper, mmaModLessonComponent, $mmApp, $state, mmCoreEventOnlineStatusChanged) { + $mmEvents, $mmText, $mmUtil, $mmCourseHelper, mmaModLessonComponent, $mmApp, $state, mmCoreEventOnlineStatusChanged, + $ionicHistory) { var module = $stateParams.module || {}, courseId = $stateParams.courseid, lesson, accessInfo, - scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonIndexScroll'), + scrollView, onlineObserver; $scope.title = module.name; @@ -88,6 +89,21 @@ angular.module('mm.addons.mod_lesson') }); } + function showSpinnerAndRefresh() { + if (!scrollView) { + scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonIndexScroll'); + } + + $scope.lessonLoaded = false; + $scope.refreshIcon = 'spinner'; + scrollView.scrollTop(); + + refreshData().finally(function() { + $scope.lessonLoaded = true; + $scope.refreshIcon = 'ion-refresh'; + }); + } + // Fetch the Lesson data. fetchLessonData().then(function() { $mmaModLesson.logViewLesson(lesson.id).then(function() { @@ -124,7 +140,23 @@ angular.module('mm.addons.mod_lesson') $mmText.expandText($translate.instant('mm.core.description'), $scope.description, false, mmaModLessonComponent, module.id); }; - // Refresh online status when changes. + // Update data when we come back from the player since the status could have changed. + // We want to skip the first $ionicView.enter event because it's when the view is created. + var skip = true; + $scope.$on('$ionicView.enter', function() { + if (skip) { + skip = false; + return; + } + + var forwardView = $ionicHistory.forwardView(); + if (forwardView && forwardView.stateName === 'site.mod_lesson-player') { + // Refresh data. + showSpinnerAndRefresh(); + } + }); + + // Update online status when changes. onlineObserver = $mmEvents.on(mmCoreEventOnlineStatusChanged, function(online) { $scope.isOnline = online; }); diff --git a/www/addons/mod/lesson/controllers/player.js b/www/addons/mod/lesson/controllers/player.js index 6393fb474ac..372893c522a 100644 --- a/www/addons/mod/lesson/controllers/player.js +++ b/www/addons/mod/lesson/controllers/player.js @@ -22,15 +22,15 @@ angular.module('mm.addons.mod_lesson') * @name mmaModLessonPlayerCtrl */ .controller('mmaModLessonPlayerCtrl', function($scope, $stateParams, $mmaModLesson, $q, $ionicScrollDelegate, $mmUtil, - mmaModLessonComponent, $mmSyncBlock) { + mmaModLessonComponent, $mmSyncBlock, $mmaModLessonHelper) { var lessonId = $stateParams.lessonid, courseId = $stateParams.courseid, - pageId = $stateParams.pageid, + currentPage = $stateParams.pageid, lesson, accessInfo, offline = false, - scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonPlayerScroll'); + scrollView; // Block the lesson so it cannot be synced. $mmSyncBlock.blockOperation(mmaModLessonComponent, lessonId); @@ -57,7 +57,7 @@ angular.module('mm.addons.mod_lesson') return; } - return launchAttempt(pageId); + return launchAttempt(currentPage); }).catch(function(message) { $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); return $q.reject(); @@ -67,21 +67,32 @@ angular.module('mm.addons.mod_lesson') // Start or continue an attempt. function launchAttempt(pageId) { return $mmaModLesson.launchAttempt(lesson.id, undefined, pageId).then(function() { - pageId = pageId || accessInfo.firstpageid; + currentPage = pageId || accessInfo.firstpageid; - return loadPage(pageId); + return loadPage(currentPage); }); } // Load a certain page. function loadPage(pageId) { - return $mmaModLesson.getPageData(lesson.id, pageId, undefined, false, offline, true).then(function(data) { + return $mmaModLesson.getPageData(lesson.id, pageId, undefined, false, true, offline, true).then(function(data) { $scope.title = data.page.title; $scope.pageContent = data.page.contents; $scope.pageLoaded = true; + $scope.pageButtons = $mmaModLessonHelper.getPageButtonsFromHtml(data.pagecontent); + currentPage = pageId; }); } + // Scroll top and show the spinner. + function showLoading() { + if (!scrollView) { + scrollView = $ionicScrollDelegate.$getByHandle('mmaModLessonPlayerScroll'); + } + scrollView.scrollTop(); + $scope.pageLoaded = false; + } + // Function called when the user wants to leave the player. Save the attempt before leaving. function leavePlayer() { // @todo @@ -93,4 +104,33 @@ angular.module('mm.addons.mod_lesson') $scope.pageLoaded = true; }); + // A button was clicked. + $scope.buttonClicked = function(button) { + showLoading(); + + return $mmaModLesson.processPage(lessonId, currentPage, button.data).then(function(result) { + if (result.newpageid === 0) { + // Not a valid page, return to entry view. + // This happens, for example, when the user clicks to go to previous page and there is no previous page. + blockData && blockData.back(); + return; + } else if (result.newpageid == $mmaModLesson.LESSON_EOL) { + // End of lesson reached. + // @todo Show grade, progress bar, min questions, etc. in final page. + $scope.endOfLesson = true; + $scope.title = lesson.name; + return; + } + + $scope.endOfLesson = false; + // Load new page. + return loadPage(result.newpageid); + }).catch(function(error) { + $mmUtil.showErrorModalDefault(error, 'Error processing page'); + return $q.reject(); + }).finally(function() { + $scope.pageLoaded = true; + }); + }; + }); diff --git a/www/addons/mod/lesson/lang/en.json b/www/addons/mod/lesson/lang/en.json index 478d2e7c805..67353aa7675 100644 --- a/www/addons/mod/lesson/lang/en.json +++ b/www/addons/mod/lesson/lang/en.json @@ -1,4 +1,6 @@ { + "congratulations": "Congratulations - end of lesson reached", "startattempt": "Start attempt", + "welldone": "Well done!", "youhaveseen": "You have seen more than one page of this lesson already.
Do you want to start at the last page you saw?" } diff --git a/www/addons/mod/lesson/services/helper.js b/www/addons/mod/lesson/services/helper.js new file mode 100644 index 00000000000..743e026ac6b --- /dev/null +++ b/www/addons/mod/lesson/services/helper.js @@ -0,0 +1,81 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +angular.module('mm.addons.mod_lesson') + +/** + * Helper to gather some common lesson functions. + * + * @module mm.addons.mod_lesson + * @ngdoc service + * @name $mmaModLessonHelper + */ +.factory('$mmaModLessonHelper', function() { + + var self = {}; + + /** + * Get the buttons to change pages. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLessonHelper#getPageButtonsFromHtml + * @param {String} html Page's HTML. + * @return {Object[]} List of buttons. + */ + self.getPageButtonsFromHtml = function(html) { + var buttons = [], + rootElement = document.createElement('div'), + buttonsContainer, + forms; + + // Get the container of the buttons if it exists. + rootElement.innerHTML = html; + buttonsContainer = rootElement.querySelector('.branchbuttoncontainer'); + + if (!buttonsContainer) { + // Button container not found, no buttons. + return buttons; + } + + forms = buttonsContainer.querySelectorAll('form'); + angular.forEach(forms, function(form) { + var buttonEl = form.querySelector('button[type="submit"]'), + inputs = form.querySelectorAll('input'), + button; + + if (!buttonEl || !inputs || !inputs.length) { + // Button not found or no inputs, ignore it. + return; + } + + button = { + id: buttonEl.id, + title: buttonEl.title, + content: buttonEl.innerHTML, + data: {} + }; + + angular.forEach(inputs, function(input) { + button.data[input.name] = input.value; + }); + + buttons.push(button); + }); + + return buttons; + }; + + return self; +}); \ No newline at end of file diff --git a/www/addons/mod/lesson/services/lesson.js b/www/addons/mod/lesson/services/lesson.js index aa225c2042c..64d718712e9 100644 --- a/www/addons/mod/lesson/services/lesson.js +++ b/www/addons/mod/lesson/services/lesson.js @@ -21,7 +21,7 @@ angular.module('mm.addons.mod_lesson') * @ngdoc service * @name $mmaModLesson */ -.factory('$mmaModLesson', function($log, $mmSitesManager, $q) { +.factory('$mmaModLesson', function($log, $mmSitesManager, $q, $mmUtil) { $log = $log.getInstance('$mmaModLesson'); @@ -177,20 +177,23 @@ angular.module('mm.addons.mod_lesson') * @module mm.addons.mod_lesson * @ngdoc method * @name $mmaModLesson#getAccessInformation - * @param {Number} lessonId Lesson ID. - * @param {Number} pageId Page ID. - * @param {String} [password] Lesson password (if any). - * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. - * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the access information- + * @param {Number} lessonId Lesson ID. + * @param {Number} pageId Page ID. + * @param {String} [password] Lesson password (if any). + * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). + * @param {Boolean} [includeContents] Include the page rendered contents. + * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. + * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the access information- */ - self.getPageData = function(lessonId, pageId, password, review, forceCache, ignoreCache, siteId) { + self.getPageData = function(lessonId, pageId, password, review, includeContents, forceCache, ignoreCache, siteId) { return $mmSitesManager.getSite(siteId).then(function(site) { var params = { lessonid: lessonId, - pageid: pageId + pageid: pageId, + review: review ? 1 : 0, + returncontents: includeContents ? 1 : 0 }, preSets = { cacheKey: getPageDataCacheKey(lessonId, pageId) @@ -199,9 +202,6 @@ angular.module('mm.addons.mod_lesson') if (typeof password != 'undefined') { params.password = password; } - if (review) { - params.review = true; - } if (forceCache) { preSets.omitExpires = true; @@ -311,7 +311,8 @@ angular.module('mm.addons.mod_lesson') self.launchAttempt = function(id, password, pageId, review, siteId) { return $mmSitesManager.getSite(siteId).then(function(site) { var params = { - lessonid: id + lessonid: id, + review: review ? 1 : 0 }; if (typeof password != 'undefined') { @@ -320,9 +321,6 @@ angular.module('mm.addons.mod_lesson') if (typeof pageId == 'number') { params.pageid = pageId; } - if (review) { - params.review = true; - } return site.write('mod_lesson_launch_attempt', params).then(function(result) { if (!result.status) { @@ -363,5 +361,36 @@ angular.module('mm.addons.mod_lesson') }); }; + /** + * Process a lesson page, saving its data. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#processAttempt + * @param {Number} lessonId Lesson ID. + * @param {Number} pageId Page ID. + * @param {Object} data Data to save. + * @param {String} [password] Lesson password (if any). + * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved in success, rejected otherwise. + */ + self.processPage = function(lessonId, pageId, data, password, review, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: lessonId, + pageid: pageId, + data: $mmUtil.objectToArrayOfObjects(data, 'name', 'value'), + review: review ? 1 : 0 + }; + + if (typeof password != 'undefined') { + params.password = password; + } + + return site.write('mod_lesson_process_page', params); + }); + }; + return self; }); diff --git a/www/addons/mod/lesson/templates/player.html b/www/addons/mod/lesson/templates/player.html index 0c6b1def76f..abcc4a02627 100644 --- a/www/addons/mod/lesson/templates/player.html +++ b/www/addons/mod/lesson/templates/player.html @@ -1,9 +1,19 @@ {{ title }} - + - - {{pageContent}} +
+ + {{pageContent}} +
+ +
+
+ +
+

{{ 'mma.mod_lesson.congratulations' | translate }}

+

{{ 'mma.mod_lesson.welldone' | translate }}

+
\ No newline at end of file From 403176e3858538769fcf95e821f512db3bea8166 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 9 Mar 2017 10:54:23 +0100 Subject: [PATCH 011/218] MOBILE-2010 lesson: Call finish attempt and print EOL page --- www/addons/mod/lesson/controllers/player.js | 12 ++++-- www/addons/mod/lesson/lang/en.json | 1 - www/addons/mod/lesson/services/lesson.js | 45 +++++++++++++++++++++ www/addons/mod/lesson/templates/player.html | 22 ++++++++-- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/www/addons/mod/lesson/controllers/player.js b/www/addons/mod/lesson/controllers/player.js index 372893c522a..2250ae506e5 100644 --- a/www/addons/mod/lesson/controllers/player.js +++ b/www/addons/mod/lesson/controllers/player.js @@ -84,6 +84,13 @@ angular.module('mm.addons.mod_lesson') }); } + // Finish the attempt. + function finishAttempt() { + return $mmaModLesson.finishAttempt(lesson.id).then(function(data) { + $scope.eolData = data.data; + }); + } + // Scroll top and show the spinner. function showLoading() { if (!scrollView) { @@ -117,13 +124,12 @@ angular.module('mm.addons.mod_lesson') } else if (result.newpageid == $mmaModLesson.LESSON_EOL) { // End of lesson reached. // @todo Show grade, progress bar, min questions, etc. in final page. - $scope.endOfLesson = true; $scope.title = lesson.name; - return; + return finishAttempt(); } - $scope.endOfLesson = false; // Load new page. + $scope.eolData = false; return loadPage(result.newpageid); }).catch(function(error) { $mmUtil.showErrorModalDefault(error, 'Error processing page'); diff --git a/www/addons/mod/lesson/lang/en.json b/www/addons/mod/lesson/lang/en.json index 67353aa7675..10893131120 100644 --- a/www/addons/mod/lesson/lang/en.json +++ b/www/addons/mod/lesson/lang/en.json @@ -1,6 +1,5 @@ { "congratulations": "Congratulations - end of lesson reached", "startattempt": "Start attempt", - "welldone": "Well done!", "youhaveseen": "You have seen more than one page of this lesson already.
Do you want to start at the last page you saw?" } diff --git a/www/addons/mod/lesson/services/lesson.js b/www/addons/mod/lesson/services/lesson.js index 64d718712e9..b491ce5403b 100644 --- a/www/addons/mod/lesson/services/lesson.js +++ b/www/addons/mod/lesson/services/lesson.js @@ -29,6 +29,51 @@ angular.module('mm.addons.mod_lesson') self.LESSON_EOL = -9; + /** + * Finishes an attempt. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#finishAttempt + * @param {Number} lessonId Lesson ID. + * @param {String} [password] Lesson password (if any). + * @param {Boolean} [outOfTime] If the user ran out of time. + * @param {Boolean} [review] If the user wants to review just after finishing (1 hour margin). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved in success, rejected otherwise. + */ + self.finishAttempt = function(lessonId, password, outOfTime, review, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: lessonId, + outoftime: outOfTime ? 1 : 0, + review: review ? 1 : 0 + }; + + if (typeof password != 'undefined') { + params.password = password; + } + + return site.write('mod_lesson_finish_attempt', params).then(function(response) { + // Convert the data array into an object and decode the values. + var map = {}; + angular.forEach(response.data, function(entry) { + if (entry.value && typeof entry.value == 'string' && entry.value !== '1') { + // It's a JSON encoded object. Try to decode it. + try { + entry.value = JSON.parse(entry.value); + } catch(ex) { + // Error decoding it, leave the value as it is. + } + } + map[entry.name] = entry; + }); + response.data = map; + return response; + }); + }); + }; + /** * Get cache key for access information WS calls. * diff --git a/www/addons/mod/lesson/templates/player.html b/www/addons/mod/lesson/templates/player.html index abcc4a02627..8742d26b2b1 100644 --- a/www/addons/mod/lesson/templates/player.html +++ b/www/addons/mod/lesson/templates/player.html @@ -2,7 +2,7 @@ {{ title }} -
+
{{pageContent}}
@@ -10,9 +10,23 @@
-
-

{{ 'mma.mod_lesson.congratulations' | translate }}

-

{{ 'mma.mod_lesson.welldone' | translate }}

+
+

{{ 'mma.mod_lesson.congratulations' | translate }}

+

{{ eolData.notenoughtimespent.message }}

+

{{ eolData.numberofpagesviewed.message }}

+

{{ eolData.youshouldview.message }}

+

{{ eolData.numberofcorrectanswers.message }}

+

{{ eolData.displayscorewithessays.message }}

+

{{ eolData.displayscorewithoutessays.message }}

+

{{ eolData.yourcurrentgradeisoutof.message }}

+

{{ eolData.eolstudentoutoftimenoanswers.message }}

+

{{ eolData.welldone.message }}

+ +

{{ eolData.displayofgrade.message }}

+

{{ eolData.reviewlesson.message }}

+

{{ eolData.modattemptsnoteacher.message }}

+ +
From 582ecb49761cff26aacf0f7baf29ad9814f9b27e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 9 Mar 2017 17:04:01 +0100 Subject: [PATCH 012/218] MOBILE-2011 lesson: Display lesson menu --- www/addons/mod/lesson/controllers/player.js | 63 +++++++++++++++++--- www/addons/mod/lesson/lang/en.json | 1 + www/addons/mod/lesson/scss/styles.scss | 4 ++ www/addons/mod/lesson/services/lesson.js | 65 ++++++++++++++++++--- www/addons/mod/lesson/templates/menu.html | 17 ++++++ www/addons/mod/lesson/templates/player.html | 3 + 6 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 www/addons/mod/lesson/scss/styles.scss create mode 100644 www/addons/mod/lesson/templates/menu.html diff --git a/www/addons/mod/lesson/controllers/player.js b/www/addons/mod/lesson/controllers/player.js index 2250ae506e5..563ab3522b7 100644 --- a/www/addons/mod/lesson/controllers/player.js +++ b/www/addons/mod/lesson/controllers/player.js @@ -22,11 +22,10 @@ angular.module('mm.addons.mod_lesson') * @name mmaModLessonPlayerCtrl */ .controller('mmaModLessonPlayerCtrl', function($scope, $stateParams, $mmaModLesson, $q, $ionicScrollDelegate, $mmUtil, - mmaModLessonComponent, $mmSyncBlock, $mmaModLessonHelper) { + mmaModLessonComponent, $mmSyncBlock, $mmaModLessonHelper, $mmSideMenu) { var lessonId = $stateParams.lessonid, courseId = $stateParams.courseid, - currentPage = $stateParams.pageid, lesson, accessInfo, offline = false, @@ -39,6 +38,7 @@ angular.module('mm.addons.mod_lesson') blockData = $mmUtil.blockLeaveView($scope, leavePlayer); $scope.component = mmaModLessonComponent; + $scope.currentPage = $stateParams.pageid; // Convenience function to get Lesson data. function fetchLessonData() { @@ -57,7 +57,7 @@ angular.module('mm.addons.mod_lesson') return; } - return launchAttempt(currentPage); + return launchAttempt($scope.currentPage); }).catch(function(message) { $mmUtil.showErrorModalDefault(message, 'mm.course.errorgetmodule', true); return $q.reject(); @@ -67,9 +67,9 @@ angular.module('mm.addons.mod_lesson') // Start or continue an attempt. function launchAttempt(pageId) { return $mmaModLesson.launchAttempt(lesson.id, undefined, pageId).then(function() { - currentPage = pageId || accessInfo.firstpageid; + $scope.currentPage = pageId || accessInfo.firstpageid; - return loadPage(currentPage); + return loadPage($scope.currentPage); }); } @@ -80,7 +80,13 @@ angular.module('mm.addons.mod_lesson') $scope.pageContent = data.page.contents; $scope.pageLoaded = true; $scope.pageButtons = $mmaModLessonHelper.getPageButtonsFromHtml(data.pagecontent); - currentPage = pageId; + $scope.currentPage = pageId; + + if (data.displaymenu && !$scope.displayMenu) { + // Load the menu. + loadMenu(); + } + $scope.displayMenu = !!data.displaymenu; }); } @@ -106,6 +112,26 @@ angular.module('mm.addons.mod_lesson') return $q.when(); } + // Load the lesson menu. + function loadMenu() { + if ($scope.loadingMenu) { + // Already loading. + return; + } + + $scope.loadingMenu = true; + return $mmaModLesson.getPages(lesson.id).then(function(pages) { + $scope.lessonPages = pages.map(function(entry) { + return entry.page; + }); + }).catch(function(error) { + $mmUtil.showErrorModalDefault(error, 'Error loading menu.'); + return $q.reject(); + }).finally(function() { + $scope.loadingMenu = false; + }); + } + // Fetch the Lesson data. fetchLessonData().finally(function() { $scope.pageLoaded = true; @@ -115,7 +141,7 @@ angular.module('mm.addons.mod_lesson') $scope.buttonClicked = function(button) { showLoading(); - return $mmaModLesson.processPage(lessonId, currentPage, button.data).then(function(result) { + return $mmaModLesson.processPage(lessonId, $scope.currentPage, button.data).then(function(result) { if (result.newpageid === 0) { // Not a valid page, return to entry view. // This happens, for example, when the user clicks to go to previous page and there is no previous page. @@ -139,4 +165,27 @@ angular.module('mm.addons.mod_lesson') }); }; + // Menu page was clicked, load the page. + $scope.loadPage = function(pageId) { + if (!$scope.eolData && $scope.currentPage == pageId) { + // Page already loaded, stop. + return; + } + + showLoading(); + + return loadPage(pageId).then(function() { + // Page loaded, hide the EOL page if shown. + $scope.eolData = false; + }).catch(function(error) { + $mmUtil.showErrorModalDefault(error, 'Error loading page'); + return $q.reject(); + }).finally(function() { + $scope.pageLoaded = true; + }); + }; + + // Setup right side menu. + $mmSideMenu.showRightSideMenu('addons/mod/lesson/templates/menu.html', $scope); + }); diff --git a/www/addons/mod/lesson/lang/en.json b/www/addons/mod/lesson/lang/en.json index 10893131120..332ad628549 100644 --- a/www/addons/mod/lesson/lang/en.json +++ b/www/addons/mod/lesson/lang/en.json @@ -1,5 +1,6 @@ { "congratulations": "Congratulations - end of lesson reached", + "lessonmenu": "Lesson menu", "startattempt": "Start attempt", "youhaveseen": "You have seen more than one page of this lesson already.
Do you want to start at the last page you saw?" } diff --git a/www/addons/mod/lesson/scss/styles.scss b/www/addons/mod/lesson/scss/styles.scss new file mode 100644 index 00000000000..ce2ffa30c90 --- /dev/null +++ b/www/addons/mod/lesson/scss/styles.scss @@ -0,0 +1,4 @@ + +.item.mma-mod-lesson-selected { + font-weight: bold; +} diff --git a/www/addons/mod/lesson/services/lesson.js b/www/addons/mod/lesson/services/lesson.js index b491ce5403b..688bcec0c9f 100644 --- a/www/addons/mod/lesson/services/lesson.js +++ b/www/addons/mod/lesson/services/lesson.js @@ -50,7 +50,7 @@ angular.module('mm.addons.mod_lesson') review: review ? 1 : 0 }; - if (typeof password != 'undefined') { + if (typeof password == 'string') { params.password = password; } @@ -217,11 +217,11 @@ angular.module('mm.addons.mod_lesson') } /** - * Get the access information of a certain lesson. + * Get page data. * * @module mm.addons.mod_lesson * @ngdoc method - * @name $mmaModLesson#getAccessInformation + * @name $mmaModLesson#getPageData * @param {Number} lessonId Lesson ID. * @param {Number} pageId Page ID. * @param {String} [password] Lesson password (if any). @@ -230,7 +230,7 @@ angular.module('mm.addons.mod_lesson') * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the access information- + * @return {Promise} Promise resolved with the page data. */ self.getPageData = function(lessonId, pageId, password, review, includeContents, forceCache, ignoreCache, siteId) { return $mmSitesManager.getSite(siteId).then(function(site) { @@ -244,7 +244,7 @@ angular.module('mm.addons.mod_lesson') cacheKey: getPageDataCacheKey(lessonId, pageId) }; - if (typeof password != 'undefined') { + if (typeof password == 'string') { params.password = password; } @@ -259,6 +259,55 @@ angular.module('mm.addons.mod_lesson') }); }; + /** + * Get cache key for get pages WS calls. + * + * @param {Number} lessonId Lesson ID. + * @return {String} Cache key. + */ + function getPagesCacheKey(lessonId) { + return 'mmaModLesson:pages:' + lessonId; + } + + /** + * Get lesson pages. + * + * @module mm.addons.mod_lesson + * @ngdoc method + * @name $mmaModLesson#getPages + * @param {Number} lessonId Lesson ID. + * @param {String} [password] Lesson password (if any). + * @param {Boolean} forceCache True if it should return cached data. Has priority over ignoreCache. + * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the pages. + */ + self.getPages = function(lessonId, password, forceCache, ignoreCache, siteId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + lessonid: lessonId, + }, + preSets = { + cacheKey: getPagesCacheKey(lessonId) + }; + + if (typeof password == 'string') { + params.password = password; + } + + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; + } + + return site.read('mod_lesson_get_pages', params, preSets).then(function(response) { + return response.pages; + }); + }); + }; + /** * Invalidates Lesson data. * @@ -360,7 +409,7 @@ angular.module('mm.addons.mod_lesson') review: review ? 1 : 0 }; - if (typeof password != 'undefined') { + if (typeof password == 'string') { params.password = password; } if (typeof pageId == 'number') { @@ -393,7 +442,7 @@ angular.module('mm.addons.mod_lesson') lessonid: id }; - if (typeof password != 'undefined') { + if (typeof password == 'string') { params.password = password; } @@ -429,7 +478,7 @@ angular.module('mm.addons.mod_lesson') review: review ? 1 : 0 }; - if (typeof password != 'undefined') { + if (typeof password == 'string') { params.password = password; } diff --git a/www/addons/mod/lesson/templates/menu.html b/www/addons/mod/lesson/templates/menu.html new file mode 100644 index 00000000000..4cf15ab2030 --- /dev/null +++ b/www/addons/mod/lesson/templates/menu.html @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/www/addons/mod/lesson/templates/player.html b/www/addons/mod/lesson/templates/player.html index 8742d26b2b1..d4d89c8e110 100644 --- a/www/addons/mod/lesson/templates/player.html +++ b/www/addons/mod/lesson/templates/player.html @@ -1,5 +1,8 @@ {{ title }} + + +
From 8ccd60188f46285466fbd14fbb4ec6585f1bf222 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 10 Mar 2017 10:14:45 +0100 Subject: [PATCH 013/218] MOBILE-2011 lesson: Display media file --- www/addons/mod/lesson/controllers/player.js | 1 + www/addons/mod/lesson/lang/en.json | 1 + www/addons/mod/lesson/templates/menu.html | 6 ++++++ www/addons/mod/lesson/templates/player.html | 2 +- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/www/addons/mod/lesson/controllers/player.js b/www/addons/mod/lesson/controllers/player.js index 563ab3522b7..6fc646caff8 100644 --- a/www/addons/mod/lesson/controllers/player.js +++ b/www/addons/mod/lesson/controllers/player.js @@ -46,6 +46,7 @@ angular.module('mm.addons.mod_lesson') lesson = lessonData; $scope.lesson = lesson; $scope.title = lesson.name; // Temporary title. + $scope.mediaFile = lesson.mediafiles && lesson.mediafiles[0]; return $mmaModLesson.getAccessInformation(lesson.id, offline, true); }).then(function(info) { diff --git a/www/addons/mod/lesson/lang/en.json b/www/addons/mod/lesson/lang/en.json index 332ad628549..aaa7481553a 100644 --- a/www/addons/mod/lesson/lang/en.json +++ b/www/addons/mod/lesson/lang/en.json @@ -1,6 +1,7 @@ { "congratulations": "Congratulations - end of lesson reached", "lessonmenu": "Lesson menu", + "linkedmedia": "Linked media", "startattempt": "Start attempt", "youhaveseen": "You have seen more than one page of this lesson already.
Do you want to start at the last page you saw?" } diff --git a/www/addons/mod/lesson/templates/menu.html b/www/addons/mod/lesson/templates/menu.html index 4cf15ab2030..2f7ea8a4ac9 100644 --- a/www/addons/mod/lesson/templates/menu.html +++ b/www/addons/mod/lesson/templates/menu.html @@ -1,6 +1,12 @@