mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-11 12:12:55 +00:00
205 lines
6.2 KiB
TypeScript
205 lines
6.2 KiB
TypeScript
/*
|
|
* Copyright (C) 2021 StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public Licens for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import {Injectable, OnDestroy} from '@angular/core';
|
|
import {
|
|
DateSeriesRelevantData,
|
|
dateSeriesRelevantKeys,
|
|
formatRelevantKeys,
|
|
ScheduleProvider,
|
|
} from '../../calendar/schedule.provider';
|
|
import {SCDateSeries, SCThingType, SCUuid} from '@openstapps/core';
|
|
import {Device} from '@capacitor/device';
|
|
import {LocalNotifications} from '@capacitor/local-notifications';
|
|
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
|
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
|
import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
|
import {StorageProvider} from '../../storage/storage.provider';
|
|
import {CalendarService} from '../../calendar/calendar.service';
|
|
import {flatMap} from 'lodash-es';
|
|
import {toICal} from '../../calendar/ical/ical';
|
|
import {Subscription} from 'rxjs';
|
|
import {ChangesOf} from './changes';
|
|
import {hashStringToInt} from './hash';
|
|
import {
|
|
CALENDAR_NOTIFICATIONS_ENABLED_KEY,
|
|
CALENDAR_SYNC_ENABLED_KEY,
|
|
CALENDAR_SYNC_SETTINGS_KEY,
|
|
} from '../../settings/page/calendar-sync-settings-keys';
|
|
import {filter} from 'rxjs/operators';
|
|
|
|
@Injectable()
|
|
export class ScheduleSyncService implements OnDestroy {
|
|
constructor(
|
|
private scheduleProvider: ScheduleProvider,
|
|
private storageProvider: StorageProvider,
|
|
private translator: ThingTranslateService,
|
|
private dateFormatPipe: DateFormatPipe,
|
|
private durationFormatPipe: DurationPipe,
|
|
private calendar: CalendarService,
|
|
) {
|
|
this.scheduleProvider.uuids$
|
|
.pipe(filter(uuids => uuids?.length > 0))
|
|
.subscribe(uuids => {
|
|
this.uuids = uuids;
|
|
void this.syncNativeCalendar();
|
|
});
|
|
}
|
|
|
|
uuids: SCUuid[];
|
|
|
|
uuidSubscription: Subscription;
|
|
|
|
ngOnDestroy() {
|
|
this.uuidSubscription?.unsubscribe();
|
|
}
|
|
|
|
private async isSyncEnabled(): Promise<boolean> {
|
|
return await this.storageProvider.get(
|
|
`${CALENDAR_SYNC_SETTINGS_KEY}.${CALENDAR_SYNC_ENABLED_KEY}`,
|
|
);
|
|
}
|
|
|
|
private async isNotificationsEnabled(): Promise<boolean> {
|
|
return await this.storageProvider.get(
|
|
`${CALENDAR_SYNC_SETTINGS_KEY}.${CALENDAR_NOTIFICATIONS_ENABLED_KEY}`,
|
|
);
|
|
}
|
|
|
|
async enable() {
|
|
if ((await Device.getInfo()).platform === 'web') return;
|
|
|
|
await BackgroundFetch.stop();
|
|
|
|
if (
|
|
[this.isSyncEnabled, this.isNotificationsEnabled].some(
|
|
async it => await it(),
|
|
)
|
|
) {
|
|
const status = await BackgroundFetch.configure(
|
|
{
|
|
minimumFetchInterval: 15,
|
|
stopOnTerminate: false,
|
|
enableHeadless: true,
|
|
},
|
|
async taskId => {
|
|
await Promise.all([
|
|
this.postDifferencesNotification(),
|
|
this.syncNativeCalendar(),
|
|
]);
|
|
|
|
await BackgroundFetch.finish(taskId);
|
|
},
|
|
);
|
|
|
|
if (status !== BackgroundFetch.STATUS_AVAILABLE) {
|
|
if (status === BackgroundFetch.STATUS_DENIED) {
|
|
console.error(
|
|
'The user explicitly disabled background behavior for this app or for the whole system.',
|
|
);
|
|
} else if (status === BackgroundFetch.STATUS_RESTRICTED) {
|
|
console.error(
|
|
'Background updates are unavailable and the user cannot enable them again.',
|
|
);
|
|
}
|
|
} else {
|
|
console.info('Starting background fetch.');
|
|
|
|
await BackgroundFetch.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
async getDifferences(): Promise<
|
|
ChangesOf<SCDateSeries, DateSeriesRelevantData>[]
|
|
> {
|
|
const partialEvents = this.scheduleProvider.partialEvents$.getValue();
|
|
|
|
const result = (
|
|
await this.scheduleProvider.getDateSeries(partialEvents.map(it => it.uid))
|
|
).dates;
|
|
|
|
return result
|
|
.map(it => ({
|
|
new: it,
|
|
old: partialEvents.find(partialEvent => partialEvent.uid === it.uid),
|
|
}))
|
|
.map(it => ({
|
|
...it,
|
|
changes: it.old
|
|
? (Object.keys(it.old) as Array<keyof DateSeriesRelevantData>).filter(
|
|
key =>
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
JSON.stringify(it.old![key]) !== JSON.stringify(it.new[key]),
|
|
)
|
|
: dateSeriesRelevantKeys,
|
|
}));
|
|
}
|
|
|
|
private formatChanges(
|
|
changes: ChangesOf<SCDateSeries, DateSeriesRelevantData>,
|
|
): string[] {
|
|
return changes.changes.map(
|
|
change =>
|
|
`${
|
|
this.translator.translator.translatedPropertyNames<SCDateSeries>(
|
|
SCThingType.DateSeries,
|
|
)?.[change]
|
|
}: ${formatRelevantKeys[change](
|
|
changes.new[change] as never,
|
|
this.dateFormatPipe,
|
|
this.durationFormatPipe,
|
|
)}`,
|
|
);
|
|
}
|
|
|
|
async syncNativeCalendar() {
|
|
if (!(await this.isSyncEnabled())) return;
|
|
|
|
const dateSeries = (await this.scheduleProvider.getDateSeries(this.uuids))
|
|
.dates;
|
|
|
|
const events = flatMap(dateSeries, event =>
|
|
toICal(event, this.translator.translator, {
|
|
allowRRuleExceptions: false,
|
|
excludeCancelledEvents: true,
|
|
}),
|
|
);
|
|
|
|
return this.calendar.syncEvents(events);
|
|
}
|
|
|
|
async postDifferencesNotification() {
|
|
if (!(await this.isNotificationsEnabled())) return;
|
|
|
|
const differences = (await this.getDifferences()).filter(
|
|
it => it.changes.length > 0,
|
|
);
|
|
if (differences.length === 0) return;
|
|
|
|
if ((await Device.getInfo()).platform === 'web') {
|
|
// TODO: Implement web notification
|
|
} else {
|
|
await LocalNotifications.schedule({
|
|
notifications: differences.map(it => ({
|
|
title: it.new.event.name,
|
|
body: this.formatChanges(it).join('\n'),
|
|
id: hashStringToInt(it.new.uid),
|
|
})),
|
|
});
|
|
}
|
|
}
|
|
}
|