@applicaster/zapp-react-native-utils 13.0.0-alpha.3013095924 → 13.0.0-alpha.3044917147

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.
@@ -5,9 +5,14 @@ import {
5
5
  actionExecutor as _actionExecutor,
6
6
  ActionResult,
7
7
  } from "./ActionExecutor";
8
- import { batchSaveToLocalStorage } from "../zappFrameworkUtils/localStorageHelper";
9
- import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
8
+ import {
9
+ batchRemoveAllFromNamespaceForStorage,
10
+ batchSave,
11
+ } from "../zappFrameworkUtils/localStorageHelper";
12
+ import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
13
+
10
14
  import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
15
+ import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
11
16
  import { showConfirmationDialog } from "../alertUtils";
12
17
  import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
13
18
  import { createLogger } from "../logger";
@@ -27,7 +32,8 @@ import {
27
32
  usePickFromState,
28
33
  } from "@applicaster/zapp-react-native-redux/hooks";
29
34
  import { TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT } from "./consts";
30
- import { postEvent } from "../reactHooks/useSubscriberFor";
35
+ import { postEvent, useSubscriberFor } from "../reactHooks/useSubscriberFor";
36
+ import { APP_EVENTS } from "../appUtils/events";
31
37
 
32
38
  export const { log_error, log_info, log_debug } = createLogger({
33
39
  subsystem: "ActionExecutorContext",
@@ -92,7 +98,15 @@ async function onMaxTagsReached(data: MaxTagsReachedEvent) {
92
98
  const prepareDefaultActions = (actionExecutor) => {
93
99
  actionExecutor.registerAction("localStorageSet", async (action) => {
94
100
  const namespaces = action.options.content;
95
- await batchSaveToLocalStorage(namespaces);
101
+ await batchSave(namespaces, localStorage);
102
+ // TODO: Add support for ownershipKey and ownershipNamespace
103
+
104
+ return ActionResult.Success;
105
+ });
106
+
107
+ actionExecutor.registerAction("sessionStorageSet", async (action) => {
108
+ const namespaces = action.options.content;
109
+ await batchSave(namespaces, sessionStorage);
96
110
  // TODO: Add support for ownershipKey and ownershipNamespace
97
111
 
98
112
  return ActionResult.Success;
@@ -149,15 +163,13 @@ const prepareDefaultActions = (actionExecutor) => {
149
163
  actionExecutor.registerAction("confirmDialog", async (action) => {
150
164
  log_info("handleAction: confirmDialog event");
151
165
 
152
- const confirmationPromise = new Promise<ActionResult>((resolve) => {
166
+ return new Promise<ActionResult>((resolve) => {
153
167
  showConfirmationDialog({
154
168
  ...action.options,
155
169
  confirmCompletion: () => resolve(ActionResult.Success),
156
170
  cancelCompletion: () => resolve(ActionResult.Cancel),
157
171
  });
158
172
  });
159
-
160
- return confirmationPromise;
161
173
  });
162
174
 
163
175
  actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
@@ -303,9 +315,24 @@ export function withActionExecutor(Component) {
303
315
  };
304
316
  }, []);
305
317
 
318
+ useSubscriberFor(APP_EVENTS.onLogout, () => {
319
+ log_debug(
320
+ "User profile: onLogout event received, clearing user profile data"
321
+ );
322
+
323
+ const userAccountKey = "user_account";
324
+ void batchRemoveAllFromNamespaceForStorage(userAccountKey, localStorage);
325
+
326
+ void batchRemoveAllFromNamespaceForStorage(
327
+ userAccountKey,
328
+ sessionStorage
329
+ );
330
+ });
331
+
306
332
  useEffect(() => {
307
333
  return _actionExecutor.registerAction(
308
334
  "navigateToScreen",
335
+
309
336
  async (action: ActionType, context?: Record<string, any>) => {
310
337
  const screenType = action.options?.typeMapping;
311
338
 
@@ -47,4 +47,22 @@ export const BUTTON_ACCESSIBILITY_KEYS = {
47
47
  label: "accessibility_fullscreen_label",
48
48
  hint: "accessibility_fullscreen_hint",
49
49
  },
50
+ // EPG-specific buttons
51
+ now: {
52
+ label: "accessibility_now_label",
53
+ hint: "accessibility_now_hint",
54
+ },
55
+ day: {
56
+ label: "accessibility_day_label",
57
+ hint: "accessibility_day_hint",
58
+ },
59
+ program: {
60
+ label: "accessibility_program_label",
61
+ hint: "accessibility_program_hint",
62
+ },
63
+ // Menu-specific buttons
64
+ menu_item: {
65
+ label: "accessibility_menu_item_label",
66
+ hint: "accessibility_menu_item_hint",
67
+ },
50
68
  } as const;
@@ -8,6 +8,7 @@ const {
8
8
 
9
9
  const { tvActionButtonsContainer } = require("./tvAction/container");
10
10
  const { tvActionButton } = require("./tvAction/button");
11
+ const { compact } = require("./_internals");
11
12
 
12
13
  const { spacingKey, absolutePositionElement } = require("./containers");
13
14
 
@@ -46,4 +47,5 @@ module.exports = {
46
47
  tvProgressBar,
47
48
  secondaryImage,
48
49
  getUpdatedSecondaryImageKeys,
50
+ compact,
49
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.0-alpha.3013095924",
3
+ "version": "13.0.0-alpha.3044917147",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "13.0.0-alpha.3013095924",
30
+ "@applicaster/applicaster-types": "13.0.0-alpha.3044917147",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -1,5 +1,9 @@
1
1
  import * as R from "ramda";
2
2
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
3
+ import { isString } from "@applicaster/zapp-react-native-utils/typeGuards";
4
+ import { isFilledArray } from "@applicaster/zapp-react-native-utils/arrayUtils";
5
+ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
6
+
3
7
  import { getBoolFromConfigValue } from "../configurationUtils";
4
8
 
5
9
  /**
@@ -72,3 +76,22 @@ function isLiveByManager(): boolean {
72
76
  export function isLive(entry: ZappEntry): boolean {
73
77
  return isEntryLive(entry) || isLiveByManager();
74
78
  }
79
+
80
+ export const isAudioItem = (item: Option<ZappEntry>) => {
81
+ if (
82
+ isString(item?.content?.type) &&
83
+ item?.content?.type?.includes?.("audio")
84
+ ) {
85
+ return true;
86
+ }
87
+
88
+ if (isString(item?.type?.value) && item?.type?.value?.includes?.("audio")) {
89
+ return true;
90
+ }
91
+
92
+ return false;
93
+ };
94
+
95
+ export const isInlineTV = (screenData) => {
96
+ return isTV() && isFilledArray(screenData?.ui_components);
97
+ };
@@ -14,10 +14,10 @@ export function useAppState(returnIsActive) {
14
14
  };
15
15
 
16
16
  React.useEffect(() => {
17
- AppState.addEventListener("change", setAppState);
17
+ const subscription = AppState.addEventListener("change", setAppState);
18
18
 
19
19
  return () => {
20
- AppState.removeEventListener("change", setAppState);
20
+ subscription.remove();
21
21
  };
22
22
  }, []);
23
23
 
@@ -3,7 +3,7 @@ import { platformSelect } from "../../reactUtils";
3
3
  const TAB_BAR_HEIGHT_IOS = 49;
4
4
  const TAB_BAR_HEIGHT_ANDROID = 56;
5
5
 
6
- export const useGetTabBarHeight = () =>
6
+ export const getTabBarHeight = () =>
7
7
  platformSelect({
8
8
  ios: TAB_BAR_HEIGHT_IOS,
9
9
  android: TAB_BAR_HEIGHT_ANDROID,
@@ -1,12 +1,19 @@
1
1
  import { useGetNavBarTopBorderWidth } from "./useGetNavBarTopBorderWidth";
2
- import { useGetTabBarHeight } from "./useGetTabBarHeight";
2
+ import { getTabBarHeight } from "./getTabBarHeight";
3
+ import { useNavigation } from "./useNavigation";
3
4
  import { MenuTypes, useNavigationType } from "./useNavigationType";
5
+ import { useNavigationPluginData } from "./useNavigationPluginData";
4
6
 
5
7
  export const useGetBottomTabBarHeight = (): number => {
8
+ const { activeRiver } = useNavigation();
9
+
10
+ const navigationPluginData = useNavigationPluginData(activeRiver);
11
+ const navigationType = useNavigationType(navigationPluginData);
12
+
6
13
  const topBorderWidth = useGetNavBarTopBorderWidth();
7
- const tabBarHeight = useGetTabBarHeight();
14
+ const tabBarHeight = getTabBarHeight();
8
15
 
9
- const isBottomBarNavigation = useNavigationType() === MenuTypes.bottomTabBar;
16
+ const isBottomBarNavigation = navigationType === MenuTypes.bottomTabBar;
10
17
 
11
18
  return !isBottomBarNavigation ? 0 : tabBarHeight + topBorderWidth;
12
19
  };
@@ -1,13 +1,17 @@
1
1
  import { useRoute } from "./useRoute";
2
2
 
3
- export const useNavigationPluginData = (): ZappNavigation | undefined => {
3
+ export const useNavigationPluginData = (
4
+ screenData?: LegacyNavigationScreenData | null
5
+ ): ZappNavigation | undefined => {
4
6
  const {
5
7
  screenData: useRouteScreenData,
6
8
  }: { screenData: QuickBrickNavigationData | null } = useRoute();
7
9
 
8
- const navigations = useRouteScreenData?.targetScreen
9
- ? (useRouteScreenData.targetScreen as ZappRiver).navigations
10
- : (useRouteScreenData as ZappRiver).navigations;
10
+ const activeScreenData = screenData ?? useRouteScreenData;
11
+
12
+ const navigations = activeScreenData?.targetScreen
13
+ ? (activeScreenData.targetScreen as ZappRiver).navigations
14
+ : (activeScreenData as ZappRiver).navigations;
11
15
 
12
16
  const navigationMenu = navigations?.find((nav) => nav.category === "menu");
13
17
 
@@ -5,8 +5,10 @@ export enum MenuTypes {
5
5
  bottomTabBar = "BOTTOM_TAB_BAR",
6
6
  }
7
7
 
8
- export const useNavigationType = (): MenuTypes => {
9
- const navigationMenu = useNavigationPluginData();
8
+ export const useNavigationType = (navigation?: ZappNavigation): MenuTypes => {
9
+ const navigationPluginData = useNavigationPluginData();
10
+
11
+ const navigationMenu = navigation ?? navigationPluginData;
10
12
 
11
13
  return !navigationMenu ||
12
14
  navigationMenu.navigation_type === "quick_brick_side_menu"
@@ -0,0 +1,79 @@
1
+ # ZStoreProvider and useZStore
2
+
3
+ This module provides a React context-based solution for managing Zustand stores with named access.
4
+
5
+ ## Usage
6
+
7
+ ### ZStoreProvider
8
+
9
+ The `ZStoreProvider` component creates a Zustand store from the provided value and makes it available to child components.
10
+
11
+ ```tsx
12
+ import { ZStoreProvider } from "@applicaster/zapp-react-native-utils/reactHooks/state";
13
+
14
+ // In your component
15
+ <ZStoreProvider name="playerConfiguration" value={controller?.config}>
16
+ <YourComponent />
17
+ </ZStoreProvider>
18
+ ```
19
+
20
+ ### useZStore
21
+
22
+ The `useZStore` hook allows you to access a Zustand store by name from within a `ZStoreProvider`.
23
+
24
+ ```tsx
25
+ import { useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
26
+ import { useStore } from "zustand";
27
+
28
+ // In your component
29
+ const MyComponent = () => {
30
+ const store = useZStore("playerConfiguration");
31
+ const config = useStore(store, (state) => state.someProperty);
32
+
33
+ return <Text>{config}</Text>;
34
+ };
35
+ ```
36
+
37
+ ## Example
38
+
39
+ ```tsx
40
+ import React from "react";
41
+ import { ZStoreProvider, useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
42
+ import { useStore } from "zustand";
43
+
44
+ // Component that uses the store
45
+ const PlayerConfigDisplay = () => {
46
+ const store = useZStore("playerConfiguration");
47
+ const config = useStore(store, (state) => state);
48
+
49
+ return (
50
+ <View>
51
+ <Text>Player Config: {JSON.stringify(config)}</Text>
52
+ </View>
53
+ );
54
+ };
55
+
56
+ // Main component that provides the store
57
+ const PlayerComponent = ({ controller }) => {
58
+ return (
59
+ <ZStoreProvider name="playerConfiguration" value={controller?.config}>
60
+ <PlayerConfigDisplay />
61
+ </ZStoreProvider>
62
+ );
63
+ };
64
+ ```
65
+
66
+ ## Features
67
+
68
+ - **Named Stores**: Access stores by name instead of importing them directly
69
+ - **Context-based**: Uses React Context for store management
70
+ - **Zustand Integration**: Seamlessly works with existing Zustand stores
71
+ - **Type Safety**: Full TypeScript support
72
+ - **Error Handling**: Clear error messages when stores are not found or used outside providers
73
+
74
+ ## Error Handling
75
+
76
+ The `useZStore` hook will throw errors in the following cases:
77
+
78
+ 1. **Used outside provider**: "useZStore must be used within a ZStoreProvider"
79
+ 2. **Store not found**: "Store with name 'storeName' not found. Make sure it's provided by a ZStoreProvider"
@@ -0,0 +1,71 @@
1
+ import React, { createContext, useContext, ReactNode, useMemo } from "react";
2
+ import { create, UseBoundStore } from "zustand";
3
+
4
+ interface ZStoreContextType {
5
+ stores: Map<string, UseBoundStore<any>>;
6
+ registerStore: (name: string, store: UseBoundStore<any>) => void;
7
+ getStore: (name: string) => UseBoundStore<any> | undefined;
8
+ }
9
+
10
+ const ZStoreContext = createContext<ZStoreContextType | null>(null);
11
+
12
+ interface ZStoreProviderProps {
13
+ children: ReactNode;
14
+ name: string;
15
+ value: any;
16
+ }
17
+
18
+ export const ZStoreProvider: React.FC<ZStoreProviderProps> = ({
19
+ children,
20
+ name,
21
+ value,
22
+ }) => {
23
+ const parentContext = useContext(ZStoreContext);
24
+
25
+ const context = useMemo(() => {
26
+ if (parentContext) {
27
+ // If parent context exists, create a new store and register it
28
+ const store = create(() => value);
29
+ parentContext.registerStore(name, store);
30
+
31
+ return parentContext;
32
+ }
33
+
34
+ // Create a new context if none exists
35
+ const stores = new Map<string, UseBoundStore<any>>();
36
+
37
+ // Create a store from the provided value
38
+ const store = create(() => value);
39
+ stores.set(name, store);
40
+
41
+ return {
42
+ stores,
43
+ registerStore: (storeName: string, storeInstance: UseBoundStore<any>) => {
44
+ stores.set(storeName, storeInstance);
45
+ },
46
+ getStore: (storeName: string) => stores.get(storeName),
47
+ };
48
+ }, [parentContext, name, value]);
49
+
50
+ return (
51
+ <ZStoreContext.Provider value={context}>{children}</ZStoreContext.Provider>
52
+ );
53
+ };
54
+
55
+ export const useZStore = (name: string): UseBoundStore<any> => {
56
+ const context = useContext(ZStoreContext);
57
+
58
+ if (!context) {
59
+ throw new Error("useZStore must be used within a ZStoreProvider");
60
+ }
61
+
62
+ const store = context.getStore(name);
63
+
64
+ if (!store) {
65
+ throw new Error(
66
+ `Store with name "${name}" not found. Make sure it's provided by a ZStoreProvider.`
67
+ );
68
+ }
69
+
70
+ return store;
71
+ };
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react-native";
3
+ import { Text } from "react-native";
4
+ import { ZStoreProvider, useZStore } from "../ZStoreProvider";
5
+ import { useStore } from "zustand";
6
+
7
+ interface TestState {
8
+ value: string;
9
+ }
10
+
11
+ // Test component that uses the store
12
+ const TestComponent = ({ storeName }: { storeName: string }) => {
13
+ const store = useZStore(storeName);
14
+ const value = useStore(store, (state: any) => (state as TestState).value);
15
+
16
+ return <Text testID="test-value">{value}</Text>;
17
+ };
18
+
19
+ // Test component that provides a store
20
+ const TestProvider = ({ children }: { children: React.ReactNode }) => {
21
+ return (
22
+ <ZStoreProvider name="testStore" value={{ value: "test-value" }}>
23
+ {children}
24
+ </ZStoreProvider>
25
+ );
26
+ };
27
+
28
+ describe("ZStoreProvider and useZStore", () => {
29
+ it("should provide a store and allow access via useZStore", () => {
30
+ render(
31
+ <TestProvider>
32
+ <TestComponent storeName="testStore" />
33
+ </TestProvider>
34
+ );
35
+
36
+ expect(screen.getByTestId("test-value").props.children).toBe("test-value");
37
+ });
38
+
39
+ it("should throw error when useZStore is used outside provider", () => {
40
+ // Suppress console.error for this test
41
+ const originalError = console.error;
42
+ console.error = jest.fn();
43
+
44
+ expect(() => {
45
+ render(<TestComponent storeName="testStore" />);
46
+ }).toThrow("useZStore must be used within a ZStoreProvider");
47
+
48
+ console.error = originalError;
49
+ });
50
+
51
+ it("should throw error when store name is not found", () => {
52
+ // Suppress console.error for this test
53
+ const originalError = console.error;
54
+ console.error = jest.fn();
55
+
56
+ expect(() => {
57
+ render(
58
+ <TestProvider>
59
+ <TestComponent storeName="nonExistentStore" />
60
+ </TestProvider>
61
+ );
62
+ }).toThrow('Store with name "nonExistentStore" not found');
63
+
64
+ console.error = originalError;
65
+ });
66
+ });
@@ -1,3 +1,5 @@
1
1
  export { useRivers } from "./useRivers";
2
2
 
3
3
  export { useHomeRiver } from "./useHomeRiver";
4
+
5
+ export { ZStoreProvider, useZStore } from "./ZStoreProvider";
@@ -0,0 +1,3 @@
1
+ export const isString = (value: unknown) => {
2
+ return typeof value === "string";
3
+ };
package/utils/index.ts CHANGED
@@ -2,4 +2,4 @@ export { chunk } from "./chunk";
2
2
 
3
3
  export { times } from "./times";
4
4
 
5
- export { cloneDeep as clone, flatten, drop, size } from "lodash";
5
+ export { cloneDeep as clone, flatten, drop, size, isNil } from "lodash";
@@ -7,6 +7,7 @@ import {
7
7
  StorageValuesToRemove,
8
8
  StorageValuesToAdd,
9
9
  } from "./types";
10
+ import { Storage } from "@applicaster/zapp-react-native-bridge/ZappStorage/Storage";
10
11
 
11
12
  const defaultOwnedKey = "owned_keys";
12
13
  const DEFAULT_NAMESPACE = "applicaster.v2";
@@ -50,8 +51,9 @@ function mapOwnedKeysToAdd(
50
51
  return mappedData;
51
52
  }
52
53
 
53
- export async function batchSaveToLocalStorage(
54
- storageValues: StorageValuesToAdd
54
+ export async function batchSave(
55
+ storageValues: StorageValuesToAdd,
56
+ storage: Storage
55
57
  ) {
56
58
  if (isNilOrEmpty(storageValues)) {
57
59
  return false;
@@ -64,24 +66,37 @@ export async function batchSaveToLocalStorage(
64
66
  const value = namespaceData[key];
65
67
 
66
68
  if (!isNilOrEmpty(value)) {
67
- await localStorage.setItem(key, value, namespace);
69
+ await storage.setItem(key, value, namespace);
68
70
  }
69
71
  }
70
72
  }
71
73
  }
72
74
 
73
- export async function batchRemoveFromLocalStorage(
74
- storageValues: StorageValuesToRemove
75
+ export async function batchSaveToLocalStorage(
76
+ storageValues: StorageValuesToAdd
77
+ ) {
78
+ return await batchSave(storageValues, localStorage);
79
+ }
80
+
81
+ export async function batchRemoveFromStorage(
82
+ storageValues: StorageValuesToRemove,
83
+ storage: Storage = localStorage
75
84
  ) {
76
85
  for (const namespace of Object.keys(storageValues)) {
77
86
  const namespaceData = storageValues[namespace];
78
87
 
79
88
  for (const key of namespaceData) {
80
- await localStorage.removeItem(key, namespace);
89
+ await storage.removeItem(key, namespace);
81
90
  }
82
91
  }
83
92
  }
84
93
 
94
+ export async function batchRemoveFromLocalStorage(
95
+ storageValues: StorageValuesToRemove
96
+ ) {
97
+ await batchRemoveFromStorage(storageValues, localStorage);
98
+ }
99
+
85
100
  async function addOwnedKeys({
86
101
  newKeys,
87
102
  ownershipKey,
@@ -224,13 +239,20 @@ export async function batchRemoveOwnedNamespaceKeys({
224
239
  });
225
240
  }
226
241
 
227
- export async function batchRemoveAllFromNamespace(namespace) {
228
- const allDataInNamespace = await localStorage.getAllItems(namespace);
242
+ export async function batchRemoveAllFromNamespaceForStorage(
243
+ namespace: string,
244
+ storage: Storage
245
+ ) {
246
+ const allDataInNamespace = await storage.getAllItems(namespace);
229
247
  const keysToRemove = Object.keys(allDataInNamespace);
230
248
 
231
- const dataToRemove = {
249
+ const dataToRemove: StorageValuesToRemove = {
232
250
  [namespace]: keysToRemove,
233
251
  };
234
252
 
235
- await batchRemoveFromLocalStorage(dataToRemove);
253
+ await batchRemoveFromStorage(dataToRemove, storage);
254
+ }
255
+
256
+ export async function batchRemoveAllFromNamespace(namespace: string) {
257
+ await batchRemoveAllFromNamespaceForStorage(namespace, localStorage);
236
258
  }