@applicaster/zapp-react-native-utils 14.0.0-alpha.5594607030 → 14.0.0-alpha.5810348423

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 (63) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +0 -1
  2. package/actionsExecutor/ScreenActions.ts +20 -19
  3. package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
  4. package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
  5. package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
  6. package/appUtils/accessibilityManager/const.ts +13 -0
  7. package/appUtils/accessibilityManager/hooks.ts +35 -1
  8. package/appUtils/accessibilityManager/index.ts +151 -30
  9. package/appUtils/accessibilityManager/utils.ts +24 -0
  10. package/appUtils/contextKeysManager/contextResolver.ts +29 -1
  11. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +10 -0
  12. package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
  13. package/appUtils/focusManager/events.ts +2 -0
  14. package/appUtils/focusManager/index.ios.ts +82 -1
  15. package/appUtils/focusManager/index.ts +86 -11
  16. package/appUtils/focusManagerAux/utils/index.ios.ts +107 -0
  17. package/appUtils/focusManagerAux/utils/index.ts +94 -3
  18. package/appUtils/platform/platformUtils.ts +31 -1
  19. package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -1
  20. package/configurationUtils/index.ts +1 -1
  21. package/focusManager/FocusManager.ts +78 -4
  22. package/focusManager/aux/index.ts +98 -0
  23. package/focusManager/utils.ts +12 -6
  24. package/index.d.ts +1 -1
  25. package/manifestUtils/defaultManifestConfigurations/player.js +188 -2
  26. package/manifestUtils/index.js +4 -0
  27. package/manifestUtils/keys.js +12 -0
  28. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
  29. package/navigationUtils/index.ts +1 -1
  30. package/package.json +2 -3
  31. package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
  32. package/playerUtils/PlayerTTS/index.ts +1 -0
  33. package/playerUtils/getPlayerActionButtons.ts +1 -1
  34. package/playerUtils/usePlayerTTS.ts +21 -0
  35. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  36. package/reactHooks/debugging/__tests__/index.test.js +0 -1
  37. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
  38. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +57 -37
  39. package/reactHooks/feed/index.ts +2 -0
  40. package/reactHooks/feed/useBatchLoading.ts +14 -9
  41. package/reactHooks/feed/useFeedLoader.tsx +39 -50
  42. package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
  43. package/reactHooks/navigation/useScreenStateStore.ts +3 -3
  44. package/reactHooks/state/index.ts +1 -1
  45. package/reactHooks/state/useHomeRiver.ts +4 -2
  46. package/screenPickerUtils/index.ts +13 -0
  47. package/storage/ScreenSingleValueProvider.ts +25 -22
  48. package/storage/ScreenStateMultiSelectProvider.ts +26 -23
  49. package/utils/__tests__/endsWith.test.ts +30 -0
  50. package/utils/__tests__/find.test.ts +36 -0
  51. package/utils/__tests__/omit.test.ts +19 -0
  52. package/utils/__tests__/path.test.ts +33 -0
  53. package/utils/__tests__/pathOr.test.ts +37 -0
  54. package/utils/__tests__/startsWith.test.ts +30 -0
  55. package/utils/__tests__/take.test.ts +40 -0
  56. package/utils/endsWith.ts +9 -0
  57. package/utils/find.ts +3 -0
  58. package/utils/index.ts +18 -1
  59. package/utils/omit.ts +5 -0
  60. package/utils/path.ts +5 -0
  61. package/utils/pathOr.ts +5 -0
  62. package/utils/startsWith.ts +9 -0
  63. package/utils/take.ts +5 -0
@@ -9,3 +9,5 @@ export const WILL_LOSE_FOCUS = "willLoseFocus";
9
9
  export const BLUR = "blur";
10
10
 
11
11
  export const HAS_LOST_FOCUS = "hasLostFocus";
12
+
13
+ export const RESET = "reset";
@@ -6,6 +6,13 @@ import { findFocusableNode } from "./treeDataStructure/Utils";
6
6
  import { subscriber } from "../../functionUtils";
7
7
  import { findChild } from "./utils";
8
8
 
