From 6b5c9416db0cc76899bbc11633dc64f53f3611ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Wed, 17 Oct 2018 11:26:33 +0200
Subject: [PATCH 1/4] MOBILE-2681 dashboard: Save user preferences on timeline
 and overview

---
 .../block/myoverview/component/myoverview.ts  |  12 +-
 .../timeline/components/timeline/timeline.ts  |  17 ++-
 src/classes/site.ts                           | 104 ++++++++++++++----
 3 files changed, 105 insertions(+), 28 deletions(-)

diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts
index 21176abce..c6876b019 100644
--- a/src/addon/block/myoverview/component/myoverview.ts
+++ b/src/addon/block/myoverview/component/myoverview.ts
@@ -17,6 +17,7 @@ import { Searchbar } from 'ionic-angular';
 import * as moment from 'moment';
 import { CoreEventsProvider } from '@providers/events';
 import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreSitesProvider } from '@providers/sites';
 import { CoreCoursesProvider } from '@core/courses/providers/courses';
 import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
 import { CoreCourseHelperProvider } from '@core/course/providers/helper';
@@ -42,6 +43,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
         future: []
     };
     selectedFilter = 'inprogress';
+    currentSite: any;
     downloadAllCoursesEnabled: boolean;
     filteredCourses: any[];
     prefetchCoursesData = {
@@ -62,7 +64,8 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
     constructor(injector: Injector, private coursesProvider: CoreCoursesProvider,
             private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider,
             private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider,
-            private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider) {
+            private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider,
+            private sitesProvider: CoreSitesProvider) {
 
         super(injector, 'AddonBlockMyOverviewComponent');
     }
@@ -85,7 +88,11 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
             }
         });
 
-        super.ngOnInit();
+        this.currentSite = this.sitesProvider.getCurrentSite();
+        this.currentSite.getSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
+            this.selectedFilter = value;
+            super.ngOnInit();
+        });
     }
 
     /**
@@ -246,6 +253,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
      * The selected courses have changed.
      */
     selectedChanged(): void {
+        this.currentSite.setSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter);
         this.filteredCourses = this.courses[this.selectedFilter];
     }
 
diff --git a/src/addon/block/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts
index 180aec2ef..a09eba441 100644
--- a/src/addon/block/timeline/components/timeline/timeline.ts
+++ b/src/addon/block/timeline/components/timeline/timeline.ts
@@ -15,6 +15,7 @@
 import { Component, OnInit, Injector } from '@angular/core';
 import * as moment from 'moment';
 import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreSitesProvider } from '@providers/sites';
 import { CoreCoursesProvider } from '@core/courses/providers/courses';
 import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
 import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
