From 588ceaac718bfae82351d22aeee35b9aac997dec Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 Jan 2019 12:41:22 +0100 Subject: [PATCH 1/7] MOBILE-2795 date: Fix format for datetime inputs --- src/addon/mod/data/fields/date/component/date.ts | 8 ++++++-- .../userprofilefield/datetime/component/datetime.ts | 9 +++++++-- src/providers/utils/time.ts | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/addon/mod/data/fields/date/component/date.ts b/src/addon/mod/data/fields/date/component/date.ts index d160933cf..5b17bf4a5 100644 --- a/src/addon/mod/data/fields/date/component/date.ts +++ b/src/addon/mod/data/fields/date/component/date.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component'; @@ -27,7 +28,7 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginCompo format: string; - constructor(protected fb: FormBuilder, protected timeUtils: CoreTimeUtilsProvider) { + constructor(protected fb: FormBuilder, protected timeUtils: CoreTimeUtilsProvider, protected translate: TranslateService) { super(fb); } @@ -40,7 +41,10 @@ export class AddonModDataFieldDateComponent extends AddonModDataFieldPluginCompo } let val; - this.format = this.timeUtils.getLocalizedDateFormat('LL'); + + // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them. + this.format = this.timeUtils.convertPHPToMoment(this.translate.instant('core.strftimedatefullshort')) + .replace(/[\[\]]/g, ''); if (this.mode == 'search') { this.addControl('f_' + this.field.id + '_z'); diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index 6e0ac017d..c43a61bc0 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -14,6 +14,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -30,7 +31,8 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider) { } + constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider, + private translate: TranslateService) { } /** * Component being initialized. @@ -44,7 +46,10 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { // Check if it's only date or it has time too. const hasTime = this.utils.isTrueOrOne(field.param3); - field.format = hasTime ? this.timeUtils.getLocalizedDateFormat('LLL') : this.timeUtils.getLocalizedDateFormat('LL'); + + // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them. + field.format = this.timeUtils.convertPHPToMoment(this.translate.instant('core.' + + (hasTime ? 'strftimedatetimeshort' : 'strftimedatefullshort'))).replace(/[\[\]]/g, ''); // Check min value. if (field.param1) { diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index fb3d2915f..00bb2360b 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -278,6 +278,7 @@ export class CoreTimeUtilsProvider { /** * Return the localized ISO format (i.e DDMMYY) from the localized moment format. Useful for translations. + * DO NOT USE this function for ion-datetime format. Moment escapes characters with [], but ion-datetime doesn't support it. * * @param {any} localizedFormat Format to use. * @return {string} Localized ISO format From 7ced9ccd5de692a41b290c0cf9ef971c3f1b1e25 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 Jan 2019 14:38:51 +0100 Subject: [PATCH 2/7] MOBILE-2795 lang: Use fallback for countries list --- src/providers/lang.ts | 37 +++++++++++++++++++ src/providers/utils/utils.ts | 71 ++++++++++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/providers/lang.ts b/src/providers/lang.ts index d6dbfae87..621a89a19 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -240,6 +240,43 @@ export class CoreLangProvider { }); } + /** + * Get the default language. + * + * @return {string} Default language. + */ + getDefaultLanguage(): string { + return this.defaultLanguage; + } + + /** + * Get the fallback language. + * + * @return {string} Fallback language. + */ + getFallbackLanguage(): string { + return this.fallbackLanguage; + } + + /** + * Get the full list of translations for a certain language. + * + * @param {string} lang The language to check. + * @return {Promise} Promise resolved when done. + */ + getTranslationTable(lang: string): Promise { + // Create a promise to convert the observable into a promise. + return new Promise((resolve, reject): void => { + const observer = this.translate.getTranslation(lang).subscribe((table) => { + resolve(table); + observer.unsubscribe(); + }, (err) => { + reject(err); + observer.unsubscribe(); + }); + }); + } + /** * Load certain custom strings. * diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index bfe707092..8a45fa2d2 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -561,29 +561,68 @@ export class CoreUtilsProvider { * @return {Promise} Promise resolved with the list of countries. */ getCountryList(): Promise { - // Get the current language. - return this.langProvider.getCurrentLanguage().then((lang) => { - // Get the full list of translations. Create a promise to convert the observable into a promise. - return new Promise((resolve, reject): void => { - const observer = this.translate.getTranslation(lang).subscribe((table) => { - resolve(table); - observer.unsubscribe(); - }, (err) => { - reject(err); - observer.unsubscribe(); - }); - }); - }).then((table) => { + // Get the keys of the countries. + return this.getCountryKeysList().then((keys) => { + // Now get the code and the translated name. const countries = {}; + keys.forEach((key) => { + if (key.indexOf('assets.countries.') === 0) { + const code = key.replace('assets.countries.', ''); + countries[code] = this.translate.instant(key); + } + }); + + return countries; + }); + } + + /** + * Get the list of language keys of the countries. + * + * @return {Promise} Promise resolved with the countries list. Rejected if not translated. + */ + protected getCountryKeysList(): Promise { + // It's possible that the current language isn't translated, so try with default language first. + const defaultLang = this.langProvider.getDefaultLanguage(); + + return this.getCountryKeysListForLanguage(defaultLang).catch(() => { + // Not translated, try to use the fallback language. + const fallbackLang = this.langProvider.getFallbackLanguage(); + + if (fallbackLang === defaultLang) { + // Same language, just reject. + return Promise.reject('Countries not found.'); + } + + return this.getCountryKeysListForLanguage(fallbackLang); + }); + } + + /** + * Get the list of language keys of the countries, based on the translation table for a certain language. + * + * @param {string} lang Language to check. + * @return {Promise} Promise resolved with the countries list. Rejected if not translated. + */ + protected getCountryKeysListForLanguage(lang: string): Promise { + // Get the translation table for the language. + return this.langProvider.getTranslationTable(lang).then((table): any => { + // Gather all the keys for countries, + const keys = []; + for (const name in table) { if (name.indexOf('assets.countries.') === 0) { - const code = name.replace('assets.countries.', ''); - countries[code] = table[name]; + keys.push(name); } } - return countries; + if (keys.length === 0) { + // Not translated, reject. + return Promise.reject('Countries not found.'); + } + + return keys; }); } From 2a439224f2d99603894acd501c5e7736f08ca6ed Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 Jan 2019 15:41:57 +0100 Subject: [PATCH 3/7] MOBILE-2795 login: Fix forgot password in 3.3 or older --- src/core/login/providers/helper.ts | 3 ++- src/providers/ws.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index f254071dc..af4bb667f 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -203,7 +203,8 @@ export class CoreLoginHelperProvider { return this.requestPasswordReset(siteUrl).then(() => { return true; }).catch((error) => { - return error.available == 1 || (error.errorcode != 'invalidrecord' && error.errorcode != ''); + return error.available == 1 || (typeof error.errorcode != 'undefined' && error.errorcode != 'invalidrecord' && + error.errorcode != ''); }); } diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 84c85bd71..7372bbfff 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -254,14 +254,14 @@ export class CoreWSProvider { if (!data || typeof data != 'object') { return rejectWithError(this.createFakeWSError('core.serverconnection', true)); } else if (data.error) { - return rejectWithError(data.error); + return rejectWithError(data); } // Get the first response since only one request was done. data = data[0]; if (data.error) { - return rejectWithError(data.exception); + return rejectWithError(data); } return data.data; From bdf3ec6fa769e16ddcc84b5b764e515b778eb7ec Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jan 2019 08:19:53 +0100 Subject: [PATCH 4/7] MOBILE-2795 file: Allow opening local attachments --- src/components/file/file.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/file/file.ts b/src/components/file/file.ts index f79be7811..53fb53aa6 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -152,6 +152,22 @@ export class CoreFileComponent implements OnInit, OnDestroy { return; } + if (!this.canDownload) { + // File cannot be downloaded, just open it. + if (this.file.toURL) { + // Local file. + this.utils.openFile(this.file.toURL()); + } else if (this.fileUrl) { + if (this.fileUrl.indexOf('http') === 0) { + this.utils.openOnlineFile(this.fileUrl); + } else { + this.utils.openFile(this.fileUrl); + } + } + + return; + } + if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) { this.domUtils.showErrorModal('core.networkerrormsg', true); From c41cb6bb19f8b6a71762d2b5377c8712a7b1d9b6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jan 2019 11:18:29 +0100 Subject: [PATCH 5/7] MOBILE-2795 core: Fix redirect to other sites --- src/addon/messages/messages.module.ts | 18 ++-- src/addon/messages/providers/messages.ts | 19 ++++- .../notifications/notifications.module.ts | 9 +- src/components/ion-tabs/ion-tabs.ts | 83 ++++++++++++++----- src/core/login/providers/helper.ts | 14 +++- src/core/mainmenu/pages/menu/menu.ts | 9 ++ 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index 0d3cc13fc..43b3609c6 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -107,11 +107,14 @@ export class AddonMessagesModule { } messagesProvider.invalidateDiscussionsCache(notification.site).finally(() => { - let pageName = 'AddonMessagesIndexPage'; - if (messagesProvider.isGroupMessagingEnabled()) { - pageName = 'AddonMessagesGroupConversationsPage'; - } - linkHelper.goInSite(undefined, pageName, undefined, notification.site); + // Check if group messaging is enabled, to determine which page should be loaded. + messagesProvider.isGroupMessagingEnabledInSite(notification.site).then((enabled) => { + let pageName = 'AddonMessagesIndexPage'; + if (enabled) { + pageName = 'AddonMessagesGroupConversationsPage'; + } + linkHelper.goInSite(undefined, pageName, undefined, notification.site); + }); }); }); }); @@ -125,7 +128,10 @@ export class AddonMessagesModule { // Register push notification clicks. pushNotificationsDelegate.on('click').subscribe((notification) => { if (utils.isFalseOrZero(notification.notif)) { - notificationClicked(notification); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + notificationClicked(notification); + }); return true; } diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index 5b49d149c..c81d27e65 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -1770,17 +1770,32 @@ export class AddonMessagesProvider { /** * Returns whether or not group messaging is supported. * - * @return {boolean} If related WS is avalaible on current site. + * @return {boolean} If related WS is available on current site. * @since 3.6 */ isGroupMessagingEnabled(): boolean { return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_conversations'); } + /** + * Returns whether or not group messaging is supported in a certain site. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with boolean: whether related WS is available on a certain site. + * @since 3.6 + */ + isGroupMessagingEnabledInSite(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('core_message_get_conversations'); + }).catch(() => { + return false; + }); + } + /** * Returns whether or not we can mark all messages as read. * - * @return {boolean} If related WS is avalaible on current site. + * @return {boolean} If related WS is available on current site. * @since 3.2 */ isMarkAllMessagesReadEnabled(): boolean { diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts index 5359e1e35..384901f44 100644 --- a/src/addon/notifications/notifications.module.ts +++ b/src/addon/notifications/notifications.module.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { NgModule, NgZone } from '@angular/core'; import { AddonNotificationsProvider } from './providers/notifications'; import { AddonNotificationsMainMenuHandler } from './providers/mainmenu-handler'; import { AddonNotificationsSettingsHandler } from './providers/settings-handler'; @@ -47,7 +47,7 @@ export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ export class AddonNotificationsModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: AddonNotificationsMainMenuHandler, settingsDelegate: CoreSettingsDelegate, settingsHandler: AddonNotificationsSettingsHandler, - cronDelegate: CoreCronDelegate, cronHandler: AddonNotificationsCronHandler, + cronDelegate: CoreCronDelegate, cronHandler: AddonNotificationsCronHandler, zone: NgZone, appProvider: CoreAppProvider, utils: CoreUtilsProvider, sitesProvider: CoreSitesProvider, notificationsProvider: AddonNotificationsProvider, localNotifications: CoreLocalNotificationsProvider, linkHelper: CoreContentLinksHelperProvider, pushNotificationsDelegate: AddonPushNotificationsDelegate) { @@ -76,7 +76,10 @@ export class AddonNotificationsModule { // Register push notification clicks. pushNotificationsDelegate.on('click').subscribe((notification) => { if (utils.isTrueOrOne(notification.notif)) { - notificationClicked(notification); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + notificationClicked(notification); + }); return true; } diff --git a/src/components/ion-tabs/ion-tabs.ts b/src/components/ion-tabs/ion-tabs.ts index 9de17f576..4a6eaccf8 100644 --- a/src/components/ion-tabs/ion-tabs.ts +++ b/src/components/ion-tabs/ion-tabs.ts @@ -14,9 +14,11 @@ import { Component, Optional, ElementRef, Renderer, ViewEncapsulation, forwardRef, ViewChild, Input, OnDestroy } from '@angular/core'; -import { Tabs, NavController, ViewController, App, Config, Platform, DeepLinker, Keyboard, RootNode } from 'ionic-angular'; +import { + Tabs, Tab, NavController, ViewController, App, Config, Platform, DeepLinker, Keyboard, RootNode, NavOptions +} from 'ionic-angular'; import { CoreIonTabComponent } from './ion-tab'; -import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreUtilsProvider, PromiseDefer } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; /** @@ -66,6 +68,7 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { protected firstSelectedTab: string; protected unregisterBackButtonAction: any; + protected selectTabPromiseDefer: PromiseDefer; constructor(protected utils: CoreUtilsProvider, protected appProvider: CoreAppProvider, @Optional() parent: NavController, @Optional() viewCtrl: ViewController, _app: App, config: Config, elementRef: ElementRef, _plt: Platform, @@ -148,20 +151,18 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { // Tabs initialized. Force select the tab if it's not enabled. if (this.selectedDisabled && typeof this.selectedIndex != 'undefined') { const tab = this.getByIndex(this.selectedIndex); - if (tab && (!tab.enabled)) { - this.select(tab); - } - } else { - // Select first tab on init. - const tab = this._tabs.find((tab) => { - return tab.enabled; - }); - if (tab) { + if (tab && !tab.enabled) { this.select(tab); } } this.firstSelectedTab = this._selectHistory[0] || null; + }).finally(() => { + // If there was a select promise pending to be resolved, do it now. + if (this.selectTabPromiseDefer) { + this.selectTabPromiseDefer.resolve(); + delete this.selectTabPromiseDefer; + } }); } else { // Tabs not loaded yet. Set the tab bar position so the tab bar is shown, it'll have a spinner. @@ -265,20 +266,62 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { } } + /** + * Select a tab. + * + * @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select. + * @param {NavOptions} Nav options. + * @param {boolean} [fromUrl=true] Whether to load from a URL. + * @return {Promise} Promise resolved when selected. + */ + select(tabOrIndex: number | Tab, opts: NavOptions = {}, fromUrl: boolean = false): Promise { + + if (this.initialized) { + // Tabs have been initialized, select the tab. + return super.select(tabOrIndex, opts, fromUrl); + } else { + // Tabs not initialized yet. Mark it as "selectedIndex" input so it's treated when the tabs are initialized. + if (typeof tabOrIndex == 'number') { + this.selectedIndex = tabOrIndex; + } else { + this.selectedIndex = this.getIndex(tabOrIndex); + } + + // Don't resolve the Promise until the tab is really selected (tabs are initialized). + this.selectTabPromiseDefer = this.selectTabPromiseDefer || this.utils.promiseDefer(); + + return this.selectTabPromiseDefer.promise; + } + } + /** * Select a tab by Index. First it will reset the status of the tab. * * @param {number} index Index of the tab. + * @return {Promise} Promise resolved when selected. */ - selectTabRootByIndex(index: number): void { - const tab = this.getByIndex(index); - if (tab) { - tab.goToRoot({animate: false, updateUrl: true, isNavRoot: true}).then(() => { - // Tab not previously selected. Select it after going to root. - if (!tab.isSelected) { - this.select(tab, {animate: false, updateUrl: true, isNavRoot: true}); - } - }); + selectTabRootByIndex(index: number): Promise { + if (this.initialized) { + const tab = this.getByIndex(index); + if (tab) { + return tab.goToRoot({animate: false, updateUrl: true, isNavRoot: true}).then(() => { + // Tab not previously selected. Select it after going to root. + if (!tab.isSelected) { + return this.select(tab, {animate: false, updateUrl: true, isNavRoot: true}); + } + }); + } + + // Not found. + return Promise.reject(null); + } else { + // Tabs not initialized yet. Mark it as "selectedIndex" input so it's treated when the tabs are initialized. + this.selectedIndex = index; + + // Don't resolve the Promise until the tab is really selected (tabs are initialized). + this.selectTabPromiseDefer = this.selectTabPromiseDefer || this.utils.promiseDefer(); + + return this.selectTabPromiseDefer.promise; } } diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index af4bb667f..2f0dc2d78 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -587,18 +587,24 @@ export class CoreLoginHelperProvider { * @param {string} siteId Site to load. */ protected loadSiteAndPage(page: string, params: any, siteId: string): void { + const navCtrl = this.appProvider.getRootNavController(); + if (siteId == CoreConstants.NO_SITE_ID) { // Page doesn't belong to a site, just load the page. - this.appProvider.getRootNavController().setRoot(page, params); + navCtrl.setRoot(page, params); } else { const modal = this.domUtils.showModalLoading(); this.sitesProvider.loadSite(siteId, page, params).then((loggedIn) => { if (loggedIn) { - this.loadPageInMainMenu(page, params); + // Due to DeepLinker, we need to remove the path from the URL before going to main menu. + // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL. + this.location.replaceState(''); + + navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params }); } - }).catch(() => { + }).catch((error) => { // Site doesn't exist. - this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage'); + navCtrl.setRoot('CoreLoginSitesPage'); }).finally(() => { modal.dismiss(); }); diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index d41e91a2e..4bd7b97d5 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -43,6 +43,15 @@ export class CoreMainMenuPage implements OnDestroy { constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams, private navCtrl: NavController, private eventsProvider: CoreEventsProvider) { + + // Check if the menu was loaded with a redirect. + const redirectPage = navParams.get('redirectPage'); + if (redirectPage) { + this.pendingRedirect = { + redirectPage: redirectPage, + redirectParams: navParams.get('redirectParams') + }; + } } /** From 987ff6ad439996a0ca1d6a1b838d82e96b4964f8 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jan 2019 14:26:53 +0100 Subject: [PATCH 6/7] MOBILE-2795 notifications: Refresh list when mark all as read --- src/addon/notifications/pages/list/list.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index 3d0d5aa85..62add77ad 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -139,13 +139,15 @@ export class AddonNotificationsListPage { this.loadingMarkAllNotificationsAsRead = true; this.notificationsProvider.markAllNotificationsAsRead().catch(() => { // Omit failure. - }).finally(() => { + }).then(() => { const siteId = this.sitesProvider.getCurrentSiteId(); this.eventsProvider.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, null, siteId); - this.notificationsProvider.getUnreadNotificationsCount().then((unread) => { - this.canMarkAllNotificationsAsRead = unread > 0; - this.loadingMarkAllNotificationsAsRead = false; + // All marked as read, refresh the list. + this.notificationsLoaded = false; + + return this.refreshNotifications().finally(() => { + this.notificationsLoaded = true; }); }); } @@ -182,6 +184,7 @@ export class AddonNotificationsListPage { return this.notificationsProvider.getUnreadNotificationsCount().then((unread) => { this.canMarkAllNotificationsAsRead = unread > 0; + }).finally(() => { this.loadingMarkAllNotificationsAsRead = false; }); } @@ -193,9 +196,10 @@ export class AddonNotificationsListPage { * Refresh notifications. * * @param {any} [refresher] Refresher. + * @return Promise Promise resolved when done. */ - refreshNotifications(refresher?: any): void { - this.notificationsProvider.invalidateNotificationsList().finally(() => { + refreshNotifications(refresher?: any): Promise { + return this.notificationsProvider.invalidateNotificationsList().finally(() => { return this.fetchNotifications(true).finally(() => { if (refresher) { refresher.complete(); From c5440811dd544f9fbee161365c9446bb3d85720f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jan 2019 15:29:27 +0100 Subject: [PATCH 7/7] MOBILE-2795 course: Fix disable download course --- .../course-progress/core-courses-course-progress.html | 2 +- src/core/courses/pages/dashboard/dashboard.html | 2 +- src/core/courses/pages/dashboard/dashboard.ts | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/courses/components/course-progress/core-courses-course-progress.html b/src/core/courses/components/course-progress/core-courses-course-progress.html index 413d94520..4e2757ebb 100644 --- a/src/core/courses/components/course-progress/core-courses-course-progress.html +++ b/src/core/courses/components/course-progress/core-courses-course-progress.html @@ -22,7 +22,7 @@
- + - + diff --git a/src/core/courses/pages/dashboard/dashboard.ts b/src/core/courses/pages/dashboard/dashboard.ts index 150ad911a..fccd91e7b 100644 --- a/src/core/courses/pages/dashboard/dashboard.ts +++ b/src/core/courses/pages/dashboard/dashboard.ts @@ -52,6 +52,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { downloadEnabled: boolean; downloadEnabledIcon = 'square-outline'; // Disabled by default. downloadCourseEnabled: boolean; + downloadCoursesEnabled: boolean; protected isDestroyed; protected updateSiteObserver; @@ -69,11 +70,13 @@ export class CoreCoursesDashboardPage implements OnDestroy { ionViewDidLoad(): void { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); // Refresh the enabled flags if site is updated. this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); this.switchDownload(this.downloadEnabled); @@ -196,7 +199,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { * @param {boolean} enable If enable or disable. */ protected switchDownload(enable: boolean): void { - this.downloadEnabled = this.downloadCourseEnabled && enable; + this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; this.downloadEnabledIcon = this.downloadEnabled ? 'checkbox-outline' : 'square-outline'; this.eventsProvider.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, {enabled: this.downloadEnabled}); }