UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

294 lines (228 loc) • 7.99 kB
import { MultiSelectProvider } from "./StorageMultiSelectProvider"; import { BehaviorSubject } from "rxjs"; import { createLogger } from "../logger"; import { bridgeLogger } from "../../zapp-react-native-bridge/logger"; import { useScreenStateStore } from "../reactHooks/navigation/useScreenStateStore"; export const { log_debug, log_error } = createLogger({ category: "ScreenMultiSelectProvider", subsystem: "zapp-react-native-bridge", parent: bridgeLogger, }); export class ScreenMultiSelectProvider implements MultiSelectProvider { // @ts-ignore private itemSubject: BehaviorSubject<string[] | null>; private screenStateStoreUnsubscribe: (() => void) | null = null; private static providers: Record< string, Record<string, MultiSelectProvider> > = {}; public static getProvider( key: string, screenRoute: string, screenStateStore: ReturnType<typeof useScreenStateStore> ): MultiSelectProvider { if (!key) { throw new Error("ScreenMultiSelectProvider: Key is required"); } if (!screenRoute) { throw new Error("ScreenMultiSelectProvider: Screen route is required"); } if (!screenStateStore) { throw new Error( "ScreenMultiSelectProvider: screen state store is required" ); } if (!this.providers[screenRoute]) { this.providers[screenRoute] = {}; } try { if (!this.providers[screenRoute][key]) { this.providers[screenRoute][key] = new ScreenMultiSelectProvider( key, screenRoute, screenStateStore ); } else { ( this.providers[screenRoute][key] as ScreenMultiSelectProvider ).updateStore(screenStateStore); } } catch (error) { log_error("Failed to create provider", { key, screenRoute, error }); throw error; } return this.providers[screenRoute][key]; } // @ts-ignore private constructor( private key: string, route: string, private screenStateStore: ReturnType<typeof useScreenStateStore> ) { if (!key) { throw new Error("ScreenMultiSelectProvider: Key is required"); } if (!screenStateStore) { throw new Error( "ScreenMultiSelectProvider: screen state store is required" ); } this.key = key; this.itemSubject = new BehaviorSubject<string[]>([]); this.setupScreenStateSubscription(); void this.getSelectedAsync(); log_debug("ScreenMultiSelectProvider: Initializing", { key, route }); } private updateStore( screenStateStore: ReturnType<typeof useScreenStateStore> ): void { if (screenStateStore === this.screenStateStore) { return; } this.cleanup(); this.screenStateStore = screenStateStore; this.setupScreenStateSubscription(); void this.getSelectedAsync(); } private setupScreenStateSubscription(): void { this.screenStateStoreUnsubscribe = this.screenStateStore.subscribe( (state) => ({ value: state.data[this.key], exists: this.key in state.data, }) // (current, previous) => { // if (!current.exists && previous.exists) { // log_debug( // `ScreenMultiSelectProvider: Key deleted from store: ${this.key}` // ); // // TODO: If we need to handle deletion, we can do it here // } // if (current.value !== previous.value) { // const items = this.parseStoredValue(current.value); // this.itemSubject.next(items); // log_debug(`ScreenMultiSelectProvider: Key updated: ${this.key}`, { // previous: this.parseStoredValue(previous.value), // current: items, // }); // } // } ); log_debug( `ScreenMultiSelectProvider: screen state store subscription setup for key: ${this.key}` ); } private parseStoredValue(value: string): string[] { if (!value) return []; return typeof value === "string" ? value.split(",").filter((item) => item.length > 0) : []; } private formatValueForStorage(items: string[]): string { return items.join(","); } private getCurrentItems(): Set<string> { const currentValue = this.screenStateStore.getState().data[this.key]; const items = this.parseStoredValue(currentValue); return new Set(items); } private updateScreenState(items: Set<string>): void { const itemsArray = Array.from(items); const formattedValue = this.formatValueForStorage(itemsArray); this.screenStateStore.getState().setValue(this.key, formattedValue); const itemsToUpdate = Array.from(items) || []; this.itemSubject.next(itemsToUpdate); } private cleanup(): void { if (this.screenStateStoreUnsubscribe) { this.screenStateStoreUnsubscribe(); this.screenStateStoreUnsubscribe = null; log_debug( `ScreenMultiSelectProvider: Unsubscribed from screen state store for key: ${this.key}` ); } } addItem(item: string): Promise<void> { const items = this.getCurrentItems(); items.add(item); this.updateScreenState(items); log_debug( `addItem: item added: ${item}, current items: [${Array.from(items).join(", ")}]` ); return Promise.resolve(); } addItems(itemsToAdd: string[]): Promise<void> { const items = this.getCurrentItems(); for (const item of itemsToAdd) { items.add(item); } this.updateScreenState(items); log_debug( `addItems: items added: [${itemsToAdd.join(", ")}], current items: [${Array.from(items).join(", ")}]` ); return Promise.resolve(); } public getObservable = (): BehaviorSubject<string[]> => this.itemSubject; getSelectedAsync(): Promise<string[]> { const currentValue = this.screenStateStore.getState().data[this.key] || []; const values = this.parseStoredValue(currentValue); const selected = this.getSelectedItems(); const arraysAreEqual = Array.isArray(values) && Array.isArray(selected) && values.length === selected.length && values.every((val, index) => val === selected[index]); const bothEmpty = values.length === 0 && selected.length === 0; if (!this.itemSubject.closed && !arraysAreEqual && !bothEmpty) { this.itemSubject.next(values); } return Promise.resolve(values); } getSelectedItems(): string[] { return this.itemSubject.value || []; } removeAllItems(): Promise<void> { this.screenStateStore.getState().removeValue(this.key); this.itemSubject.next([]); log_debug(`removeAllItems: all items removed for key: ${this.key}`); return Promise.resolve(); } removeItem(item: string): Promise<void> { const items = this.getCurrentItems(); items.delete(item); this.updateScreenState(items); log_debug( `removeItem: item removed: ${item}, remaining items: ${Array.from(items).join(", ")}` ); return Promise.resolve(); } removeItems(itemsToRemove: string[]): Promise<void> { const items = this.getCurrentItems(); for (const item of itemsToRemove) { items.delete(item); } this.updateScreenState(items); log_debug( `removeItems: items removed: [${itemsToRemove.join(", ")}], remaining items: [${Array.from(items).join(", ")}]` ); return Promise.resolve(); } setSelectedItems(items: string[]): Promise<void> { this.updateScreenState(new Set(items)); log_debug(`setSelectedItems: items set to: [${items.join(", ")}]`); return Promise.resolve(); } // TODO: remove if not needed public static cleanupRoute(screenRoute: string): void { if (this.providers[screenRoute]) { Object.values(this.providers[screenRoute]).forEach((provider) => { (provider as ScreenMultiSelectProvider).destroy(); }); delete this.providers[screenRoute]; } } // TODO: remove if not needed public destroy(): void { this.itemSubject.complete(); this.cleanup(); } }