@@ -31,6 +32,7 @@ import { AddonBlockTimelineProvider } from '../../providers/timeline';
 export class AddonBlockTimelineComponent extends AddonBlockComponent implements OnInit {
     sort = 'sortbydates';
     filter = 'next30days';
+    currentSite: any;
     timeline = {
         events: [],
         loaded: false,
@@ -49,7 +51,7 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
 
     constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider,
             private timelineProvider: AddonBlockTimelineProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate,
-            private coursesHelper: CoreCoursesHelperProvider) {
+            private coursesHelper: CoreCoursesHelperProvider, private sitesProvider: CoreSitesProvider) {
 
         super(injector, 'AddonBlockTimelineComponent');
     }
@@ -58,8 +60,15 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      * Component being initialized.
      */
     ngOnInit(): void {
-        this.switchFilter();
-        super.ngOnInit();
+        this.currentSite = this.sitesProvider.getCurrentSite();
+        this.currentSite.getSiteConfig('AddonBlockTimelineFilter', this.filter).then((value) => {
+            this.filter = value;
+            this.switchFilter();
+        });
+        this.currentSite.getSiteConfig('AddonBlockTimelineSort', this.sort).then((value) => {
+            this.sort = value;
+            super.ngOnInit();
+        });
     }
 
     /**
@@ -167,6 +176,7 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      * Change timeline filter being viewed.
      */
     switchFilter(): void {
+        this.currentSite.setSiteConfig('AddonBlockTimelineFilter', this.filter);
         switch (this.filter) {
             case 'overdue':
                 this.dataFrom = -14;
@@ -200,6 +210,7 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      * Change timeline sort being viewed.
      */
     switchSort(): void {
+        this.currentSite.setSiteConfig('AddonBlockTimelineSort', this.sort);
         if (!this.timeline.loaded && this.sort == 'sortbydates') {
             this.fetchContent();
         } else if (!this.timelineCourses.loaded && this.sort == 'sortbycourses') {
diff --git a/src/classes/site.ts b/src/classes/site.ts
index 503a6b279..24c2adc2c 100644
--- a/src/classes/site.ts
+++ b/src/classes/site.ts
@@ -167,28 +167,46 @@ export class CoreSite {
 
     // Variables for the database.
     protected WS_CACHE_TABLE = 'wscache';
-    protected tableSchema = {
-        name: this.WS_CACHE_TABLE,
-        columns: [
-            {
-                name: 'id',
-                type: 'TEXT',
-                primaryKey: true
-            },
-            {
-                name: 'data',
-                type: 'TEXT'
-            },
-            {
-                name: 'key',
-                type: 'TEXT'
-            },
-            {
-                name: 'expirationTime',
-                type: 'INTEGER'
-            }
-        ]
-    };
+    protected CONFIG_TABLE = 'core_site_config';
+    protected tableSchemas = [
+        {
+            name: this.WS_CACHE_TABLE,
+            columns: [
+                {
+                    name: 'id',
+                    type: 'TEXT',
+                    primaryKey: true
+                },
+                {
+                    name: 'data',
+                    type: 'TEXT'
+                },
+                {
+                    name: 'key',
+                    type: 'TEXT'
+                },
+                {
+                    name: 'expirationTime',
+                    type: 'INTEGER'
+                }
+            ]
+        },
+        {
+            name: this.CONFIG_TABLE,
+            columns: [
+                {
+                    name: 'name',
+                    type: 'TEXT',
+                    unique: true,
+                    notNull: true
+                },
+                {
+                    name: 'value'
+                }
+            ]
+        }
+
+    ];
 
     // Versions of Moodle releases.
     protected MOODLE_RELEASES = {
@@ -247,7 +265,7 @@ export class CoreSite {
      */
     initDB(): void {
         this.db = this.dbProvider.getDB('Site-' + this.id);
-        this.db.createTableFromSchema(this.tableSchema);
+        this.db.createTablesFromSchema(this.tableSchemas);
     }
 
     /**
@@ -1543,4 +1561,44 @@ export class CoreSite {
 
         return this.MOODLE_RELEASES[releases[position + 1]];
     }
+
+    /**
+     * Deletes a site setting.
+     *
+     * @param {string} name The config name.
+     * @return {Promise<any>} Promise resolved when done.
+     */
+    deleteSiteConfig(name: string): Promise<any> {
+        return this.db.deleteRecords(this.CONFIG_TABLE, { name: name });
+    }
+
+    /**
+     * Get a site setting.
+     *
+     * @param {string} name The config name.
+     * @param {any} [defaultValue] Default value to use if the entry is not found.
+     * @return {Promise<any>} Resolves upon success along with the config data. Reject on failure.
+     */
+    getSiteConfig(name: string, defaultValue?: any): Promise<any> {
+        return this.db.getRecord(this.CONFIG_TABLE, { name: name }).then((entry) => {
+            return entry.value;
+        }).catch((error) => {
+            if (typeof defaultValue != 'undefined') {
+                return defaultValue;
+            }
+
+            return Promise.reject(error);
+        });
+    }
+
+    /**
+     * Set a site setting.
+     *
+     * @param {string} name The config name.
+     * @param {number|string} value The config value. Can only store number or strings.
+     * @return {Promise<any>} Promise resolved when done.
+     */
+    setSiteConfig(name: string, value: number | string): Promise<any> {
+        return this.db.insertRecord(this.CONFIG_TABLE, { name: name, value: value });
+    }
 }

From c77a33edd5e7ef80093332dcadfa23fdba9005fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Wed, 17 Oct 2018 15:03:48 +0200
Subject: [PATCH 2/4] MOBILE-2681 dashboard: Apply same styles from website

---
 src/addon/block/myoverview/component/myoverview.ts   |  8 +++++++-
 src/components/progress-bar/progress-bar.scss        | 12 ++++++------
 .../components/course-progress/course-progress.scss  |  5 -----
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts
index c6876b019..ae339862f 100644
--- a/src/addon/block/myoverview/component/myoverview.ts
+++ b/src/addon/block/myoverview/component/myoverview.ts
@@ -168,7 +168,13 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
 
             this.courses.filter = '';
             this.showFilter = false;
-            this.showSelectorFilter = (this.courses.past.length + this.courses.future.length) > 0;
+            this.showSelectorFilter = this.courses.past.length > 0 || this.courses.future.length > 0 || courses.some((course) => {
+                return typeof course.enddate != 'undefined';
+            });
+            if (!this.showSelectorFilter) {
+                // No selector, show all.
+                this.selectedFilter = 'all';
+            }
             this.filteredCourses = this.courses[this.selectedFilter];
 
             this.initPrefetchCoursesIcons();
diff --git a/src/components/progress-bar/progress-bar.scss b/src/components/progress-bar/progress-bar.scss
index cdab090cc..a385388e0 100644
--- a/src/components/progress-bar/progress-bar.scss
+++ b/src/components/progress-bar/progress-bar.scss
@@ -1,4 +1,4 @@
-$core-progress-bar-height: 10px !default;
+$core-progress-bar-height: 8px !default;
 
 ion-app.app-root core-progress-bar {
     @include padding(null, 55px, null, null);
@@ -11,7 +11,7 @@ ion-app.app-root core-progress-bar {
         line-height: 40px;
         font-size: 1.4rem;
         color: $gray-darker;
-        @include position(-15px, 10px, null, null);
+        @include position(-16px, 10px, null, null);
         position: absolute;
     }
 
@@ -19,7 +19,7 @@ ion-app.app-root core-progress-bar {
         -webkit-appearance: none;
         appearance: none;
         height: $core-progress-bar-height;
-        margin: 15px 0;
+        margin: 16px 0;
         padding: 0;
         display: block;
         width: 100%;
@@ -27,15 +27,15 @@ ion-app.app-root core-progress-bar {
         .progress-bar-fallback,
         &[value]::-webkit-progress-bar {
             background-color: $gray-lighter;
-            border-radius: 999px;
-            border: 1px solid $gray-light;
+            border-radius: 0;
+            border:0;
             box-shadow: none;
         }
 
         .progress-bar-fallback span,
         &[value]::-webkit-progress-value {
             background-color: $core-progressbar-color;
-            border-radius: 999px;
+            border-radius: 0;
         }
 
         .progress-bar-fallback {
diff --git a/src/core/courses/components/course-progress/course-progress.scss b/src/core/courses/components/course-progress/course-progress.scss
index 259eac524..ce16ecd9b 100644
--- a/src/core/courses/components/course-progress/course-progress.scss
+++ b/src/core/courses/components/course-progress/course-progress.scss
@@ -8,11 +8,6 @@ ion-app.app-root core-courses-course-progress {
                 .core-course-thumb {
                     background: nth($core-course-image-background, $i + 1);
                 }
-
-                progress .progress-bar-fallback span,
-                progress[value]::-webkit-progress-value {
-                    background-color: nth($core-course-image-background, $i + 1);
-                }
             }
         }
 

From 603ae1566f0dd1640f7b0f2e402360c29ff68797 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Thu, 18 Oct 2018 11:04:37 +0200
Subject: [PATCH 3/4] MOBILE-2614 myoverview: Add lastaccess sort

---
 scripts/langindex.json                        |  2 +
 .../component/addon-block-myoverview.html     | 29 ++++---
 .../block/myoverview/component/myoverview.ts  | 84 ++++++++++++++-----
 src/addon/block/myoverview/lang/en.json       |  4 +-
 src/assets/lang/en.json                       |  6 +-
 5 files changed, 93 insertions(+), 32 deletions(-)

diff --git a/scripts/langindex.json b/scripts/langindex.json
index 0c05cd558..4cdb865ae 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -13,11 +13,13 @@
   "addon.block_myoverview.all": "block_myoverview",
   "addon.block_myoverview.future": "block_myoverview",
   "addon.block_myoverview.inprogress": "block_myoverview",
+  "addon.block_myoverview.lastaccessed": "block_myoverview",
   "addon.block_myoverview.morecourses": "block_myoverview",
   "addon.block_myoverview.nocoursesfuture": "block_myoverview",
   "addon.block_myoverview.nocoursesinprogress": "block_myoverview",
   "addon.block_myoverview.nocoursespast": "block_myoverview",
   "addon.block_myoverview.past": "block_myoverview",
+  "addon.block_myoverview.title": "block_myoverview",
   "addon.block_timeline.duedate": "block_timeline",
   "addon.block_timeline.next30days": "block_timeline",
   "addon.block_timeline.next3months": "block_timeline",
diff --git a/src/addon/block/myoverview/component/addon-block-myoverview.html b/src/addon/block/myoverview/component/addon-block-myoverview.html
index 8a5cf1324..e12f8e3ee 100644
--- a/src/addon/block/myoverview/component/addon-block-myoverview.html
+++ b/src/addon/block/myoverview/component/addon-block-myoverview.html
@@ -6,22 +6,31 @@
 </core-navbar-buttons>
 
 <core-loading [hideUntil]="loaded" class="core-loading-center">
-    <!-- "Time" selector. -->
-    <div padding class="clearfix" [hidden]="showFilter" ion-row justify-content-end>
-        <ion-select [hidden]="!showSelectorFilter" [title]="'core.show' | translate" [(ngModel)]="selectedFilter" ion-col (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select">
-            <ion-option value="all">{{ 'addon.block_myoverview.all' | translate }}</ion-option>∫
-            <ion-option value="inprogress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option>
-            <ion-option value="future">{{ 'addon.block_myoverview.future' | translate }}</ion-option>
-            <ion-option value="past">{{ 'addon.block_myoverview.past' | translate }}</ion-option>
-        </ion-select>
+    <div padding ion-row justify-content-end [hidden]="showFilter">
+        <!-- "Time" selector. -->
+        <ion-col [hidden]="!showSelectorFilter">
+            <ion-select text-start [title]="'core.show' | translate" [(ngModel)]="selectedFilter" ion-col (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select">
+                <ion-option value="all">{{ 'addon.block_myoverview.all' | translate }}</ion-option>∫
+                <ion-option value="inprogress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option>
+                <ion-option value="future">{{ 'addon.block_myoverview.future' | translate }}</ion-option>
+                <ion-option value="past">{{ 'addon.block_myoverview.past' | translate }}</ion-option>
+            </ion-select>
+        </ion-col>
+        <!-- "Sort" selector. -->
+        <ion-col [hidden]="!showSortFilter">
+            <ion-select text-start [(ngModel)]="sort" (ngModelChange)="switchSort()" interface="popover" class="core-button-select">
+                <ion-option value="title">{{ 'addon.block_myoverview.title' | translate }}</ion-option>
+                <ion-option value="lastaccess">{{ 'addon.block_myoverview.lastaccessed' | translate }}</ion-option>
+            </ion-select>
+        </ion-col>
         <!-- Download all courses. -->
-        <div *ngIf="downloadAllCoursesEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1" class="core-button-spinner" ion-col text-end col-1>
+        <ion-col *ngIf="downloadAllCoursesEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1" class="core-button-spinner" text-end col-1>
             <button *ngIf="prefetchCoursesData[selectedFilter].icon && prefetchCoursesData[selectedFilter].icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()">
                 <core-icon [name]="prefetchCoursesData[selectedFilter].icon"></core-icon>
             </button>
             <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge">{{prefetchCoursesData[selectedFilter].badge}}</ion-badge>
             <ion-spinner *ngIf="!prefetchCoursesData[selectedFilter].icon || prefetchCoursesData[selectedFilter].icon == 'spinner'"></ion-spinner>
-        </div>
+        </ion-col>
     </div>
     <ng-container *ngIf="courses[selectedFilter].length == 0">
         <core-empty-box *ngIf="selectedFilter == 'inprogress'" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocoursesinprogress' | translate"></core-empty-box>
diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts
index ae339862f..1638d3c4a 100644
--- a/src/addon/block/myoverview/component/myoverview.ts
+++ b/src/addon/block/myoverview/component/myoverview.ts
@@ -43,6 +43,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
         future: []
     };
     selectedFilter = 'inprogress';
+    sort = 'title';
     currentSite: any;
     downloadAllCoursesEnabled: boolean;
     filteredCourses: any[];
@@ -54,6 +55,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
     };
     showFilter = false;
     showSelectorFilter = false;
+    showSortFilter = false;
 
     protected prefetchIconsInitialized = false;
     protected isDestroyed;
@@ -89,8 +91,16 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
         });
 
         this.currentSite = this.sitesProvider.getCurrentSite();
-        this.currentSite.getSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
+
+        const promises = [];
+        promises.push(this.currentSite.getSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
+            this.sort = value;
+        }));
+        promises.push(this.currentSite.getSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
             this.selectedFilter = value;
+        }));
+
+        Promise.all(promises).finally(() => {
             super.ngOnInit();
         });
     }
@@ -146,26 +156,12 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
                 });
             }));
         }).then((courses) => {
-            const today = moment().unix();
-
-            this.courses.all = courses;
-            this.courses.past = [];
-            this.courses.inprogress = [];
-            this.courses.future = [];
-
-            courses.forEach((course) => {
-                if ((course.enddate && course.enddate < today) || course.completed) {
-                    // Courses that have already ended.
-                    this.courses.past.push(course);
-                } else if (course.startdate > today) {
-                    // Courses that have not started yet.
-                    this.courses.future.push(course);
-                } else {
-                    // Courses still in progress.
-                    this.courses.inprogress.push(course);
-                }
+            this.showSortFilter = courses.some((course) => {
+                return typeof course.lastaccess != 'undefined';
             });
 
+            this.sortCourses(courses);
+
             this.courses.filter = '';
             this.showFilter = false;
             this.showSelectorFilter = this.courses.past.length > 0 || this.courses.future.length > 0 || courses.some((course) => {
@@ -256,13 +252,61 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
     }
 
     /**
-     * The selected courses have changed.
+     * The selected courses filter have changed.
      */
     selectedChanged(): void {
         this.currentSite.setSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter);
         this.filteredCourses = this.courses[this.selectedFilter];
     }
 
+    /**
+     * Sort and init courses filters.
+     * @param {any[]} courses Courses to sort.
+     */
+    sortCourses(courses: any[]): void {
+        if (this.showSortFilter) {
+            if (this.sort == 'lastaccess') {
+                courses.sort((a, b) => {
+                    return b.lastaccess - a.lastaccess;
+                });
+            } else if (this.sort == 'title') {
+                courses.sort((a, b) => {
+                    const compareA = a.fullname.toLowerCase(),
+                        compareB = b.fullname.toLowerCase();
+
+                    return compareA.localeCompare(compareB);
+                });
+            }
+        }
+
+        this.courses.all = courses;
+        this.courses.past = [];
+        this.courses.inprogress = [];
+        this.courses.future = [];
+
+        const today = moment().unix();
+        courses.forEach((course) => {
+            if ((course.enddate && course.enddate < today) || course.completed) {
+                // Courses that have already ended.
+                this.courses.past.push(course);
+            } else if (course.startdate > today) {
+                // Courses that have not started yet.
+                this.courses.future.push(course);
+            } else {
+                // Courses still in progress.
+                this.courses.inprogress.push(course);
+            }
+        });
+    }
+
+    /**
+     * The selected courses sort filter have changed.
+     */
+    switchSort(): void {
+        this.currentSite.setSiteConfig('AddonBlockMyOverviewSort', this.sort);
+        this.sortCourses(this.courses.all);
+    }
+
     /**
      * Show or hide the filter.
      */
diff --git a/src/addon/block/myoverview/lang/en.json b/src/addon/block/myoverview/lang/en.json
index b18d8b53e..3e7c5ea22 100644
--- a/src/addon/block/myoverview/lang/en.json
+++ b/src/addon/block/myoverview/lang/en.json
@@ -6,5 +6,7 @@
     "morecourses": "More courses",
     "nocoursesfuture": "No future courses",
     "nocoursesinprogress": "No in progress courses",
-    "nocoursespast": "No past courses"
+    "nocoursespast": "No past courses",
+    "lastaccessed": "Last accessed",
+    "title": "Title"
 }
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index d8dbc7f79..aec95a8ca 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -13,11 +13,13 @@
     "addon.block_myoverview.all": "All",
     "addon.block_myoverview.future": "Future",
     "addon.block_myoverview.inprogress": "In progress",
+    "addon.block_myoverview.lastaccessed": "Last accessed",
     "addon.block_myoverview.morecourses": "More courses",
     "addon.block_myoverview.nocoursesfuture": "No future courses",
     "addon.block_myoverview.nocoursesinprogress": "No in progress courses",
     "addon.block_myoverview.nocoursespast": "No past courses",
     "addon.block_myoverview.past": "Past",
+    "addon.block_myoverview.title": "Title",
     "addon.block_timeline.duedate": "Due date",
     "addon.block_timeline.next30days": "Next 30 days",
     "addon.block_timeline.next3months": "Next 3 months",
@@ -1070,7 +1072,7 @@
     "core.areyousure": "Are you sure?",
     "core.back": "Back",
     "core.cancel": "Cancel",
-    "core.cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle 2.4 or later.",
+    "core.cannotconnect": "Cannot connect: verify that you have typed correctly the URL.",
     "core.cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
     "core.captureaudio": "Record audio",
     "core.capturedimage": "Taken picture.",
@@ -1354,6 +1356,7 @@
     "core.login.selectsite": "Please select your site:",
     "core.login.signupplugindisabled": "{{$a}} is not enabled.",
     "core.login.siteaddress": "Site address",
+    "core.login.sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.",
     "core.login.siteinmaintenance": "Your site is in maintenance mode",
     "core.login.sitepolicynotagreederror": "Site policy not agreed.",
     "core.login.siteurl": "Site URL",
@@ -1408,6 +1411,7 @@
     "core.more": "more",
     "core.mygroups": "My groups",
     "core.name": "Name",
+    "core.networkerroriframemsg": "This content is not available offline. Please connect to the internet and try again.",
     "core.networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.",
     "core.never": "Never",
     "core.next": "Next",

From 0024f701d0f34edc3a9d6d07bdc5cc7d56eff103 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Mon, 22 Oct 2018 12:43:36 +0200
Subject: [PATCH 4/4] MOBILE-2614 blocks: Improve some WS checks

---
 .../block/myoverview/component/myoverview.ts    | 17 +++++++----------
 .../timeline/components/timeline/timeline.ts    |  8 ++++----
 src/classes/site.ts                             |  8 ++++----
 3 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/src/addon/block/myoverview/component/myoverview.ts b/src/addon/block/myoverview/component/myoverview.ts
index 1638d3c4a..554b31107 100644
--- a/src/addon/block/myoverview/component/myoverview.ts
+++ b/src/addon/block/myoverview/component/myoverview.ts
@@ -93,10 +93,10 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
         this.currentSite = this.sitesProvider.getCurrentSite();
 
         const promises = [];
-        promises.push(this.currentSite.getSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
+        promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
             this.sort = value;
         }));
-        promises.push(this.currentSite.getSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
+        promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
             this.selectedFilter = value;
         }));
 
