@focus8/settings-registry 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
- package/src/index.ts +2461 -2460
- package/tsconfig.json +22 -22
package/src/index.ts
CHANGED
|
@@ -1,2460 +1,2461 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Settings Registry — single source of truth for ALL FocusPlanner app settings.
|
|
3
|
-
*
|
|
4
|
-
* This shared package defines every setting key, its category, data type,
|
|
5
|
-
* default value, and whether it should be synced to the server.
|
|
6
|
-
*
|
|
7
|
-
* Used by:
|
|
8
|
-
* - The mobile app (local SQLite settings table, React Query hooks)
|
|
9
|
-
* - The API (validate & serialize settings profiles)
|
|
10
|
-
* - The web portal (render settings viewer/editor)
|
|
11
|
-
*
|
|
12
|
-
* App-specific defaults (theme, locale, enrolled mode) are parameterized
|
|
13
|
-
* via `createRegistry(config)`.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Setting types shared across mobile, API, and web
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
export type ThemeSetting = 'light' | 'dark' | 'system';
|
|
21
|
-
export type AlarmSound =
|
|
22
|
-
| 'none'
|
|
23
|
-
| 'alarm1'
|
|
24
|
-
| 'alarm2'
|
|
25
|
-
| 'alarm3'
|
|
26
|
-
| 'alarm4'
|
|
27
|
-
| 'alarm5'
|
|
28
|
-
| 'alarm6'
|
|
29
|
-
| 'alarm7'
|
|
30
|
-
| 'alarm8'
|
|
31
|
-
| 'alarm9';
|
|
32
|
-
export type LocaleCode = 'en' | 'nb';
|
|
33
|
-
|
|
34
|
-
export type CalendarType = 'chronological' | 'time-based';
|
|
35
|
-
export type ClockType = 'digital' | 'analog';
|
|
36
|
-
export type TimePickerMode = 'dials' | 'keypad';
|
|
37
|
-
export type AlarmTimeout = 5 | 30 | 60 | 120 | 180 | 300 | 600;
|
|
38
|
-
export const ALARM_TIMEOUTS: AlarmTimeout[] = [5, 30, 60, 120, 180, 300, 600];
|
|
39
|
-
export type CalendarViewMode =
|
|
40
|
-
| 'day'
|
|
41
|
-
| '3-days'
|
|
42
|
-
| '5-days'
|
|
43
|
-
| '7-days'
|
|
44
|
-
| 'week'
|
|
45
|
-
| 'month'
|
|
46
|
-
| 'overview';
|
|
47
|
-
export type CalendarDayViewCellZoom = 15 | 30 | 60;
|
|
48
|
-
export type InactivityTimeoutMinutes = 1 | 5 | 10 | 15 | 30 | 45;
|
|
49
|
-
export type LockScreenClockDisplay = 'none' | 'digital' | 'analog';
|
|
50
|
-
export type LockScreenImageMode = 'none' | 'background' | 'photoFrame';
|
|
51
|
-
export type PhotoFrameIntervalSeconds = 30 | 60 | 120 | 300;
|
|
52
|
-
export type EventVisibility = 'Public' | 'Private' | 'Custom';
|
|
53
|
-
export type SuggestEndTimeUnit = 'minutes' | 'hours';
|
|
54
|
-
export type ChronologicalDayViewDisplayMode = 'list' | 'timeline';
|
|
55
|
-
|
|
56
|
-
export type WeatherLocation = {
|
|
57
|
-
address: string;
|
|
58
|
-
name?: string;
|
|
59
|
-
latitude: number;
|
|
60
|
-
longitude: number;
|
|
61
|
-
placeId?: string;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type LockScreenImage = {
|
|
65
|
-
uri: string;
|
|
66
|
-
filePath: string;
|
|
67
|
-
fileName?: string;
|
|
68
|
-
fileSize?: number;
|
|
69
|
-
width?: number;
|
|
70
|
-
height?: number;
|
|
71
|
-
order: number;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const LOCK_SCREEN_MAX_IMAGES = 10;
|
|
75
|
-
|
|
76
|
-
export type AllDayPreset = {
|
|
77
|
-
daysBefore: number;
|
|
78
|
-
time: string;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// UI type discriminator — determines which component renders each setting
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
export type SettingUiType =
|
|
86
|
-
| 'TOGGLE'
|
|
87
|
-
| 'SELECT'
|
|
88
|
-
| 'VOLUME_SLIDER'
|
|
89
|
-
| 'SLIDER'
|
|
90
|
-
| 'NUMBER_INPUT'
|
|
91
|
-
| 'TEXT_INPUT'
|
|
92
|
-
| 'PIN_INPUT'
|
|
93
|
-
| 'CUSTOM_THEME_PICKER'
|
|
94
|
-
| 'CUSTOM_LANGUAGE_PICKER'
|
|
95
|
-
| 'CUSTOM_CALENDAR_TYPE'
|
|
96
|
-
| 'CUSTOM_CLOCK_TYPE'
|
|
97
|
-
| 'CUSTOM_WEATHER_LOCATION'
|
|
98
|
-
| 'CUSTOM_IMAGE'
|
|
99
|
-
| 'CUSTOM_IMAGE_ARRAY'
|
|
100
|
-
| 'CUSTOM_CALENDAR_IDS'
|
|
101
|
-
| 'CUSTOM_REMINDER_PRESETS'
|
|
102
|
-
| 'CUSTOM_EVENT_FORM'
|
|
103
|
-
| 'CUSTOM_CHRONOLOGICAL_EVENT_FORM'
|
|
104
|
-
| 'CUSTOM_DISPLAY_DENSITY'
|
|
105
|
-
| 'CUSTOM_BRIGHTNESS'
|
|
106
|
-
| 'CUSTOM_SPLIT_VIEW_CONFIG'
|
|
107
|
-
| 'CUSTOM_EVENT_CATEGORIES'
|
|
108
|
-
| 'HIDDEN';
|
|
109
|
-
|
|
110
|
-
export type SettingOption<T = unknown> = {
|
|
111
|
-
/** The option value */
|
|
112
|
-
value: T;
|
|
113
|
-
/** i18n message key for the option label */
|
|
114
|
-
labelKey: string;
|
|
115
|
-
/** Optional values to pass to the i18n message formatter */
|
|
116
|
-
labelValues?: Record<string, unknown>;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export type SliderConfig = {
|
|
120
|
-
min: number;
|
|
121
|
-
max: number;
|
|
122
|
-
step: number;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
// Visibility & disabled conditions — declarative rules for conditional display
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
|
|
129
|
-
export type VisibilityCondition =
|
|
130
|
-
| { dependsOn: string; equals: unknown }
|
|
131
|
-
| { dependsOn: string; notEquals: unknown }
|
|
132
|
-
| { dependsOn: string; isTruthy: true };
|
|
133
|
-
|
|
134
|
-
/** Single condition or array of conditions (all must be true = AND). */
|
|
135
|
-
export type VisibilityRule = VisibilityCondition | VisibilityCondition[];
|
|
136
|
-
|
|
137
|
-
/** Alias — same shape, controls interactivity instead of visibility. */
|
|
138
|
-
export type DisabledRule = VisibilityCondition | VisibilityCondition[];
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Evaluate a visibility/disabled rule against current setting values.
|
|
142
|
-
* Returns `true` if the setting should be visible (or disabled, depending on usage).
|
|
143
|
-
* `undefined` rule = always true (no restriction).
|
|
144
|
-
*/
|
|
145
|
-
export function evaluateVisibility(
|
|
146
|
-
rule: VisibilityRule | undefined,
|
|
147
|
-
getValue: (key: string) => unknown,
|
|
148
|
-
): boolean {
|
|
149
|
-
if (!rule) return true;
|
|
150
|
-
const conditions: VisibilityCondition[] = Array.isArray(rule) ? rule : [rule];
|
|
151
|
-
return conditions.every((c) => {
|
|
152
|
-
const value = getValue(c.dependsOn);
|
|
153
|
-
if ('equals' in c) return value === c.equals;
|
|
154
|
-
if ('notEquals' in c) return value !== c.notEquals;
|
|
155
|
-
if ('isTruthy' in c) return !!value;
|
|
156
|
-
return true;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Convenience: returns true when the setting should be disabled. */
|
|
161
|
-
export function evaluateDisabled(
|
|
162
|
-
rule: DisabledRule | undefined,
|
|
163
|
-
getValue: (key: string) => unknown,
|
|
164
|
-
): boolean {
|
|
165
|
-
if (!rule) return false;
|
|
166
|
-
return evaluateVisibility(rule, getValue);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Setting categories — used for UI grouping and profile organization
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
export const SETTINGS_CATEGORIES = [
|
|
174
|
-
'appearance',
|
|
175
|
-
'calendarView',
|
|
176
|
-
'calendars',
|
|
177
|
-
'sound',
|
|
178
|
-
'timer',
|
|
179
|
-
'media',
|
|
180
|
-
'lockScreen',
|
|
181
|
-
'touch',
|
|
182
|
-
'device',
|
|
183
|
-
'language',
|
|
184
|
-
'notification',
|
|
185
|
-
'chronological',
|
|
186
|
-
'eventForm',
|
|
187
|
-
'chronologicalEventForm',
|
|
188
|
-
] as const;
|
|
189
|
-
|
|
190
|
-
export type SettingsCategory = (typeof SETTINGS_CATEGORIES)[number];
|
|
191
|
-
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
// Category display labels — human-readable names for UI rendering
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
|
|
196
|
-
export const CATEGORY_LABELS: Record<SettingsCategory, string> = {
|
|
197
|
-
appearance: 'Appearance',
|
|
198
|
-
calendarView: 'Calendar View',
|
|
199
|
-
calendars: 'Calendars',
|
|
200
|
-
sound: 'Sound & Alerts',
|
|
201
|
-
timer: 'Timer',
|
|
202
|
-
media: 'Media',
|
|
203
|
-
lockScreen: 'Lock Screen',
|
|
204
|
-
touch: 'Touch & Gestures',
|
|
205
|
-
device: 'Device',
|
|
206
|
-
language: 'Language',
|
|
207
|
-
notification: 'Notifications',
|
|
208
|
-
chronological: 'Chronological',
|
|
209
|
-
eventForm: 'Event Form',
|
|
210
|
-
chronologicalEventForm: 'Event Form (Chronological)',
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// Category icons — lucide icon names matching the mobile app
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
export const CATEGORY_ICONS: Record<SettingsCategory, string> = {
|
|
218
|
-
appearance: 'Palette',
|
|
219
|
-
calendarView: 'Eye',
|
|
220
|
-
calendars: 'Calendar',
|
|
221
|
-
sound: 'Volume2',
|
|
222
|
-
timer: 'Timer',
|
|
223
|
-
media: 'Images',
|
|
224
|
-
lockScreen: 'Lock',
|
|
225
|
-
touch: 'Hand',
|
|
226
|
-
device: 'Smartphone',
|
|
227
|
-
language: 'Globe',
|
|
228
|
-
notification: 'Bell',
|
|
229
|
-
chronological: 'List',
|
|
230
|
-
eventForm: 'CalendarPlus',
|
|
231
|
-
chronologicalEventForm: 'CalendarPlus',
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
// Calendar-type filtering — which categories are exclusive to a calendar type
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
/** Categories only shown for chronological calendar type */
|
|
239
|
-
export const CHRONOLOGICAL_ONLY_CATEGORIES: ReadonlySet<SettingsCategory> =
|
|
240
|
-
new Set<SettingsCategory>(['chronological', 'chronologicalEventForm']);
|
|
241
|
-
|
|
242
|
-
/** Categories only shown for time-based calendar type */
|
|
243
|
-
export const TIME_BASED_ONLY_CATEGORIES: ReadonlySet<SettingsCategory> =
|
|
244
|
-
new Set<SettingsCategory>(['eventForm']);
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Filter categories based on the active calendar type.
|
|
248
|
-
* Categories not exclusive to either type are always included.
|
|
249
|
-
*/
|
|
250
|
-
export function getCategoriesForCalendarType(
|
|
251
|
-
calendarType: CalendarType,
|
|
252
|
-
): SettingsCategory[] {
|
|
253
|
-
return SETTINGS_CATEGORIES.filter((cat) => {
|
|
254
|
-
if (
|
|
255
|
-
calendarType === 'time-based' &&
|
|
256
|
-
CHRONOLOGICAL_ONLY_CATEGORIES.has(cat)
|
|
257
|
-
) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
if (
|
|
261
|
-
calendarType === 'chronological' &&
|
|
262
|
-
TIME_BASED_ONLY_CATEGORIES.has(cat)
|
|
263
|
-
) {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
return true;
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ---------------------------------------------------------------------------
|
|
271
|
-
// Settings sections — top-level groupings (e.g. "Kalender", "Enhet")
|
|
272
|
-
// ---------------------------------------------------------------------------
|
|
273
|
-
|
|
274
|
-
export const SETTINGS_SECTION_IDS = ['calendar', 'unit'] as const;
|
|
275
|
-
export type SettingsSectionId = (typeof SETTINGS_SECTION_IDS)[number];
|
|
276
|
-
|
|
277
|
-
export type SettingsSectionDef = {
|
|
278
|
-
id: SettingsSectionId;
|
|
279
|
-
labelKey: string;
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
|
|
283
|
-
{ id: 'calendar', labelKey: 'Calendar.LabelCalendar' },
|
|
284
|
-
{ id: 'unit', labelKey: 'Settings.Unit' },
|
|
285
|
-
];
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// Settings menu items — ordered list of all items in the settings menu
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
|
|
291
|
-
export const SETTINGS_MENU_ITEM_IDS = [
|
|
292
|
-
// Calendar section
|
|
293
|
-
'calendarView',
|
|
294
|
-
'calendars',
|
|
295
|
-
'eventCategories',
|
|
296
|
-
'notification',
|
|
297
|
-
'timer',
|
|
298
|
-
'guests',
|
|
299
|
-
'eventForm',
|
|
300
|
-
'chronologicalEventForm',
|
|
301
|
-
'weather',
|
|
302
|
-
'gallery',
|
|
303
|
-
'audioClips',
|
|
304
|
-
'gestures',
|
|
305
|
-
'calendarType',
|
|
306
|
-
'sharing',
|
|
307
|
-
// Unit section
|
|
308
|
-
'screen',
|
|
309
|
-
'volume',
|
|
310
|
-
'lockScreen',
|
|
311
|
-
'language',
|
|
312
|
-
'wifi',
|
|
313
|
-
'devices',
|
|
314
|
-
'profiles',
|
|
315
|
-
'tts',
|
|
316
|
-
'updates',
|
|
317
|
-
'about',
|
|
318
|
-
'help',
|
|
319
|
-
'dev',
|
|
320
|
-
// Account (rendered separately)
|
|
321
|
-
'account',
|
|
322
|
-
'activityLog',
|
|
323
|
-
] as const;
|
|
324
|
-
|
|
325
|
-
export type SettingsMenuItemId = (typeof SETTINGS_MENU_ITEM_IDS)[number];
|
|
326
|
-
|
|
327
|
-
export type SettingsMenuItemDef = {
|
|
328
|
-
id: SettingsMenuItemId;
|
|
329
|
-
labelKey: string;
|
|
330
|
-
icon: string;
|
|
331
|
-
section: SettingsSectionId | 'account';
|
|
332
|
-
/** Screen/page identifiers rendered for this menu item */
|
|
333
|
-
screens: string[];
|
|
334
|
-
/**
|
|
335
|
-
* Setting key matchers for remote editing (web portal / profile editor).
|
|
336
|
-
* A setting is included if its key equals a matcher exactly, or starts
|
|
337
|
-
* with a matcher that ends with '.' (prefix match).
|
|
338
|
-
* Items without keys are navigation-only (e.g. gallery, guests).
|
|
339
|
-
*/
|
|
340
|
-
keys?: string[];
|
|
341
|
-
/** Only show for this calendar type (undefined = always) */
|
|
342
|
-
calendarType?: CalendarType;
|
|
343
|
-
/** Only show for this app mode (undefined = always) */
|
|
344
|
-
appMode?: 'ENROLLED';
|
|
345
|
-
/** If true, don't wrap content in a scroll container */
|
|
346
|
-
noScrollView?: boolean;
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Ordered list of all settings menu items. The array order defines the
|
|
351
|
-
* display order within each section. Items without registry setting keys
|
|
352
|
-
* are navigation-only (e.g. gallery, guests).
|
|
353
|
-
*/
|
|
354
|
-
export const SETTINGS_MENU_ITEMS: readonly SettingsMenuItemDef[] = [
|
|
355
|
-
// -- Calendar section --
|
|
356
|
-
{
|
|
357
|
-
id: 'calendarView',
|
|
358
|
-
labelKey: 'Settings.Calendar',
|
|
359
|
-
icon: 'Eye',
|
|
360
|
-
section: 'calendar',
|
|
361
|
-
screens: [
|
|
362
|
-
'calendarDayView',
|
|
363
|
-
'calendarWeekView',
|
|
364
|
-
'chronologicalHeader',
|
|
365
|
-
'chronologicalFooter',
|
|
366
|
-
'chronologicalMenu',
|
|
367
|
-
'dayColors',
|
|
368
|
-
'dateTime',
|
|
369
|
-
],
|
|
370
|
-
keys: [
|
|
371
|
-
'calendarView.showCalendarNames',
|
|
372
|
-
'calendarView.splitView',
|
|
373
|
-
'calendarView.dayViewZoom',
|
|
374
|
-
'calendarView.weekViewZoom',
|
|
375
|
-
'calendarView.calendarColumns',
|
|
376
|
-
'appearance.enableDayColors',
|
|
377
|
-
'chronological.dayView.displayMode',
|
|
378
|
-
'chronological.header.',
|
|
379
|
-
'chronological.footer.',
|
|
380
|
-
'chronological.menu.',
|
|
381
|
-
'chronological.quickSettings.',
|
|
382
|
-
'chronological.timeOfDay.',
|
|
383
|
-
],
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
id: 'calendars',
|
|
387
|
-
labelKey: 'Common.Calendars',
|
|
388
|
-
icon: 'Calendar',
|
|
389
|
-
section: 'calendar',
|
|
390
|
-
screens: ['calendars'],
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
id: 'eventCategories',
|
|
394
|
-
labelKey: 'EventCategory.SettingsTitle',
|
|
395
|
-
icon: 'Tag',
|
|
396
|
-
section: 'calendar',
|
|
397
|
-
screens: ['eventCategories'],
|
|
398
|
-
calendarType: 'chronological',
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
id: 'notification',
|
|
402
|
-
labelKey: 'Settings.Notification',
|
|
403
|
-
icon: 'Bell',
|
|
404
|
-
section: 'calendar',
|
|
405
|
-
screens: ['notification'],
|
|
406
|
-
keys: [
|
|
407
|
-
'sound.reminderAlarmSound',
|
|
408
|
-
'sound.startAlarmSound',
|
|
409
|
-
'sound.endAlarmSound',
|
|
410
|
-
'sound.reminderAlarmTimeout',
|
|
411
|
-
'sound.reminderVolume',
|
|
412
|
-
],
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
id: 'timer',
|
|
416
|
-
labelKey: 'Settings.TimerTitle',
|
|
417
|
-
icon: 'Timer',
|
|
418
|
-
section: 'calendar',
|
|
419
|
-
screens: ['timerFeatures', 'timer'],
|
|
420
|
-
appMode: 'ENROLLED',
|
|
421
|
-
keys: [
|
|
422
|
-
'chronological.timer.',
|
|
423
|
-
'timer.',
|
|
424
|
-
'sound.timerAlarmSound',
|
|
425
|
-
'sound.timerAlarmTimeout',
|
|
426
|
-
'sound.timerVolume',
|
|
427
|
-
],
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
id: 'guests',
|
|
431
|
-
labelKey: 'Settings.GuestUsers',
|
|
432
|
-
icon: 'Users',
|
|
433
|
-
section: 'calendar',
|
|
434
|
-
screens: ['guests'],
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
id: 'eventForm',
|
|
438
|
-
labelKey: 'SettingsEventForm.Title',
|
|
439
|
-
icon: 'ClipboardList',
|
|
440
|
-
section: 'calendar',
|
|
441
|
-
screens: ['eventForm'],
|
|
442
|
-
calendarType: 'time-based',
|
|
443
|
-
keys: ['eventForm.'],
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
id: 'chronologicalEventForm',
|
|
447
|
-
labelKey: 'SettingsChronologicalEventForm.Title',
|
|
448
|
-
icon: 'Plus',
|
|
449
|
-
section: 'calendar',
|
|
450
|
-
screens: ['chronologicalEventForm'],
|
|
451
|
-
calendarType: 'chronological',
|
|
452
|
-
keys: ['chronologicalEventForm.'],
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
id: 'weather',
|
|
456
|
-
labelKey: 'Settings.WeatherTitle',
|
|
457
|
-
icon: 'CloudSun',
|
|
458
|
-
section: 'calendar',
|
|
459
|
-
screens: ['weather'],
|
|
460
|
-
calendarType: 'time-based',
|
|
461
|
-
keys: [
|
|
462
|
-
'calendarView.showWeatherOnTimeline',
|
|
463
|
-
'calendarView.weatherLocation',
|
|
464
|
-
'calendarView.showWeatherOnEvents',
|
|
465
|
-
],
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
id: 'gallery',
|
|
469
|
-
labelKey: 'Gallery.Title',
|
|
470
|
-
icon: 'Images',
|
|
471
|
-
section: 'calendar',
|
|
472
|
-
screens: ['gallery'],
|
|
473
|
-
noScrollView: true,
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
id: 'audioClips',
|
|
477
|
-
labelKey: 'AudioClips.Title',
|
|
478
|
-
icon: 'AudioLines',
|
|
479
|
-
section: 'calendar',
|
|
480
|
-
screens: ['audioClips'],
|
|
481
|
-
noScrollView: true,
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
id: 'gestures',
|
|
485
|
-
labelKey: 'Settings.FunctionsTitle',
|
|
486
|
-
icon: 'Hand',
|
|
487
|
-
section: 'calendar',
|
|
488
|
-
screens: ['gestures'],
|
|
489
|
-
keys: ['touch.'],
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
id: 'calendarType',
|
|
493
|
-
labelKey: 'Settings.Mode',
|
|
494
|
-
icon: 'SlidersHorizontal',
|
|
495
|
-
section: 'calendar',
|
|
496
|
-
screens: ['calendarType'],
|
|
497
|
-
keys: ['calendarView.type'],
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
id: 'sharing',
|
|
501
|
-
labelKey: 'Settings.Sharing',
|
|
502
|
-
icon: 'Share2',
|
|
503
|
-
section: 'calendar',
|
|
504
|
-
screens: ['icalSubscriptions'],
|
|
505
|
-
},
|
|
506
|
-
// -- Unit section --
|
|
507
|
-
{
|
|
508
|
-
id: 'screen',
|
|
509
|
-
labelKey: 'Settings.Screen',
|
|
510
|
-
icon: 'LaptopMinimal',
|
|
511
|
-
section: 'unit',
|
|
512
|
-
screens: ['displayDensity', 'darkMode'],
|
|
513
|
-
keys: ['appearance.theme', 'appearance.clockType'],
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
id: 'volume',
|
|
517
|
-
labelKey: 'Common.Volume',
|
|
518
|
-
icon: 'Volume2',
|
|
519
|
-
section: 'unit',
|
|
520
|
-
screens: ['volume'],
|
|
521
|
-
appMode: 'ENROLLED',
|
|
522
|
-
keys: [
|
|
523
|
-
'sound.reminderVolume',
|
|
524
|
-
'sound.timerVolume',
|
|
525
|
-
'sound.mediaVolume',
|
|
526
|
-
],
|
|
527
|
-
},
|
|
528
|
-
{
|
|
529
|
-
id: 'lockScreen',
|
|
530
|
-
labelKey: 'Settings.LockScreen',
|
|
531
|
-
icon: 'Lock',
|
|
532
|
-
section: 'unit',
|
|
533
|
-
screens: ['inactivity', 'lockScreen'],
|
|
534
|
-
appMode: 'ENROLLED',
|
|
535
|
-
keys: ['lockScreen.'],
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
id: 'language',
|
|
539
|
-
labelKey: 'Settings.Language',
|
|
540
|
-
icon: 'Globe',
|
|
541
|
-
section: 'unit',
|
|
542
|
-
screens: ['language'],
|
|
543
|
-
keys: ['language.'],
|
|
544
|
-
},
|
|
545
|
-
{
|
|
546
|
-
id: 'wifi',
|
|
547
|
-
labelKey: 'Settings.WifiTitle',
|
|
548
|
-
icon: 'Wifi',
|
|
549
|
-
section: 'unit',
|
|
550
|
-
screens: ['wifi'],
|
|
551
|
-
appMode: 'ENROLLED',
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
id: 'devices',
|
|
555
|
-
labelKey: 'SettingsDevices.Title',
|
|
556
|
-
icon: 'Smartphone',
|
|
557
|
-
section: 'unit',
|
|
558
|
-
screens: ['devices'],
|
|
559
|
-
keys: [
|
|
560
|
-
'device.',
|
|
561
|
-
'calendarView.autoReturnToTodayEnabled',
|
|
562
|
-
'calendarView.autoReturnToTodayTimeoutSeconds',
|
|
563
|
-
],
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
id: 'tts',
|
|
567
|
-
labelKey: 'Settings.TtsTitle',
|
|
568
|
-
icon: 'Speech',
|
|
569
|
-
section: 'unit',
|
|
570
|
-
screens: ['tts'],
|
|
571
|
-
appMode: 'ENROLLED',
|
|
572
|
-
keys: [
|
|
573
|
-
'sound.ttsEnabled',
|
|
574
|
-
'sound.ttsRate',
|
|
575
|
-
],
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
id: 'updates',
|
|
579
|
-
labelKey: 'Settings.UpdatesTitle',
|
|
580
|
-
icon: 'Download',
|
|
581
|
-
section: 'unit',
|
|
582
|
-
screens: ['updates'],
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
id: 'about',
|
|
586
|
-
labelKey: 'Settings.About',
|
|
587
|
-
icon: 'Info',
|
|
588
|
-
section: 'unit',
|
|
589
|
-
screens: ['restoreDefaults', 'recycleAppData', 'license', 'version'],
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
id: 'help',
|
|
593
|
-
labelKey: 'Settings.HelpTitle',
|
|
594
|
-
icon: 'CircleQuestionMark',
|
|
595
|
-
section: 'unit',
|
|
596
|
-
screens: ['help'],
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
id: 'dev',
|
|
600
|
-
labelKey: 'Settings.Dev',
|
|
601
|
-
icon: 'FolderCode',
|
|
602
|
-
section: 'unit',
|
|
603
|
-
screens: [
|
|
604
|
-
'devDeviceInfo',
|
|
605
|
-
'devComponents',
|
|
606
|
-
'devStores',
|
|
607
|
-
'devActions',
|
|
608
|
-
'
|
|
609
|
-
'
|
|
610
|
-
'
|
|
611
|
-
'
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
*
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
'device.
|
|
638
|
-
'device.
|
|
639
|
-
'
|
|
640
|
-
'notification.
|
|
641
|
-
'notification.
|
|
642
|
-
'notification.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
'appearance.
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
'calendarView.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
'calendarView.
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
'sound.
|
|
740
|
-
'sound.
|
|
741
|
-
'sound.
|
|
742
|
-
'sound.
|
|
743
|
-
'sound.
|
|
744
|
-
'sound.
|
|
745
|
-
'sound.
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
'timer.
|
|
757
|
-
'timer.
|
|
758
|
-
'timer.
|
|
759
|
-
'timer.
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
'lockScreen.
|
|
764
|
-
'lockScreen.
|
|
765
|
-
'lockScreen.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
'lockScreen.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
'notification.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
'eventForm.
|
|
890
|
-
'eventForm.
|
|
891
|
-
'eventForm.
|
|
892
|
-
'eventForm.
|
|
893
|
-
'eventForm.
|
|
894
|
-
'eventForm.
|
|
895
|
-
'eventForm.
|
|
896
|
-
'eventForm.
|
|
897
|
-
'eventForm.
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
*
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
*
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
*
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
if (s.
|
|
995
|
-
return
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
options
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
//
|
|
1086
|
-
//
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
{ value: '
|
|
1111
|
-
{ value: '
|
|
1112
|
-
{ value: '
|
|
1113
|
-
{ value: '
|
|
1114
|
-
{ value: '
|
|
1115
|
-
{ value: '
|
|
1116
|
-
{ value: '
|
|
1117
|
-
{ value: '
|
|
1118
|
-
{ value: '
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
{ value:
|
|
1124
|
-
{ value:
|
|
1125
|
-
{ value:
|
|
1126
|
-
{ value:
|
|
1127
|
-
{ value:
|
|
1128
|
-
{ value:
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
{ value:
|
|
1135
|
-
{ value:
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
//
|
|
1189
|
-
//
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
//
|
|
1195
|
-
//
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
'
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
{ value: '
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
{ value: '
|
|
1210
|
-
{ value: '
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
'
|
|
1217
|
-
'
|
|
1218
|
-
'
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
{ value: '
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
'
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
//
|
|
1237
|
-
//
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
'
|
|
1241
|
-
'
|
|
1242
|
-
'
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
'
|
|
1259
|
-
'
|
|
1260
|
-
'
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
{
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
{
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
'
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
'
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
'
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
'
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
'
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
'
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
'
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
'
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
'
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
'
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
'
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
'eventForm.
|
|
1367
|
-
|
|
1368
|
-
'
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
'eventForm.
|
|
1374
|
-
|
|
1375
|
-
'
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
'
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
'eventForm.
|
|
1387
|
-
'eventForm.
|
|
1388
|
-
|
|
1389
|
-
'
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
'
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
'
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
//
|
|
1408
|
-
//
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
'
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
'
|
|
1431
|
-
'
|
|
1432
|
-
'
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
'
|
|
1438
|
-
'
|
|
1439
|
-
'
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
'
|
|
1448
|
-
'
|
|
1449
|
-
'
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
'
|
|
1458
|
-
'
|
|
1459
|
-
'
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
'
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
'
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
'
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
'
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
//
|
|
1503
|
-
//
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
{ value: '
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
'timer.
|
|
1512
|
-
'timer.
|
|
1513
|
-
'timer.
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
'
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
'
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
{ value:
|
|
1533
|
-
{ value:
|
|
1534
|
-
{ value:
|
|
1535
|
-
{ value:
|
|
1536
|
-
{ value:
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
'lockScreen.
|
|
1543
|
-
|
|
1544
|
-
'
|
|
1545
|
-
'
|
|
1546
|
-
'
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
{
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
'
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
'
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
'
|
|
1577
|
-
'
|
|
1578
|
-
'
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
{
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
'
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
'
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
{ value:
|
|
1609
|
-
{ value:
|
|
1610
|
-
{ value:
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
'
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
//
|
|
1626
|
-
//
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
'
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
//
|
|
1640
|
-
//
|
|
1641
|
-
|
|
1642
|
-
'device.
|
|
1643
|
-
|
|
1644
|
-
'
|
|
1645
|
-
'
|
|
1646
|
-
'
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
{
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
'
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
'
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
//
|
|
1674
|
-
//
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
'
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
{ value: '
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
//
|
|
1690
|
-
//
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
'
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
'
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
'
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
'
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
//
|
|
1719
|
-
//
|
|
1720
|
-
//
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
'
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
'
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
'
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
'
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
'
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
'
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
'
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
'
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
'
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
'
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
'
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
'
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
'
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
'
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
'
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
'
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
'
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
'
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
'
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
'
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
'
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
'
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
'
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
'
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
'
|
|
1874
|
-
'
|
|
1875
|
-
'
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
'
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
'
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
'
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
'
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
'
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
//
|
|
1929
|
-
//
|
|
1930
|
-
//
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
'
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
'
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
'
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
'
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
'
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
'
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
'
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
'
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
'
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
'
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
'
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
'
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
'
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
'
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
'
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
'
|
|
2027
|
-
'
|
|
2028
|
-
'
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
'
|
|
2046
|
-
'
|
|
2047
|
-
'
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
{ value: '
|
|
2052
|
-
{ value: '
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
'
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
'
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
'
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
'
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
{ daysBefore:
|
|
2082
|
-
{ daysBefore:
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
//
|
|
2091
|
-
//
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
//
|
|
2108
|
-
//
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
this.
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
//
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
//
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
*
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
*
|
|
2238
|
-
*
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
const
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
.replace(
|
|
2247
|
-
.replace(
|
|
2248
|
-
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
//
|
|
2253
|
-
//
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
key
|
|
2258
|
-
|
|
2259
|
-
name
|
|
2260
|
-
|
|
2261
|
-
label
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
value
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
options
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
category
|
|
2287
|
-
|
|
2288
|
-
label
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
2299
|
-
* @param
|
|
2300
|
-
* @param
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
...(entryDef?.
|
|
2355
|
-
...(entryDef?.
|
|
2356
|
-
...(entryDef?.
|
|
2357
|
-
...(entryDef?.
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
.
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
*
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
//
|
|
2448
|
-
//
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
//
|
|
2454
|
-
//
|
|
2455
|
-
//
|
|
2456
|
-
//
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Settings Registry — single source of truth for ALL FocusPlanner app settings.
|
|
3
|
+
*
|
|
4
|
+
* This shared package defines every setting key, its category, data type,
|
|
5
|
+
* default value, and whether it should be synced to the server.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* - The mobile app (local SQLite settings table, React Query hooks)
|
|
9
|
+
* - The API (validate & serialize settings profiles)
|
|
10
|
+
* - The web portal (render settings viewer/editor)
|
|
11
|
+
*
|
|
12
|
+
* App-specific defaults (theme, locale, enrolled mode) are parameterized
|
|
13
|
+
* via `createRegistry(config)`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Setting types shared across mobile, API, and web
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export type ThemeSetting = 'light' | 'dark' | 'system';
|
|
21
|
+
export type AlarmSound =
|
|
22
|
+
| 'none'
|
|
23
|
+
| 'alarm1'
|
|
24
|
+
| 'alarm2'
|
|
25
|
+
| 'alarm3'
|
|
26
|
+
| 'alarm4'
|
|
27
|
+
| 'alarm5'
|
|
28
|
+
| 'alarm6'
|
|
29
|
+
| 'alarm7'
|
|
30
|
+
| 'alarm8'
|
|
31
|
+
| 'alarm9';
|
|
32
|
+
export type LocaleCode = 'en' | 'nb';
|
|
33
|
+
|
|
34
|
+
export type CalendarType = 'chronological' | 'time-based';
|
|
35
|
+
export type ClockType = 'digital' | 'analog';
|
|
36
|
+
export type TimePickerMode = 'dials' | 'keypad';
|
|
37
|
+
export type AlarmTimeout = 5 | 30 | 60 | 120 | 180 | 300 | 600;
|
|
38
|
+
export const ALARM_TIMEOUTS: AlarmTimeout[] = [5, 30, 60, 120, 180, 300, 600];
|
|
39
|
+
export type CalendarViewMode =
|
|
40
|
+
| 'day'
|
|
41
|
+
| '3-days'
|
|
42
|
+
| '5-days'
|
|
43
|
+
| '7-days'
|
|
44
|
+
| 'week'
|
|
45
|
+
| 'month'
|
|
46
|
+
| 'overview';
|
|
47
|
+
export type CalendarDayViewCellZoom = 15 | 30 | 60;
|
|
48
|
+
export type InactivityTimeoutMinutes = 1 | 5 | 10 | 15 | 30 | 45;
|
|
49
|
+
export type LockScreenClockDisplay = 'none' | 'digital' | 'analog';
|
|
50
|
+
export type LockScreenImageMode = 'none' | 'background' | 'photoFrame';
|
|
51
|
+
export type PhotoFrameIntervalSeconds = 30 | 60 | 120 | 300;
|
|
52
|
+
export type EventVisibility = 'Public' | 'Private' | 'Custom';
|
|
53
|
+
export type SuggestEndTimeUnit = 'minutes' | 'hours';
|
|
54
|
+
export type ChronologicalDayViewDisplayMode = 'list' | 'timeline';
|
|
55
|
+
|
|
56
|
+
export type WeatherLocation = {
|
|
57
|
+
address: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
latitude: number;
|
|
60
|
+
longitude: number;
|
|
61
|
+
placeId?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type LockScreenImage = {
|
|
65
|
+
uri: string;
|
|
66
|
+
filePath: string;
|
|
67
|
+
fileName?: string;
|
|
68
|
+
fileSize?: number;
|
|
69
|
+
width?: number;
|
|
70
|
+
height?: number;
|
|
71
|
+
order: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const LOCK_SCREEN_MAX_IMAGES = 10;
|
|
75
|
+
|
|
76
|
+
export type AllDayPreset = {
|
|
77
|
+
daysBefore: number;
|
|
78
|
+
time: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// UI type discriminator — determines which component renders each setting
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
export type SettingUiType =
|
|
86
|
+
| 'TOGGLE'
|
|
87
|
+
| 'SELECT'
|
|
88
|
+
| 'VOLUME_SLIDER'
|
|
89
|
+
| 'SLIDER'
|
|
90
|
+
| 'NUMBER_INPUT'
|
|
91
|
+
| 'TEXT_INPUT'
|
|
92
|
+
| 'PIN_INPUT'
|
|
93
|
+
| 'CUSTOM_THEME_PICKER'
|
|
94
|
+
| 'CUSTOM_LANGUAGE_PICKER'
|
|
95
|
+
| 'CUSTOM_CALENDAR_TYPE'
|
|
96
|
+
| 'CUSTOM_CLOCK_TYPE'
|
|
97
|
+
| 'CUSTOM_WEATHER_LOCATION'
|
|
98
|
+
| 'CUSTOM_IMAGE'
|
|
99
|
+
| 'CUSTOM_IMAGE_ARRAY'
|
|
100
|
+
| 'CUSTOM_CALENDAR_IDS'
|
|
101
|
+
| 'CUSTOM_REMINDER_PRESETS'
|
|
102
|
+
| 'CUSTOM_EVENT_FORM'
|
|
103
|
+
| 'CUSTOM_CHRONOLOGICAL_EVENT_FORM'
|
|
104
|
+
| 'CUSTOM_DISPLAY_DENSITY'
|
|
105
|
+
| 'CUSTOM_BRIGHTNESS'
|
|
106
|
+
| 'CUSTOM_SPLIT_VIEW_CONFIG'
|
|
107
|
+
| 'CUSTOM_EVENT_CATEGORIES'
|
|
108
|
+
| 'HIDDEN';
|
|
109
|
+
|
|
110
|
+
export type SettingOption<T = unknown> = {
|
|
111
|
+
/** The option value */
|
|
112
|
+
value: T;
|
|
113
|
+
/** i18n message key for the option label */
|
|
114
|
+
labelKey: string;
|
|
115
|
+
/** Optional values to pass to the i18n message formatter */
|
|
116
|
+
labelValues?: Record<string, unknown>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type SliderConfig = {
|
|
120
|
+
min: number;
|
|
121
|
+
max: number;
|
|
122
|
+
step: number;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Visibility & disabled conditions — declarative rules for conditional display
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
export type VisibilityCondition =
|
|
130
|
+
| { dependsOn: string; equals: unknown }
|
|
131
|
+
| { dependsOn: string; notEquals: unknown }
|
|
132
|
+
| { dependsOn: string; isTruthy: true };
|
|
133
|
+
|
|
134
|
+
/** Single condition or array of conditions (all must be true = AND). */
|
|
135
|
+
export type VisibilityRule = VisibilityCondition | VisibilityCondition[];
|
|
136
|
+
|
|
137
|
+
/** Alias — same shape, controls interactivity instead of visibility. */
|
|
138
|
+
export type DisabledRule = VisibilityCondition | VisibilityCondition[];
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Evaluate a visibility/disabled rule against current setting values.
|
|
142
|
+
* Returns `true` if the setting should be visible (or disabled, depending on usage).
|
|
143
|
+
* `undefined` rule = always true (no restriction).
|
|
144
|
+
*/
|
|
145
|
+
export function evaluateVisibility(
|
|
146
|
+
rule: VisibilityRule | undefined,
|
|
147
|
+
getValue: (key: string) => unknown,
|
|
148
|
+
): boolean {
|
|
149
|
+
if (!rule) return true;
|
|
150
|
+
const conditions: VisibilityCondition[] = Array.isArray(rule) ? rule : [rule];
|
|
151
|
+
return conditions.every((c) => {
|
|
152
|
+
const value = getValue(c.dependsOn);
|
|
153
|
+
if ('equals' in c) return value === c.equals;
|
|
154
|
+
if ('notEquals' in c) return value !== c.notEquals;
|
|
155
|
+
if ('isTruthy' in c) return !!value;
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Convenience: returns true when the setting should be disabled. */
|
|
161
|
+
export function evaluateDisabled(
|
|
162
|
+
rule: DisabledRule | undefined,
|
|
163
|
+
getValue: (key: string) => unknown,
|
|
164
|
+
): boolean {
|
|
165
|
+
if (!rule) return false;
|
|
166
|
+
return evaluateVisibility(rule, getValue);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Setting categories — used for UI grouping and profile organization
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
export const SETTINGS_CATEGORIES = [
|
|
174
|
+
'appearance',
|
|
175
|
+
'calendarView',
|
|
176
|
+
'calendars',
|
|
177
|
+
'sound',
|
|
178
|
+
'timer',
|
|
179
|
+
'media',
|
|
180
|
+
'lockScreen',
|
|
181
|
+
'touch',
|
|
182
|
+
'device',
|
|
183
|
+
'language',
|
|
184
|
+
'notification',
|
|
185
|
+
'chronological',
|
|
186
|
+
'eventForm',
|
|
187
|
+
'chronologicalEventForm',
|
|
188
|
+
] as const;
|
|
189
|
+
|
|
190
|
+
export type SettingsCategory = (typeof SETTINGS_CATEGORIES)[number];
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Category display labels — human-readable names for UI rendering
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
export const CATEGORY_LABELS: Record<SettingsCategory, string> = {
|
|
197
|
+
appearance: 'Appearance',
|
|
198
|
+
calendarView: 'Calendar View',
|
|
199
|
+
calendars: 'Calendars',
|
|
200
|
+
sound: 'Sound & Alerts',
|
|
201
|
+
timer: 'Timer',
|
|
202
|
+
media: 'Media',
|
|
203
|
+
lockScreen: 'Lock Screen',
|
|
204
|
+
touch: 'Touch & Gestures',
|
|
205
|
+
device: 'Device',
|
|
206
|
+
language: 'Language',
|
|
207
|
+
notification: 'Notifications',
|
|
208
|
+
chronological: 'Chronological',
|
|
209
|
+
eventForm: 'Event Form',
|
|
210
|
+
chronologicalEventForm: 'Event Form (Chronological)',
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Category icons — lucide icon names matching the mobile app
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
export const CATEGORY_ICONS: Record<SettingsCategory, string> = {
|
|
218
|
+
appearance: 'Palette',
|
|
219
|
+
calendarView: 'Eye',
|
|
220
|
+
calendars: 'Calendar',
|
|
221
|
+
sound: 'Volume2',
|
|
222
|
+
timer: 'Timer',
|
|
223
|
+
media: 'Images',
|
|
224
|
+
lockScreen: 'Lock',
|
|
225
|
+
touch: 'Hand',
|
|
226
|
+
device: 'Smartphone',
|
|
227
|
+
language: 'Globe',
|
|
228
|
+
notification: 'Bell',
|
|
229
|
+
chronological: 'List',
|
|
230
|
+
eventForm: 'CalendarPlus',
|
|
231
|
+
chronologicalEventForm: 'CalendarPlus',
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Calendar-type filtering — which categories are exclusive to a calendar type
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
/** Categories only shown for chronological calendar type */
|
|
239
|
+
export const CHRONOLOGICAL_ONLY_CATEGORIES: ReadonlySet<SettingsCategory> =
|
|
240
|
+
new Set<SettingsCategory>(['chronological', 'chronologicalEventForm']);
|
|
241
|
+
|
|
242
|
+
/** Categories only shown for time-based calendar type */
|
|
243
|
+
export const TIME_BASED_ONLY_CATEGORIES: ReadonlySet<SettingsCategory> =
|
|
244
|
+
new Set<SettingsCategory>(['eventForm']);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Filter categories based on the active calendar type.
|
|
248
|
+
* Categories not exclusive to either type are always included.
|
|
249
|
+
*/
|
|
250
|
+
export function getCategoriesForCalendarType(
|
|
251
|
+
calendarType: CalendarType,
|
|
252
|
+
): SettingsCategory[] {
|
|
253
|
+
return SETTINGS_CATEGORIES.filter((cat) => {
|
|
254
|
+
if (
|
|
255
|
+
calendarType === 'time-based' &&
|
|
256
|
+
CHRONOLOGICAL_ONLY_CATEGORIES.has(cat)
|
|
257
|
+
) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
calendarType === 'chronological' &&
|
|
262
|
+
TIME_BASED_ONLY_CATEGORIES.has(cat)
|
|
263
|
+
) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Settings sections — top-level groupings (e.g. "Kalender", "Enhet")
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
export const SETTINGS_SECTION_IDS = ['calendar', 'unit'] as const;
|
|
275
|
+
export type SettingsSectionId = (typeof SETTINGS_SECTION_IDS)[number];
|
|
276
|
+
|
|
277
|
+
export type SettingsSectionDef = {
|
|
278
|
+
id: SettingsSectionId;
|
|
279
|
+
labelKey: string;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
|
|
283
|
+
{ id: 'calendar', labelKey: 'Calendar.LabelCalendar' },
|
|
284
|
+
{ id: 'unit', labelKey: 'Settings.Unit' },
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Settings menu items — ordered list of all items in the settings menu
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
export const SETTINGS_MENU_ITEM_IDS = [
|
|
292
|
+
// Calendar section
|
|
293
|
+
'calendarView',
|
|
294
|
+
'calendars',
|
|
295
|
+
'eventCategories',
|
|
296
|
+
'notification',
|
|
297
|
+
'timer',
|
|
298
|
+
'guests',
|
|
299
|
+
'eventForm',
|
|
300
|
+
'chronologicalEventForm',
|
|
301
|
+
'weather',
|
|
302
|
+
'gallery',
|
|
303
|
+
'audioClips',
|
|
304
|
+
'gestures',
|
|
305
|
+
'calendarType',
|
|
306
|
+
'sharing',
|
|
307
|
+
// Unit section
|
|
308
|
+
'screen',
|
|
309
|
+
'volume',
|
|
310
|
+
'lockScreen',
|
|
311
|
+
'language',
|
|
312
|
+
'wifi',
|
|
313
|
+
'devices',
|
|
314
|
+
'profiles',
|
|
315
|
+
'tts',
|
|
316
|
+
'updates',
|
|
317
|
+
'about',
|
|
318
|
+
'help',
|
|
319
|
+
'dev',
|
|
320
|
+
// Account (rendered separately)
|
|
321
|
+
'account',
|
|
322
|
+
'activityLog',
|
|
323
|
+
] as const;
|
|
324
|
+
|
|
325
|
+
export type SettingsMenuItemId = (typeof SETTINGS_MENU_ITEM_IDS)[number];
|
|
326
|
+
|
|
327
|
+
export type SettingsMenuItemDef = {
|
|
328
|
+
id: SettingsMenuItemId;
|
|
329
|
+
labelKey: string;
|
|
330
|
+
icon: string;
|
|
331
|
+
section: SettingsSectionId | 'account';
|
|
332
|
+
/** Screen/page identifiers rendered for this menu item */
|
|
333
|
+
screens: string[];
|
|
334
|
+
/**
|
|
335
|
+
* Setting key matchers for remote editing (web portal / profile editor).
|
|
336
|
+
* A setting is included if its key equals a matcher exactly, or starts
|
|
337
|
+
* with a matcher that ends with '.' (prefix match).
|
|
338
|
+
* Items without keys are navigation-only (e.g. gallery, guests).
|
|
339
|
+
*/
|
|
340
|
+
keys?: string[];
|
|
341
|
+
/** Only show for this calendar type (undefined = always) */
|
|
342
|
+
calendarType?: CalendarType;
|
|
343
|
+
/** Only show for this app mode (undefined = always) */
|
|
344
|
+
appMode?: 'ENROLLED';
|
|
345
|
+
/** If true, don't wrap content in a scroll container */
|
|
346
|
+
noScrollView?: boolean;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Ordered list of all settings menu items. The array order defines the
|
|
351
|
+
* display order within each section. Items without registry setting keys
|
|
352
|
+
* are navigation-only (e.g. gallery, guests).
|
|
353
|
+
*/
|
|
354
|
+
export const SETTINGS_MENU_ITEMS: readonly SettingsMenuItemDef[] = [
|
|
355
|
+
// -- Calendar section --
|
|
356
|
+
{
|
|
357
|
+
id: 'calendarView',
|
|
358
|
+
labelKey: 'Settings.Calendar',
|
|
359
|
+
icon: 'Eye',
|
|
360
|
+
section: 'calendar',
|
|
361
|
+
screens: [
|
|
362
|
+
'calendarDayView',
|
|
363
|
+
'calendarWeekView',
|
|
364
|
+
'chronologicalHeader',
|
|
365
|
+
'chronologicalFooter',
|
|
366
|
+
'chronologicalMenu',
|
|
367
|
+
'dayColors',
|
|
368
|
+
'dateTime',
|
|
369
|
+
],
|
|
370
|
+
keys: [
|
|
371
|
+
'calendarView.showCalendarNames',
|
|
372
|
+
'calendarView.splitView',
|
|
373
|
+
'calendarView.dayViewZoom',
|
|
374
|
+
'calendarView.weekViewZoom',
|
|
375
|
+
'calendarView.calendarColumns',
|
|
376
|
+
'appearance.enableDayColors',
|
|
377
|
+
'chronological.dayView.displayMode',
|
|
378
|
+
'chronological.header.',
|
|
379
|
+
'chronological.footer.',
|
|
380
|
+
'chronological.menu.',
|
|
381
|
+
'chronological.quickSettings.',
|
|
382
|
+
'chronological.timeOfDay.',
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: 'calendars',
|
|
387
|
+
labelKey: 'Common.Calendars',
|
|
388
|
+
icon: 'Calendar',
|
|
389
|
+
section: 'calendar',
|
|
390
|
+
screens: ['calendars'],
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: 'eventCategories',
|
|
394
|
+
labelKey: 'EventCategory.SettingsTitle',
|
|
395
|
+
icon: 'Tag',
|
|
396
|
+
section: 'calendar',
|
|
397
|
+
screens: ['eventCategories'],
|
|
398
|
+
calendarType: 'chronological',
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: 'notification',
|
|
402
|
+
labelKey: 'Settings.Notification',
|
|
403
|
+
icon: 'Bell',
|
|
404
|
+
section: 'calendar',
|
|
405
|
+
screens: ['notification'],
|
|
406
|
+
keys: [
|
|
407
|
+
'sound.reminderAlarmSound',
|
|
408
|
+
'sound.startAlarmSound',
|
|
409
|
+
'sound.endAlarmSound',
|
|
410
|
+
'sound.reminderAlarmTimeout',
|
|
411
|
+
'sound.reminderVolume',
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
id: 'timer',
|
|
416
|
+
labelKey: 'Settings.TimerTitle',
|
|
417
|
+
icon: 'Timer',
|
|
418
|
+
section: 'calendar',
|
|
419
|
+
screens: ['timerFeatures', 'timer'],
|
|
420
|
+
appMode: 'ENROLLED',
|
|
421
|
+
keys: [
|
|
422
|
+
'chronological.timer.',
|
|
423
|
+
'timer.',
|
|
424
|
+
'sound.timerAlarmSound',
|
|
425
|
+
'sound.timerAlarmTimeout',
|
|
426
|
+
'sound.timerVolume',
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'guests',
|
|
431
|
+
labelKey: 'Settings.GuestUsers',
|
|
432
|
+
icon: 'Users',
|
|
433
|
+
section: 'calendar',
|
|
434
|
+
screens: ['guests'],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: 'eventForm',
|
|
438
|
+
labelKey: 'SettingsEventForm.Title',
|
|
439
|
+
icon: 'ClipboardList',
|
|
440
|
+
section: 'calendar',
|
|
441
|
+
screens: ['eventForm'],
|
|
442
|
+
calendarType: 'time-based',
|
|
443
|
+
keys: ['eventForm.'],
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'chronologicalEventForm',
|
|
447
|
+
labelKey: 'SettingsChronologicalEventForm.Title',
|
|
448
|
+
icon: 'Plus',
|
|
449
|
+
section: 'calendar',
|
|
450
|
+
screens: ['chronologicalEventForm'],
|
|
451
|
+
calendarType: 'chronological',
|
|
452
|
+
keys: ['chronologicalEventForm.'],
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: 'weather',
|
|
456
|
+
labelKey: 'Settings.WeatherTitle',
|
|
457
|
+
icon: 'CloudSun',
|
|
458
|
+
section: 'calendar',
|
|
459
|
+
screens: ['weather'],
|
|
460
|
+
calendarType: 'time-based',
|
|
461
|
+
keys: [
|
|
462
|
+
'calendarView.showWeatherOnTimeline',
|
|
463
|
+
'calendarView.weatherLocation',
|
|
464
|
+
'calendarView.showWeatherOnEvents',
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 'gallery',
|
|
469
|
+
labelKey: 'Gallery.Title',
|
|
470
|
+
icon: 'Images',
|
|
471
|
+
section: 'calendar',
|
|
472
|
+
screens: ['gallery'],
|
|
473
|
+
noScrollView: true,
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: 'audioClips',
|
|
477
|
+
labelKey: 'AudioClips.Title',
|
|
478
|
+
icon: 'AudioLines',
|
|
479
|
+
section: 'calendar',
|
|
480
|
+
screens: ['audioClips'],
|
|
481
|
+
noScrollView: true,
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: 'gestures',
|
|
485
|
+
labelKey: 'Settings.FunctionsTitle',
|
|
486
|
+
icon: 'Hand',
|
|
487
|
+
section: 'calendar',
|
|
488
|
+
screens: ['gestures'],
|
|
489
|
+
keys: ['touch.'],
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'calendarType',
|
|
493
|
+
labelKey: 'Settings.Mode',
|
|
494
|
+
icon: 'SlidersHorizontal',
|
|
495
|
+
section: 'calendar',
|
|
496
|
+
screens: ['calendarType'],
|
|
497
|
+
keys: ['calendarView.type'],
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: 'sharing',
|
|
501
|
+
labelKey: 'Settings.Sharing',
|
|
502
|
+
icon: 'Share2',
|
|
503
|
+
section: 'calendar',
|
|
504
|
+
screens: ['icalSubscriptions'],
|
|
505
|
+
},
|
|
506
|
+
// -- Unit section --
|
|
507
|
+
{
|
|
508
|
+
id: 'screen',
|
|
509
|
+
labelKey: 'Settings.Screen',
|
|
510
|
+
icon: 'LaptopMinimal',
|
|
511
|
+
section: 'unit',
|
|
512
|
+
screens: ['displayDensity', 'darkMode'],
|
|
513
|
+
keys: ['appearance.theme', 'appearance.clockType'],
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: 'volume',
|
|
517
|
+
labelKey: 'Common.Volume',
|
|
518
|
+
icon: 'Volume2',
|
|
519
|
+
section: 'unit',
|
|
520
|
+
screens: ['volume'],
|
|
521
|
+
appMode: 'ENROLLED',
|
|
522
|
+
keys: [
|
|
523
|
+
'sound.reminderVolume',
|
|
524
|
+
'sound.timerVolume',
|
|
525
|
+
'sound.mediaVolume',
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: 'lockScreen',
|
|
530
|
+
labelKey: 'Settings.LockScreen',
|
|
531
|
+
icon: 'Lock',
|
|
532
|
+
section: 'unit',
|
|
533
|
+
screens: ['inactivity', 'lockScreen'],
|
|
534
|
+
appMode: 'ENROLLED',
|
|
535
|
+
keys: ['lockScreen.'],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: 'language',
|
|
539
|
+
labelKey: 'Settings.Language',
|
|
540
|
+
icon: 'Globe',
|
|
541
|
+
section: 'unit',
|
|
542
|
+
screens: ['language'],
|
|
543
|
+
keys: ['language.'],
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
id: 'wifi',
|
|
547
|
+
labelKey: 'Settings.WifiTitle',
|
|
548
|
+
icon: 'Wifi',
|
|
549
|
+
section: 'unit',
|
|
550
|
+
screens: ['wifi'],
|
|
551
|
+
appMode: 'ENROLLED',
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: 'devices',
|
|
555
|
+
labelKey: 'SettingsDevices.Title',
|
|
556
|
+
icon: 'Smartphone',
|
|
557
|
+
section: 'unit',
|
|
558
|
+
screens: ['devices'],
|
|
559
|
+
keys: [
|
|
560
|
+
'device.',
|
|
561
|
+
'calendarView.autoReturnToTodayEnabled',
|
|
562
|
+
'calendarView.autoReturnToTodayTimeoutSeconds',
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: 'tts',
|
|
567
|
+
labelKey: 'Settings.TtsTitle',
|
|
568
|
+
icon: 'Speech',
|
|
569
|
+
section: 'unit',
|
|
570
|
+
screens: ['tts'],
|
|
571
|
+
appMode: 'ENROLLED',
|
|
572
|
+
keys: [
|
|
573
|
+
'sound.ttsEnabled',
|
|
574
|
+
'sound.ttsRate',
|
|
575
|
+
],
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 'updates',
|
|
579
|
+
labelKey: 'Settings.UpdatesTitle',
|
|
580
|
+
icon: 'Download',
|
|
581
|
+
section: 'unit',
|
|
582
|
+
screens: ['updates'],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: 'about',
|
|
586
|
+
labelKey: 'Settings.About',
|
|
587
|
+
icon: 'Info',
|
|
588
|
+
section: 'unit',
|
|
589
|
+
screens: ['restoreDefaults', 'recycleAppData', 'license', 'version'],
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: 'help',
|
|
593
|
+
labelKey: 'Settings.HelpTitle',
|
|
594
|
+
icon: 'CircleQuestionMark',
|
|
595
|
+
section: 'unit',
|
|
596
|
+
screens: ['help'],
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: 'dev',
|
|
600
|
+
labelKey: 'Settings.Dev',
|
|
601
|
+
icon: 'FolderCode',
|
|
602
|
+
section: 'unit',
|
|
603
|
+
screens: [
|
|
604
|
+
'devDeviceInfo',
|
|
605
|
+
'devComponents',
|
|
606
|
+
'devStores',
|
|
607
|
+
'devActions',
|
|
608
|
+
'devEventGenerator',
|
|
609
|
+
'devAuth',
|
|
610
|
+
'devNavigation',
|
|
611
|
+
'devNetwork',
|
|
612
|
+
'devAndroidSettings',
|
|
613
|
+
],
|
|
614
|
+
},
|
|
615
|
+
// -- Account (rendered separately) --
|
|
616
|
+
{
|
|
617
|
+
id: 'account',
|
|
618
|
+
labelKey: 'Settings.Profile',
|
|
619
|
+
icon: 'User',
|
|
620
|
+
section: 'account',
|
|
621
|
+
screens: ['account', 'mfa'],
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
id: 'activityLog',
|
|
625
|
+
labelKey: 'SettingsActivityLog.Title',
|
|
626
|
+
icon: 'ScrollText',
|
|
627
|
+
section: 'account',
|
|
628
|
+
screens: ['activityLog'],
|
|
629
|
+
},
|
|
630
|
+
];
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Settings excluded from remote editing (web portal / profile management).
|
|
634
|
+
* These are device-local or internal-only settings.
|
|
635
|
+
*/
|
|
636
|
+
export const EXCLUDED_DEVICE_SETTINGS: ReadonlySet<string> = new Set([
|
|
637
|
+
'device.id',
|
|
638
|
+
'device.devMenuEnabled',
|
|
639
|
+
'device.authWarningDismissTtlDays',
|
|
640
|
+
'notification.enabled',
|
|
641
|
+
'notification.notifyAllCalendars',
|
|
642
|
+
'notification.enabledCalendarIds',
|
|
643
|
+
'notification.hasBeenPrompted',
|
|
644
|
+
]);
|
|
645
|
+
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
// Per-setting i18n label / description keys
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
|
|
650
|
+
export type SettingLabelDef = {
|
|
651
|
+
/** i18n message key for the setting label */
|
|
652
|
+
labelKey: string;
|
|
653
|
+
/** Optional i18n message key for a description shown below the label */
|
|
654
|
+
descriptionKey?: string;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Maps each setting key to its i18n label and optional description key.
|
|
659
|
+
* These keys must exist in every consumer's i18n catalog.
|
|
660
|
+
*
|
|
661
|
+
* When a key is missing from this map, consumers should fall back to
|
|
662
|
+
* the auto-generated label from `SettingsRegistry.getSettingLabel()`.
|
|
663
|
+
*/
|
|
664
|
+
export const SETTINGS_LABELS: Readonly<Record<string, SettingLabelDef>> = {
|
|
665
|
+
// ── Appearance ──────────────────────────────────────────────────────────
|
|
666
|
+
'appearance.theme': { labelKey: 'Settings.SelectTheme' },
|
|
667
|
+
'appearance.clockType': {
|
|
668
|
+
labelKey: 'Settings.ClockType',
|
|
669
|
+
descriptionKey: 'Settings.ClockTypeDescription',
|
|
670
|
+
},
|
|
671
|
+
'appearance.enableDayColors': {
|
|
672
|
+
labelKey: 'Settings.EnableDayColors',
|
|
673
|
+
descriptionKey: 'Settings.EnableDayColorsDescription',
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
// ── Chronological day view display mode ──────────────────────────────────
|
|
677
|
+
'chronological.dayView.displayMode': {
|
|
678
|
+
labelKey: 'Settings.DayViewDisplayMode',
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
// ── Calendar type ───────────────────────────────────────────────────────
|
|
682
|
+
'calendarView.type': {
|
|
683
|
+
labelKey: 'Settings.CalendarType',
|
|
684
|
+
descriptionKey: 'Settings.ModeDescription',
|
|
685
|
+
},
|
|
686
|
+
'calendarView.view': { labelKey: 'Settings.Calendar' },
|
|
687
|
+
|
|
688
|
+
// ── Calendar view ───────────────────────────────────────────────────────
|
|
689
|
+
'calendarView.showCalendarNames': { labelKey: 'Settings.ShowCalendarNames' },
|
|
690
|
+
'calendarView.splitView': {
|
|
691
|
+
labelKey: 'Settings.SplitViewLabel',
|
|
692
|
+
descriptionKey: 'Settings.SplitViewDescription',
|
|
693
|
+
},
|
|
694
|
+
'calendarView.calendarColumns': {
|
|
695
|
+
labelKey: 'Settings.SplitViewConfig',
|
|
696
|
+
descriptionKey: 'Settings.SplitViewConfigDescription',
|
|
697
|
+
},
|
|
698
|
+
'calendarView.dayViewZoom': {
|
|
699
|
+
labelKey: 'Settings.CalendarDayViewIntervalTitle',
|
|
700
|
+
descriptionKey: 'Settings.CalendarDayViewIntervalDescription',
|
|
701
|
+
},
|
|
702
|
+
'calendarView.weekViewZoom': {
|
|
703
|
+
labelKey: 'Settings.CalendarWeekViewIntervalTitle',
|
|
704
|
+
descriptionKey: 'Settings.CalendarWeekViewIntervalDescription',
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
// ── Auto return to today ────────────────────────────────────────────────
|
|
708
|
+
'calendarView.autoReturnToTodayEnabled': {
|
|
709
|
+
labelKey: 'Settings.AutoReturnToTodayEnabled',
|
|
710
|
+
},
|
|
711
|
+
'calendarView.autoReturnToTodayTimeoutSeconds': {
|
|
712
|
+
labelKey: 'Settings.AutoReturnToTodayTimeout',
|
|
713
|
+
},
|
|
714
|
+
|
|
715
|
+
// ── Weather ─────────────────────────────────────────────────────────────
|
|
716
|
+
'calendarView.weatherLocation': { labelKey: 'Settings.WeatherLocation' },
|
|
717
|
+
'calendarView.showWeatherOnEvents': {
|
|
718
|
+
labelKey: 'Settings.ShowWeatherOnEvents',
|
|
719
|
+
descriptionKey: 'Settings.ShowWeatherOnEventsDescription',
|
|
720
|
+
},
|
|
721
|
+
'calendarView.showWeatherOnTimeline': {
|
|
722
|
+
labelKey: 'Settings.ShowWeatherOnTimeline',
|
|
723
|
+
descriptionKey: 'Settings.ShowWeatherOnTimelineDescription',
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
// ── Sound & alerts ──────────────────────────────────────────────────────
|
|
727
|
+
'sound.reminderVolume': {
|
|
728
|
+
labelKey: 'Settings.ReminderVolume',
|
|
729
|
+
descriptionKey: 'Settings.ReminderVolumeDescription',
|
|
730
|
+
},
|
|
731
|
+
'sound.timerVolume': {
|
|
732
|
+
labelKey: 'Settings.TimerVolume',
|
|
733
|
+
descriptionKey: 'Settings.TimerVolumeDescription',
|
|
734
|
+
},
|
|
735
|
+
'sound.mediaVolume': {
|
|
736
|
+
labelKey: 'Settings.MediaVolume',
|
|
737
|
+
descriptionKey: 'Settings.MediaVolumeDescription',
|
|
738
|
+
},
|
|
739
|
+
'sound.alarmSound': { labelKey: 'Settings.AlarmSound' },
|
|
740
|
+
'sound.reminderAlarmSound': { labelKey: 'Settings.ReminderAlarmAsset' },
|
|
741
|
+
'sound.startAlarmSound': { labelKey: 'Settings.StartAlarmSound' },
|
|
742
|
+
'sound.endAlarmSound': { labelKey: 'Settings.EndAlarmSound' },
|
|
743
|
+
'sound.reminderAlarmTimeout': { labelKey: 'Settings.ReminderAlarmTimeout' },
|
|
744
|
+
'sound.timerAlarmSound': { labelKey: 'Settings.TimerAlarmSound' },
|
|
745
|
+
'sound.timerAlarmTimeout': { labelKey: 'Settings.TimerAlarmTimeout' },
|
|
746
|
+
'sound.allowCustomReminderSounds': {
|
|
747
|
+
labelKey: 'Settings.AllowCustomReminderSounds',
|
|
748
|
+
},
|
|
749
|
+
'sound.ttsEnabled': {
|
|
750
|
+
labelKey: 'Settings.EnableSpeech',
|
|
751
|
+
descriptionKey: 'Settings.EnableSpeechDescription',
|
|
752
|
+
},
|
|
753
|
+
'sound.ttsRate': { labelKey: 'Settings.TtsTitle' },
|
|
754
|
+
|
|
755
|
+
// ── Timer ───────────────────────────────────────────────────────────────
|
|
756
|
+
'timer.face': { labelKey: 'Settings.TimerFace' },
|
|
757
|
+
'timer.showTimeRemaining': { labelKey: 'Settings.TimerShowTimeRemaining' },
|
|
758
|
+
'timer.showEndTime': { labelKey: 'Settings.TimerShowEndTime' },
|
|
759
|
+
'timer.showRestartButton': { labelKey: 'Settings.TimerShowRestartButton' },
|
|
760
|
+
'timer.showPauseButton': { labelKey: 'Settings.TimerShowPauseButton' },
|
|
761
|
+
|
|
762
|
+
// ── Lock screen ─────────────────────────────────────────────────────────
|
|
763
|
+
'lockScreen.pin': { labelKey: 'Settings.LockScreenTitlePin' },
|
|
764
|
+
'lockScreen.clockDisplay': { labelKey: 'Settings.LockScreenClockTitle' },
|
|
765
|
+
'lockScreen.showDate': { labelKey: 'Settings.LockScreenShowDate' },
|
|
766
|
+
'lockScreen.showHourNumbers': {
|
|
767
|
+
labelKey: 'Settings.LockScreenShowHourNumbers',
|
|
768
|
+
},
|
|
769
|
+
'lockScreen.imageMode': { labelKey: 'Settings.LockScreenImageModeTitle' },
|
|
770
|
+
'lockScreen.backgroundImage': {
|
|
771
|
+
labelKey: 'Settings.LockScreenImageModeBackground',
|
|
772
|
+
},
|
|
773
|
+
'lockScreen.photoFrameImages': {
|
|
774
|
+
labelKey: 'Settings.LockScreenImageModePhotoFrame',
|
|
775
|
+
},
|
|
776
|
+
'lockScreen.photoFrameIntervalSeconds': {
|
|
777
|
+
labelKey: 'Settings.LockScreenPhotoFrameIntervalTitle',
|
|
778
|
+
},
|
|
779
|
+
'lockScreen.inactivityLockEnabled': {
|
|
780
|
+
labelKey: 'SettingsInactivity.EnableLabel',
|
|
781
|
+
},
|
|
782
|
+
'lockScreen.inactivityTimeoutMinutes': {
|
|
783
|
+
labelKey: 'SettingsInactivity.TimeoutLabel',
|
|
784
|
+
},
|
|
785
|
+
|
|
786
|
+
// ── Touch / gestures ───────────────────────────────────────────────────
|
|
787
|
+
'touch.enableTapToCreate': {
|
|
788
|
+
labelKey: 'Settings.EnableTapToCreate',
|
|
789
|
+
descriptionKey: 'Settings.EnableTapToCreateDescription',
|
|
790
|
+
},
|
|
791
|
+
'touch.enableDragDrop': {
|
|
792
|
+
labelKey: 'Settings.EnableDragDrop',
|
|
793
|
+
descriptionKey: 'Settings.EnableDragDropDescription',
|
|
794
|
+
},
|
|
795
|
+
|
|
796
|
+
// ── Device ──────────────────────────────────────────────────────────────
|
|
797
|
+
'device.timePickerMode': { labelKey: 'Settings.DateTimeTitle' },
|
|
798
|
+
|
|
799
|
+
// ── Language ────────────────────────────────────────────────────────────
|
|
800
|
+
'language.locale': { labelKey: 'Settings.SelectLanguage' },
|
|
801
|
+
|
|
802
|
+
// ── Notifications ───────────────────────────────────────────────────────
|
|
803
|
+
'notification.enabled': { labelKey: 'Settings.NotificationsEnabled' },
|
|
804
|
+
'notification.notifyAllCalendars': {
|
|
805
|
+
labelKey: 'Settings.NotifyAllCalendars',
|
|
806
|
+
},
|
|
807
|
+
'notification.enabledCalendarIds': {
|
|
808
|
+
labelKey: 'Settings.NotificationsCalendars',
|
|
809
|
+
},
|
|
810
|
+
|
|
811
|
+
// ── Chronological header ────────────────────────────────────────────────
|
|
812
|
+
'chronological.header.showNavigationArrows': {
|
|
813
|
+
labelKey: 'ChronologicalFeatures.NavigationArrows',
|
|
814
|
+
},
|
|
815
|
+
'chronological.header.showClock': {
|
|
816
|
+
labelKey: 'ChronologicalFeatures.Clock',
|
|
817
|
+
},
|
|
818
|
+
'chronological.header.showCurrentYearInDate': {
|
|
819
|
+
labelKey: 'ChronologicalFeatures.ShowCurrentYear',
|
|
820
|
+
},
|
|
821
|
+
'chronological.header.showTimeOfDay': {
|
|
822
|
+
labelKey: 'ChronologicalFeatures.TimeOfDay',
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
// ── Chronological footer ────────────────────────────────────────────────
|
|
826
|
+
'chronological.footer.showMenuButton': {
|
|
827
|
+
labelKey: 'ChronologicalFeatures.MenuButton',
|
|
828
|
+
},
|
|
829
|
+
'chronological.footer.showViewSwitcherDay': {
|
|
830
|
+
labelKey: 'ChronologicalFeatures.ViewSwitcherDay',
|
|
831
|
+
},
|
|
832
|
+
'chronological.footer.showViewSwitcherWeek': {
|
|
833
|
+
labelKey: 'ChronologicalFeatures.ViewSwitcherWeek',
|
|
834
|
+
},
|
|
835
|
+
'chronological.footer.showViewSwitcherMonth': {
|
|
836
|
+
labelKey: 'ChronologicalFeatures.ViewSwitcherMonth',
|
|
837
|
+
},
|
|
838
|
+
'chronological.footer.showTimerButton': {
|
|
839
|
+
labelKey: 'ChronologicalFeatures.TimerButton',
|
|
840
|
+
},
|
|
841
|
+
'chronological.footer.showNewEventButton': {
|
|
842
|
+
labelKey: 'ChronologicalFeatures.NewEventButton',
|
|
843
|
+
},
|
|
844
|
+
'chronological.footer.showNowButton': {
|
|
845
|
+
labelKey: 'ChronologicalFeatures.NowButton',
|
|
846
|
+
},
|
|
847
|
+
'chronological.footer.showSettingsButton': {
|
|
848
|
+
labelKey: 'ChronologicalFeatures.SettingsButton',
|
|
849
|
+
},
|
|
850
|
+
|
|
851
|
+
// ── Chronological timer ─────────────────────────────────────────────────
|
|
852
|
+
'chronological.timer.showNewCountdown': {
|
|
853
|
+
labelKey: 'TimerFeatures.ShowNewCountdown',
|
|
854
|
+
},
|
|
855
|
+
'chronological.timer.showFromTemplate': {
|
|
856
|
+
labelKey: 'TimerFeatures.ShowFromTemplate',
|
|
857
|
+
},
|
|
858
|
+
'chronological.timer.showEditTemplate': {
|
|
859
|
+
labelKey: 'TimerFeatures.ShowEditTemplate',
|
|
860
|
+
},
|
|
861
|
+
'chronological.timer.showDeleteTemplate': {
|
|
862
|
+
labelKey: 'TimerFeatures.ShowDeleteTemplate',
|
|
863
|
+
},
|
|
864
|
+
'chronological.timer.showAddTemplate': {
|
|
865
|
+
labelKey: 'TimerFeatures.ShowAddTemplate',
|
|
866
|
+
},
|
|
867
|
+
|
|
868
|
+
// ── Chronological menu ──────────────────────────────────────────────────
|
|
869
|
+
'chronological.menu.showSettingsButton': {
|
|
870
|
+
labelKey: 'ChronologicalFeatures.MenuSettingsButton',
|
|
871
|
+
},
|
|
872
|
+
'chronological.quickSettings.showTimerVolume': {
|
|
873
|
+
labelKey: 'ChronologicalFeatures.QuickSettingsTimerVolume',
|
|
874
|
+
},
|
|
875
|
+
'chronological.quickSettings.showReminderVolume': {
|
|
876
|
+
labelKey: 'ChronologicalFeatures.QuickSettingsReminderVolume',
|
|
877
|
+
},
|
|
878
|
+
'chronological.quickSettings.showMediaVolume': {
|
|
879
|
+
labelKey: 'ChronologicalFeatures.QuickSettingsMediaVolume',
|
|
880
|
+
},
|
|
881
|
+
'chronological.quickSettings.showBrightness': {
|
|
882
|
+
labelKey: 'ChronologicalFeatures.QuickSettingsBrightness',
|
|
883
|
+
},
|
|
884
|
+
'chronological.quickSettings.showLockScreen': {
|
|
885
|
+
labelKey: 'ChronologicalFeatures.QuickSettingsLockScreen',
|
|
886
|
+
},
|
|
887
|
+
|
|
888
|
+
// ── Event form (time-based) ─────────────────────────────────────────────
|
|
889
|
+
'eventForm.recurrence': { labelKey: 'Calendar.LabelRecurrence' },
|
|
890
|
+
'eventForm.reminders': { labelKey: 'Calendar.LabelReminders' },
|
|
891
|
+
'eventForm.emailReminders': { labelKey: 'Calendar.LabelEmailReminders' },
|
|
892
|
+
'eventForm.location': { labelKey: 'Common.Location' },
|
|
893
|
+
'eventForm.travelTime': { labelKey: 'TravelTime.Title' },
|
|
894
|
+
'eventForm.description': { labelKey: 'Common.Description' },
|
|
895
|
+
'eventForm.checklist': { labelKey: 'EventChecklist.DefaultName' },
|
|
896
|
+
'eventForm.images': { labelKey: 'EventImageGallery.SectionTitle' },
|
|
897
|
+
'eventForm.audioClips': { labelKey: 'EventAudioGallery.SectionTitle' },
|
|
898
|
+
'eventForm.notificationReceivers': {
|
|
899
|
+
labelKey: 'EventForm.NotificationReceivers',
|
|
900
|
+
},
|
|
901
|
+
'eventForm.visibility': { labelKey: 'EventVisibility.Title' },
|
|
902
|
+
|
|
903
|
+
// ── Chronological event form ────────────────────────────────────────────
|
|
904
|
+
'chronologicalEventForm.field.category': {
|
|
905
|
+
labelKey: 'ChronologicalEventForm.CategoryField',
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
/** Return the index of the first matching key pattern in the group, for sorting. */
|
|
910
|
+
function groupKeyIndex(key: string, keys: readonly string[]): number {
|
|
911
|
+
for (let i = 0; i < keys.length; i++) {
|
|
912
|
+
const matcher = keys[i];
|
|
913
|
+
if (key === matcher || (matcher.endsWith('.') && key.startsWith(matcher))) {
|
|
914
|
+
return i;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return keys.length;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Lazy-initialized map from setting key → declaration index in buildEntries.
|
|
922
|
+
* Used as a tiebreaker when multiple keys share the same prefix group index.
|
|
923
|
+
*/
|
|
924
|
+
let _registryKeyOrder: Map<string, number> | undefined;
|
|
925
|
+
function registryKeyOrder(): Map<string, number> {
|
|
926
|
+
if (!_registryKeyOrder) {
|
|
927
|
+
_registryKeyOrder = new Map();
|
|
928
|
+
const keys = Object.keys(buildEntries(DEFAULT_REGISTRY_CONFIG));
|
|
929
|
+
for (let i = 0; i < keys.length; i++) {
|
|
930
|
+
_registryKeyOrder.set(keys[i], i);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return _registryKeyOrder;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Get the ordered list of setting keys for a given menu item.
|
|
938
|
+
* Returns keys matching the app's render order, filtered by EXCLUDED_DEVICE_SETTINGS and HIDDEN.
|
|
939
|
+
*/
|
|
940
|
+
export function getKeysForMenuItem(
|
|
941
|
+
menuItemId: SettingsMenuItemId,
|
|
942
|
+
reg: SettingsRegistry = defaultRegistry,
|
|
943
|
+
): string[] {
|
|
944
|
+
const menuItem = SETTINGS_MENU_ITEMS.find((m) => m.id === menuItemId);
|
|
945
|
+
if (!menuItem?.keys || menuItem.keys.length === 0) return [];
|
|
946
|
+
|
|
947
|
+
const matched = reg.keys.filter((key) => {
|
|
948
|
+
if (EXCLUDED_DEVICE_SETTINGS.has(key)) return false;
|
|
949
|
+
const entry = reg.entries[key as keyof typeof reg.entries];
|
|
950
|
+
if (entry?.uiType === 'HIDDEN') return false;
|
|
951
|
+
return menuItem.keys!.some((k) =>
|
|
952
|
+
k.endsWith('.') ? key.startsWith(k) : key === k,
|
|
953
|
+
);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const order = registryKeyOrder();
|
|
957
|
+
matched.sort((a, b) => {
|
|
958
|
+
const aIdx = groupKeyIndex(a, menuItem.keys!);
|
|
959
|
+
const bIdx = groupKeyIndex(b, menuItem.keys!);
|
|
960
|
+
if (aIdx !== bIdx) return aIdx - bIdx;
|
|
961
|
+
return (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
return matched;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Group parsed setting entries using the app's menu structure (SETTINGS_MENU_ITEMS).
|
|
969
|
+
* Only includes menu items that have `keys` defined (editable settings).
|
|
970
|
+
* This ensures the web editor has the exact same menu items, order, sections,
|
|
971
|
+
* labels, and icons as the mobile app.
|
|
972
|
+
*/
|
|
973
|
+
export function groupSettingsForWeb(
|
|
974
|
+
allSettings: ParsedSettingEntry[],
|
|
975
|
+
calendarType: CalendarType,
|
|
976
|
+
appMode?: 'ENROLLED',
|
|
977
|
+
): {
|
|
978
|
+
id: SettingsMenuItemId;
|
|
979
|
+
labelKey: string;
|
|
980
|
+
icon: string;
|
|
981
|
+
section: SettingsSectionId | 'account';
|
|
982
|
+
settings: ParsedSettingEntry[];
|
|
983
|
+
}[] {
|
|
984
|
+
// Filter excluded and hidden settings
|
|
985
|
+
const settings = allSettings.filter(
|
|
986
|
+
(s) => !EXCLUDED_DEVICE_SETTINGS.has(s.key) && s.uiType !== 'HIDDEN',
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
const isChronological = calendarType === 'chronological';
|
|
990
|
+
const filteredSettings = settings.filter((s) => {
|
|
991
|
+
if (isChronological && s.key.startsWith('eventForm.')) return false;
|
|
992
|
+
if (!isChronological && s.key.startsWith('chronologicalEventForm.'))
|
|
993
|
+
return false;
|
|
994
|
+
if (s.calendarType && s.calendarType !== calendarType) return false;
|
|
995
|
+
if (s.appMode && s.appMode !== appMode) return false;
|
|
996
|
+
return true;
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const claimed = new Set<string>();
|
|
1000
|
+
const result: {
|
|
1001
|
+
id: SettingsMenuItemId;
|
|
1002
|
+
labelKey: string;
|
|
1003
|
+
icon: string;
|
|
1004
|
+
section: SettingsSectionId | 'account';
|
|
1005
|
+
settings: ParsedSettingEntry[];
|
|
1006
|
+
}[] = [];
|
|
1007
|
+
|
|
1008
|
+
for (const menuItem of SETTINGS_MENU_ITEMS) {
|
|
1009
|
+
// Skip items without keys (navigation-only like gallery, guests, etc.)
|
|
1010
|
+
if (!menuItem.keys || menuItem.keys.length === 0) continue;
|
|
1011
|
+
|
|
1012
|
+
// Skip items not relevant for current calendar type
|
|
1013
|
+
if (menuItem.calendarType && menuItem.calendarType !== calendarType)
|
|
1014
|
+
continue;
|
|
1015
|
+
|
|
1016
|
+
// Skip items not relevant for current app mode
|
|
1017
|
+
if (menuItem.appMode && menuItem.appMode !== appMode) continue;
|
|
1018
|
+
|
|
1019
|
+
const matched = filteredSettings.filter((s) => {
|
|
1020
|
+
if (claimed.has(s.key)) return false;
|
|
1021
|
+
return menuItem.keys!.some((k) =>
|
|
1022
|
+
k.endsWith('.') ? s.key.startsWith(k) : s.key === k,
|
|
1023
|
+
);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
if (matched.length === 0) continue;
|
|
1027
|
+
|
|
1028
|
+
// Sort by key order within the menu item's keys array
|
|
1029
|
+
const order = registryKeyOrder();
|
|
1030
|
+
matched.sort((a, b) => {
|
|
1031
|
+
const aIdx = groupKeyIndex(a.key, menuItem.keys!);
|
|
1032
|
+
const bIdx = groupKeyIndex(b.key, menuItem.keys!);
|
|
1033
|
+
if (aIdx !== bIdx) return aIdx - bIdx;
|
|
1034
|
+
return (order.get(a.key) ?? Infinity) - (order.get(b.key) ?? Infinity);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
for (const s of matched) {
|
|
1038
|
+
claimed.add(s.key);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
result.push({
|
|
1042
|
+
id: menuItem.id,
|
|
1043
|
+
labelKey: menuItem.labelKey,
|
|
1044
|
+
icon: menuItem.icon,
|
|
1045
|
+
section: menuItem.section,
|
|
1046
|
+
settings: matched,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
return result;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// ---------------------------------------------------------------------------
|
|
1054
|
+
// Data type discriminator — stored alongside each setting in the DB
|
|
1055
|
+
// ---------------------------------------------------------------------------
|
|
1056
|
+
|
|
1057
|
+
export type SettingsDataType = 'string' | 'number' | 'boolean' | 'json';
|
|
1058
|
+
|
|
1059
|
+
// ---------------------------------------------------------------------------
|
|
1060
|
+
// Single registry entry definition
|
|
1061
|
+
// ---------------------------------------------------------------------------
|
|
1062
|
+
|
|
1063
|
+
export type SettingDef<T = unknown> = {
|
|
1064
|
+
category: SettingsCategory;
|
|
1065
|
+
type: SettingsDataType;
|
|
1066
|
+
default: T;
|
|
1067
|
+
/** Whether this setting should be synced to the server. Default true. */
|
|
1068
|
+
sync: boolean;
|
|
1069
|
+
/** UI component type for rendering this setting */
|
|
1070
|
+
uiType: SettingUiType;
|
|
1071
|
+
/** Available options for SELECT-type settings */
|
|
1072
|
+
options?: readonly SettingOption<T>[];
|
|
1073
|
+
/** Slider configuration for SLIDER-type settings */
|
|
1074
|
+
sliderConfig?: SliderConfig;
|
|
1075
|
+
/** Only show this setting for enrolled/kiosk devices (undefined = always) */
|
|
1076
|
+
appMode?: 'ENROLLED';
|
|
1077
|
+
/** Only show this setting for a specific calendar type (undefined = always) */
|
|
1078
|
+
calendarType?: CalendarType;
|
|
1079
|
+
/** Show this setting only when condition(s) are met */
|
|
1080
|
+
visibleWhen?: VisibilityRule;
|
|
1081
|
+
/** Disable (but still show) this setting when condition(s) are met */
|
|
1082
|
+
disabledWhen?: DisabledRule;
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
// ---------------------------------------------------------------------------
|
|
1086
|
+
// Configuration for app-specific defaults
|
|
1087
|
+
// ---------------------------------------------------------------------------
|
|
1088
|
+
|
|
1089
|
+
export type RegistryConfig = {
|
|
1090
|
+
/** Whether the device is in enrolled/kiosk mode */
|
|
1091
|
+
isEnrolled: boolean;
|
|
1092
|
+
/** Default theme code */
|
|
1093
|
+
defaultTheme: ThemeSetting;
|
|
1094
|
+
/** Default locale code */
|
|
1095
|
+
defaultLocale: LocaleCode;
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
/** Default config for non-enrolled mode */
|
|
1099
|
+
export const DEFAULT_REGISTRY_CONFIG: RegistryConfig = {
|
|
1100
|
+
isEnrolled: false,
|
|
1101
|
+
defaultTheme: 'system',
|
|
1102
|
+
defaultLocale: 'nb',
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
// ---------------------------------------------------------------------------
|
|
1106
|
+
// Shared option arrays (reused across multiple settings)
|
|
1107
|
+
// ---------------------------------------------------------------------------
|
|
1108
|
+
|
|
1109
|
+
const ALARM_SOUND_OPTIONS: readonly SettingOption<AlarmSound>[] = [
|
|
1110
|
+
{ value: 'none', labelKey: 'AlarmAsset.none' },
|
|
1111
|
+
{ value: 'alarm1', labelKey: 'AlarmAsset.alarm1' },
|
|
1112
|
+
{ value: 'alarm2', labelKey: 'AlarmAsset.alarm2' },
|
|
1113
|
+
{ value: 'alarm3', labelKey: 'AlarmAsset.alarm3' },
|
|
1114
|
+
{ value: 'alarm4', labelKey: 'AlarmAsset.alarm4' },
|
|
1115
|
+
{ value: 'alarm5', labelKey: 'AlarmAsset.alarm5' },
|
|
1116
|
+
{ value: 'alarm6', labelKey: 'AlarmAsset.alarm6' },
|
|
1117
|
+
{ value: 'alarm7', labelKey: 'AlarmAsset.alarm7' },
|
|
1118
|
+
{ value: 'alarm8', labelKey: 'AlarmAsset.alarm8' },
|
|
1119
|
+
{ value: 'alarm9', labelKey: 'AlarmAsset.alarm9' },
|
|
1120
|
+
];
|
|
1121
|
+
|
|
1122
|
+
const ALARM_TIMEOUT_OPTIONS: readonly SettingOption<AlarmTimeout>[] = [
|
|
1123
|
+
{ value: 5, labelKey: 'Duration.CountSeconds', labelValues: { count: 5 } },
|
|
1124
|
+
{ value: 30, labelKey: 'Duration.CountSeconds', labelValues: { count: 30 } },
|
|
1125
|
+
{ value: 60, labelKey: 'Duration.CountMinutes', labelValues: { count: 1 } },
|
|
1126
|
+
{ value: 120, labelKey: 'Duration.CountMinutes', labelValues: { count: 2 } },
|
|
1127
|
+
{ value: 180, labelKey: 'Duration.CountMinutes', labelValues: { count: 3 } },
|
|
1128
|
+
{ value: 300, labelKey: 'Duration.CountMinutes', labelValues: { count: 5 } },
|
|
1129
|
+
{ value: 600, labelKey: 'Duration.CountMinutes', labelValues: { count: 10 } },
|
|
1130
|
+
];
|
|
1131
|
+
|
|
1132
|
+
const DAY_VIEW_ZOOM_OPTIONS: readonly SettingOption<CalendarDayViewCellZoom>[] =
|
|
1133
|
+
[
|
|
1134
|
+
{ value: 15, labelKey: 'Settings.Option.DayViewZoom.15min' },
|
|
1135
|
+
{ value: 30, labelKey: 'Settings.Option.DayViewZoom.30min' },
|
|
1136
|
+
{ value: 60, labelKey: 'Settings.Option.DayViewZoom.1hour' },
|
|
1137
|
+
];
|
|
1138
|
+
|
|
1139
|
+
const TIME_OF_DAY_SLIDER: SliderConfig = { min: 0, max: 23, step: 1 };
|
|
1140
|
+
|
|
1141
|
+
// ---------------------------------------------------------------------------
|
|
1142
|
+
// Helper
|
|
1143
|
+
// ---------------------------------------------------------------------------
|
|
1144
|
+
|
|
1145
|
+
function def<T>(
|
|
1146
|
+
category: SettingsCategory,
|
|
1147
|
+
type: SettingsDataType,
|
|
1148
|
+
defaultValue: T,
|
|
1149
|
+
uiType: SettingUiType,
|
|
1150
|
+
extra?: {
|
|
1151
|
+
sync?: boolean;
|
|
1152
|
+
options?: readonly SettingOption<T>[];
|
|
1153
|
+
sliderConfig?: SliderConfig;
|
|
1154
|
+
appMode?: 'ENROLLED';
|
|
1155
|
+
calendarType?: CalendarType;
|
|
1156
|
+
visibleWhen?: VisibilityRule;
|
|
1157
|
+
disabledWhen?: DisabledRule;
|
|
1158
|
+
},
|
|
1159
|
+
): SettingDef<T> {
|
|
1160
|
+
const result: SettingDef<T> = {
|
|
1161
|
+
category,
|
|
1162
|
+
type,
|
|
1163
|
+
default: defaultValue,
|
|
1164
|
+
sync: extra?.sync ?? true,
|
|
1165
|
+
uiType,
|
|
1166
|
+
};
|
|
1167
|
+
if (extra?.options) {
|
|
1168
|
+
result.options = extra.options;
|
|
1169
|
+
}
|
|
1170
|
+
if (extra?.sliderConfig) {
|
|
1171
|
+
result.sliderConfig = extra.sliderConfig;
|
|
1172
|
+
}
|
|
1173
|
+
if (extra?.appMode) {
|
|
1174
|
+
result.appMode = extra.appMode;
|
|
1175
|
+
}
|
|
1176
|
+
if (extra?.calendarType) {
|
|
1177
|
+
result.calendarType = extra.calendarType;
|
|
1178
|
+
}
|
|
1179
|
+
if (extra?.visibleWhen) {
|
|
1180
|
+
result.visibleWhen = extra.visibleWhen;
|
|
1181
|
+
}
|
|
1182
|
+
if (extra?.disabledWhen) {
|
|
1183
|
+
result.disabledWhen = extra.disabledWhen;
|
|
1184
|
+
}
|
|
1185
|
+
return result;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// ---------------------------------------------------------------------------
|
|
1189
|
+
// Entry builder (internal — used by SettingsRegistry class and factory)
|
|
1190
|
+
// ---------------------------------------------------------------------------
|
|
1191
|
+
|
|
1192
|
+
function buildEntries(config: RegistryConfig) {
|
|
1193
|
+
return {
|
|
1194
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1195
|
+
// Appearance
|
|
1196
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1197
|
+
'appearance.theme': def<ThemeSetting>(
|
|
1198
|
+
'appearance',
|
|
1199
|
+
'string',
|
|
1200
|
+
config.defaultTheme,
|
|
1201
|
+
'CUSTOM_THEME_PICKER',
|
|
1202
|
+
{
|
|
1203
|
+
options: config.isEnrolled
|
|
1204
|
+
? [
|
|
1205
|
+
{ value: 'light', labelKey: 'Settings.Option.Theme.Light' },
|
|
1206
|
+
{ value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
|
|
1207
|
+
]
|
|
1208
|
+
: [
|
|
1209
|
+
{ value: 'light', labelKey: 'Settings.Option.Theme.Light' },
|
|
1210
|
+
{ value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
|
|
1211
|
+
{ value: 'system', labelKey: 'Settings.Option.Theme.System' },
|
|
1212
|
+
],
|
|
1213
|
+
},
|
|
1214
|
+
),
|
|
1215
|
+
'appearance.clockType': def<ClockType>(
|
|
1216
|
+
'appearance',
|
|
1217
|
+
'string',
|
|
1218
|
+
'digital',
|
|
1219
|
+
'CUSTOM_CLOCK_TYPE',
|
|
1220
|
+
{
|
|
1221
|
+
options: [
|
|
1222
|
+
{ value: 'digital', labelKey: 'Settings.Option.ClockType.Digital' },
|
|
1223
|
+
{ value: 'analog', labelKey: 'Settings.Option.ClockType.Analog' },
|
|
1224
|
+
],
|
|
1225
|
+
appMode: 'ENROLLED',
|
|
1226
|
+
},
|
|
1227
|
+
),
|
|
1228
|
+
'appearance.enableDayColors': def<boolean>(
|
|
1229
|
+
'appearance',
|
|
1230
|
+
'boolean',
|
|
1231
|
+
false,
|
|
1232
|
+
'TOGGLE',
|
|
1233
|
+
{ calendarType: 'chronological' },
|
|
1234
|
+
),
|
|
1235
|
+
|
|
1236
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1237
|
+
// Calendar view
|
|
1238
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1239
|
+
'calendarView.type': def<CalendarType>(
|
|
1240
|
+
'calendarView',
|
|
1241
|
+
'string',
|
|
1242
|
+
'time-based',
|
|
1243
|
+
'CUSTOM_CALENDAR_TYPE',
|
|
1244
|
+
{
|
|
1245
|
+
options: [
|
|
1246
|
+
{
|
|
1247
|
+
value: 'chronological',
|
|
1248
|
+
labelKey: 'Settings.Option.CalendarType.Chronological',
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
value: 'time-based',
|
|
1252
|
+
labelKey: 'Settings.Option.CalendarType.TimeBased',
|
|
1253
|
+
},
|
|
1254
|
+
],
|
|
1255
|
+
},
|
|
1256
|
+
),
|
|
1257
|
+
'calendarView.view': def<CalendarViewMode>(
|
|
1258
|
+
'calendarView',
|
|
1259
|
+
'string',
|
|
1260
|
+
'day',
|
|
1261
|
+
'SELECT',
|
|
1262
|
+
{
|
|
1263
|
+
options: [
|
|
1264
|
+
{ value: 'day', labelKey: 'Settings.Option.CalendarView.Day' },
|
|
1265
|
+
{
|
|
1266
|
+
value: '3-days',
|
|
1267
|
+
labelKey: 'Settings.Option.CalendarView.3Days',
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
value: '5-days',
|
|
1271
|
+
labelKey: 'Settings.Option.CalendarView.5Days',
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
value: '7-days',
|
|
1275
|
+
labelKey: 'Settings.Option.CalendarView.7Days',
|
|
1276
|
+
},
|
|
1277
|
+
{ value: 'week', labelKey: 'Settings.Option.CalendarView.Week' },
|
|
1278
|
+
{
|
|
1279
|
+
value: 'month',
|
|
1280
|
+
labelKey: 'Settings.Option.CalendarView.Month',
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
value: 'overview',
|
|
1284
|
+
labelKey: 'Settings.Option.CalendarView.Overview',
|
|
1285
|
+
},
|
|
1286
|
+
],
|
|
1287
|
+
},
|
|
1288
|
+
),
|
|
1289
|
+
'calendarView.dayViewZoom': def<CalendarDayViewCellZoom>(
|
|
1290
|
+
'calendarView',
|
|
1291
|
+
'number',
|
|
1292
|
+
60,
|
|
1293
|
+
'SELECT',
|
|
1294
|
+
{ options: DAY_VIEW_ZOOM_OPTIONS, calendarType: 'time-based' },
|
|
1295
|
+
),
|
|
1296
|
+
'calendarView.weekViewZoom': def<CalendarDayViewCellZoom>(
|
|
1297
|
+
'calendarView',
|
|
1298
|
+
'number',
|
|
1299
|
+
60,
|
|
1300
|
+
'SELECT',
|
|
1301
|
+
{ options: DAY_VIEW_ZOOM_OPTIONS, calendarType: 'time-based' },
|
|
1302
|
+
),
|
|
1303
|
+
'calendarView.splitView': def<boolean>(
|
|
1304
|
+
'calendarView',
|
|
1305
|
+
'boolean',
|
|
1306
|
+
false,
|
|
1307
|
+
'TOGGLE',
|
|
1308
|
+
),
|
|
1309
|
+
'calendarView.showCalendarNames': def<boolean>(
|
|
1310
|
+
'calendarView',
|
|
1311
|
+
'boolean',
|
|
1312
|
+
true,
|
|
1313
|
+
'TOGGLE',
|
|
1314
|
+
),
|
|
1315
|
+
'calendarView.calendarColumns': def<unknown[]>(
|
|
1316
|
+
'calendarView',
|
|
1317
|
+
'json',
|
|
1318
|
+
[],
|
|
1319
|
+
'CUSTOM_SPLIT_VIEW_CONFIG',
|
|
1320
|
+
),
|
|
1321
|
+
'calendarView.autoReturnToTodayEnabled': def<boolean>(
|
|
1322
|
+
'calendarView',
|
|
1323
|
+
'boolean',
|
|
1324
|
+
config.isEnrolled,
|
|
1325
|
+
'TOGGLE',
|
|
1326
|
+
),
|
|
1327
|
+
'calendarView.autoReturnToTodayTimeoutSeconds': def<number>(
|
|
1328
|
+
'calendarView',
|
|
1329
|
+
'number',
|
|
1330
|
+
300,
|
|
1331
|
+
'SLIDER',
|
|
1332
|
+
{
|
|
1333
|
+
sliderConfig: { min: 30, max: 600, step: 30 },
|
|
1334
|
+
visibleWhen: { dependsOn: 'calendarView.autoReturnToTodayEnabled', isTruthy: true },
|
|
1335
|
+
},
|
|
1336
|
+
),
|
|
1337
|
+
'calendarView.showWeatherOnEvents': def<boolean>(
|
|
1338
|
+
'calendarView',
|
|
1339
|
+
'boolean',
|
|
1340
|
+
false,
|
|
1341
|
+
'TOGGLE',
|
|
1342
|
+
),
|
|
1343
|
+
'calendarView.showWeatherOnTimeline': def<boolean>(
|
|
1344
|
+
'calendarView',
|
|
1345
|
+
'boolean',
|
|
1346
|
+
false,
|
|
1347
|
+
'TOGGLE',
|
|
1348
|
+
),
|
|
1349
|
+
'calendarView.weatherLocation': def<WeatherLocation | null>(
|
|
1350
|
+
'calendarView',
|
|
1351
|
+
'json',
|
|
1352
|
+
null,
|
|
1353
|
+
'CUSTOM_WEATHER_LOCATION',
|
|
1354
|
+
{ visibleWhen: { dependsOn: 'calendarView.showWeatherOnTimeline', isTruthy: true } },
|
|
1355
|
+
),
|
|
1356
|
+
|
|
1357
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1358
|
+
// Event form field visibility (time-based)
|
|
1359
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1360
|
+
'eventForm.recurrence': def<boolean>(
|
|
1361
|
+
'eventForm',
|
|
1362
|
+
'boolean',
|
|
1363
|
+
true,
|
|
1364
|
+
'TOGGLE',
|
|
1365
|
+
),
|
|
1366
|
+
'eventForm.location': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
|
|
1367
|
+
'eventForm.travelTime': def<boolean>(
|
|
1368
|
+
'eventForm',
|
|
1369
|
+
'boolean',
|
|
1370
|
+
false,
|
|
1371
|
+
'TOGGLE',
|
|
1372
|
+
),
|
|
1373
|
+
'eventForm.reminders': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
|
|
1374
|
+
'eventForm.emailReminders': def<boolean>(
|
|
1375
|
+
'eventForm',
|
|
1376
|
+
'boolean',
|
|
1377
|
+
false,
|
|
1378
|
+
'TOGGLE',
|
|
1379
|
+
),
|
|
1380
|
+
'eventForm.description': def<boolean>(
|
|
1381
|
+
'eventForm',
|
|
1382
|
+
'boolean',
|
|
1383
|
+
true,
|
|
1384
|
+
'TOGGLE',
|
|
1385
|
+
),
|
|
1386
|
+
'eventForm.checklist': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
|
|
1387
|
+
'eventForm.images': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
|
|
1388
|
+
'eventForm.audioClips': def<boolean>(
|
|
1389
|
+
'eventForm',
|
|
1390
|
+
'boolean',
|
|
1391
|
+
false,
|
|
1392
|
+
'TOGGLE',
|
|
1393
|
+
),
|
|
1394
|
+
'eventForm.notificationReceivers': def<boolean>(
|
|
1395
|
+
'eventForm',
|
|
1396
|
+
'boolean',
|
|
1397
|
+
true,
|
|
1398
|
+
'TOGGLE',
|
|
1399
|
+
),
|
|
1400
|
+
'eventForm.visibility': def<boolean>(
|
|
1401
|
+
'eventForm',
|
|
1402
|
+
'boolean',
|
|
1403
|
+
false,
|
|
1404
|
+
'TOGGLE',
|
|
1405
|
+
),
|
|
1406
|
+
|
|
1407
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1408
|
+
// Sound & alerts
|
|
1409
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1410
|
+
'sound.timerVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', {
|
|
1411
|
+
visibleWhen: { dependsOn: 'sound.timerAlarmSound', notEquals: 'none' },
|
|
1412
|
+
}),
|
|
1413
|
+
'sound.reminderVolume': def<number>(
|
|
1414
|
+
'sound',
|
|
1415
|
+
'number',
|
|
1416
|
+
0.5,
|
|
1417
|
+
'VOLUME_SLIDER',
|
|
1418
|
+
{
|
|
1419
|
+
appMode: 'ENROLLED',
|
|
1420
|
+
visibleWhen: { dependsOn: 'sound.reminderAlarmSound', notEquals: 'none' },
|
|
1421
|
+
},
|
|
1422
|
+
),
|
|
1423
|
+
'sound.mediaVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', {
|
|
1424
|
+
appMode: 'ENROLLED',
|
|
1425
|
+
}),
|
|
1426
|
+
'sound.alarmSound': def<AlarmSound>('sound', 'string', 'alarm1', 'SELECT', {
|
|
1427
|
+
options: ALARM_SOUND_OPTIONS,
|
|
1428
|
+
}),
|
|
1429
|
+
'sound.reminderAlarmSound': def<AlarmSound>(
|
|
1430
|
+
'sound',
|
|
1431
|
+
'string',
|
|
1432
|
+
'alarm1',
|
|
1433
|
+
'SELECT',
|
|
1434
|
+
{ options: ALARM_SOUND_OPTIONS },
|
|
1435
|
+
),
|
|
1436
|
+
'sound.startAlarmSound': def<AlarmSound>(
|
|
1437
|
+
'sound',
|
|
1438
|
+
'string',
|
|
1439
|
+
'alarm1',
|
|
1440
|
+
'SELECT',
|
|
1441
|
+
{
|
|
1442
|
+
options: ALARM_SOUND_OPTIONS,
|
|
1443
|
+
visibleWhen: { dependsOn: 'calendarView.type', equals: 'chronological' },
|
|
1444
|
+
},
|
|
1445
|
+
),
|
|
1446
|
+
'sound.endAlarmSound': def<AlarmSound>(
|
|
1447
|
+
'sound',
|
|
1448
|
+
'string',
|
|
1449
|
+
'alarm1',
|
|
1450
|
+
'SELECT',
|
|
1451
|
+
{
|
|
1452
|
+
options: ALARM_SOUND_OPTIONS,
|
|
1453
|
+
visibleWhen: { dependsOn: 'calendarView.type', equals: 'chronological' },
|
|
1454
|
+
},
|
|
1455
|
+
),
|
|
1456
|
+
'sound.timerAlarmSound': def<AlarmSound>(
|
|
1457
|
+
'sound',
|
|
1458
|
+
'string',
|
|
1459
|
+
'alarm1',
|
|
1460
|
+
'SELECT',
|
|
1461
|
+
{ options: ALARM_SOUND_OPTIONS },
|
|
1462
|
+
),
|
|
1463
|
+
'sound.timerAlarmTimeout': def<AlarmTimeout>(
|
|
1464
|
+
'sound',
|
|
1465
|
+
'number',
|
|
1466
|
+
180,
|
|
1467
|
+
'SELECT',
|
|
1468
|
+
{
|
|
1469
|
+
options: ALARM_TIMEOUT_OPTIONS,
|
|
1470
|
+
visibleWhen: { dependsOn: 'sound.timerAlarmSound', notEquals: 'none' },
|
|
1471
|
+
},
|
|
1472
|
+
),
|
|
1473
|
+
'sound.reminderAlarmTimeout': def<AlarmTimeout>(
|
|
1474
|
+
'sound',
|
|
1475
|
+
'number',
|
|
1476
|
+
180,
|
|
1477
|
+
'SELECT',
|
|
1478
|
+
{
|
|
1479
|
+
options: ALARM_TIMEOUT_OPTIONS,
|
|
1480
|
+
visibleWhen: { dependsOn: 'sound.reminderAlarmSound', notEquals: 'none' },
|
|
1481
|
+
},
|
|
1482
|
+
),
|
|
1483
|
+
'sound.allowCustomReminderSounds': def<boolean>(
|
|
1484
|
+
'sound',
|
|
1485
|
+
'boolean',
|
|
1486
|
+
false,
|
|
1487
|
+
'HIDDEN',
|
|
1488
|
+
),
|
|
1489
|
+
'sound.ttsEnabled': def<boolean>(
|
|
1490
|
+
'sound',
|
|
1491
|
+
'boolean',
|
|
1492
|
+
config.isEnrolled,
|
|
1493
|
+
'TOGGLE',
|
|
1494
|
+
{ appMode: 'ENROLLED' },
|
|
1495
|
+
),
|
|
1496
|
+
'sound.ttsRate': def<number>('sound', 'number', 1.0, 'SLIDER', {
|
|
1497
|
+
sliderConfig: { min: 0.5, max: 2, step: 0.1 },
|
|
1498
|
+
appMode: 'ENROLLED',
|
|
1499
|
+
visibleWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true },
|
|
1500
|
+
}),
|
|
1501
|
+
|
|
1502
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1503
|
+
// Timer
|
|
1504
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1505
|
+
'timer.face': def<'ring' | 'bars'>('timer', 'string', 'ring', 'SELECT', {
|
|
1506
|
+
options: [
|
|
1507
|
+
{ value: 'ring', labelKey: 'Timer.FaceRing' },
|
|
1508
|
+
{ value: 'bars', labelKey: 'Timer.FaceBars' },
|
|
1509
|
+
],
|
|
1510
|
+
}),
|
|
1511
|
+
'timer.showTimeRemaining': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
|
|
1512
|
+
'timer.showEndTime': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
|
|
1513
|
+
'timer.showRestartButton': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
|
|
1514
|
+
'timer.showPauseButton': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
|
|
1515
|
+
|
|
1516
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1517
|
+
// Lock screen
|
|
1518
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1519
|
+
'lockScreen.inactivityLockEnabled': def<boolean>(
|
|
1520
|
+
'lockScreen',
|
|
1521
|
+
'boolean',
|
|
1522
|
+
false,
|
|
1523
|
+
'TOGGLE',
|
|
1524
|
+
),
|
|
1525
|
+
'lockScreen.inactivityTimeoutMinutes': def<InactivityTimeoutMinutes>(
|
|
1526
|
+
'lockScreen',
|
|
1527
|
+
'number',
|
|
1528
|
+
5,
|
|
1529
|
+
'SELECT',
|
|
1530
|
+
{
|
|
1531
|
+
options: [
|
|
1532
|
+
{ value: 1, labelKey: 'Settings.Option.InactivityTimeout.1min' },
|
|
1533
|
+
{ value: 5, labelKey: 'Settings.Option.InactivityTimeout.5min' },
|
|
1534
|
+
{ value: 10, labelKey: 'Settings.Option.InactivityTimeout.10min' },
|
|
1535
|
+
{ value: 15, labelKey: 'Settings.Option.InactivityTimeout.15min' },
|
|
1536
|
+
{ value: 30, labelKey: 'Settings.Option.InactivityTimeout.30min' },
|
|
1537
|
+
{ value: 45, labelKey: 'Settings.Option.InactivityTimeout.45min' },
|
|
1538
|
+
],
|
|
1539
|
+
visibleWhen: { dependsOn: 'lockScreen.inactivityLockEnabled', isTruthy: true },
|
|
1540
|
+
},
|
|
1541
|
+
),
|
|
1542
|
+
'lockScreen.pin': def<string>('lockScreen', 'string', '', 'PIN_INPUT'),
|
|
1543
|
+
'lockScreen.clockDisplay': def<LockScreenClockDisplay>(
|
|
1544
|
+
'lockScreen',
|
|
1545
|
+
'string',
|
|
1546
|
+
'digital',
|
|
1547
|
+
'SELECT',
|
|
1548
|
+
{
|
|
1549
|
+
options: [
|
|
1550
|
+
{ value: 'none', labelKey: 'Settings.Option.ClockDisplay.None' },
|
|
1551
|
+
{
|
|
1552
|
+
value: 'digital',
|
|
1553
|
+
labelKey: 'Settings.Option.ClockDisplay.Digital',
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
value: 'analog',
|
|
1557
|
+
labelKey: 'Settings.Option.ClockDisplay.Analog',
|
|
1558
|
+
},
|
|
1559
|
+
],
|
|
1560
|
+
},
|
|
1561
|
+
),
|
|
1562
|
+
'lockScreen.showHourNumbers': def<boolean>(
|
|
1563
|
+
'lockScreen',
|
|
1564
|
+
'boolean',
|
|
1565
|
+
true,
|
|
1566
|
+
'TOGGLE',
|
|
1567
|
+
{ visibleWhen: { dependsOn: 'lockScreen.clockDisplay', equals: 'analog' } },
|
|
1568
|
+
),
|
|
1569
|
+
'lockScreen.showDate': def<boolean>(
|
|
1570
|
+
'lockScreen',
|
|
1571
|
+
'boolean',
|
|
1572
|
+
true,
|
|
1573
|
+
'TOGGLE',
|
|
1574
|
+
),
|
|
1575
|
+
'lockScreen.imageMode': def<LockScreenImageMode>(
|
|
1576
|
+
'lockScreen',
|
|
1577
|
+
'string',
|
|
1578
|
+
'none',
|
|
1579
|
+
'SELECT',
|
|
1580
|
+
{
|
|
1581
|
+
options: [
|
|
1582
|
+
{ value: 'none', labelKey: 'Settings.Option.ImageMode.None' },
|
|
1583
|
+
{
|
|
1584
|
+
value: 'background',
|
|
1585
|
+
labelKey: 'Settings.Option.ImageMode.Background',
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
value: 'photoFrame',
|
|
1589
|
+
labelKey: 'Settings.Option.ImageMode.PhotoFrame',
|
|
1590
|
+
},
|
|
1591
|
+
],
|
|
1592
|
+
},
|
|
1593
|
+
),
|
|
1594
|
+
'lockScreen.backgroundImage': def<string | null>(
|
|
1595
|
+
'lockScreen',
|
|
1596
|
+
'json',
|
|
1597
|
+
null,
|
|
1598
|
+
'CUSTOM_IMAGE',
|
|
1599
|
+
{ visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'background' } },
|
|
1600
|
+
),
|
|
1601
|
+
'lockScreen.photoFrameIntervalSeconds': def<PhotoFrameIntervalSeconds>(
|
|
1602
|
+
'lockScreen',
|
|
1603
|
+
'number',
|
|
1604
|
+
60,
|
|
1605
|
+
'SELECT',
|
|
1606
|
+
{
|
|
1607
|
+
options: [
|
|
1608
|
+
{ value: 30, labelKey: 'Settings.Option.PhotoFrameInterval.30sec' },
|
|
1609
|
+
{ value: 60, labelKey: 'Settings.Option.PhotoFrameInterval.1min' },
|
|
1610
|
+
{ value: 120, labelKey: 'Settings.Option.PhotoFrameInterval.2min' },
|
|
1611
|
+
{ value: 300, labelKey: 'Settings.Option.PhotoFrameInterval.5min' },
|
|
1612
|
+
],
|
|
1613
|
+
visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'photoFrame' },
|
|
1614
|
+
},
|
|
1615
|
+
),
|
|
1616
|
+
|
|
1617
|
+
'lockScreen.photoFrameImages': def<LockScreenImage[]>(
|
|
1618
|
+
'lockScreen',
|
|
1619
|
+
'json',
|
|
1620
|
+
[],
|
|
1621
|
+
'CUSTOM_IMAGE_ARRAY',
|
|
1622
|
+
{ visibleWhen: { dependsOn: 'lockScreen.imageMode', equals: 'photoFrame' } },
|
|
1623
|
+
),
|
|
1624
|
+
|
|
1625
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1626
|
+
// Touch / gestures
|
|
1627
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1628
|
+
'touch.enableTapToCreate': def<boolean>(
|
|
1629
|
+
'touch',
|
|
1630
|
+
'boolean',
|
|
1631
|
+
false,
|
|
1632
|
+
'TOGGLE',
|
|
1633
|
+
{ disabledWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true } },
|
|
1634
|
+
),
|
|
1635
|
+
'touch.enableDragDrop': def<boolean>('touch', 'boolean', false, 'TOGGLE', {
|
|
1636
|
+
disabledWhen: { dependsOn: 'sound.ttsEnabled', isTruthy: true },
|
|
1637
|
+
}),
|
|
1638
|
+
|
|
1639
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1640
|
+
// Device (not synced unless noted)
|
|
1641
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1642
|
+
'device.id': def<string>('device', 'string', '', 'HIDDEN', { sync: false }),
|
|
1643
|
+
'device.timePickerMode': def<TimePickerMode>(
|
|
1644
|
+
'device',
|
|
1645
|
+
'string',
|
|
1646
|
+
'dials',
|
|
1647
|
+
'SELECT',
|
|
1648
|
+
{
|
|
1649
|
+
options: [
|
|
1650
|
+
{ value: 'dials', labelKey: 'Settings.Option.TimePickerMode.Dials' },
|
|
1651
|
+
{
|
|
1652
|
+
value: 'keypad',
|
|
1653
|
+
labelKey: 'Settings.Option.TimePickerMode.Keypad',
|
|
1654
|
+
},
|
|
1655
|
+
],
|
|
1656
|
+
},
|
|
1657
|
+
),
|
|
1658
|
+
'device.devMenuEnabled': def<boolean>(
|
|
1659
|
+
'device',
|
|
1660
|
+
'boolean',
|
|
1661
|
+
false,
|
|
1662
|
+
'HIDDEN',
|
|
1663
|
+
{ sync: false },
|
|
1664
|
+
),
|
|
1665
|
+
'device.authWarningDismissTtlDays': def<number>(
|
|
1666
|
+
'device',
|
|
1667
|
+
'number',
|
|
1668
|
+
3,
|
|
1669
|
+
'HIDDEN',
|
|
1670
|
+
{ sync: false },
|
|
1671
|
+
),
|
|
1672
|
+
|
|
1673
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1674
|
+
// Language
|
|
1675
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1676
|
+
'language.locale': def<LocaleCode>(
|
|
1677
|
+
'language',
|
|
1678
|
+
'string',
|
|
1679
|
+
config.defaultLocale,
|
|
1680
|
+
'CUSTOM_LANGUAGE_PICKER',
|
|
1681
|
+
{
|
|
1682
|
+
options: [
|
|
1683
|
+
{ value: 'nb', labelKey: 'Settings.Option.Language.Norwegian' },
|
|
1684
|
+
{ value: 'en', labelKey: 'Settings.Option.Language.English' },
|
|
1685
|
+
],
|
|
1686
|
+
},
|
|
1687
|
+
),
|
|
1688
|
+
|
|
1689
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1690
|
+
// Notifications
|
|
1691
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1692
|
+
'notification.enabled': def<boolean>(
|
|
1693
|
+
'notification',
|
|
1694
|
+
'boolean',
|
|
1695
|
+
false,
|
|
1696
|
+
'TOGGLE',
|
|
1697
|
+
),
|
|
1698
|
+
'notification.notifyAllCalendars': def<boolean>(
|
|
1699
|
+
'notification',
|
|
1700
|
+
'boolean',
|
|
1701
|
+
true,
|
|
1702
|
+
'TOGGLE',
|
|
1703
|
+
),
|
|
1704
|
+
'notification.enabledCalendarIds': def<string[]>(
|
|
1705
|
+
'notification',
|
|
1706
|
+
'json',
|
|
1707
|
+
[],
|
|
1708
|
+
'CUSTOM_CALENDAR_IDS',
|
|
1709
|
+
),
|
|
1710
|
+
'notification.hasBeenPrompted': def<boolean>(
|
|
1711
|
+
'notification',
|
|
1712
|
+
'boolean',
|
|
1713
|
+
false,
|
|
1714
|
+
'HIDDEN',
|
|
1715
|
+
{ sync: false },
|
|
1716
|
+
),
|
|
1717
|
+
|
|
1718
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1719
|
+
// Chronological features (header, footer, menu, quick settings, timer)
|
|
1720
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1721
|
+
// Header
|
|
1722
|
+
'chronological.header.showNavigationArrows': def<boolean>(
|
|
1723
|
+
'chronological',
|
|
1724
|
+
'boolean',
|
|
1725
|
+
true,
|
|
1726
|
+
'TOGGLE',
|
|
1727
|
+
),
|
|
1728
|
+
'chronological.header.showCurrentYearInDate': def<boolean>(
|
|
1729
|
+
'chronological',
|
|
1730
|
+
'boolean',
|
|
1731
|
+
false,
|
|
1732
|
+
'TOGGLE',
|
|
1733
|
+
),
|
|
1734
|
+
'chronological.header.showClock': def<boolean>(
|
|
1735
|
+
'chronological',
|
|
1736
|
+
'boolean',
|
|
1737
|
+
true,
|
|
1738
|
+
'TOGGLE',
|
|
1739
|
+
),
|
|
1740
|
+
'chronological.header.showTimeOfDay': def<boolean>(
|
|
1741
|
+
'chronological',
|
|
1742
|
+
'boolean',
|
|
1743
|
+
false,
|
|
1744
|
+
'TOGGLE',
|
|
1745
|
+
),
|
|
1746
|
+
// Footer
|
|
1747
|
+
'chronological.footer.showMenuButton': def<boolean>(
|
|
1748
|
+
'chronological',
|
|
1749
|
+
'boolean',
|
|
1750
|
+
true,
|
|
1751
|
+
'TOGGLE',
|
|
1752
|
+
),
|
|
1753
|
+
'chronological.footer.showViewSwitcherDay': def<boolean>(
|
|
1754
|
+
'chronological',
|
|
1755
|
+
'boolean',
|
|
1756
|
+
true,
|
|
1757
|
+
'TOGGLE',
|
|
1758
|
+
),
|
|
1759
|
+
'chronological.footer.showViewSwitcherWeek': def<boolean>(
|
|
1760
|
+
'chronological',
|
|
1761
|
+
'boolean',
|
|
1762
|
+
true,
|
|
1763
|
+
'TOGGLE',
|
|
1764
|
+
),
|
|
1765
|
+
'chronological.footer.showViewSwitcherMonth': def<boolean>(
|
|
1766
|
+
'chronological',
|
|
1767
|
+
'boolean',
|
|
1768
|
+
true,
|
|
1769
|
+
'TOGGLE',
|
|
1770
|
+
),
|
|
1771
|
+
'chronological.footer.showTimerButton': def<boolean>(
|
|
1772
|
+
'chronological',
|
|
1773
|
+
'boolean',
|
|
1774
|
+
true,
|
|
1775
|
+
'TOGGLE',
|
|
1776
|
+
),
|
|
1777
|
+
'chronological.footer.showNewEventButton': def<boolean>(
|
|
1778
|
+
'chronological',
|
|
1779
|
+
'boolean',
|
|
1780
|
+
true,
|
|
1781
|
+
'TOGGLE',
|
|
1782
|
+
),
|
|
1783
|
+
'chronological.footer.showNowButton': def<boolean>(
|
|
1784
|
+
'chronological',
|
|
1785
|
+
'boolean',
|
|
1786
|
+
true,
|
|
1787
|
+
'TOGGLE',
|
|
1788
|
+
),
|
|
1789
|
+
'chronological.footer.showSettingsButton': def<boolean>(
|
|
1790
|
+
'chronological',
|
|
1791
|
+
'boolean',
|
|
1792
|
+
true,
|
|
1793
|
+
'TOGGLE',
|
|
1794
|
+
),
|
|
1795
|
+
// Timer features
|
|
1796
|
+
'chronological.timer.showNewCountdown': def<boolean>(
|
|
1797
|
+
'chronological',
|
|
1798
|
+
'boolean',
|
|
1799
|
+
true,
|
|
1800
|
+
'TOGGLE',
|
|
1801
|
+
),
|
|
1802
|
+
'chronological.timer.showFromTemplate': def<boolean>(
|
|
1803
|
+
'chronological',
|
|
1804
|
+
'boolean',
|
|
1805
|
+
true,
|
|
1806
|
+
'TOGGLE',
|
|
1807
|
+
),
|
|
1808
|
+
'chronological.timer.showAddTemplate': def<boolean>(
|
|
1809
|
+
'chronological',
|
|
1810
|
+
'boolean',
|
|
1811
|
+
true,
|
|
1812
|
+
'TOGGLE',
|
|
1813
|
+
),
|
|
1814
|
+
'chronological.timer.showEditTemplate': def<boolean>(
|
|
1815
|
+
'chronological',
|
|
1816
|
+
'boolean',
|
|
1817
|
+
true,
|
|
1818
|
+
'TOGGLE',
|
|
1819
|
+
),
|
|
1820
|
+
'chronological.timer.showDeleteTemplate': def<boolean>(
|
|
1821
|
+
'chronological',
|
|
1822
|
+
'boolean',
|
|
1823
|
+
true,
|
|
1824
|
+
'TOGGLE',
|
|
1825
|
+
),
|
|
1826
|
+
// Menu
|
|
1827
|
+
'chronological.menu.showSettingsButton': def<boolean>(
|
|
1828
|
+
'chronological',
|
|
1829
|
+
'boolean',
|
|
1830
|
+
true,
|
|
1831
|
+
'TOGGLE',
|
|
1832
|
+
),
|
|
1833
|
+
// Quick settings
|
|
1834
|
+
'chronological.quickSettings.showTimerVolume': def<boolean>(
|
|
1835
|
+
'chronological',
|
|
1836
|
+
'boolean',
|
|
1837
|
+
true,
|
|
1838
|
+
'TOGGLE',
|
|
1839
|
+
),
|
|
1840
|
+
'chronological.quickSettings.showReminderVolume': def<boolean>(
|
|
1841
|
+
'chronological',
|
|
1842
|
+
'boolean',
|
|
1843
|
+
true,
|
|
1844
|
+
'TOGGLE',
|
|
1845
|
+
),
|
|
1846
|
+
'chronological.quickSettings.showMediaVolume': def<boolean>(
|
|
1847
|
+
'chronological',
|
|
1848
|
+
'boolean',
|
|
1849
|
+
true,
|
|
1850
|
+
'TOGGLE',
|
|
1851
|
+
),
|
|
1852
|
+
'chronological.quickSettings.showBrightness': def<boolean>(
|
|
1853
|
+
'chronological',
|
|
1854
|
+
'boolean',
|
|
1855
|
+
true,
|
|
1856
|
+
'TOGGLE',
|
|
1857
|
+
),
|
|
1858
|
+
'chronological.quickSettings.showLockScreen': def<boolean>(
|
|
1859
|
+
'chronological',
|
|
1860
|
+
'boolean',
|
|
1861
|
+
true,
|
|
1862
|
+
'TOGGLE',
|
|
1863
|
+
),
|
|
1864
|
+
'chronological.quickSettings.showDayViewMode': def<boolean>(
|
|
1865
|
+
'chronological',
|
|
1866
|
+
'boolean',
|
|
1867
|
+
true,
|
|
1868
|
+
'TOGGLE',
|
|
1869
|
+
),
|
|
1870
|
+
// Day view display mode
|
|
1871
|
+
'chronological.dayView.displayMode':
|
|
1872
|
+
def<ChronologicalDayViewDisplayMode>(
|
|
1873
|
+
'chronological',
|
|
1874
|
+
'string',
|
|
1875
|
+
'list',
|
|
1876
|
+
'SELECT',
|
|
1877
|
+
{
|
|
1878
|
+
options: [
|
|
1879
|
+
{
|
|
1880
|
+
value: 'list' as const,
|
|
1881
|
+
labelKey: 'Settings.Option.DayViewDisplayMode.List',
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
value: 'timeline' as const,
|
|
1885
|
+
labelKey: 'Settings.Option.DayViewDisplayMode.Timeline',
|
|
1886
|
+
},
|
|
1887
|
+
],
|
|
1888
|
+
calendarType: 'chronological',
|
|
1889
|
+
},
|
|
1890
|
+
),
|
|
1891
|
+
// Time-of-day periods
|
|
1892
|
+
'chronological.timeOfDay.morningStart': def<number>(
|
|
1893
|
+
'chronological',
|
|
1894
|
+
'number',
|
|
1895
|
+
6,
|
|
1896
|
+
'SLIDER',
|
|
1897
|
+
{ sliderConfig: TIME_OF_DAY_SLIDER },
|
|
1898
|
+
),
|
|
1899
|
+
'chronological.timeOfDay.forenoonStart': def<number>(
|
|
1900
|
+
'chronological',
|
|
1901
|
+
'number',
|
|
1902
|
+
9,
|
|
1903
|
+
'SLIDER',
|
|
1904
|
+
{ sliderConfig: TIME_OF_DAY_SLIDER },
|
|
1905
|
+
),
|
|
1906
|
+
'chronological.timeOfDay.afternoonStart': def<number>(
|
|
1907
|
+
'chronological',
|
|
1908
|
+
'number',
|
|
1909
|
+
12,
|
|
1910
|
+
'SLIDER',
|
|
1911
|
+
{ sliderConfig: TIME_OF_DAY_SLIDER },
|
|
1912
|
+
),
|
|
1913
|
+
'chronological.timeOfDay.eveningStart': def<number>(
|
|
1914
|
+
'chronological',
|
|
1915
|
+
'number',
|
|
1916
|
+
18,
|
|
1917
|
+
'SLIDER',
|
|
1918
|
+
{ sliderConfig: TIME_OF_DAY_SLIDER },
|
|
1919
|
+
),
|
|
1920
|
+
'chronological.timeOfDay.nightStart': def<number>(
|
|
1921
|
+
'chronological',
|
|
1922
|
+
'number',
|
|
1923
|
+
0,
|
|
1924
|
+
'SLIDER',
|
|
1925
|
+
{ sliderConfig: TIME_OF_DAY_SLIDER },
|
|
1926
|
+
),
|
|
1927
|
+
|
|
1928
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1929
|
+
// Chronological event form
|
|
1930
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1931
|
+
// Fixed fields
|
|
1932
|
+
'chronologicalEventForm.fixedField.category': def<boolean>(
|
|
1933
|
+
'chronologicalEventForm',
|
|
1934
|
+
'boolean',
|
|
1935
|
+
true,
|
|
1936
|
+
'TOGGLE',
|
|
1937
|
+
),
|
|
1938
|
+
'chronologicalEventForm.fixedField.allDay': def<boolean>(
|
|
1939
|
+
'chronologicalEventForm',
|
|
1940
|
+
'boolean',
|
|
1941
|
+
true,
|
|
1942
|
+
'TOGGLE',
|
|
1943
|
+
),
|
|
1944
|
+
'chronologicalEventForm.fixedField.endTime': def<boolean>(
|
|
1945
|
+
'chronologicalEventForm',
|
|
1946
|
+
'boolean',
|
|
1947
|
+
true,
|
|
1948
|
+
'TOGGLE',
|
|
1949
|
+
),
|
|
1950
|
+
'chronologicalEventForm.fixedField.alarm': def<boolean>(
|
|
1951
|
+
'chronologicalEventForm',
|
|
1952
|
+
'boolean',
|
|
1953
|
+
true,
|
|
1954
|
+
'TOGGLE',
|
|
1955
|
+
),
|
|
1956
|
+
'chronologicalEventForm.fixedField.visibility': def<boolean>(
|
|
1957
|
+
'chronologicalEventForm',
|
|
1958
|
+
'boolean',
|
|
1959
|
+
true,
|
|
1960
|
+
'TOGGLE',
|
|
1961
|
+
),
|
|
1962
|
+
// Toggleable fields
|
|
1963
|
+
'chronologicalEventForm.field.acknowledge': def<boolean>(
|
|
1964
|
+
'chronologicalEventForm',
|
|
1965
|
+
'boolean',
|
|
1966
|
+
true,
|
|
1967
|
+
'TOGGLE',
|
|
1968
|
+
),
|
|
1969
|
+
'chronologicalEventForm.field.description': def<boolean>(
|
|
1970
|
+
'chronologicalEventForm',
|
|
1971
|
+
'boolean',
|
|
1972
|
+
true,
|
|
1973
|
+
'TOGGLE',
|
|
1974
|
+
),
|
|
1975
|
+
'chronologicalEventForm.field.recurrence': def<boolean>(
|
|
1976
|
+
'chronologicalEventForm',
|
|
1977
|
+
'boolean',
|
|
1978
|
+
true,
|
|
1979
|
+
'TOGGLE',
|
|
1980
|
+
),
|
|
1981
|
+
'chronologicalEventForm.field.checklist': def<boolean>(
|
|
1982
|
+
'chronologicalEventForm',
|
|
1983
|
+
'boolean',
|
|
1984
|
+
true,
|
|
1985
|
+
'TOGGLE',
|
|
1986
|
+
),
|
|
1987
|
+
'chronologicalEventForm.field.extraImages': def<boolean>(
|
|
1988
|
+
'chronologicalEventForm',
|
|
1989
|
+
'boolean',
|
|
1990
|
+
true,
|
|
1991
|
+
'TOGGLE',
|
|
1992
|
+
),
|
|
1993
|
+
'chronologicalEventForm.field.reminders': def<boolean>(
|
|
1994
|
+
'chronologicalEventForm',
|
|
1995
|
+
'boolean',
|
|
1996
|
+
true,
|
|
1997
|
+
'TOGGLE',
|
|
1998
|
+
),
|
|
1999
|
+
'chronologicalEventForm.field.audioClips': def<boolean>(
|
|
2000
|
+
'chronologicalEventForm',
|
|
2001
|
+
'boolean',
|
|
2002
|
+
true,
|
|
2003
|
+
'TOGGLE',
|
|
2004
|
+
),
|
|
2005
|
+
'chronologicalEventForm.field.category': def<boolean>(
|
|
2006
|
+
'chronologicalEventForm',
|
|
2007
|
+
'boolean',
|
|
2008
|
+
true,
|
|
2009
|
+
'TOGGLE',
|
|
2010
|
+
),
|
|
2011
|
+
// Suggest end time
|
|
2012
|
+
'chronologicalEventForm.suggestEndTime.enabled': def<boolean>(
|
|
2013
|
+
'chronologicalEventForm',
|
|
2014
|
+
'boolean',
|
|
2015
|
+
false,
|
|
2016
|
+
'TOGGLE',
|
|
2017
|
+
),
|
|
2018
|
+
'chronologicalEventForm.suggestEndTime.value': def<number>(
|
|
2019
|
+
'chronologicalEventForm',
|
|
2020
|
+
'number',
|
|
2021
|
+
30,
|
|
2022
|
+
'SLIDER',
|
|
2023
|
+
{ sliderConfig: { min: 5, max: 480, step: 5 } },
|
|
2024
|
+
),
|
|
2025
|
+
'chronologicalEventForm.suggestEndTime.unit': def<SuggestEndTimeUnit>(
|
|
2026
|
+
'chronologicalEventForm',
|
|
2027
|
+
'string',
|
|
2028
|
+
'minutes',
|
|
2029
|
+
'SELECT',
|
|
2030
|
+
{
|
|
2031
|
+
options: [
|
|
2032
|
+
{
|
|
2033
|
+
value: 'minutes',
|
|
2034
|
+
labelKey: 'Settings.Option.SuggestEndTimeUnit.Minutes',
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
value: 'hours',
|
|
2038
|
+
labelKey: 'Settings.Option.SuggestEndTimeUnit.Hours',
|
|
2039
|
+
},
|
|
2040
|
+
],
|
|
2041
|
+
},
|
|
2042
|
+
),
|
|
2043
|
+
// Default visibility
|
|
2044
|
+
'chronologicalEventForm.defaultVisibility': def<EventVisibility>(
|
|
2045
|
+
'chronologicalEventForm',
|
|
2046
|
+
'string',
|
|
2047
|
+
'Private',
|
|
2048
|
+
'SELECT',
|
|
2049
|
+
{
|
|
2050
|
+
options: [
|
|
2051
|
+
{ value: 'Public', labelKey: 'Settings.Option.Visibility.Public' },
|
|
2052
|
+
{ value: 'Private', labelKey: 'Settings.Option.Visibility.Private' },
|
|
2053
|
+
{ value: 'Custom', labelKey: 'Settings.Option.Visibility.Custom' },
|
|
2054
|
+
],
|
|
2055
|
+
},
|
|
2056
|
+
),
|
|
2057
|
+
// Default alarm toggles
|
|
2058
|
+
'chronologicalEventForm.defaultAlarm.atStart': def<boolean>(
|
|
2059
|
+
'chronologicalEventForm',
|
|
2060
|
+
'boolean',
|
|
2061
|
+
true,
|
|
2062
|
+
'TOGGLE',
|
|
2063
|
+
),
|
|
2064
|
+
'chronologicalEventForm.defaultAlarm.atEnd': def<boolean>(
|
|
2065
|
+
'chronologicalEventForm',
|
|
2066
|
+
'boolean',
|
|
2067
|
+
false,
|
|
2068
|
+
'TOGGLE',
|
|
2069
|
+
),
|
|
2070
|
+
// Reminder presets
|
|
2071
|
+
'chronologicalEventForm.reminderPresets.timed': def<number[]>(
|
|
2072
|
+
'chronologicalEventForm',
|
|
2073
|
+
'json',
|
|
2074
|
+
[5, 15, 30, 60, 120, 1440],
|
|
2075
|
+
'CUSTOM_REMINDER_PRESETS',
|
|
2076
|
+
),
|
|
2077
|
+
'chronologicalEventForm.reminderPresets.allDay': def<AllDayPreset[]>(
|
|
2078
|
+
'chronologicalEventForm',
|
|
2079
|
+
'json',
|
|
2080
|
+
[
|
|
2081
|
+
{ daysBefore: 0, time: '09:00' },
|
|
2082
|
+
{ daysBefore: 1, time: '18:00' },
|
|
2083
|
+
{ daysBefore: 2, time: '09:00' },
|
|
2084
|
+
],
|
|
2085
|
+
'CUSTOM_REMINDER_PRESETS',
|
|
2086
|
+
),
|
|
2087
|
+
} as const satisfies Record<string, SettingDef>;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// ---------------------------------------------------------------------------
|
|
2091
|
+
// Derived types (based on entry shape — all configs produce the same keys)
|
|
2092
|
+
// ---------------------------------------------------------------------------
|
|
2093
|
+
|
|
2094
|
+
type SettingsEntries = ReturnType<typeof buildEntries>;
|
|
2095
|
+
|
|
2096
|
+
/** Union type of every valid setting key */
|
|
2097
|
+
export type SettingKey = keyof SettingsEntries;
|
|
2098
|
+
|
|
2099
|
+
/** Type-safe value type for a given setting key */
|
|
2100
|
+
export type SettingValue<K extends SettingKey> = SettingsEntries[K]['default'];
|
|
2101
|
+
|
|
2102
|
+
/** Complete settings map — all keys resolved to their value types */
|
|
2103
|
+
export type SettingsMap = {
|
|
2104
|
+
[K in SettingKey]: SettingValue<K>;
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
// ---------------------------------------------------------------------------
|
|
2108
|
+
// Registry class
|
|
2109
|
+
// ---------------------------------------------------------------------------
|
|
2110
|
+
|
|
2111
|
+
export class SettingsRegistry {
|
|
2112
|
+
/** The raw setting definitions (category, type, default, sync) */
|
|
2113
|
+
readonly entries: SettingsEntries;
|
|
2114
|
+
|
|
2115
|
+
/** All setting keys as an array */
|
|
2116
|
+
readonly keys: SettingKey[];
|
|
2117
|
+
|
|
2118
|
+
constructor(config: Partial<RegistryConfig> = {}) {
|
|
2119
|
+
const full: RegistryConfig = { ...DEFAULT_REGISTRY_CONFIG, ...config };
|
|
2120
|
+
this.entries = buildEntries(full);
|
|
2121
|
+
this.keys = Object.keys(this.entries) as SettingKey[];
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/** Get the default value for a setting */
|
|
2125
|
+
getDefault<K extends SettingKey>(key: K): SettingValue<K> {
|
|
2126
|
+
return this.entries[key].default as SettingValue<K>;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/** Get a complete map of all default values */
|
|
2130
|
+
getAllDefaults(): SettingsMap {
|
|
2131
|
+
const defaults = {} as Record<string, unknown>;
|
|
2132
|
+
for (const key of this.keys) {
|
|
2133
|
+
defaults[key] = this.entries[key].default;
|
|
2134
|
+
}
|
|
2135
|
+
return defaults as SettingsMap;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/** Get the data type for a setting key */
|
|
2139
|
+
getDataType(key: SettingKey): SettingsDataType {
|
|
2140
|
+
return this.entries[key].type;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/** Check whether a setting should be synced */
|
|
2144
|
+
isSynced(key: SettingKey): boolean {
|
|
2145
|
+
return this.entries[key].sync;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/** Get all setting keys for a category */
|
|
2149
|
+
getByCategory(category: SettingsCategory): SettingKey[] {
|
|
2150
|
+
return this.keys.filter((key) => this.entries[key].category === category);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/** Serialize a setting value to a string for DB storage */
|
|
2154
|
+
serialize(key: SettingKey, value: unknown): string {
|
|
2155
|
+
const dataType = this.getDataType(key);
|
|
2156
|
+
switch (dataType) {
|
|
2157
|
+
case 'string':
|
|
2158
|
+
return String(value ?? '');
|
|
2159
|
+
case 'number':
|
|
2160
|
+
return String(value ?? 0);
|
|
2161
|
+
case 'boolean':
|
|
2162
|
+
if (typeof value === 'string') {
|
|
2163
|
+
return value === 'true' ? 'true' : 'false';
|
|
2164
|
+
}
|
|
2165
|
+
return value ? 'true' : 'false';
|
|
2166
|
+
case 'json':
|
|
2167
|
+
// If already a serialized JSON string, return as-is to avoid
|
|
2168
|
+
// double-wrapping (e.g. JSON.stringify("[]") → "\"[]\"")
|
|
2169
|
+
if (typeof value === 'string') {
|
|
2170
|
+
try {
|
|
2171
|
+
JSON.parse(value);
|
|
2172
|
+
return value;
|
|
2173
|
+
} catch {
|
|
2174
|
+
return JSON.stringify(value);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
return JSON.stringify(value);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/** Deserialize a DB string back to a typed setting value */
|
|
2182
|
+
deserialize<K extends SettingKey>(
|
|
2183
|
+
key: K,
|
|
2184
|
+
raw: string | null,
|
|
2185
|
+
): SettingValue<K> {
|
|
2186
|
+
if (raw === null || raw === undefined) {
|
|
2187
|
+
return this.getDefault(key);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
const dataType = this.getDataType(key);
|
|
2191
|
+
switch (dataType) {
|
|
2192
|
+
case 'string':
|
|
2193
|
+
return raw as SettingValue<K>;
|
|
2194
|
+
case 'number':
|
|
2195
|
+
return Number(raw) as SettingValue<K>;
|
|
2196
|
+
case 'boolean':
|
|
2197
|
+
return (raw === 'true') as SettingValue<K>;
|
|
2198
|
+
case 'json':
|
|
2199
|
+
try {
|
|
2200
|
+
let result: unknown = JSON.parse(raw);
|
|
2201
|
+
// Unwrap multiply-escaped JSON strings caused by repeated
|
|
2202
|
+
// double-serialization (each push/pull cycle added a layer)
|
|
2203
|
+
while (typeof result === 'string') {
|
|
2204
|
+
try {
|
|
2205
|
+
result = JSON.parse(result);
|
|
2206
|
+
} catch {
|
|
2207
|
+
break;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return result as SettingValue<K>;
|
|
2211
|
+
} catch {
|
|
2212
|
+
return this.getDefault(key);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* Get the category for a setting key.
|
|
2219
|
+
* Returns undefined if the key is not recognized.
|
|
2220
|
+
*/
|
|
2221
|
+
getCategory(key: string): SettingsCategory | undefined {
|
|
2222
|
+
if (key in this.entries) {
|
|
2223
|
+
return this.entries[key as SettingKey].category;
|
|
2224
|
+
}
|
|
2225
|
+
// Fallback: extract category from dot-prefix
|
|
2226
|
+
const dotIndex = key.indexOf('.');
|
|
2227
|
+
if (dotIndex > 0) {
|
|
2228
|
+
const prefix = key.substring(0, dotIndex);
|
|
2229
|
+
if (SETTINGS_CATEGORIES.includes(prefix as SettingsCategory)) {
|
|
2230
|
+
return prefix as SettingsCategory;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return undefined;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* Get a human-readable label for a setting key.
|
|
2238
|
+
* Strips the category prefix and formats the remaining path.
|
|
2239
|
+
* Example: 'lockScreen.inactivityTimeoutMinutes' → 'Inactivity Timeout Minutes'
|
|
2240
|
+
*/
|
|
2241
|
+
getSettingLabel(key: string): string {
|
|
2242
|
+
const dotIndex = key.indexOf('.');
|
|
2243
|
+
const name = dotIndex > 0 ? key.substring(dotIndex + 1) : key;
|
|
2244
|
+
// Convert camelCase / dot-separated path to Title Case words
|
|
2245
|
+
return name
|
|
2246
|
+
.replace(/\./g, ' › ')
|
|
2247
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
2248
|
+
.replace(/^./, (c) => c.toUpperCase());
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// ---------------------------------------------------------------------------
|
|
2253
|
+
// Snapshot parsing — parse a JSON settings snapshot into categorized groups
|
|
2254
|
+
// ---------------------------------------------------------------------------
|
|
2255
|
+
|
|
2256
|
+
export type ParsedSettingEntry = {
|
|
2257
|
+
/** The full setting key (e.g. 'lockScreen.inactivityTimeoutMinutes') */
|
|
2258
|
+
key: string;
|
|
2259
|
+
/** The setting name without category prefix (e.g. 'inactivityTimeoutMinutes') */
|
|
2260
|
+
name: string;
|
|
2261
|
+
/** Human-readable label (e.g. 'Inactivity Timeout Minutes') */
|
|
2262
|
+
label: string;
|
|
2263
|
+
/** i18n message key for the label (from SETTINGS_LABELS) */
|
|
2264
|
+
labelKey?: string;
|
|
2265
|
+
/** i18n message key for a description (from SETTINGS_LABELS) */
|
|
2266
|
+
descriptionKey?: string;
|
|
2267
|
+
/** The setting value */
|
|
2268
|
+
value: unknown;
|
|
2269
|
+
/** UI component type for rendering this setting */
|
|
2270
|
+
uiType: SettingUiType;
|
|
2271
|
+
/** Available options for SELECT-type settings */
|
|
2272
|
+
options?: readonly SettingOption[];
|
|
2273
|
+
/** Slider configuration for SLIDER-type settings */
|
|
2274
|
+
sliderConfig?: SliderConfig;
|
|
2275
|
+
/** Only show this setting for enrolled/kiosk devices (undefined = always) */
|
|
2276
|
+
appMode?: 'ENROLLED';
|
|
2277
|
+
/** Only show this setting for a specific calendar type (undefined = always) */
|
|
2278
|
+
calendarType?: CalendarType;
|
|
2279
|
+
/** Show this setting only when condition(s) are met */
|
|
2280
|
+
visibleWhen?: VisibilityRule;
|
|
2281
|
+
/** Disable (but still show) this setting when condition(s) are met */
|
|
2282
|
+
disabledWhen?: DisabledRule;
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
export type ParsedSettingsGroup = {
|
|
2286
|
+
/** The category key */
|
|
2287
|
+
category: SettingsCategory;
|
|
2288
|
+
/** Human-readable category label */
|
|
2289
|
+
label: string;
|
|
2290
|
+
/** Settings in this category */
|
|
2291
|
+
settings: ParsedSettingEntry[];
|
|
2292
|
+
};
|
|
2293
|
+
|
|
2294
|
+
/**
|
|
2295
|
+
* Parse a settings JSON snapshot into categorized groups, optionally filtered
|
|
2296
|
+
* by calendar type. This is the single function both web and mobile should use
|
|
2297
|
+
* to display a settings snapshot.
|
|
2298
|
+
*
|
|
2299
|
+
* @param json The raw JSON string from the settings snapshot
|
|
2300
|
+
* @param calendarType Optional calendar type filter — hides irrelevant categories
|
|
2301
|
+
* @param registry Optional registry instance (uses defaultRegistry if omitted)
|
|
2302
|
+
*/
|
|
2303
|
+
export function parseSettingsSnapshot(
|
|
2304
|
+
json: string | Record<string, unknown>,
|
|
2305
|
+
calendarType?: CalendarType,
|
|
2306
|
+
registry: SettingsRegistry = defaultRegistry,
|
|
2307
|
+
): ParsedSettingsGroup[] {
|
|
2308
|
+
let parsed: Record<string, unknown>;
|
|
2309
|
+
if (typeof json === 'string') {
|
|
2310
|
+
try {
|
|
2311
|
+
parsed = JSON.parse(json);
|
|
2312
|
+
} catch {
|
|
2313
|
+
return [];
|
|
2314
|
+
}
|
|
2315
|
+
} else {
|
|
2316
|
+
parsed = json;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
const groups = new Map<SettingsCategory, ParsedSettingEntry[]>();
|
|
2320
|
+
|
|
2321
|
+
for (const [key, rawValue] of Object.entries(parsed)) {
|
|
2322
|
+
const category = registry.getCategory(key);
|
|
2323
|
+
if (!category) {
|
|
2324
|
+
continue;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// Deserialize string values to their native types using the registry
|
|
2328
|
+
let value: unknown = rawValue;
|
|
2329
|
+
if (typeof rawValue === 'string' && key in registry.entries) {
|
|
2330
|
+
value = registry.deserialize(key as SettingKey, rawValue);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
const dotIndex = key.indexOf('.');
|
|
2334
|
+
const name = dotIndex > 0 ? key.substring(dotIndex + 1) : key;
|
|
2335
|
+
|
|
2336
|
+
if (!groups.has(category)) {
|
|
2337
|
+
groups.set(category, []);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
const labelDef = SETTINGS_LABELS[key];
|
|
2341
|
+
const entryDef =
|
|
2342
|
+
key in registry.entries ? registry.entries[key as SettingKey] : undefined;
|
|
2343
|
+
groups.get(category)!.push({
|
|
2344
|
+
key,
|
|
2345
|
+
name,
|
|
2346
|
+
label: registry.getSettingLabel(key),
|
|
2347
|
+
labelKey: labelDef?.labelKey,
|
|
2348
|
+
descriptionKey: labelDef?.descriptionKey,
|
|
2349
|
+
value,
|
|
2350
|
+
uiType: entryDef?.uiType ?? 'TEXT_INPUT',
|
|
2351
|
+
...(entryDef?.options && {
|
|
2352
|
+
options: entryDef.options as readonly SettingOption[],
|
|
2353
|
+
}),
|
|
2354
|
+
...(entryDef?.sliderConfig && { sliderConfig: entryDef.sliderConfig }),
|
|
2355
|
+
...(entryDef?.appMode && { appMode: entryDef.appMode }),
|
|
2356
|
+
...(entryDef?.calendarType && { calendarType: entryDef.calendarType }),
|
|
2357
|
+
...(entryDef?.visibleWhen && { visibleWhen: entryDef.visibleWhen }),
|
|
2358
|
+
...(entryDef?.disabledWhen && { disabledWhen: entryDef.disabledWhen }),
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
// Determine which categories to show
|
|
2363
|
+
const allowedCategories = calendarType
|
|
2364
|
+
? getCategoriesForCalendarType(calendarType)
|
|
2365
|
+
: [...SETTINGS_CATEGORIES];
|
|
2366
|
+
|
|
2367
|
+
return allowedCategories
|
|
2368
|
+
.filter((cat) => groups.has(cat))
|
|
2369
|
+
.map((category) => ({
|
|
2370
|
+
category,
|
|
2371
|
+
label: CATEGORY_LABELS[category],
|
|
2372
|
+
settings: groups.get(category)!,
|
|
2373
|
+
}));
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
/**
|
|
2377
|
+
* Format a setting value for display.
|
|
2378
|
+
* Handles booleans, numbers, strings, arrays, objects, and null/undefined.
|
|
2379
|
+
*/
|
|
2380
|
+
export function formatSettingValue(value: unknown): string {
|
|
2381
|
+
if (value === null || value === undefined) {
|
|
2382
|
+
return '—';
|
|
2383
|
+
}
|
|
2384
|
+
if (typeof value === 'boolean') {
|
|
2385
|
+
return value ? 'true' : 'false';
|
|
2386
|
+
}
|
|
2387
|
+
if (typeof value === 'number') {
|
|
2388
|
+
return String(value);
|
|
2389
|
+
}
|
|
2390
|
+
if (typeof value === 'string') {
|
|
2391
|
+
return value || '""';
|
|
2392
|
+
}
|
|
2393
|
+
if (Array.isArray(value)) {
|
|
2394
|
+
return `[${value.length} items]`;
|
|
2395
|
+
}
|
|
2396
|
+
if (typeof value === 'object') {
|
|
2397
|
+
return JSON.stringify(value);
|
|
2398
|
+
}
|
|
2399
|
+
return String(value);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
/**
|
|
2403
|
+
* Serialize a settings object (with mixed native types) into a
|
|
2404
|
+
* string-values-only snapshot suitable for pushing to a mobile device.
|
|
2405
|
+
*
|
|
2406
|
+
* Uses the registry's `serialize()` method for known keys so that
|
|
2407
|
+
* booleans become "true"/"false", numbers become digit strings, etc.
|
|
2408
|
+
* Unknown keys are converted with `String(value)`.
|
|
2409
|
+
*/
|
|
2410
|
+
export function serializeSettingsSnapshot(
|
|
2411
|
+
settings: Record<string, unknown>,
|
|
2412
|
+
registry: SettingsRegistry,
|
|
2413
|
+
): Record<string, string> {
|
|
2414
|
+
const result: Record<string, string> = {};
|
|
2415
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
2416
|
+
if (key in registry.entries) {
|
|
2417
|
+
result[key] = registry.serialize(key as SettingKey, value);
|
|
2418
|
+
} else {
|
|
2419
|
+
result[key] = String(value ?? '');
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return result;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* Deserialize a settings snapshot (string values from the server/DB) into
|
|
2427
|
+
* native-typed values using the registry.
|
|
2428
|
+
*
|
|
2429
|
+
* This ensures local state always contains native types (boolean, number, etc.)
|
|
2430
|
+
* so that subsequent `serializeSettingsSnapshot` calls produce correct results.
|
|
2431
|
+
*/
|
|
2432
|
+
export function deserializeSettingsSnapshot(
|
|
2433
|
+
snapshot: Record<string, unknown>,
|
|
2434
|
+
registry: SettingsRegistry = defaultRegistry,
|
|
2435
|
+
): Record<string, unknown> {
|
|
2436
|
+
const result: Record<string, unknown> = {};
|
|
2437
|
+
for (const [key, value] of Object.entries(snapshot)) {
|
|
2438
|
+
if (typeof value === 'string' && key in registry.entries) {
|
|
2439
|
+
result[key] = registry.deserialize(key as SettingKey, value);
|
|
2440
|
+
} else {
|
|
2441
|
+
result[key] = value;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
return result;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// ---------------------------------------------------------------------------
|
|
2448
|
+
// Default registry instance (non-enrolled, system theme, nb locale)
|
|
2449
|
+
// ---------------------------------------------------------------------------
|
|
2450
|
+
|
|
2451
|
+
export const defaultRegistry = new SettingsRegistry();
|
|
2452
|
+
|
|
2453
|
+
// ---------------------------------------------------------------------------
|
|
2454
|
+
// Factory function — returns raw entry map for local type derivation.
|
|
2455
|
+
// Consumers that need precise generic inference (e.g. useQuery<K>) can use
|
|
2456
|
+
// this to anchor types on a local const variable.
|
|
2457
|
+
// ---------------------------------------------------------------------------
|
|
2458
|
+
|
|
2459
|
+
export function createSettingsRegistry(config: Partial<RegistryConfig> = {}) {
|
|
2460
|
+
return buildEntries({ ...DEFAULT_REGISTRY_CONFIG, ...config });
|
|
2461
|
+
}
|