Merge pull request #3933 from dpalou/MOBILE-4313
MOBILE-4313 notifications: Show warnings if permissions missingmain
commit
b38223bb59
|
@ -43,11 +43,10 @@
|
||||||
"@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1",
|
"@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1",
|
||||||
"@moodlehq/cordova-plugin-file-opener": "4.0.0-moodle.1",
|
"@moodlehq/cordova-plugin-file-opener": "4.0.0-moodle.1",
|
||||||
"@moodlehq/cordova-plugin-file-transfer": "2.0.0-moodle.2",
|
"@moodlehq/cordova-plugin-file-transfer": "2.0.0-moodle.2",
|
||||||
"@moodlehq/cordova-plugin-camera": "6.0.0-moodle.2",
|
|
||||||
"@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3",
|
"@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.11",
|
"@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.12",
|
||||||
"@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5",
|
"@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5",
|
||||||
"@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1",
|
"@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1",
|
||||||
|
@ -7797,7 +7796,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@moodlehq/cordova-plugin-local-notification": {
|
"node_modules/@moodlehq/cordova-plugin-local-notification": {
|
||||||
"version": "0.9.0-moodle.11",
|
"version": "0.9.0-moodle.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@moodlehq/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-moodle.12.tgz",
|
||||||
|
"integrity": "sha512-gt6BhqsltCnNmk/CRUIDxTha/c1/UGTsh2d15zoUVeGaKYqE6olmIyN/HCAn+ofy7CA6DNHOdm1v0uqC3YbPZg==",
|
||||||
"engines": [
|
"engines": [
|
||||||
{
|
{
|
||||||
"name": "cordova",
|
"name": "cordova",
|
||||||
|
@ -7819,8 +7820,7 @@
|
||||||
"name": "apple-ios",
|
"name": "apple-ios",
|
||||||
"version": ">=10.0.0"
|
"version": ">=10.0.0"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"license": "Apache 2.0"
|
|
||||||
},
|
},
|
||||||
"node_modules/@moodlehq/cordova-plugin-qrscanner": {
|
"node_modules/@moodlehq/cordova-plugin-qrscanner": {
|
||||||
"version": "3.0.1-moodle.5",
|
"version": "3.0.1-moodle.5",
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
"@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3",
|
"@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.11",
|
"@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.12",
|
||||||
"@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5",
|
"@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5",
|
||||||
"@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3",
|
"@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3",
|
||||||
"@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1",
|
"@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1",
|
||||||
|
|
|
@ -1752,6 +1752,8 @@
|
||||||
"core.errorsyncblocked": "local_moodlemobileapp",
|
"core.errorsyncblocked": "local_moodlemobileapp",
|
||||||
"core.errorurlschemeinvalidscheme": "local_moodlemobileapp",
|
"core.errorurlschemeinvalidscheme": "local_moodlemobileapp",
|
||||||
"core.errorurlschemeinvalidsite": "local_moodlemobileapp",
|
"core.errorurlschemeinvalidsite": "local_moodlemobileapp",
|
||||||
|
"core.exactalarmsturnedoff": "local_moodlemobileapp",
|
||||||
|
"core.exactalarmsturnedoffmessage": "local_moodlemobileapp",
|
||||||
"core.expand": "moodle",
|
"core.expand": "moodle",
|
||||||
"core.explanationdigitalminor": "moodle",
|
"core.explanationdigitalminor": "moodle",
|
||||||
"core.favourites": "moodle",
|
"core.favourites": "moodle",
|
||||||
|
@ -2225,6 +2227,7 @@
|
||||||
"core.notenrolledprofile": "moodle",
|
"core.notenrolledprofile": "moodle",
|
||||||
"core.notice": "moodle",
|
"core.notice": "moodle",
|
||||||
"core.notingroup": "moodle",
|
"core.notingroup": "moodle",
|
||||||
|
"core.notnow": "local_moodlemobileapp",
|
||||||
"core.notsent": "local_moodlemobileapp",
|
"core.notsent": "local_moodlemobileapp",
|
||||||
"core.now": "moodle",
|
"core.now": "moodle",
|
||||||
"core.nummore": "local_moodlemobileapp",
|
"core.nummore": "local_moodlemobileapp",
|
||||||
|
@ -2499,6 +2502,10 @@
|
||||||
"core.today": "moodle",
|
"core.today": "moodle",
|
||||||
"core.toggledelete": "local_moodlemobileapp",
|
"core.toggledelete": "local_moodlemobileapp",
|
||||||
"core.tryagain": "local_moodlemobileapp",
|
"core.tryagain": "local_moodlemobileapp",
|
||||||
|
"core.turnon": "local_moodlemobileapp",
|
||||||
|
"core.turnonexactalarms": "local_moodlemobileapp",
|
||||||
|
"core.turnonnotifications": "local_moodlemobileapp",
|
||||||
|
"core.turnonnotificationsmessage": "local_moodlemobileapp",
|
||||||
"core.twoparagraphs": "local_moodlemobileapp",
|
"core.twoparagraphs": "local_moodlemobileapp",
|
||||||
"core.type": "repository",
|
"core.type": "repository",
|
||||||
"core.uhoh": "local_moodlemobileapp",
|
"core.uhoh": "local_moodlemobileapp",
|
||||||
|
|
|
@ -120,6 +120,23 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-card class="core-warning-card core-card-with-buttons"
|
||||||
|
*ngIf="remindersEnabled && event && !canScheduleExactAlarms && !scheduleExactWarningHidden">
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
|
<ion-label>
|
||||||
|
<p><strong>{{ 'core.exactalarmsturnedoff' | translate }}</strong></p>
|
||||||
|
<p>{{ 'core.exactalarmsturnedoffmessage' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<div class="core-card-buttons">
|
||||||
|
<ion-button fill="clear" (click)="hideAlarmWarning()">
|
||||||
|
{{ 'core.dontshowagain' | translate | coreNoPeriod }}
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="outline" (click)="openAlarmSettings()">{{ 'core.turnon' | translate }}</ion-button>
|
||||||
|
</div>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<ion-card *ngIf="remindersEnabled && event">
|
<ion-card *ngIf="remindersEnabled && event">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
|
|
@ -40,6 +40,9 @@ import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-sourc
|
||||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders';
|
import { CoreReminders, CoreRemindersService } from '@features/reminders/services/reminders';
|
||||||
import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu';
|
import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu';
|
||||||
|
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||||
|
import { CorePlatform } from '@services/platform';
|
||||||
|
import { CoreConfig } from '@services/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a single calendar event.
|
* Page that displays a single calendar event.
|
||||||
|
@ -61,6 +64,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
protected defaultTimeChangedObserver: CoreEventObserver;
|
protected defaultTimeChangedObserver: CoreEventObserver;
|
||||||
protected currentSiteId: string;
|
protected currentSiteId: string;
|
||||||
protected updateCurrentTime?: number;
|
protected updateCurrentTime?: number;
|
||||||
|
protected appResumeSubscription: Subscription;
|
||||||
|
|
||||||
eventLoaded = false;
|
eventLoaded = false;
|
||||||
event?: AddonCalendarEventToDisplay;
|
event?: AddonCalendarEventToDisplay;
|
||||||
|
@ -78,6 +82,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
hasOffline = false;
|
hasOffline = false;
|
||||||
isOnline = false;
|
isOnline = false;
|
||||||
syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
|
syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
|
||||||
|
canScheduleExactAlarms = true;
|
||||||
|
scheduleExactWarningHidden = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
|
@ -138,6 +144,11 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.updateCurrentTime = window.setInterval(() => {
|
this.updateCurrentTime = window.setInterval(() => {
|
||||||
this.currentTime = CoreTimeUtils.timestamp();
|
this.currentTime = CoreTimeUtils.timestamp();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
this.checkExactAlarms();
|
||||||
|
this.appResumeSubscription = CorePlatform.resume.subscribe(() => {
|
||||||
|
this.checkExactAlarms();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,6 +164,14 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.reminders = await AddonCalendarHelper.getEventReminders(this.eventId, this.event.timestart, this.currentSiteId);
|
this.reminders = await AddonCalendarHelper.getEventReminders(this.eventId, this.event.timestart, this.currentSiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the app can schedule exact alarms.
|
||||||
|
*/
|
||||||
|
protected async checkExactAlarms(): Promise<void> {
|
||||||
|
this.scheduleExactWarningHidden = !!(await CoreConfig.get(CoreConstants.DONT_SHOW_EXACT_ALARMS_WARNING, 0));
|
||||||
|
this.canScheduleExactAlarms = await CoreLocalNotifications.canScheduleExactAlarms();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -616,6 +635,21 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open alarm settings.
|
||||||
|
*/
|
||||||
|
openAlarmSettings(): void {
|
||||||
|
CoreLocalNotifications.openAlarmSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide alarm warning.
|
||||||
|
*/
|
||||||
|
hideAlarmWarning(): void {
|
||||||
|
CoreConfig.set(CoreConstants.DONT_SHOW_EXACT_ALARMS_WARNING, 1);
|
||||||
|
this.scheduleExactWarningHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -626,6 +660,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.onlineObserver.unsubscribe();
|
this.onlineObserver.unsubscribe();
|
||||||
this.newEventObserver.off();
|
this.newEventObserver.off();
|
||||||
this.events?.destroy();
|
this.events?.destroy();
|
||||||
|
this.appResumeSubscription.unsubscribe();
|
||||||
clearInterval(this.updateCurrentTime);
|
clearInterval(this.updateCurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,23 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}" />
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}" />
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="notifications.loaded">
|
<core-loading [hideUntil]="notifications.loaded">
|
||||||
|
<ion-card class="core-warning-card core-card-with-buttons" *ngIf="!hasNotificationsPermission && !permissionWarningHidden">
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
|
<ion-label>
|
||||||
|
<p><strong>{{ 'core.turnonnotifications' | translate }}</strong></p>
|
||||||
|
<p>{{ 'core.turnonnotificationsmessage' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<div class="core-card-buttons">
|
||||||
|
<ion-button fill="clear" (click)="hidePermissionWarning()">
|
||||||
|
{{ 'core.dontshowagain' | translate | coreNoPeriod }}
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="outline" (click)="openSettings()">{{ 'core.turnon' | translate }}</ion-button>
|
||||||
|
</div>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<ion-item *ngFor="let notification of notifications.items" class="ion-text-wrap"
|
<ion-item *ngFor="let notification of notifications.items" class="ion-text-wrap addon-notification-item"
|
||||||
[attr.aria-current]="notifications.getItemAriaCurrent(notification)" (click)="notifications.select(notification)" button
|
[attr.aria-current]="notifications.getItemAriaCurrent(notification)" (click)="notifications.select(notification)" button
|
||||||
[detail]="false" lines="full">
|
[detail]="false" lines="full">
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@use "theme/globals" as *;
|
@use "theme/globals" as *;
|
||||||
|
|
||||||
ion-item {
|
ion-item.addon-notification-item {
|
||||||
ion-label {
|
ion-label {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
|
@ -31,6 +31,10 @@ import { CoreTimeUtils } from '@services/utils/time';
|
||||||
import { AddonNotificationsNotificationsSource } from '@addons/notifications/classes/notifications-source';
|
import { AddonNotificationsNotificationsSource } from '@addons/notifications/classes/notifications-source';
|
||||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
import { AddonLegacyNotificationsNotificationsSource } from '@addons/notifications/classes/legacy-notifications-source';
|
import { AddonLegacyNotificationsNotificationsSource } from '@addons/notifications/classes/legacy-notifications-source';
|
||||||
|
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||||
|
import { CoreConfig } from '@services/config';
|
||||||
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { CorePlatform } from '@services/platform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of notifications.
|
* Page that displays the list of notifications.
|
||||||
|
@ -47,12 +51,15 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy {
|
||||||
fetchMoreNotificationsFailed = false;
|
fetchMoreNotificationsFailed = false;
|
||||||
canMarkAllNotificationsAsRead = false;
|
canMarkAllNotificationsAsRead = false;
|
||||||
loadingMarkAllNotificationsAsRead = false;
|
loadingMarkAllNotificationsAsRead = false;
|
||||||
|
hasNotificationsPermission = true;
|
||||||
|
permissionWarningHidden = false;
|
||||||
|
|
||||||
protected isCurrentView?: boolean;
|
protected isCurrentView?: boolean;
|
||||||
protected cronObserver?: CoreEventObserver;
|
protected cronObserver?: CoreEventObserver;
|
||||||
protected readObserver?: CoreEventObserver;
|
protected readObserver?: CoreEventObserver;
|
||||||
protected pushObserver?: Subscription;
|
protected pushObserver?: Subscription;
|
||||||
protected pendingRefresh = false;
|
protected pendingRefresh = false;
|
||||||
|
protected appResumeSubscription?: Subscription;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
try {
|
try {
|
||||||
|
@ -67,7 +74,14 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy {
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
CoreNavigator.back();
|
CoreNavigator.back();
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkPermission();
|
||||||
|
this.appResumeSubscription = CorePlatform.resume.subscribe(() => {
|
||||||
|
this.checkPermission();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,6 +134,14 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy {
|
||||||
deepLinkManager.treatLink();
|
deepLinkManager.treatLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the app has permission to display notifications.
|
||||||
|
*/
|
||||||
|
protected async checkPermission(): Promise<void> {
|
||||||
|
this.permissionWarningHidden = !!(await CoreConfig.get(CoreConstants.DONT_SHOW_NOTIFICATIONS_PERMISSION_WARNING, 0));
|
||||||
|
this.hasNotificationsPermission = await CoreLocalNotifications.hasNotificationsPermission();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to get notifications. Gets unread notifications first.
|
* Convenience function to get notifications. Gets unread notifications first.
|
||||||
*
|
*
|
||||||
|
@ -211,6 +233,21 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy {
|
||||||
refresher?.complete();
|
refresher?.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open notification settings.
|
||||||
|
*/
|
||||||
|
openSettings(): void {
|
||||||
|
CoreLocalNotifications.openNotificationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide permission warning.
|
||||||
|
*/
|
||||||
|
hidePermissionWarning(): void {
|
||||||
|
CoreConfig.set(CoreConstants.DONT_SHOW_NOTIFICATIONS_PERMISSION_WARNING, 1);
|
||||||
|
this.permissionWarningHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User entered the page.
|
* User entered the page.
|
||||||
*/
|
*/
|
||||||
|
@ -241,6 +278,7 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy {
|
||||||
this.readObserver?.off();
|
this.readObserver?.off();
|
||||||
this.pushObserver?.unsubscribe();
|
this.pushObserver?.unsubscribe();
|
||||||
this.notifications?.destroy();
|
this.notifications?.destroy();
|
||||||
|
this.appResumeSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,9 @@ export class CoreConstants {
|
||||||
|
|
||||||
// Other constants.
|
// Other constants.
|
||||||
static readonly CALENDAR_DEFAULT_STARTING_WEEKDAY = 1;
|
static readonly CALENDAR_DEFAULT_STARTING_WEEKDAY = 1;
|
||||||
|
static readonly DONT_SHOW_NOTIFICATIONS_PERMISSION_WARNING = 'CoreDontShowNotificationsPermissionWarning';
|
||||||
|
static readonly DONT_SHOW_EXACT_ALARMS_WARNING = 'CoreDontShowScheduleExactWarning';
|
||||||
|
static readonly EXACT_ALARMS_WARNING_DISPLAYED = 'CoreScheduleExactWarningModalDisplayed';
|
||||||
|
|
||||||
// Config & environment constants.
|
// Config & environment constants.
|
||||||
static readonly CONFIG = { ...envJson.config } as unknown as EnvironmentConfig; // Data parsed from config.json files.
|
static readonly CONFIG = { ...envJson.config } as unknown as EnvironmentConfig; // Data parsed from config.json files.
|
||||||
|
|
|
@ -124,6 +124,8 @@
|
||||||
"errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
"errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
|
||||||
"errorurlschemeinvalidscheme": "This URL is meant to be used in another app: {{$a}}.",
|
"errorurlschemeinvalidscheme": "This URL is meant to be used in another app: {{$a}}.",
|
||||||
"errorurlschemeinvalidsite": "This site URL cannot be opened in this app.",
|
"errorurlschemeinvalidsite": "This site URL cannot be opened in this app.",
|
||||||
|
"exactalarmsturnedoff": "Real-time notifications are turned off",
|
||||||
|
"exactalarmsturnedoffmessage": "To make sure you don't miss any important alerts, turn on 'Alarms and reminders' in your device's settings.",
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
"explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
|
||||||
"favourites": "Starred",
|
"favourites": "Starred",
|
||||||
|
@ -225,6 +227,7 @@
|
||||||
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
"notenrolledprofile": "This profile is not available because this user is not enrolled in this course.",
|
||||||
"notice": "Notice",
|
"notice": "Notice",
|
||||||
"notingroup": "Sorry, but you need to be part of a group to see this page.",
|
"notingroup": "Sorry, but you need to be part of a group to see this page.",
|
||||||
|
"notnow": "Not now",
|
||||||
"notsent": "Not sent",
|
"notsent": "Not sent",
|
||||||
"now": "now",
|
"now": "now",
|
||||||
"nummore": "{{$a}} more",
|
"nummore": "{{$a}} more",
|
||||||
|
@ -333,6 +336,10 @@
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"toggledelete": "Toggle delete buttons",
|
"toggledelete": "Toggle delete buttons",
|
||||||
"tryagain": "Try again",
|
"tryagain": "Try again",
|
||||||
|
"turnon": "Turn on",
|
||||||
|
"turnonexactalarms": "Turn on real-time alerts",
|
||||||
|
"turnonnotifications": "Turn on notifications",
|
||||||
|
"turnonnotificationsmessage": "Would you like to receive notifications about activities and assignments?",
|
||||||
"twoparagraphs": "{{p1}}<br><br>{{p2}}",
|
"twoparagraphs": "{{p1}}<br><br>{{p2}}",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"uhoh": "Uh oh!",
|
"uhoh": "Uh oh!",
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (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 { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe to remove period at the end of a text if present.
|
||||||
|
*/
|
||||||
|
@Pipe({
|
||||||
|
name: 'coreNoPeriod',
|
||||||
|
})
|
||||||
|
export class CoreNoPeriodPipe implements PipeTransform {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a text and removes ending period.
|
||||||
|
*
|
||||||
|
* @param text The text to treat.
|
||||||
|
* @returns Treated text.
|
||||||
|
*/
|
||||||
|
transform(text: string): string {
|
||||||
|
if (!text) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.trim().replace(/\.$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import { CoreFormatDatePipe } from './format-date';
|
||||||
import { CoreNoTagsPipe } from './no-tags';
|
import { CoreNoTagsPipe } from './no-tags';
|
||||||
import { CoreSecondsToHMSPipe } from './seconds-to-hms';
|
import { CoreSecondsToHMSPipe } from './seconds-to-hms';
|
||||||
import { CoreTimeAgoPipe } from './time-ago';
|
import { CoreTimeAgoPipe } from './time-ago';
|
||||||
|
import { CoreNoPeriodPipe } from './no-period';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -30,6 +31,7 @@ import { CoreTimeAgoPipe } from './time-ago';
|
||||||
CoreDateDayOrTimePipe,
|
CoreDateDayOrTimePipe,
|
||||||
CoreDurationPipe,
|
CoreDurationPipe,
|
||||||
CoreFormatDatePipe,
|
CoreFormatDatePipe,
|
||||||
|
CoreNoPeriodPipe,
|
||||||
CoreNoTagsPipe,
|
CoreNoTagsPipe,
|
||||||
CoreSecondsToHMSPipe,
|
CoreSecondsToHMSPipe,
|
||||||
CoreTimeAgoPipe,
|
CoreTimeAgoPipe,
|
||||||
|
@ -41,6 +43,7 @@ import { CoreTimeAgoPipe } from './time-ago';
|
||||||
CoreDurationPipe,
|
CoreDurationPipe,
|
||||||
CoreFormatDatePipe,
|
CoreFormatDatePipe,
|
||||||
CoreNoTagsPipe,
|
CoreNoTagsPipe,
|
||||||
|
CoreNoPeriodPipe,
|
||||||
CoreSecondsToHMSPipe,
|
CoreSecondsToHMSPipe,
|
||||||
CoreTimeAgoPipe,
|
CoreTimeAgoPipe,
|
||||||
],
|
],
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { Push } from '@features/native/plugins';
|
||||||
import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance';
|
import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance';
|
||||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||||
|
import { CoreDomUtils } from './utils/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle local notifications.
|
* Service to handle local notifications.
|
||||||
|
@ -119,6 +120,41 @@ export class CoreLocalNotificationsProvider {
|
||||||
this.cancelSiteNotifications(site.id);
|
this.cancelSiteNotifications(site.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CoreEvents.on(CoreEvents.LOGIN, async () => {
|
||||||
|
const [hasNotificationsPermission, canScheduleExact] = await Promise.all([
|
||||||
|
this.hasNotificationsPermission(),
|
||||||
|
this.canScheduleExactAlarms(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!hasNotificationsPermission || canScheduleExact) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dontShowWarning = await CoreConfig.get(CoreConstants.EXACT_ALARMS_WARNING_DISPLAYED, 0);
|
||||||
|
if (dontShowWarning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreDomUtils.showAlertWithOptions({
|
||||||
|
header: Translate.instant('core.turnonexactalarms'),
|
||||||
|
message: Translate.instant('core.exactalarmsturnedoffmessage'),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: Translate.instant('core.notnow'),
|
||||||
|
role: 'cancel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: Translate.instant('core.turnon'),
|
||||||
|
handler: (): void => {
|
||||||
|
this.openAlarmSettings();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
CoreConfig.set(CoreConstants.EXACT_ALARMS_WARNING_DISPLAYED, 1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,6 +199,40 @@ export class CoreLocalNotificationsProvider {
|
||||||
this.triggeredTable.setInstance(triggeredTable);
|
this.triggeredTable.setInstance(triggeredTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the app has the permission to display notifications.
|
||||||
|
*
|
||||||
|
* @returns Whether has notifications permission.
|
||||||
|
*/
|
||||||
|
async hasNotificationsPermission(): Promise<boolean> {
|
||||||
|
if (!CorePlatform.isMobile()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalNotifications.hasPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the app can schedule exact alarms.
|
||||||
|
*
|
||||||
|
* @returns Whether can schedule exact alarms.
|
||||||
|
*/
|
||||||
|
async canScheduleExactAlarms(): Promise<boolean> {
|
||||||
|
if (!CorePlatform.isAndroid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugin = this.getCordovaPlugin();
|
||||||
|
if (!plugin || !plugin.canScheduleExactAlarms) {
|
||||||
|
// Cannot check, assume it's enabled.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
plugin.canScheduleExactAlarms(canSchedule => resolve(canSchedule));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel a local notification.
|
* Cancel a local notification.
|
||||||
*
|
*
|
||||||
|
@ -370,11 +440,18 @@ export class CoreLocalNotificationsProvider {
|
||||||
* @returns Whether local notifications plugin is available.
|
* @returns Whether local notifications plugin is available.
|
||||||
*/
|
*/
|
||||||
isPluginAvailable(): boolean {
|
isPluginAvailable(): boolean {
|
||||||
const win = <any> window; // eslint-disable-line @typescript-eslint/no-explicit-any
|
return !!this.getCordovaPlugin() && CorePlatform.is('cordova');
|
||||||
|
}
|
||||||
|
|
||||||
const enabled = !!win.cordova?.plugins?.notification?.local;
|
/**
|
||||||
|
* Get the Cordova plugin object.
|
||||||
return enabled && CorePlatform.is('cordova');
|
*
|
||||||
|
* @returns Cordova plugin, undefined if not found.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
protected getCordovaPlugin(): any | undefined {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (<any> window).cordova?.plugins?.notification?.local;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -740,6 +817,28 @@ export class CoreLocalNotificationsProvider {
|
||||||
await this.componentsTable.update({ id: newId }, { id: oldId });
|
await this.componentsTable.update({ id: newId }, { id: oldId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open notification settings.
|
||||||
|
*/
|
||||||
|
openNotificationSettings(): void {
|
||||||
|
if (!CorePlatform.isMobile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCordovaPlugin()?.openNotificationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open alarm settings (Android only).
|
||||||
|
*/
|
||||||
|
openAlarmSettings(): void {
|
||||||
|
if (!CorePlatform.isAndroid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCordovaPlugin()?.openAlarmSettings();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreLocalNotifications = makeSingleton(CoreLocalNotificationsProvider);
|
export const CoreLocalNotifications = makeSingleton(CoreLocalNotificationsProvider);
|
||||||
|
|
|
@ -931,6 +931,25 @@ ion-card {
|
||||||
ion-card-title {
|
ion-card-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.core-card-with-buttons .item ion-label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-card-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
@include margin(0, 8px, 8px, 8px);
|
||||||
|
|
||||||
|
ion-button {
|
||||||
|
text-transform: none;
|
||||||
|
|
||||||
|
&[fill="outline"] {
|
||||||
|
--background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-course-module-handler:not(.addon-mod-label-handler) .item-heading .filter_mathjaxloader_equation div {
|
.core-course-module-handler:not(.addon-mod-label-handler) .item-heading .filter_mathjaxloader_equation div {
|
||||||
|
|
Loading…
Reference in New Issue