@@ -156,17 +156,14 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
                 });
             }));
         }).then((courses) => {
-            this.showSortFilter = courses.some((course) => {
-                return typeof course.lastaccess != 'undefined';
-            });
+            this.showSortFilter = courses.length > 0 && typeof courses[0].lastaccess != 'undefined';
 
             this.sortCourses(courses);
 
             this.courses.filter = '';
             this.showFilter = false;
-            this.showSelectorFilter = this.courses.past.length > 0 || this.courses.future.length > 0 || courses.some((course) => {
-                return typeof course.enddate != 'undefined';
-            });
+            this.showSelectorFilter = this.courses.past.length > 0 || this.courses.future.length > 0 || (courses.length > 0 &&
+                typeof courses[0].enddate != 'undefined');
             if (!this.showSelectorFilter) {
                 // No selector, show all.
                 this.selectedFilter = 'all';
@@ -255,7 +252,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
      * The selected courses filter have changed.
      */
     selectedChanged(): void {
-        this.currentSite.setSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter);
+        this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter);
         this.filteredCourses = this.courses[this.selectedFilter];
     }
 
@@ -303,7 +300,7 @@ export class AddonBlockMyOverviewComponent extends AddonBlockComponent implement
      * The selected courses sort filter have changed.
      */
     switchSort(): void {
-        this.currentSite.setSiteConfig('AddonBlockMyOverviewSort', this.sort);
+        this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort);
         this.sortCourses(this.courses.all);
     }
 
