diff --git a/src/addons/badges/badge-lazy.module.ts b/src/addons/badges/badge-lazy.module.ts new file mode 100644 index 000000000..e59049c66 --- /dev/null +++ b/src/addons/badges/badge-lazy.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge'; + +const routes: Routes = [ + { + path: ':badgeHash', + component: AddonBadgesIssuedBadgePage, + data: { usesSwipeNavigation: false }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + ], +}) +export class AddonBadgeLazyModule {} diff --git a/src/addons/badges/badges-lazy.module.ts b/src/addons/badges/badges-lazy.module.ts index e23e2d26c..db153560e 100644 --- a/src/addons/badges/badges-lazy.module.ts +++ b/src/addons/badges/badges-lazy.module.ts @@ -31,6 +31,7 @@ const mobileRoutes: Routes = [ { path: ':badgeHash', component: AddonBadgesIssuedBadgePage, + data: { usesSwipeNavigation: true }, }, ]; @@ -42,6 +43,7 @@ const tabletRoutes: Routes = [ { path: ':badgeHash', component: AddonBadgesIssuedBadgePage, + data: { usesSwipeNavigation: true }, }, ], }, @@ -59,7 +61,6 @@ const routes: Routes = [ ], declarations: [ AddonBadgesUserBadgesPage, - AddonBadgesIssuedBadgePage, ], }) export class AddonBadgesLazyModule {} diff --git a/src/addons/badges/badges.module.ts b/src/addons/badges/badges.module.ts index e65429de8..149b428e5 100644 --- a/src/addons/badges/badges.module.ts +++ b/src/addons/badges/badges.module.ts @@ -40,6 +40,10 @@ export async function getBadgesServices(): Promise[]> { } const mainMenuRoutes: Routes = [ + { + path: 'badge', + loadChildren: () => import('./badge-lazy.module').then(m => m.AddonBadgeLazyModule), + }, { path: 'badges', loadChildren: () => import('./badges-lazy.module').then(m => m.AddonBadgesLazyModule), diff --git a/src/addons/badges/pages/issued-badge/issued-badge.html b/src/addons/badges/pages/issued-badge/issued-badge.html index 9686be66d..a4a2fb942 100644 --- a/src/addons/badges/pages/issued-badge/issued-badge.html +++ b/src/addons/badges/pages/issued-badge/issued-badge.html @@ -25,15 +25,14 @@ - - -

- {{ 'addon.badges.awardedto' | translate: {$a: user.fullname } }} -

-
-
- + + +

+ {{ 'addon.badges.awardedto' | translate: {$a: badge.recipientfullname } }} +

