@applicaster/zapp-react-native-utils 14.0.0-rc.60 → 14.0.0-rc.62

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.
@@ -6,7 +6,8 @@ exports[`focusManager should be defined 1`] = `
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
8
  "findPreferredFocusChild": [Function],
9
- "focusTopNavigation": [Function],
9
+ "focusOnSelectedTab": [Function],
10
+ "focusOnSelectedTopMenuItem": [Function],
10
11
  "focusableTree": Tree {
11
12
  "loadingCounter": 0,
12
13
  "root": {
@@ -29,6 +30,7 @@ exports[`focusManager should be defined 1`] = `
29
30
  "isFocusOnContent": [Function],
30
31
  "isFocusOnMenu": [Function],
31
32
  "isGroupItemFocused": [Function],
33
+ "isTabsScreenContentFocused": [Function],
32
34
  "longPress": [Function],
33
35
  "moveFocus": [Function],
34
36
  "on": [Function],
@@ -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";
@@ -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 {
@@ -15,9 +16,7 @@ import { coreLogger } from "../../logger";
15
16
  import { ACTION } from "./utils/enums";
16
17
 
17
18
  import {
18
- findSelectedTabId,
19
- findSelectedMenuId,
20
- isTabsScreenContentFocused,
19
+ isTabsScreenOnContentFocused,
21
20
  isCurrentFocusOnContent,
22
21
  isCurrentFocusOnMenu,
23
22
  isCurrentFocusOn,
@@ -300,37 +299,40 @@ export const focusManager = (function () {
300
299
  return isCurrentFocusOnMenu(currentFocusNode);
301
300
  }
302
301
 
303
- // Move focus to appropriate top navigation tab with context
304
- function focusTopNavigation(isTabsScreen: boolean, item: ZappEntry) {
305
- const landFocusTo = (id) => {
306
- if (id) {
307
- // set focus on selected menu item
308
- const direction = undefined;
302
+ function landFocusToWithoutScrolling(id) {
303
+ if (id) {
304
+ // set focus on selected menu item
305
+ const direction = undefined;
309
306
 
310
- const context: FocusManager.FocusContext = {
311
- source: "back",
312
- preserveScroll: true,
313
- };
307
+ const context: FocusManager.FocusContext = {
308
+ source: "back",
309
+ preserveScroll: true,
310
+ };
314
311
 
315
- logger.log({ message: "landFocusTo", data: { id } });
312
+ logger.log({ message: "landFocusTo", data: { id } });
316
313
 
317
- blur(direction);
318
- setFocus(id, direction, context);
319
- }
320
- };
314
+ blur(direction);
315
+ setFocus(id, direction, context);
316
+ }
317
+ }
321
318
 
322
- if (isTabsScreen && isTabsScreenContentFocused(currentFocusNode)) {
323
- const selectedTabId = findSelectedTabId(item);
319
+ function isTabsScreenContentFocused() {
320
+ return isTabsScreenOnContentFocused(currentFocusNode);
321
+ }
324
322
 
325
- // Set focus with back button context to tabs-menu
326
- landFocusTo(selectedTabId);
323
+ function focusOnSelectedTab(item: ZappEntry): void {
324
+ // Move focus to appropriate top navigation tab with context
325
+ const selectedTabId = getFocusableId(item.id);
327
326
 
328
- return;
329
- }
327
+ // Set focus with back button context to tabs-menu
328
+ landFocusToWithoutScrolling(selectedTabId);
329
+ }
330
330
 
331
- const selectedMenuItemId = findSelectedMenuId(focusableTree);
331
+ function focusOnSelectedTopMenuItem(
332
+ selectedMenuItemId: Option<string>
333
+ ): void {
332
334
  // Set focus with back button context to top-menu
333
- landFocusTo(selectedMenuItemId);
335
+ landFocusToWithoutScrolling(selectedMenuItemId);
334
336
  }
335
337
 
336
338
  /**
@@ -643,9 +645,11 @@ export const focusManager = (function () {
643
645
  recoverFocus,
644
646
  isCurrentFocusOnTheTopScreen,
645
647
  findPreferredFocusChild,
646
- focusTopNavigation,
647
648
  isFocusOnContent,
648
649
  isFocusOnMenu,
649
650
  isFocusOn,
651
+ focusOnSelectedTopMenuItem,
652
+ focusOnSelectedTab,
653
+ isTabsScreenContentFocused,
650
654
  };
651
655
  })();
@@ -123,19 +123,7 @@ export const findSelectedTabId = (item: ZappEntry): string => {
123
123
  return selectedTabId;
124
124
  };
125
125
 
126
- export const findSelectedMenuId = (focusableTree) => {
127
- // Set focus with back button context
128
- const navbar = getNavbarNode(focusableTree);
129
-
130
- const selectedMenuItemId = find(
131
- (child) => child.component.props.selected,
132
- navbar.children
133
- )?.id;
134
-
135
- return selectedMenuItemId;
136
- };
137
-
138
- export const isTabsScreenContentFocused = (node) => {
126
+ export const isTabsScreenOnContentFocused = (node) => {
139
127
  if (isRoot(node)) {
140
128
  return false;
141
129
  }
@@ -152,7 +140,7 @@ export const isTabsScreenContentFocused = (node) => {
152
140
  return true;
153
141
  }
154
142
 
155
- return isTabsScreenContentFocused(node.parent);
143
+ return isTabsScreenOnContentFocused(node.parent);
156
144
  };
157
145
 
158
146
  export const isCurrentFocusOnMenu = (node) => {
@@ -2,11 +2,24 @@ import { path } from "ramda";
2
2
  import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
3
3
  import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
4
4
 
5
+ import {
6
+ QUICK_BRICK_CONTENT,
7
+ QUICK_BRICK_NAVBAR,
8
+ } from "@applicaster/quick-brick-core/const";
9
+
5
10
  import { logger } from "./logger";
6
11
  import { TreeNode } from "./TreeNode";
7
12
  import { Tree } from "./Tree";
8
13
  import { subscriber } from "../functionUtils";
9
14
  import { getFocusableId, toFocusDirection } from "./utils";
15
+ import {
16
+ findSelectedMenuId,
17
+ isTabsScreenContentFocused,
18
+ findSelectedTabId,
19
+ contextWithoutScrolling,
20
+ } from "./aux";
21
+
22
+ export { contextWithoutScrolling } from "./aux";
10
23
 
11
24
  export {
12
25
  toFocusDirection,
@@ -221,7 +234,8 @@ class FocusManager {
221
234
 
222
235
  private setNextFocus(
223
236
  nextFocus: FocusManager.TouchableReactRef,
224
- options?: FocusManager.Android.CallbackOptions
237
+ options?: FocusManager.Android.CallbackOptions,
238
+ context?: FocusManager.FocusContext
225
239
  ) {
226
240
  if (nextFocus?.current?.props?.blockFocus) {
227
241
  return;
@@ -250,7 +264,7 @@ class FocusManager {
250
264
 
251
265
  FocusManager.instance.setPreviousNavigationDirection(options ?? null);
252
266
 
253
- nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
267
+ nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {}, context);
254
268
  }
255
269
  }
256
270
 
@@ -291,7 +305,8 @@ class FocusManager {
291
305
 
292
306
  setFocus(
293
307
  newFocus: FocusManager.TouchableReactRef | string,
294
- options?: FocusManager.Android.CallbackOptions
308
+ options?: FocusManager.Android.CallbackOptions,
309
+ context?: FocusManager.FocusContext
295
310
  ) {
296
311
  // Checks if element is focusable
297
312
  const { isFocusable, error } = FocusManager.isFocusable(newFocus);
@@ -316,7 +331,7 @@ class FocusManager {
316
331
  }
317
332
 
318
333
  if (newFocusRef) {
319
- FocusManager.instance.setNextFocus(newFocusRef, options);
334
+ FocusManager.instance.setNextFocus(newFocusRef, options, context);
320
335
  }
321
336
  }
322
337
  }
@@ -351,6 +366,11 @@ class FocusManager {
351
366
  FocusManager.instance.focused.onBlur(FocusManager.instance.focused, {});
352
367
  }
353
368
 
369
+ // send reset event to some handler to reset their internal state, before real reset happens
370
+ this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.RESET, {
371
+ focusedId: FocusManager.instance.focusedId,
372
+ });
373
+
354
374
  FocusManager.instance.setFocusLocal({ current: null });
355
375
  }
356
376
 
@@ -417,6 +437,60 @@ class FocusManager {
417
437
  throw new Error(`Group with id ${id} not found`);
418
438
  }
419
439
  }
440
+
441
+ isFocusOnMenu(): boolean {
442
+ return this.isFocusableChildOf(
443
+ FocusManager.instance.focusedId,
444
+ QUICK_BRICK_NAVBAR
445
+ );
446
+ }
447
+
448
+ isFocusOnContent(): boolean {
449
+ return this.isFocusableChildOf(
450
+ FocusManager.instance.focusedId,
451
+ QUICK_BRICK_CONTENT
452
+ );
453
+ }
454
+
455
+ private landFocusToWithoutScrolling = (id) => {
456
+ if (id) {
457
+ // set focus on selected menu item
458
+ const direction = undefined;
459
+
460
+ const context: FocusManager.FocusContext =
461
+ contextWithoutScrolling("back");
462
+
463
+ logger.log({ message: "landFocusToWithoutScrolling", data: { id } });
464
+
465
+ this.setFocus(id, direction, context);
466
+ }
467
+ };
468
+
469
+ // Move focus to appropriate top navigation tab with context
470
+ focusOnSelectedTab(index: number): void {
471
+ const selectedTabId = findSelectedTabId(this.tree, index);
472
+
473
+ // Set focus with back button context to tabs-menu
474
+ this.landFocusToWithoutScrolling(selectedTabId);
475
+ }
476
+
477
+ // Move focus to appropriate top navigation tab with context
478
+ focusOnSelectedTopMenuItem(index: number, sectionKey: string): void {
479
+ const selectedMenuItemId = findSelectedMenuId(this.tree, {
480
+ index,
481
+ sectionKey,
482
+ });
483
+
484
+ // Set focus with back button context to top-menu
485
+ this.landFocusToWithoutScrolling(selectedMenuItemId);
486
+ }
487
+
488
+ isTabsScreenContentFocused(): boolean {
489
+ return isTabsScreenContentFocused(
490
+ this.tree,
491
+ FocusManager.instance.focusedId
492
+ );
493
+ }
420
494
  }
421
495
 
422
496
  export const focusManager = FocusManager.getInstance();
@@ -0,0 +1,98 @@
1
+ import { isNil, pathOr } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ import {
4
+ QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
+ QUICK_BRICK_NAVBAR_SECTIONS,
7
+ } from "@applicaster/quick-brick-core/const";
8
+
9
+ const isNavBar = (node) => QUICK_BRICK_NAVBAR === node?.id;
10
+ const isContent = (node) => QUICK_BRICK_CONTENT === node?.id;
11
+
12
+ // SCREEN_PICKER_SELECTOR_CONTAINER(we assume there is only one SCREEN_PICKER)
13
+ let screenPickerSelectorContainerId;
14
+
15
+ export const onRegisterScreenPickerSelectorContainer = (id) => {
16
+ screenPickerSelectorContainerId = id;
17
+ };
18
+
19
+ export const onUnregisterScreenPickerSelectorContainer = (id) => {
20
+ // reset screenSelectorId on unregistration
21
+ if (screenPickerSelectorContainerId === id) {
22
+ screenPickerSelectorContainerId = undefined;
23
+ }
24
+ };
25
+ // SCREEN_PICKER_SELECTOR_CONTAINER
26
+
27
+ // SCREEN_PICKER_CONTENT_CONTAINER(we assume there is only one SCREEN_PICKER)
28
+ let screenPickerContentContainerId;
29
+
30
+ export const onRegisterScreenPickerContentContainer = (id) => {
31
+ screenPickerContentContainerId = id;
32
+ };
33
+
34
+ export const onUnregisterScreenPickerContentContainer = (id) => {
35
+ // reset screenSelectorId on unregistration
36
+ if (screenPickerContentContainerId === id) {
37
+ screenPickerContentContainerId = undefined;
38
+ }
39
+ };
40
+
41
+ const isScreenPickerContentContainer = (node) =>
42
+ screenPickerContentContainerId === node?.id;
43
+
44
+ // SCREEN_PICKER_CONTENT_CONTAINER
45
+
46
+ export const findSelectedMenuId = (
47
+ focusableTree,
48
+ { index, sectionKey }: { index: number; sectionKey: string }
49
+ ) => {
50
+ const sectionName = QUICK_BRICK_NAVBAR_SECTIONS[sectionKey];
51
+
52
+ return pathOr(
53
+ undefined,
54
+ ["children", index, "id"],
55
+ focusableTree.find(sectionName)
56
+ );
57
+ };
58
+
59
+ export const findSelectedTabId = (focusableTree, index: number): string => {
60
+ const screenSelectorContainerNode = focusableTree.find(
61
+ screenPickerSelectorContainerId
62
+ );
63
+
64
+ const selectedTabId = screenSelectorContainerNode.children[index]?.id;
65
+
66
+ return selectedTabId;
67
+ };
68
+
69
+ export const isTabsScreenContentFocused = (focusableTree, id) => {
70
+ const node = focusableTree.find(id);
71
+
72
+ if (isNil(node)) {
73
+ return false;
74
+ }
75
+
76
+ if (isNavBar(node)) {
77
+ return false;
78
+ }
79
+
80
+ if (isContent(node)) {
81
+ return false;
82
+ }
83
+
84
+ if (isScreenPickerContentContainer(node)) {
85
+ return true;
86
+ }
87
+
88
+ return isTabsScreenContentFocused(focusableTree, node.parentId);
89
+ };
90
+
91
+ export const contextWithoutScrolling = (
92
+ source: FocusManager.FocusContext["source"]
93
+ ): FocusManager.FocusContext => {
94
+ return {
95
+ source,
96
+ preserveScroll: true,
97
+ };
98
+ };
@@ -4,11 +4,11 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
4
4
 
5
5
  export const getFocusableId = (ref) => ref?.current?.props.id;
6
6
 
7
- type Direction = "up" | "down" | "left" | "right";
8
-
9
7
  const normalizeDirection = (direction) => direction.toLowerCase();
10
8
 
11
- const checkDirection = (direction: Direction) => {
9
+ const checkDirection = (
10
+ direction: FocusManager.Android.FocusNavigationDirections
11
+ ) => {
12
12
  invariant(!isNilOrEmpty(direction), "direction should not be empty");
13
13
 
14
14
  invariant(
@@ -17,19 +17,25 @@ const checkDirection = (direction: Direction) => {
17
17
  );
18
18
  };
19
19
 
20
- export const toFocusDirection = (direction: Direction) => {
20
+ export const toFocusDirection = (
21
+ direction: FocusManager.Android.FocusNavigationDirections
22
+ ) => {
21
23
  checkDirection(direction);
22
24
 
23
25
  return `nextFocus${capitalize(normalizeDirection(direction))}`;
24
26
  };
25
27
 
26
- export const isHorizontalDirection = (direction: Direction) => {
28
+ export const isHorizontalDirection = (
29
+ direction: FocusManager.Android.FocusNavigationDirections
30
+ ) => {
27
31
  checkDirection(direction);
28
32
 
29
33
  return ["left", "right"].includes(normalizeDirection(direction));
30
34
  };
31
35
 
32
- export const isVerticalDirection = (direction: Direction) => {
36
+ export const isVerticalDirection = (
37
+ direction: FocusManager.Android.FocusNavigationDirections
38
+ ) => {
33
39
  checkDirection(direction);
34
40
 
35
41
  return ["up", "down"].includes(normalizeDirection(direction));
@@ -365,6 +365,86 @@ function getPlayerConfiguration({ platform, version }) {
365
365
  ],
366
366
  },
367
367
  ]),
368
+ fieldsGroup(
369
+ "Partial Player (Roku only)",
370
+ "This section allows you to configure width and height of video player in Partial Player",
371
+ [
372
+ {
373
+ key: "video_theater_width",
374
+ label: "Width of player",
375
+ type: "number_input",
376
+ initial_value: 1420,
377
+ placeholder: "1420",
378
+ },
379
+ {
380
+ key: "video_theater_height",
381
+ label: "Height of player",
382
+ type: "number_input",
383
+ initial_value: 900,
384
+ placeholder: "900",
385
+ },
386
+ {
387
+ key: "full_screen_button_offset_x",
388
+ label: "Fullscreen button X",
389
+ type: "number_input",
390
+ initial_value: 160,
391
+ placeholder: "160",
392
+ },
393
+ {
394
+ key: "full_screen_button_offset_y",
395
+ label: "Fullscreen button Y",
396
+ type: "number_input",
397
+ initial_value: 160,
398
+ placeholder: "160",
399
+ },
400
+ {
401
+ key: "full_screen_button_w",
402
+ label: "Fullscreen button width",
403
+ type: "number_input",
404
+ initial_value: 120,
405
+ placeholder: "120",
406
+ },
407
+ {
408
+ key: "full_screen_button_h",
409
+ label: "Fullscreen button height",
410
+ type: "number_input",
411
+ initial_value: 120,
412
+ placeholder: "120",
413
+ },
414
+ {
415
+ key: "full_screen_button_background_color",
416
+ type: "color_picker",
417
+ label: "Fullscreen Button background color",
418
+ initial_value: "#00000000",
419
+ placeholder: "color",
420
+ label_tooltip: "Pick Color",
421
+ },
422
+ {
423
+ key: "full_screen_button_background_url",
424
+ type: "text_input",
425
+ label: "Fullscreen Button background URL",
426
+ initial_value: "pkg:/images/tv_fullscreen.png",
427
+ placeholder: "",
428
+ label_tooltip: "",
429
+ },
430
+ {
431
+ key: "full_screen_button_highlighted_background_color",
432
+ type: "color_picker",
433
+ label: "Fullscreen Button highlighted background color",
434
+ initial_value: "#00000000",
435
+ placeholder: "color",
436
+ label_tooltip: "Pick Color",
437
+ },
438
+ {
439
+ key: "full_screen_button_highlighted_background_url",
440
+ type: "text_input",
441
+ label: "Fullscreen Button highlighted URL",
442
+ initial_value: "pkg:/images/tv_fullscreen.png",
443
+ placeholder: "",
444
+ label_tooltip: "",
445
+ },
446
+ ]
447
+ ),
368
448
  fieldsGroup(
369
449
  "Skip Button",
370
450
  "This section allows you to configure the skip button styles for tv",
@@ -496,6 +576,20 @@ function getPlayerConfiguration({ platform, version }) {
496
576
  key: "skip_button_style_text_android_font_size",
497
577
  initial_value: 24,
498
578
  },
579
+ {
580
+ type: "roku_font_selector",
581
+ label_tooltip: "",
582
+ label: "Roku Font Family",
583
+ key: "skip_button_style_text_roku_font_family",
584
+ initial_value: "Ubuntu-Bold",
585
+ },
586
+ {
587
+ type: "number_input",
588
+ label_tooltip: "",
589
+ label: "Roku Font Size",
590
+ key: "skip_button_style_text_roku_font_size",
591
+ initial_value: 24,
592
+ },
499
593
  {
500
594
  type: "select",
501
595
  options: [
@@ -2904,6 +2998,18 @@ function getPlayerConfiguration({ platform, version }) {
2904
2998
  key: "android_tv_audio_player_title_font_size",
2905
2999
  initial_value: 40,
2906
3000
  },
3001
+ {
3002
+ type: "roku_font_selector",
3003
+ label: "Roku Font Family",
3004
+ key: "roku_audio_player_title_font_family",
3005
+ initial_value: "Ubuntu-Bold",
3006
+ },
3007
+ {
3008
+ type: "number_input",
3009
+ label: "Roku Font Size",
3010
+ key: "roku_audio_player_title_font_size",
3011
+ initial_value: 40,
3012
+ },
2907
3013
  ]
2908
3014
  ),
2909
3015
  // Audio Player Summary Font Family
@@ -2977,6 +3083,18 @@ function getPlayerConfiguration({ platform, version }) {
2977
3083
  key: "android_tv_audio_player_summary_font_size",
2978
3084
  initial_value: 22,
2979
3085
  },
3086
+ {
3087
+ type: "roku_font_selector",
3088
+ label: "Roku Font Family",
3089
+ key: "roku_audio_player_summary_font_family",
3090
+ initial_value: "Ubuntu-Medium",
3091
+ },
3092
+ {
3093
+ type: "number_input",
3094
+ label: "Roku Font Size",
3095
+ key: "roku_audio_player_summary_font_size",
3096
+ initial_value: 22,
3097
+ },
2980
3098
  ]
2981
3099
  ),
2982
3100
  fieldsGroup(
@@ -3344,6 +3462,60 @@ function getPlayerConfiguration({ platform, version }) {
3344
3462
  },
3345
3463
  ]
3346
3464
  ),
3465
+ fieldsGroup(
3466
+ "Roku only",
3467
+ "This section allows you to configure RAF - Roku Ad Framework settings",
3468
+ [
3469
+ {
3470
+ key: "raf_enabled",
3471
+ type: "switch",
3472
+ initial_value: false,
3473
+ },
3474
+ {
3475
+ key: "raf_url",
3476
+ type: "text_input",
3477
+ label: "Ad URL",
3478
+ label_tooltip:
3479
+ "Publisher's ad URL. The default is the Roku Ad Server with a single pre-roll placeholder, with revenue split ad sharing by default. TO GET PAID A URL MUST BE PASSED IN HERE. Note: If you are putting ads in child targetted content then your ad url will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
3480
+ },
3481
+ {
3482
+ key: "genre",
3483
+ type: "text_input",
3484
+ label: "Roku Genre",
3485
+ initial_value: "Entertainment",
3486
+ label_tooltip:
3487
+ "Choose value from Roku genre tags, from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md",
3488
+ },
3489
+ {
3490
+ key: "nielsen_enabled",
3491
+ type: "checkbox",
3492
+ initial_value: false,
3493
+ label_tooltip:
3494
+ "Required only for apps launched in the US market, See developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md for details on configuration",
3495
+ },
3496
+ {
3497
+ key: "nielsen_app_id",
3498
+ type: "text_input",
3499
+ initial_value: "",
3500
+ label_tooltip:
3501
+ "id of your app for nielsen leave blank if you don't have a specific id (that is almost always the case)",
3502
+ },
3503
+ {
3504
+ key: "nielsen_genre",
3505
+ type: "text_input",
3506
+ initial_value: "General",
3507
+ label_tooltip:
3508
+ "genre from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md#nielsen-dar-genre-tags",
3509
+ },
3510
+ {
3511
+ key: "is_kids_content",
3512
+ type: "checkbox",
3513
+ initial_value: false,
3514
+ label_tooltip:
3515
+ "If your content is directed at kids, this must be checked. See developer.roku.com/docs/developer-program/advertising/raf-api.md#setcontentgenregenres-as-string-kidscontent-as-boolean for more info. Also note your ad urls will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
3516
+ },
3517
+ ]
3518
+ ),
3347
3519
  fieldsGroup(
3348
3520
  "Audio Tracks",
3349
3521
  "This section allows you to configure default audio track behavior for videos with multiple audio tracks",
@@ -29,6 +29,9 @@ const {
29
29
  getUpdatedSecondaryImageKeys,
30
30
  } = require("./secondaryImage");
31
31
 
32
+ const DEFAULT_GRADIENT_IMAGE =
33
+ "data:image/png;base64,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";
34
+
32
35
  module.exports = {
33
36
  fontKey,
34
37
  fontKeyTV,
@@ -47,5 +50,6 @@ module.exports = {
47
50
  tvProgressBar,
48
51
  secondaryImage,
49
52
  getUpdatedSecondaryImageKeys,
53
+ DEFAULT_GRADIENT_IMAGE,
50
54
  compact,
51
55
  };
@@ -485,6 +485,18 @@ const TV_MENU_LABEL_FIELDS = [
485
485
  type: ZAPPIFEST_FIELDS.number_input,
486
486
  suffix: "LG letter spacing",
487
487
  },
488
+ {
489
+ type: ZAPPIFEST_FIELDS.font_selector.roku,
490
+ suffix: "Roku font family",
491
+ },
492
+ {
493
+ type: ZAPPIFEST_FIELDS.number_input,
494
+ suffix: "Roku font size",
495
+ },
496
+ {
497
+ type: ZAPPIFEST_FIELDS.number_input,
498
+ suffix: "Roku line height",
499
+ },
488
500
  {
489
501
  type: ZAPPIFEST_FIELDS.select,
490
502
  suffix: "text transform",
@@ -424,6 +424,12 @@ const titleFields = [
424
424
  key: "vizio_font_family",
425
425
  initial_value: fontFamily,
426
426
  },
427
+ {
428
+ type: "roku_font_selector",
429
+ label: "Roku TV Font Family",
430
+ key: "roku_font_family",
431
+ initial_value: fontFamily,
432
+ },
427
433
  ...generateFontConfiguration(),
428
434
  // text transform
429
435
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "14.0.0-rc.60",
3
+ "version": "14.0.0-rc.62",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "14.0.0-rc.60",
30
+ "@applicaster/applicaster-types": "14.0.0-rc.62",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -13,5 +13,5 @@ export const getPlayerActionButtons = (configuration: any) => {
13
13
  return [];
14
14
  }
15
15
 
16
- return take(map(buttonsString.split(","), trim), 2);
16
+ return take(2, map(buttonsString.split(","), trim));
17
17
  };
@@ -5,3 +5,9 @@ export const getPickerSelectorId = (id) => `PickerSelector.${id}`;
5
5
  export const SCREEN_PICKER_CONTAINER = "ScreenPickerContainer";
6
6
 
7
7
  export const getScreenPickerId = (id) => `${SCREEN_PICKER_CONTAINER}.${id}`;
8
+
9
+ export const getScreenPickerSelectorContainerId = (id) =>
10
+ `${getScreenPickerId(id)}-screen-selector`;
11
+
12
+ export const getScreenPickerContentContainerId = (id) =>
13
+ `${getScreenPickerId(id)}-screen-container`;
@@ -0,0 +1,30 @@
1
+ import { endsWith } from "../endsWith";
2
+
3
+ describe("endsWith", () => {
4
+ it("returns false when str is null", () => {
5
+ expect(endsWith("a", null)).toBe(false);
6
+ });
7
+
8
+ it("returns false when str is undefined", () => {
9
+ expect(endsWith("a", undefined)).toBe(false);
10
+ });
11
+
12
+ it("returns true when string ends with target", () => {
13
+ expect(endsWith("lo", "hello")).toBe(true);
14
+ expect(endsWith("", "hello")).toBe(true); // empty target always matches
15
+ });
16
+
17
+ it("returns false when string does not end with target", () => {
18
+ expect(endsWith("yo", "hello")).toBe(false);
19
+ });
20
+
21
+ it("works with single character target", () => {
22
+ expect(endsWith("o", "hello")).toBe(true);
23
+ expect(endsWith("x", "hello")).toBe(false);
24
+ });
25
+
26
+ it("is case-sensitive", () => {
27
+ expect(endsWith("Lo", "hello")).toBe(false);
28
+ expect(endsWith("lo", "hello")).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,19 @@
1
+ import { omit } from "../omit";
2
+
3
+ test("example 1", () => {
4
+ const path = ["a", "b", "c"];
5
+ const record = { a: 1, b: 2, c: 3 };
6
+
7
+ const output = {};
8
+
9
+ expect(omit(path, record)).toEqual(output);
10
+ });
11
+
12
+ test("example 2", () => {
13
+ const path = ["a", "b"];
14
+ const record = { a: 1, b: 2, c: 3 };
15
+
16
+ const output = { c: 3 };
17
+
18
+ expect(omit(path, record)).toEqual(output);
19
+ });
@@ -0,0 +1,33 @@
1
+ import { path } from "../path";
2
+
3
+ test("example 1", () => {
4
+ const route = ["a", "b", "c"];
5
+ const xs = { a: { b: { c: 1 } } };
6
+
7
+ const output = 1;
8
+
9
+ expect(path(route, xs)).toEqual(output);
10
+ });
11
+
12
+ test("example 2", () => {
13
+ const route = ["a", "b"];
14
+ const xs = { a: { b: { c: 1 } } };
15
+
16
+ const output = { c: 1 };
17
+
18
+ expect(path(route, xs)).toEqual(output);
19
+ });
20
+
21
+ test("example 3", () => {
22
+ const route = ["a", "b", "x"];
23
+ const xs = { a: { b: { c: 1 } } };
24
+
25
+ expect(path(route, xs)).toBeUndefined();
26
+ });
27
+
28
+ test("example 4", () => {
29
+ const route = ["a", "b", "c"];
30
+ const xs = undefined;
31
+
32
+ expect(path(route, xs)).toBeUndefined();
33
+ });
@@ -0,0 +1,40 @@
1
+ import { take } from "../take";
2
+
3
+ describe("take", () => {
4
+ it("takes n elements from the beginning", () => {
5
+ expect(take(2, [1, 2, 3])).toEqual([1, 2]);
6
+ });
7
+
8
+ it("returns the whole array if n is larger than length", () => {
9
+ expect(take(5, [1, 2, 3])).toEqual([1, 2, 3]);
10
+ });
11
+
12
+ it("returns empty array if n is 0", () => {
13
+ expect(take(0, [1, 2, 3])).toEqual([]);
14
+ });
15
+
16
+ it("returns empty array for empty input array", () => {
17
+ expect(take(2, [])).toEqual([]);
18
+ });
19
+
20
+ it("returns empty array if n is negative", () => {
21
+ expect(take(-1, [1, 2, 3])).toEqual([]);
22
+ });
23
+
24
+ it("works with strings in array", () => {
25
+ expect(take(2, ["a", "b", "c"])).toEqual(["a", "b"]);
26
+ });
27
+
28
+ it("works with objects in array", () => {
29
+ const arr = [{ id: 1 }, { id: 2 }];
30
+ expect(take(1, arr)).toEqual([{ id: 1 }]);
31
+ });
32
+
33
+ it("returns empty array if input is not an array", () => {
34
+ // @ts-expect-error testing non-array input
35
+ expect(take(2, null)).toEqual([]);
36
+
37
+ // @ts-expect-error testing non-array input
38
+ expect(take(2, undefined)).toEqual([]);
39
+ });
40
+ });
@@ -0,0 +1,9 @@
1
+ import { isNil } from "lodash";
2
+
3
+ export const endsWith = (target, str) => {
4
+ if (isNil(str)) {
5
+ return false;
6
+ }
7
+
8
+ return str.endsWith(target);
9
+ };
package/utils/index.ts CHANGED
@@ -8,6 +8,14 @@ export { find } from "./find";
8
8
 
9
9
  export { pathOr } from "./pathOr";
10
10
 
11
+ export { path } from "./path";
12
+
13
+ export { omit } from "./omit";
14
+
15
+ export { endsWith } from "./endsWith";
16
+
17
+ export { take } from "./take";
18
+
11
19
  export {
12
20
  cloneDeep as clone,
13
21
  flatten,
@@ -19,10 +27,11 @@ export {
19
27
  has,
20
28
  flatMap,
21
29
  difference,
22
- take,
23
30
  pick,
24
31
  map,
25
32
  trim,
26
33
  toString,
27
34
  last,
35
+ toLower,
36
+ isEqual as equals,
28
37
  } from "lodash";
package/utils/omit.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { omit as Lodash_omit } from "lodash";
2
+
3
+ export const omit = (path, record) => {
4
+ return Lodash_omit(record, path);
5
+ };
package/utils/path.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { get } from "lodash";
2
+
3
+ export const path = (route, record) => {
4
+ return get(record, route, undefined);
5
+ };
package/utils/take.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { take as Ltake } from "lodash";
2
+
3
+ export function take<T>(n: number, xs: T[]): T[] {
4
+ return Ltake(xs, n);
5
+ }