Merge pull request #2998 from dpalou/MOBILE-3902

MOBILE-3902 notifications: Warn admin if push are disabled
main
Pau Ferrer Ocaña 2021-11-17 09:47:13 +01:00 committed by GitHub
commit c5c662989b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 142 additions and 19 deletions

View File

@ -1063,6 +1063,7 @@
"addon.notifications.notificationpreferences": "message", "addon.notifications.notificationpreferences": "message",
"addon.notifications.notifications": "local_moodlemobileapp", "addon.notifications.notifications": "local_moodlemobileapp",
"addon.notifications.playsound": "local_moodlemobileapp", "addon.notifications.playsound": "local_moodlemobileapp",
"addon.notifications.pushdisabledwarning": "local_moodlemobileapp",
"addon.notifications.therearentnotificationsyet": "local_moodlemobileapp", "addon.notifications.therearentnotificationsyet": "local_moodlemobileapp",
"addon.notifications.unreadnotification": "message", "addon.notifications.unreadnotification": "message",
"addon.privatefiles.couldnotloadfiles": "local_moodlemobileapp", "addon.privatefiles.couldnotloadfiles": "local_moodlemobileapp",
@ -1667,6 +1668,7 @@
"core.fulllistofcourses": "moodle", "core.fulllistofcourses": "moodle",
"core.fullnameandsitename": "local_moodlemobileapp", "core.fullnameandsitename": "local_moodlemobileapp",
"core.fullscreen": "h5p", "core.fullscreen": "h5p",
"core.goto": "local_moodlemobileapp",
"core.grades.aggregatemean": "grades", "core.grades.aggregatemean": "grades",
"core.grades.aggregatesum": "grades", "core.grades.aggregatesum": "grades",
"core.grades.average": "grades", "core.grades.average": "grades",

View File

@ -4,6 +4,7 @@
"notificationpreferences": "Notification preferences", "notificationpreferences": "Notification preferences",
"notifications": "Notifications", "notifications": "Notifications",
"playsound": "Play sound", "playsound": "Play sound",
"pushdisabledwarning": "Your users are not receiving any notification from this site on their mobile devices. Enable mobile notifications in the Notification settings page.",
"therearentnotificationsyet": "There are no notifications.", "therearentnotificationsyet": "There are no notifications.",
"unreadnotification": "Unread notification: {{$a}}" "unreadnotification": "Unread notification: {{$a}}"
} }

View File

@ -26,7 +26,7 @@ import { AddonNotificationsCronHandler } from './services/handlers/cron';
import { AddonNotificationsPushClickHandler } from './services/handlers/push-click'; import { AddonNotificationsPushClickHandler } from './services/handlers/push-click';
import { AddonNotificationsSettingsHandler, AddonNotificationsSettingsHandlerService } from './services/handlers/settings'; import { AddonNotificationsSettingsHandler, AddonNotificationsSettingsHandlerService } from './services/handlers/settings';
import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing'; import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing';
import { AddonNotificationsProvider } from './services/notifications'; import { AddonNotifications, AddonNotificationsProvider } from './services/notifications';
import { AddonNotificationsHelperProvider } from './services/notifications-helper'; import { AddonNotificationsHelperProvider } from './services/notifications-helper';
export const ADDON_NOTIFICATIONS_SERVICES: Type<unknown>[] = [ export const ADDON_NOTIFICATIONS_SERVICES: Type<unknown>[] = [
@ -65,6 +65,7 @@ const preferencesRoutes: Routes = [
CoreSettingsDelegate.registerHandler(AddonNotificationsSettingsHandler.instance); CoreSettingsDelegate.registerHandler(AddonNotificationsSettingsHandler.instance);
AddonNotificationsMainMenuHandler.initialize(); AddonNotificationsMainMenuHandler.initialize();
AddonNotifications.initialize();
}, },
}, },
], ],

View File

