@applicaster/zapp-react-native-utils 13.0.0-alpha.6714678893 → 13.0.0-alpha.6944415045

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.
Files changed (75) hide show
  1. package/actionsExecutor/ActionExecutor.ts +16 -9
  2. package/actionsExecutor/ActionExecutorContext.tsx +59 -8
  3. package/alertUtils/alert.ts +148 -0
  4. package/alertUtils/index.ts +1 -148
  5. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  6. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  7. package/analyticsUtils/helpers/hooks.ts +1 -1
  8. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +7 -7
  9. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +186 -186
  10. package/appUtils/HooksManager/types.ts +3 -0
  11. package/appUtils/accessibilityManager/index.ts +42 -1
  12. package/appUtils/contextKeysManager/contextResolver.ts +73 -0
  13. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +6 -6
  14. package/appUtils/focusManager/index.ts +90 -63
  15. package/appUtils/focusManager/treeDataStructure/Tree/index.js +23 -0
  16. package/appUtils/focusManager/utils/enums.ts +4 -0
  17. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  18. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  19. package/appUtils/keyCodes/__tests__/keyCodes.test.js +1 -1
  20. package/appUtils/keyCodes/keys/keysByPlatform.ts +4 -0
  21. package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
  22. package/appUtils/orientationHelper.ts +1 -1
  23. package/appUtils/platform/const.ts +14 -0
  24. package/appUtils/platform/platformUtils.ts +17 -1
  25. package/appUtils/playerManager/player.ts +17 -5
  26. package/appUtils/playerManager/playerNative.ts +3 -2
  27. package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -3
  28. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  29. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  30. package/logger/const.ts +9 -67
  31. package/manifestUtils/__tests__/createConfig.test.js +18 -16
  32. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
  33. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  34. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  35. package/package.json +3 -2
  36. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  37. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -1
  38. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  39. package/reactHooks/cell-click/index.ts +7 -1
  40. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  41. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  42. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  43. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  44. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  45. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  46. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  47. package/reactHooks/navigation/__tests__/index.test.tsx +0 -4
  48. package/reactHooks/navigation/index.ts +22 -214
  49. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
  50. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  51. package/reactHooks/navigation/useGetTabBarHeight.ts +12 -0
  52. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  53. package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
  54. package/reactHooks/navigation/useNavigationType.ts +15 -0
  55. package/reactHooks/navigation/usePathname.ts +4 -0
  56. package/reactHooks/navigation/useRoute.ts +100 -0
  57. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  58. package/reactHooks/player/TVSeekControlller/ISeekController.ts +32 -0
  59. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +71 -0
  60. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +258 -0
  61. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +53 -0
  62. package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +2 -3
  63. package/reactHooks/player/const.ts +0 -4
  64. package/reactHooks/player/index.ts +0 -6
  65. package/reactUtils/__tests__/reactUtils.test.js +10 -28
  66. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +19 -19
  67. package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
  68. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -3
  69. package/reactUtils/createContext/__tests__/helpers.test.js +8 -8
  70. package/theme/__mocks__/index.ts +4 -0
  71. package/time/Timer.ts +1 -1
  72. package/reactHooks/player/useAutoSeek.tsx +0 -87
  73. package/reactHooks/player/useSeek.tsx +0 -66
  74. package/reactHooks/player/useTapSeek.tsx +0 -58
  75. /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.ts} +0 -0