diff --git a/src/addon/block/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts
index a09eba441..4aadf42cd 100644
--- a/src/addon/block/timeline/components/timeline/timeline.ts
+++ b/src/addon/block/timeline/components/timeline/timeline.ts
@@ -61,11 +61,11 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      */
     ngOnInit(): void {
         this.currentSite = this.sitesProvider.getCurrentSite();
-        this.currentSite.getSiteConfig('AddonBlockTimelineFilter', this.filter).then((value) => {
+        this.currentSite.getLocalSiteConfig('AddonBlockTimelineFilter', this.filter).then((value) => {
             this.filter = value;
             this.switchFilter();
         });
-        this.currentSite.getSiteConfig('AddonBlockTimelineSort', this.sort).then((value) => {
+        this.currentSite.getLocalSiteConfig('AddonBlockTimelineSort', this.sort).then((value) => {
             this.sort = value;
             super.ngOnInit();
         });
@@ -176,7 +176,7 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      * Change timeline filter being viewed.
      */
     switchFilter(): void {
-        this.currentSite.setSiteConfig('AddonBlockTimelineFilter', this.filter);
+        this.currentSite.setLocalSiteConfig('AddonBlockTimelineFilter', this.filter);
         switch (this.filter) {
             case 'overdue':
                 this.dataFrom = -14;
@@ -210,7 +210,7 @@ export class AddonBlockTimelineComponent extends AddonBlockComponent implements
      * Change timeline sort being viewed.
      */
     switchSort(): void {
-        this.currentSite.setSiteConfig('AddonBlockTimelineSort', this.sort);
+        this.currentSite.setLocalSiteConfig('AddonBlockTimelineSort', this.sort);
         if (!this.timeline.loaded && this.sort == 'sortbydates') {
             this.fetchContent();
         } else if (!this.timelineCourses.loaded && this.sort == 'sortbycourses') {
diff --git a/src/classes/site.ts b/src/classes/site.ts
index 24c2adc2c..b76499d21 100644
--- a/src/classes/site.ts
+++ b/src/classes/site.ts
@@ -1573,13 +1573,13 @@ export class CoreSite {
     }
 
     /**
-     * Get a site setting.
+     * Get a site setting on local device.
      *
      * @param {string} name The config name.
      * @param {any} [defaultValue] Default value to use if the entry is not found.
      * @return {Promise<any>} Resolves upon success along with the config data. Reject on failure.
      */
-    getSiteConfig(name: string, defaultValue?: any): Promise<any> {
+    getLocalSiteConfig(name: string, defaultValue?: any): Promise<any> {
         return this.db.getRecord(this.CONFIG_TABLE, { name: name }).then((entry) => {
             return entry.value;
         }).catch((error) => {
@@ -1592,13 +1592,13 @@ export class CoreSite {
     }
 
     /**
-     * Set a site setting.
+     * Set a site setting on local device.
      *
      * @param {string} name The config name.
      * @param {number|string} value The config value. Can only store number or strings.
      * @return {Promise<any>} Promise resolved when done.
      */
-    setSiteConfig(name: string, value: number | string): Promise<any> {
+    setLocalSiteConfig(name: string, value: number | string): Promise<any> {
         return this.db.insertRecord(this.CONFIG_TABLE, { name: name, value: value });
     }
 }