@applicaster/zapp-react-native-utils 13.0.0-alpha.5428512514 → 13.0.0-alpha.5855333506

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.
@@ -17,9 +17,10 @@ class ActionExecutor {
17
17
 
18
18
  handleAction = async (
19
19
  action: ActionType,
20
- entry: ZappEntry = null
20
+ context?: Record<string, any>
21
21
  ): Promise<ActionResult> => {
22
22
  const handler = this.registeredActions[action.type];
23
+ const entry = context?.entry as ZappEntry;
23
24
 
24
25
  if (!handler) {
25
26
  log_error(
@@ -32,17 +33,17 @@ class ActionExecutor {
32
33
 
33
34
  log_debug(
34
35
  `handleAction: ${action.type}${
35
- entry ? `, entry id ${entry.id}, title: ${entry.title}` : ""
36
+ entry ? `, entry id ${entry?.id}, title: ${entry?.title}` : ""
36
37
  }`,
37
38
  { action, entry }
38
39
  );
39
40
 
40
41
  try {
41
- return handler(action, entry);
42
+ return handler(action, context);
42
43
  } catch (error) {
43
44
  log_error(
44
45
  `handleAction: error handling action type: ${action.type}, message: ${error.message}`,
45
- { action, entry, error }
46
+ { action, context, error }
46
47
  );
47
48
 
48
49
  throw error;
@@ -51,11 +52,11 @@ class ActionExecutor {
51
52
 
52
53
  handleActions = async (
53
54
  actionTypes: ActionType[],
54
- entry: ZappEntry = null
55
+ context?: Record<string, any>
55
56
  ): Promise<ActionResult> => {
56
57
  for (const action of actionTypes) {
57
58
  // TODO: Handle action result error
58
- const result = await this.handleAction(action, entry);
59
+ const result = await this.handleAction(action, context);
59
60
 
60
61
  if (result === ActionResult.Cancel) {
61
62
  log_info(`handleActions: action: ${action.type} canceled`);
@@ -67,7 +68,10 @@ class ActionExecutor {
67
68
  return ActionResult.Success;
68
69
  };
69
70
 
70
- handleEntryActions = async (entry: ZappEntry): Promise<ActionResult> => {
71
+ handleEntryActions = async (
72
+ entry: ZappEntry,
73
+ context?: Record<string, any>
74
+ ): Promise<ActionResult> => {
71
75
  const tapActions = entry.extensions?.tap_actions?.actions;
72
76
 
73
77
  if (!tapActions) {
@@ -77,7 +81,7 @@ class ActionExecutor {
77
81
  if (!Array.isArray(tapActions)) {
78
82
  log_error(
79
83
  `handleEntryActions: tapActions is not an array for id: ${entry.id} title: ${entry.title}`,
80
- { entry }
84
+ { context, entry }
81
85
  );
82
86
 
83
87
  return;
@@ -87,7 +91,7 @@ class ActionExecutor {
87
91
  return;
88
92
  }
89
93
 
90
- await this.handleActions(tapActions, entry);
94
+ await this.handleActions(tapActions, { ...context, entry });
91
95
  };
92
96
 
93
97
  unregisterAction = (actionType: string) => {
@@ -13,6 +13,8 @@ import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
13
13
  import { createLogger } from "../logger";
14
14
  import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
15
15
  import { ACTIVE_LAYOUT_ID_STORAGE_KEY } from "@applicaster/quick-brick-core/App/remoteContextReloader/consts";
16
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
17
+ import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
16
18
  import {
17
19
  EntryResolver,
18
20
  resolveObjectValues,
@@ -23,18 +25,28 @@ const { log_error, log_info } = createLogger({
23
25
  category: "General",
24
26
  });
25
27
 
28
+ // TODO: move entry inside options and rename options to context
26
29
  type ActionExecutorContextType = {
27
30
  registerAction: (
28
31
  type: string,
29
- handler: (action: ActionType, entry: ZappEntry) => Promise<ActionResult>
32
+ handler: (
33
+ action: ActionType,
34
+ context?: Record<string, any>
35
+ ) => Promise<ActionResult>
30
36
  ) => void;
31
37
  unregisterAction: (type: string) => void;
32
- handleAction: (action: ActionType, entry: ZappEntry) => Promise<ActionResult>;
38
+ handleAction: (
39
+ action: ActionType,
40
+ context?: Record<string, any>
41
+ ) => Promise<ActionResult>;
33
42
  handleActions: (
34
43
  actions: ActionType[],
35
- entry: ZappEntry
44
+ context: Record<string, any>
45
+ ) => Promise<ActionResult>;
46
+ handleEntryActions: (
47
+ entry: ZappEntry,
48
+ context?: Record<string, any>
36
49
  ) => Promise<ActionResult>;
37
- handleEntryActions: (entry: ZappEntry) => Promise<ActionResult>;
38
50
  };
39
51
  type Props = {
40
52
  children: React.ReactNode;
@@ -49,6 +61,31 @@ const prepareDefaultActions = (actionExecutor) => {
49
61
  return ActionResult.Success;
50
62
  });
51
63
 
64
+ actionExecutor.registerAction(
65
+ "refreshComponent",
66
+ async (_action, context) => {
67
+ const dispatch = appStore.getDispatch();
68
+
69
+ log_info("handleAction: refreshComponent event");
70
+
71
+ const dataSource = context?.component?.data?.source;
72
+
73
+ // TODO: Later we can pass component ids to do refresh instead of current one
74
+ // TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
75
+ // TODO: handle focused item removal
76
+ dispatch(
77
+ loadPipesData(dataSource, {
78
+ silentRefresh: false,
79
+ clearCache: true,
80
+ callback: (_data) => {},
81
+ riverId: context?.screenData?.id,
82
+ })
83
+ );
84
+
85
+ return ActionResult.Success;
86
+ }
87
+ );
88
+
52
89
  actionExecutor.registerAction("switchLayout", async (action) => {
53
90
  log_info("handleAction: switchLayout event");
54
91
 
@@ -84,10 +121,11 @@ const prepareDefaultActions = (actionExecutor) => {
84
121
  return confirmationPromise;
85
122
  });
86
123
 
87
- actionExecutor.registerAction("sendCloudEvent", async (action, entry) => {
124
+ actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
88
125
  try {
89
126
  const options = action.options;
90
127
 
128
+ const entry = context?.entry || {};
91
129
  const entryResolver = new EntryResolver(entry);
92
130
 
93
131
  const data =
@@ -4,6 +4,8 @@ import { HOOKS_EVENTS } from "./constants";
4
4
  export interface HookConfiguration {}
5
5
 
6
6
  /* existing Hook Plugn props: sample */
7
+
8
+ /*
7
9
  interface _HookModulePlugin {
8
10
  Component:
9
11
  | React.FC<HookPluginProps>
@@ -23,6 +25,7 @@ interface _HookModulePlugin {
23
25
  presentUI: (payload?: HookPluginProps["payload"]) => void
24
26
  ) => any;
25
27
  }
28
+ */
26
29
 
27
30
  //* Derived from usage */
28
31
  export interface HookModule {
@@ -6,6 +6,9 @@ import { AccessibilityRole } from "react-native";
6
6
 
7
7
  export class AccessibilityManager {
8
8
  private static _instance: AccessibilityManager | null = null;
9
+ private headingTimeout: NodeJS.Timeout | null = null;
10
+ private WORDS_PER_MINUTE = 160;
11
+ private MINIMUM_PAUSE = 500;
9
12
  private state$ = new BehaviorSubject<AccessibilityState>({
10
13
  screenReaderEnabled: false,
11
14
  reduceMotionEnabled: false,
@@ -20,6 +23,7 @@ export class AccessibilityManager {
20
23
 
21
24
  private ttsManager = TTSManager.getInstance();
22
25
  private localizations: { [key: string]: string } = {};
26
+ private headingQueue: string[] = [];
23
27
 
24
28
  private constructor() {}
25
29
 
@@ -52,6 +56,28 @@ export class AccessibilityManager {
52
56
  return this.state$.asObservable();
53
57
  }
54
58
 
59
+ /** Calculates the reading time for a given text
60
+ * This method is a bit of a hack because we don't have a callback, or promise from VIZIO API
61
+ * @param text - The text to calculate the reading time for
62
+ * @returns The reading time in milliseconds
63
+ */
64
+ private calculateReadingTime(text: string): number {
65
+ const words = text.trim().split(/\s+/).length;
66
+
67
+ return Math.max(
68
+ this.MINIMUM_PAUSE,
69
+ (words / this.WORDS_PER_MINUTE) * 60 * 1000
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Adds a heading to the queue, headings will be read before the next text
75
+ * Each heading will be read once and removed from the queue
76
+ */
77
+ public addHeading(heading: string) {
78
+ this.headingQueue.push(heading);
79
+ }
80
+
55
81
  /**
56
82
  * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
57
83
  * keyOfLocalizedText is the key to the localized text
@@ -85,7 +111,22 @@ export class AccessibilityManager {
85
111
  textToRead = localizedMessage;
86
112
  }
87
113
 
88
- this.ttsManager && this.ttsManager.readText(textToRead);
114
+ if (this.headingQueue.length > 0) {
115
+ const heading = this.headingQueue.shift()!;
116
+ this.ttsManager?.readText(heading);
117
+
118
+ if (this.headingTimeout) {
119
+ clearTimeout(this.headingTimeout);
120
+ }
121
+
122
+ const pauseTime = this.calculateReadingTime(heading);
123
+
124
+ this.headingTimeout = setTimeout(() => {
125
+ this.ttsManager?.readText(textToRead);
126
+ }, pauseTime);
127
+ } else {
128
+ this.ttsManager?.readText(textToRead);
129
+ }
89
130
 
90
131
  this.announcements$.next({
91
132
  message: textToRead,
@@ -1,8 +1,8 @@
1
1
  import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
- import { find, last, startsWith, pathOr } from "ramda";
2
+ import { find, last, pathOr, startsWith } from "ramda";
3
3
  import {
4
- QUICK_BRICK_NAVBAR,
5
4
  QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
6
  } from "@applicaster/quick-brick-core/const";
7
7
 
8
8
  // run check each 300 ms
@@ -66,7 +66,8 @@ export const getContentNode = (focusableTree) => {
66
66
  const content = find(
67
67
  (child) =>
68
68
  startsWith(QUICK_BRICK_CONTENT, child.id) ||
69
- startsWith("player", child.id),
69
+ startsWith("player", child.id) ||
70
+ startsWith("/hooks", child?.parent?.id), // For startup hooks case
70
71
  getActiveScreenChildren(focusableTree)
71
72
  );
72
73
 
@@ -23,3 +23,17 @@ export const PLATFORMS = {
23
23
  export const PLATFORM_KEYS = {
24
24
  deviceClosedCaptioningEnabled: "device_closed_captioning_enabled",
25
25
  };
26
+
27
+ export enum ZappPlatform {
28
+ Android = "android",
29
+ AndroidTV = "android_tv",
30
+ AmazonFireTV = "amazon",
31
+ iOS = "ios",
32
+ tvOS = "tvos",
33
+ Roku = "roku",
34
+ Tizen = "tizen",
35
+ Web = "web",
36
+ WebOS = "webos",
37
+ Vizio = "vizio",
38
+ Unknown = "unknown",
39
+ }
@@ -6,8 +6,10 @@ import {
6
6
  isSamsungPlatform,
7
7
  } from "@applicaster/zapp-react-dom-app/App/Loader/utils/platform";
8
8
 
9
- import { PLATFORM_KEYS, PLATFORMS } from "./const";
9
+ import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
10
10
  import { createLogger, utilsLogger } from "../../logger";
11
+ import { getPlatform } from "../../reactUtils";
12
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
11
13
 
12
14
  const { log_debug } = createLogger({
13
15
  category: "General",
@@ -15,6 +17,20 @@ const { log_debug } = createLogger({
15
17
  parent: utilsLogger,
16
18
  });
17
19
 
20
+ export const getZappPlatform = (): ZappPlatform => {
21
+ const platform = appStore.get("appData")?.platform;
22
+
23
+ switch (platform) {
24
+ case "amazon_fire_tv":
25
+ return ZappPlatform.AmazonFireTV;
26
+ case ZappPlatform.Vizio:
27
+ return ZappPlatform.Vizio;
28
+ default:
29
+ // TODO: Use only ZappPlatform enum
30
+ return getPlatform() as ZappPlatform;
31
+ }
32
+ };
33
+
18
34
  export const getUserAgent = () => window?.navigator?.userAgent;
19
35
 
20
36
  /** VIZIO UTILS */
package/logger/const.ts CHANGED
@@ -1,6 +1,3 @@
1
- /* eslint-disable unused-imports/no-unused-vars */
2
- import * as R from "ramda";
3
-
4
1
  enum XRayLogLevel {
5
2
  verbose = 0,
6
3
  debug = 1,
@@ -11,51 +8,9 @@ enum XRayLogLevel {
11
8
  off = 100,
12
9
  }
13
10
 
14
- enum ToastLogLevel {
15
- off = "off",
16
- verbose = "verbose",
17
- debug = "debug",
18
- info = "info",
19
- warning = "warning",
20
- error = "error",
21
- }
22
-
23
- const toastLevelToLogLevel = (toastLogLevel: ToastLogLevel): XRayLogLevel => {
24
- if (R.isNil(toastLogLevel)) {
25
- return XRayLogLevel.off;
26
- }
27
-
28
- switch (toastLogLevel) {
29
- case ToastLogLevel.verbose:
30
- return XRayLogLevel.verbose;
31
- case ToastLogLevel.debug:
32
- return XRayLogLevel.debug;
33
- case ToastLogLevel.info:
34
- return XRayLogLevel.info;
35
- case ToastLogLevel.error:
36
- return XRayLogLevel.error;
37
- default:
38
- return XRayLogLevel.off;
39
- }
40
- };
41
-
42
- const PLUGIN_IDENTIFIER = "xray_logging_plugin";
43
-
44
- const wrapInObject = (obj, name) => {};
45
-
46
- const applyConditions = (wrappedObj) => {};
47
-
48
- const sanitizeEventPayload = () => {};
11
+ const wrapInObject = (_ctx: unknown, _key: string) => {};
49
12
 
50
- const __isRunningRepoTests = () => {};
51
-
52
- const isWeb = () => {};
53
-
54
- const isDOMApp = () => {};
55
-
56
- const isBrowser = () => {};
57
-
58
- const eventAllowed = () => {};
13
+ const applyConditions = (_fn: unknown) => {};
59
14
 
60
15
  /**
61
16
  * This is the mocked version of XRayLogger
@@ -90,34 +45,21 @@ export default class XRayLogger {
90
45
  );
91
46
  };
92
47
 
93
- log = (event: string | XRayEventData) => {
94
- const logEvent = typeof event === "string" ? { message: event } : event;
95
- };
48
+ log = (_event) => {};
96
49
 
97
- debug = (event: string | XRayEventData) => {
98
- const logEvent = typeof event === "string" ? { message: event } : event;
99
- };
50
+ debug = (_event) => {};
100
51
 
101
- info = (event: string | XRayEventData) => {
102
- const logEvent = typeof event === "string" ? { message: event } : event;
103
- };
52
+ info = (_event) => {};
104
53
 
105
- warning = (event: string | XRayEventData) => {
106
- const logEvent = typeof event === "string" ? { message: event } : event;
107
- };
54
+ warning = (_event) => {};
108
55
 
109
- warn = (event: string | XRayEventData) => {
56
+ warn = (_event) => {
110
57
  this.warning(event);
111
58
  };
112
59
 
113
- error = (event: Error | XRayEventData) => {
114
- const logEvent: XRayEventData =
115
- event instanceof Error
116
- ? { exception: event as Error, message: event.message }
117
- : (event as XRayEventData);
118
- };
60
+ error = (_event) => {};
119
61
 
120
- sendEvent = (event, eventData) => {};
62
+ sendEvent = (_event, _eventData) => {};
121
63
 
122
64
  // To be used by QuickBrick Hook events
123
65
  sendAudienceEvent = (event, eventData = null) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.0-alpha.5428512514",
3
+ "version": "13.0.0-alpha.5855333506",
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": "13.0.0-alpha.5428512514",
31
+ "@applicaster/applicaster-types": "13.0.0-alpha.5855333506",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -25,6 +25,11 @@ jest.mock("@applicaster/zapp-react-native-utils/analyticsUtils/", () => ({
25
25
  }),
26
26
  }));
27
27
 
28
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
29
+ useTargetScreenData: jest.fn(() => ({})),
30
+ useCurrentScreenData: jest.fn(() => ({})),
31
+ }));
32
+
28
33
  jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
29
34
  ...jest.requireActual(
30
35
  "@applicaster/zapp-react-native-ui-components/Contexts"
@@ -16,6 +16,7 @@ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/acti
16
16
  import { isFunction, noop } from "../../functionUtils";
17
17
  import { useSendAnalyticsOnPress } from "../analytics";
18
18
  import { logOnPress, warnEmptyContentType } from "./helpers";
19
+ import { useCurrentScreenData } from "../screen";
19
20
 
20
21
  /**
21
22
  * If onCellTap is defined execute the function and
@@ -44,6 +45,7 @@ export const useCellClick = ({
44
45
 
45
46
  const onCellTap: Option<Function> = React.useContext(CellTapContext);
46
47
  const actionExecutor = React.useContext(ActionExecutorContext);
48
+ const screenData = useCurrentScreenData();
47
49
 
48
50
  const cellSelectable = toBooleanWithDefaultTrue(
49
51
  component?.rules?.component_cells_selectable
@@ -78,7 +80,10 @@ export const useCellClick = ({
78
80
  logOnPress(selectedItem, pathname, component, onCellTap);
79
81
 
80
82
  if (selectedItem) {
81
- await actionExecutor?.handleEntryActions(selectedItem);
83
+ await actionExecutor?.handleEntryActions(selectedItem, {
84
+ component,
85
+ screenData,
86
+ });
82
87
  }
83
88
 
84
89
  if (selectedItem?.type?.value === "action") {
@@ -111,6 +116,7 @@ export const useCellClick = ({
111
116
  pathname,
112
117
  push,
113
118
  sendAnalyticsOnPress,
119
+ screenData,
114
120
  ]
115
121
  );
116
122
 
@@ -87,7 +87,7 @@ export const useFeedLoader = ({
87
87
  );
88
88
  }
89
89
  }
90
- }, [callableFeedUrl, currentFeed?.data]);
90
+ }, [callableFeedUrl, currentFeed?.data?.next]);
91
91
 
92
92
  useEffect(() => {
93
93
  if (
package/time/Timer.ts CHANGED
@@ -4,7 +4,7 @@ export class Timer {
4
4
  private timeoutId: NodeJS.Timeout | null;
5
5
 
6
6
  constructor(private callback: () => void, private duration: number) {
7
- this.remaining = duration;
7
+ this.remaining = this.duration;
8
8
  this.timeoutId = null;
9
9
  }
10
10