@applicaster/zapp-react-native-utils 15.0.0-alpha.3564377339 → 15.0.0-alpha.3564837831

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.
@@ -1,11 +1,31 @@
1
- import * as R from "ramda";
2
-
3
- import { focusManager } from "../focusManager";
1
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
2
+ import { QUICK_BRICK_CONTENT } from "@applicaster/quick-brick-core/const";
3
+ import { isNil, isEmpty } from "@applicaster/zapp-react-native-utils/utils";
4
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
4
5
 
5
6
  let riverFocusData = {};
6
7
  let initialyPresentedScreenFocused = false;
7
8
 
8
9
  export const riverFocusManager = (function () {
10
+ /**
11
+ * Create unique key that will be used for save focused group data inside specific screen
12
+ * @param {{ screenId: string, isInsideContainer: boolean }}
13
+ * screenId Unique Id of the screen from layout.json
14
+ * isInsideContainer If this screen a screen picker child
15
+ *
16
+ */
17
+ function screenFocusableGroupId({
18
+ screenId,
19
+ isInsideContainer,
20
+ }: {
21
+ screenId: string;
22
+ isInsideContainer: Option<boolean>;
23
+ }) {
24
+ return `${QUICK_BRICK_CONTENT}-${screenId}${
25
+ isNil(isInsideContainer) ? "" : "-isInsideContainer"
26
+ }`;
27
+ }
28
+
9
29
  function setScreenFocusableData({
10
30
  screenFocusableGroupId,
11
31
  groupId,
@@ -78,8 +98,8 @@ export const riverFocusManager = (function () {
78
98
  }) {
79
99
  // Check if screen should be focused
80
100
  const shouldFocus =
81
- (initialyPresentedScreenFocused === false && R.isEmpty(riverFocusData)) ||
82
- R.compose(R.not, R.isNil)(riverFocusData[screenFocusableGroupId]) ||
101
+ (initialyPresentedScreenFocused === false && isEmpty(riverFocusData)) ||
102
+ isNotNil(riverFocusData[screenFocusableGroupId]) ||
83
103
  isDeepLink;
84
104
 
85
105
  // TODO: Uncommit it to start fixing bug where selection wrong item
@@ -118,19 +138,6 @@ export const riverFocusManager = (function () {
118
138
  }
119
139
  }
120
140
 
121
- /**
122
- * Create unique key that will be used for save focused group data inside specific screen
123
- * @param {{ screenId: string, isInsideContainer: boolean }}
124
- * screenId Unique Id of the screen from layout.json
125
- * isInsideContainer If this screen a screen picker child
126
- *
127
- */
128
- function screenFocusableGroupId({ screenId, isInsideContainer }) {
129
- return `RiverFocusableGroup-${screenId}${
130
- R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
131
- }`;
132
- }
133
-
134
141
  return {
135
142
  setScreenFocusableData,
136
143
  clearAllScreensData,
@@ -71,6 +71,9 @@ exports[`focusManagerIOS should be defined 1`] = `
71
71
  "invokeHandler": [Function],
72
72
  "isChildOf": [Function],
73
73
  "isFocusOn": [Function],
74
+ "isFocusOnContent": [Function],
75
+ "isFocusOnMenu": [Function],
76
+ "isFocusOnTabsScreen": [Function],
74
77
  "isGroupItemFocused": [Function],
75
78
  "moveFocus": [Function],
76
79
  "on": [Function],
@@ -4,7 +4,11 @@ import * as R from "ramda";
4
4
  import {
5
5
  isCurrentFocusOn,
6
6
  isChildOf as isChildOfUtils,
7
- } from "../focusManagerAux/utils";
7
+ isPartOfMenu,
8
+ isPartOfContent,
9
+ isPartOfTabsScreen,
10
+ } from "../focusManagerAux/utils/index.ios";
11
+
8
12
  import { Tree } from "./treeDataStructure/Tree";
9
13
  import { findFocusableNode } from "./treeDataStructure/Utils";
10
14
  import { subscriber } from "../../functionUtils";
@@ -13,6 +17,7 @@ import { findChild } from "./utils";
13
17
  import {
14
18
  emitRegistered,
15
19
  emitUnregistered,
20
+ emitWillFocused,
16
21
  } from "../focusManagerAux/utils/utils.ios";
17
22
 
18
23
  const { FocusableManagerModule } = NativeModules;
@@ -194,9 +199,9 @@ export const focusManager = (function () {
194
199
  registerItem(id, component);
195
200
  }
196
201
 
197
- const parentId = component?.props?.groupId;
202
+ const groupId = component?.props?.groupId;
198
203
 
199
- emitRegistered({ id, parentId, isGroup });
204
+ emitRegistered({ id, groupId, isGroup });
200
205
  }
201
206
 
202
207
  function unregister(id, { group = false } = {}) {
@@ -279,12 +284,16 @@ export const focusManager = (function () {
279
284
  function setFocus(
280
285
  id: string,
281
286
  direction?: FocusManager.IOS.Direction,
282
- options?: Partial<{ groupFocusedChanged: boolean }>,
287
+ options?: Partial<{
288
+ groupFocusedChanged: boolean;
289
+ }>,
283
290
  callback?: any
284
291
  ) {
285
292
  blur(direction);
286
293
  currentFocus = id;
287
294
 
295
+ emitWillFocused(id);
296
+
288
297
  // Extract groupFocusedChanged with a default value of false
289
298
  let groupFocusedChanged = false;
290
299
 
@@ -418,6 +427,28 @@ export const focusManager = (function () {
418
427
  return id && isCurrentFocusOn(id, currentFocusNode);
419
428
  }
420
429
 
430
+ function isFocusOnMenu(): boolean {
431
+ const currentFocusable = getCurrentFocus();
432
+
433
+ return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
434
+ }
435
+
436
+ function isFocusOnContent(): boolean {
437
+ const currentFocusable = getCurrentFocus();
438
+
439
+ return isPartOfContent(focusableTree, currentFocusable?.props?.id);
440
+ }
441
+
442
+ function isFocusOnTabsScreen(tabsScreenContainerId: string): boolean {
443
+ const currentFocusable = getCurrentFocus();
444
+
445
+ return isPartOfTabsScreen(
446
+ focusableTree,
447
+ tabsScreenContainerId,
448
+ currentFocusable?.props?.id
449
+ );
450
+ }
451
+
421
452
  function isChildOf(childId, parentId): boolean {
422
453
  return isChildOfUtils(focusableTree, childId, parentId);
423
454
  }
@@ -444,6 +475,9 @@ export const focusManager = (function () {
444
475
  isGroupItemFocused,
445
476
  getPreferredFocusChild,
446
477
  isFocusOn,
478
+ isFocusOnMenu,
479
+ isFocusOnContent,
480
+ isFocusOnTabsScreen,
447
481
  isChildOf,
448
482
  };
449
483
  })();
@@ -0,0 +1,122 @@
1
+ import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ import {
4
+ QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
9
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
10
+ const isRoot = (node) => node?.id === "root";
11
+
12
+ export const isPartOfTabsScreen = (
13
+ focusableTree,
14
+ tabsScreenContainerId,
15
+ id
16
+ ) => {
17
+ const node = focusableTree.findInTree(id);
18
+
19
+ if (isNil(node)) {
20
+ return false;
21
+ }
22
+
23
+ if (isRoot(node)) {
24
+ return false;
25
+ }
26
+
27
+ if (isNavBar(node)) {
28
+ return false;
29
+ }
30
+
31
+ if (isContent(node)) {
32
+ return false;
33
+ }
34
+
35
+ if (node?.id === tabsScreenContainerId) {
36
+ return true;
37
+ }
38
+
39
+ return isPartOfTabsScreen(
40
+ focusableTree,
41
+ tabsScreenContainerId,
42
+ node.parent?.id
43
+ );
44
+ };
45
+
46
+ export const isPartOfMenu = (focusableTree, id): boolean => {
47
+ const node = focusableTree.findInTree(id);
48
+
49
+ if (isNil(node)) {
50
+ return false;
51
+ }
52
+
53
+ if (isRoot(node)) {
54
+ return false;
55
+ }
56
+
57
+ if (isNavBar(node)) {
58
+ return true;
59
+ }
60
+
61
+ if (isContent(node)) {
62
+ return false;
63
+ }
64
+
65
+ return isPartOfMenu(focusableTree, node.parent?.id);
66
+ };
67
+
68
+ export const isPartOfContent = (focusableTree, id) => {
69
+ const node = focusableTree.findInTree(id);
70
+
71
+ if (isNil(node)) {
72
+ return false;
73
+ }
74
+
75
+ if (isRoot(node)) {
76
+ return false;
77
+ }
78
+
79
+ if (isNavBar(node)) {
80
+ return false;
81
+ }
82
+
83
+ if (isContent(node)) {
84
+ return true;
85
+ }
86
+
87
+ return isPartOfContent(focusableTree, node.parent?.id);
88
+ };
89
+
90
+ export const isCurrentFocusOn = (id, node) => {
91
+ if (!node) {
92
+ return false;
93
+ }
94
+
95
+ if (isRoot(node)) {
96
+ return false;
97
+ }
98
+
99
+ if (node?.id === id) {
100
+ return true;
101
+ }
102
+
103
+ return isCurrentFocusOn(id, node.parent);
104
+ };
105
+
106
+ export const isChildOf = (focusableTree, childId, parentId) => {
107
+ if (isNil(childId) || isNil(parentId)) {
108
+ return false;
109
+ }
110
+
111
+ const childNode = focusableTree.findInTree(childId);
112
+
113
+ if (isNil(childNode)) {
114
+ return false;
115
+ }
116
+
117
+ if (childNode.parent?.id === parentId) {
118
+ return true;
119
+ }
120
+
121
+ return isChildOf(focusableTree, childNode.parent?.id, parentId);
122
+ };
@@ -1,7 +1,9 @@
1
1
  import { ReplaySubject, Subject } from "rxjs";
2
- import { filter, switchMap, take } from "rxjs/operators";
2
+ import { filter, switchMap, take, withLatestFrom, map } from "rxjs/operators";
3
+
3
4
  import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
4
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
6
+ import { isPartOfMenu, isPartOfContent } from "./index.ios";
5
7
 
6
8
  type FocusableID = string;
7
9
  type RegistrationEvent = {
@@ -11,13 +13,23 @@ type RegistrationEvent = {
11
13
 
12
14
  let focusableViewRegistrationSubject$ = new Subject<{
13
15
  id: FocusableID;
14
- parentId: FocusableID;
16
+ groupId: FocusableID;
15
17
  }>();
16
18
 
17
19
  let focusableGroupRegistrationSubject$ = new ReplaySubject<{
18
20
  id: FocusableID;
19
21
  }>();
20
22
 
23
+ let focusableNativeViewRegistrationSubject$ = new Subject<{
24
+ id: FocusableID;
25
+ groupId: FocusableID;
26
+ }>();
27
+
28
+ let focusableNativeGroupRegistrationSubject$ = new ReplaySubject<{
29
+ id: FocusableID;
30
+ groupId: FocusableID;
31
+ }>();
32
+
21
33
  const isFocusableButton = (id: Option<FocusableID>): boolean =>
22
34
  id && id.includes?.(BUTTON_PREFIX);
23
35
 
@@ -41,35 +53,90 @@ export const resetFocusableRegistration = () => {
41
53
  focusableGroupRegistrationSubject$.complete();
42
54
  }
43
55
 
56
+ if (!focusableNativeViewRegistrationSubject$.closed) {
57
+ focusableNativeViewRegistrationSubject$.complete();
58
+ }
59
+
60
+ if (!focusableNativeGroupRegistrationSubject$.closed) {
61
+ focusableNativeGroupRegistrationSubject$.complete();
62
+ }
63
+
44
64
  focusableViewRegistrationSubject$ = new Subject<{
45
65
  id: FocusableID;
46
- parentId: FocusableID;
66
+ groupId: FocusableID;
47
67
  }>();
48
68
 
49
69
  focusableGroupRegistrationSubject$ = new ReplaySubject<{
50
70
  id: FocusableID;
51
71
  }>();
72
+
73
+ focusableNativeViewRegistrationSubject$ = new Subject<{
74
+ id: FocusableID;
75
+ groupId: FocusableID;
76
+ }>();
77
+
78
+ focusableNativeGroupRegistrationSubject$ = new ReplaySubject<{
79
+ id: FocusableID;
80
+ groupId: FocusableID;
81
+ }>();
82
+ };
83
+
84
+ const focusableNativeViewRegistration = ({ focusableView, focusableGroup }) => {
85
+ return focusableNativeViewRegistrationSubject$.pipe(
86
+ filter(
87
+ (focusableNativeView) => focusableNativeView.id === focusableView.id
88
+ ),
89
+ take(1),
90
+ switchMap((focusableNativeView) =>
91
+ // start waiting registration of its parent FocusableNativeGroup
92
+ focusableNativeGroupRegistrationSubject$.pipe(
93
+ filter(
94
+ (focusableNativeGroup) =>
95
+ focusableNativeGroup.id === focusableNativeView.groupId
96
+ ),
97
+ take(1),
98
+ map((focusableNativeGroup) => ({
99
+ focusableNativeGroup,
100
+ focusableNativeView,
101
+ focusableView,
102
+ focusableGroup,
103
+ }))
104
+ )
105
+ )
106
+ );
52
107
  };
53
108
 
54
109
  export const firstFocusableViewRegistrationFactory = () =>
55
110
  focusableViewRegistrationSubject$.pipe(
56
111
  take(1), // we care about only first FocusableView registration
57
- switchMap(({ parentId }) =>
58
- // start waiting registration of its parent
112
+ switchMap((focusableView) =>
113
+ // start waiting registration of its parent FocusableGroup
59
114
  focusableGroupRegistrationSubject$.pipe(
60
- filter(({ id }) => id === parentId)
115
+ filter((focusableGroup) => focusableGroup.id === focusableView.groupId),
116
+ take(1),
117
+ map((focusableGroup) => ({
118
+ focusableView,
119
+ focusableGroup,
120
+ }))
61
121
  )
62
122
  ),
63
- take(1)
123
+ // start waiting registration for FocusableNativeView and its parent FocusableNativeGroup
124
+ switchMap(({ focusableView, focusableGroup }) =>
125
+ focusableNativeViewRegistration({
126
+ focusableView,
127
+ focusableGroup,
128
+ })
129
+ )
64
130
  );
65
131
 
132
+ // registration on RN level(into RN focusManager)
66
133
  export const emitRegistered = ({
67
134
  id,
68
- parentId,
135
+ groupId,
69
136
  isGroup,
70
137
  }: {
71
138
  id: Option<FocusableID>;
72
- parentId: Option<FocusableID>;
139
+ groupId: Option<FocusableID>;
73
140
  isGroup: boolean;
74
141
  }): void => {
75
142
  if (isFocusableButton(id)) {
@@ -80,13 +147,96 @@ export const emitRegistered = ({
80
147
  focusableGroupRegistrationSubject$.next({ id });
81
148
  }
82
149
 
83
- if (!isGroup && id && parentId) {
84
- focusableViewRegistrationSubject$.next({ id, parentId });
150
+ if (!isGroup && id && groupId) {
151
+ focusableViewRegistrationSubject$.next({ id, groupId });
85
152
  }
86
153
  };
87
154
 
155
+ // unregistration on RN level(into RN focusManager)
88
156
  export const emitUnregistered = (id: Option<FocusableID>): void => {
89
157
  if (isFocusableButton(id)) {
90
158
  registeredSubject$.next({ id, registered: false });
91
159
  }
92
160
  };
161
+
162
+ // registration focusableNativeView and focusableNativeGroup
163
+ export const emitNativeRegistered = ({
164
+ id,
165
+ groupId,
166
+ isGroup,
167
+ }: {
168
+ id: Option<FocusableID>;
169
+ groupId: Option<FocusableID>;
170
+ isGroup: boolean;
171
+ }): void => {
172
+ if (!isGroup && id && groupId) {
173
+ focusableNativeViewRegistrationSubject$.next({ id, groupId });
174
+ }
175
+
176
+ if (isGroup && id && groupId) {
177
+ focusableNativeGroupRegistrationSubject$.next({ id, groupId });
178
+ }
179
+ };
180
+
181
+ // Focusable has been focused
182
+ const willFocusedSubject$ = new Subject<FocusableID>();
183
+ const didFocusedSubject$ = new Subject<FocusableID>();
184
+
185
+ export const willFocused$ = willFocusedSubject$.asObservable();
186
+
187
+ export const didFocused$ = didFocusedSubject$.asObservable();
188
+
189
+ export const emitWillFocused = (id: FocusableID): void => {
190
+ willFocusedSubject$.next(id);
191
+ };
192
+
193
+ export const emitDidFocused = (id: FocusableID): void => {
194
+ didFocusedSubject$.next(id);
195
+ };
196
+
197
+ export const topMenuItemDidFocused$ = didFocused$.pipe(
198
+ filter((id) => id && isPartOfMenu(focusManager.focusableTree, id))
199
+ );
200
+
201
+ export const contentDidFocused$ = didFocused$.pipe(
202
+ filter((id) => {
203
+ const isContent = isPartOfContent(focusManager.focusableTree, id);
204
+
205
+ return id && isContent;
206
+ })
207
+ );
208
+ // Focusable has been focused
209
+
210
+ const createFocusableRegistry = () => {
211
+ const subject$ = new ReplaySubject<FocusableID | undefined>(1);
212
+
213
+ return {
214
+ observable$: subject$.asObservable(),
215
+ register: (id: FocusableID) => {
216
+ // save focusable_id on registration
217
+ subject$.next(id);
218
+ },
219
+ unregister: () => {
220
+ // reset focusable_id on unregistration
221
+ subject$.next(undefined);
222
+ },
223
+ };
224
+ };
225
+
226
+ /// HOME_TOP_MENU_ITEM
227
+ export const HomeTopMenuItemRegistry = createFocusableRegistry();
228
+
229
+ export const homeTopMenuItemDidFocused$ = topMenuItemDidFocused$.pipe(
230
+ withLatestFrom(HomeTopMenuItemRegistry.observable$),
231
+ filter(([id, homeId]) => id === homeId)
232
+ );
233
+
234
+ /// TABS SCREEN
235
+ export const TabsScreenContentContainerRegistry = createFocusableRegistry();
236
+
237
+ export const TabsScreenScreenSelectorContainerRegistry =
238
+ createFocusableRegistry();
239
+ /// TABS SCREEN
240
+
241
+ /// SEARCH_INPUT
242
+ export const SearchInputRegistry = createFocusableRegistry();
@@ -678,6 +678,22 @@ function getPlayerConfiguration({ platform, version }) {
678
678
  }
679
679
 
680
680
  if (isMobile(platform)) {
681
+ localizations.fields.push(
682
+ {
683
+ type: "text_input",
684
+ label: "Restrict playback on mobile networks alert title",
685
+ key: "mobile_connection_restricted_alert_title",
686
+ initial_value: "Restricted Connection Type",
687
+ },
688
+ {
689
+ type: "text_input",
690
+ label: "Restrict playback on mobile networks alert message",
691
+ key: "mobile_connection_restricted_alert_message",
692
+ initial_value:
693
+ "This content can only be viewed over a Wi-Fi or LAN network.",
694
+ }
695
+ );
696
+
681
697
  general.fields.push(
682
698
  {
683
699
  section: "Default Timestamp Type",
@@ -1200,40 +1216,35 @@ function getPlayerConfiguration({ platform, version }) {
1200
1216
  label: "Playback Speed 0.8x",
1201
1217
  type: "uploader",
1202
1218
  label_tooltip: "Playback Speed 0.8x",
1203
- initial_value:
1204
- "data:image/png;base64,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",
1219
+ initial_value: null,
1205
1220
  },
1206
1221
  {
1207
1222
  key: "speed_1",
1208
1223
  label: "Playback Speed 1x",
1209
1224
  type: "uploader",
1210
1225
  label_tooltip: "Playback Speed 1x",
1211
- initial_value:
1212
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAI4SURBVHgB7dzhcRJBGIDhT8cCkg6SDmIHsQNLiBUYKwAqUCtIC5ZACVqBdIAdxN3JMSLecXcE8Tt5npkdJlxyf97ZcLcLRAAAAAAAAAAAAAAAAAAAAAAAAADAMb2IiViv17fl4aGMq45fub68vFwFv3kVyZWwF+VhVsZ9MFrqwANmLT1SBm5mbQ37NniWl5FMifu+PHwPcY8izQwuYa/iadbeBkeTaQbfxf64P8r4GoyS7l90h89lXJfxLRgl+23Sqox35f52WX8o/8aDcTLP4EUZrzdxOUzGGbws40MJ6/X2CDIFrhdRNeyn4GjSBBb275jKVTQHEvg/d/aB69Lo+sljGR/jADvnmEUiZx242a2qr/0XzVP35bmHkeeY7Zxj3pw3hXOfwTctz90NjdzEnbccuo0kzj1w1712b+Q9cfed9+TOOnCzSrboONwZuSfuopz3SyRx9hdZJcY8RkQeELfr2D/hNimGR55a3Cr9m+5OpcZpdqvabnNq5HpBdtPx5ynjVmbwlp6ZPLm4lcA7eiLvSh23ErjFwMjp41YCd3t85vEUBG7Rc7W8Mc+27txG4B0D426kjyzwlp64y47nU0cWuDFgEeNNdF94pY0scAxfoeq5uk4Z2Yb/yOXHqUW24X/A2vKAyGk+OGfDv13vIsaBy5onZ8P/T4NXqPZEtuGfwdaG/yp+vfF+HiNsRV5tnSPNhv9kvoSlaj75f9F2zBewAAAAAAAAAAAAAAAAAAAAAAAAwHT8BNhH51wNSx95AAAAAElFTkSuQmCC",
1226
+ initial_value: null,
1213
1227
  },
1214
1228
  {
1215
1229
  key: "speed_1_2",
1216
1230
  label: "Playback Speed 1.2x",
1217
1231
  type: "uploader",
1218
1232
  label_tooltip: "Playback Speed 1.2x",
1219
- initial_value:
1220
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAUjSURBVHgB7d3bUeNIFAbgA7Xv2EUCmgiWjWBNBAwRYCKYdQQrIliIYEUEw0awymDIYDQBUFC8g+eccWsulPomteTu1v9VdZlSGyP0W1KrWxciAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgPgc0Mw8Pj4u+KUt4mm5XDaUqVED5oX5gV/ea6o3vGDvaQI8Hyt+ueAir4XmbTIvNZebnAIfJWBeoCf88g/tFqjOKS/ImkbE8/FezUdBfu5o9wVsKHGHFJBs/rjIAv1E5nBHpebjX/7xI/mHK+SL8Zk/429K3G8UiNoMykItaI/UPvZ/Lic0XMmfR7wmX1GiBq/BP621slAL2j9Z60KE25KQ/6JEDdoHD9jHieD7YJ6fNe22IjpPXG5p16Bq1DT5MpyReZciv/eO5/eJEtN7E63C/Wh5myyQBU1Hu888ODi42263lx0h1Vyu1ZdDvqxd8yvTZC0uKTFDNtGm4GQhbrjc0ERUy73QVNeLxeLctAZyXcUv56R3QQkK2or+9oGHhxXtNmfXNK2Voe6SHKhdxq2muuAvUUGJCRlww+X06Ojock/7qj810+89j2fvDHUFJSZUwLIp/mPsjgsL3S7jC/mZpHdtKkOPg2VhbPYcbEuOVbs2rw35mbJROLohAdeqYRKFgF+ywlCX3GFS7010xiMwZ5rpzVSDIyEFb0WnTLWS1111fHRQU4IQ8K+0vWCvr69J9kcjYEWNHK001c5jxNIvz2XLpfdolIyEtZ/x/Pw8qIMFAdP3ExNKTXVDjl2UqruzHZgoaDdQ4RWyGuZct5/x8vJSqRGyXmYfMC88WUNMvW6nHh03q45pziG/Cdf2uU5mHbAKtzK8xfesjloz3RqyIVzRu/U+24Adwr3y7U+XfgFujOn6srUhW8K9GnJIOsuAHcMtqYfj4+O1T8gO4ZY0wOwCdgj3duhCdQ157HDFrAJ2DHdNATiELCcmrjX1QcIVswl4ynBblpB1540FC1fMImB1nFsZ3hI83JYl5LeChiuyD1jt70yt4dHCbTmGHDxckXXAKtzS8JbRw23xYMXW8hZbfb+/S5mKKVxLa7nl3a3pItiVDaGpoTs5NVf6YeW8qjuP300t3FbwKymiDLgrIJ4m3XXWfuGEwpX/p6slHTTk6AJWIzJlR5UsDDnR/tTwu7ZwGy6Vuo6qF9dTg1w6MR4eHireN3cNBwYLOcY1+MxQt5Khs661WF0/VJJZQbtrqPpquLyzvcm1h0pa1xwyjRlyjI0s29jnief0Sfl2P/r2XfuKMWDbeczRnvimdi9rTbX2ONch5N5f3hgDrgx1N8u4r/BbaaZbOzEsIRfUU3QBq0aMXEvUvKmS1m/s1+nWHdOce6gMIffeao3dyJJj10ZTp5v+beCcN0s17farsk9uHFqvFenPqAjFuPVQ813wjx/UJO+TBiRk/gz5Oxfq7w0a8AcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANif7B8Q/dOlJHI7iEJNvlcl+8tCsg5Y3R/Ldm1QuUz46aI2ud9lx+XCr1HubhOLLNdgh6eQdjn3uZNPKnIN+DP5XzQtl6ha77+Rmuw20eoOOgX5K4bcfSdWOe6Dh9yMJYobuYSUY8BDnj2Y1XMLRY4BD7lJS3LPJrTJMeAht1lK7tmENmhF/4BWdEI25C/L3qwsA1YdFj6BXcX0LOSQcu+LLvnF1g258b2XVUrmMppUcvmdfhznNlz+43Kd+2jSV0z9PFgMpokeAAAAAElFTkSuQmCC",
1233
+ initial_value: null,
1221
1234
  },
1222
1235
  {
1223
1236
  key: "speed_1_5",
1224
1237
  label: "Playback Speed 1.5x",
1225
1238
  type: "uploader",
1226
1239
  label_tooltip: "Playback Speed 1.5x",
1227
- initial_value:
1228
- "data:image/png;base64,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",
1240
+ initial_value: null,
1229
1241
  },
1230
1242
  {
1231
1243
  key: "speed_2",
1232
1244
  label: "Playback Speed 2x",
1233
1245
  type: "uploader",
1234
1246
  label_tooltip: "Playback Speed 2x",
1235
- initial_value:
1236
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQnSURBVHgB7d2PUdRAFMfxh2MBUIGxArECjwqECjwqUCowV4FYgdABVCAdSAekg7ME35pFgnOb3ZDAvmy+n5lMGJLL3dzvkuyfZCMCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2LMnM7Hdbvd11p2ag4ODRtDLdMAa6kpnH3U61qkKrHbrp0sN/EbwiMmAfbBfdVrJMI1OZxr0leAvcwFruC7YWsY515DPBLYC1nC/6eyLTONCQz6VhXslRvg9d6pwnbX/wSyaiT1Yg6h0dhdZzZ1Xr6U9zzqHOn2QtgDW52jJhS8rAf/Q2Tqw2JWQT0JVIv/j+CnhUvaNvvZIFip7wL5+uw0sbqTdA5vINiqd/ZK2fryLbuLgtyyQhXPwYc+yTUpjhl/ne88qK1koCwGvQgs0uAtJ17duJQtlIeDQYfVWBojs6fuyUK8lP1c63hXmlOfMRZ5/newBT1WF0YJW37m8kYUy09Axgb6ABx3uSzKb7sI+vqrlqknVjsXJ9WC/HRlTpZpiG1MqZQ92zZxVYNmlJPBNpdv2z6c1cfrXjdrG1Ga/B+sX+Vln54HFV7onnUh8GytpW8O6BnVWBFrjTnJ3Xc56D/YFq1C47hCZ2mW46/y99qGlfI5QU+uhZDbbgDtt0CFJrWBeqBAWDTnSjn4jmc0y4E64oQYMF+65JPJVtU1gcTDkSLgbC71YszsHJ/QeuWuz1vK0bdfSFth2eXROTgi3FgNmFXBCuLf6xb6Xce9RSyTkuYTrzOmyWVdg6Tssu/Po0RT1z0jIjYR/YKbCdWYR8EuG23nPWsIh72IuXMd8wDnC7bx3LWkhmwzXsX7he7ZwO5+hlv6QzYbrmK0mWQjXezNyeVZW72wwEW6ktNxl9hpsc3uwfqmfZF7hOsnNmi/NVMA+3AsJh9tI24CfM9ybwP9Nhmzpzob7cEMaSbiEdoLPEWvEcH3Lg5s1c7Fy4Xss3PueoUaeKKVdeEgL1ZBmzZwsXPi+kv5eoUnoF74X+Rx9N77trArNIWQLh+hKMtOgjmVguI7/f9/h+lgyK+miuzGqwP+jjRiRkOnwN2JXh39yC1VPyFeSGQHLvwKYu3bLBd1IOwxELQP49U//28atZGahkFXJC9wcNvA+JwAAAAAAAAAAAAAAAAAAAAAAAIxWxIjvffytMW5M6e4ziO+fOTxkRNpZKjrgyGDh92oNeSOFKvbuQj9Ef8qQwrVft0hF7sEa2FpnQwdDyT78/nMoNeA7GT40RKMBv5XCFHeI9oO6VDJc5V9blBLPwWPGxcg+psbUSgx4X55uzGtNKjHgMcMcPvfItS+uxIDHDHySfdCUqVGKfkApekZSn3jWVWRrVpEB+waLIYFtSh1mqfS26FriD9U4G/KUtLlZSm9SrdM7eajnNjpd63Reem/SH2FDq35f8ateAAAAAElFTkSuQmCC",
1247
+ initial_value: null,
1237
1248
  },
1238
1249
  {
1239
1250
  key: "sleep_timer",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "15.0.0-alpha.3564377339",
3
+ "version": "15.0.0-alpha.3564837831",
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": "15.0.0-alpha.3564377339",
30
+ "@applicaster/applicaster-types": "15.0.0-alpha.3564837831",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -88,6 +88,10 @@ export function isNavBarVisible(
88
88
  if (route.startsWith("/hooks/")) {
89
89
  const module = screenData?.module;
90
90
 
91
+ if (module === undefined) {
92
+ return showNavBar;
93
+ }
94
+
91
95
  if (module?.presentFullScreen) {
92
96
  return false;
93
97
  }
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { Provider } from "react-redux";
3
3
  import { renderHook } from "@testing-library/react-hooks";
4
4
  import configureStore from "redux-mock-store";
5
- import thunk from "redux-thunk";
5
+ import { thunk } from "redux-thunk";
6
6
 
7
7
  const mockStore = configureStore([thunk]);
8
8
 
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { Provider } from "react-redux";
3
3
  import { renderHook } from "@testing-library/react-hooks";
4
4
  import configureStore from "redux-mock-store";
5
- import thunk from "redux-thunk";
5
+ import { thunk } from "redux-thunk";
6
6
 
7
7
  const mockStore = configureStore([thunk]);
8
8
 
@@ -0,0 +1,7 @@
1
+ export const FOCUSABLE_GROUP_ID = "search-screen";
2
+
3
+ export const FOCUSABLE_RESULTS_ID = "search-screen-results";
4
+
5
+ export const SEARCH_BOX_GROUP_ID = "search_box_group_id";
6
+
7
+ export const SEARCH_INPUT_FOCUSABLE_ID = "search_input_group_id";
@@ -0,0 +1,3 @@
1
+ import { SEARCH_INPUT_FOCUSABLE_ID } from "./const";
2
+
3
+ export const isSearchInputId = (id) => id === SEARCH_INPUT_FOCUSABLE_ID;
@@ -4,7 +4,7 @@ import React, { PropsWithChildren } from "react";
4
4
  import configureStore from "redux-mock-store";
5
5
  import { Provider } from "react-redux";
6
6
  import { View } from "react-native";
7
- import thunk from "redux-thunk";
7
+ import { thunk } from "redux-thunk";
8
8
  import * as R from "ramda";
9
9
 
10
10
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
@@ -0,0 +1,48 @@
1
+ import { mergeRight } from "../mergeRight";
2
+
3
+ describe("mergeRight", () => {
4
+ test("merges two objects with no overlapping keys", () => {
5
+ const a = { x: 1, y: 2 };
6
+ const b = { z: 3 };
7
+
8
+ const result = mergeRight(a, b);
9
+
10
+ expect(result).toEqual({ x: 1, y: 2, z: 3 });
11
+ });
12
+
13
+ test("overwrites keys from the second object", () => {
14
+ const a = { x: 1, y: 2 };
15
+ const b = { y: 10, z: 3 };
16
+
17
+ const result = mergeRight(a, b);
18
+
19
+ expect(result).toEqual({ x: 1, y: 10, z: 3 });
20
+ });
21
+
22
+ test("does not mutate the original objects", () => {
23
+ const a = { x: 1 };
24
+ const b = { y: 2 };
25
+
26
+ const result = mergeRight(a, b);
27
+
28
+ expect(result).not.toBe(a);
29
+ expect(result).not.toBe(b);
30
+ expect(a).toEqual({ x: 1 });
31
+ expect(b).toEqual({ y: 2 });
32
+ });
33
+
34
+ test("works with empty objects", () => {
35
+ expect(mergeRight({}, { a: 1 })).toEqual({ a: 1 });
36
+ expect(mergeRight({ a: 1 }, {})).toEqual({ a: 1 });
37
+ expect(mergeRight({}, {})).toEqual({});
38
+ });
39
+
40
+ test("works with nested objects (shallow merge only)", () => {
41
+ const a = { x: { nested: 1 }, y: 2 };
42
+ const b = { x: { nested: 10 }, z: 3 };
43
+
44
+ const result = mergeRight(a, b);
45
+
46
+ expect(result).toEqual({ x: { nested: 10 }, y: 2, z: 3 });
47
+ });
48
+ });
package/utils/index.ts CHANGED
@@ -18,6 +18,8 @@ export { take } from "./take";
18
18
 
19
19
  export { mapAccum } from "./mapAccum";
20
20
 
21
+ export { mergeRight } from "./mergeRight";
22
+
21
23
  export {
22
24
  cloneDeep as clone,
23
25
  flatten,
@@ -51,4 +53,5 @@ export {
51
53
  findIndex,
52
54
  set,
53
55
  compact,
56
+ identity,
54
57
  } from "lodash";
@@ -0,0 +1,5 @@
1
+ import { assign } from "lodash";
2
+
3
+ export const mergeRight = (a, b) => {
4
+ return assign({}, a, b);
5
+ };