MOBILE-3909 calendar: Change reminders to time before
parent
4fa1d72a2b
commit
47c913f434
|
@ -140,6 +140,7 @@
|
||||||
"addon.calendar.sunday": "calendar",
|
"addon.calendar.sunday": "calendar",
|
||||||
"addon.calendar.thu": "calendar",
|
"addon.calendar.thu": "calendar",
|
||||||
"addon.calendar.thursday": "calendar",
|
"addon.calendar.thursday": "calendar",
|
||||||
|
"addon.calendar.timebefore": "local_moodlemobileapp",
|
||||||
"addon.calendar.today": "calendar",
|
"addon.calendar.today": "calendar",
|
||||||
"addon.calendar.tomorrow": "calendar",
|
"addon.calendar.tomorrow": "calendar",
|
||||||
"addon.calendar.tue": "calendar",
|
"addon.calendar.tue": "calendar",
|
||||||
|
@ -153,6 +154,7 @@
|
||||||
"addon.calendar.typeopen": "calendar",
|
"addon.calendar.typeopen": "calendar",
|
||||||
"addon.calendar.typesite": "calendar",
|
"addon.calendar.typesite": "calendar",
|
||||||
"addon.calendar.typeuser": "calendar",
|
"addon.calendar.typeuser": "calendar",
|
||||||
|
"addon.calendar.units": "qtype_numerical",
|
||||||
"addon.calendar.upcomingevents": "calendar",
|
"addon.calendar.upcomingevents": "calendar",
|
||||||
"addon.calendar.userevents": "calendar",
|
"addon.calendar.userevents": "calendar",
|
||||||
"addon.calendar.wed": "calendar",
|
"addon.calendar.wed": "calendar",
|
||||||
|
@ -1562,6 +1564,7 @@
|
||||||
"core.courses.therearecourses": "moodle",
|
"core.courses.therearecourses": "moodle",
|
||||||
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
||||||
"core.currentdevice": "local_moodlemobileapp",
|
"core.currentdevice": "local_moodlemobileapp",
|
||||||
|
"core.custom": "form",
|
||||||
"core.datastoredoffline": "local_moodlemobileapp",
|
"core.datastoredoffline": "local_moodlemobileapp",
|
||||||
"core.date": "moodle",
|
"core.date": "moodle",
|
||||||
"core.day": "moodle",
|
"core.day": "moodle",
|
||||||
|
@ -1943,6 +1946,8 @@
|
||||||
"core.maxsizeandattachments": "moodle",
|
"core.maxsizeandattachments": "moodle",
|
||||||
"core.min": "moodle",
|
"core.min": "moodle",
|
||||||
"core.mins": "moodle",
|
"core.mins": "moodle",
|
||||||
|
"core.minute": "moodle",
|
||||||
|
"core.minutes": "moodle",
|
||||||
"core.misc": "admin",
|
"core.misc": "admin",
|
||||||
"core.mod_assign": "assign/pluginname",
|
"core.mod_assign": "assign/pluginname",
|
||||||
"core.mod_assignment": "assignment/pluginname",
|
"core.mod_assignment": "assignment/pluginname",
|
||||||
|
@ -2283,6 +2288,8 @@
|
||||||
"core.viewprofile": "moodle",
|
"core.viewprofile": "moodle",
|
||||||
"core.warningofflinedatadeleted": "local_moodlemobileapp",
|
"core.warningofflinedatadeleted": "local_moodlemobileapp",
|
||||||
"core.warnopeninbrowser": "local_moodlemobileapp",
|
"core.warnopeninbrowser": "local_moodlemobileapp",
|
||||||
|
"core.week": "moodle",
|
||||||
|
"core.weeks": "moodle",
|
||||||
"core.whatisyourage": "moodle",
|
"core.whatisyourage": "moodle",
|
||||||
"core.wheredoyoulive": "moodle",
|
"core.wheredoyoulive": "moodle",
|
||||||
"core.whoissiteadmin": "local_moodlemobileapp",
|
"core.whoissiteadmin": "local_moodlemobileapp",
|
||||||
|
|
|
@ -19,12 +19,14 @@ import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { AddonCalendarCalendarComponent } from './calendar/calendar';
|
import { AddonCalendarCalendarComponent } from './calendar/calendar';
|
||||||
import { AddonCalendarUpcomingEventsComponent } from './upcoming-events/upcoming-events';
|
import { AddonCalendarUpcomingEventsComponent } from './upcoming-events/upcoming-events';
|
||||||
import { AddonCalendarFilterPopoverComponent } from './filter/filter';
|
import { AddonCalendarFilterPopoverComponent } from './filter/filter';
|
||||||
|
import { AddonCalendarReminderTimeModalComponent } from './reminder-time-modal/reminder-time-modal';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AddonCalendarCalendarComponent,
|
AddonCalendarCalendarComponent,
|
||||||
AddonCalendarUpcomingEventsComponent,
|
AddonCalendarUpcomingEventsComponent,
|
||||||
AddonCalendarFilterPopoverComponent,
|
AddonCalendarFilterPopoverComponent,
|
||||||
|
AddonCalendarReminderTimeModalComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
|
@ -35,6 +37,7 @@ import { AddonCalendarFilterPopoverComponent } from './filter/filter';
|
||||||
AddonCalendarCalendarComponent,
|
AddonCalendarCalendarComponent,
|
||||||
AddonCalendarUpcomingEventsComponent,
|
AddonCalendarUpcomingEventsComponent,
|
||||||
AddonCalendarFilterPopoverComponent,
|
AddonCalendarFilterPopoverComponent,
|
||||||
|
AddonCalendarReminderTimeModalComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonCalendarComponentsModule {}
|
export class AddonCalendarComponentsModule {}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<h2>{{ 'addon.calendar.reminders' | translate }}</h2>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-radio-group [(ngModel)]="radioValue" class="ion-text-wrap">
|
||||||
|
<!-- Preset options. -->
|
||||||
|
<ion-item *ngIf="allowDisable">
|
||||||
|
<ion-label>
|
||||||
|
<p>{{ 'core.settings.disabled' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-radio slot="end" value="disabled"></ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngFor="let option of presetOptions">
|
||||||
|
<ion-label>
|
||||||
|
<p>{{ option.label }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-radio slot="end" [value]="option.radioValue"></ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<!-- Custom value. -->
|
||||||
|
<ion-item lines="none" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p>{{ 'core.custom' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
<ion-radio slot="end" value="custom"></ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-label></ion-label>
|
||||||
|
|
||||||
|
<div class="flex-row">
|
||||||
|
<!-- Input to enter the value. -->
|
||||||
|
<ion-input type="number" name="customvalue" [(ngModel)]="customValue" [disabled]="radioValue != 'custom'"
|
||||||
|
placeholder="10">
|
||||||
|
</ion-input>
|
||||||
|
|
||||||
|
<!-- Units. -->
|
||||||
|
<label class="accesshide" for="reminderUnits">{{ 'addon.calendar.units' | translate }}</label>
|
||||||
|
<ion-select id="reminderUnits" name="customunits" [(ngModel)]="customUnits" interface="action-sheet"
|
||||||
|
[disabled]="radioValue != 'custom'" slot="end"
|
||||||
|
[interfaceOptions]="{header: 'addon.calendar.units' | translate}">
|
||||||
|
<ion-select-option *ngFor="let option of customUnitsOptions" [value]="option.value">
|
||||||
|
{{ option.label | translate }}
|
||||||
|
</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</div>
|
||||||
|
</ion-item>
|
||||||
|
</ion-radio-group>
|
||||||
|
|
||||||
|
<ion-button class="ion-margin" expand="block" (click)="saveReminder()" [disabled]="radioValue == 'custom' && !customValue">
|
||||||
|
{{ 'core.done' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,156 @@
|
||||||
|
// (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 { AddonCalendar, AddonCalendarReminderUnits, AddonCalendarValueAndUnit } from '@addons/calendar/services/calendar';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { ModalController } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal to choose a reminder time.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-calendar-new-reminder-modal',
|
||||||
|
templateUrl: 'reminder-time-modal.html',
|
||||||
|
})
|
||||||
|
export class AddonCalendarReminderTimeModalComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() initialValue?: AddonCalendarValueAndUnit;
|
||||||
|
@Input() allowDisable?: boolean;
|
||||||
|
|
||||||
|
radioValue = '5m';
|
||||||
|
customValue = '10';
|
||||||
|
customUnits = AddonCalendarReminderUnits.MINUTE;
|
||||||
|
|
||||||
|
presetOptions = [
|
||||||
|
{
|
||||||
|
radioValue: '5m',
|
||||||
|
value: 5,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radioValue: '10m',
|
||||||
|
value: 10,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radioValue: '30m',
|
||||||
|
value: 30,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radioValue: '1h',
|
||||||
|
value: 1,
|
||||||
|
unit: AddonCalendarReminderUnits.HOUR,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radioValue: '12h',
|
||||||
|
value: 12,
|
||||||
|
unit: AddonCalendarReminderUnits.HOUR,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
radioValue: '1d',
|
||||||
|
value: 1,
|
||||||
|
unit: AddonCalendarReminderUnits.DAY,
|
||||||
|
label: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
customUnitsOptions = [
|
||||||
|
{
|
||||||
|
value: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
label: 'core.minutes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: AddonCalendarReminderUnits.HOUR,
|
||||||
|
label: 'core.hours',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: AddonCalendarReminderUnits.DAY,
|
||||||
|
label: 'core.days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: AddonCalendarReminderUnits.WEEK,
|
||||||
|
label: 'core.weeks',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.presetOptions.forEach((option) => {
|
||||||
|
option.label = AddonCalendar.getUnitValueLabel(option.value, option.unit);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.initialValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.initialValue.value === 0) {
|
||||||
|
this.radioValue = 'disabled';
|
||||||
|
} else {
|
||||||
|
// Search if it's one of the preset options.
|
||||||
|
const option = this.presetOptions.find(option =>
|
||||||
|
option.value === this.initialValue?.value && option.unit === this.initialValue.unit);
|
||||||
|
|
||||||
|
if (option) {
|
||||||
|
this.radioValue = option.radioValue;
|
||||||
|
} else {
|
||||||
|
// It's a custom value.
|
||||||
|
this.radioValue = 'custom';
|
||||||
|
this.customValue = String(this.initialValue.value);
|
||||||
|
this.customUnits = this.initialValue.unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the reminder.
|
||||||
|
*/
|
||||||
|
saveReminder(): void {
|
||||||
|
if (this.radioValue === 'disabled') {
|
||||||
|
ModalController.dismiss(0);
|
||||||
|
} else if (this.radioValue === 'custom') {
|
||||||
|
const value = parseInt(this.customValue, 10);
|
||||||
|
if (!value) {
|
||||||
|
CoreDomUtils.showErrorModal('core.errorinvalidform', true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalController.dismiss(Math.abs(value) * this.customUnits);
|
||||||
|
} else {
|
||||||
|
const option = this.presetOptions.find(option => option.radioValue === this.radioValue);
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalController.dismiss(option.unit * option.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,6 +41,7 @@
|
||||||
"noevents": "There are no events",
|
"noevents": "There are no events",
|
||||||
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event.",
|
"nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event.",
|
||||||
"reminders": "Reminders",
|
"reminders": "Reminders",
|
||||||
|
"units": "Units",
|
||||||
"repeatedevents": "Repeated events",
|
"repeatedevents": "Repeated events",
|
||||||
"repeateditall": "Also apply changes to the other {{$a}} events in this repeat series",
|
"repeateditall": "Also apply changes to the other {{$a}} events in this repeat series",
|
||||||
"repeateditthis": "Apply changes to this event only",
|
"repeateditthis": "Apply changes to this event only",
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
"sunday": "Sunday",
|
"sunday": "Sunday",
|
||||||
"thu": "Thu",
|
"thu": "Thu",
|
||||||
"thursday": "Thursday",
|
"thursday": "Thursday",
|
||||||
|
"timebefore": "{{value}} {{units}} before",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
"tomorrow": "Tomorrow",
|
"tomorrow": "Tomorrow",
|
||||||
"tue": "Tue",
|
"tue": "Tue",
|
||||||
|
|
|
@ -123,34 +123,26 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-container *ngFor="let reminder of reminders">
|
<ng-container *ngFor="let reminder of reminders">
|
||||||
<ion-item *ngIf="reminder.time > 0 || defaultTime > 0" class="ion-text-wrap"
|
<ion-item *ngIf="reminder.timestamp > 0" class="ion-text-wrap"
|
||||||
[class.item-dimmed]="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) <= currentTime!">
|
[class.item-dimmed]="reminder.timestamp <= currentTime">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p *ngIf="reminder.time == -1">
|
<p>{{ reminder.label }}</p>
|
||||||
{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}
|
|
||||||
</p>
|
|
||||||
<p *ngIf="reminder.time > 0">{{ reminder.time * 1000 | coreFormatDate }}</p>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button fill="clear" (click)="cancelNotification(reminder.id, $event)"
|
<ion-button fill="clear" (click)="cancelNotification(reminder.id, $event)"
|
||||||
[attr.aria-label]="'core.delete' | translate" slot="end"
|
[attr.aria-label]="'core.delete' | translate" slot="end" *ngIf="reminder.timestamp > currentTime">
|
||||||
*ngIf="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) > currentTime!">
|
|
||||||
<ion-icon name="fas-trash" color="danger" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-trash" color="danger" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="event.timestart + event.timeduration > currentTime!">
|
<ng-container *ngIf="event.timestart > currentTime">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<ion-button expand="block" color="primary" (click)="notificationPicker.open()">
|
<ion-button expand="block" color="primary" (click)="addReminder()">
|
||||||
{{ 'addon.calendar.setnewreminder' | translate }}
|
{{ 'addon.calendar.setnewreminder' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-datetime #notificationPicker hidden [(ngModel)]="notificationTimeText"
|
|
||||||
[displayFormat]="notificationFormat" [min]="notificationMin" [max]="notificationMax"
|
|
||||||
[doneText]="'core.add' | translate" (ionChange)="addNotificationTime()" [monthNames]="monthNames">
|
|
||||||
</ion-datetime>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
AddonCalendarEventToDisplay,
|
AddonCalendarEventToDisplay,
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
} from '../../services/calendar';
|
} from '../../services/calendar';
|
||||||
import { AddonCalendarHelper } from '../../services/calendar-helper';
|
import { AddonCalendarEventReminder, AddonCalendarHelper } from '../../services/calendar-helper';
|
||||||
import { AddonCalendarOffline } from '../../services/calendar-offline';
|
import { AddonCalendarOffline } from '../../services/calendar-offline';
|
||||||
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
|
import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
|
@ -36,10 +36,9 @@ import { Network, NgZone, Translate } from '@singletons';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { AddonCalendarReminderDBRecord } from '../../services/database/calendar';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CoreLang } from '@services/lang';
|
import { AddonCalendarReminderTimeModalComponent } from '@addons/calendar/components/reminder-time-modal/reminder-time-modal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a single calendar event.
|
* Page that displays a single calendar event.
|
||||||
|
@ -57,13 +56,11 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
protected syncObserver: CoreEventObserver;
|
protected syncObserver: CoreEventObserver;
|
||||||
protected manualSyncObserver: CoreEventObserver;
|
protected manualSyncObserver: CoreEventObserver;
|
||||||
protected onlineObserver: Subscription;
|
protected onlineObserver: Subscription;
|
||||||
|
protected defaultTimeChangedObserver: CoreEventObserver;
|
||||||
protected currentSiteId: string;
|
protected currentSiteId: string;
|
||||||
|
protected updateCurrentTime?: number;
|
||||||
|
|
||||||
eventLoaded = false;
|
eventLoaded = false;
|
||||||
notificationFormat?: string;
|
|
||||||
notificationMin?: string;
|
|
||||||
notificationMax?: string;
|
|
||||||
notificationTimeText?: string;
|
|
||||||
event?: AddonCalendarEventToDisplay;
|
event?: AddonCalendarEventToDisplay;
|
||||||
courseId?: number;
|
courseId?: number;
|
||||||
courseName = '';
|
courseName = '';
|
||||||
|
@ -72,19 +69,16 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
notificationsEnabled = false;
|
notificationsEnabled = false;
|
||||||
moduleUrl = '';
|
moduleUrl = '';
|
||||||
categoryPath = '';
|
categoryPath = '';
|
||||||
currentTime?: number;
|
currentTime = -1;
|
||||||
defaultTime = 0;
|
reminders: AddonCalendarEventReminder[] = [];
|
||||||
reminders: AddonCalendarReminderDBRecord[] = [];
|
|
||||||
canEdit = false;
|
canEdit = false;
|
||||||
hasOffline = false;
|
hasOffline = false;
|
||||||
isOnline = false;
|
isOnline = false;
|
||||||
syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
|
syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
|
||||||
monthNames?: string[];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.notificationsEnabled = CoreLocalNotifications.isAvailable();
|
this.notificationsEnabled = CoreLocalNotifications.isAvailable();
|
||||||
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
||||||
this.currentSiteId = CoreSites.getCurrentSiteId();
|
this.currentSiteId = CoreSites.getCurrentSiteId();
|
||||||
|
@ -121,21 +115,35 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.isOnline = CoreApp.isOnline();
|
this.isOnline = CoreApp.isOnline();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reload reminders if default notification time changes.
|
||||||
|
this.defaultTimeChangedObserver = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
|
||||||
|
this.loadReminders();
|
||||||
|
|
||||||
|
if (this.event) {
|
||||||
|
AddonCalendar.scheduleEventsNotifications([this.event]);
|
||||||
|
}
|
||||||
|
}, this.currentSiteId);
|
||||||
|
|
||||||
|
// Set and update current time. Use a 5 seconds error margin.
|
||||||
|
this.currentTime = CoreTimeUtils.timestamp();
|
||||||
|
this.updateCurrentTime = window.setInterval(() => {
|
||||||
|
this.currentTime = CoreTimeUtils.timestamp();
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initReminders(): Promise<void> {
|
/**
|
||||||
if (this.notificationsEnabled) {
|
* Load reminders.
|
||||||
this.monthNames = CoreLang.getMonthNames();
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
this.reminders = await AddonCalendar.getEventReminders(this.eventId);
|
*/
|
||||||
this.defaultTime = await AddonCalendar.getDefaultNotificationTime() * 60;
|
protected async loadReminders(): Promise<void> {
|
||||||
|
if (!this.notificationsEnabled || !this.event) {
|
||||||
// Calculate format to use.
|
return;
|
||||||
this.notificationFormat =
|
|
||||||
CoreTimeUtils.fixFormatForDatetime(CoreTimeUtils.convertPHPToMoment(
|
|
||||||
Translate.instant('core.strftimedatetime'),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reminders = await AddonCalendar.getEventReminders(this.eventId, this.currentSiteId);
|
||||||
|
this.reminders = await AddonCalendarHelper.formatReminders(reminders, this.event.timestart, this.currentSiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,7 +163,6 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
this.syncIcon = CoreConstants.ICON_LOADING;
|
||||||
|
|
||||||
this.fetchEvent();
|
this.fetchEvent();
|
||||||
this.initReminders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,6 +216,10 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
const event = await AddonCalendar.getEventById(this.eventId);
|
const event = await AddonCalendar.getEventById(this.eventId);
|
||||||
this.event = await AddonCalendarHelper.formatEventData(event);
|
this.event = await AddonCalendarHelper.formatEventData(event);
|
||||||
|
|
||||||
|
// Load reminders, and re-schedule them if needed (maybe the event time has changed).
|
||||||
|
this.loadReminders();
|
||||||
|
AddonCalendar.scheduleEventsNotifications([this.event]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const offlineEvent = AddonCalendarHelper.formatOfflineEventData(
|
const offlineEvent = AddonCalendarHelper.formatOfflineEventData(
|
||||||
await AddonCalendarOffline.getEvent(this.eventId),
|
await AddonCalendarOffline.getEvent(this.eventId),
|
||||||
|
@ -223,29 +234,21 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.hasOffline = false;
|
this.hasOffline = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTime = CoreTimeUtils.timestamp();
|
|
||||||
this.notificationMin = CoreTimeUtils.userDate(this.currentTime * 1000, 'YYYY-MM-DDTHH:mm', false);
|
|
||||||
this.notificationMax = CoreTimeUtils.userDate(
|
|
||||||
(this.event!.timestart + this.event!.timeduration) * 1000,
|
|
||||||
'YYYY-MM-DDTHH:mm',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset some of the calculated data.
|
// Reset some of the calculated data.
|
||||||
this.categoryPath = '';
|
this.categoryPath = '';
|
||||||
this.courseName = '';
|
this.courseName = '';
|
||||||
this.courseUrl = '';
|
this.courseUrl = '';
|
||||||
this.moduleUrl = '';
|
this.moduleUrl = '';
|
||||||
|
|
||||||
if (this.event!.moduleIcon) {
|
if (this.event.moduleIcon) {
|
||||||
// It's a module event, translate the module name to the current language.
|
// It's a module event, translate the module name to the current language.
|
||||||
const name = CoreCourse.translateModuleName(this.event!.modulename || '');
|
const name = CoreCourse.translateModuleName(this.event.modulename || '');
|
||||||
if (name.indexOf('core.mod_') === -1) {
|
if (name.indexOf('core.mod_') === -1) {
|
||||||
this.event!.modulename = name;
|
this.event.modulename = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the module URL.
|
// Get the module URL.
|
||||||
this.moduleUrl = this.event!.url || '';
|
this.moduleUrl = this.event.url || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
@ -310,22 +313,23 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Add a reminder for this event.
|
* Add a reminder for this event.
|
||||||
*/
|
*/
|
||||||
async addNotificationTime(): Promise<void> {
|
async addReminder(): Promise<void> {
|
||||||
if (this.notificationTimeText && this.event && this.event.id) {
|
if (!this.event || !this.event.id) {
|
||||||
let notificationTime = CoreTimeUtils.convertToTimestamp(this.notificationTimeText);
|
return;
|
||||||
|
|
||||||
const currentTime = CoreTimeUtils.timestamp();
|
|
||||||
const minute = Math.floor(currentTime / 60) * 60;
|
|
||||||
|
|
||||||
// Check if the notification time is in the same minute as we are, so the notification is triggered.
|
|
||||||
if (notificationTime >= minute && notificationTime < minute + 60) {
|
|
||||||
notificationTime = currentTime + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
await AddonCalendar.addEventReminder(this.event, notificationTime);
|
|
||||||
this.reminders = await AddonCalendar.getEventReminders(this.eventId);
|
|
||||||
this.notificationTimeText = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reminderTime = await CoreDomUtils.openModal<number>({
|
||||||
|
component: AddonCalendarReminderTimeModalComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reminderTime === undefined) {
|
||||||
|
// User canceled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddonCalendar.addEventReminder(this.event, reminderTime, this.currentSiteId);
|
||||||
|
|
||||||
|
await this.loadReminders();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,7 +349,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await AddonCalendar.deleteEventReminder(id);
|
await AddonCalendar.deleteEventReminder(id);
|
||||||
this.reminders = await AddonCalendar.getEventReminders(this.eventId);
|
await this.loadReminders();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error deleting reminder');
|
CoreDomUtils.showErrorModalDefault(error, 'Error deleting reminder');
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -549,6 +553,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
|
||||||
this.syncObserver?.off();
|
this.syncObserver?.off();
|
||||||
this.manualSyncObserver?.off();
|
this.manualSyncObserver?.off();
|
||||||
this.onlineObserver?.unsubscribe();
|
this.onlineObserver?.unsubscribe();
|
||||||
|
clearInterval(this.updateCurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,10 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item *ngIf="defaultTime != -1">
|
<ion-item *ngIf="defaultTimeLabel">
|
||||||
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
|
<ion-label>{{ 'addon.calendar.defaultnotificationtime' | translate }}</ion-label>
|
||||||
<ion-select [(ngModel)]="defaultTime" (ionChange)="updateDefaultTime(defaultTime)" interface="action-sheet"
|
<ion-select [(ngModel)]="defaultTimeLabel" (click)="changeDefaultTime($event)">
|
||||||
[interfaceOptions]="{header: 'addon.calendar.defaultnotificationtime' | translate}">
|
<ion-select-option [value]="defaultTimeLabel">{{ defaultTimeLabel }}</ion-select-option>
|
||||||
<ion-select-option [value]="0">{{ 'core.settings.disabled' | translate }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="10">{{ 600 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="30">{{ 1800 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="60">{{ 3600 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="120">{{ 7200 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="360">{{ 21600 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="720">{{ 43200 | coreDuration }}</ion-select-option>
|
|
||||||
<ion-select-option [value]="1440">{{ 86400 | coreDuration }}</ion-select-option>
|
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
|
@ -13,9 +13,16 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { AddonCalendar, AddonCalendarProvider } from '../../services/calendar';
|
import {
|
||||||
|
AddonCalendar,
|
||||||
|
AddonCalendarProvider,
|
||||||
|
AddonCalendarReminderUnits,
|
||||||
|
AddonCalendarValueAndUnit,
|
||||||
|
} from '../../services/calendar';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { AddonCalendarReminderTimeModalComponent } from '@addons/calendar/components/reminder-time-modal/reminder-time-modal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the calendar settings.
|
* Page that displays the calendar settings.
|
||||||
|
@ -26,13 +33,51 @@ import { CoreSites } from '@services/sites';
|
||||||
})
|
})
|
||||||
export class AddonCalendarSettingsPage implements OnInit {
|
export class AddonCalendarSettingsPage implements OnInit {
|
||||||
|
|
||||||
defaultTime = -1;
|
defaultTimeLabel = '';
|
||||||
|
|
||||||
|
protected defaultTime: AddonCalendarValueAndUnit = {
|
||||||
|
value: 0,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View loaded.
|
* View loaded.
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.defaultTime = await AddonCalendar.getDefaultNotificationTime();
|
const defaultTime = await AddonCalendar.getDefaultNotificationTime();
|
||||||
|
|
||||||
|
this.defaultTime = AddonCalendarProvider.convertSecondsToValueAndUnit(defaultTime);
|
||||||
|
this.defaultTimeLabel = AddonCalendar.getUnitValueLabel(this.defaultTime.value, this.defaultTime.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change default time.
|
||||||
|
*
|
||||||
|
* @param e Event.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async changeDefaultTime(e: Event): Promise<void> {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const reminderTime = await CoreDomUtils.openModal<number>({
|
||||||
|
component: AddonCalendarReminderTimeModalComponent,
|
||||||
|
componentProps: {
|
||||||
|
initialValue: this.defaultTime,
|
||||||
|
allowDisable: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (reminderTime === undefined) {
|
||||||
|
// User canceled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.defaultTime = AddonCalendarProvider.convertSecondsToValueAndUnit(reminderTime);
|
||||||
|
this.defaultTimeLabel = AddonCalendar.getUnitValueLabel(this.defaultTime.value, this.defaultTime.unit);
|
||||||
|
|
||||||
|
this.updateDefaultTime(reminderTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
AddonCalendarEventType,
|
AddonCalendarEventType,
|
||||||
AddonCalendarGetEventsEvent,
|
AddonCalendarGetEventsEvent,
|
||||||
AddonCalendarProvider,
|
AddonCalendarProvider,
|
||||||
|
AddonCalendarReminderUnits,
|
||||||
AddonCalendarWeek,
|
AddonCalendarWeek,
|
||||||
AddonCalendarWeekDay,
|
AddonCalendarWeekDay,
|
||||||
} from './calendar';
|
} from './calendar';
|
||||||
|
@ -35,6 +36,7 @@ import { makeSingleton } from '@singletons';
|
||||||
import { AddonCalendarSyncInvalidateEvent } from './calendar-sync';
|
import { AddonCalendarSyncInvalidateEvent } from './calendar-sync';
|
||||||
import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
|
import { AddonCalendarOfflineEventDBRecord } from './database/calendar-offline';
|
||||||
import { CoreCategoryData } from '@features/courses/services/courses';
|
import { CoreCategoryData } from '@features/courses/services/courses';
|
||||||
|
import { AddonCalendarReminderDBRecord } from './database/calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context levels enumeration.
|
* Context levels enumeration.
|
||||||
|
@ -282,6 +284,55 @@ export class AddonCalendarHelperProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format reminders, adding calculated data.
|
||||||
|
*
|
||||||
|
* @param reminders Reminders.
|
||||||
|
* @param timestart Event timestart.
|
||||||
|
* @param siteId Site ID.
|
||||||
|
* @return Formatted reminders.
|
||||||
|
*/
|
||||||
|
async formatReminders(
|
||||||
|
reminders: AddonCalendarReminderDBRecord[],
|
||||||
|
timestart: number,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<AddonCalendarEventReminder[]> {
|
||||||
|
const defaultTime = await AddonCalendar.getDefaultNotificationTime(siteId);
|
||||||
|
|
||||||
|
const formattedReminders = <AddonCalendarEventReminder[]> reminders;
|
||||||
|
const eventTimestart = timestart;
|
||||||
|
let defaultTimeValue: number | undefined;
|
||||||
|
let defaultTimeUnit: AddonCalendarReminderUnits | undefined;
|
||||||
|
|
||||||
|
if (defaultTime > 0) {
|
||||||
|
const data = AddonCalendarProvider.convertSecondsToValueAndUnit(defaultTime);
|
||||||
|
defaultTimeValue = data.value;
|
||||||
|
defaultTimeUnit = data.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedReminders.map((reminder) => {
|
||||||
|
if (reminder.time === null) {
|
||||||
|
// Default time. Check if default notifications are disabled.
|
||||||
|
if (defaultTimeValue !== undefined && defaultTimeUnit) {
|
||||||
|
reminder.value = defaultTimeValue;
|
||||||
|
reminder.unit = defaultTimeUnit;
|
||||||
|
reminder.timestamp = eventTimestart - reminder.value * reminder.unit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = AddonCalendarProvider.convertSecondsToValueAndUnit(reminder.time);
|
||||||
|
reminder.value = data.value;
|
||||||
|
reminder.unit = data.unit;
|
||||||
|
reminder.timestamp = eventTimestart - reminder.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reminder.value && reminder.unit) {
|
||||||
|
reminder.label = AddonCalendar.getUnitValueLabel(reminder.value, reminder.unit, reminder.time === null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reminder;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get options (name & value) for each allowed event type.
|
* Get options (name & value) for each allowed event type.
|
||||||
*
|
*
|
||||||
|
@ -725,3 +776,13 @@ export type AddonCalendarEventTypeOption = {
|
||||||
name: string;
|
name: string;
|
||||||
value: AddonCalendarEventType;
|
value: AddonCalendarEventType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted event reminder.
|
||||||
|
*/
|
||||||
|
export type AddonCalendarEventReminder = AddonCalendarReminderDBRecord & {
|
||||||
|
value?: number; // Amount of time.
|
||||||
|
unit?: AddonCalendarReminderUnits; // Units.
|
||||||
|
timestamp?: number; // Timestamp (in seconds).
|
||||||
|
label?: string; // Label to represent the reminder.
|
||||||
|
};
|
||||||
|
|
|
@ -53,6 +53,16 @@ export enum AddonCalendarEventType {
|
||||||
USER = 'user',
|
USER = 'user',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Units to set a reminder.
|
||||||
|
*/
|
||||||
|
export enum AddonCalendarReminderUnits {
|
||||||
|
MINUTE = CoreConstants.SECONDS_MINUTE,
|
||||||
|
HOUR = CoreConstants.SECONDS_HOUR,
|
||||||
|
DAY = CoreConstants.SECONDS_DAY,
|
||||||
|
WEEK = CoreConstants.SECONDS_WEEK,
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@singletons/events' {
|
declare module '@singletons/events' {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +82,21 @@ declare module '@singletons/events' {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REMINDER_UNITS_LABELS = {
|
||||||
|
single: {
|
||||||
|
[AddonCalendarReminderUnits.MINUTE]: 'core.minute',
|
||||||
|
[AddonCalendarReminderUnits.HOUR]: 'core.hour',
|
||||||
|
[AddonCalendarReminderUnits.DAY]: 'core.day',
|
||||||
|
[AddonCalendarReminderUnits.WEEK]: 'core.week',
|
||||||
|
},
|
||||||
|
multi: {
|
||||||
|
[AddonCalendarReminderUnits.MINUTE]: 'core.minutes',
|
||||||
|
[AddonCalendarReminderUnits.HOUR]: 'core.hours',
|
||||||
|
[AddonCalendarReminderUnits.DAY]: 'core.days',
|
||||||
|
[AddonCalendarReminderUnits.WEEK]: 'core.weeks',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle calendar events.
|
* Service to handle calendar events.
|
||||||
*/
|
*/
|
||||||
|
@ -82,7 +107,7 @@ export class AddonCalendarProvider {
|
||||||
static readonly COMPONENT = 'AddonCalendarEvents';
|
static readonly COMPONENT = 'AddonCalendarEvents';
|
||||||
static readonly DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
static readonly DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||||
static readonly DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
static readonly DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||||
static readonly DEFAULT_NOTIFICATION_TIME = 60;
|
static readonly DEFAULT_NOTIFICATION_TIME = 3600;
|
||||||
static readonly STARTING_WEEK_DAY = 'addon_calendar_starting_week_day';
|
static readonly STARTING_WEEK_DAY = 'addon_calendar_starting_week_day';
|
||||||
static readonly NEW_EVENT_EVENT = 'addon_calendar_new_event';
|
static readonly NEW_EVENT_EVENT = 'addon_calendar_new_event';
|
||||||
static readonly NEW_EVENT_DISCARDED_EVENT = 'addon_calendar_new_event_discarded';
|
static readonly NEW_EVENT_DISCARDED_EVENT = 'addon_calendar_new_event_discarded';
|
||||||
|
@ -156,6 +181,41 @@ export class AddonCalendarProvider {
|
||||||
return !!site?.isVersionGreaterEqualThan('3.7.1');
|
return !!site?.isVersionGreaterEqualThan('3.7.1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a number of seconds, convert it to a unit&value format compatible with reminders.
|
||||||
|
*
|
||||||
|
* @param seconds Number of seconds.
|
||||||
|
* @return Value and unit.
|
||||||
|
*/
|
||||||
|
static convertSecondsToValueAndUnit(seconds: number): AddonCalendarValueAndUnit {
|
||||||
|
if (seconds <= 0) {
|
||||||
|
return {
|
||||||
|
value: 0,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
};
|
||||||
|
} else if (seconds % AddonCalendarReminderUnits.WEEK === 0) {
|
||||||
|
return {
|
||||||
|
value: seconds / AddonCalendarReminderUnits.WEEK,
|
||||||
|
unit: AddonCalendarReminderUnits.WEEK,
|
||||||
|
};
|
||||||
|
} else if (seconds % AddonCalendarReminderUnits.DAY === 0) {
|
||||||
|
return {
|
||||||
|
value: seconds / AddonCalendarReminderUnits.DAY,
|
||||||
|
unit: AddonCalendarReminderUnits.DAY,
|
||||||
|
};
|
||||||
|
} else if (seconds % AddonCalendarReminderUnits.HOUR === 0) {
|
||||||
|
return {
|
||||||
|
value: seconds / AddonCalendarReminderUnits.HOUR,
|
||||||
|
unit: AddonCalendarReminderUnits.HOUR,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
value: seconds / AddonCalendarReminderUnits.MINUTE,
|
||||||
|
unit: AddonCalendarReminderUnits.MINUTE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an event.
|
* Delete an event.
|
||||||
*
|
*
|
||||||
|
@ -248,7 +308,7 @@ export class AddonCalendarProvider {
|
||||||
REMINDERS_TABLE,
|
REMINDERS_TABLE,
|
||||||
{ eventid: eventId },
|
{ eventid: eventId },
|
||||||
).then((reminders) =>
|
).then((reminders) =>
|
||||||
Promise.all(reminders.map((reminder) => this.deleteEventReminder(reminder.id!, siteId)))));
|
Promise.all(reminders.map((reminder) => this.deleteEventReminder(reminder.id, siteId)))));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -548,7 +608,7 @@ export class AddonCalendarProvider {
|
||||||
* Get the configured default notification time.
|
* Get the configured default notification time.
|
||||||
*
|
*
|
||||||
* @param siteId ID of the site. If not defined, use current site.
|
* @param siteId ID of the site. If not defined, use current site.
|
||||||
* @return Promise resolved with the default time.
|
* @return Promise resolved with the default time (in seconds).
|
||||||
*/
|
*/
|
||||||
async getDefaultNotificationTime(siteId?: string): Promise<number> {
|
async getDefaultNotificationTime(siteId?: string): Promise<number> {
|
||||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||||
|
@ -678,24 +738,27 @@ export class AddonCalendarProvider {
|
||||||
/**
|
/**
|
||||||
* Adds an event reminder and schedule a new notification.
|
* Adds an event reminder and schedule a new notification.
|
||||||
*
|
*
|
||||||
* @param event Event to update its notification time.
|
* @param event Event to set the reminder.
|
||||||
* @param time New notification setting timestamp.
|
* @param time Amount of seconds of the reminder. Undefined for default reminder.
|
||||||
* @param siteId ID of the site the event belongs to. If not defined, use current site.
|
* @param siteId ID of the site the event belongs to. If not defined, use current site.
|
||||||
* @return Promise resolved when the notification is updated.
|
* @return Promise resolved when the notification is updated.
|
||||||
*/
|
*/
|
||||||
async addEventReminder(
|
async addEventReminder(
|
||||||
event: { id: number; timestart: number; timeduration: number; name: string},
|
event: { id: number; timestart: number; timeduration: number; name: string},
|
||||||
time: number,
|
time?: number | null,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
const reminder: AddonCalendarReminderDBRecord = {
|
const reminder: Partial<AddonCalendarReminderDBRecord> = {
|
||||||
eventid: event.id,
|
eventid: event.id,
|
||||||
time: time,
|
time: time ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reminderId = await site.getDb().insertRecord(REMINDERS_TABLE, reminder);
|
const reminderId = await site.getDb().insertRecord(REMINDERS_TABLE, reminder);
|
||||||
|
|
||||||
await this.scheduleEventNotification(event, reminderId, time, site.getId());
|
const timestamp = time ? event.timestart - time : time;
|
||||||
|
|
||||||
|
await this.scheduleEventNotification(event, reminderId, timestamp, site.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1022,6 +1085,35 @@ export class AddonCalendarProvider {
|
||||||
(categoryId ? categoryId : '');
|
(categoryId ? categoryId : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a value and a unit, return the translated label.
|
||||||
|
*
|
||||||
|
* @param value Value.
|
||||||
|
* @param unit Unit.
|
||||||
|
* @param addDefaultLabel Whether to add the "Default" text.
|
||||||
|
* @return Translated label.
|
||||||
|
*/
|
||||||
|
getUnitValueLabel(value: number, unit: AddonCalendarReminderUnits, addDefaultLabel = false): string {
|
||||||
|
if (value === 0) {
|
||||||
|
return Translate.instant('core.settings.disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
const unitsLabel = value === 1 ?
|
||||||
|
REMINDER_UNITS_LABELS.single[unit] :
|
||||||
|
REMINDER_UNITS_LABELS.multi[unit];
|
||||||
|
|
||||||
|
const label = Translate.instant('addon.calendar.timebefore', {
|
||||||
|
units: Translate.instant(unitsLabel),
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (addDefaultLabel) {
|
||||||
|
return Translate.instant('core.defaultvalue', { $a: label });
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get upcoming calendar events.
|
* Get upcoming calendar events.
|
||||||
*
|
*
|
||||||
|
@ -1335,14 +1427,14 @@ export class AddonCalendarProvider {
|
||||||
*
|
*
|
||||||
* @param event Event to schedule.
|
* @param event Event to schedule.
|
||||||
* @param reminderId The reminder ID.
|
* @param reminderId The reminder ID.
|
||||||
* @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start".
|
* @param time Notification timestamp (in seconds). Undefined for default time.
|
||||||
* @param siteId Site ID the event belongs to. If not defined, use current site.
|
* @param siteId Site ID the event belongs to. If not defined, use current site.
|
||||||
* @return Promise resolved when the notification is scheduled.
|
* @return Promise resolved when the notification is scheduled.
|
||||||
*/
|
*/
|
||||||
protected async scheduleEventNotification(
|
protected async scheduleEventNotification(
|
||||||
event: { id: number; timestart: number; name: string},
|
event: { id: number; timestart: number; name: string},
|
||||||
reminderId: number,
|
reminderId: number,
|
||||||
time: number,
|
time?: number | null,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
||||||
|
@ -1357,16 +1449,16 @@ export class AddonCalendarProvider {
|
||||||
return CoreLocalNotifications.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId);
|
return CoreLocalNotifications.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time == -1) {
|
if (!time) {
|
||||||
// If time is -1, get event default time to calculate the notification time.
|
// Get event default time to calculate the notification time.
|
||||||
time = await this.getDefaultNotificationTime(siteId);
|
time = await this.getDefaultNotificationTime(siteId);
|
||||||
|
|
||||||
if (time == 0) {
|
if (time === 0) {
|
||||||
// Default notification time is disabled, do not show.
|
// Default notification time is disabled, do not show.
|
||||||
return CoreLocalNotifications.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId);
|
return CoreLocalNotifications.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
time = event.timestart - (time * 60);
|
time = event.timestart - time;
|
||||||
}
|
}
|
||||||
|
|
||||||
time = time * 1000;
|
time = time * 1000;
|
||||||
|
@ -1417,17 +1509,18 @@ export class AddonCalendarProvider {
|
||||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
const promises = events.map(async (event) => {
|
const promises = events.map(async (event) => {
|
||||||
const timeEnd = (event.timestart + event.timeduration) * 1000;
|
if (event.timestart * 1000 <= Date.now()) {
|
||||||
|
// The event has already started, don't schedule it.
|
||||||
if (timeEnd <= new Date().getTime()) {
|
|
||||||
// The event has finished already, don't schedule it.
|
|
||||||
return this.deleteLocalEvent(event.id, siteId);
|
return this.deleteLocalEvent(event.id, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reminders = await this.getEventReminders(event.id, siteId);
|
const reminders = await this.getEventReminders(event.id, siteId);
|
||||||
|
|
||||||
const p2 = reminders.map((reminder: AddonCalendarReminderDBRecord) =>
|
const p2 = reminders.map((reminder) => {
|
||||||
this.scheduleEventNotification(event, (reminder.id!), reminder.time, siteId));
|
const time = reminder.time ? event.timestart - reminder.time : reminder.time;
|
||||||
|
|
||||||
|
return this.scheduleEventNotification(event, reminder.id, time, siteId);
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all(p2);
|
await Promise.all(p2);
|
||||||
});
|
});
|
||||||
|
@ -1467,7 +1560,8 @@ export class AddonCalendarProvider {
|
||||||
const reminders = await this.getEventReminders(event.id, siteId);
|
const reminders = await this.getEventReminders(event.id, siteId);
|
||||||
|
|
||||||
if (reminders.length == 0) {
|
if (reminders.length == 0) {
|
||||||
this.addEventReminder(event, -1, siteId);
|
// No reminders, create the default one.
|
||||||
|
this.addEventReminder(event, undefined, siteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1537,9 +1631,7 @@ export class AddonCalendarProvider {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
siteId = site.getId();
|
siteId = site.getId();
|
||||||
|
|
||||||
await Promise.all(events.map((event: AddonCalendarGetEventsEvent| AddonCalendarCalendarEvent) =>
|
await Promise.all(events.map((event) => this.storeEventInLocalDb(event, siteId)));
|
||||||
// If event does not exist on the DB, schedule the reminder.
|
|
||||||
this.storeEventInLocalDb(event, siteId)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2154,3 +2246,11 @@ type AddonCalendarPushNotificationData = {
|
||||||
reminderId: number;
|
reminderId: number;
|
||||||
siteId: string;
|
siteId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value and unit for reminders.
|
||||||
|
*/
|
||||||
|
export type AddonCalendarValueAndUnit = {
|
||||||
|
value: number;
|
||||||
|
unit: AddonCalendarReminderUnits;
|
||||||
|
};
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
import { CoreSiteSchema } from '@services/sites';
|
import { CoreSiteSchema } from '@services/sites';
|
||||||
import { AddonCalendarEventType } from '../calendar';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { AddonCalendar, AddonCalendarEventType } from '../calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database variables for AddonDatabase service.
|
* Database variables for AddonDatabase service.
|
||||||
|
@ -23,7 +24,7 @@ export const EVENTS_TABLE = 'addon_calendar_events_3';
|
||||||
export const REMINDERS_TABLE = 'addon_calendar_reminders';
|
export const REMINDERS_TABLE = 'addon_calendar_reminders';
|
||||||
export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = {
|
export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = {
|
||||||
name: 'AddonCalendarProvider',
|
name: 'AddonCalendarProvider',
|
||||||
version: 3,
|
version: 4,
|
||||||
canBeCleared: [EVENTS_TABLE],
|
canBeCleared: [EVENTS_TABLE],
|
||||||
tables: [
|
tables: [
|
||||||
{
|
{
|
||||||
|
@ -199,8 +200,9 @@ export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
|
async migrate(db: SQLiteDB, oldVersion: number, siteId: string): Promise<void> {
|
||||||
if (oldVersion < 3) {
|
if (oldVersion < 3) {
|
||||||
|
// Migrate calendar events. New format @since 3.7.
|
||||||
let oldTable = 'addon_calendar_events_2';
|
let oldTable = 'addon_calendar_events_2';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -212,6 +214,46 @@ export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = {
|
||||||
|
|
||||||
await db.migrateTable(oldTable, EVENTS_TABLE);
|
await db.migrateTable(oldTable, EVENTS_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 4) {
|
||||||
|
// Migrate reminders. New format @since 4.0.
|
||||||
|
const defaultTime = await CoreUtils.ignoreErrors(AddonCalendar.getDefaultNotificationTime(siteId));
|
||||||
|
if (defaultTime) {
|
||||||
|
// Convert from minutes to seconds.
|
||||||
|
AddonCalendar.setDefaultNotificationTime(defaultTime * 60, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = await db.getAllRecords<AddonCalendarReminderDBRecord>(REMINDERS_TABLE);
|
||||||
|
const events: Record<number, AddonCalendarEventDBRecord> = {};
|
||||||
|
|
||||||
|
await Promise.all(records.map(async (record) => {
|
||||||
|
// Get the event to compare the reminder time with the event time.
|
||||||
|
if (!events[record.eventid]) {
|
||||||
|
try {
|
||||||
|
events[record.eventid] = await db.getRecord(EVENTS_TABLE, { id: record.eventid });
|
||||||
|
} catch {
|
||||||
|
// Event not found in local DB, shouldn't happen. Delete the reminder.
|
||||||
|
await db.deleteRecords(REMINDERS_TABLE, { id: record.id });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!record.time || record.time === -1) {
|
||||||
|
// Default reminder. Use null now.
|
||||||
|
record.time = null;
|
||||||
|
} else if (record.time > events[record.eventid].timestart) {
|
||||||
|
// Reminder is after the event, delete it.
|
||||||
|
await db.deleteRecords(REMINDERS_TABLE, { id: record.id });
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
record.time = events[record.eventid].timestart - record.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.insertRecord(REMINDERS_TABLE, record);
|
||||||
|
}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -257,7 +299,7 @@ export type AddonCalendarEventDBRecord = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddonCalendarReminderDBRecord = {
|
export type AddonCalendarReminderDBRecord = {
|
||||||
id?: number;
|
id: number;
|
||||||
eventid: number;
|
eventid: number;
|
||||||
time: number;
|
time: number | null; // Number of seconds before the event, null for default time.
|
||||||
};
|
};
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"coursedetails": "Course details",
|
"coursedetails": "Course details",
|
||||||
"coursenogroups": "You are not a member of any group of this course.",
|
"coursenogroups": "You are not a member of any group of this course.",
|
||||||
"currentdevice": "Current device",
|
"currentdevice": "Current device",
|
||||||
|
"custom": "Custom",
|
||||||
"datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.",
|
"datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"day": "day",
|
"day": "day",
|
||||||
|
@ -157,6 +158,8 @@
|
||||||
"maxsizeandattachments": "Maximum file size: {{$a.size}}, maximum number of files: {{$a.attachments}}",
|
"maxsizeandattachments": "Maximum file size: {{$a.size}}, maximum number of files: {{$a.attachments}}",
|
||||||
"min": "min",
|
"min": "min",
|
||||||
"mins": "mins",
|
"mins": "mins",
|
||||||
|
"minute": "minute",
|
||||||
|
"minutes": "minutes",
|
||||||
"misc": "Miscellaneous",
|
"misc": "Miscellaneous",
|
||||||
"mod_assign": "Assignment",
|
"mod_assign": "Assignment",
|
||||||
"mod_assignment": "Assignment 2.2 (Disabled)",
|
"mod_assignment": "Assignment 2.2 (Disabled)",
|
||||||
|
@ -330,6 +333,8 @@
|
||||||
"viewprofile": "View profile",
|
"viewprofile": "View profile",
|
||||||
"warningofflinedatadeleted": "Offline data from {{component}} '{{name}}' has been deleted. {{error}}",
|
"warningofflinedatadeleted": "Offline data from {{component}} '{{name}}' has been deleted. {{error}}",
|
||||||
"warnopeninbrowser": "<p>You are about to leave the app to open the following URL in your device's browser. Do you want to continue?</p>\n<p><b>{{url}}</b></p>",
|
"warnopeninbrowser": "<p>You are about to leave the app to open the following URL in your device's browser. Do you want to continue?</p>\n<p><b>{{url}}</b></p>",
|
||||||
|
"week": "week",
|
||||||
|
"weeks": "weeks",
|
||||||
"whatisyourage": "What is your age?",
|
"whatisyourage": "What is your age?",
|
||||||
"wheredoyoulive": "In which country do you live?",
|
"wheredoyoulive": "In which country do you live?",
|
||||||
"whoissiteadmin": "\"Site Administrators\" are the people who manage the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",
|
"whoissiteadmin": "\"Site Administrators\" are the people who manage the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",
|
||||||
|
|
Loading…
Reference in New Issue