@applicaster/zapp-react-native-utils 12.0.0-alpha.5933014273 → 12.0.0-alpha.6158046395

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.
@@ -85,18 +85,7 @@ export class AccessibilityManager {
85
85
  textToRead = localizedMessage;
86
86
  }
87
87
 
88
- if (this.ttsManager) {
89
- logger.debug({
90
- message: `Reading text: ${textToRead}`,
91
- data: {
92
- textToRead,
93
- text,
94
- keyOfLocalizedText,
95
- },
96
- });
97
-
98
- this.ttsManager.readText(textToRead);
99
- }
88
+ this.ttsManager && this.ttsManager.readText(textToRead);
100
89
 
101
90
  this.announcements$.next({
102
91
  message: textToRead,
@@ -29,8 +29,7 @@ const { log_debug } = createLogger({
29
29
  parent: utilsLogger,
30
30
  });
31
31
 
32
- export const getUserAgent = () =>
33
- window?.navigator?.userAgent || global?.navigator?.userAgent;
32
+ export const getUserAgent = () => window?.navigator?.userAgent;
34
33
 
35
34
  /** VIZIO UTILS */
36
35
 
@@ -43,6 +42,8 @@ export const getUserAgent = () =>
43
42
  export const isVizioPlatform = () => {
44
43
  const userAgent = getUserAgent();
45
44
 
45
+ if (!userAgent) return false;
46
+
46
47
  const isVizioAgent =
47
48
  userAgent.includes(PLATFORMS.vizio) ||
48
49
  userAgent.includes(PLATFORMS.smartcast) ||
@@ -50,7 +50,7 @@ export const usePlayerControllerSetup = ({
50
50
  });
51
51
 
52
52
  setPlayerController(newPlayerController);
53
- }, [playerId, entry?.id, ref.current]);
53
+ }, [playerId, entry?.id]);
54
54
 
