Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 2x 2x 2x 10x 3x 1x 3x 2x 1x 5x 5x 1x 5x 5x 5x 30x 3x 5x | // Made with ❤️ by [Vibey](https://the-vibey-project.github.io/vibey/), Developed by [Adam Matthew Steinberger](https://vibewithadam.matthewsteinberger.com/) ([@adammatthewsteinberger](https://github.com/adammatthewsteinberger/)).
/** The settings model: pure, so every screen and store agrees on it. Declared by `interfaces/settings-interface.ts`. */
import { DEFAULT_THEME_MODE, THEME_MODES } from '@vibey/core';
import type { ThemeMode } from '@vibey/core';
import type {
KryptonSettings,
NotificationClass,
SettingsChange,
SettingsModelInterface,
} from './interfaces/settings-interface';
export class SettingsModel implements SettingsModelInterface {
static readonly CLASSES: readonly NotificationClass[] = [
'gate',
'finished',
'failed',
'budget',
'unlimited-reminder',
'device-paired',
];
/** What each class is, in the words the Settings screen shows. */
static readonly LABELS: Readonly<Record<NotificationClass, string>> = {
gate: 'A gate needs you',
finished: 'A run or an ULTRA pass finished',
failed: 'A run failed or parked',
budget: 'A budget step was reached',
'unlimited-reminder': 'Reminder: spending has no cap',
'device-paired': 'A device was paired',
};
readonly defaults: KryptonSettings = {
theme: DEFAULT_THEME_MODE,
sounds: true,
volume: 0.6,
notifications: {
gate: true,
finished: true,
failed: true,
budget: true,
'unlimited-reminder': true,
'device-paired': true,
},
};
apply(settings: KryptonSettings, change: SettingsChange): KryptonSettings {
switch (change.type) {
case 'theme':
return { ...settings, theme: change.theme };
case 'sounds':
return { ...settings, sounds: change.on };
case 'volume':
return { ...settings, volume: SettingsModel.clamp(change.volume) };
case 'notification':
return { ...settings, notifications: { ...settings.notifications, [change.which]: change.on } };
case 'reset':
return this.defaults;
}
}
load(text: string | null): KryptonSettings {
let stored: unknown;
try {
stored = text === null ? {} : JSON.parse(text);
} catch {
stored = {};
}
const record = typeof stored === 'object' && stored !== null ? (stored as Record<string, unknown>) : {};
const notifications = typeof record.notifications === 'object' && record.notifications !== null
? (record.notifications as Record<string, unknown>)
: {};
return {
theme: (THEME_MODES as readonly unknown[]).includes(record.theme) ? (record.theme as ThemeMode) : this.defaults.theme,
sounds: typeof record.sounds === 'boolean' ? record.sounds : this.defaults.sounds,
volume: typeof record.volume === 'number' ? SettingsModel.clamp(record.volume) : this.defaults.volume,
notifications: Object.fromEntries(
SettingsModel.CLASSES.map((which) => [
which,
typeof notifications[which] === 'boolean' ? notifications[which] : this.defaults.notifications[which],
]),
) as Record<NotificationClass, boolean>,
};
}
save(settings: KryptonSettings): string {
return JSON.stringify(settings);
}
private static clamp(volume: number): number {
return Number.isFinite(volume) ? Math.min(1, Math.max(0, volume)) : 0;
}
}
|