@@ -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,
@@ -0,0 +1,73 @@
1
+ import { createLogger } from "../../logger";
2
+ import { ContextKeysManager } from "./index";
3
+ import * as R from "ramda";
4
+
5
+ const { log_error } = createLogger({
6
+ subsystem: "contextResolver",
7
+ category: "General",
8
+ });
9
+
10
+ interface IResolver {
11
+ resolve: (string) => Promise<string | number | object>;
12
+ }
13
+
14
+ export class EntryResolver implements IResolver {
15
+ entry: ZappEntry;
16
+
17
+ constructor(entry: ZappEntry) {
18
+ this.entry = entry;
19
+ }
20
+
21
+ async resolve(key: string) {
22
+ if (!key || key.length === 0) {
23
+ return this.entry;
24
+ }
25
+
26
+ return R.view(R.lensPath(key.split(".")), this.entry);
27
+ }
28
+ }
29
+
30
+ export class ContextResolver implements IResolver {
31
+ resolve = async (compositeKey: string) =>
32
+ ContextKeysManager.instance.getKey(compositeKey);
33
+ }
34
+
35
+ export const resolveObjectValues = async (
36
+ data: Record<string, any>,
37
+ resolvers: Record<string, IResolver> = null
38
+ ) => {
39
+ const entries = Object.entries(data);
40
+
41
+ const exResolvers = { ctx: new ContextResolver(), ...resolvers };
42
+
43
+ const resolvedEntries = await Promise.all(
44
+ entries.map(async ([key, value]) => {
45
+ const regex = /@{([^/]+)\/([^}]*)}/;
46
+
47
+ if (typeof value !== "string") {
48
+ return [key, value];
49
+ }
50
+
51
+ const match = (value as string).match(regex);
52
+
53
+ if (match) {
54
+ const contextResolverName = match[1];
55
+ const compositeKey = match[2];
56
+
57
+ const resolver = exResolvers[contextResolverName] || exResolvers.ctx;
58
+ const resolvedValue = await resolver.resolve(compositeKey);
59
+
60
+ return [key, resolvedValue];
61
+ }
62
+
63
+ log_error("resolveObjectValues: invalid context string", {
64
+ key,
65
+ value,
66
+ });
67
+
68
+ return [key, null];
69
+ })
70
+ );
71
+
72
+ return Object.fromEntries(resolvedEntries);
73
+ };
@@ -1,14 +1,14 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`focusManager should be defined 1`] = `
4
- Object {
4
+ {
5
5
  "blur": [Function],
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
8
  "focusableTree": Tree {
9
9
  "loadingCounter": 0,
10
- "root": Object {
11
- "children": Array [],
10
+ "root": {
11
+ "children": [],
12
12
  "component": null,
13
13
  "id": "root",
14
14
  "lastFocusedItem": null,
@@ -39,12 +39,12 @@ Object {
39
39
  `;
40
40
 
41
41
  exports[`focusManagerIOS should be defined 1`] = `
42
- Object {
42
+ {
43
43
  "focus": [Function],
44
44
  "focusableTree": Tree {
45
45
  "loadingCounter": 0,
46
- "root": Object {
47
- "children": Array [],
46
+ "root": {
47
+ "children": [],
48
48
  "component": null,
49
49
  "id": "root",
50
50
  "lastFocusedItem": null,
@@ -11,8 +11,8 @@ import {
11
11
  haveSameParentBeforeRoot,
12
12
  } from "./treeDataStructure/Utils";
13
13
  import { subscriber } from "../../functionUtils";
14
- import * as FOCUS_EVENTS from "./events";
15
14
  import { coreLogger } from "../../logger";
15
+ import { ACTION } from "./utils/enums";
16
16
  const logger = coreLogger.addSubsystem("focusManager");
17
17
 
18
18
  const isFocusEnabled = (focusableItem): boolean => {
@@ -167,13 +167,82 @@ export const focusManager = (function () {
167
167
  }
168
168
 
169
169
  /**
170
- * returns the current focusable's group
171
- * @private
172
- * @returns {React.Component} the current focus
170
+ * Computes the difference in parent groups between two nodes.
171
+ * It returns the IDs of parents that are present in the current node but not in the previous node,
172
+ * as well as the IDs of parents that are present in the previous node but not in the current node.
173
+ *
174
+ * @param {string} currentNodeID - The ID of the current node.
175
+ * @param {string} previousNodeID - The ID of the previous node.
176
+ * @returns {Object} An object containing the differences in parent IDs:
177
+ * - {Array<string>} currentNodeParentsIDs: Parent IDs present in the current node but not in the previous node.
178
+ * - {Array<string>} previousNodeParentsIDs: Parent IDs present in the previous node but not in the current node.
173
179
  */
174
- function getCurrentGroup() {
175
- return getGroupById(R.path(["props", "groupId"], getCurrentFocus()));
176
- }
180
+ const getParentDiffs = (currentNodeID, previousNodeID) => {
181
+ const currentNodeParentsIDs =
182
+ focusableTree.getParentGroupIds(currentNodeID);
183
+
184
+ const previousNodeParentsIDs =
185
+ focusableTree.getParentGroupIds(previousNodeID);
186
+
187
+ return {
188
+ currentNodeParentsIDs: R.difference(
189
+ currentNodeParentsIDs,
190
+ previousNodeParentsIDs
191
+ ),
192
+ previousNodeParentsIDs: R.difference(
193
+ previousNodeParentsIDs,
194
+ currentNodeParentsIDs
195
+ ),
196
+ };
197
+ };
198
+
199
+ /**
200
+ * Updates the focus state of nodes identified by their IDs.
201
+ * For each ID, if `setFocus` is true, the node will receive focus;
202
+ * otherwise, it will lose focus.
203
+ *
204
+ * @param {Array<string>} ids - An array of node IDs to update.
205
+ * @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
206
+ */
207
+ const updateNodeFocus = (ids, action) => {
208
+ if (!ids || ids.length === 0) {
209
+ return; // Nothing to do
210
+ }
211
+
212
+ const startNode = focusableTree.findInTree(ids[0]);
213
+
214
+ if (!startNode) {
215
+ return; // Start node not found
216
+ }
217
+
218
+ const endNodeId = ids[ids.length - 1]; // Get the ID of the end node
219
+
220
+ let currentNode = startNode;
221
+
222
+ // Function to apply the action (focus or blur)
223
+ const applyAction = (node) => {
224
+ if (node && node.component) {
225
+ if (action === "focus") {
226
+ node.component.setFocus();
227
+ } else if (action === "blur") {
228
+ node.component.setBlur();
229
+ }
230
+ }
231
+ };
232
+
233
+ // Apply action to the starting node
234
+ applyAction(currentNode);
235
+
236
+ // Traverse upwards
237
+ while (currentNode && currentNode.id !== endNodeId) {
238
+ currentNode = currentNode.parent;
239
+
240
+ if (currentNode) {
241
+ // check if current node exists, as parent might be root
242
+ applyAction(currentNode);
243
+ }
244
+ }
245
+ };
177
246
 
178
247
  /**
179
248
  * Sets the current focus to the provided id. If there is an existing focus, it will
@@ -181,34 +250,29 @@ export const focusManager = (function () {
181
250
  * @private
182
251
  * @param {String} id of the focusable to set as currentFocus
183
252
  * @param {Object} direction of the navigation, which led to this focus change
184
- * @param {Object} options
185
- * @param {Boolean} options.groupFocusedChanged tells wether the focus has moved
186
253
  * to another group or not. defaults to false
187
254
  */
188
- function setFocus(
189
- id: string,
190
- direction?: FocusManager.Web.Direction,
191
- options?: Partial<{ groupFocusedChanged: boolean }>
192
- ) {
255
+ function setFocus(id: string, direction?: FocusManager.Web.Direction) {
193
256
  if (focusDisabled) return false;
194
257
 
195
- const { groupFocusedChanged = false } = options || {};
196
-
197
258
  // due to optimisiation it's recommanded to set currentFocusNode before setFocus
198
259
  // is called, so there will be no delays on tree search.
199
260
  // If currentFocusNode is null or undefined we still want it to findInTree
200
261
  if (currentFocusNode?.id !== id) {
262
+ const { currentNodeParentsIDs, previousNodeParentsIDs } = getParentDiffs(
263
+ id,
264
+ currentFocusNode?.id
265
+ );
266
+
267
+ // Set focus on current node parents and blur on previous node parents
268
+ updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
269
+ updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
270
+
201
271
  currentFocusNode = focusableTree.findInTree(id);
202
272
  }
203
273
 
204
274
  setLastFocusOnParentNode(currentFocusNode);
205
275
 
206
- const currentGroup = getCurrentGroup();
207
-
208
- if (groupFocusedChanged && currentGroup) {
209
- currentGroup.hasLostFocus(currentGroup, direction);
210
- }
211
-
212
276
  focus(direction);
213
277
  }
214
278
 
@@ -251,6 +315,7 @@ export const focusManager = (function () {
251
315
  !focusableItem.isGroup &&
252
316
  isFocusEnabled(focusableItem)
253
317
  ) {
318
+ // TODO: Move this to setFocus method
254
319
  blur();
255
320
 
256
321
  logger.log({
@@ -261,7 +326,7 @@ export const focusManager = (function () {
261
326
  });
262
327
 
263
328
  currentFocusNode = focusCandidate;
264
- focusableItem.willReceiveFocus(focusableItem, null);
329
+
265
330
  focusableItem && setFocus(focusCandidate.id, null);
266
331
 
267
332
  return { success: true };
@@ -448,20 +513,10 @@ export const focusManager = (function () {
448
513
  const nextRect = nextFocusable.component.getRect();
449
514
 
450
515
  if (nextRect && nextRect.width > 0) {
451
- nextFocusable.component.willReceiveFocus(
452
- nextFocusable.component,
453
- direction
454
- );
455
-
516
+ // TODO: Move this to setFocus method
456
517
  blur(direction);
457
518
 
458
- const groupChanged =
459
- nextFocusable.component.props.groupId !==
460
- currentFocusNode?.component?.props?.groupId;
461
-
462
- setFocus(nextFocusable.id, direction, {
463
- groupFocusedChanged: groupChanged,
464
- });
519
+ setFocus(nextFocusable.id, direction);
465
520
  }
466
521
  } else {
467
522
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
@@ -481,34 +536,6 @@ export const focusManager = (function () {
481
536
 
482
537
  const { on, invokeHandler, removeHandler } = eventHandler;
483
538
 
484
- on(FOCUS_EVENTS.FOCUS, (focusable, direction) => {
485
- if (focusable.props.groupId) {
486
- const parent = focusableTree.findInTree(focusable.props.groupId);
487
- parent?.component?.onFocus(focusable.component, direction);
488
- }
489
- });
490
-
491
- on(FOCUS_EVENTS.BLUR, (focusable, direction) => {
492
- if (focusable.props.groupId) {
493
- const parent = focusableTree.findInTree(focusable.props.groupId);
494
- parent?.component?.onBlur(focusable.component, direction);
495
- }
496
- });
497
-
498
- on(FOCUS_EVENTS.HAS_LOST_FOCUS, (focusable, direction) => {
499
- if (focusable.props.groupId) {
500
- const parent = focusableTree.findInTree(focusable.props.groupId);
501
- parent?.component?.hasLostFocus(parent.component, direction);
502
- }
503
- });
504
-
505
- on(FOCUS_EVENTS.WILL_RECEIVE_FOCUS, (focusable, direction) => {
506
- if (focusable.props.groupId) {
507
- const parent = focusableTree.findInTree(focusable.props.groupId);
508
- parent?.component?.willReceiveFocus(focusable.component, direction);
509
- }
510
- });
511
-
512
539
  /**
513
540
  * this is the list of the functions available externally
514
541
  * when importing the focus manager
@@ -231,4 +231,27 @@ export class Tree {
231
231
 
232
232
  return retVal;
233
233
  }
234
+
235
+ /**
236
+ * Gets the IDs of all parent groups for a given node id.
237
+ * @public
238
+ * @param {String} nodeId - The id of the node to get parent IDs for.
239
+ * @returns {Array} An array of parent group IDs or an empty array if the node is not found.
240
+ */
241
+ getParentGroupIds(nodeId) {
242
+ const parentIds = [];
243
+ let currentNode = this.findInTree(nodeId);
244
+
245
+ if (!currentNode) {
246
+ return []; // Node not found, return an empty array
247
+ }
248
+
249
+ // Traverse up the tree, collecting parent IDs
250
+ while (currentNode.parent && currentNode.parent.id !== "root") {
251
+ parentIds.push(currentNode.parent.id);
252
+ currentNode = currentNode.parent;
253
+ }
254
+
255
+ return parentIds;
256
+ }
234
257
  }
@@ -0,0 +1,4 @@
1
+ export enum ACTION {
2
+ FOCUS = "focus",
3
+ BLUR = "blur",
4
+ }
@@ -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
 
@@ -1,11 +1,11 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`keyCodes keyCode returns an object for a given key event 1`] = `
4
- Object {
4
+ {
5
5
  "direction": [Function],
6
6
  "matches": [Function],
7
7
  "matchesAny": [Function],
8
- "value": Object {
8
+ "value": {
9
9
  "code": "Enter",
10
10
  "keyCode": 13,
11
11
  },
@@ -1,4 +1,4 @@
1
- const { Platform } = require("react-native");
1
+ import { Platform } from "react-native";
2
2
 
3
3
  Platform.OS = "web";
4
4
 
@@ -77,6 +77,10 @@ const web = {
77
77
  Exit: { code: "Exit", keyCode: null },
78
78
  Escape: { code: "Escape", keyCode: 27 },
79
79
  EscapeDevice: { code: "Escape", keyCode: 27 },
80
+ // For Debugging
81
+ Z: { code: "KeyZ", keyCode: 90 }, // Hardware Backward Button
82
+ X: { code: "KeyX", keyCode: 88 }, // Hardware Enter Button
83
+ C: { code: "KeyC", keyCode: 67 }, // Hardware Forward Button
80
84
  };
81
85
 
82
86
  const android_tv = {
@@ -0,0 +1,68 @@
1
+ export type SeekKeyHandlerListener = {
2
+ onRewindPress: () => boolean;
3
+ onForwardPress: () => boolean;
4
+ onRewindPressOut: () => void;
5
+ onForwardPressOut: () => void;
6
+ };
7
+
8
+ export class KeyInputHandler {
9
+ private static instance: KeyInputHandler;
10
+
11
+ public static getInstance() {
12
+ if (!KeyInputHandler.instance) {
13
+ KeyInputHandler.instance = new KeyInputHandler();
14
+ }
15
+
16
+ return KeyInputHandler.instance;
17
+ }
18
+
19
+ constructor() {
20
+ this.listeners = [];
21
+ }
22
+
23
+ private listeners: SeekKeyHandlerListener[] = [];
24
+
25
+ addListener = (listener: SeekKeyHandlerListener) => {
26
+ this.listeners.splice(0, 0, listener);
27
+
28
+ return () => this.removeListener(listener);
29
+ };
30
+
31
+ removeListener = (listener: SeekKeyHandlerListener) => {
32
+ this.listeners = this.listeners.filter((l) => l !== listener);
33
+ };
34
+
35
+ onRewindPress = () => {
36
+ for (const element of this.listeners) {
37
+ if (element.onRewindPress()) {
38
+ break;
39
+ }
40
+ }
41
+ };
42
+
43
+ onForwardPress = () => {
44
+ for (const element of this.listeners) {
45
+ if (element.onForwardPress()) {
46
+ break;
47
+ }
48
+ }
49
+ };
50
+
51
+ // TODO: Event should be send to listeners who handled down event
52
+ onRewindPressOut = () => {
53
+ this.listeners.forEach((listener) => listener.onRewindPressOut());
54
+ };
55
+
56
+ onForwardPressOut = () => {
57
+ this.listeners.forEach((listener) => listener.onForwardPressOut());
58
+ };
59
+
60
+ getSeekMethods = () => {
61
+ return {
62
+ onForwardPress: this.onForwardPress,
63
+ onRewindPress: this.onRewindPress,
64
+ onForwardPressOut: this.onForwardPressOut,
65
+ onRewindPressOut: this.onRewindPressOut,
66
+ };
67
+ };
68
+ }
@@ -2,7 +2,7 @@
2
2
  import * as ReactNative from "react-native";
3
3
  import { isTV, platformSelect } from "../reactUtils";
4
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
- import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+ import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks/device/useIsTablet";
6
6
 
7
7
  import {
8
8
  QUICK_BRICK_EVENTS,
@@ -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 */