9
+ import {
10
+ isCurrentFocusOn,
11
+ isCurrentFocusOnMenu,
12
+ isCurrentFocusOnContent,
13
+ isCurrentFocusOnTabsScreenContent,
14
+ } from "../focusManagerAux/utils/index.ios";
15
+
9
16
  const { FocusableManagerModule } = NativeModules;
10
17
 
11
18
  /**
@@ -179,10 +186,19 @@ export const focusManager = (function () {
179
186
  function register({ id, component }) {
180
187
  const { isGroup = false } = component;
181
188
 
189
+ // console.log("debug_2", "FM - register", {
190
+ // id,
191
+ // isGroup,
192
+ // component,
193
+ // name: component.constructor?.name,
194
+ // });
195
+
182
196
  return isGroup ? registerGroup(id, component) : registerItem(id, component);
183
197
  }
184
198
 
185
199
  function unregister(id, { group = false } = {}) {
200
+ // console.log("debug_2", "FM - unregister", { id, group });
201
+
186
202
  group ? unregisterGroup(id) : unregisterItem(id);
187
203
  }
188
204
 
@@ -260,7 +276,9 @@ export const focusManager = (function () {
260
276
  function setFocus(
261
277
  id: string,
262
278
  direction?: FocusManager.IOS.Direction,
263
- options?: Partial<{ groupFocusedChanged: boolean }>,
279
+ options?: Partial<{
280
+ groupFocusedChanged: boolean;
281
+ }>,
264
282
  callback?: any
265
283
  ) {
266
284
  blur(direction);
@@ -391,6 +409,65 @@ export const focusManager = (function () {
391
409
  return node;
392
410
  }
393
411
 
412
+ function isFocusOn(id): boolean {
413
+ const currentFocusNode = focusableTree.findInTree(
414
+ getCurrentFocus()?.props?.id
415
+ );
416
+
417
+ return id && isCurrentFocusOn(id, currentFocusNode);
418
+ }
419
+
420
+ function isFocusOnMenu(): boolean {
421
+ const currentFocusable = getCurrentFocus();
422
+
423
+ const result = isCurrentFocusOnMenu(
424
+ focusableTree,
425
+ currentFocusable?.props?.id
426
+ );
427
+
428
+ console.log("debug_2", "FM - isFocusOnMenu", {
429
+ result,
430
+ focusableTree,
431
+ currentFocusable,
432
+ });
433
+
434
+ return result;
435
+ }
436
+
437
+ function isFocusOnContent(): boolean {
438
+ const currentFocusable = getCurrentFocus();
439
+
440
+ const result = isCurrentFocusOnContent(
441
+ focusableTree,
442
+ currentFocusable?.props?.id
443
+ );
444
+
445
+ console.log("debug_2", "FM - isFocusOnContent", {
446
+ result,
447
+ focusableTree,
448
+ currentFocusable,
449
+ });
450
+
451
+ return result;
452
+ }
453
+
454
+ function isTabsScreenContentFocused(): boolean {
455
+ const currentFocusable = getCurrentFocus();
456
+
457
+ const result = isCurrentFocusOnTabsScreenContent(
458
+ focusableTree,
459
+ currentFocusable?.props?.id
460
+ );
461
+
462
+ console.log("debug_2", "FM - isTabsScreenContentFocused", {
463
+ result,
464
+ focusableTree,
465
+ currentFocusable,
466
+ });
467
+
468
+ return result;
469
+ }
470
+
394
471
  return {
395
472
  on,
396
473
  invokeHandler,
@@ -412,5 +489,9 @@ export const focusManager = (function () {
412
489
  getGroupRootById,
413
490
  isGroupItemFocused,
414
491
  getPreferredFocusChild,
492
+ isFocusOn,
493
+ isFocusOnMenu,
494
+ isFocusOnContent,
495
+ isTabsScreenContentFocused,
415
496
  };
416
497
  })();
@@ -3,6 +3,7 @@ import {
3
3
  isNilOrEmpty,
4
4
  isNotNil,
5
5
  } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
6
+ import { getFocusableId } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
6
7
 
7
8
  import { Tree } from "./treeDataStructure/Tree";
8
9
  import {
@@ -14,6 +15,13 @@ import { subscriber } from "../../functionUtils";
14
15
  import { coreLogger } from "../../logger";
15
16
  import { ACTION } from "./utils/enums";
16
17
 
18
+ import {
19
+ isTabsScreenOnContentFocused,
20
+ isCurrentFocusOnContent,
21
+ isCurrentFocusOnMenu,
22
+ isCurrentFocusOn,
23
+ } from "../focusManagerAux/utils";
24
+
17
25
  const logger = coreLogger.addSubsystem("focusManager");
18
26
 
19
27
  const isFocusEnabled = (focusableItem): boolean => {
@@ -100,7 +108,7 @@ export const focusManager = (function () {
100
108
  * @private
101
109
  * @param {Object} direction of the navigation which led to this action
102
110
  */
