@applicaster/zapp-react-native-utils 12.0.0-alpha.8521132326 → 12.0.0-alpha.8818231309

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,50 @@
1
+ export const BUTTON_ACCESSIBILITY_KEYS = {
2
+ play: {
3
+ label: "accessibility_play_label",
4
+ hint: "accessibility_play_hint",
5
+ mini: {
6
+ label: "accessibility_play_mini_label",
7
+ hint: "accessibility_play_mini_hint",
8
+ },
9
+ },
10
+ pause: {
11
+ label: "accessibility_pause_label",
12
+ hint: "accessibility_pause_hint",
13
+ mini: {
14
+ label: "accessibility_pause_mini_label",
15
+ hint: "accessibility_pause_mini_hint",
16
+ },
17
+ },
18
+ rewind: {
19
+ label: "accessibility_rewind_label",
20
+ hint: "accessibility_rewind_hint",
21
+ },
22
+ forward: {
23
+ label: "accessibility_forward_label",
24
+ hint: "accessibility_forward_hint",
25
+ },
26
+ close: {
27
+ label: "accessibility_close_label",
28
+ hint: "accessibility_close_hint",
29
+ mini: {
30
+ label: "accessibility_close_mini_label",
31
+ hint: "accessibility_close_mini_hint",
32
+ },
33
+ },
34
+ maximize: {
35
+ label: "accessibility_maximize_label",
36
+ hint: "accessibility_maximize_hint",
37
+ },
38
+ subtitles: {
39
+ label: "accessibility_subtitle_label",
40
+ hint: "accessibility_subtitle_hint",
41
+ },
42
+ back: {
43
+ label: "accessibility_back_label",
44
+ hint: "accessibility_back_hint",
45
+ },
46
+ fullscreen: {
47
+ label: "accessibility_fullscreen_label",
48
+ hint: "accessibility_fullscreen_hint",
49
+ },
50
+ } as const;
@@ -0,0 +1,31 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
+ import { AccessibilityManager } from "./index";
4
+
5
+ export const useAccessibilityManager = () => {
6
+ const { remoteConfigurations } = usePickFromState(["remoteConfigurations"]);
7
+ const { localizations } = remoteConfigurations;
8
+
9
+ const accessibilityManager = useMemo(() => {
10
+ return AccessibilityManager.getInstance();
11
+ }, []);
12
+
13
+ useEffect(() => {
14
+ accessibilityManager.updateLocalizations(localizations);
15
+ }, [localizations]);
16
+
17
+ useEffect(() => {
18
+ const subscription = accessibilityManager.getStateAsObservable().subscribe({
19
+ next: () => {
20
+ // TODO: handle accessibility states
21
+ // screenReaderEnabled: false
22
+ // reduceMotionEnabled: false
23
+ // boldTextEnabled: false
24
+ },
25
+ });
26
+
27
+ return () => subscription.unsubscribe();
28
+ }, [accessibilityManager]);
29
+
30
+ return accessibilityManager;
31
+ };
@@ -0,0 +1,150 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ import { accessibilityManagerLogger as logger } from "./logger";
3
+ import { TTSManager } from "../platform/platformUtils";
4
+ import { getLocalizations } from "../../localizationUtils";
5
+ import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
6
+
7
+ export interface AccessibilityState {
8
+ screenReaderEnabled: boolean;
9
+ reduceMotionEnabled: boolean;
10
+ boldTextEnabled: boolean;
11
+ }
12
+
13
+ export interface AccessibilityProps {
14
+ accessibilityLabel?: string;
15
+ accessibilityHint?: string;
16
+ "aria-label"?: string;
17
+ "aria-description"?: string;
18
+ }
19
+
20
+ export class AccessibilityManager {
21
+ private static _instance: AccessibilityManager | null = null;
22
+ private state$ = new BehaviorSubject<AccessibilityState>({
23
+ screenReaderEnabled: false,
24
+ reduceMotionEnabled: false,
25
+ boldTextEnabled: false,
26
+ });
27
+
28
+ private announcements$ = new BehaviorSubject<{
29
+ message: string;
30
+ localizedMessage?: string | undefined;
31
+ timestamp: number;
32
+ } | null>(null);
33
+
34
+ private ttsManager = TTSManager.getInstance();
35
+ private localizations: { [key: string]: string };
36
+
37
+ private constructor(localizations?: { [key: string]: string }) {
38
+ if (localizations) {
39
+ this.localizations = getLocalizations({ localizations });
40
+ }
41
+ }
42
+
43
+ public static getInstance(): AccessibilityManager {
44
+ if (!AccessibilityManager._instance) {
45
+ AccessibilityManager._instance = new AccessibilityManager();
46
+ }
47
+
48
+ return AccessibilityManager._instance;
49
+ }
50
+
51
+ public updateLocalizations(localizations: { [key: string]: string }) {
52
+ this.localizations = getLocalizations({ localizations });
53
+ }
54
+
55
+ public getState(): AccessibilityState {
56
+ return this.state$.getValue();
57
+ }
58
+
59
+ public getStateAsObservable() {
60
+ return this.state$.asObservable();
61
+ }
62
+
63
+ /**
64
+ * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
65
+ * keyOfLocalizedText is the key to the localized text
66
+ */
67
+ public readText({
68
+ text,
69
+ keyOfLocalizedText,
70
+ }: {
71
+ text: string;
72
+ keyOfLocalizedText?: string;
73
+ }) {
74
+ logger.debug("Reading text", { text, keyOfLocalizedText });
75
+
76
+ if (keyOfLocalizedText) {
77
+ if (!this.localizations) {
78
+ logger.error(
79
+ "Attempting to use localized key without initialized localizations"
80
+ );
81
+
82
+ return;
83
+ }
84
+
85
+ const localizedMessage = this.getLocalizedMessage(keyOfLocalizedText);
86
+
87
+ if (!localizedMessage) {
88
+ logger.warn(`No localization found for key: ${keyOfLocalizedText}`);
89
+
90
+ return;
91
+ }
92
+
93
+ text = localizedMessage;
94
+ }
95
+
96
+ if (this.ttsManager) {
97
+ this.ttsManager.readText(text);
98
+ }
99
+
100
+ this.announcements$.next({
101
+ message: text,
102
+ localizedMessage: keyOfLocalizedText ? text : undefined,
103
+ timestamp: Date.now(),
104
+ });
105
+ }
106
+
107
+ public getButtonAccessibilityProps(buttonName: string): AccessibilityProps {
108
+ const buttonConfig = BUTTON_ACCESSIBILITY_KEYS[buttonName];
109
+
110
+ if (!buttonConfig) {
111
+ return {
112
+ accessibilityLabel: buttonName,
113
+ accessibilityHint: `Press button to perform action on ${buttonName}`,
114
+ "aria-label": buttonName,
115
+ "aria-description": `Press button to perform action on ${buttonName}`,
116
+ };
117
+ }
118
+
119
+ const labelKey = buttonConfig.label;
120
+ const hintKey = buttonConfig.hint;
121
+
122
+ const label = this.getLocalizedMessage(labelKey) || buttonName;
123
+
124
+ const hint =
125
+ this.getLocalizedMessage(hintKey) ||
126
+ `Press button to perform action on ${buttonName}`;
127
+
128
+ return {
129
+ accessibilityLabel: label,
130
+ accessibilityHint: hint,
131
+ "aria-label": label,
132
+ "aria-description": hint,
133
+ };
134
+ }
135
+
136
+ public getLocalizedMessage(key: string): string | void {
137
+ if (!key) return;
138
+
139
+ if (!this.localizations) {
140
+ logger.error(
141
+ "Attempting to use localized key without initialized localizations",
142
+ { key }
143
+ );
144
+
145
+ return;
146
+ }
147
+
148
+ return this.localizations[key];
149
+ }
150
+ }
@@ -0,0 +1,5 @@
1
+ import { utilsLogger } from "../../logger";
2
+
3
+ export const accessibilityManagerLogger = utilsLogger.addSubsystem(
4
+ "accessibilityManager"
5
+ );
@@ -13,7 +13,6 @@ import {
13
13
  import { subscriber } from "../../functionUtils";
14
14
  import * as FOCUS_EVENTS from "./events";
15
15
  import { coreLogger } from "../../logger";
16
-
17
16
  const logger = coreLogger.addSubsystem("focusManager");
18
17
 
19
18
  const isFocusEnabled = (focusableItem): boolean => {
package/appUtils/index.js CHANGED
@@ -9,6 +9,8 @@ export { KEYS, ARROW_KEYS } from "./keyCodes/keys";
9
9
 
10
10
  export { playerManager } from "./playerManager";
11
11
 
12
+ export { accessibilityManager } from "./accessibilityManager";
13
+
12
14
  export function deprecationMessage(fn, replacement) {
13
15
  if (__DEV__ && process.env.NODE_ENV !== "test") {
14
16
  console.warn("/** DEPRECATION WARNING **/");
@@ -1,8 +1,6 @@
1
1
  import { BehaviorSubject } from "rxjs";
2
2
 
3
3
  import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
4
- import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
5
- import { PlayerWebController } from "@applicaster/zapp-react-native-default-player/src/playerFactory/players/playerWebController";
6
4
 
7
5
  import { PLATFORM_KEYS, PLATFORMS } from "./const";
8
6
  import { createLogger, utilsLogger } from "../../logger";
@@ -11,6 +9,9 @@ declare global {
11
9
  interface Window {
12
10
  VIZIO?: {
13
11
  exitApplication: () => void;
12
+ Chromevox: {
13
+ play: (text: string) => void;
14
+ };
14
15
  setClosedCaptionHandler: (
15
16
  callback: (isCCEnabled: boolean) => void
16
17
  ) => void;
@@ -18,7 +19,7 @@ declare global {
18
19
  }
19
20
  }
20
21
 
21
- const { log_debug, log_error } = createLogger({
22
+ const { log_debug } = createLogger({
22
23
  category: "General",
23
24
  subsystem: "PlatformUtils",
24
25
  parent: utilsLogger,
@@ -63,7 +64,7 @@ export const hasVizioAPIs = () => {
63
64
  * Manages device based closed captioning state
64
65
  * Returns an observable that emits the current state and updates whenever it changes
65
66
  */
66
- class ClosedCaptioningManager {
67
+ export class ClosedCaptioningManager {
67
68
  private ccState$ = new BehaviorSubject<boolean>(false);
68
69
  private static ccManagerInstance: ClosedCaptioningManager;
69
70
 
@@ -128,96 +129,57 @@ export const getClosedCaptionState = () => {
128
129
  return ccManager.getStateAsObservable();
129
130
  };
130
131
 
131
- export class TextTrackManager {
132
- private controller?: PlayerWebController;
133
- private static instance: TextTrackManager;
134
-
135
- private constructor() {}
136
-
137
- static getInstance(): TextTrackManager {
138
- if (!TextTrackManager.instance) {
139
- TextTrackManager.instance = new TextTrackManager();
140
- }
141
-
142
- return TextTrackManager.instance;
143
- }
144
-
145
- initialize(controller: PlayerWebController) {
146
- if (!controller) {
147
- throw new Error(
148
- "Controller is required for TextTrackManager initialization"
149
- );
150
- }
132
+ /**
133
+ * Manages device based closed captioning state
134
+ * Returns an observable that emits the current state and updates whenever it changes
135
+ */
136
+ export class TTSManager {
137
+ private ttsState$ = new BehaviorSubject<boolean>(false);
138
+ private static ttsManagerInstance: TTSManager;
151
139
 
152
- this.controller = controller;
140
+ private constructor() {
141
+ this.initialize();
153
142
  }
154
143
 
155
- async setTextTrackBasedOnCaptions(closedCaptionsState: boolean) {
156
- if (!this.controller) {
157
- log_error("Controller is not initialized");
158
-
159
- return;
144
+ static getInstance(): TTSManager {
145
+ if (!TTSManager.ttsManagerInstance) {
146
+ TTSManager.ttsManagerInstance = new TTSManager();
160
147
  }
161
148
 
162
- if (closedCaptionsState) {
163
- const textTracks = this.controller.getTracksState().textTracks;
164
- const preferredTextTrack = await this.getPreferredTextTrack(textTracks);
165
-
166
- if (preferredTextTrack) {
167
- this.controller.selectTrack(preferredTextTrack);
168
- }
169
- } else {
170
- this.controller.selectTrack(null);
171
- }
149
+ return TTSManager.ttsManagerInstance;
172
150
  }
173
151
 
174
- async getPreferredTextTrack(
175
- textTracks: QuickBrickPlayer.TextTrack[]
176
- ): Promise<QuickBrickPlayer.TextTrack | undefined> {
177
- if (!this.controller) return;
178
-
179
- const userPreferredTextLanguageLocalStorage = await localStorage.getItem(
180
- "preferred_text_language",
181
- "user_preferences"
152
+ async initialize() {
153
+ if (!isVizioPlatform()) return;
154
+
155
+ document.addEventListener(
156
+ "VIZIO_TTS_ENABLED",
157
+ () => {
158
+ this.ttsState$.next(true);
159
+ },
160
+ false
182
161
  );
183
162
 
184
- let userPreferredTextLanguage;
185
-
186
- if (userPreferredTextLanguageLocalStorage) {
187
- userPreferredTextLanguage = textTracks.find(
188
- (track) => track.language === userPreferredTextLanguageLocalStorage
189
- );
190
- }
191
-
192
- if (userPreferredTextLanguage?.id) {
193
- return userPreferredTextLanguage;
194
- }
195
-
196
- const enableAutoTextTrackSelection =
197
- this.controller.getPluginConfiguration()
198
- ?.enable_auto_text_track_selection ||
199
- this.controller.getTracksState().textTracks.length > 0;
200
-
201
- if (enableAutoTextTrackSelection) {
202
- const languageCode = await sessionStorage.getItem("languageCode");
203
-
204
- if (languageCode) {
205
- const chosenTextTrackBySystemLanguage = textTracks.find(
206
- (track) => track.language === languageCode
207
- );
163
+ document.addEventListener(
164
+ "VIZIO_TTS_DISABLED",
165
+ () => {
166
+ this.ttsState$.next(false);
167
+ },
168
+ false
169
+ );
170
+ }
208
171
 
209
- if (chosenTextTrackBySystemLanguage?.id) {
210
- return chosenTextTrackBySystemLanguage;
211
- }
212
- }
213
- }
172
+ getCurrentState(): boolean {
173
+ return this.ttsState$.getValue();
174
+ }
214
175
 
215
- return textTracks?.[0];
176
+ getStateAsObservable() {
177
+ return this.ttsState$.asObservable();
216
178
  }
217
179
 
218
- isTextTrackEnabled(): boolean {
219
- if (!this.controller) return false;
180
+ readText(text: string) {
181
+ if (!window.VIZIO?.Chromevox) return;
220
182
 
221
- return this.controller.getTracksState().textTrackId !== null;
183
+ window.VIZIO.Chromevox.play(text);
222
184
  }
223
185
  }
@@ -733,4 +733,5 @@ export class Player {
733
733
 
734
734
  public supportsNativeControls = (): boolean => false;
735
735
  public supportNativeCast = (): boolean => false;
736
+ public destroy = () => {};
736
737
  }
@@ -72,6 +72,7 @@ export const usePlayerControllerSetup = ({
72
72
 
73
73
  return () => {
74
74
  playerManager.unregisterPlayer(playerId);
75
+ playerController.destroy?.();
75
76
  };
76
77
  }, [playerId, playerController]);
77
78
 
@@ -592,3 +592,51 @@ export function isMenuDisabled(
592
592
  export function routeIsPlayerScreen(currentRoute) {
593
593
  return currentRoute?.includes("/playable");
594
594
  }
595
+
596
+ export const getNavBarProps =
597
+ (currentRiver: ZappRiver, pathname: string, title: string) => () => {
598
+ const props = getNavigationPropsV2({
599
+ currentRiver,
600
+ title,
601
+ category: "nav_bar",
602
+ });
603
+
604
+ if (props) {
605
+ return {
606
+ ...props,
607
+ id: pathname,
608
+ pathname: pathname,
609
+ };
610
+ }
611
+
612
+ return null;
613
+ };
614
+
615
+ export const findMenuPlugin = (
616
+ navigations: ZappNavigation[],
617
+ plugins
618
+ ): QuickBrickPlugin => {
619
+ return resolveNavigationPlugin({
620
+ category: "menu",
621
+ navigations,
622
+ plugins,
623
+ }) as QuickBrickPlugin;
624
+ };
625
+
626
+ export const shouldNavBarDisplayMenu = (currentRiver: ZappRiver, plugins) =>
627
+ findMenuPlugin(currentRiver.navigations, plugins)?.identifier ===
628
+ "quick_brick_side_menu" ||
629
+ currentRiver?.navigations.findIndex(
630
+ (nav) => nav.category === "nav_bar" && nav.general?.override_menu_target
631
+ ) > -1;
632
+
633
+ export const getScreenId = (
634
+ screenData: LegacyNavigationScreenData,
635
+ fallbackRiver: ZappRiver
636
+ ) => {
637
+ return screenData?.targetScreen
638
+ ? screenData?.targetScreen?.id ?? fallbackRiver.id
639
+ : "screen_id" in screenData
640
+ ? screenData.screen_id
641
+ : fallbackRiver.id;
642
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.8521132326",
3
+ "version": "12.0.0-alpha.8818231309",
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": "12.0.0-alpha.8521132326",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.8818231309",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -245,3 +245,12 @@ export function generateManifest({ manifestConfig, version }) {
245
245
 
246
246
  return manifestConfig({ platform, version });
247
247
  }
248
+
249
+ const getPluginType = R.pathOr("unknown_plugin_type", [
250
+ "state",
251
+ "screen",
252
+ "plugin_type",
253
+ ]);
254
+
255
+ export const isPlayerPlugin = (routeState): boolean =>
256
+ getPluginType(routeState) === "player";
@@ -78,6 +78,10 @@ export function isNavBarVisible(
78
78
  return false;
79
79
  }
80
80
 
81
+ if (screenData?.plugin_type === "login" && isTV()) {
82
+ return false;
83
+ }
84
+
81
85
  /* Match if screen is Hook */
82
86
  if (route.startsWith("/hooks/")) {
83
87
  const module = screenData?.module;
@@ -112,6 +112,9 @@ export const isAndroidTVPlatform = () => getPlatform() === "android_tv";
112
112
  declare global {
113
113
  interface Window {
114
114
  VIZIO?: {
115
+ Chromevox: {
116
+ play: (text: string) => void;
117
+ };
115
118
  exitApplication: () => void;
116
119
  setClosedCaptionHandler: (
117
120
  callback: (isCCEnabled: boolean) => void