@applicaster/zapp-react-native-utils 11.0.0-rc.99 → 12.0.0-rc.1

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.
@@ -0,0 +1,117 @@
1
+ import { createLogger } from "../logger";
2
+
3
+ export enum ActionResult {
4
+ Success = "success",
5
+ Error = "error",
6
+ Cancel = "cancel",
7
+ }
8
+
9
+ const { log_error, log_debug, log_info } = createLogger({
10
+ subsystem: "ActionExecutor",
11
+ category: "General",
12
+ });
13
+
14
+ class ActionExecutor {
15
+ private registeredActions: Record<string, any> = {};
16
+ // TODO: Remove context
17
+
18
+ handleAction = async (
19
+ action: ActionType,
20
+ entry: ZappEntry = null
21
+ ): Promise<ActionResult> => {
22
+ const handler = this.registeredActions[action.type];
23
+
24
+ if (!handler) {
25
+ log_error(
26
+ `handleAction: no handler found for action type: ${action.type}`,
27
+ { action, entry }
28
+ );
29
+
30
+ return;
31
+ }
32
+
33
+ log_debug(
34
+ `handleAction: ${action.type}${
35
+ entry ? `, entry id ${entry.id}, title: ${entry.title}` : ""
36
+ }`,
37
+ { action, entry }
38
+ );
39
+
40
+ try {
41
+ return handler(action, entry);
42
+ } catch (error) {
43
+ log_error(
44
+ `handleAction: error handling action type: ${action.type}, message: ${error.message}`,
45
+ { action, entry, error }
46
+ );
47
+
48
+ throw error;
49
+ }
50
+ };
51
+
52
+ handleActions = async (
53
+ actionTypes: ActionType[],
54
+ entry: ZappEntry = null
55
+ ): Promise<ActionResult> => {
56
+ for (const action of actionTypes) {
57
+ // TODO: Handle action result error
58
+ const result = await this.handleAction(action, entry);
59
+
60
+ if (result === ActionResult.Cancel) {
61
+ log_info(`handleActions: action: ${action.type} canceled`);
62
+
63
+ return result;
64
+ }
65
+ }
66
+
67
+ return ActionResult.Success;
68
+ };
69
+
70
+ handleEntryActions = async (entry: ZappEntry): Promise<ActionResult> => {
71
+ const tapActions = entry.extensions?.tap_actions?.actions;
72
+
73
+ if (!tapActions) {
74
+ return;
75
+ }
76
+
77
+ if (!Array.isArray(tapActions)) {
78
+ log_error(
79
+ `handleEntryActions: tapActions is not an array for id: ${entry.id} title: ${entry.title}`,
80
+ { entry }
81
+ );
82
+
83
+ return;
84
+ }
85
+
86
+ if (tapActions.length === 0) {
87
+ return;
88
+ }
89
+
90
+ await this.handleActions(tapActions, entry);
91
+ };
92
+
93
+ unregisterAction = (actionType: string) => {
94
+ delete this.registeredActions[actionType];
95
+ };
96
+
97
+ registerAction = (
98
+ type: string,
99
+ handler: (action: ActionType, entry?: ZappEntry) => Promise<ActionResult>
100
+ ) => {
101
+ if (this.registeredActions[type]) {
102
+ log_error(`registerAction: action type: ${type} already registered`);
103
+
104
+ return () => {};
105
+ }
106
+
107
+ this.registeredActions[type] = handler;
108
+ log_debug(`registerAction: action type: ${type} registered`);
109
+
110
+ return () => {
111
+ this.unregisterAction(type);
112
+ log_debug(`registerAction: action type: ${type} un-registered`);
113
+ };
114
+ };
115
+ }
116
+
117
+ export const actionExecutor = new ActionExecutor();
@@ -1,23 +1,25 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
 
3
3
  import React, { createContext, useMemo } from "react";
4
- import { createLogger } from "../logger";
4
+ import {
5
+ actionExecutor as _actionExecutor,
6
+ ActionResult,
7
+ } from "./ActionExecutor";
5
8
  import { batchSaveToLocalStorage } from "../zappFrameworkUtils/localStorageHelper";
6
- import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
7
9
  import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
10
+ import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
8
11
  import { showConfirmationDialog } from "../alertUtils";
12
+ import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
13
+ import { createLogger } from "../logger";
14
+ import {
15
+ StorageMultiSelectProvider
16
+ } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
9
17
 
10
18
  const { log_error, log_debug, log_info } = createLogger({
11
19
  subsystem: "ActionExecutorContext",
12
20
  category: "General",
13
21
  });
14
22
 