103
- function focus(direction) {
111
+ function focus(direction, context?: FocusManager.FocusContext) {
104
112
  const currentFocusable = getCurrentFocus();
105
113
 
106
114
  if (
@@ -108,7 +116,7 @@ export const focusManager = (function () {
108
116
  !currentFocusable.isGroup &&
109
117
  currentFocusable.isMounted()
110
118
  ) {
111
- currentFocusable.setFocus(direction);
119
+ currentFocusable.setFocus(direction, context);
112
120
  }
113
121
  }
114
122
 
@@ -205,7 +213,7 @@ export const focusManager = (function () {
205
213
  * @param {Array<string>} ids - An array of node IDs to update.
206
214
  * @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
207
215
  */
208
- const updateNodeFocus = (ids, action) => {
216
+ const updateNodeFocus = (ids, action, context: FocusManager.FocusContext) => {
209
217
  if (!ids || ids.length === 0) {
210
218
  return; // Nothing to do
211
219
  }
@@ -222,11 +230,13 @@ export const focusManager = (function () {
222
230
 
223
231
  // Function to apply the action (focus or blur)
224
232
  const applyAction = (node) => {
233
+ const direction = undefined;
234
+
225
235
  if (node && node.component) {
226
236
  if (action === "focus") {
227
- node.component.setFocus();
237
+ node.component.setFocus(direction, context);
228
238
  } else if (action === "blur") {
229
- node.component.setBlur();
239
+ node.component.setBlur(direction, context);
230
240
  }
231
241
  }
232
242
  };
@@ -253,7 +263,11 @@ export const focusManager = (function () {
253
263
  * @param {Object} direction of the navigation, which led to this focus change
254
264
  * to another group or not. defaults to false
255
265
  */
256
- function setFocus(id: string, direction?: FocusManager.Web.Direction) {
266
+ function setFocus(
267
+ id: string,
268
+ direction?: FocusManager.Web.Direction,
269
+ context?: FocusManager.FocusContext
270
+ ) {
257
271
  if (focusDisabled) return false;
258
272
 
259
273
  // due to optimisiation it's recommanded to set currentFocusNode before setFocus
@@ -266,21 +280,68 @@ export const focusManager = (function () {
266
280
  );
267
281
 
268
282
  // Set focus on current node parents and blur on previous node parents
269
- updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
270
- updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
283
+ updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS, context);
284
+ updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR, context);
271
285
 
272
286
  currentFocusNode = focusableTree.findInTree(id);
273
287
  }
274
288
 
275
289
  setLastFocusOnParentNode(currentFocusNode);
276
290
 
277
- focus(direction);
291
+ focus(direction, context);
292
+ }
293
+
294
+ function isFocusOnContent() {
295
+ return isCurrentFocusOnContent(currentFocusNode);
296
+ }
297
+
298
+ function isFocusOnMenu() {
299
+ return isCurrentFocusOnMenu(currentFocusNode);
300
+ }
301
+
302
+ function landFocusToWithoutScrolling(id) {
303
+ if (id) {
304
+ // set focus on selected menu item
305
+ const direction = undefined;
306
+
307
+ const context: FocusManager.FocusContext = {
308
+ source: "back",
309
+ preserveScroll: true,
310
+ };
311
+
312
+ logger.log({ message: "landFocusTo", data: { id } });
313
+
314
+ blur(direction);
315
+ setFocus(id, direction, context);
316
+ }
317
+ }
318
+
319
+ function isTabsScreenContentFocused() {
320
+ return isTabsScreenOnContentFocused(currentFocusNode);
321
+ }
322
+
323
+ function focusOnSelectedTab(item: ZappEntry): void {
324
+ // Move focus to appropriate top navigation tab with context
325
+ const selectedTabId = getFocusableId(item.id);
326
+
327
+ // Set focus with back button context to tabs-menu
328
+ landFocusToWithoutScrolling(selectedTabId);
329
+ }
330
+
331
+ function focusOnSelectedTopMenuItem(
332
+ selectedMenuItemId: Option<string>
333
+ ): void {
334
+ // Set focus with back button context to top-menu
335
+ landFocusToWithoutScrolling(selectedMenuItemId);
278
336
  }
279
337
 
280
338
  /**
281
339
  * sets the initial focus when the screen loads, or when focus is lost
282
340
  */
283
- function setInitialFocus(lastAddedParentNode?: any) {
341
+ function setInitialFocus(
342
+ lastAddedParentNode?: any,
343
+ context?: FocusManager.FocusContext
344
+ ) {
284
345
  const preferredFocus = findPriorityItem(
285
346
  lastAddedParentNode?.children || focusableTree.root.children
286
347
  );
@@ -326,7 +387,7 @@ export const focusManager = (function () {
326
387
  },
327
388
  });
328
389
 
329
- focusableItem && setFocus(focusCandidate.id, null);
390
+ focusableItem && setFocus(focusCandidate.id, null, context);
330
391
 
331
392
  return { success: true };
332
393
  }
@@ -546,6 +607,14 @@ export const focusManager = (function () {
546
607
  return preferredFocus[0];
547
608
  }
548
609
 
610
+ function isFocusOn(id): boolean {
611
+ return (
612
+ id &&
613
+ isCurrentFocusOnTheTopScreen() &&
614
+ isCurrentFocusOn(id, currentFocusNode)
615
+ );
616
+ }
617
+
549
618
  /**
550
619
  * this is the list of the functions available externally
551
620
  * when importing the focus manager
@@ -576,5 +645,11 @@ export const focusManager = (function () {
576
645
  recoverFocus,
577
646
  isCurrentFocusOnTheTopScreen,
578
647
  findPreferredFocusChild,
648
+ isFocusOnContent,
649
+ isFocusOnMenu,
650
+ isFocusOn,
651
+ focusOnSelectedTopMenuItem,
652
+ focusOnSelectedTab,
653
+ isTabsScreenContentFocused,
579
654
  };
580
655
  })();
@@ -0,0 +1,107 @@
1
+ import {
2
+ isNil,
3
+ startsWith,
4
+ endsWith,
5
+ } from "@applicaster/zapp-react-native-utils/utils";
6
+
7
+ import {
8
+ QUICK_BRICK_CONTENT,
9
+ QUICK_BRICK_NAVBAR,
10
+ } from "@applicaster/quick-brick-core/const";
11
+
12
+ import { SCREEN_PICKER_CONTAINER } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
13
+
14
+ const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
15
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
16
+ const isRoot = (node) => node?.id === "root";
17
+
18
+ // FIXME
19
+ const isScrenPicker = (node) =>
20
+ startsWith(SCREEN_PICKER_CONTAINER, node?.id) &&
21
+ endsWith("-screen-container", node?.id);
22
+
23
+ export const isCurrentFocusOnTabsScreenContent = (focusableTree, id) => {
24
+ const node = focusableTree.findInTree(id);
25
+
26
+ if (isNil(node)) {
27
+ return false;
28
+ }
29
+
30
+ if (isRoot(node)) {
31
+ return false;
32
+ }
33
+
34
+ if (isNavBar(node)) {
35
+ return false;
36
+ }
37
+
38
+ if (isContent(node)) {
39
+ return false;
40
+ }
41
+
42
+ if (isScrenPicker(node)) {
43
+ return true;
44
+ }
45
+
46
+ return isCurrentFocusOnTabsScreenContent(focusableTree, node.parent?.id);
47
+ };
48
+
49
+ export const isCurrentFocusOnMenu = (focusableTree, id): boolean => {
50
+ const node = focusableTree.findInTree(id);
51
+
52
+ if (isNil(node)) {
53
+ return false;
54
+ }
55
+
56
+ if (isRoot(node)) {
57
+ return false;
58
+ }
59
+
60
+ if (isNavBar(node)) {
61
+ return true;
62
+ }
63
+
64
+ if (isContent(node)) {
65
+ return false;
66
+ }
67
+
68
+ return isCurrentFocusOnMenu(focusableTree, node.parent.id);
69
+ };
70
+
71
+ export const isCurrentFocusOnContent = (focusableTree, id) => {
72
+ const node = focusableTree.findInTree(id);
73
+
74
+ if (isNil(node)) {
75
+ return false;
76
+ }
77
+
78
+ if (isRoot(node)) {
79
+ return false;
80
+ }
81
+
82
+ if (isNavBar(node)) {
83
+ return false;
84
+ }
85
+
86
+ if (isContent(node)) {
87
+ return true;
88
+ }
89
+
90
+ return isCurrentFocusOnContent(focusableTree, node.parent?.id);
91
+ };
92
+
93
+ export const isCurrentFocusOn = (id, node) => {
94
+ if (!node) {
95
+ return false;
96
+ }
97
+
98
+ if (isRoot(node)) {
99
+ return false;
100
+ }
101
+
102
+ if (node?.id === id) {
103
+ return true;
104
+ }
105
+
106
+ return isCurrentFocusOn(id, node.parent);
107
+ };
@@ -1,14 +1,31 @@
1
- import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
- import { find, last, pathOr, startsWith } from "ramda";
1
+ import {
2
+ isNil,
3
+ last,
4
+ startsWith,
5
+ find,
6
+ pathOr,
7
+ } from "@applicaster/zapp-react-native-utils/utils";
8
+
3
9
  import {
4
10
  QUICK_BRICK_CONTENT,
5
11
  QUICK_BRICK_NAVBAR,
6
12
  } from "@applicaster/quick-brick-core/const";
7
13
 
14
+ import {
15
+ getFocusableId,
16
+ SCREEN_PICKER_CONTAINER,
17
+ } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
18
+
8
19
  // run check each 300 ms
9
20
  // run this check too often could lead to performance penalty on low-end devices
10
21
  const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
22
 
23
+ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
24
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
25
+ const isRoot = (node) => node?.id === "root";
26
+
27
+ const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
28
+
12
29
  type Props = {
13
30
  maxTimeout: number;
14
31
  conditionFn: () => boolean;
@@ -49,7 +66,7 @@ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
49
66
 
50
67
  const route = find((route) => route.id === currentRoute, routes);
51
68
 
52
- return isNotNil(route);
69
+ return !isNil(route);
53
70
  };
54
71
 
55
72
  return waitUntil({
@@ -99,3 +116,77 @@ export const waitForContent = (focusableTree) => {
99
116
  conditionFn: contentHasAnyChildren,
100
117
  });
101
118
  };
119
+
120
+ export const findSelectedTabId = (item: ZappEntry): string => {
121
+ const selectedTabId = getFocusableId(item.id);
122
+
123
+ return selectedTabId;
124
+ };
125
+
126
+ export const isTabsScreenOnContentFocused = (node) => {
127
+ if (isRoot(node)) {
128
+ return false;
129
+ }
130
+
131
+ if (isTopMenu(node)) {
132
+ return false;
133
+ }
134
+
135
+ if (isContent(node)) {
136
+ return false;
137
+ }
138
+
139
+ if (isScrenPicker(node)) {
140
+ return true;
141
+ }
142
+
143
+ return isTabsScreenOnContentFocused(node.parent);
144
+ };
145
+
146
+ export const isCurrentFocusOnMenu = (node) => {
147
+ if (isRoot(node)) {
148
+ return false;
149
+ }
150
+
151
+ if (isTopMenu(node)) {
152
+ return true;
153
+ }
154
+
155
+ if (isContent(node)) {
156
+ return false;
157
+ }
158
+
159
+ return isCurrentFocusOnMenu(node.parent);
160
+ };
161
+
162
+ export const isCurrentFocusOnContent = (node) => {
163
+ if (isRoot(node)) {
164
+ return false;
165
+ }
166
+
167
+ if (isTopMenu(node)) {
168
+ return false;
169
+ }
170
+
171
+ if (isContent(node)) {
172
+ return true;
173
+ }
174
+
175
+ return isCurrentFocusOnContent(node.parent);
176
+ };
177
+
178
+ export const isCurrentFocusOn = (id, node) => {
179
+ if (!node) {
180
+ return false;
181
+ }
182
+
183
+ if (isRoot(node)) {
184
+ return false;
185
+ }
186
+
187
+ if (node?.id === id) {
188
+ return true;
189
+ }
190
+
191
+ return isCurrentFocusOn(id, node.parent);
192
+ };
@@ -10,6 +10,7 @@ import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
10
10
  import { createLogger, utilsLogger } from "../../logger";
11
11
  import { getPlatform } from "../../reactUtils";
12
12
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
13
+ import { calculateReadingTime } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/utils";
13
14
 
14
15
  const { log_debug } = createLogger({
15
16
  category: "General",
@@ -212,9 +213,16 @@ export class TTSManager {
212
213
  }
213
214
 
214
215
  readText(text: string) {
216
+ this.ttsState$.next(true);
217
+
215
218
  if (isSamsungPlatform() && window.speechSynthesis) {
216
219
  const utterance = new SpeechSynthesisUtterance(text);
220
+
221
+ window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
217
222
  window.speechSynthesis.speak(utterance);
223
+
224
+ // Estimate reading time and set inactive when done
225
+ this.scheduleTTSComplete(text);
218
226
  }
219
227
 
220
228
  if (isLgPlatform() && window.webOS?.service) {
@@ -225,23 +233,45 @@ export class TTSManager {
225
233
  log_debug("There was a failure setting up webOS TTS service", {
226
234
  error,
227
235
  });
236
+
237
+ this.ttsState$.next(false);
228
238
  },
229
239
  onSuccess(response: any) {
230
240
  log_debug("webOS TTS service is configured successfully", {
231
241
  response,
232
242
  });
243
+
244
+ // Estimate reading time and set inactive when done
245
+ this.scheduleTTSComplete(text);
233
246
  },
234
247
  parameters: {
235
248
  text,
249
+ clear: true, // Clear any previous speech before speaking new text
236
250
  },
237
251
  });
238
252
  } catch (error) {
239
253
  log_debug("webOS TTS service error", { error });
254
+ this.ttsState$.next(false);
240
255
  }
241
256
  }
242
257
 
243
- if (!window.VIZIO?.Chromevox) return;
258
+ if (!window.VIZIO?.Chromevox) {
259
+ // For platforms without TTS, estimate reading time
260
+ this.scheduleTTSComplete(text);
261
+
262
+ return;
263
+ }
244
264
 
245
265
  window.VIZIO.Chromevox.play(text);
266
+ // Estimate reading time and set inactive when done
267
+ this.scheduleTTSComplete(text);
268
+ }
269
+
270
+ private scheduleTTSComplete(text: string) {
271
+ const readingTime = calculateReadingTime(text);
272
+
273
+ setTimeout(() => {
274
+ this.ttsState$.next(false);
275
+ }, readingTime);
246
276
  }
247
277
  }
@@ -1,6 +1,5 @@
1
1
  import { getAllSpecificStyles } from "../manifestKeyParser";
2
2
 
3
- // Mock the dependencies
4
3
  jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
5
4
  platformSelect: jest.fn((platforms) => platforms.samsung_tv), // Default to samsung for tests
6
5
  }));
@@ -399,7 +399,7 @@ export const populateConfigurationValues =
399
399
  flattenAndPopulateFields(fields, configuration, skipDefaults)
400
400
  );
401
401
 
402
- export const getAccesabilityProps = (item: ZappEntry) => ({
402
+ export const getAccessibilityProps = (item: ZappEntry) => ({
403
403
  accessible: item?.extensions?.accessibility,
404
404
  accessibilityLabel: item?.extensions?.accessibility?.label || item?.title,
405
405
  accessibilityHint: item?.extensions?.accessibility?.hint,