Merge pull request #2998 from dpalou/MOBILE-3902
MOBILE-3902 notifications: Warn admin if push are disabledmain
commit
c5c662989b
|
@ -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",
|
||||||
|
|
|
@ -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}}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue