Subscription and contract management app for Android based on Ionic and Angular. Written in TypeScript.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

246 lines
10 KiB

import { Injectable } from '@angular/core';
import { Storage } from '@capacitor/storage';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { ISubscription } from '../tab-overview/Interfaces/subscriptionInterface';
import { ISettings } from '../tab-settings/Interfaces/settingsInterface';
import { ToastController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class StorageService {
defaultSettings: ISettings = {
hideOverviewHelperTextGeneral: false,
hideOverviewHelperTextMenuBar: false
}
constructor(
private toastController: ToastController,
private translateService: TranslateService) { }
async retrieveSubscriptionsFromStorage(): Promise<ISubscription[]> {
const entries = await Storage.get({ key: 'subscriptions' });
if (entries.value) {
return JSON.parse(entries.value);
} else {
return [];
}
}
async saveSubscriptionsToStorage(entries: ISubscription[]) {
await Storage.set({
key: 'subscriptions',
value: JSON.stringify(entries)
});
}
async retrieveSettingsFromStorage(): Promise<ISettings> {
const settingsString = await Storage.get({ key: 'settings' });
if (settingsString.value) {
return JSON.parse(settingsString.value);
} else {
return this.defaultSettings;
}
}
async saveSettingsToStorage(settings: ISettings) {
await Storage.set({
key: 'settings',
value: JSON.stringify(settings)
});
}
async getAllData(): Promise<string> {
const entries = await this.retrieveSubscriptionsFromStorage();
const set = await this.retrieveSettingsFromStorage();
const backup = {
subscriptions: entries,
settings: set
};
return JSON.stringify(backup);
}
async backupAllDataAndroid() {
const backup = await this.getAllData();
try {
await Filesystem.writeFile({
path: 'subz-backup.json',
data: backup,
directory: Directory.Documents,
encoding: Encoding.UTF8
});
this.translateService.get('TABS.SETTINGS.BACKUP_SUCCESS').subscribe(BACKUP_SUCCESS => {
this.toastMessage(BACKUP_SUCCESS + ' Documents/subz-backup.json');
});
} catch (e) {
this.translateService.get('TABS.SETTINGS.BACKUP_ERROR').subscribe(BACKUP_ERROR => {
this.toastMessage(BACKUP_ERROR);
});
}
}
async restoreAllDataAndroid(mergeWithCurrent?: boolean) {
try {
await Filesystem.readFile({
path: 'subz-backup.json',
directory: Directory.Documents,
encoding: Encoding.UTF8
}).then((fileReadResult) => {
this.restoreAllData(fileReadResult.data, mergeWithCurrent);
});
this.translateService.get('TABS.SETTINGS.RESTORE_BACKUP_SUCCESS').subscribe(RESTORE_BACKUP_SUCCESS => {
this.toastMessage(RESTORE_BACKUP_SUCCESS);
});
} catch (e) {
this.translateService.get('TABS.SETTINGS.RESTORE_BACKUP_ERROR_ANDROID').subscribe(RESTORE_BACKUP_ERROR_ANDROID => {
this.toastMessage(RESTORE_BACKUP_ERROR_ANDROID);
});
}
}
/**
* Restores passed data
* @param backup Backup data which shall be restored like { subscriptions: ISubscription[], settings: ISettings }
* @param mergeWithCurrent If true, backup and current subscriptions will be merged by their id, keeps newer
* subscription if lastEdited property is present, else keeps the backup subscription
*/
async restoreAllData(backup: string, mergeWithCurrent?: boolean) {
let backupObject: { subscriptions: ISubscription[], settings: ISettings };
try {
backupObject = JSON.parse(backup);
if (!backupObject.hasOwnProperty('subscriptions') && !backupObject.hasOwnProperty('settings')) {
throw Error;
}
// SUBSCRIPTIONS
let subscriptions: ISubscription[] = backupObject.subscriptions;
let isValid = true;
for (const subscription of subscriptions) {
// Mandatory fields
isValid = 'id' in subscription && typeof subscription.id === 'number' &&
'name' in subscription && typeof subscription.name === 'string' &&
'cost' in subscription && typeof subscription.cost === 'number' &&
'color' in subscription && typeof subscription.color === 'string' &&
'billingStart' in subscription && typeof subscription.billingStart === 'string' &&
'billingEvery' in subscription && typeof subscription.billingEvery === 'number' &&
'billingInterval' in subscription && typeof subscription.billingInterval === 'string' &&
'contractStart' in subscription && typeof subscription.contractStart === 'string' &&
'minimumContractDuration' in subscription && typeof subscription.minimumContractDuration === 'number' &&
'minimumContractDurationInterval' in subscription && typeof subscription.minimumContractDurationInterval === 'string' &&
'extensionAfterMinimumContractDurationEvery' in subscription
&& typeof subscription.extensionAfterMinimumContractDurationEvery === 'number' &&
'extensionAfterMinimumContractDurationInterval' in subscription
&& typeof subscription.extensionAfterMinimumContractDurationInterval === 'string' &&
'cancelationPeriodEvery' in subscription && typeof subscription.cancelationPeriodEvery === 'number' &&
'cancelationPeriodInterval' in subscription && typeof subscription.cancelationPeriodInterval === 'string';
if (!isValid) { throw Error; }
// Optional fields
if ('description' in subscription) { this.throwErrorHelper(typeof subscription.description !== 'string'); }
if ('notificationBeforeCancelationPeriodInDays' in subscription) {
this.throwErrorHelper(typeof subscription.notificationBeforeCancelationPeriodInDays !== 'number'
&& typeof subscription.notificationBeforeCancelationPeriodInDays !== 'object');
}
if ('lastEdited' in subscription) {
this.throwErrorHelper(typeof subscription.lastEdited !== 'number' && typeof subscription.lastEdited !== 'object'); }
if ('created' in subscription) {
this.throwErrorHelper(typeof subscription.created !== 'number' && typeof subscription.created !== 'object'); }
}
// SETTINGS
const settings: ISettings = backupObject.settings;
if ('forceDarkMode' in settings) { this.throwErrorHelper(typeof settings.forceDarkMode !== 'boolean'); }
if ('currency' in settings) { this.throwErrorHelper(typeof settings.currency !== 'string'); }
if ('dateFormat' in settings) { this.throwErrorHelper(typeof settings.dateFormat !== 'string'); }
if ('notificationBeforeCancelationPeriodInDays' in settings) {
// Can be null, so it can be an object
this.throwErrorHelper(typeof settings.notificationBeforeCancelationPeriodInDays !== 'number'
&& typeof settings.notificationBeforeCancelationPeriodInDays !== 'object');
}
if ('defaultBillingInterval' in settings) { this.throwErrorHelper(typeof settings.defaultBillingInterval !== 'string'); }
if ('defaultSortBy' in settings) { this.throwErrorHelper(typeof settings.defaultSortBy !== 'string'); }
if ('hideOverviewHelperTextGeneral' in settings) {
this.throwErrorHelper(typeof settings.hideOverviewHelperTextGeneral !== 'boolean'); }
if ('hideOverviewHelperTextMenuBar' in settings) {
this.throwErrorHelper(typeof settings.hideOverviewHelperTextMenuBar !== 'boolean'); }
// Merge current subscriptions with backup based on id
if (mergeWithCurrent) {
const currentSubscriptions = await this.retrieveSubscriptionsFromStorage();
for (let currentSubscription of currentSubscriptions) {
// Check if subscription with same id is present in backup and current subscriptions
let backupSubscriptionIndex = subscriptions.findIndex(sub => sub.id === currentSubscription.id);
if (backupSubscriptionIndex !== -1) {
// Check which subscription will be kept based on lastEdited date (4 cases)
// 1. Case: Both have lastEdited, so use the one with newer date
if (currentSubscription.lastEdited && subscriptions[backupSubscriptionIndex].lastEdited) {
if (currentSubscription.lastEdited > subscriptions[backupSubscriptionIndex].lastEdited) {
subscriptions[backupSubscriptionIndex] = currentSubscription;
}
// Otherwise nothing todo, as backup subscription is used
}
// 2. Case: Only current subscription has lastEdited (= is newer)
else if (currentSubscription.lastEdited && !subscriptions[backupSubscriptionIndex].lastEdited) {
subscriptions[backupSubscriptionIndex] = currentSubscription;
}
// 3. Case: Only backup subscription has lastEdited (= is newer)
else if (!currentSubscription.lastEdited && subscriptions[backupSubscriptionIndex].lastEdited) {
// Nothing todo, as backup subscription is used
}
// 4. Case: Both don't have lastEdited, use the backup one
else if (!currentSubscription.lastEdited && !subscriptions[backupSubscriptionIndex].lastEdited) {
// Nothing todo, as backup subscription is used
}
else {
// Invalid case
throw Error;
}
} else {
// Current subscription can just be appended
subscriptions.push(currentSubscription);
}
}
}
this.saveSubscriptionsToStorage(subscriptions);
this.saveSettingsToStorage(settings);
this.translateService.get('TABS.SETTINGS.RESTORE_BACKUP_SUCCESS').subscribe(RESTORE_BACKUP_SUCCESS => {
this.toastMessage(RESTORE_BACKUP_SUCCESS);
});
} catch (exception) {
this.translateService.get('TABS.SETTINGS.RESTORE_BACKUP_ERROR').subscribe(RESTORE_BACKUP_ERROR => {
this.toastMessage(RESTORE_BACKUP_ERROR);
});
}
}
throwErrorHelper(boolVar: boolean) {
if (boolVar) { throw Error; }
}
async toastMessage(toastMessage: string) {
const toast = await this.toastController.create({
cssClass: 'toast-center-text',
message: toastMessage,
duration: 3000
});
toast.present();
}
}