@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
294 lines (228 loc) • 7.99 kB
text/typescript
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();
}
}