15
- export enum ActionResult {
16
- Success = "success",
17
- Error = "error",
18
- Cancel = "cancel",
19
- }
20
-
21
23
  type ActionExecutorContextType = {
22
24
  registerAction: (
23
25
  type: string,
@@ -35,161 +37,114 @@ type Props = {
35
37
  children: React.ReactNode;
36
38
  };
37
39
 
38
- export const ActionExecutorContext =
39
- createContext<ActionExecutorContextType>(null);
40
-
41
- class ActionExecutor {
42
- private registeredActions: Record<string, any> = {};
43
- // TODO: Remove context
44
-
45
- constructor() {
46
- this.prepareDefaultActions();
47
- }
48
-
49
- prepareDefaultActions = () => {
50
- this.registerAction("localStorageSet", async (action) => {
51
- const namespaces = action.options.content;
52
- await batchSaveToLocalStorage(namespaces);
53
- // TODO: Add support for ownershipKey and ownershipNamespace
40
+ const prepareDefaultActions = (actionExecutor) => {
41
+ actionExecutor.registerAction("localStorageSet", async (action) => {
42
+ const namespaces = action.options.content;
43
+ await batchSaveToLocalStorage(namespaces);
44
+ // TODO: Add support for ownershipKey and ownershipNamespace
54
45
 
55
- return ActionResult.Success;
56
- });
46
+ return ActionResult.Success;
47
+ });
57
48
 
58
- this.registeredActions["appRestart"] = async () => {
59
- log_info(`handleAction: ${QUICK_BRICK_EVENTS.FORCE_APP_RELOAD} event`);
49
+ actionExecutor.registerAction("appRestart", async () => {
50
+ log_info(`handleAction: ${QUICK_BRICK_EVENTS.FORCE_APP_RELOAD} event`);
60
51
 
61
- QuickBrickManager.sendQuickBrickEvent(
62
- QUICK_BRICK_EVENTS.FORCE_APP_RELOAD
63
- );
52
+ QuickBrickManager.sendQuickBrickEvent(QUICK_BRICK_EVENTS.FORCE_APP_RELOAD);
64
53
 
65
- return ActionResult.Success;
66
- };
54
+ return ActionResult.Success;
55
+ });
67
56
 
68
- this.registeredActions["confirmDialog"] = async (action) => {
69
- log_info("handleAction: confirmDialog event");
57
+ actionExecutor.registerAction("confirmDialog", async (action) => {
58
+ log_info("handleAction: confirmDialog event");
70
59
 
71
- const confirmationPromise = new Promise<ActionResult>((resolve) => {
72
- showConfirmationDialog({
73
- ...action.options,
74
- confirmCompletion: () => resolve(ActionResult.Success),
75
- cancelCompletion: () => resolve(ActionResult.Cancel),
76
- });
60
+ const confirmationPromise = new Promise<ActionResult>((resolve) => {
61
+ showConfirmationDialog({
62
+ ...action.options,
63
+ confirmCompletion: () => resolve(ActionResult.Success),
64
+ cancelCompletion: () => resolve(ActionResult.Cancel),
77
65
  });
66
+ });
78
67
 
79
- return confirmationPromise;
80
- };
81
- };
82
-
83
- handleAction = async (
84
- action: ActionType,
85
- entry: ZappEntry = null
86
- ): Promise<ActionResult> => {
87
- const handler = this.registeredActions[action.type];
88
-
89
- if (!handler) {
90
- log_error(
91
- `handleAction: no handler found for action type: ${action.type}`,
92
- { action, entry }
93
- );
94
-
95
- return;
96
- }
97
-
98
- log_debug(
99
- `handleAction: ${action.type}${
100
- entry ? `, entry id ${entry.id}, title: ${entry.title}` : ""
101
- }`,
102
- { action, entry }
103
- );
68
+ return confirmationPromise;
69
+ });
104
70
 
71
+ actionExecutor.registerAction("sendCloudEvent", async (action, entry) => {
105
72
  try {
106
- return handler(action, entry);
107
- } catch (error) {
108
- log_error(
109
- `handleAction: error handling action type: ${action.type}, message: ${error.message}`,
110
- { action, entry, error }
111
- );
112
-
113
- throw error;
114
- }
115
- };
73
+ const options = action.options;
116
74
 
117
- handleActions = async (
118
- actionTypes: ActionType[],
119
- entry: ZappEntry = null
120
- ): Promise<ActionResult> => {
121
- for (const action of actionTypes) {
122
- // TODO: Handle action result error
123
- const result = await this.handleAction(action, entry);
75
+ const cloudEvent = await createCloudEvent({
76
+ type: options.type || "com.applicaster.selector.action.v1",
77
+ data: options.data || entry,
78
+ subject: options.subject || entry?.id,
79
+ });
124
80
 
125
- if (result === ActionResult.Cancel) {
126
- log_info(`handleActions: action: ${action.type} canceled`);
81
+ log_info("handleAction: sendCloudEvent event", { cloudEvent });
127
82
 
128
- return result;
129
- }
130
- }
131
-
132
- return ActionResult.Success;
133
- };
83
+ const { error, code } = await sendCloudEvent(cloudEvent, options.url);
134
84
 
135
- handleEntryActions = async (entry: ZappEntry): Promise<ActionResult> => {
136
- const tapActions = entry.extensions?.tap_actions?.actions;
85
+ if (error) {
86
+ log_error("sendCloudEvent: error sending cloud event", { error });
137
87
 
138
- if (!tapActions) {
139
- return;
140
- }
88
+ return ActionResult.Error;
89
+ }
141
90
 
142
- if (!Array.isArray(tapActions)) {
143
- log_error(
144
- `handleEntryActions: tapActions is not an array for id: ${entry.id} title: ${entry.title}`,
145
- { entry }
146
- );
91
+ if (code && code >= 200 && code < 300 && !error) {
92
+ log_info("sendCloudEvent: cloud event sent successfully");
147
93
 
148
- return;
94
+ return ActionResult.Success;
95
+ }
96
+ } catch (error) {
97
+ log_error("sendCloudEvent: error sending cloud event", {
98
+ action,
99
+ error,
100
+ });
149
101
  }
150
102
 
151
- if (tapActions.length === 0) {
152
- return;
153
- }
103
+ return ActionResult.Error;
104
+ });
154
105
 
155
- await this.handleActions(tapActions, entry);
156
- };
106
+ actionExecutor.registerAction(
107
+ "localStorageToggleFlag",
108
+ async (action: ActionType, entry?: ZappEntry): Promise<ActionResult> => {
109
+ const keyNamespace = action.options?.key;
110
+ const tag = entry?.extensions?.tag;
157
111
 
158
- unregisterAction = (actionType: string) => {
159
- delete this.registeredActions[actionType];
160
- };
112
+ if (keyNamespace && tag) {
113
+ const multiSelectProvider = StorageMultiSelectProvider.getProvider(keyNamespace);
161
114
 
162
- registerAction = (
163
- type: string,
164
- handler: (action: ActionType, entry?: ZappEntry) => Promise<ActionResult>
165
- ) => {
166
- if (this.registeredActions[type]) {
167
- log_error(`registerAction: action type: ${type} already registered`);
115
+ const selectedItems = await multiSelectProvider.getSelectedAsync();
116
+ const isTagInSelectedItems = selectedItems.includes(tag);
117
+ log_info(`handleAction: localStorageToggleFlag event will ${isTagInSelectedItems ? "remove" : "add"} tag: ${tag} for keyNamespace: ${keyNamespace}, current selectedItems: ${selectedItems}`);
168
118
 
169
- return () => {};
170
- }
119
+ if (selectedItems.includes(tag)) {
120
+ await multiSelectProvider.removeItem(tag);
121
+ } else {
122
+ await multiSelectProvider.addItem(tag);
123
+ }
124
+ } else {
125
+ log_error("handleAction: localStorageToggleFlag event missing keyNamespace or tag", { keyNamespace, tag });
171
126
 
172
- this.registeredActions[type] = handler;
173
- log_debug(`registerAction: action type: ${type} registered`);
127
+ return ActionResult.Error;
128
+ }
174
129
 
175
- return () => {
176
- this.unregisterAction(type);
177
- log_debug(`registerAction: action type: ${type} un-registered`);
178
- };
179
- };
180
- }
130
+ return ActionResult.Success;
131
+ });
132
+ };
181
133
 
182
- export const actionExecutor = new ActionExecutor();
134
+ export const ActionExecutorContext =
135
+ createContext<ActionExecutorContextType>(null);
183
136
 
184
137
  export function withActionExecutor(Component) {
138
+ prepareDefaultActions(_actionExecutor);
139
+
185
140
  return function ActionExecutorComponent(props: Props) {
186
141
  const handlers = useMemo(() => {
187
142
  return {
188
- unregisterAction: actionExecutor.unregisterAction,
189
- registerAction: actionExecutor.registerAction,
190
- handleAction: actionExecutor.handleAction,
191
- handleActions: actionExecutor.handleActions,
192
- handleEntryActions: actionExecutor.handleEntryActions,
143
+ unregisterAction: _actionExecutor.unregisterAction,
144
+ registerAction: _actionExecutor.registerAction,
145
+ handleAction: _actionExecutor.handleAction,
146
+ handleActions: _actionExecutor.handleActions,
147
+ handleEntryActions: _actionExecutor.handleEntryActions,
193
148
  };
194
149
  }, []);
195
150
 
@@ -200,3 +155,6 @@ export function withActionExecutor(Component) {
200
155
  );
201
156
  };
202
157
  }
158
+
159
+ // TODO: Please use direct import from the ActionExecutor.ts
160
+ export const actionExecutor = _actionExecutor;
@@ -1,7 +1,10 @@
1
1
  import * as R from "ramda";
2
2
 
3
3
  import { subscriber } from "@applicaster/zapp-react-native-utils/functionUtils";
4
- import { QUICK_BRICK_EVENTS, sendQuickBrickEvent } from "@applicaster/zapp-react-native-bridge/QuickBrick";
4
+ import {
5
+ QUICK_BRICK_EVENTS,
6
+ sendQuickBrickEvent,
7
+ } from "@applicaster/zapp-react-native-bridge/QuickBrick";
5
8
 
6
9
  import { Hook } from "./Hook";
7
10
  import { HOOKS_EVENTS, HOOKS_TYPE } from "./constants";
@@ -269,8 +269,6 @@ export const focusManager = (function () {
269
269
  logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
270
270
 
271
271
  if (focusAutoEnabled) {
272
- logger.log({ message: `setInitialFocus ${setInitialFocus}` });
273
-
274
272
  setInitialFocus(initialFocusOnContent ? node : undefined);
275
273
  }
276
274
  }
@@ -4,27 +4,40 @@ import {
4
4
  RequestBuilder,
5
5
  } from "@applicaster/zapp-pipes-v2-client";
6
6
  import { log_error, log_info } from "./OverlaysObserver";
7
- import * as R from "ramda/es";
7
+ import * as R from "ramda";
8
8
  import { toNumber, toNumberWithDefault } from "../../../numberUtils";
9
9
 
10
- export const parseTimeToSeconds = (timeStr) => {
10
+ export const parseTimeToSeconds = (timeStr: string): number => {
11
+ const validationError = new Error("Invalid time format");
12
+
13
+ if (!timeStr) {
14
+ throw validationError;
15
+ }
16
+
17
+ const toTime = (value: number, max: number = 59): number => {
18
+ // Validate the time value
19
+ if (Number.isNaN(value) || value < 0 || value > max) {
20
+ throw validationError;
21
+ }
22
+
23
+ return value;
24
+ };
25
+
11
26
  // Split the time string by colon
12
27
  const parts = timeStr.split(":").map((part) => parseInt(part, 10));
13
28
 
14
- let seconds = 0;
15
-
16
29
  // Calculate the seconds based on the number of parts
17
30
  if (parts.length === 2) {
18
31
  // Format is MM:SS
19
- seconds = parts[0] * 60 + parts[1];
32
+ return toTime(parts[0]) * 60 + toTime(parts[1]);
20
33
  } else if (parts.length === 3) {
21
34
  // Format is HH:MM:SS
22
- seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
35
+ return (
36
+ toTime(parts[0], 23) * 3600 + toTime(parts[1]) * 60 + toTime(parts[2])
37
+ );
23
38
  } else {
24
- throw new Error("Invalid time format");
39
+ throw validationError;
25
40
  }
26
-
27
- return seconds;
28
41
  };
29
42
 
30
43
  export const retrieveFeedUrl = (entry: ZappEntry) =>
@@ -1,4 +1,5 @@
1
1
  import * as R from "ramda";
2
+ import { BehaviorSubject } from "rxjs";
2
3
  import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
3
4
  import { createLogger, utilsLogger } from "../../logger";
4
5
  import { getBoolFromConfigValue } from "../../configurationUtils";
@@ -35,18 +36,28 @@ export class Player {
35
36
  readonly playerState: QuickBrickPlayer.PlayerState;
36
37
 
37
38
  public playerPluginId: string;
38
- public entry: ZappEntry | null;
39
+ protected _entry: ZappEntry | null;
39
40
 
40
41
  protected config?: Record<string, any>;
41
42
 
42
43
  protected startPosition?: number | null;
43
44
  readonly playerRole: PlayerRole = PlayerRole.Unspecified;
44
45
  private chaptersObserver?: OverlaysObserver | null;
46
+ private entryObservable?: BehaviorSubject<ZappEntry> | null;
45
47
 
46
48
  protected listeners: {
47
49
  [key: string]: QuickBrickPlayer.SharedPlayerCallBacks;
48
50
  } = {};
49
51
 
52
+ get entry(): ZappEntry | null {
53
+ return this._entry;
54
+ }
55
+
56
+ set entry(newEntry: ZappEntry | null) {
57
+ this._entry = newEntry;
58
+ this.entryObservable?.next(newEntry);
59
+ }
60
+
50
61
  constructor(props) {
51
62
  this.playerState = {
52
63
  // We want isLive null by default to be able not to show controls if we do not know type
@@ -80,6 +91,7 @@ export class Player {
80
91
  this.onVideoProgress = this.onVideoProgress.bind(this);
81
92
  this.onVideoLoad = this.onVideoLoad.bind(this);
82
93
  this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
94
+ this.entryObservable = null;
83
95
  }
84
96
 
85
97
  getOverlayObservable = () => {
@@ -90,6 +102,14 @@ export class Player {
90
102
  return this.chaptersObserver;
91
103
  };
92
104
 
105
+ getEntryObservable = (): BehaviorSubject<ZappEntry | null> => {
106
+ if (!this.entryObservable) {
107
+ this.entryObservable = new BehaviorSubject(this.entry);
108
+ }
109
+
110
+ return this.entryObservable;
111
+ };
112
+
93
113
  /**
94
114
  * ---------------------------------------- PLAYER SETTERS GETTERS ----------------------------------------
95
115
  * These methods let you get information about the content / ad playing
@@ -469,6 +489,8 @@ export class Player {
469
489
  this.invokeListenersUpdate({
470
490
  callbackName: SharedPlayerCallBacksKeys.OnPlayerClose,
471
491
  });
492
+
493
+ this.entryObservable?.complete();
472
494
  };
473
495
 
474
496
  onAdBegin = (event) => {
@@ -0,0 +1,56 @@
1
+ import { v4 as uuid } from "uuid";
2
+ import { RequestBuilder } from "@applicaster/zapp-pipes-v2-client";
3
+ import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
4
+ import { getAppUrlScheme } from "@applicaster/zapp-react-native-utils/appDataUtils";
5
+
6
+ let source: string | null = null;
7
+
8
+ const addSource = async () => {
9
+ if (source) {
10
+ return source;
11
+ }
12
+
13
+ const appUrlScheme = getAppUrlScheme();
14
+ const bundleId = await sessionStorage.getItem("bundleIdentifier");
15
+ const appVersion = await sessionStorage.getItem("version_name");
16
+
17
+ if (!appUrlScheme || !bundleId || !appVersion) {
18
+ throw Error(
19
+ `Error: Can not create client data addSource: Check appUrlScheme, bundleId, appversion. Item(s) not exist.
20
+ {appUrlScheme: ${appUrlScheme}, bundleId: ${bundleId}, appVersion: ${appVersion}}`
21
+ );
22
+ }
23
+
24
+ source = `${appUrlScheme}://${bundleId}/versions/${appVersion}`;
25
+
26
+ return source;
27
+ };
28
+
29
+ export const createCloudEvent = async ({ type, subject, data }) => {
30
+ return {
31
+ specversion: "1.0",
32
+ type,
33
+ datacontenttype: "application/json",
34
+ subject,
35
+ source: await addSource(),
36
+ time: new Date().toISOString(),
37
+ data,
38
+ id: uuid(),
39
+ };
40
+ };
41
+
42
+ export const sendCloudEvent = async (cloudEvent: any, url: string) => {
43
+ if (!url) {
44
+ throw new Error("Feed locator is missing");
45
+ }
46
+
47
+ const requestBuilder = new RequestBuilder();
48
+
49
+ const response = await requestBuilder
50
+ .setUrl(url)
51
+ .setMethod("POST")
52
+ .addBodyParams(cloudEvent)
53
+ .call<ZappFeed>();
54
+
55
+ return response;
56
+ };
@@ -16,6 +16,6 @@ Object {
16
16
  "styles": Object {
17
17
  "style": "foo",
18
18
  },
19
- "title": "base screen",
19
+ "title": "testTitle",
20
20
  }
21
21
  `;
@@ -1,5 +1,5 @@
1
1
  import * as R from "ramda";
2
- import * as navigationUtils from "../index";
2
+ import * as navigationUtils from "..";
3
3
  import {
4
4
  river,
5
5
  riverPlugin,
@@ -148,7 +148,9 @@ describe("getNavigationProps", () => {
148
148
  activeRiver: R.merge(baseScreen, { navigations }),
149
149
  };
150
150
 
151
- expect(getNavigationProps({ navigator, category })).toMatchSnapshot();
151
+ expect(
152
+ getNavigationProps({ navigator, title: "testTitle", category })
153
+ ).toMatchSnapshot();
152
154
  });
153
155
  });
154
156
 
@@ -95,10 +95,12 @@ export function resolveNavigationPlugin({
95
95
  * @returns {Object} props
96
96
  */
97
97
  export function getNavigationProps({
98
- navigator: { activeRiver, screenTitle: title },
98
+ navigator: { activeRiver },
99
+ title = "",
99
100
  category,
100
101
  }: {
101
102
  navigator: QuickBrickAppNavigator;
103
+ title: string;
102
104
  category: string;
103
105
  }): Nullable<
104
106
  Partial<ZappNavigation> & { selected: string; title: string; home: boolean }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "11.0.0-rc.99",
3
+ "version": "12.0.0-rc.1",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "11.0.0-rc.99",
31
+ "@applicaster/applicaster-types": "12.0.0-rc.1",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -7,6 +7,7 @@ import * as React from "react";
7
7
  import configureStore from "redux-mock-store";
8
8
  import thunk from "redux-thunk";
9
9
  import * as navigationHooks from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
10
+ import * as useNavigationHooks from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
10
11
 
11
12
  const mockStore = configureStore([thunk]);
12
13
 
@@ -35,7 +36,7 @@ jest.mock("../../logger", () => ({
35
36
  }));
36
37
 
37
38
  jest
38
- .spyOn(navigationHooks, "useNavigation")
39
+ .spyOn(useNavigationHooks, "useNavigation")
39
40
  .mockImplementation(() => navigator);
40
41
 
41
42
  jest.spyOn(navigationHooks, "useRoute").mockImplementation(() => routeData);
@@ -9,7 +9,7 @@ import {
9
9
  getInflatedDataSourceUrl,
10
10
  getSearchContext,
11
11
  } from "@applicaster/zapp-react-native-utils/reactHooks";
12
- import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
12
+ import { useScreenContext } from "../screen/useScreenContext";
13
13
 
14
14
  type Options = {
15
15
  initialBatchSize?: number;
@@ -2,7 +2,7 @@ import * as R from "ramda";
2
2
  import { StatusBar, Platform } from "react-native";
3
3
 
4
4
  import { platformSelect, isTV } from "../../reactUtils";
5
- import { useNavigation } from "../navigation";
5
+ import { useIsNavBarVisible } from "../navigation";
6
6
  import { useSafeAreaInsets } from "react-native-safe-area-context";
7
7
  import { isTablet } from "./isTablet";
8
8
 
@@ -48,9 +48,13 @@ export function useStatusBarHeight() {
48
48
  });
49
49
  }
50
50
 
51
+ const getNavbarHeight = () => {
52
+ return isTV() ? 60 : platformSelect({ ios: 44, android: 56 });
53
+ };
54
+
51
55
  export function useCurrentNavbarHeight() {
52
- const { isNavBarVisible } = useNavigation();
53
- const navbarHeight = isTV() ? 60 : platformSelect({ ios: 44, android: 56 });
56
+ const isNavBarVisible = useIsNavBarVisible();
57
+ const navbarHeight = getNavbarHeight();
54
58
 
55
59
  return isNavBarVisible ? navbarHeight : 0;
56
60
  }
@@ -16,6 +16,7 @@ export const mockMenu: ZappNavigation = {
16
16
  };
17
17
 
18
18
  export const useNavigation = jest.fn(() => ({
19
+ data: {},
19
20
  currentRoute: "someRoute",
20
21
  }));
21
22
 
@@ -9,46 +9,30 @@ import {
9
9
  useContentTypes,
10
10
  usePickFromState,
11
11
  } from "@applicaster/zapp-react-native-redux/hooks";
12
- import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
13
12
  import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
14
13
 
15
14
  import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
16
15
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
17
16
  import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
18
- import { isNavBarVisible as shouldShowNavBar } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
19
17
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
20
18
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
21
19
  import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
22
- import { routeIsPlayerScreen } from "@applicaster/zapp-react-native-utils/navigationUtils";
23
- import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
20
+ import { getNavigationProps } from "@applicaster/zapp-react-native-utils/navigationUtils";
24
21
  import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
25
22
  import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
26
23
  import { platformSelect } from "../../reactUtils";
27
24
  import { useConnectionInfo } from "../connection";
28
25
  import { getTargetScreenData } from "../screen/useTargetScreenData";
26
+ import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
29
27
 
30
- const { useContext } = React;
28
+ import { useNavigation } from "./useNavigation";
29
+
30
+ export { useNavigation } from "./useNavigation";
31
31
 
32
32
  const { ProfilerBridge } = NativeModules;
33
33
 
34
34
  export const usePathname = () => React.useContext(PathnameContext);
35
35
 
36
- /**
37
- * Return NavigationContext
38
- */
39
- export const useNavigation = (
40
- memoized: boolean = false
41
- ): QuickBrickAppNavigator => {
42
- const navigator = useContext(NavigationContext);
43
- const memoizedNavigator = useMemo(() => navigator, []);
44
-
45
- if (memoized) {
46
- return memoizedNavigator;
47
- }
48
-
49
- return navigator;
50
- };
51
-
52
36
  export const useBackHandler = (cb: () => boolean) => {
53
37
  React.useEffect(() => {
54
38
  BackHandler.addEventListener("hardwareBackPress", cb);
@@ -59,25 +43,6 @@ export const useBackHandler = (cb: () => boolean) => {
59
43
  }, [cb]);
60
44
  };
61
45
 
62
- export const useNavBarStateForScreen = (pathname: string) => {
63
- const navigator = useNavigation();
64
-
65
- const [isNavBarVisible, setIsNavBarVisible] = React.useState(
66
- navigator.isNavBarVisible
67
- );
68
-
69
- React.useEffect(() => {
70
- if (
71
- navigator.currentRoute === pathname &&
72
- isNavBarVisible !== navigator.isNavBarVisible
73
- ) {
74
- setIsNavBarVisible(navigator.isNavBarVisible);
75
- }
76
- }, [pathname, navigator.isNavBarVisible]);
77
-
78
- return { isNavBarVisible, canGoBack: navigator.canGoBack };
79
- };
80
-
81
46
  type Callbacks = Partial<{
82
47
  handleHookPresent: ({
83
48
  route,
@@ -204,12 +169,13 @@ export enum MenuTypes {
204
169
  }
205
170
 
206
171
  export const useNavigationPluginData = (): ZappNavigation | undefined => {
207
- const { screenData } = useRoute(); // was replaced legacyScreenData with useRoute screenData
172
+ const {
173
+ screenData: useRouteScreenData,
174
+ }: { screenData: QuickBrickNavigationData | null } = useRoute();
208
175
 
209
- const navigations =
210
- screenData && "screen" in screenData
211
- ? (screenData.screen as ZappRiver).navigations
212
- : (screenData as ZappRiver)?.navigations ?? [];
176
+ const navigations = useRouteScreenData?.targetScreen
177
+ ? (useRouteScreenData.targetScreen as ZappRiver).navigations
178
+ : (useRouteScreenData as ZappRiver).navigations;
213
179
 
214
180
  const navigationMenu = navigations?.find((nav) => nav.category === "menu");
215
181
 
@@ -261,23 +227,15 @@ export const useRunIfLongPress = (
261
227
  return { onPress, onPressOut };
262
228
  };
263
229
 
230
+ /**
231
+ * @returns boolean - navbar visiblity status
232
+ */
264
233
  export const useIsNavBarVisible = (): boolean => {
265
- const { isNavBarVisible, currentRoute, canGoBack } = useNavigation();
266
- const screenData = useCurrentScreenData();
267
-
268
- const navBarVisible = React.useMemo(() => {
269
- return shouldShowNavBar(
270
- currentRoute,
271
- screenData,
272
- isNavBarVisible && !routeIsPlayerScreen(currentRoute),
273
- canGoBack()
274
- );
275
- }, [isNavBarVisible, currentRoute, screenData?.id]);
234
+ const visible = useNavbarState().visible;
276
235
 
277
- return navBarVisible;
236
+ return useMemo(() => visible, [visible]);
278
237
  };
279
238
 
280
- // starts with modal/
281
239
  const isModalPathname = (pathname: string) => /^modal\//.test(pathname);
282
240
 
283
241
  const isVideoModalPathname = (pathname: string) =>
@@ -320,8 +278,11 @@ export const useRoute = () => {
320
278
 
321
279
  const currentRouteScreenData = legacyScreenData(
322
280
  {
323
- screen: currentStack?.state?.screen ?? navigator.data?.screen ?? {},
324
- entry: currentStack?.state?.entry ?? {},
281
+ screen:
282
+ currentStack?.state?.screen ??
283
+ navigator.data?.screen ??
284
+ ({} as ZappRiver),
285
+ entry: currentStack?.state?.entry ?? ({} as ZappEntry),
325
286
  },
326
287
 
327
288
  plugins
@@ -346,6 +307,14 @@ export const useRoute = () => {
346
307
 
347
308
  export const useGetBottomTabBarHeight = (): number => {
348
309
  const insets = useSafeAreaInsets();
310
+ const navigator = useNavigation();
311
+
312
+ const navProps = getNavigationProps({
313
+ navigator,
314
+ title: "",
315
+ category: "menu",
316
+ });
317
+
349
318
  const navType = useNavigationType();
350
319
  const isBottomBarNavigation = navType === MenuTypes.bottomTabBar;
351
320
 
@@ -354,8 +323,10 @@ export const useGetBottomTabBarHeight = (): number => {
354
323
 
355
324
  if (!isBottomBarNavigation) return 0;
356
325
 
357
- return platformSelect({
326
+ const tabBarHeight = platformSelect({
358
327
  ios: TAB_BAR_HEIGHT_IOS + insets.bottom,
359
328
  android: TAB_BAR_HEIGHT_ANDROID,
360
- });
329
+ }) as number;
330
+
331
+ return tabBarHeight + Number(navProps?.styles?.top_border_width ?? 0);
361
332
  };
@@ -0,0 +1,21 @@
1
+ /* eslint max-len: off */
2
+
3
+ import { useMemo, useContext } from "react";
4
+
5
+ import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
6
+
7
+ /**
8
+ * Return NavigationContext
9
+ */
10
+ export const useNavigation = (
11
+ memoized: boolean = false
12
+ ): QuickBrickAppNavigator => {
13
+ const navigator = useContext(NavigationContext);
14
+ const memoizedNavigator = useMemo(() => navigator, []);
15
+
16
+ if (memoized) {
17
+ return memoizedNavigator;
18
+ }
19
+
20
+ return navigator;
21
+ };
@@ -2,4 +2,9 @@ export { useCurrentScreenData } from "./useCurrentScreenData";
2
2
 
3
3
  export { useTargetScreenData } from "./useTargetScreenData";
4
4
 
5
- export { useScreenContext } from "./useScreenContext";
5
+ export {
6
+ useScreenContext,
7
+ useLegacyScreenData,
8
+ useNavbarState,
9
+ useSetNavbarState,
10
+ } from "./useScreenContext";
@@ -1,10 +1,16 @@
1
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
1
+ import { useRivers } from "../state";
2
2
  import { useScreenContext } from "./useScreenContext";
3
3
 
4
+ /**
5
+ * used to get the screen configuration from the layout.json
6
+ * when screenIdOverride passed. It will return the screen configuration from the useScreenContext() by default
7
+ * @param screenIdOverride - screen id to get the screen configuration
8
+ * @returns ZappRiver - screen configuration
9
+ * */
4
10
  export const useCurrentScreenData = (
5
11
  screenIdOverride: string | null = null
6
12
  ): ZappRiver => {
7
- const { rivers } = usePickFromState(["rivers"]);
13
+ const rivers = useRivers();
8
14
  const screenData = useScreenContext();
9
15
 
10
16
  if (screenIdOverride) {
@@ -1,8 +1,46 @@
1
- import { useMemo } from "react";
1
+ import { useContext, useMemo } from "react";
2
2
 
3
3
  import { useModalNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ModalNavigationContext";
4
4
  import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
5
- import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
+ import { useNavigation } from "../navigation";
6
+
7
+ import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
8
+
9
+ function useScreenContextV2() {
10
+ return useContext(ScreenContext);
11
+ }
12
+
13
+ /**
14
+ * Equivalent to existing useNavigation().screenData
15
+ * @returns legacyFormatScreenData - screen configuration
16
+ */
17
+ export function useLegacyScreenData() {
18
+ const { legacyFormatScreenData } = useScreenContextV2();
19
+
20
+ return useMemo(() => legacyFormatScreenData, [legacyFormatScreenData]);
21
+ }
22
+
23
+ export function useNavbarState() {
24
+ const {
25
+ navBar: { title, summary, visible },
26
+ } = useScreenContextV2();
27
+
28
+ return useMemo(
29
+ () => ({ title, summary, visible }),
30
+ [title, summary, visible]
31
+ );
32
+ }
33
+
34
+ export function useSetNavbarState() {
35
+ const {
36
+ navBar: { setSummary, setTitle, setVisible },
37
+ } = useScreenContextV2();
38
+
39
+ return useMemo(
40
+ () => ({ setSummary, setTitle, setVisible }),
41
+ [setSummary, setTitle, setVisible]
42
+ );
43
+ }
6
44
 
7
45
  export function useScreenContext(): NavigationScreenData {
8
46
  const navigator = useNavigation();
@@ -9,6 +9,8 @@ export const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
9
9
 
10
10
  export const isNotEmpty = R.complement(R.isEmpty);
11
11
 
12
+ export const withoutNil = R.reject(R.isNil);
13
+
12
14
  /**
13
15
  * tells wether a React Component is a class component or not
14
16
  * @param {ReactComponent} Component to test
@@ -0,0 +1,87 @@
1
+ import * as R from "ramda";
2
+
3
+ import React from "react";
4
+ import { View } from "react-native";
5
+
6
+ import { Provider } from "react-redux";
7
+ import thunk from "redux-thunk";
8
+ import configureStore from "redux-mock-store";
9
+
10
+ import { render } from "@testing-library/react-native";
11
+
12
+ /**
13
+ * Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
14
+ * Example:
15
+ * wrapper.findWhere(testIDEquals('yourID'))
16
+ * @param {*} testId
17
+ * @return boolean
18
+ */
19
+ export const testIDEquals = (testId: any): boolean =>
20
+ R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
21
+
22
+ export const getByTestId = (wrapper, testId) => {
23
+ try {
24
+ return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
25
+ } catch (e) {
26
+ // eslint-disable-next-line no-console
27
+ console.log("getByTestId E:", e);
28
+
29
+ return undefined;
30
+ }
31
+ };
32
+
33
+ /** Helper function for react-test-renderer, accepts type and renderer
34
+ * * @param {*} type React Component type instance or name of component
35
+ * * @param {*} renderer renderer instance
36
+ */
37
+ export const findByType = (type, renderer) => {
38
+ try {
39
+ renderer.root ? renderer.root.findByType(type) : renderer.findByType(type);
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ };
44
+
45
+ export function createComponentMock(mockName, hasChildren = false) {
46
+ return hasChildren
47
+ ? jest.fn(({ children }) => (
48
+ <View comp={mockName} testID={mockName}>
49
+ {typeof children === "function" ? children() : children}
50
+ </View>
51
+ ))
52
+ : jest.fn(() => <View comp={mockName} testID={mockName} />);
53
+ }
54
+
55
+ const initialState = {
56
+ styles: {},
57
+ remoteConfigurations: {
58
+ styles: {},
59
+ },
60
+ components: {
61
+ River: createComponentMock("river"),
62
+ AppContainer: createComponentMock("appContainer", true),
63
+ DisplayError: createComponentMock("displayError"),
64
+ },
65
+ rivers: [{ id: "A1234", home: true }],
66
+ appSettings: {},
67
+ plugins: [],
68
+ appState: { appReady: true },
69
+ };
70
+
71
+ export const WrappedWithProviders = ({ children, store = {} }: any) => {
72
+ const _store = configureStore([thunk])({ ...initialState, store });
73
+
74
+ return <Provider store={_store}>{children}</Provider>;
75
+ };
76
+
77
+ /**
78
+ * Helper test function - replacement of render() - with providers
79
+ * @param component component to render in test
80
+ * @param store optional store to pass to the provider
81
+ * @returns
82
+ */
83
+ export const renderWithProviders = (component, store?: unknown) => {
84
+ return render(
85
+ <WrappedWithProviders store={store}>{component}</WrappedWithProviders>
86
+ );
87
+ };
package/theme/index.ts CHANGED
@@ -48,11 +48,12 @@ export function useThemeSelector(): [string, (string) => void] {
48
48
  * @return {Theme}
49
49
  */
50
50
  export function createTheme(options) {
51
- const { id, props } = options;
51
+ const { id, props, decorateFeed } = options;
52
52
 
53
53
  return {
54
54
  isTheme: true,
55
55
  id,
56
56
  getProps: populateConfigurationValues(props),
57
+ decorateFeed,
57
58
  };
58
59
  }
@@ -1,34 +0,0 @@
1
- import * as R from "ramda";
2
-
3
- /**
4
- * Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
5
- * Example:
6
- * wrapper.findWhere(testIDEquals('yourID'))
7
- * @param {*} testId
8
- * @return boolean
9
- */
10
- export const testIDEquals = (testId: any): boolean =>
11
- R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
12
-
13
- export const getByTestId = (wrapper, testId) => {
14
- try {
15
- return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
16
- } catch (e) {
17
- // eslint-disable-next-line no-console
18
- console.log("getByTestId E:", e);
19
-
20
- return undefined;
21
- }
22
- };
23
-
24
- /** Helper function for react-test-renderer, accepts type and renderer
25
- * * @param {*} type React Component type instance or name of component
26
- * * @param {*} renderer renderer instance
27
- */
28
- export const findByType = (type, renderer) => {
29
- try {
30
- renderer.root ? renderer.root.findByType(type) : renderer.findByType(type);
31
- } catch {
32
- return undefined;
33
- }
34
- };