From 44a39fd763706adfd874dd279ea73badd2a08438 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 22 Mar 2019 11:08:47 +0100 Subject: [PATCH] MOBILE-2927 notification: Use new notifications WS --- .../notifications/notifications.module.ts | 5 +- src/addon/notifications/pages/list/list.ts | 61 +++--------- src/addon/notifications/providers/helper.ts | 93 +++++++++++++++++++ .../notifications/providers/notifications.ts | 84 ++++++++++++++++- 4 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 src/addon/notifications/providers/helper.ts diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts index b19fd72dc..d5042b001 100644 --- a/src/addon/notifications/notifications.module.ts +++ b/src/addon/notifications/notifications.module.ts @@ -14,6 +14,7 @@ import { NgModule, NgZone } from '@angular/core'; import { AddonNotificationsProvider } from './providers/notifications'; +import { AddonNotificationsHelperProvider } from './providers/helper'; import { AddonNotificationsMainMenuHandler } from './providers/mainmenu-handler'; import { AddonNotificationsSettingsHandler } from './providers/settings-handler'; import { AddonNotificationsCronHandler } from './providers/cron-handler'; @@ -30,7 +31,8 @@ import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers // List of providers (without handlers). export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ - AddonNotificationsProvider + AddonNotificationsProvider, + AddonNotificationsHelperProvider ]; @NgModule({ @@ -40,6 +42,7 @@ export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ ], providers: [ AddonNotificationsProvider, + AddonNotificationsHelperProvider, AddonNotificationsMainMenuHandler, AddonNotificationsSettingsHandler, AddonNotificationsCronHandler, diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index 2799cc8ee..3b8d1751a 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -21,6 +21,7 @@ import { CoreEventsProvider, CoreEventObserver } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { AddonNotificationsProvider } from '../../providers/notifications'; +import { AddonNotificationsHelperProvider } from '../../providers/helper'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; /** @@ -40,15 +41,14 @@ export class AddonNotificationsListPage { canMarkAllNotificationsAsRead = false; loadingMarkAllNotificationsAsRead = false; - protected readCount = 0; - protected unreadCount = 0; protected cronObserver: CoreEventObserver; protected pushObserver: Subscription; constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private notificationsProvider: AddonNotificationsProvider, - private pushNotificationsDelegate: CorePushNotificationsDelegate) { + private pushNotificationsDelegate: CorePushNotificationsDelegate, + private notificationsHelper: AddonNotificationsHelperProvider) { } /** @@ -79,53 +79,17 @@ export class AddonNotificationsListPage { protected fetchNotifications(refresh?: boolean): Promise { this.loadMoreError = false; - if (refresh) { - this.readCount = 0; - this.unreadCount = 0; - } + return this.notificationsHelper.getNotifications(refresh ? [] : this.notifications).then((result) => { + result.notifications.forEach(this.formatText.bind(this)); - const limit = AddonNotificationsProvider.LIST_LIMIT; - - return this.notificationsProvider.getUnreadNotifications(this.unreadCount, limit).then((unread) => { - const promises = []; - - unread.forEach(this.formatText.bind(this)); - - /* Don't add the unread notifications to this.notifications yet. If there are no unread notifications - that causes that the "There are no notifications" message is shown in pull to refresh. */ - this.unreadCount += unread.length; - - if (unread.length < limit) { - // Limit not reached. Get read notifications until reach the limit. - const readLimit = limit - unread.length; - promises.push(this.notificationsProvider.getReadNotifications(this.readCount, readLimit).then((read) => { - read.forEach(this.formatText.bind(this)); - this.readCount += read.length; - if (refresh) { - this.notifications = unread.concat(read); - } else { - this.notifications = this.notifications.concat(unread, read); - } - this.canLoadMore = read.length >= readLimit; - }).catch((error) => { - if (unread.length == 0) { - this.domUtils.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true); - this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. - } - })); + if (refresh) { + this.notifications = result.notifications; } else { - if (refresh) { - this.notifications = unread; - } else { - this.notifications = this.notifications.concat(unread); - } - this.canLoadMore = true; + this.notifications = this.notifications.concat(result.notifications); } + this.canLoadMore = result.canLoadMore; - return Promise.all(promises).then(() => { - // Mark retrieved notifications as read if they are not. - this.markNotificationsAsRead(unread); - }); + this.markNotificationsAsRead(result.notifications); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true); this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. @@ -162,6 +126,11 @@ export class AddonNotificationsListPage { if (notifications.length > 0) { const promises = notifications.map((notification) => { + if (notification.read) { + // Already read, don't mark it. + return Promise.resolve(); + } + return this.notificationsProvider.markNotificationRead(notification.id); }); diff --git a/src/addon/notifications/providers/helper.ts b/src/addon/notifications/providers/helper.ts new file mode 100644 index 000000000..f9c9e80bf --- /dev/null +++ b/src/addon/notifications/providers/helper.ts @@ -0,0 +1,93 @@ +// (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. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonNotificationsProvider } from './notifications'; + +/** + * Service that provides some helper functions for notifications. + */ +@Injectable() +export class AddonNotificationsHelperProvider { + + constructor(private notificationsProvider: AddonNotificationsProvider, private sitesProvider: CoreSitesProvider) { + } + + /** + * Get some notifications. It will try to use the new WS if available. + * + * @param {any[]} notifications Current list of loaded notifications. It's used to calculate the offset. + * @param {number} [limit] Number of notifications to get. Defaults to LIST_LIMIT. + * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. + * @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, use current site. + * @return {Promise<{notifications: any[], canLoadMore: boolean}>} Promise resolved with notifications and if can load more. + */ + getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, + siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { + + notifications = notifications || []; + limit = limit || AddonNotificationsProvider.LIST_LIMIT; + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.notificationsProvider.isPopupAvailable(siteId).then((available) => { + + if (available) { + return this.notificationsProvider.getPopupNotifications(notifications.length, limit, toDisplay, forceCache, + ignoreCache, siteId); + + } else { + // Fallback to get_messages. We need 2 calls, one for read and the other one for unread. + const unreadFrom = notifications.reduce((total, current) => { + return total + (current.read ? 0 : 1); + }, 0); + + return this.notificationsProvider.getUnreadNotifications(unreadFrom, limit, toDisplay, forceCache, ignoreCache, + siteId).then((unread) => { + + let promise; + + if (unread.length < limit) { + // Limit not reached. Get read notifications until reach the limit. + const readLimit = limit - unread.length, + readFrom = notifications.length - unreadFrom; + + promise = this.notificationsProvider.getReadNotifications(readFrom, readLimit, toDisplay, forceCache, + ignoreCache, siteId).then((read) => { + return unread.concat(read); + }).catch((error): any => { + if (unread.length > 0) { + // We were able to get some unread, return only the unread ones. + return unread; + } + + return Promise.reject(error); + }); + } else { + promise = Promise.resolve(unread); + } + + return promise.then((notifications) => { + return { + notifications: notifications, + canLoadMore: notifications.length >= limit + }; + }); + }); + } + }); + } +} diff --git a/src/addon/notifications/providers/notifications.ts b/src/addon/notifications/providers/notifications.ts index 1b9219fe8..b52578a53 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -45,9 +45,10 @@ export class AddonNotificationsProvider { * Function to format notification data. * * @param {any[]} notifications List of notifications. + * @param {boolean} [read] Whether the notifications are read or unread. * @return {Promise} Promise resolved with notifications. */ - protected formatNotificationsData(notifications: any[]): Promise { + protected formatNotificationsData(notifications: any[], read?: boolean): Promise { const promises = notifications.map((notification) => { // Set message to show. if (notification.contexturl && notification.contexturl.indexOf('/mod/forum/') >= 0) { @@ -55,6 +56,7 @@ export class AddonNotificationsProvider { } else { notification.mobiletext = notification.fullmessage; } + // Try to set courseid the notification belongs to. const cid = notification.fullmessagehtml.match(/course\/view\.php\?id=([^"]*)/); if (cid && cid[1]) { @@ -71,6 +73,11 @@ export class AddonNotificationsProvider { }); } + notification.notification = 1; + if (typeof read != 'undefined') { + notification.read = read; + } + return Promise.resolve(notification); }); @@ -154,7 +161,7 @@ export class AddonNotificationsProvider { if (response.messages) { const notifications = response.messages; - return this.formatNotificationsData(notifications).then(() => { + return this.formatNotificationsData(notifications, read).then(() => { if (this.appProvider.isDesktop() && toDisplay && !read && limitFrom === 0) { // Store the last received notification. Don't block the user for this. this.emulatorHelper.storeLastReceivedNotification( @@ -170,6 +177,67 @@ export class AddonNotificationsProvider { }); } + /** + * Get notifications from site using the new WebService. + * + * @param {number} offset Position of the first notification to get. + * @param {number} [limit] Number of notifications to get. Defaults to LIST_LIMIT. + * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. + * @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, use current site. + * @return {Promise<{notifications: any[], canLoadMore: boolean}>} Promise resolved with notifications and if can load more. + * @since 3.2 + */ + getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, + siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { + + limit = limit || AddonNotificationsProvider.LIST_LIMIT; + + this.logger.debug('Get popup notifications from ' + offset + '. Limit: ' + limit); + + return this.sitesProvider.getSite(siteId).then((site) => { + const data = { + useridto: site.getUserId(), + newestfirst: 1, + offset: offset, + limit: limit + 1 // Get one more to calculate canLoadMore. + }, + preSets = { + cacheKey: this.getNotificationsCacheKey(), + omitExpires: forceCache, + getFromCache: forceCache || !ignoreCache, + emergencyCache: forceCache || !ignoreCache, + }; + + // Get notifications. + return site.read('message_popup_get_popup_notifications', data, preSets).then((response) => { + if (response.notifications) { + const result: any = { + canLoadMore: response.notifications.length > limit + }, + notifications = response.notifications.slice(0, limit); + + result.notifications = notifications; + + return this.formatNotificationsData(notifications).then(() => { + const first = notifications[0]; + + if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) { + // Store the last received notification. Don't block the user for this. + this.emulatorHelper.storeLastReceivedNotification( + AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, first, siteId); + } + + return result; + }); + } else { + return Promise.reject(null); + } + }); + }); + } + /** * Get read notifications from site. * @@ -244,6 +312,18 @@ export class AddonNotificationsProvider { }); } + /** + * Returns whether or not popup WS is available for a certain site. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if available, resolved with false or rejected otherwise. + */ + isPopupAvailable(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('message_popup_get_popup_notifications'); + }); + } + /** * Mark all message notification as read. *