@applicaster/zapp-react-native-utils 13.0.0-alpha.3311671337 → 13.0.0-alpha.3322736490

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.
@@ -1,6 +1,6 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
  import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
3
- import React, { createContext, useMemo } from "react";
3
+ import React, { createContext, useEffect, useMemo } from "react";
4
4
  import {
5
5
  actionExecutor as _actionExecutor,
6
6
  ActionResult,
@@ -19,8 +19,15 @@ import {
19
19
  EntryResolver,
20
20
  resolveObjectValues,
21
21
  } from "../appUtils/contextKeysManager/contextResolver";
22
+ import { useNavigation } from "../reactHooks";
23
+ import { get } from "lodash";
22
24
 
23
- const { log_error, log_info } = createLogger({
25
+ import {
26
+ useContentTypes,
27
+ usePickFromState,
28
+ } from "@applicaster/zapp-react-native-redux/hooks";
29
+
30
+ export const { log_error, log_info, log_debug } = createLogger({
24
31
  subsystem: "ActionExecutorContext",
25
32
  category: "General",
26
33
  });
@@ -51,6 +58,22 @@ type Props = {
51
58
  children: React.ReactNode;
52
59
  };
53
60
 
61
+ function findParentComponent(
62
+ childId: string,
63
+ parent: ZappRiver | ZappUIComponent
64
+ ): ZappRiver | ZappUIComponent | null {
65
+ for (const child of parent.ui_components) {
66
+ if (child.id === childId) return parent;
67
+
68
+ if (child.ui_components) {
69
+ const found = findParentComponent(childId, child);
70
+ if (found) return found;
71
+ }
72
+ }
73
+
74
+ return null;
75
+ }
76
+
54
77
  const prepareDefaultActions = (actionExecutor) => {
55
78
  actionExecutor.registerAction("localStorageSet", async (action) => {
56
79
  const namespaces = action.options.content;
@@ -65,11 +88,13 @@ const prepareDefaultActions = (actionExecutor) => {
65
88
  async (_action, context) => {
66
89
  const dispatch = appStore.getDispatch();
67
90
 
68
- log_info("handleAction: refreshComponent event");
91
+ const dataSource =
92
+ context?.component?.data?.source ||
93
+ findParentComponent(context?.component.id, context?.screenData)?.data
94
+ ?.source;
69
95
 
70
- const dataSource = context?.component?.data?.source;
96
+ log_info(`handleAction: refreshComponent for dataSource:${dataSource}`);
71
97
 
72
- // TODO: Later we can pass component ids to do refresh instead of current one
73
98
  // TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
74
99
  // TODO: handle focused item removal
75
100
  dispatch(
@@ -169,8 +194,29 @@ const prepareDefaultActions = (actionExecutor) => {
169
194
  action: ActionType,
170
195
  context?: Record<string, any>
171
196
  ): Promise<ActionResult> => {
197
+ if (!context) {
198
+ log_error(
199
+ "handleAction: localStorageToggleFlag action missing context"
200
+ );
201
+
202
+ return ActionResult.Error;
203
+ }
204
+
205
+ const entry = context?.entry as ZappEntry;
206
+
207
+ if (!entry) {
208
+ log_error(
209
+ "handleAction: localStorageToggleFlag action missing entry. Entry is required to get the tag."
210
+ );
211
+
212
+ return ActionResult.Error;
213
+ }
214
+
215
+ const tag = action.options?.selector
216
+ ? get(entry, action.options.selector)
217
+ : (entry.extensions?.tag ?? entry.id);
218
+
172
219
  const keyNamespace = action.options?.key;
173
- const tag = context?.entry?.extensions?.tag;
174
220
 
175
221
  if (keyNamespace && tag) {
176
222
  const multiSelectProvider =
@@ -188,6 +234,16 @@ const prepareDefaultActions = (actionExecutor) => {
188
234
  if (selectedItems.includes(tag)) {
189
235
  await multiSelectProvider.removeItem(tag);
190
236
  } else {
237
+ const maxItems = action.options?.max_items;
238
+
239
+ if (maxItems && selectedItems.length >= maxItems) {
240
+ log_info(
241
+ `handleAction: localStorageToggleFlag event reached max items limit: ${maxItems}, cannot add tag: ${tag}`
242
+ );
243
+
244
+ return ActionResult.Cancel;
245
+ }
246
+
191
247
  await multiSelectProvider.addItem(tag);
192
248
  }
193
249
  } else {
@@ -211,6 +267,10 @@ export function withActionExecutor(Component) {
211
267
  prepareDefaultActions(_actionExecutor);
212
268
 
213
269
  return function ActionExecutorComponent(props: Props) {
270
+ const navigator = useNavigation();
271
+ const { rivers } = usePickFromState(["rivers"]);
272
+ const contentTypes = useContentTypes();
273
+
214
274
  const handlers = useMemo(() => {
215
275
  return {
216
276
  unregisterAction: _actionExecutor.unregisterAction,
@@ -221,6 +281,44 @@ export function withActionExecutor(Component) {
221
281
  };
222
282
  }, []);
223
283
 
284
+ useEffect(() => {
285
+ return _actionExecutor.registerAction(
286
+ "navigateToScreen",
287
+ async (action: ActionType, context?: Record<string, any>) => {
288
+ const screenType = action.options?.typeMapping;
289
+
290
+ if (!screenType) {
291
+ log_error("navigateToScreen: typeMapping option is missing");
292
+
293
+ return ActionResult.Error;
294
+ }
295
+
296
+ const screenId = contentTypes?.[screenType]?.screen_id || null;
297
+
298
+ if (!screenId) {
299
+ log_error(
300
+ `navigateToScreen: can not resolve screen type mapping: ${screenType}`
301
+ );
302
+
303
+ return ActionResult.Error;
304
+ }
305
+
306
+ const river = rivers[screenId];
307
+
308
+ if (!river) {
309
+ log_error("navigateToScreen: can not resolve river");
310
+
311
+ return ActionResult.Error;
312
+ }
313
+
314
+ context?.callback?.({ success: false, error: null, abort: true });
315
+ navigator.replace(river);
316
+
317
+ return ActionResult.Success;
318
+ }
319
+ );
320
+ }, [navigator, rivers, contentTypes]);
321
+
224
322
  return (
225
323
  <ActionExecutorContext.Provider value={handlers}>
226
324
  <Component {...props} />
@@ -65,6 +65,10 @@ export class Hook implements HookInterface {
65
65
  event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS],
66
66
  ...args
67
67
  ) {
68
+ if (this.state === hookState(HOOKS_EVENTS.CANCEL)) {
69
+ return;
70
+ }
71
+
68
72
  this.state = hookState(event);
69
73
  this.manager.subscriber.invokeHandler(event, ...args);
70
74
  }
@@ -269,6 +269,7 @@ export function HooksManager({
269
269
  payload = initialPayload,
270
270
  callback: callbackArg,
271
271
  cancelled,
272
+ abort,
272
273
  }) {
273
274
  let callback = callbackArg;
274
275
 
@@ -308,6 +309,7 @@ export function HooksManager({
308
309
  hookPlugin.setStateAndNotify(HOOKS_EVENTS.CANCEL, {
309
310
  hookPlugin,
310
311
  payload,
312
+ abort,
311
313
  });
312
314
 
313
315
  const isHookInHomescreen = payload?.home;
@@ -1,12 +1,6 @@
1
- import { createLogger } from "../../logger";
2
1
  import { ContextKeysManager } from "./index";
3
2
  import * as R from "ramda";
4
3
 
5
- const { log_error } = createLogger({
6
- subsystem: "contextResolver",
7
- category: "General",
8
- });
9
-
10
4
  interface IResolver {
11
5
  resolve: (string) => Promise<string | number | object>;
12
6
  }
@@ -64,12 +58,7 @@ export const resolveObjectValues = async (
64
58
  return [key, resolvedValue];
65
59
  }
66
60
 
67
- log_error("resolveObjectValues: invalid context string", {
68
- key,
69
- value,
70
- });
71
-
72
- return [key, null];
61
+ return [key, value];
73
62
  })
74
63
  );
75
64
 
@@ -5,6 +5,7 @@ exports[`focusManager should be defined 1`] = `
5
5
  "blur": [Function],
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
+ "findPreferredFocusChild": [Function],
8
9
  "focusableTree": Tree {
9
10
  "loadingCounter": 0,
10
11
  "root": {
@@ -534,6 +534,18 @@ export const focusManager = (function () {
534
534
 
535
535
  const { on, invokeHandler, removeHandler } = eventHandler;
536
536
 
537
+ function findPreferredFocusChild(id) {
538
+ const node = getCurrentFocusNode(id);
539
+
540
+ const preferredFocus = findPriorityItem([node]);
541
+
542
+ if (isNilOrEmpty(preferredFocus)) {
543
+ return undefined;
544
+ }
545
+
546
+ return preferredFocus[0];
547
+ }
548
+
537
549
  /**
538
550
  * this is the list of the functions available externally
539
551
  * when importing the focus manager
@@ -563,5 +575,6 @@ export const focusManager = (function () {
563
575
  isGroupItemFocused,
564
576
  recoverFocus,
565
577
  isCurrentFocusOnTheTopScreen,
578
+ findPreferredFocusChild,
566
579
  };
567
580
  })();
@@ -97,7 +97,7 @@ export class Player {
97
97
  this.onVideoLoad = this.onVideoLoad.bind(this);
98
98
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
99
99
  this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
100
-
100
+ this.onAdBreakEnd = this.onAdBreakEnd.bind(this);
101
101
  this.entryObservable = null;
102
102
  }
103
103
 
@@ -201,17 +201,25 @@ export class Player {
201
201
  adPosition: currentTime,
202
202
  };
203
203
  } else {
204
- if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
205
- this.playerState.seekableDuration = seekableDuration;
206
- }
204
+ this.updateContentDuration({ seekableDuration, duration, currentTime });
205
+ }
206
+ };
207
207
 
208
- if (!isEmptyOrNil(duration) && duration >= 0) {
209
- this.playerState.contentDuration = duration;
210
- }
208
+ protected updateContentDuration = ({
209
+ seekableDuration = null,
210
+ duration = null,
211
+ currentTime = null,
212
+ }) => {
213
+ if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
214
+ this.playerState.seekableDuration = seekableDuration;
215
+ }
211
216
 
212
- if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
213
- this.playerState.contentPosition = currentTime;
214
- }
217
+ if (!isEmptyOrNil(duration) && duration >= 0) {
218
+ this.playerState.contentDuration = duration;
219
+ }
220
+
221
+ if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
222
+ this.playerState.contentPosition = currentTime;
215
223
  }
216
224
  };
217
225
 
@@ -559,7 +567,7 @@ export class Player {
559
567
  });
560
568
  };
561
569
 
562
- onAdBreakEnd = (event) => {
570
+ onAdBreakEnd(event) {
563
571
  this.playerState.adState = null;
564
572
  this.playerState.isInAdBreak = false;
565
573
 
@@ -567,7 +575,7 @@ export class Player {
567
575
  callbackName: SharedPlayerCallBacksKeys.OnAdBreakEnd,
568
576
  event,
569
577
  });
570
- };
578
+ }
571
579
 
572
580
  onAdEnd = (event) => {
573
581
  this.playerState.adState = null;
@@ -225,7 +225,9 @@ export class PlayerNative extends Player {
225
225
  };
226
226
 
227
227
  onVideoLoad = (event) => {
228
- if (this.startPosition) {
228
+ this.updateContentDuration(event);
229
+
230
+ if (this.startPosition && !this.playerState.isInAdBreak) {
229
231
  super.onVideoLoad({ ...event, ignoreContinueWatching: true });
230
232
 
231
233
  this.seekTo(this.startPosition);
@@ -253,6 +255,15 @@ export class PlayerNative extends Player {
253
255
  }
254
256
  };
255
257
 
258
+ onAdBreakEnd = (event) => {
259
+ super.onAdBreakEnd(event);
260
+
261
+ if (this.startPosition) {
262
+ this.seekTo(this.startPosition);
263
+ this.startPosition = null;
264
+ }
265
+ };
266
+
256
267
  // This function is called when native player used directly without wrapped component,
257
268
  // Since event from native comes with wrapped in NativeEvent property
258
269
  getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
@@ -2,10 +2,12 @@ import dayjs from "dayjs";
2
2
  import utc from "dayjs/plugin/utc";
3
3
  import timezone from "dayjs/plugin/timezone";
4
4
  import isoWeek from "dayjs/plugin/isoWeek";
5
+ import duration from "dayjs/plugin/duration";
5
6
 
6
7
  // Extend dayjs with plugins
7
8
  dayjs.extend(utc);
8
9
  dayjs.extend(timezone);
9
10
  dayjs.extend(isoWeek);
11
+ dayjs.extend(duration);
10
12
 
11
13
  export { dayjs };
@@ -227,7 +227,9 @@ class FocusManager {
227
227
 
228
228
  const nextNextFocus = FocusManager.getNextFocusable(direction, nextFocus);
229
229
 
230
- FocusManager.instance.setFocus(nextNextFocus, options);
230
+ if (nextNextFocus) {
231
+ FocusManager.instance.setFocus(nextNextFocus, options);
232
+ }
231
233
  } else {
232
234
  FocusManager.instance.setFocusLocal(nextFocus);
233
235
 
@@ -8,8 +8,6 @@ import { isNilOrEmpty } from "../reactUtils/helpers";
8
8
  import { isAndroidTVPlatform } from "../reactUtils";
9
9
  import { noop } from "../functionUtils";
10
10
 
11
- const EMPTY_GROUP_COMPONENT = "empty_group_component";
12
-
13
11
  export function useFocusManager() {
14
12
  return focusManager;
15
13
  }
@@ -76,14 +74,6 @@ export function useFocusEffect(onFocus, focusWatcher) {
76
74
  }, focusWatcher);
77
75
  }
78
76
 
79
- export function shouldShowEmptyGroup(component) {
80
- return (
81
- component?.ui_components?.filter(
82
- ({ component_type }) => component_type === EMPTY_GROUP_COMPONENT
83
- )?.length > 0
84
- );
85
- }
86
-
87
77
  export const isFocusable = (component: ZappEntry | ZappUIComponent | any) =>
88
78
  R.tryCatch(
89
79
  R.compose(
@@ -101,6 +91,7 @@ type Props = {
101
91
 
102
92
  export const useIsFocusable = (props: Props): void => {
103
93
  const { component, zappPipesData } = props;
94
+
104
95
  const { setIsFocusable } = useFocusable();
105
96
 
106
97
  const componentCellsFocusable = toBooleanWithDefaultTrue(
@@ -119,12 +110,7 @@ export const useIsFocusable = (props: Props): void => {
119
110
 
120
111
  const hasDataError = !!zappPipesData?.error;
121
112
 
122
- const hasEmptyGroup = shouldShowEmptyGroup(component);
123
-
124
- const isGallery = component?.component_type === "gallery-qb";
125
-
126
- const isHiddenDueToEmptyData =
127
- !isLoadingData && hasNoData && hideIfDataEmpty && !hasEmptyGroup;
113
+ const isHiddenDueToEmptyData = !isLoadingData && hasNoData && hideIfDataEmpty;
128
114
 
129
115
  const defaultFocusableLogic =
130
116
  componentCellsFocusable && (!hasNoData || !hideIfDataEmpty);
@@ -133,9 +119,7 @@ export const useIsFocusable = (props: Props): void => {
133
119
  if (isAndroidTVPlatform()) {
134
120
  let isFocusableNew = false;
135
121
 
136
- if (isGallery) {
137
- isFocusableNew = true;
138
- } else if (isFocusable(component)) {
122
+ if (isFocusable(component)) {
139
123
  isFocusableNew = defaultFocusableLogic;
140
124
  }
141
125
 
@@ -144,9 +128,7 @@ export const useIsFocusable = (props: Props): void => {
144
128
  }, [
145
129
  defaultFocusableLogic,
146
130
  isHiddenDueToEmptyData,
147
- isGallery,
148
131
  setIsFocusable,
149
132
  hasDataError,
150
- hasEmptyGroup,
151
133
  ]);
152
134
  };
@@ -258,3 +258,28 @@ export const extractDataFromProperty = (property, data): any =>
258
258
  );
259
259
 
260
260
  export const then = (fn) => (promise) => promise.then(fn);
261
+
262
+ /**
263
+ * Returns a replacer function for JSON.stringify that handles circular references,
264
+ * functions, and React context objects
265
+ * @returns {Function} A replacer function that detects and handles circular references,
266
+ * functions, and React context objects
267
+ */
268
+ export const getJsonReplacer = () => {
269
+ return (_key, value) => {
270
+ if (typeof value === "function") {
271
+ return "[Function]";
272
+ }
273
+
274
+ if (
275
+ typeof value === "object" &&
276
+ value !== null &&
277
+ value.Provider &&
278
+ value.Consumer
279
+ ) {
280
+ return "[ReactContext]";
281
+ }
282
+
283
+ return value;
284
+ };
285
+ };
package/index.d.ts CHANGED
@@ -105,18 +105,6 @@ declare type NavBarState = {
105
105
  title: string;
106
106
  };
107
107
 
108
- declare type StorageValuesToAdd = Record<string, NamespaceValues>;
109
-
110
- declare type StorageValuesToRemove = {
111
- string?: string[];
112
- };
113
-
114
- declare type StorageOwnedValues = {
115
- string?: string[];
116
- };
117
-
118
- declare type NamespaceValues = Record<string, string>;
119
-
120
108
  declare type RefreshProps = {
121
109
  forceRefresh?: boolean;
122
110
  };
@@ -0,0 +1,51 @@
1
+ import { modalStore } from "../";
2
+
3
+ describe("Modal State", () => {
4
+ it("should have initial state", () => {
5
+ const state = modalStore.getState();
6
+
7
+ expect(state).toMatchObject({
8
+ modalState: {
9
+ visible: false,
10
+ screen: null,
11
+ options: {},
12
+ props: {},
13
+ },
14
+ });
15
+
16
+ expect(state.openModal).toBeDefined();
17
+ expect(state.dismissModal).toBeDefined();
18
+ });
19
+
20
+ describe("opening and dismissing modal", () => {
21
+ it("should open modal", () => {
22
+ const item = { id: "test", name: "Test Modal" };
23
+ const options = { animated: true, animationType: "slide" };
24
+ const props = { customProp: "value" };
25
+
26
+ modalStore.getState().openModal({ item, options, props });
27
+
28
+ const state = modalStore.getState();
29
+
30
+ expect(state.modalState).toMatchObject({
31
+ visible: true,
32
+ screen: item,
33
+ options,
34
+ props,
35
+ });
36
+ });
37
+
38
+ it("should dismiss modal", () => {
39
+ modalStore.getState().dismissModal();
40
+
41
+ const state = modalStore.getState();
42
+
43
+ expect(state.modalState).toMatchObject({
44
+ visible: false,
45
+ screen: null,
46
+ options: {},
47
+ props: {},
48
+ });
49
+ });
50
+ });
51
+ });
@@ -1,4 +1,4 @@
1
- import { create } from "zustand";
1
+ import { useStore, createStore } from "zustand";
2
2
 
3
3
  interface ModalState {
4
4
  visible: boolean;
@@ -25,7 +25,7 @@ const initialModalState: ModalState = {
25
25
  props: {},
26
26
  };
27
27
 
28
- export const useModalStore = create<ModalStore>((set) => ({
28
+ export const modalStore = createStore<ModalStore>((set) => ({
29
29
  modalState: initialModalState,
30
30
  setModalState: (newState) =>
31
31
  set((state) => ({
@@ -51,3 +51,33 @@ export const useModalStore = create<ModalStore>((set) => ({
51
51
  }),
52
52
  dismissModal: () => set(() => ({ modalState: initialModalState })),
53
53
  }));
54
+
55
+ export const useModalStore = <T>(selector: (state: ModalStore) => T) =>
56
+ useStore(modalStore, selector);
57
+
58
+ export const openModal = modalStore.getInitialState().openModal;
59
+
60
+ export const dismissModal = modalStore.getInitialState().dismissModal;
61
+
62
+ export const openBottomSheetModal = ({
63
+ ModalBottomSheetContent,
64
+ modalBottomSheetContentProps,
65
+ props = {},
66
+ options = {},
67
+ }: OpenModalBottomSheetArgs) => {
68
+ openModal({
69
+ item: {
70
+ ModalBottomSheetContent,
71
+ modalBottomSheetContentProps,
72
+ },
73
+ props,
74
+ options: {
75
+ animationType: "none",
76
+ animated: false,
77
+ ...options,
78
+ },
79
+ });
80
+ };
81
+
82
+ export const useModalStoreState = (): ModalState =>
83
+ useModalStore<ModalState>((state) => state.modalState);
@@ -1,10 +1,10 @@
1
1
  import { toPositiveNumberWithDefault } from "..";
2
2
 
3
- describe("toNumber", () => {
3
+ describe("toPositiveNumberWithDefault", () => {
4
4
  const DEFAULT = 5;
5
5
 
6
6
  it("return number if input is positive number", () => {
7
- const inputs = [0, 1, 2, 100, 200.12, Infinity];
7
+ const inputs = [1, 2, 100, 200.12, Infinity];
8
8
  expect.assertions(inputs.length);
9
9
 
10
10
  inputs.forEach((input) => {
@@ -14,7 +14,7 @@ describe("toNumber", () => {
14
14
  });
15
15
 
16
16
  it("return number if input is positive number as string", () => {
17
- const inputs = ["0", "1", "2", "100", "200.12"];
17
+ const inputs = ["1", "2", "100", "200.12"];
18
18
  expect.assertions(inputs.length);
19
19
 
20
20
  inputs.forEach((input) => {
@@ -63,4 +63,24 @@ describe("toNumber", () => {
63
63
  expect(output).toBe(DEFAULT);
64
64
  });
65
65
  });
66
+
67
+ it("return default if input is zero number", () => {
68
+ const inputs = [0, -0];
69
+ expect.assertions(inputs.length);
70
+
71
+ inputs.forEach((input) => {
72
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
73
+ expect(output).toBe(DEFAULT);
74
+ });
75
+ });
76
+
77
+ it("return default if input is zero string", () => {
78
+ const inputs = ["0", "-0"];
79
+ expect.assertions(inputs.length);
80
+
81
+ inputs.forEach((input) => {
82
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
83
+ expect(output).toBe(DEFAULT);
84
+ });
85
+ });
66
86
  });
@@ -83,6 +83,10 @@ export const toPositiveNumberWithDefault = (
83
83
  return defaultValue;
84
84
  }
85
85
 
86
+ if (isZero(possibleNumber)) {
87
+ return defaultValue;
88
+ }
89
+
86
90
  return possibleNumber;
87
91
  };
88
92
 
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.0-alpha.3311671337",
3
+ "version": "13.0.0-alpha.3322736490",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "scripts": {
8
- "test": "jest -w=2",
9
8
  "build": "echo skipping hybrid ts build in main packages for now"
10
9
  },
11
10
  "publishConfig": {
@@ -28,7 +27,7 @@
28
27
  },
29
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
29
  "dependencies": {
31
- "@applicaster/applicaster-types": "13.0.0-alpha.3311671337",
30
+ "@applicaster/applicaster-types": "13.0.0-alpha.3322736490",
32
31
  "buffer": "^5.2.1",
33
32
  "camelize": "^1.0.0",
34
33
  "dayjs": "^1.11.10",
@@ -5,6 +5,7 @@ import * as R from "ramda";
5
5
  import { useNavigation } from "../navigation";
6
6
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
7
7
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
8
+ import { dismissModal, openModal } from "../../modalState";
8
9
 
9
10
  const GOOGLE_ADS_IDENTIFIER = "google-admob-ads-qb";
10
11
 
@@ -14,9 +15,8 @@ const ModalContainer = platformSelect({
14
15
  });
15
16
 
16
17
  export function useAdvertisingInterstitial() {
17
- const navigator = useNavigation();
18
+ const { screenData, currentRoute } = useNavigation();
18
19
  const { plugins } = usePickFromState();
19
- const { screenData } = navigator;
20
20
 
21
21
  return useEffect(() => {
22
22
  // TODD: typing problem: fix any type
@@ -26,7 +26,7 @@ export function useAdvertisingInterstitial() {
26
26
  plugins
27
27
  );
28
28
 
29
- navigator.openModal({
29
+ openModal({
30
30
  item: {},
31
31
  options: {
32
32
  ModalContainer,
@@ -36,11 +36,11 @@ export function useAdvertisingInterstitial() {
36
36
  screenType: "hooks",
37
37
  screenData: {
38
38
  payload: screenData,
39
- callback: navigator.dismissModal,
39
+ callback: dismissModal,
40
40
  hookPlugin: GoogleAdsPlugin,
41
41
  },
42
42
  },
43
43
  });
44
44
  }
45
- }, [navigator.currentRoute, screenData?.id]);
45
+ }, [currentRoute, screenData?.id]);
46
46
  }
@@ -5,7 +5,7 @@ import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
5
5
  import { useZappPipesFeed } from "@applicaster/zapp-react-native-redux/hooks";
6
6
 
7
7
  import { reactHooksLogger } from "../logger";
8
- import { useIsInitialRender, shouldDispatchData } from "../utils";
8
+ import { shouldDispatchData, useIsInitialRender } from "../utils";
9
9
  import { useInflatedUrl } from "./useInflatedUrl";
10
10
  import { useRoute } from "../navigation";
11
11
 
@@ -55,7 +55,9 @@ export const useFeedLoader = ({
55
55
 
56
56
  const currentFeed = useZappPipesFeed(callableFeedUrl);
57
57
 
58
- const riverId = screenData?.id;
58
+ const riverId =
59
+ (screenData as LegacyNavigationScreenData)?.targetScreen?.id ??
60
+ screenData?.id;
59
61
 
60
62
  const reloadData = React.useCallback<ReloadDataFunction>(
61
63
  (silentRefresh = true, callback) => {
@@ -0,0 +1,39 @@
1
+ import { reactHooksLogger } from "../../logger";
2
+ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
3
+
4
+ const logger = reactHooksLogger.addSubsystem("useHookModalScreenData");
5
+
6
+ type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
7
+
8
+ export const useHookModalScreenData = (): VariousScreenData | undefined => {
9
+ const hookModalState = useZappHooksModalState();
10
+
11
+ if (!hookModalState.state?.screenData) {
12
+ return;
13
+ }
14
+
15
+ try {
16
+ const screenData = hookModalState.state.screenData?.payload;
17
+ const hookPlugin = hookModalState.state.screenData.hookPlugin;
18
+
19
+ if (hookPlugin?.screen_id) {
20
+ return {
21
+ ...screenData,
22
+ targetScreen: hookPlugin,
23
+ };
24
+ }
25
+
26
+ return hookModalState.state.screenData?.payload;
27
+ } catch (error) {
28
+ logger.error({
29
+ message: "Hook modal screen data creation failed",
30
+ data: {
31
+ error,
32
+ hookModalState,
33
+ },
34
+ jsOnly: true,
35
+ });
36
+
37
+ return hookModalState.state.screenData?.payload;
38
+ }
39
+ };
@@ -108,9 +108,11 @@ export function isNavBarVisible(
108
108
  return false;
109
109
  }
110
110
 
111
- const showSecondaryLevel = screenData?.navigations?.some(
112
- (navigation) => navigation.styles?.show_secondary_level_menu
113
- );
111
+ const showSecondaryLevel =
112
+ !isTV() ||
113
+ screenData?.navigations?.some(
114
+ (navigation) => navigation.styles?.show_secondary_level_menu
115
+ );
114
116
 
115
117
  if (canGoBack && !showSecondaryLevel) {
116
118
  return false;
@@ -7,12 +7,11 @@ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
7
7
 
8
8
  import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
9
9
 
10
- import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
11
-
12
10
  import { getTargetScreenData } from "../screen/useTargetScreenData";
11
+ import { useHookModalScreenData } from "../hookModal/hooks/useHookModalScreenData";
13
12
 
14
13
  import { useNavigation } from "./useNavigation";
15
- import { useModalStore } from "../../modalState";
14
+ import { useModalStoreState } from "../../modalState";
16
15
  import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
17
16
  import { usePathname } from "./usePathname";
18
17
 
@@ -44,12 +43,11 @@ export const useRoute = (): {
44
43
  "contentTypes",
45
44
  ]);
46
45
 
47
- const { modalState } = useModalStore();
48
- const hookModalState = useZappHooksModalState();
46
+ const modalState = useModalStoreState();
49
47
 
50
48
  const modalScreenData = modalState.screen;
51
49
 
52
- const hookModalScreenData = hookModalState.state.screenData?.payload;
50
+ const hookModalScreenData = useHookModalScreenData();
53
51
 
54
52
  const videoModalScreenData =
55
53
  navigator?.videoModalState?.item &&
@@ -0,0 +1,30 @@
1
+ ### ramda replacement utils
2
+
3
+ See contribution guide below
4
+
5
+ ## Reference
6
+
7
+ Prefer non-fp variant as it more performant - if refactoring assumes you need more efforts rewrite ramda sample to lodash and it can be easily replaced with lodash-fp - use lodash-fp.
8
+
9
+ ### Ramda alternatives:
10
+
11
+ Custom
12
+ - `splitEvery` => `chunk`
13
+ - `times` => `times`
14
+
15
+ From `lodash`
16
+ - `clone` => `clone`
17
+ - `flatten` => `flatten`
18
+ - `drop` => `drop`
19
+ - `length` => `size`
20
+
21
+
22
+ ## Contribution
23
+
24
+ To add new helper function as ramda replacement
25
+
26
+ 1. Search for replacement ( you can use lodash or pure JS solution), arguments should match with Ramda definition (for easier transition).
27
+ 2. Add new file if needed, add test, update exports in index file. 2.1. Make sure it's well-typed.
28
+ 3. If the function can-be reexported from other 3rd party lib - update index.ts to re-export it.
29
+ 4. Make sure README is updated and contains reference for newly added function.
30
+ 5. You can use AI's help. Use query like 'lodash & lodash/fp equivalent of Ramda's R.clone function' to get an options you can use.
package/utils/chunk.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { chunk as _chunk } from "lodash";
2
+
3
+ export const chunk = <T>(size: number, array: T[]): T[][] => {
4
+ return _chunk(array, size);
5
+ };
package/utils/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { chunk } from "./chunk";
2
+
3
+ export { times } from "./times";
4
+
5
+ export { cloneDeep as clone, flatten, drop, size } from "lodash";
package/utils/times.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { times as _times } from "lodash";
2
+
3
+ export const times = <T>(iteratee: (index: number) => T, n: number) => {
4
+ return _times(n, iteratee);
5
+ };
@@ -1,6 +1,12 @@
1
1
  import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
2
2
  import { isNilOrEmpty } from "../reactUtils/helpers";
3
3
  import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
4
+ import {
5
+ StorageOwnedValues,
6
+ NamespaceValues,
7
+ StorageValuesToRemove,
8
+ StorageValuesToAdd,
9
+ } from "./types";
4
10
 
5
11
  const defaultOwnedKey = "owned_keys";
6
12
  const DEFAULT_NAMESPACE = "applicaster.v2";
@@ -89,11 +89,15 @@ const runInBackground = async ({
89
89
  const shouldPresent = await hookPresentPolicy.shouldPresentScreen();
90
90
 
91
91
  if (!shouldPresent) {
92
+ const isRefreshed = await checkUserAuthenticated();
93
+
92
94
  log_info(
93
- "runInBackground: Background mode finished, hook plugin will be skipped"
95
+ `runInBackground: Background mode finished, ${isRefreshed ? "finish hook with completion true" : "refresh failed will presentUI"}`
94
96
  );
95
97
 
96
- await checkUserAuthenticated();
98
+ isRefreshed
99
+ ? callback({ success: isRefreshed, payload: item })
100
+ : presentUI();
97
101
 
98
102
  return;
99
103
  }
@@ -0,0 +1,11 @@
1
+ export type StorageValuesToAdd = Record<string, NamespaceValues>;
2
+
3
+ export type StorageValuesToRemove = {
4
+ string?: string[];
5
+ };
6
+
7
+ export type StorageOwnedValues = {
8
+ string?: string[];
9
+ };
10
+
11
+ export type NamespaceValues = Record<string, string>;