MOBILE-3909 calendar: Allow setting reminders when creating event
parent
b31b0764a5
commit
e86d49742a
|
@ -225,6 +225,32 @@
|
|||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- Reminders. Right now, only allow adding them here for new events. -->
|
||||
<ng-container *ngIf="notificationsEnabled && !eventId">
|
||||
<ion-item-divider class="addon-calendar-reminders-title">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'addon.calendar.reminders' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item-divider>
|
||||
<ion-item *ngFor="let reminder of reminders" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p>{{ reminder.label }}</p>
|
||||
</ion-label>
|
||||
<ion-button fill="clear" (click)="removeReminder(reminder)" [attr.aria-label]="'core.delete' | translate"
|
||||
slot="end">
|
||||
<ion-icon name="fas-trash" color="danger" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<ion-button expand="block" color="light" (click)="addReminder()">
|
||||
{{ 'addon.calendar.setnewreminder' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ion-item>
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
AddonCalendarSubmitCreateUpdateFormDataWSParams,
|
||||
} from '../../services/calendar';
|
||||
import { AddonCalendarOffline } from '../../services/calendar-offline';
|
||||
import { AddonCalendarEventTypeOption, AddonCalendarHelper } from '../../services/calendar-helper';
|
||||
import { AddonCalendarEventReminder, AddonCalendarEventTypeOption, AddonCalendarHelper } from '../../services/calendar-helper';
|
||||
import { AddonCalendarSync, AddonCalendarSyncProvider } from '../../services/calendar-sync';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { Translate } from '@singletons';
|
||||
|
@ -43,6 +43,8 @@ import { CoreError } from '@classes/errors/error';
|
|||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||
import { AddonCalendarReminderTimeModalComponent } from '@addons/calendar/components/reminder-time-modal/reminder-time-modal';
|
||||
|
||||
/**
|
||||
* Page that displays a form to create/edit an event.
|
||||
|
@ -83,6 +85,10 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
groupControl: FormControl;
|
||||
descriptionControl: FormControl;
|
||||
|
||||
// Reminders.
|
||||
notificationsEnabled = false;
|
||||
reminders: AddonCalendarEventCandidateReminder[] = [];
|
||||
|
||||
protected courseId!: number;
|
||||
protected originalData?: AddonCalendarOfflineEventDBRecord;
|
||||
protected currentSite: CoreSite;
|
||||
|
@ -95,6 +101,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
protected fb: FormBuilder,
|
||||
) {
|
||||
this.currentSite = CoreSites.getRequiredCurrentSite();
|
||||
this.notificationsEnabled = CoreLocalNotifications.isAvailable();
|
||||
this.errors = {
|
||||
required: Translate.instant('core.required'),
|
||||
};
|
||||
|
@ -140,6 +147,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
this.form.addControl('timedurationuntil', this.fb.control(currentDate));
|
||||
this.form.addControl('courseid', this.fb.control(this.courseId));
|
||||
|
||||
this.initReminders();
|
||||
this.fetchData().finally(() => {
|
||||
this.originalData = CoreUtils.clone(this.form.value);
|
||||
this.loaded = true;
|
||||
|
@ -519,7 +527,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
let event: AddonCalendarEvent | AddonCalendarOfflineEventDBRecord;
|
||||
|
||||
try {
|
||||
const result = await AddonCalendar.submitEvent(this.eventId, data);
|
||||
const result = await AddonCalendar.submitEvent(this.eventId, data, {
|
||||
reminders: this.reminders,
|
||||
});
|
||||
event = result.event;
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, result.sent, this.currentSite.getId());
|
||||
|
@ -630,6 +640,69 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init reminders.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async initReminders(): Promise<void> {
|
||||
if (!this.notificationsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if default reminders are enabled.
|
||||
const defaultTime = await AddonCalendar.getDefaultNotificationTime(this.currentSite.getId());
|
||||
if (defaultTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = AddonCalendarProvider.convertSecondsToValueAndUnit(defaultTime);
|
||||
|
||||
// Add default reminder.
|
||||
this.reminders.push({
|
||||
time: null,
|
||||
value: data.value,
|
||||
unit: data.unit,
|
||||
label: AddonCalendar.getUnitValueLabel(data.value, data.unit, true),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reminder.
|
||||
*/
|
||||
async addReminder(): Promise<void> {
|
||||
const reminderTime = await CoreDomUtils.openModal<number>({
|
||||
component: AddonCalendarReminderTimeModalComponent,
|
||||
});
|
||||
|
||||
if (reminderTime === undefined) {
|
||||
// User canceled.
|
||||
return;
|
||||
}
|
||||
|
||||
const data = AddonCalendarProvider.convertSecondsToValueAndUnit(reminderTime);
|
||||
|
||||
// Add reminder.
|
||||
this.reminders.push({
|
||||
time: reminderTime,
|
||||
value: data.value,
|
||||
unit: data.unit,
|
||||
label: AddonCalendar.getUnitValueLabel(data.value, data.unit),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a reminder.
|
||||
*
|
||||
* @param reminder The reminder to remove.
|
||||
*/
|
||||
removeReminder(reminder: AddonCalendarEventCandidateReminder): void {
|
||||
const index = this.reminders.indexOf(reminder);
|
||||
if (index != -1) {
|
||||
this.reminders.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
|
@ -639,3 +712,5 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonCalendarEventCandidateReminder = Omit<AddonCalendarEventReminder, 'id'|'eventid'>;
|
||||
|
|
|
@ -215,20 +215,18 @@ export class AddonCalendarOfflineProvider {
|
|||
/**
|
||||
* Offline version for adding a new discussion to a forum.
|
||||
*
|
||||
* @param eventId Event ID. If it's a new event, set it to undefined/null.
|
||||
* @param eventId Event ID. Negative value to edit offline event. If it's a new event, set it to undefined/null.
|
||||
* @param data Event data.
|
||||
* @param timeCreated The time the event was created. If not defined, current time.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the stored event.
|
||||
*/
|
||||
async saveEvent(
|
||||
eventId: number | undefined,
|
||||
data: AddonCalendarSubmitCreateUpdateFormDataWSParams,
|
||||
timeCreated?: number,
|
||||
siteId?: string,
|
||||
): Promise<AddonCalendarOfflineEventDBRecord> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
timeCreated = timeCreated || Date.now();
|
||||
const timeCreated = Date.now();
|
||||
const event: AddonCalendarOfflineEventDBRecord = {
|
||||
id: eventId || -timeCreated,
|
||||
name: data.name,
|
||||
|
|
|
@ -744,7 +744,7 @@ export class AddonCalendarProvider {
|
|||
* @return Promise resolved when the notification is updated.
|
||||
*/
|
||||
async addEventReminder(
|
||||
event: { id: number; timestart: number; timeduration: number; name: string},
|
||||
event: { id: number; timestart: number; name: string},
|
||||
time?: number | null,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
|
@ -836,7 +836,7 @@ export class AddonCalendarProvider {
|
|||
preSets.emergencyCache = false;
|
||||
}
|
||||
const response: AddonCalendarCalendarDay = await site.read('core_calendar_get_calendar_day_view', params, preSets);
|
||||
this.storeEventsInLocalDB(response.events, siteId);
|
||||
this.storeEventsInLocalDB(response.events, { siteId });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
@ -1040,7 +1040,7 @@ export class AddonCalendarProvider {
|
|||
const response = await site.read<AddonCalendarMonth>('core_calendar_get_calendar_monthly_view', params, preSets);
|
||||
response.weeks.forEach((week) => {
|
||||
week.days.forEach((day) => {
|
||||
this.storeEventsInLocalDB(day.events as AddonCalendarCalendarEvent[], siteId);
|
||||
this.storeEventsInLocalDB(day.events as AddonCalendarCalendarEvent[], { siteId });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1153,7 +1153,7 @@ export class AddonCalendarProvider {
|
|||
}
|
||||
|
||||
const response = await site.read<AddonCalendarUpcoming>('core_calendar_get_calendar_upcoming_view', params, preSets);
|
||||
this.storeEventsInLocalDB(response.events, siteId);
|
||||
this.storeEventsInLocalDB(response.events, { siteId });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
@ -1548,23 +1548,31 @@ export class AddonCalendarProvider {
|
|||
* Store an event in local DB as it is.
|
||||
*
|
||||
* @param event Event to store.
|
||||
* @param siteId ID of the site the event belongs to. If not defined, use current site.
|
||||
* @param options Options.
|
||||
* @return Promise resolved when stored.
|
||||
*/
|
||||
async storeEventInLocalDb(event: AddonCalendarGetEventsEvent | AddonCalendarCalendarEvent, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
siteId = site.getId();
|
||||
protected async storeEventInLocalDb(
|
||||
event: AddonCalendarGetEventsEvent | AddonCalendarCalendarEvent | AddonCalendarEvent,
|
||||
options: AddonCalendarStoreEventsOptions = {},
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(options.siteId);
|
||||
const siteId = site.getId();
|
||||
const addDefaultReminder = options.addDefaultReminder ?? true;
|
||||
|
||||
if (addDefaultReminder) {
|
||||
// Add default reminder if the event isn't stored already and doesn't have any reminder.
|
||||
try {
|
||||
await this.getEventFromLocalDb(event.id, site.id);
|
||||
await this.getEventFromLocalDb(event.id, siteId);
|
||||
} catch {
|
||||
// Event does not exist. Check if any reminder exists first.
|
||||
// Event does not exist.
|
||||
const reminders = await this.getEventReminders(event.id, siteId);
|
||||
|
||||
if (reminders.length == 0) {
|
||||
if (reminders.length === 0) {
|
||||
// No reminders, create the default one.
|
||||
this.addEventReminder(event, undefined, siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't store data that can be calculated like formattedtime, iscategoryevent, etc.
|
||||
let eventRecord: AddonCalendarEventDBRecord = {
|
||||
|
@ -1600,12 +1608,17 @@ export class AddonCalendarProvider {
|
|||
viewurl: event.viewurl,
|
||||
isactionevent: event.isactionevent ? 1 : 0,
|
||||
url: event.url,
|
||||
});
|
||||
|
||||
if ('islastday' in event) {
|
||||
eventRecord = Object.assign(eventRecord, {
|
||||
islastday: event.islastday ? 1 : 0,
|
||||
popupname: event.popupname,
|
||||
mindaytimestamp: event.mindaytimestamp,
|
||||
maxdaytimestamp: event.maxdaytimestamp,
|
||||
draggable: event.draggable ? 1 : 0,
|
||||
});
|
||||
}
|
||||
} else if ('uuid' in event) {
|
||||
eventRecord = Object.assign(eventRecord, {
|
||||
courseid: event.courseid,
|
||||
|
@ -1622,23 +1635,20 @@ export class AddonCalendarProvider {
|
|||
* Store events in local DB.
|
||||
*
|
||||
* @param events Events to store.
|
||||
* @param siteId ID of the site the event belongs to. If not defined, use current site.
|
||||
* @param options Options.
|
||||
* @return Promise resolved when the events are stored.
|
||||
*/
|
||||
protected async storeEventsInLocalDB(
|
||||
events: (AddonCalendarGetEventsEvent | AddonCalendarCalendarEvent)[],
|
||||
siteId?: string,
|
||||
events: (AddonCalendarGetEventsEvent | AddonCalendarCalendarEvent | AddonCalendarEvent)[],
|
||||
options: AddonCalendarStoreEventsOptions = {},
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
siteId = site.getId();
|
||||
|
||||
await Promise.all(events.map((event) => this.storeEventInLocalDb(event, siteId)));
|
||||
await Promise.all(events.map((event) => this.storeEventInLocalDb(event, options)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a calendar event.
|
||||
*
|
||||
* @param eventId ID of the event. If undefined/null, create a new event.
|
||||
* @param eventId ID of the event. Negative value to edit offline event. If undefined/null, create a new event.
|
||||
* @param formData Form data.
|
||||
* @param timeCreated The time the event was created. Only if modifying a new offline event.
|
||||
* @param forceOffline True to always save it in offline.
|
||||
|
@ -1648,19 +1658,26 @@ export class AddonCalendarProvider {
|
|||
async submitEvent(
|
||||
eventId: number | undefined,
|
||||
formData: AddonCalendarSubmitCreateUpdateFormDataWSParams,
|
||||
timeCreated?: number,
|
||||
forceOffline = false,
|
||||
siteId?: string,
|
||||
options: AddonCalendarSubmitEventOptions = {},
|
||||
): Promise<{sent: boolean; event: AddonCalendarOfflineEventDBRecord | AddonCalendarEvent}> {
|
||||
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
const siteId = options.siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
// Function to store the event to be synchronized later.
|
||||
const storeOffline = (): Promise<{ sent: boolean; event: AddonCalendarOfflineEventDBRecord }> =>
|
||||
AddonCalendarOffline.saveEvent(eventId, formData, timeCreated, siteId).then((event) =>
|
||||
({ sent: false, event }));
|
||||
const storeOffline = async (): Promise<{ sent: boolean; event: AddonCalendarOfflineEventDBRecord }> => {
|
||||
const event = await AddonCalendarOffline.saveEvent(eventId, formData, siteId);
|
||||
|
||||
if (forceOffline || !CoreApp.isOnline()) {
|
||||
// Now save the reminders if any.
|
||||
if (options.reminders) {
|
||||
await CoreUtils.ignoreErrors(
|
||||
Promise.all(options.reminders.map((reminder) => this.addEventReminder(event, reminder.time, siteId))),
|
||||
);
|
||||
}
|
||||
|
||||
return { sent: false, event };
|
||||
};
|
||||
|
||||
if (options.forceOffline || !CoreApp.isOnline()) {
|
||||
// App is offline, store the event.
|
||||
return storeOffline();
|
||||
}
|
||||
|
@ -1672,6 +1689,13 @@ export class AddonCalendarProvider {
|
|||
try {
|
||||
const event = await this.submitEventOnline(eventId, formData, siteId);
|
||||
|
||||
// Now save the reminders if any.
|
||||
if (options.reminders) {
|
||||
await CoreUtils.ignoreErrors(
|
||||
Promise.all(options.reminders.map((reminder) => this.addEventReminder(event, reminder.time, siteId))),
|
||||
);
|
||||
}
|
||||
|
||||
return ({ sent: true, event });
|
||||
} catch (error) {
|
||||
if (error && !CoreUtils.isWebServiceError(error)) {
|
||||
|
@ -1731,6 +1755,11 @@ export class AddonCalendarProvider {
|
|||
);
|
||||
}
|
||||
|
||||
if (formData.id === 0) {
|
||||
// Store the new event in local DB.
|
||||
await CoreUtils.ignoreErrors(this.storeEventInLocalDb(result.event, { addDefaultReminder: false, siteId }));
|
||||
}
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
|
@ -2267,3 +2296,22 @@ export type AddonCalendarValueAndUnit = {
|
|||
value: number;
|
||||
unit: AddonCalendarReminderUnits;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options to pass to submit event.
|
||||
*/
|
||||
export type AddonCalendarSubmitEventOptions = {
|
||||
reminders?: {
|
||||
time: number | null;
|
||||
}[];
|
||||
forceOffline?: boolean;
|
||||
siteId?: string; // Site ID. If not defined, current site.
|
||||
};
|
||||
|
||||
/**
|
||||
* Options to pass to store events in local DB.
|
||||
*/
|
||||
export type AddonCalendarStoreEventsOptions = {
|
||||
addDefaultReminder?: boolean; // Whether to add default reminder for new events with no reminders. Defaults to true.
|
||||
siteId?: string; // Site ID. If not defined, current site.
|
||||
};
|
||||
|
|
|
@ -653,9 +653,19 @@ export class CoreLocalNotificationsProvider {
|
|||
*/
|
||||
async trigger(notification: ILocalNotification): Promise<number> {
|
||||
const db = await this.appDB;
|
||||
let time = Date.now();
|
||||
if (notification.trigger?.at) {
|
||||
// The type says "at" is a Date, but in Android we can receive timestamps instead.
|
||||
if (typeof notification.trigger.at === 'number') {
|
||||
time = <number> notification.trigger.at;
|
||||
} else {
|
||||
time = notification.trigger.at.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
const entry = {
|
||||
id: notification.id,
|
||||
at: notification.trigger && notification.trigger.at ? notification.trigger.at.getTime() : Date.now(),
|
||||
at: time,
|
||||
};
|
||||
|
||||
return db.insertRecord(TRIGGERED_TABLE_NAME, entry);
|
||||
|
|
Loading…
Reference in New Issue