@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
209 lines (167 loc) • 5.7 kB
text/typescript
import { BehaviorSubject } from "rxjs";
import { SingleValueProvider } from "./StorageSingleSelectProvider";
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: "ScreenSingleValueProvider",
subsystem: "zapp-react-native-bridge",
parent: bridgeLogger,
});
export class ScreenSingleValueProvider implements SingleValueProvider {
// @ts-ignore
private valueSubject: BehaviorSubject<string | null>;
private screenStateStoreUnsubscribe: (() => void) | null = null;
private static providers: Record<
string,
Record<string, SingleValueProvider>
> = {};
public static getProvider(
key: string,
screenRoute: string,
screenStateStore: ReturnType<typeof useScreenStateStore>
): SingleValueProvider {
if (!key) {
throw new Error("ScreenSingleValueProvider: Key is required");
}
if (!screenRoute) {
throw new Error("ScreenSingleValueProvider: Screen route is required");
}
if (!screenStateStore) {
throw new Error(
"ScreenSingleValueProvider: screen state store is required"
);
}
if (!this.providers[screenRoute]) {
this.providers[screenRoute] = {};
}
try {
if (!this.providers[screenRoute][key]) {
this.providers[screenRoute][key] = new ScreenSingleValueProvider(
key,
screenRoute,
screenStateStore
);
} else {
(
this.providers[screenRoute][key] as ScreenSingleValueProvider
).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("ScreenSingleValueProvider: Key is required");
}
if (!screenStateStore) {
throw new Error(
"ScreenSingleValueProvider: screen state store is required"
);
}
this.key = key;
this.valueSubject = new BehaviorSubject<string | null>(null);
this.setupScreenStateSubscription();
void this.getValueAsync();
log_debug("ScreenSingleValueProvider: Initializing", { key, route });
}
private updateStore(
screenStateStore: ReturnType<typeof useScreenStateStore>
): void {
this.cleanup();
this.screenStateStore = screenStateStore;
this.setupScreenStateSubscription();
void this.getValueAsync();
}
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(
// `ScreenSingleValueProvider: Key deleted from store: ${this.key}`
// );
// // TODO: If we need to handle deletion, we can do it here
// }
// if (current.value !== previous.value) {
// this.valueSubject.next(current.value || null);
// log_debug(`ScreenSingleValueProvider: Key updated: ${this.key}`, {
// previous: previous.value,
// current: current.value,
// });
// }
// }
);
log_debug(
`ScreenSingleValueProvider: screen state store subscription setup for key: ${this.key}`
);
}
private cleanup(): void {
if (this.screenStateStoreUnsubscribe) {
this.screenStateStoreUnsubscribe();
this.screenStateStoreUnsubscribe = null;
log_debug(
`ScreenSingleValueProvider: Unsubscribed from screen state store for key: ${this.key}`
);
}
}
clearValue(): Promise<void> {
this.screenStateStore.getState().removeValue(this.key);
this.valueSubject.next(null);
log_debug(`clearValue: value cleared for key: ${this.key}`);
return Promise.resolve();
}
getObservable(): BehaviorSubject<string | null> {
return this.valueSubject;
}
getValue(): string | null {
return this.valueSubject.value;
}
getValueAsync(): Promise<string | null> {
const currentValue = this.screenStateStore.getState().data[this.key];
const value = currentValue || null;
const selected = this.getValue();
const valuesAreEqual = value === selected;
const bothEmpty =
(value === null || value === "") &&
(selected === null || selected === "");
if (!this.valueSubject.closed && !valuesAreEqual && !bothEmpty) {
this.valueSubject.next(value);
}
return Promise.resolve(value);
}
setValue(value: string): Promise<void> {
this.screenStateStore.getState().setValue(this.key, value);
this.valueSubject.next(value);
log_debug(`setValue: value set for key: ${this.key}`, { value });
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 ScreenSingleValueProvider).destroy();
});
delete this.providers[screenRoute];
}
}
// TODO: remove if not needed
public destroy(): void {
log_debug(
`ScreenSingleValueProvider: Destroying provider for key: ${this.key}`
);
this.valueSubject.complete();
this.cleanup();
}
}