+
+
diff --git a/src/addons/badges/pages/issued-badge/issued-badge.ts b/src/addons/badges/pages/issued-badge/issued-badge.ts index 640b75e5e..ff2c0d376 100644 --- a/src/addons/badges/pages/issued-badge/issued-badge.ts +++ b/src/addons/badges/pages/issued-badge/issued-badge.ts @@ -16,7 +16,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreSites } from '@services/sites'; -import { CoreUser, CoreUserProfile } from '@features/user/services/user'; +import { CoreUser } from '@features/user/services/user'; import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges'; import { CoreUtils } from '@services/utils/utils'; import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses'; @@ -27,6 +27,7 @@ import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges- import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreTime } from '@singletons/time'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Page that displays the list of calendar events. @@ -34,6 +35,10 @@ import { CoreTime } from '@singletons/time'; @Component({ selector: 'page-addon-badges-issued-badge', templateUrl: 'issued-badge.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { @@ -42,10 +47,9 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { protected logView: (badge: AddonBadgesUserBadge) => void; courseId = 0; - user?: CoreUserProfile; course?: CoreEnrolledCourseData; badge?: AddonBadgesUserBadge; - badges: CoreSwipeNavigationItemsManager; + badges?: CoreSwipeNavigationItemsManager; badgeLoaded = false; currentTime = 0; @@ -54,12 +58,15 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getRequiredCurrentSite().getUserId(); this.badgeHash = CoreNavigator.getRouteParam('badgeHash') || ''; - const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( - AddonBadgesUserBadgesSource, - [this.courseId, this.userId], - ); + const routeData = CoreNavigator.getRouteData(this.route); + if (routeData.usesSwipeNavigation) { + const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource( + AddonBadgesUserBadgesSource, + [this.courseId, this.userId], + ); - this.badges = new CoreSwipeNavigationItemsManager(source); + this.badges = new CoreSwipeNavigationItemsManager(source); + } this.logView = CoreTime.once((badge) => { CoreAnalytics.logEvent({ @@ -80,14 +87,14 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { this.badgeLoaded = true; }); - this.badges.start(); + this.badges?.start(); } /** * @inheritdoc */ ngOnDestroy(): void { - this.badges.destroy(); + this.badges?.destroy(); } /** @@ -96,16 +103,29 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { * @returns Promise resolved when done. */ async fetchIssuedBadge(): Promise { + const site = CoreSites.getRequiredCurrentSite(); this.currentTime = CoreTimeUtils.timestamp(); - this.user = await CoreUser.getProfile(this.userId, this.courseId, true); - try { + // Search the badge in the user badges. const badges = await AddonBadges.getUserBadges(this.courseId, this.userId); - const badge = badges.find((badge) => this.badgeHash == badge.uniquehash); + let badge = badges.find((badge) => this.badgeHash == badge.uniquehash); - if (!badge) { - return; + if (badge) { + if (!site.isVersionGreaterEqualThan('4.5')) { + // Web service does not return the name of the recipient. + const user = await CoreUser.getProfile(this.userId, this.courseId, true); + badge.recipientfullname = user.fullname; + } + } else { + // The badge is awarded to another user, try to fetch the badge by hash. + if (site.isVersionGreaterEqualThan('4.5')) { + badge = await AddonBadges.getUserBadgeByHash(this.badgeHash); + } + if (!badge) { + // Should never happen. The app opens the badge in the browser if it can't be fetched. + throw new Error('Error getting badge data.'); + } } this.badge = badge; @@ -130,9 +150,10 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { * @param refresher Refresher. */ async refreshBadges(refresher?: HTMLIonRefresherElement): Promise { - await CoreUtils.ignoreErrors(Promise.all([ + await CoreUtils.allPromisesIgnoringErrors([ AddonBadges.invalidateUserBadges(this.courseId, this.userId), - ])); + AddonBadges.invalidateUserBadgeByHash(this.badgeHash), + ]); await CoreUtils.ignoreErrors(Promise.all([ this.fetchIssuedBadge(), diff --git a/src/addons/badges/services/badges-helper.ts b/src/addons/badges/services/badges-helper.ts new file mode 100644 index 000000000..9b3a27ed2 --- /dev/null +++ b/src/addons/badges/services/badges-helper.ts @@ -0,0 +1,55 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { makeSingleton } from '@singletons'; +import { AddonBadges } from './badges'; +import { CoreSites } from '@services/sites'; + +/** + * Helper service that provides some features for badges. + */ +@Injectable({ providedIn: 'root' }) +export class AddonBadgesHelperProvider { + + /** + * Return whether the badge can be opened in the app. + * + * @param badgeHash Badge hash. + * @param siteId Site ID. If not defined, current site. + * @returns Whether the badge can be opened in the app. + */ + async canOpenBadge(badgeHash: string, siteId?: string): Promise { + if (!AddonBadges.isPluginEnabled(siteId)) { + return false; + } + + const site = await CoreSites.getSite(siteId); + + if (site.isVersionGreaterEqualThan('4.5')) { + // The WS to fetch a badge by hash is available and it returns the name of the recipient. + return true; + } + + // Open in app if badge is one of the user badges. + const badges = await AddonBadges.getUserBadges(0, site.getUserId()); + const badge = badges.find((badge) => badgeHash == badge.uniquehash); + + return badge !== undefined; + } + +} + +export const AddonBadgesHelper = makeSingleton(AddonBadgesHelperProvider); diff --git a/src/addons/badges/services/badges.ts b/src/addons/badges/services/badges.ts index 2aed4eb22..6588d87f1 100644 --- a/src/addons/badges/services/badges.ts +++ b/src/addons/badges/services/badges.ts @@ -106,6 +106,58 @@ export class AddonBadgesProvider { await site.invalidateWsCacheForKey(this.getBadgesCacheKey(courseId, userId)); } + /** + * Get the cache key for the get badge by hash WS call. + * + * @param hash Badge issued hash. + * @returns Cache key. + */ + protected getUserBadgeByHashCacheKey(hash: string): string { + return ROOT_CACHE_KEY + 'badge:' + hash; + } + + /** + * Get issued badge by hash. + * + * @param hash Badge issued hash. + * @returns Promise to be resolved when the badge is retrieved. + * @since 4.5 with the recpient name, 4.3 without the recipient name. + */ + async getUserBadgeByHash(hash: string, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + const data: AddonBadgesGetUserBadgeByHashWSParams = { + hash, + }; + const preSets = { + cacheKey: this.getUserBadgeByHashCacheKey(hash), + updateFrequency: CoreSite.FREQUENCY_RARELY, + }; + + const response = await site.read( + 'core_badges_get_user_badge_by_hash', + data, + preSets, + ); + if (!response || !response.badge?.[0]) { + throw new CoreError('Invalid badge response'); + } + + return response.badge[0]; + } + + /** + * Invalidate get badge by hash WS call. + * + * @param hash Badge issued hash. + * @param siteId Site ID. If not defined, current site. + * @returns Promise resolved when data is invalidated. + */ + async invalidateUserBadgeByHash(hash: string, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getUserBadgeByHashCacheKey(hash)); + } + } export const AddonBadges = makeSingleton(AddonBadgesProvider); @@ -167,6 +219,8 @@ export type AddonBadgesUserBadge = { dateissued: number; // Date issued. dateexpire: number; // Date expire. visible?: number; // Visible. + recipientid?: number; // @since 4.5. Id of the awarded user. + recipientfullname?: string; // @since 4.5. Full name of the awarded user. email?: string; // @since 3.6. User email. version?: string; // @since 3.6. Version. language?: string; // @since 3.6. Language. @@ -211,3 +265,18 @@ export type AddonBadgesUserBadge = { type?: number; // Type. }[]; }; + +/** + * Params of core_badges_get_user_badge_by_hash WS. + */ +type AddonBadgesGetUserBadgeByHashWSParams = { + hash: string; // Badge issued hash. +}; + +/** + * Data returned by core_badges_get_user_badge_by_hash WS. + */ +type AddonBadgesGetUserBadgeByHashWSResponse = { + badge: AddonBadgesUserBadge[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addons/badges/services/handlers/badge-link.ts b/src/addons/badges/services/handlers/badge-link.ts index 52e41e02b..d45554cf0 100644 --- a/src/addons/badges/services/handlers/badge-link.ts +++ b/src/addons/badges/services/handlers/badge-link.ts @@ -18,7 +18,7 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreNavigator } from '@services/navigator'; import { makeSingleton } from '@singletons'; -import { AddonBadges } from '../badges'; +import { AddonBadgesHelper } from '../badges-helper'; /** * Handler to treat links to user participants page. @@ -36,7 +36,7 @@ export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerB return [{ action: async (siteId: string): Promise => { - await CoreNavigator.navigateToSitePath(`/badges/${params.hash}`, { siteId }); + await CoreNavigator.navigateToSitePath(`/badge/${params.hash}`, { siteId }); }, }]; } @@ -44,8 +44,8 @@ export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerB /** * @inheritdoc */ - isEnabled(siteId: string): Promise { - return AddonBadges.isPluginEnabled(siteId); + async isEnabled(siteId: string, url: string, params: Record): Promise { + return AddonBadgesHelper.canOpenBadge(params.hash, siteId); } } diff --git a/src/addons/badges/services/handlers/push-click.ts b/src/addons/badges/services/handlers/push-click.ts index 57281fc30..8d3dfc90a 100644 --- a/src/addons/badges/services/handlers/push-click.ts +++ b/src/addons/badges/services/handlers/push-click.ts @@ -20,6 +20,7 @@ import { AddonBadges } from '../badges'; import { makeSingleton } from '@singletons'; import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; import { CoreNavigator } from '@services/navigator'; +import { AddonBadgesHelper } from '../badges-helper'; /** * Handler for badges push notifications clicks. @@ -42,6 +43,10 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications if (CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' && (notification.name == 'badgerecipientnotice' || (notification.name == 'badgecreatornotice' && data.hash))) { + if (notification.customdata?.hash) { + return await AddonBadgesHelper.canOpenBadge(String(notification.customdata?.hash), notification.site); + } + return AddonBadges.isPluginEnabled(notification.site); } @@ -59,7 +64,7 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications if (data.hash) { // We have the hash, open the badge directly. - await CoreNavigator.navigateToSitePath(`/badges/${data.hash}`, { + await CoreNavigator.navigateToSitePath(`/badge/${data.hash}`, { siteId: notification.site, });