@ -14,15 +14,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreWSExternalWarning } from '@services/ws'; import { CoreWSExternalWarning } from '@services/ws';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
import { CoreUser } from '@features/user/services/user'; import { CoreUser } from '@features/user/services/user';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { makeSingleton } from '@singletons'; import { makeSingleton, Translate } from '@singletons';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreEvents } from '@singletons/events';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
const ROOT_CACHE_KEY = 'mmaNotifications:'; const ROOT_CACHE_KEY = 'mmaNotifications:';
@ -43,6 +46,15 @@ export class AddonNotificationsProvider {
this.logger = CoreLogger.getInstance('AddonNotificationsProvider'); this.logger = CoreLogger.getInstance('AddonNotificationsProvider');
} }
/**
* Initialize the service.
*/
initialize(): void {
CoreEvents.on(CoreEvents.LOGIN, (data) => {
this.warnPushDisabledForAdmin(data.siteId);
});
}
/** /**
* Function to format notification data. * Function to format notification data.
* *
@ -115,16 +127,17 @@ export class AddonNotificationsProvider {
/** /**
* Get notification preferences. * Get notification preferences.
* *
* @param siteId Site ID. If not defined, use current site. * @param options Options.
* @return Promise resolved with the notification preferences. * @return Promise resolved with the notification preferences.
*/ */
async getNotificationPreferences(siteId?: string): Promise<AddonNotificationsPreferences> { async getNotificationPreferences(options: CoreSitesCommonWSOptions = {}): Promise<AddonNotificationsPreferences> {
this.logger.debug('Get notification preferences'); this.logger.debug('Get notification preferences');
const site = await CoreSites.getSite(siteId); const site = await CoreSites.getSite(options.siteId);
const preSets: CoreSiteWSPreSets = { const preSets: CoreSiteWSPreSets = {
cacheKey: this.getNotificationPreferencesCacheKey(), cacheKey: this.getNotificationPreferencesCacheKey(),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES, updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
}; };
const data = await site.read<AddonNotificationsGetUserNotificationPreferencesResult>( const data = await site.read<AddonNotificationsGetUserNotificationPreferencesResult>(
@ -382,6 +395,80 @@ export class AddonNotificationsProvider {
await site.invalidateWsCacheForKey(this.getNotificationsCacheKey()); await site.invalidateWsCacheForKey(this.getNotificationsCacheKey());
} }
/**
* Is user is an admin and push are disabled, notify him.
*
* @param siteId Site ID.
* @return Promise resolved when done.
*/
protected async warnPushDisabledForAdmin(siteId?: string): Promise<void> {
if (!siteId) {
return;
}
try {
const site = await CoreSites.getSite(siteId);
if (!site.getInfo()?.userissiteadmin) {
// Not an admin or we don't know, stop.
return;
}
// Check if the admin already asked not to be reminded.
const dontAsk = await site.getLocalSiteConfig('AddonNotificationsDontRemindPushDisabled', 0);
if (dontAsk) {
return;
}
// Check if push are disabled.
const preferences = await this.getNotificationPreferences({
readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
siteId,
});
const processor = preferences.processors.find(processor => processor.name === 'airnotifier');
if (processor) {
// Enabled.
return;
}
// Warn the admin.
const dontShowAgain = await CoreDomUtils.showPrompt(
Translate.instant('addon.notifications.pushdisabledwarning'),
undefined,
Translate.instant('core.dontshowagain'),
'checkbox',
[
{
text: Translate.instant('core.ok'),
},
{
text: Translate.instant('core.goto', { $a: Translate.instant('core.settings.settings') }),
handler: (data, resolve) => {
resolve(data[0]);
const url = CoreTextUtils.concatenatePaths(
site.getURL(),
site.isVersionGreaterEqualThan('3.11') ?
'message/output/airnotifier/checkconfiguration.php' :
'admin/message.php',
);
// Don't try auto-login, admins cannot use it.
CoreUtils.openInBrowser(url);
},
},
],
);
if (dontShowAgain) {
await site.setLocalSiteConfig('AddonNotificationsDontRemindPushDisabled', 1);
}
} catch {
// Ignore errors.
}
}
} }
export const AddonNotifications = makeSingleton(AddonNotificationsProvider); export const AddonNotifications = makeSingleton(AddonNotificationsProvider);

View File

@ -123,6 +123,7 @@
"fulllistofcourses": "All courses", "fulllistofcourses": "All courses",
"fullnameandsitename": "{{fullname}} ({{sitename}})", "fullnameandsitename": "{{fullname}} ({{sitename}})",
"fullscreen": "Fullscreen", "fullscreen": "Fullscreen",
"goto": "Go to {{$a}}",
"group": "Group", "group": "Group",
"groupsseparate": "Separate groups", "groupsseparate": "Separate groups",
"groupsvisible": "Visible groups", "groupsvisible": "Visible groups",

View File

@ -1478,7 +1478,7 @@ export class CoreDomUtilsProvider {
* @param header Modal header. * @param header Modal header.
* @param placeholderOrLabel Placeholder (for textual/numeric inputs) or label (for radio/checkbox). By default, "Password". * @param placeholderOrLabel Placeholder (for textual/numeric inputs) or label (for radio/checkbox). By default, "Password".
* @param type Type of the input element. By default, password. * @param type Type of the input element. By default, password.
* @param options More options to pass to the alert. * @param buttons Buttons. If not provided, OK and Cancel buttons will be displayed.
* @return Promise resolved with the input data (true for checkbox/radio) if the user clicks OK, rejected if cancels. * @return Promise resolved with the input data (true for checkbox/radio) if the user clicks OK, rejected if cancels.
*/ */
showPrompt( showPrompt(
@ -1486,12 +1486,25 @@ export class CoreDomUtilsProvider {
header?: string, header?: string,
placeholderOrLabel?: string, placeholderOrLabel?: string,
type: TextFieldTypes | 'checkbox' | 'radio' | 'textarea' = 'password', type: TextFieldTypes | 'checkbox' | 'radio' | 'textarea' = 'password',
buttons?: PromptButton[],
): Promise<any> { // eslint-disable-line @typescript-eslint/no-explicit-any ): Promise<any> { // eslint-disable-line @typescript-eslint/no-explicit-any
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
placeholderOrLabel = placeholderOrLabel ?? Translate.instant('core.login.password'); placeholderOrLabel = placeholderOrLabel ?? Translate.instant('core.login.password');
const isCheckbox = type === 'checkbox'; const isCheckbox = type === 'checkbox';
const isRadio = type === 'radio'; const isRadio = type === 'radio';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const resolvePromise = (data: any) => {
if (isCheckbox) {
resolve(data[0]);
} else if (isRadio) {
resolve(data);
} else {
resolve(data.promptinput);
}
};
const options: AlertOptions = { const options: AlertOptions = {
header, header,
message, message,
@ -1504,7 +1517,25 @@ export class CoreDomUtilsProvider {
value: (isCheckbox || isRadio) ? true : undefined, value: (isCheckbox || isRadio) ? true : undefined,
}, },
], ],
buttons: [ };
if (buttons?.length) {
options.buttons = buttons.map((button) => ({
...button,
handler: (data) => {
if (!button.handler) {
// Just resolve the promise.
resolvePromise(data);
return;
}
button.handler(data, resolve, reject);
},
}));
} else {
// Default buttons.
options.buttons = [
{ {
text: Translate.instant('core.cancel'), text: Translate.instant('core.cancel'),
role: 'cancel', role: 'cancel',
@ -1514,18 +1545,10 @@ export class CoreDomUtilsProvider {
}, },
{ {
text: Translate.instant('core.ok'), text: Translate.instant('core.ok'),
handler: (data) => { handler: resolvePromise,
if (isCheckbox) {
resolve(data[0]);
} else if (isRadio) {
resolve(data);
} else {
resolve(data.promptinput);
}
},
}, },
], ];
}; }
this.showAlertWithOptions(options); this.showAlertWithOptions(options);
}); });
@ -2045,3 +2068,11 @@ export type OpenModalOptions = ModalOptions & {
waitForDismissCompleted?: boolean; waitForDismissCompleted?: boolean;
closeOnNavigate?: boolean; // Default true. closeOnNavigate?: boolean; // Default true.
}; };
/**
* Buttons for prompt alert.
*/
export type PromptButton = Omit<AlertButton, 'handler'> & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler?: (value: any, resolve: (value: any) => void, reject: (reason: any) => void) => void;
};