|
1 | | -import { getContext, setContext } from 'svelte'; |
2 | | -import { useProjectContext } from '$project/project-context.svelte'; |
| 1 | +import {getContext, setContext} from 'svelte'; |
| 2 | + |
| 3 | +import type {IPreferencesService} from '$lib/dotnet-types/generated-types/FwLiteShared/Services'; |
| 4 | +import {tryUsePreferencesService} from '$lib/services/service-provider'; |
| 5 | +import {useProjectContext} from '$project/project-context.svelte'; |
3 | 6 |
|
4 | 7 | /** |
5 | 8 | * Project-specific storage service |
6 | 9 | * |
7 | 10 | * This service provides project-scoped storage for user preferences. |
8 | | - * Currently uses localStorage with project-prefixed keys. |
9 | | - * |
10 | | - * TODO: Enhance to use MAUI Preferences when running in MAUI app |
11 | | - * (detect platform via useFwLiteConfig().os and use a preferences service) |
| 11 | + * When running in MAUI, uses MAUI Preferences via the PreferencesService. |
| 12 | + * Otherwise, falls back to localStorage. |
12 | 13 | */ |
13 | 14 |
|
14 | 15 | const projectStorageContextKey = 'project-storage'; |
15 | 16 |
|
16 | 17 | /** |
17 | | - * Reactive storage property that automatically syncs to localStorage |
| 18 | + * localStorage-based storage backend implementing IPreferencesService |
| 19 | + */ |
| 20 | +class LocalStorageBackend implements IPreferencesService { |
| 21 | + get(key: string): Promise<string | null> { |
| 22 | + return Promise.resolve(localStorage.getItem(key)); |
| 23 | + } |
| 24 | + |
| 25 | + set(key: string, value: string): Promise<void> { |
| 26 | + localStorage.setItem(key, value); |
| 27 | + return Promise.resolve(); |
| 28 | + } |
| 29 | + |
| 30 | + remove(key: string): Promise<void> { |
| 31 | + localStorage.removeItem(key); |
| 32 | + return Promise.resolve(); |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +/** |
| 37 | + * Returns the preferences service if available (MAUI), otherwise localStorage fallback |
| 38 | + */ |
| 39 | +function getPreferencesService(): IPreferencesService { |
| 40 | + return tryUsePreferencesService() ?? new LocalStorageBackend(); |
| 41 | +} |
| 42 | + |
| 43 | +/** |
| 44 | + * Reactive storage property with async persistence. |
| 45 | + * |
| 46 | + * - `current` getter is reactive (Svelte 5 runes) and read-only |
| 47 | + * - `set()` is async - callers can await or fire-and-forget |
| 48 | + * - Initial value loads asynchronously; early subscribers get updates when ready |
18 | 49 | */ |
19 | 50 | class StorageProp { |
20 | 51 | #projectCode: string; |
21 | 52 | #key: string; |
| 53 | + #backend: IPreferencesService; |
22 | 54 | #value = $state<string>(''); |
| 55 | + #hasBeenSet = $state(false); |
23 | 56 |
|
24 | | - constructor(projectCode: string, key: string) { |
| 57 | + constructor(projectCode: string, key: string, backend: IPreferencesService) { |
25 | 58 | this.#projectCode = projectCode; |
26 | 59 | this.#key = key; |
27 | | - // Load initial value |
28 | | - this.#value = this.load(); |
| 60 | + this.#backend = backend; |
| 61 | + void this.load(); |
29 | 62 | } |
30 | 63 |
|
31 | 64 | get current(): string { |
32 | 65 | return this.#value; |
33 | 66 | } |
34 | 67 |
|
35 | | - set current(value: string) { |
| 68 | + get loading(): boolean { |
| 69 | + return !this.#hasBeenSet; |
| 70 | + } |
| 71 | + |
| 72 | + async set(value: string): Promise<void> { |
| 73 | + this.#hasBeenSet = true; |
36 | 74 | this.#value = value; |
37 | | - this.persist(value); |
| 75 | + const storageKey = this.getStorageKey(); |
| 76 | + if (value) { |
| 77 | + await this.#backend.set(storageKey, value); |
| 78 | + } else { |
| 79 | + await this.#backend.remove(storageKey); |
| 80 | + } |
38 | 81 | } |
39 | 82 |
|
40 | 83 | private getStorageKey(): string { |
41 | 84 | return `project:${this.#projectCode}:${this.#key}`; |
42 | 85 | } |
43 | 86 |
|
44 | | - private load(): string { |
45 | | - return localStorage.getItem(this.getStorageKey()) ?? ''; |
46 | | - } |
47 | | - |
48 | | - private persist(value: string): void { |
49 | | - const storageKey = this.getStorageKey(); |
50 | | - if (value) { |
51 | | - localStorage.setItem(storageKey, value); |
52 | | - } else { |
53 | | - localStorage.removeItem(storageKey); |
| 87 | + private async load(): Promise<void> { |
| 88 | + const value = await this.#backend.get(this.getStorageKey()); |
| 89 | + if (!this.#hasBeenSet) { |
| 90 | + this.#value = value ?? ''; |
| 91 | + this.#hasBeenSet = true; |
54 | 92 | } |
55 | 93 | } |
56 | 94 | } |
57 | 95 |
|
58 | 96 | export class ProjectStorage { |
59 | 97 | readonly selectedTaskId: StorageProp; |
60 | 98 |
|
61 | | - constructor(projectCode: string) { |
62 | | - this.selectedTaskId = new StorageProp(projectCode, 'selectedTaskId'); |
| 99 | + constructor(projectCode: string, backend: IPreferencesService) { |
| 100 | + this.selectedTaskId = new StorageProp(projectCode, 'selectedTaskId', backend); |
63 | 101 | } |
64 | 102 | } |
65 | 103 |
|
66 | 104 | export function useProjectStorage(): ProjectStorage { |
67 | 105 | let storage = getContext<ProjectStorage>(projectStorageContextKey); |
68 | 106 | if (!storage) { |
69 | 107 | const projectContext = useProjectContext(); |
70 | | - storage = new ProjectStorage(projectContext.projectCode); |
| 108 | + const backend = getPreferencesService(); |
| 109 | + storage = new ProjectStorage(projectContext.projectCode, backend); |
71 | 110 | setContext(projectStorageContextKey, storage); |
72 | 111 | } |
73 | 112 | return storage; |
|
0 commit comments