55
55
  React.useEffect(() => {
56
56
  if (!playerController) {
@@ -58,7 +58,7 @@ export const usePlayerControllerSetup = ({
58
58
  }
59
59
 
60
60
  if (!playerManager) {
61
- return null;
61
+ return;
62
62
  }
63
63
 
64
64
  if (playerManager.isPlayerRegistered(playerId)) {
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable unused-imports/no-unused-vars */
2
2
  import * as R from "ramda";
3
+ import { invariant } from "@applicaster/zapp-react-native-utils/errorUtils";
3
4
 
4
5
  /**
5
6
  * shifts an array by the given offset. a negative offset will take items
@@ -100,3 +101,13 @@ export const makeListOf = (value: unknown, size: number): number[] => {
100
101
  export function isFilledArray(value: unknown): boolean {
101
102
  return R.is(Array, value) && R.length(value) > 0;
102
103
  }
104
+
105
+ // get random item from the list
106
+ export const sample = (xs: unknown[]): unknown => {
107
+ invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
108
+ invariant(isFilledArray(xs), `input array is empty: ${xs}`);
109
+
110
+ const index = Math.floor(Math.random() * xs.length);
111
+
112
+ return xs[index];
113
+ };
@@ -18,6 +18,7 @@ class FocusManager {
18
18
  private static instance: FocusManager;
19
19
 
20
20
  private _focusedId: string | null = null;
21
+ private _prevFocusedId: string | null = null;
21
22
  public previousNavigationDirection: FocusManager.Android.NavDir = null;
22
23
 
23
24
  /**
@@ -47,10 +48,22 @@ class FocusManager {
47
48
  return focusedRef?.current;
48
49
  }
49
50
 
51
+ get prevFocused() {
52
+ const focusedRef = this.prevFocusedId
53
+ ? FocusManager.findFocusable(this.prevFocusedId)
54
+ : { current: null };
55
+
56
+ return focusedRef?.current;
57
+ }
58
+
50
59
  get focusedId() {
51
60
  return FocusManager.instance._focusedId;
52
61
  }
53
62
 
63
+ get prevFocusedId() {
64
+ return FocusManager.instance._prevFocusedId;
65
+ }
66
+
54
67
  public static getInstance(): FocusManager {
55
68
  if (!FocusManager.instance) {
56
69
  FocusManager.instance = new FocusManager();
@@ -151,9 +164,8 @@ class FocusManager {
151
164
  }
152
165
 
153
166
  private setFocusLocal(nextFocus: FocusManager.TouchableReactRef) {
167
+ FocusManager.instance._prevFocusedId = FocusManager.instance._focusedId;
154
168
  FocusManager.instance._focusedId = nextFocus?.current?.props?.id ?? null;
155
-
156
- this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
157
169
  }
158
170
 
159
171
  private setPreviousNavigationDirection(
@@ -217,8 +229,11 @@ class FocusManager {
217
229
 
218
230
  FocusManager.instance.setFocus(nextNextFocus, options);
219
231
  } else {
220
- FocusManager.instance.blurPrevious(options);
221
232
  FocusManager.instance.setFocusLocal(nextFocus);
233
+
234
+ FocusManager.instance.blurPrevious(options);
235
+ this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
236
+
222
237
  FocusManager.instance.setPreviousNavigationDirection(options ?? null);
223
238
 
224
239
  nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
@@ -227,8 +242,8 @@ class FocusManager {
227
242
 
228
243
  blurPrevious(options?: FocusManager.Android.CallbackOptions) {
229
244
  if (options) {
230
- FocusManager.instance.focused?.onBlur?.(
231
- FocusManager.instance.focused,
245
+ FocusManager.instance.prevFocused?.onBlur?.(
246
+ FocusManager.instance.prevFocused,
232
247
  options
233
248
  );
234
249
  }
@@ -156,7 +156,6 @@ describe("FocusManager", () => {
156
156
  child1.current.onBlur.mockClear();
157
157
  focusManager.setFocus(child1.current.props.id);
158
158
  focusManager.blurPrevious({ direction: "down" });
159
- expect(child1.current.onBlur).toBeCalled();
160
159
  });
161
160
 
162
161
  it("focusManager - focus shouldn't change if nextFocus has blockFocus", () => {
@@ -18,6 +18,7 @@ function getDevice(platform) {
18
18
  case "lg_tv":
19
19
  case "samsung_tv":
20
20
  case "web":
21
+ case "vizio":
21
22
  return "tv";
22
23
  }
23
24
  }
@@ -1795,6 +1796,30 @@ function getPlayerConfiguration({ platform }) {
1795
1796
  key: "menu_header_samsung_letter_spacing",
1796
1797
  initial_value: 0,
1797
1798
  },
1799
+ {
1800
+ type: "vizio_font_selector",
1801
+ label: "Vizio Font Family",
1802
+ key: "menu_header_vizio_font_family",
1803
+ initial_value: "SmsungOneUI-Bold",
1804
+ },
1805
+ {
1806
+ type: "number_input",
1807
+ label: "Vizio Font Size",
1808
+ key: "menu_header_vizio_font_size",
1809
+ initial_value: 30,
1810
+ },
1811
+ {
1812
+ type: "number_input",
1813
+ label: "Vizio Line Height",
1814
+ key: "menu_header_vizio_line_height",
1815
+ initial_value: 60,
1816
+ },
1817
+ {
1818
+ type: "number_input",
1819
+ label: "Vizio Letter Spacing",
1820
+ key: "menu_header_vizio_letter_spacing",
1821
+ initial_value: 0,
1822
+ },
1798
1823
  ]
1799
1824
  ),
1800
1825
  // Captions Menu - Item Text Label
@@ -1874,6 +1899,30 @@ function getPlayerConfiguration({ platform }) {
1874
1899
  key: "menu_item_samsung_letter_spacing",
1875
1900
  initial_value: 0,
1876
1901
  },
1902
+ {
1903
+ type: "vizio_font_selector",
1904
+ label: "Vizio Font Family",
1905
+ key: "menu_item_vizio_font_family",
1906
+ initial_value: "SmsungOneUI-Bold",
1907
+ },
1908
+ {
1909
+ type: "number_input",
1910
+ label: "Vizio Font Size",
1911
+ key: "menu_item_vizio_font_size",
1912
+ initial_value: 25,
1913
+ },
1914
+ {
1915
+ type: "number_input",
1916
+ label: "Vizio Line Height",
1917
+ key: "menu_item_vizio_line_height",
1918
+ initial_value: 40,
1919
+ },
1920
+ {
1921
+ type: "number_input",
1922
+ label: "Vizio Letter Spacing",
1923
+ key: "menu_item_vizio_letter_spacing",
1924
+ initial_value: 0,
1925
+ },
1877
1926
  ]
1878
1927
  ),
1879
1928
  // Captions Menu - Item Border
@@ -2017,6 +2066,18 @@ function getPlayerConfiguration({ platform }) {
2017
2066
  key: "samsung_tv_video_player_title_font_size",
2018
2067
  initial_value: 64,
2019
2068
  },
2069
+ {
2070
+ type: "vizio_font_selector",
2071
+ label: "Vizio Font Family",
2072
+ key: "vizio_video_player_title_font_family",
2073
+ initial_value: "Ubuntu-Bold",
2074
+ },
2075
+ {
2076
+ type: "number_input",
2077
+ label: "Vizio Font Size",
2078
+ key: "vizio_video_player_title_font_size",
2079
+ initial_value: 64,
2080
+ },
2020
2081
  {
2021
2082
  type: "tvos_font_selector",
2022
2083
  label: "tvOS Font Family",
@@ -2090,6 +2151,18 @@ function getPlayerConfiguration({ platform }) {
2090
2151
  key: "samsung_tv_video_player_summary_font_size",
2091
2152
  initial_value: 30,
2092
2153
  },
2154
+ {
2155
+ type: "vizio_font_selector",
2156
+ label: "Vizio Font Family",
2157
+ key: "vizio_video_player_summary_font_family",
2158
+ initial_value: "Ubuntu-Bold",
2159
+ },
2160
+ {
2161
+ type: "number_input",
2162
+ label: "Vizio Font Size",
2163
+ key: "vizio_video_player_summary_font_size",
2164
+ initial_value: 30,
2165
+ },
2093
2166
  {
2094
2167
  type: "tvos_font_selector",
2095
2168
  label: "tvOS Font Family",
@@ -2157,6 +2230,18 @@ function getPlayerConfiguration({ platform }) {
2157
2230
  key: "samsung_tv_audio_player_title_font_size",
2158
2231
  initial_value: 38,
2159
2232
  },
2233
+ {
2234
+ type: "vizio_font_selector",
2235
+ label: "Vizio Font Family",
2236
+ key: "vizio_tv_audio_player_title_font_family",
2237
+ initial_value: "Ubuntu-Bold",
2238
+ },
2239
+ {
2240
+ type: "number_input",
2241
+ label: "Vizio Font Size",
2242
+ key: "vizio_audio_player_title_font_size",
2243
+ initial_value: 38,
2244
+ },
2160
2245
  {
2161
2246
  type: "tvos_font_selector",
2162
2247
  label: "tvOS Font Family",
@@ -2212,6 +2297,18 @@ function getPlayerConfiguration({ platform }) {
2212
2297
  key: "samsung_tv_audio_player_summary_font_size",
2213
2298
  initial_value: 20,
2214
2299
  },
2300
+ {
2301
+ type: "vizio_font_selector",
2302
+ label: "Vizio Font Family",
2303
+ key: "vizio_audio_player_summary_font_family",
2304
+ initial_value: "Ubuntu-Bold",
2305
+ },
2306
+ {
2307
+ type: "number_input",
2308
+ label: "Vizio Font Size",
2309
+ key: "vizio_audio_player_summary_font_size",
2310
+ initial_value: 20,
2311
+ },
2215
2312
  {
2216
2313
  type: "tvos_font_selector",
2217
2314
  label: "tvOS Font Family",
@@ -2267,6 +2364,18 @@ function getPlayerConfiguration({ platform }) {
2267
2364
  key: "samsung_tv_player_time_font_size",
2268
2365
  initial_value: 20,
2269
2366
  },
2367
+ {
2368
+ type: "vizio_font_selector",
2369
+ label: "Vizio Font Family",
2370
+ key: "vizio_player_time_font_family",
2371
+ initial_value: "Ubuntu-Bold",
2372
+ },
2373
+ {
2374
+ type: "number_input",
2375
+ label: "Vizio Font Size",
2376
+ key: "vizio_player_time_font_size",
2377
+ initial_value: 20,
2378
+ },
2270
2379
  {
2271
2380
  type: "android_font_selector",
2272
2381
  label: "Android TV Font Family",
@@ -2322,6 +2431,18 @@ function getPlayerConfiguration({ platform }) {
2322
2431
  key: "samsung_tv_audio_player_run_time_font_size",
2323
2432
  initial_value: 20,
2324
2433
  },
2434
+ {
2435
+ type: "vizio_font_selector",
2436
+ label: "Vizio Font Family",
2437
+ key: "vizio_audio_player_run_time_font_family",
2438
+ initial_value: "Ubuntu-Bold",
2439
+ },
2440
+ {
2441
+ type: "number_input",
2442
+ label: "Vizio Font Size",
2443
+ key: "vizio_audio_player_run_time_font_size",
2444
+ initial_value: 20,
2445
+ },
2325
2446
  {
2326
2447
  type: "tvos_font_selector",
2327
2448
  label: "tvOS Font Family",
@@ -17,10 +17,11 @@ const ZAPPIFEST_FIELDS = {
17
17
  lg: "lg_tv_font_selector",
18
18
  samsung: "samsung_font_selector",
19
19
  roku: "roku_font_selector",
20
+ vizio: "vizio_font_selector",
20
21
  },
21
22
  };
22
23
 
23
- const TV_PLATFORMS = ["tvOS", "android", "LG", "samsung", "roku"];
24
+ const TV_PLATFORMS = ["tvOS", "android", "LG", "samsung", "roku", "vizio"];
24
25
 
25
26
  const FontPropsForTVPlatform = (platform) => ({
26
27
  [`${R.toLower(platform)}FontFamily`]: {
@@ -263,7 +263,7 @@ export function getTargetRoute(
263
263
  pathname = "",
264
264
  {
265
265
  contentTypes = undefined,
266
- layoutVersion = undefined,
266
+ layoutVersion = "v2",
267
267
  rivers = {},
268
268
  }: {
269
269
  contentTypes?: ZappContentTypes | null;
@@ -0,0 +1,66 @@
1
+ import { toPositiveNumberWithDefault } from "..";
2
+
3
+ describe("toNumber", () => {
4
+ const DEFAULT = 5;
5
+
6
+ it("return number if input is positive number", () => {
7
+ const inputs = [0, 1, 2, 100, 200.12, Infinity];
8
+ expect.assertions(inputs.length);
9
+
10
+ inputs.forEach((input) => {
11
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
12
+ expect(output).toBe(input);
13
+ });
14
+ });
15
+
16
+ it("return number if input is positive number as string", () => {
17
+ const inputs = ["0", "1", "2", "100", "200.12"];
18
+ expect.assertions(inputs.length);
19
+
20
+ inputs.forEach((input) => {
21
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
22
+ expect(output).toBe(Number(input));
23
+ });
24
+ });
25
+
26
+ it("return default number if input is negative number", () => {
27
+ const inputs = [-1, -2, -100, -Infinity];
28
+ expect.assertions(inputs.length);
29
+
30
+ inputs.forEach((input) => {
31
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
32
+ expect(output).toBe(DEFAULT);
33
+ });
34
+ });
35
+
36
+ it("return default number if input is negative number as string", () => {
37
+ const inputs = ["-1", "-2", "-100"];
38
+ expect.assertions(inputs.length);
39
+
40
+ inputs.forEach((input) => {
41
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
42
+ expect(output).toBe(DEFAULT);
43
+ });
44
+ });
45
+
46
+ it("return default if input is not a number or infinite", () => {
47
+ const inputs = [
48
+ "vfdvf",
49
+ null,
50
+ undefined,
51
+ NaN,
52
+ "",
53
+ {},
54
+ { test: 1 },
55
+ [],
56
+ [1],
57
+ ];
58
+
59
+ expect.assertions(inputs.length);
60
+
61
+ inputs.forEach((input) => {
62
+ const output = toPositiveNumberWithDefault(DEFAULT, input);
63
+ expect(output).toBe(DEFAULT);
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,66 @@
1
+ import { toPositiveNumberWithDefaultZero } from "..";
2
+
3
+ const DEFAULT = 0;
4
+
5
+ describe("toNumber", () => {
6
+ it("return number if input is positive number", () => {
7
+ const inputs = [0, 1, 2, 100, 200.12];
8
+ expect.assertions(inputs.length);
9
+
10
+ inputs.forEach((input) => {
11
+ const output = toPositiveNumberWithDefaultZero(input);
12
+ expect(output).toBe(input);
13
+ });
14
+ });
15
+
16
+ it("return number if input is positive number as string", () => {
17
+ const inputs = ["0", "1", "2", "100", "200.12"];
18
+ expect.assertions(inputs.length);
19
+
20
+ inputs.forEach((input) => {
21
+ const output = toPositiveNumberWithDefaultZero(input);
22
+ expect(output).toBe(Number(input));
23
+ });
24
+ });
25
+
26
+ it("return default number if input is negative number", () => {
27
+ const inputs = [-1, -2, -100];
28
+ expect.assertions(inputs.length);
29
+
30
+ inputs.forEach((input) => {
31
+ const output = toPositiveNumberWithDefaultZero(input);
32
+ expect(output).toBe(DEFAULT);
33
+ });
34
+ });
35
+
36
+ it("return default number if input is negative number as string", () => {
37
+ const inputs = ["-1", "-2", "-100"];
38
+ expect.assertions(inputs.length);
39
+
40
+ inputs.forEach((input) => {
41
+ const output = toPositiveNumberWithDefaultZero(input);
42
+ expect(output).toBe(DEFAULT);
43
+ });
44
+ });
45
+
46
+ it("return default if input is not a number or infinite", () => {
47
+ const inputs = [
48
+ "vfdvf",
49
+ null,
50
+ undefined,
51
+ NaN,
52
+ "",
53
+ {},
54
+ { test: 1 },
55
+ [],
56
+ [1],
57
+ ];
58
+
59
+ expect.assertions(inputs.length);
60
+
61
+ inputs.forEach((input) => {
62
+ const output = toPositiveNumberWithDefaultZero(input);
63
+ expect(output).toBe(DEFAULT);
64
+ });
65
+ });
66
+ });
@@ -66,3 +66,23 @@ export const isZero = (value: unknown): boolean =>
66
66
  export const isMinusZero = (value: number) => {
67
67
  return 1 / value === -Infinity;
68
68
  };
69
+
70
+ export const toPositiveNumberWithDefault = (
71
+ defaultValue: number,
72
+ value: unknown
73
+ ): number => {
74
+ const possibleNumber = toNumber(value);
75
+
76
+ if (R.isNil(possibleNumber)) {
77
+ return defaultValue;
78
+ }
79
+
80
+ if (possibleNumber < 0) {
81
+ return defaultValue;
82
+ }
83
+
84
+ return possibleNumber;
85
+ };
86
+
87
+ export const toPositiveNumberWithDefaultZero = (value: unknown): number =>
88
+ toPositiveNumberWithDefault(0, value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.5933014273",
3
+ "version": "12.0.0-alpha.6158046395",
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.5933014273",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.6158046395",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -1798,6 +1798,30 @@ function getPlayerConfiguration({ platform }) {
1798
1798
  key: "menu_header_samsung_letter_spacing",
1799
1799
  initial_value: 0,
1800
1800
  },
1801
+ {
1802
+ type: "vizio_font_selector",
1803
+ label: "Vizio Font Family",
1804
+ key: "menu_header_vizio_font_family",
1805
+ initial_value: "Ubuntu-Bold",
1806
+ },
1807
+ {
1808
+ type: "number_input",
1809
+ label: "Vizio Font Size",
1810
+ key: "menu_header_vizio_font_size",
1811
+ initial_value: 30,
1812
+ },
1813
+ {
1814
+ type: "number_input",
1815
+ label: "Vizio Line Height",
1816
+ key: "menu_header_vizio_line_height",
1817
+ initial_value: 60,
1818
+ },
1819
+ {
1820
+ type: "number_input",
1821
+ label: "Vizio Letter Spacing",
1822
+ key: "menu_header_vizio_letter_spacing",
1823
+ initial_value: 0,
1824
+ },
1801
1825
  ]
1802
1826
  ),
1803
1827
  // Captions Menu - Item Text Label
@@ -1877,6 +1901,30 @@ function getPlayerConfiguration({ platform }) {
1877
1901
  key: "menu_item_samsung_letter_spacing",
1878
1902
  initial_value: 0,
1879
1903
  },
1904
+ {
1905
+ type: "vizio_font_selector",
1906
+ label: "Vizio Font Family",
1907
+ key: "menu_item_vizio_font_family",
1908
+ initial_value: "Ubuntu-Medium",
1909
+ },
1910
+ {
1911
+ type: "number_input",
1912
+ label: "Vizio Font Size",
1913
+ key: "menu_item_vizio_font_size",
1914
+ initial_value: 25,
1915
+ },
1916
+ {
1917
+ type: "number_input",
1918
+ label: "Vizio Line Height",
1919
+ key: "menu_item_vizio_line_height",
1920
+ initial_value: 40,
1921
+ },
1922
+ {
1923
+ type: "number_input",
1924
+ label: "Vizio Letter Spacing",
1925
+ key: "menu_item_vizio_letter_spacing",
1926
+ initial_value: 0,
1927
+ },
1880
1928
  ]
1881
1929
  ),
1882
1930
  // Captions Menu - Item Border
@@ -2020,6 +2068,18 @@ function getPlayerConfiguration({ platform }) {
2020
2068
  key: "samsung_tv_video_player_title_font_size",
2021
2069
  initial_value: 58,
2022
2070
  },
2071
+ {
2072
+ type: "vizio_font_selector",
2073
+ label: "Vizio Font Family",
2074
+ key: "vizio_video_player_title_font_family",
2075
+ initial_value: "Ubuntu-Bold",
2076
+ },
2077
+ {
2078
+ type: "number_input",
2079
+ label: "Vizio Font Size",
2080
+ key: "vizio_video_player_title_font_size",
2081
+ initial_value: 58,
2082
+ },
2023
2083
  {
2024
2084
  type: "tvos_font_selector",
2025
2085
  label: "tvOS Font Family",
@@ -2093,6 +2153,18 @@ function getPlayerConfiguration({ platform }) {
2093
2153
  key: "samsung_tv_video_player_summary_font_size",
2094
2154
  initial_value: 30,
2095
2155
  },
2156
+ {
2157
+ type: "vizio_font_selector",
2158
+ label: "Vizio Font Family",
2159
+ key: "vizio_video_player_summary_font_family",
2160
+ initial_value: "Ubuntu-Medium",
2161
+ },
2162
+ {
2163
+ type: "number_input",
2164
+ label: "Vizio Font Size",
2165
+ key: "vizio_video_player_summary_font_size",
2166
+ initial_value: 30,
2167
+ },
2096
2168
  {
2097
2169
  type: "tvos_font_selector",
2098
2170
  label: "tvOS Font Family",
@@ -2160,6 +2232,18 @@ function getPlayerConfiguration({ platform }) {
2160
2232
  key: "samsung_tv_audio_player_title_font_size",
2161
2233
  initial_value: 40,
2162
2234
  },
2235
+ {
2236
+ type: "vizio_font_selector",
2237
+ label: "Vizio Font Family",
2238
+ key: "vizio_audio_player_title_font_family",
2239
+ initial_value: "Ubuntu-Bold",
2240
+ },
2241
+ {
2242
+ type: "number_input",
2243
+ label: "Vizio Font Size",
2244
+ key: "vizio_audio_player_title_font_size",
2245
+ initial_value: 40,
2246
+ },
2163
2247
  {
2164
2248
  type: "tvos_font_selector",
2165
2249
  label: "tvOS Font Family",
@@ -2215,6 +2299,18 @@ function getPlayerConfiguration({ platform }) {
2215
2299
  key: "samsung_tv_audio_player_summary_font_size",
2216
2300
  initial_value: 22,
2217
2301
  },
2302
+ {
2303
+ type: "vizio_font_selector",
2304
+ label: "Vizio Font Family",
2305
+ key: "vizio_audio_player_summary_font_family",
2306
+ initial_value: "Ubuntu-Medium",
2307
+ },
2308
+ {
2309
+ type: "number_input",
2310
+ label: "Vizio Font Size",
2311
+ key: "vizio_audio_player_summary_font_size",
2312
+ initial_value: 22,
2313
+ },
2218
2314
  {
2219
2315
  type: "tvos_font_selector",
2220
2316
  label: "tvOS Font Family",
@@ -2270,6 +2366,18 @@ function getPlayerConfiguration({ platform }) {
2270
2366
  key: "samsung_tv_player_time_font_size",
2271
2367
  initial_value: 20,
2272
2368
  },
2369
+ {
2370
+ type: "vizio_font_selector",
2371
+ label: "Vizio Font Family",
2372
+ key: "vizio_player_time_font_family",
2373
+ initial_value: "Ubuntu-Medium",
2374
+ },
2375
+ {
2376
+ type: "number_input",
2377
+ label: "Vizio Font Size",
2378
+ key: "vizio_player_time_font_size",
2379
+ initial_value: 20,
2380
+ },
2273
2381
  {
2274
2382
  type: "android_font_selector",
2275
2383
  label: "Android TV Font Family",
@@ -2325,6 +2433,18 @@ function getPlayerConfiguration({ platform }) {
2325
2433
  key: "samsung_tv_audio_player_run_time_font_size",
2326
2434
  initial_value: 20,
2327
2435
  },
2436
+ {
2437
+ type: "vizio_font_selector",
2438
+ label: "Vizio Font Family",
2439
+ key: "vizio_audio_player_run_time_font_family",
2440
+ initial_value: "Ubuntu-Medium",
2441
+ },
2442
+ {
2443
+ type: "number_input",
2444
+ label: "Vizio Font Size",
2445
+ key: "vizio_audio_player_run_time_font_size",
2446
+ initial_value: 20,
2447
+ },
2328
2448
  {
2329
2449
  type: "tvos_font_selector",
2330
2450
  label: "tvOS Font Family",
@@ -30,7 +30,15 @@ type Props = {
30
30
  zappPipesData?: ZappPipesData;
31
31
  };
32
32
 
33
- export const useCellClick = ({ component, zappPipesData, item }: Props) => {
33
+ type onPressReturnFn =
34
+ | ((_item?: ZappEntry | any, _index?: number) => Promise<void>)
35
+ | (() => void);
36
+
37
+ export const useCellClick = ({
38
+ component,
39
+ zappPipesData,
40
+ item,
41
+ }: Props): onPressReturnFn => {
34
42
  const { push, currentRoute } = useNavigation();
35
43
  const { pathname } = useRoute();
36
44
 
@@ -106,9 +114,15 @@ export const useCellClick = ({ component, zappPipesData, item }: Props) => {
106
114
  ]
107
115
  );
108
116
 
117
+ const onPressRef = React.useRef<onPressReturnFn>();
118
+
109
119
  if (!cellSelectable) {
110
- return noop;
120
+ // update ref with dummy function
121
+ onPressRef.current = noop;
122
+ } else {
123
+ // update ref with the latest onPress function
124
+ onPressRef.current = onPress;
111
125
  }
112
126
 
113
- return onPress;
127
+ return React.useCallback(onPressRef.current, []);
114
128
  };
@@ -77,8 +77,6 @@ describe("useBatchLoading", () => {
77
77
 
78
78
  const actions = store.getActions();
79
79
 
80
- expect(useDispatchSpy).toBeCalledTimes(1);
81
-
82
80
  expect(actions).toHaveLength(2);
83
81
 
84
82
  expect(actions[0]).toMatchObject({
@@ -129,6 +127,8 @@ describe("useBatchLoading", () => {
129
127
  // ... more items
130
128
  ];
131
129
 
130
+ expect(useDispatchSpy).toBeCalledTimes(0);
131
+
132
132
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
133
133
  wrapper,
134
134
  initialProps: { store },
@@ -136,8 +136,6 @@ describe("useBatchLoading", () => {
136
136
 
137
137
  const actions = store.getActions();
138
138
 
139
- expect(useDispatchSpy).toBeCalledTimes(1);
140
-
141
139
  expect(actions).toHaveLength(1);
142
140
 
143
141
  expect(actions[0]).toMatchObject({
@@ -172,8 +170,6 @@ describe("useBatchLoading", () => {
172
170
  initialProps: { store },
173
171
  });
174
172
 
175
- expect(useDispatchSpy).toBeCalledTimes(1);
176
-
177
173
  const actions = store.getActions();
178
174
 
179
175
  expect(actions).toHaveLength(3);
@@ -218,6 +214,73 @@ describe("useBatchLoading", () => {
218
214
 
219
215
  expect(result.current).toBe(true);
220
216
  });
217
+
218
+ it("gallery-qb: loadPipesData should be called only once for first component in the gallery", () => {
219
+ const store = mockStore({
220
+ zappPipes: {},
221
+ test: "true",
222
+ });
223
+
224
+ useDispatchSpy.mockReturnValue(store.dispatch);
225
+
226
+ const initialBatchSize = 3;
227
+ const riverId = "123";
228
+
229
+ const data = [
230
+ {
231
+ component_type: "gallery-qb",
232
+ ui_components: [{ data: { source: "url1" } }],
233
+ },
234
+ { data: { source: "url2" } },
235
+ { data: { source: "url3" } },
236
+ { data: { source: "url4" } },
237
+ { data: { source: "url5" } },
238
+ { data: { source: "url6" } },
239
+ // ... more items
240
+ ];
241
+
242
+ renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
243
+ wrapper,
244
+ initialProps: { store },
245
+ });
246
+
247
+ const actions = store.getActions();
248
+
249
+ expect(actions).toHaveLength(1);
250
+ });
251
+
252
+ it("gallery-qb: initial batch ready when all initial items loaded", () => {
253
+ const store = mockStore({
254
+ zappPipes: {
255
+ url1: {
256
+ loading: false,
257
+ error: false,
258
+ data: {},
259
+ },
260
+ },
261
+ });
262
+
263
+ useDispatchSpy.mockReturnValue(store.dispatch);
264
+
265
+ const initialBatchSize = 3;
266
+ const riverId = "123";
267
+
268
+ const data: Partial<ZappUIComponent>[] = [
269
+ {
270
+ component_type: "gallery-qb",
271
+ ui_components: [{ data: { source: "url1" } }],
272
+ },
273
+ { data: { source: "url2" } },
274
+ { data: { source: "url3" } },
275
+ ];
276
+
277
+ const { result } = renderHook(
278
+ () => useBatchLoading(data, { initialBatchSize, riverId }),
279
+ { wrapper, initialProps: { store } }
280
+ );
281
+
282
+ expect(result.current).toBe(true);
283
+ });
221
284
  });
222
285
 
223
286
  describe("allFeedsIsReady", () => {
@@ -45,19 +45,23 @@ const filterEmptyData = (data) => {
45
45
  return true;
46
46
  };
47
47
 
48
- const extractData = compose(uniq, map(dataOf));
48
+ const getData = (rawData) =>
49
+ rawData.component_type === "gallery-qb"
50
+ ? rawData.ui_components[0].data
51
+ : rawData.data;
52
+
53
+ const extractData = compose(uniq, map(getData));
49
54
 
50
55
  export const allFeedsIsReady = (
51
56
  feeds: ZappPipesCollection,
52
57
  urls: (ZappDataSourceSource | null)[]
53
58
  ): boolean =>
54
- urls
55
- .filter(complement(isNil))
56
- .map((url) => (url ? checkFeedIsReady(feeds[url]) : false))
57
- .every(Boolean);
59
+ urls.every((url) =>
60
+ isNil(url) ? true : url && feeds[url] ? checkFeedIsReady(feeds[url]) : false
61
+ );
58
62
 
59
63
  export const useBatchLoading = (
60
- data: { data?: ZappDataSource }[],
64
+ componentsToRender: { data?: ZappDataSource; component_type: string }[],
61
65
  options: Options
62
66
  ) => {
63
67
  const dispatch = useDispatch();
@@ -65,10 +69,18 @@ export const useBatchLoading = (
65
69
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
66
70
  const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
67
71
 
68
- const takeBatchSize = take(
69
- min(options.initialBatchSize ?? DEFAULT_BATCH_SIZE, data.length)
70
- );
72
+ // if first component is gallery-qb, take only one component for initial load
73
+ const takeSize =
74
+ componentsToRender?.[0]?.component_type === "gallery-qb"
75
+ ? 1
76
+ : min(
77
+ options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
78
+ componentsToRender.length
79
+ );
80
+
81
+ const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);
71
82
 
83
+ // Prepare inflated url for datasource
72
84
  const getUrl = React.useCallback(
73
85
  (data) => {
74
86
  const mappedFeed = data.mapping
@@ -88,13 +100,16 @@ export const useBatchLoading = (
88
100
  [entryContext, screenContext, searchContext]
89
101
  );
90
102
 
91
- const batchData = takeBatchSize(data);
103
+ // components for initial load
104
+ const batchComponents = takeBatchSize(componentsToRender);
92
105
 
106
+ // contains datasources from batch components loading
93
107
  const feedData: ZappDataSource[] = React.useMemo(
94
- () => extractData(batchData).filter(filterEmptyData),
95
- [batchData]
108
+ () => extractData(batchComponents).filter(filterEmptyData),
109
+ [batchComponents]
96
110
  );
97
111
 
112
+ // contains inflated urls from batch components loading
98
113
  const feedUrls: ZappDataSourceSource[] = React.useMemo(
99
114
  () =>
100
115
  feedData.map((data) => {
@@ -102,34 +117,43 @@ export const useBatchLoading = (
102
117
 
103
118
  return mappedFeedUrl;
104
119
  }),
105
- [] // No watchers as batch loaders is used only for initial load
120
+ []
106
121
  );
107
122
 
108
123
  const feeds = useZappPipesFeeds(feedUrls);
109
124
 
110
- React.useEffect(() => {
111
- const datasources = map(dataOf, batchData)
112
- .filter(filterEmptyData)
113
- .filter((data) => {
114
- const feed = feeds[data.source];
125
+ // dispatch loadPipesData for each feed that is not loaded
126
+ const runBatchLoading = React.useCallback(() => {
127
+ batchComponents.forEach((rawData: any) => {
128
+ // 1. get data (dataOf)
115
129
 
116
- if (!feed) return true;
130
+ const data = getData(rawData);
117
131
 
118
- return !feedHasData(feed) && !feedIsLoading(feed);
119
- });
132
+ // 2. filter out empty data filterEmptyData
133
+ if (!filterEmptyData(data)) return false;
120
134
 
121
- datasources.forEach((data) => {
122
- const mappedFeedUrl = getUrl(data);
135
+ // 3. filter out data that is already loaded (feeds[data.source]) & !feedHasData(feed) && !feedIsLoading(feed);
136
+ const feed = feeds[data?.source];
123
137
 
124
- if (mappedFeedUrl) {
125
- return dispatch(
126
- loadPipesData(mappedFeedUrl, { riverId: options.riverId })
127
- );
138
+ if (!feed || (!feedHasData(feed) && !feedIsLoading(feed))) {
139
+ const mappedFeedUrl = getUrl(data);
140
+
141
+ if (mappedFeedUrl) {
142
+ // 4. load data
143
+ return dispatch(
144
+ loadPipesData(mappedFeedUrl, { riverId: options.riverId })
145
+ );
146
+ }
128
147
  }
129
148
  });
130
- }, []); // No watchers as batch loaders is used only for initial load
149
+ }, [feedUrls]);
150
+
151
+ React.useEffect(() => {
152
+ runBatchLoading();
153
+ }, []);
131
154
 
132
155
  React.useEffect(() => {
156
+ // check if all feeds are ready and set hasEverBeenReady to true
133
157
  if (allFeedsIsReady(feeds, feedUrls) && !hasEverBeenReady) {
134
158
  setHasEverBeenReady(true);
135
159
  }
@@ -0,0 +1,134 @@
1
+ import { renderHook } from "@testing-library/react-hooks";
2
+ import { Dimensions, StatusBar } from "react-native";
3
+
4
+ const mockUsePickFromState = jest.fn();
5
+ const mockUseIsScreenActive = jest.fn();
6
+ const mockGetInitialDimensions = jest.fn();
7
+ const mockGetDeviceInfo = jest.fn();
8
+
9
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
10
+ ...(jest.requireActual("@applicaster/zapp-react-native-redux/hooks") as {}),
11
+ usePickFromState: mockUsePickFromState,
12
+ }));
13
+
14
+ jest.mock("../../../navigation", () => ({
15
+ useIsScreenActive: mockUseIsScreenActive,
16
+ }));
17
+
18
+ jest.mock("../helpers", () => ({
19
+ getInitialDimensions: mockGetInitialDimensions,
20
+ }));
21
+
22
+ jest.mock("../../getDeviceInfo", () => ({
23
+ getDeviceInfo: mockGetDeviceInfo,
24
+ }));
25
+
26
+ const { useDimensions } = require("../useDimensions");
27
+
28
+ describe("useDimensions", () => {
29
+ const mockDimensions = { width: 100, height: 200, scale: 1, fontScale: 1 };
30
+ const mockAppData = { someData: "test" };
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ Dimensions.get = jest.fn().mockReturnValue(mockDimensions);
35
+
36
+ Dimensions.addEventListener = jest.fn().mockReturnValue({
37
+ remove: jest.fn(),
38
+ });
39
+
40
+ mockUsePickFromState.mockReturnValue({ appData: mockAppData });
41
+ mockUseIsScreenActive.mockReturnValue(true);
42
+ mockGetInitialDimensions.mockReturnValue(mockDimensions);
43
+ mockGetDeviceInfo.mockReturnValue({ deviceInfo: "testDeviceInfo" });
44
+ });
45
+
46
+ it("returns correct initial dimensions", () => {
47
+ const { result } = renderHook(() =>
48
+ useDimensions("window", { fullDimensions: false })
49
+ );
50
+
51
+ expect(result.current).toEqual({
52
+ ...mockDimensions,
53
+ statusBarHeight: StatusBar.currentHeight,
54
+ });
55
+
56
+ expect(mockGetInitialDimensions).toHaveBeenCalledWith("window");
57
+ });
58
+
59
+ it("calls handler on mount", () => {
60
+ renderHook(() => useDimensions("window", { fullDimensions: false }));
61
+
62
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
63
+ "change",
64
+ expect.any(Function)
65
+ );
66
+ });
67
+
68
+ it("calls handler on isActive change", () => {
69
+ const { rerender } = renderHook(() =>
70
+ useDimensions("window", { fullDimensions: false })
71
+ );
72
+
73
+ mockUseIsScreenActive.mockReturnValue(false);
74
+ rerender();
75
+
76
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
77
+ "change",
78
+ expect.any(Function)
79
+ );
80
+ });
81
+
82
+ it("handles fullDimensions option", () => {
83
+ const { result } = renderHook(() =>
84
+ useDimensions("window", { fullDimensions: true })
85
+ );
86
+
87
+ expect(result.current).toEqual({
88
+ ...mockDimensions,
89
+ scale: 1,
90
+ fontScale: 1,
91
+ statusBarHeight: StatusBar.currentHeight,
92
+ });
93
+ });
94
+
95
+ it("handles excludeStatusBar option", () => {
96
+ const { result } = renderHook(() =>
97
+ useDimensions("window", { excludeStatusBar: true })
98
+ );
99
+
100
+ expect(result.current.height).toBe(
101
+ mockDimensions.height - StatusBar.currentHeight ?? 0
102
+ );
103
+ });
104
+
105
+ it("handles rounded option", () => {
106
+ const { result } = renderHook(() =>
107
+ useDimensions("window", { rounded: true })
108
+ );
109
+
110
+ expect(result.current.width).toBe(Math.ceil(mockDimensions.width));
111
+ expect(result.current.height).toBe(Math.ceil(mockDimensions.height));
112
+ });
113
+
114
+ it("handles deviceInfo option", () => {
115
+ const { result } = renderHook(() =>
116
+ useDimensions("window", { deviceInfo: true })
117
+ );
118
+
119
+ expect(result.current.deviceInfo).toMatchObject({
120
+ deviceInfo: "testDeviceInfo",
121
+ });
122
+ });
123
+
124
+ it("logs deprecation warning when fullDimensions is boolean", () => {
125
+ const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
126
+ renderHook(() => useDimensions("window", true));
127
+
128
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
129
+ "Deprecation Warning\nSecond argument is now the options object. {fullDimensions: boolean, ...}"
130
+ );
131
+
132
+ consoleWarnSpy.mockRestore();
133
+ });
134
+ });
@@ -17,7 +17,7 @@ import {
17
17
  QUICK_BRICK_NAVBAR,
18
18
  QUICK_BRICK_CONTENT,
19
19
  } from "@applicaster/quick-brick-core/const";
20
- import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
20
+ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
21
21
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
22
22
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
23
23
  import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
@@ -1,5 +1,4 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import * as R from "ramda";
3
2
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
4
3
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
5
4
  import {
@@ -17,10 +16,6 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
17
16
  const intervalTimer = React.useRef(null);
18
17
  const prevRunning = usePrevious(isRunning);
19
18
 
20
- const setIntervalTimer = (cb) => {
21
- intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
22
- };
23
-
24
19
  const clearIntervalTimer = () => {
25
20
  try {
26
21
  clearInterval(intervalTimer.current);
@@ -29,6 +24,11 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
29
24
  }
30
25
  };
31
26
 
27
+ const setIntervalTimer = (cb) => {
28
+ clearIntervalTimer();
29
+ intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
30
+ };
31
+
32
32
  const resetState = () => {
33
33
  setIsRunning(false);
34
34
  setDirection(null);
@@ -42,7 +42,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
42
42
  if (!player) return;
43
43
 
44
44
  if (direction) {
45
- player[direction](R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
45
+ player[direction](Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
46
46
  resetHUDTimer();
47
47
  }
48
48
  }, [multiplier, direction]);
@@ -50,7 +50,9 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
50
50
  useEffect(() => {
51
51
  if (isRunning && isRunning !== prevRunning) {
52
52
  setIntervalTimer(() => {
53
- setMultiplier(R.add(1));
53
+ setMultiplier((prev) => {
54
+ return prev + 1;
55
+ });
54
56
  });
55
57
  } else if (!isRunning && isRunning !== prevRunning) {
56
58
  resetState();
@@ -67,7 +67,7 @@ export const useConfiguration = ({
67
67
  if (component) {
68
68
  const remappedStylesConfiguration = remapUpdatedKeys(
69
69
  pluginManifest,
70
- component.styles
70
+ component
71
71
  );
72
72
 
73
73
  R.forEachObjIndexed((value, key) => {
@@ -83,7 +83,7 @@ export const useConfiguration = ({
83
83
 
84
84
  const styles = populateConfigurationValues(
85
85
  pluginManifest?.styles?.fields,
86
- remappedStylesConfiguration,
86
+ remappedStylesConfiguration.styles,
87
87
  skipDefaults
88
88
  );
89
89
 
@@ -7,9 +7,11 @@ import { Provider } from "react-redux";
7
7
  import thunk from "redux-thunk";
8
8
  import configureStore from "redux-mock-store";
9
9
  import { SafeAreaProvider } from "react-native-safe-area-context";
10
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
10
11
 
11
12
  import { render } from "@testing-library/react-native";
12
13
  import { AnalyticsProvider } from "../analyticsUtils";
14
+ import { ThemeContext } from "../theme";
13
15
 
14
16
  export { getByTestId } from "./getByTestId";
15
17
 
@@ -59,7 +61,9 @@ const initialState = {
59
61
  appSettings: {},
60
62
  plugins: [],
61
63
  appState: { appReady: true, appLaunched: true },
64
+ pluginConfigurations: {},
62
65
  appData: { layoutVersion: "v1" },
66
+ zappPipes: {},
63
67
  contentTypes: null,
64
68
  };
65
69
 
@@ -68,20 +72,49 @@ export const WrappedWithProviders = ({
68
72
  store: storeObj = {},
69
73
  }: any) => {
70
74
  const _store = configureStore([thunk])(
71
- R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
75
+ R.mergeDeepRight(initialState, { ...storeObj })
72
76
  );
73
77
 
78
+ appStore.set(_store);
79
+
74
80
  return (
75
- <SafeAreaProvider
76
- initialMetrics={{
77
- insets: { top: 0, left: 0, right: 0, bottom: 0 },
78
- frame: { x: 0, y: 0, width: 0, height: 0 },
81
+ <ThemeContext.Provider
82
+ value={{
83
+ themes: {
84
+ light: {
85
+ component_margin_top: 0,
86
+ component_margin_bottom: 0,
87
+ component_margin_left: 0,
88
+ component_margin_right: 0,
89
+ component_padding_top: 0,
90
+ component_padding_bottom: 0,
91
+ component_padding_left: 0,
92
+ component_padding_right: 0,
93
+ component_gutter_vertical: 0,
94
+ component_gutter_horizontal: 0,
95
+ component_corner_radius: 0,
96
+ app_background_color: "rgba(0,0,0,0)",
97
+ screen_margin_top: 0,
98
+ screen_margin_bottom: 0,
99
+ component_anchor_point_y: 0,
100
+ assets: "",
101
+ },
102
+ },
103
+ selectedThemeId: "light",
104
+ setSelectedThemeId: () => {},
79
105
  }}
80
106
  >
81
- <Provider store={_store}>
82
- <AnalyticsProvider>{children}</AnalyticsProvider>
83
- </Provider>
84
- </SafeAreaProvider>
107
+ <SafeAreaProvider
108
+ initialMetrics={{
109
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
110
+ frame: { x: 0, y: 0, width: 0, height: 0 },
111
+ }}
112
+ >
113
+ <Provider store={_store}>
114
+ <AnalyticsProvider>{children}</AnalyticsProvider>
115
+ </Provider>
116
+ </SafeAreaProvider>
117
+ </ThemeContext.Provider>
85
118
  );
86
119
  };
87
120