@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 15.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/Components/BaseFocusable/index.ios.ts +2 -12
  2. package/Components/Cell/FocusableWrapper.tsx +0 -3
  3. package/Components/Cell/TvOSCellComponent.tsx +0 -5
  4. package/Components/Focusable/Focusable.tsx +2 -4
  5. package/Components/Focusable/FocusableTvOS.tsx +1 -18
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +0 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +1 -30
  8. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  10. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  11. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  12. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  13. package/Components/HandlePlayable/HandlePlayable.tsx +24 -42
  14. package/Components/HandlePlayable/utils.ts +31 -0
  15. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  16. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  17. package/Components/Layout/TV/LayoutBackground.tsx +2 -5
  18. package/Components/Layout/TV/ScreenContainer.tsx +6 -2
  19. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  20. package/Components/Layout/TV/index.tsx +4 -3
  21. package/Components/Layout/TV/index.web.tsx +4 -3
  22. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  23. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +10 -4
  24. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +1 -5
  25. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -11
  26. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +1 -9
  27. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +14 -15
  28. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  29. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +34 -16
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +6 -7
  32. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +2 -6
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +11 -233
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +15 -19
  36. package/Components/Navigator/StackNavigator.tsx +6 -0
  37. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  38. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +18 -17
  39. package/Components/OfflineHandler/__tests__/index.test.tsx +18 -27
  40. package/Components/PlayerContainer/PlayerContainer.tsx +14 -32
  41. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  42. package/Components/PreloaderWrapper/index.tsx +15 -0
  43. package/Components/River/ComponentsMap/ComponentsMap.tsx +3 -4
  44. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  45. package/Components/River/RefreshControl.tsx +9 -3
  46. package/Components/River/RiverItem.tsx +26 -20
  47. package/Components/River/TV/River.tsx +14 -31
  48. package/Components/River/TV/index.tsx +4 -8
  49. package/Components/River/TV/withTVEventHandler.tsx +36 -0
  50. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +0 -1
  51. package/Components/River/__tests__/componentsMap.test.js +0 -38
  52. package/Components/Screen/TV/index.web.tsx +2 -4
  53. package/Components/Screen/__tests__/Screen.test.tsx +43 -65
  54. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +44 -68
  55. package/Components/Screen/hooks.ts +76 -5
  56. package/Components/Screen/index.tsx +10 -3
  57. package/Components/Screen/orientationHandler.ts +3 -3
  58. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  59. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  60. package/Components/ScreenFeedLoader/index.ts +1 -0
  61. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  62. package/Components/ScreenResolver/hooks/index.ts +3 -0
  63. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  64. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  65. package/Components/ScreenResolver/index.tsx +9 -115
  66. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  67. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  68. package/Components/ScreenResolver/utils/index.ts +1 -0
  69. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  70. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  71. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  72. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  73. package/Components/ScreenRevealManager/ScreenRevealManager.ts +8 -40
  74. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +69 -86
  75. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  76. package/Components/Tabs/TabContent.tsx +4 -7
  77. package/Components/Transitioner/Scene.tsx +9 -15
  78. package/Components/Transitioner/index.js +3 -3
  79. package/Components/VideoLive/LiveImageManager.ts +199 -54
  80. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  81. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  82. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  83. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +7 -15
  84. package/Components/VideoModal/utils.ts +9 -12
  85. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  86. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +6 -4
  87. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  88. package/Components/ZappUIComponent/index.tsx +12 -6
  89. package/Components/index.js +1 -1
  90. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  91. package/Contexts/ScreenContext/index.tsx +64 -26
  92. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  93. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  94. package/Decorators/Analytics/index.tsx +5 -6
  95. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +0 -1
  96. package/Decorators/ConfigurationWrapper/const.ts +0 -1
  97. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  98. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  99. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  100. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  101. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  102. package/Helpers/DataSourceHelper/index.js +19 -0
  103. package/package.json +5 -5
  104. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  105. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  106. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  107. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  108. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
  109. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +0 -30
  110. package/Components/River/TV/utils/index.ts +0 -4
  111. package/Components/River/TV/withFocusableGroupForContent.tsx +0 -71
  112. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +0 -80
  113. package/Helpers/DataSourceHelper/index.ts +0 -19
  114. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -0,0 +1,352 @@
1
+ import { prepareEntry } from "../index";
2
+
3
+ const makeEntry = (overrides = {}): any => ({
4
+ content: { src: "https://example.com/video.mp4", type: "video/mp4" },
5
+ extensions: {},
6
+ ...overrides,
7
+ });
8
+
9
+ describe("prepareEntry", () => {
10
+ it("should return null when entry is null or undefined", () => {
11
+ expect(prepareEntry(null)).toBeNull();
12
+ expect(prepareEntry(undefined)).toBeNull();
13
+ });
14
+
15
+ describe("preview_playback_override", () => {
16
+ it("should deep merge override with entry, override taking precedence", () => {
17
+ const override = {
18
+ link: { href: "https://video-preload.com/id", rel: "self" },
19
+ extensions: {
20
+ brightcove: { video_id: 1234 },
21
+ preview: true,
22
+ free: true,
23
+ requires_authentication: false,
24
+ },
25
+ };
26
+
27
+ const entry = makeEntry({
28
+ extensions: {
29
+ preview_playback_override: override,
30
+ existing_field: "keep-me",
31
+ },
32
+ });
33
+
34
+ const result = prepareEntry(entry);
35
+
36
+ expect(result.extensions.brightcove.video_id).toBe(1234);
37
+ expect(result.extensions.preview).toBe(true);
38
+ expect(result.extensions.existing_field).toBe("keep-me");
39
+
40
+ expect(result.link).toEqual({
41
+ href: "https://video-preload.com/id",
42
+ rel: "self",
43
+ });
44
+
45
+ expect(result.content).toEqual(entry.content);
46
+ });
47
+
48
+ it("should override entry fields when both have the same key", () => {
49
+ const override = {
50
+ extensions: {
51
+ brightcove: { video_id: 9999 },
52
+ },
53
+ content: { src: "https://override.com/video.mp4" },
54
+ };
55
+
56
+ const entry = makeEntry({
57
+ extensions: {
58
+ preview_playback_override: override,
59
+ brightcove: { video_id: 1111, account_id: "abc" },
60
+ },
61
+ content: { src: "https://original.com/video.mp4", type: "video/mp4" },
62
+ });
63
+
64
+ const result = prepareEntry(entry);
65
+
66
+ expect(result.extensions.brightcove.video_id).toBe(9999);
67
+ expect(result.extensions.brightcove.account_id).toBe("abc");
68
+ expect(result.content.src).toBe("https://override.com/video.mp4");
69
+ expect(result.content.type).toBe("video/mp4");
70
+ });
71
+
72
+ it("should take priority over other playback sources", () => {
73
+ const override = {
74
+ link: { href: "https://override.com" },
75
+ extensions: { brightcove: { video_id: 5555 } },
76
+ };
77
+
78
+ const entry = makeEntry({
79
+ extensions: {
80
+ preview_playback_override: override,
81
+ brightcove: { preview_playback: "other-id" },
82
+ preview_playback: "https://other.com/video.mp4",
83
+ },
84
+ content: {
85
+ src: "https://example.com/video.mp4",
86
+ teaser: "https://teaser.com/video.mp4",
87
+ },
88
+ });
89
+
90
+ const result = prepareEntry(entry);
91
+
92
+ expect(result.link).toEqual({ href: "https://override.com" });
93
+ expect(result.extensions.brightcove.video_id).toBe(5555);
94
+ });
95
+ });
96
+
97
+ describe("brightcove preview_playback", () => {
98
+ it("should return entry with brightcove video_id replaced by preview_playback", () => {
99
+ const entry = makeEntry({
100
+ extensions: {
101
+ brightcove: {
102
+ video_id: "original-id",
103
+ preview_playback: "preview-id",
104
+ },
105
+ some_other: "value",
106
+ },
107
+ });
108
+
109
+ const result = prepareEntry(entry);
110
+
111
+ expect(result).toEqual({
112
+ ...entry,
113
+ extensions: {
114
+ ...entry.extensions,
115
+ brightcove: {
116
+ video_id: "preview-id",
117
+ preview_playback: "preview-id",
118
+ },
119
+ },
120
+ });
121
+ });
122
+
123
+ it("should preserve other extensions", () => {
124
+ const entry = makeEntry({
125
+ extensions: {
126
+ brightcove: { preview_playback: "preview-id" },
127
+ custom_field: "keep-me",
128
+ },
129
+ });
130
+
131
+ const result = prepareEntry(entry);
132
+
133
+ expect(result.extensions.custom_field).toBe("keep-me");
134
+ });
135
+ });
136
+
137
+ describe("extensions.preview_playback (non-brightcove)", () => {
138
+ it("should return entry with content.src set to preview_playback URL", () => {
139
+ const entry = makeEntry({
140
+ extensions: { preview_playback: "https://preview.com/video.m3u8" },
141
+ content: { src: "https://original.com/video.mp4" },
142
+ });
143
+
144
+ const result = prepareEntry(entry);
145
+
146
+ expect(result.content.src).toBe("https://preview.com/video.m3u8");
147
+ });
148
+
149
+ it("should preserve other content fields", () => {
150
+ const entry = makeEntry({
151
+ extensions: { preview_playback: "https://preview.com/video.m3u8" },
152
+ content: { src: "https://original.com/video.mp4", type: "video/mp4" },
153
+ });
154
+
155
+ const result = prepareEntry(entry);
156
+
157
+ expect(result.content.type).toBe("video/mp4");
158
+ });
159
+ });
160
+
161
+ describe("content.teaser", () => {
162
+ it("should return entry with content.src set to teaser URL", () => {
163
+ const entry = makeEntry({
164
+ extensions: {},
165
+ content: {
166
+ src: "https://original.com/video.mp4",
167
+ teaser: "https://teaser.com/teaser.mp4",
168
+ },
169
+ });
170
+
171
+ const result = prepareEntry(entry);
172
+
173
+ expect(result.content.src).toBe("https://teaser.com/teaser.mp4");
174
+ });
175
+ });
176
+
177
+ describe("brightcove video_id (without preview_playback)", () => {
178
+ it("should return the entry as-is when brightcove video_id exists", () => {
179
+ const entry = makeEntry({
180
+ extensions: { brightcove: { video_id: "some-id" } },
181
+ content: { src: "" },
182
+ });
183
+
184
+ const result = prepareEntry(entry);
185
+
186
+ expect(result).toBe(entry);
187
+ });
188
+ });
189
+
190
+ describe("content.src fallback", () => {
191
+ it("should return null when content.src is missing", () => {
192
+ const entry = makeEntry({
193
+ extensions: {},
194
+ content: {},
195
+ });
196
+
197
+ expect(prepareEntry(entry)).toBeNull();
198
+ });
199
+
200
+ it("should return null when content.src is empty", () => {
201
+ const entry = makeEntry({
202
+ extensions: {},
203
+ content: { src: "" },
204
+ });
205
+
206
+ expect(prepareEntry(entry)).toBeNull();
207
+ });
208
+
209
+ it("should return entry when content.type starts with video", () => {
210
+ const entry = makeEntry({
211
+ extensions: {},
212
+ content: { src: "https://example.com/stream", type: "video/mp4" },
213
+ });
214
+
215
+ expect(prepareEntry(entry)).toBe(entry);
216
+ });
217
+
218
+ it("should return entry when src has a known video extension", () => {
219
+ const extensions = ["m3u8", "mp4", "m4v", "mkv", "mov", "mpd", "ogv"];
220
+
221
+ for (const ext of extensions) {
222
+ const entry = makeEntry({
223
+ extensions: {},
224
+ content: { src: `https://example.com/video.${ext}` },
225
+ });
226
+
227
+ expect(prepareEntry(entry)).toBe(entry);
228
+ }
229
+ });
230
+
231
+ it("should return entry for audio extensions", () => {
232
+ const extensions = ["mp3", "oga", "opus"];
233
+
234
+ for (const ext of extensions) {
235
+ const entry = makeEntry({
236
+ extensions: {},
237
+ content: { src: `https://example.com/audio.${ext}` },
238
+ });
239
+
240
+ expect(prepareEntry(entry)).toBe(entry);
241
+ }
242
+ });
243
+
244
+ it("should return null for non-video/non-audio URLs", () => {
245
+ const entry = makeEntry({
246
+ extensions: {},
247
+ content: { src: "https://example.com/page.html" },
248
+ });
249
+
250
+ expect(prepareEntry(entry)).toBeNull();
251
+ });
252
+
253
+ it("should return null for URLs without extensions", () => {
254
+ const entry = makeEntry({
255
+ extensions: {},
256
+ content: { src: "https://example.com/stream" },
257
+ });
258
+
259
+ expect(prepareEntry(entry)).toBeNull();
260
+ });
261
+ });
262
+
263
+ describe("priority order", () => {
264
+ it("should prefer brightcove.preview_playback over extensions.preview_playback", () => {
265
+ const entry = makeEntry({
266
+ extensions: {
267
+ brightcove: { preview_playback: "bc-preview" },
268
+ preview_playback: "ext-preview",
269
+ },
270
+ });
271
+
272
+ const result = prepareEntry(entry);
273
+
274
+ expect(result.extensions.brightcove.video_id).toBe("bc-preview");
275
+ });
276
+
277
+ it("should prefer extensions.preview_playback over content.teaser", () => {
278
+ const entry = makeEntry({
279
+ extensions: { preview_playback: "https://preview.com/video.mp4" },
280
+ content: {
281
+ src: "https://original.com/video.mp4",
282
+ teaser: "https://teaser.com/video.mp4",
283
+ },
284
+ });
285
+
286
+ const result = prepareEntry(entry);
287
+
288
+ expect(result.content.src).toBe("https://preview.com/video.mp4");
289
+ });
290
+
291
+ it("should prefer preview_playback_override over all other sources", () => {
292
+ const override = {
293
+ extensions: { brightcove: { video_id: "override-id" } },
294
+ };
295
+
296
+ const entry = makeEntry({
297
+ extensions: {
298
+ preview_playback_override: override,
299
+ brightcove: { preview_playback: "bc-preview", video_id: "original" },
300
+ preview_playback: "https://ext-preview.com/video.mp4",
301
+ },
302
+ content: {
303
+ src: "https://original.com/video.mp4",
304
+ teaser: "https://teaser.com/video.mp4",
305
+ },
306
+ });
307
+
308
+ const result = prepareEntry(entry);
309
+
310
+ expect(result.extensions.brightcove.video_id).toBe("override-id");
311
+ });
312
+
313
+ it("should prefer content.teaser over brightcove.video_id", () => {
314
+ const entry = makeEntry({
315
+ extensions: { brightcove: { video_id: "some-id" } },
316
+ content: {
317
+ src: "https://original.com/video.mp4",
318
+ teaser: "https://teaser.com/teaser.mp4",
319
+ },
320
+ });
321
+
322
+ const result = prepareEntry(entry);
323
+
324
+ expect(result.content.src).toBe("https://teaser.com/teaser.mp4");
325
+ });
326
+
327
+ it("should prefer content.teaser over content.src URL check", () => {
328
+ const entry = makeEntry({
329
+ extensions: {},
330
+ content: {
331
+ src: "https://original.com/video.mp4",
332
+ teaser: "https://teaser.com/teaser.m3u8",
333
+ },
334
+ });
335
+
336
+ const result = prepareEntry(entry);
337
+
338
+ expect(result.content.src).toBe("https://teaser.com/teaser.m3u8");
339
+ });
340
+
341
+ it("should prefer brightcove.video_id over content.src URL check", () => {
342
+ const entry = makeEntry({
343
+ extensions: { brightcove: { video_id: "bc-id" } },
344
+ content: { src: "https://example.com/page.html" },
345
+ });
346
+
347
+ const result = prepareEntry(entry);
348
+
349
+ expect(result).toBe(entry);
350
+ });
351
+ });
352
+ });
@@ -0,0 +1,136 @@
1
+ import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
2
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
3
+ import { loggerLiveImageManager } from "../../../VideoLive/loggerHelper";
4
+
5
+ const { log_debug, log_error } = loggerLiveImageManager;
6
+
7
+ export type PreloadHookConfig = ZappPreloadPlugins & {
8
+ configuration: any;
9
+ };
10
+
11
+ function runHookWithCallback(
12
+ invoke: (
13
+ payload: any,
14
+ callback: (result: hookCallbackArgs) => void,
15
+ abortCallback: () => void
16
+ ) => void,
17
+ payload: any
18
+ ): Promise<{ payload: any; aborted: boolean }> {
19
+ return new Promise((resolve, reject) => {
20
+ const abortCallback = () => {
21
+ resolve({ payload, aborted: true });
22
+ };
23
+
24
+ try {
25
+ invoke(
26
+ payload,
27
+ ({
28
+ success,
29
+ error,
30
+ payload: resultPayload,
31
+ abort,
32
+ }: hookCallbackArgs) => {
33
+ if (error) {
34
+ reject(error);
35
+ } else if (!success || abort) {
36
+ resolve({ payload: resultPayload ?? payload, aborted: true });
37
+ } else {
38
+ resolve({ payload: resultPayload ?? payload, aborted: false });
39
+ }
40
+ },
41
+ abortCallback
42
+ );
43
+ } catch (error) {
44
+ log_error(`Error running preload hook: ${error.message}`, { error });
45
+ reject(error);
46
+ }
47
+ });
48
+ }
49
+
50
+ // TODO: Move this function to a more generic place,
51
+ // as it can be used by other components that want to run preload hooks
52
+ // and add test coverage for it
53
+ export async function executePreloadHooks({
54
+ preloadHooks,
55
+ entry,
56
+ }: {
57
+ preloadHooks: PreloadHookConfig[];
58
+ entry: ZappEntry;
59
+ }): Promise<ZappEntry | null> {
60
+ if (!preloadHooks?.length) {
61
+ return entry;
62
+ }
63
+
64
+ const sortedHooks = [...preloadHooks].sort(
65
+ (a, b) => (a.weight || 0) - (b.weight || 0)
66
+ );
67
+
68
+ const hookNames = sortedHooks.map((hook) => hook.identifier).join(", ");
69
+
70
+ log_debug(
71
+ `Preload hook sequence: ${hookNames}, entry: ${entry.id} - ${entry.title}`
72
+ );
73
+
74
+ let payload: any = entry;
75
+
76
+ let needsAbort = false;
77
+ const plugins = appStore.get("plugins");
78
+
79
+ for (const hookConfig of sortedHooks) {
80
+ const plugin = findPluginByIdentifier(hookConfig.identifier, plugins, true);
81
+
82
+ if (!plugin?.module) {
83
+ log_debug(
84
+ `Preload hook plugin not found: ${hookConfig.identifier}, skipping`
85
+ );
86
+
87
+ continue;
88
+ }
89
+
90
+ const { module: hookModule } = plugin;
91
+
92
+ if (hookModule.skipHook?.(payload)) {
93
+ log_debug(`Skipping hook: ${hookConfig.identifier}`);
94
+
95
+ continue;
96
+ }
97
+
98
+ if (hookModule.runInBackground) {
99
+ log_debug(
100
+ `Running background hook: ${hookConfig.identifier}, entry: ${payload?.id} - ${payload?.title}`
101
+ );
102
+
103
+ const result = await runHookWithCallback(
104
+ (item, callback, abortCallback) =>
105
+ hookModule
106
+ .runInBackground(item, callback, hookConfig, abortCallback)
107
+ ?.catch?.((error: Error) => {
108
+ log_error(
109
+ `Error running background preload hook: ${hookConfig.identifier}, error: ${error.message}`,
110
+ { error }
111
+ );
112
+
113
+ callback({ success: false, payload: item, error });
114
+ }),
115
+ payload
116
+ );
117
+
118
+ payload = result.payload;
119
+ needsAbort = result.aborted;
120
+ }
121
+
122
+ if (needsAbort) {
123
+ log_debug(
124
+ `Preload hook requested abort: ${hookConfig.identifier}, stopping chain`
125
+ );
126
+
127
+ break;
128
+ }
129
+ }
130
+
131
+ if (needsAbort) {
132
+ return null;
133
+ }
134
+
135
+ return payload;
136
+ }
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
2
+ import merge from "lodash/merge";
3
+ import { clone } from "@applicaster/zapp-react-native-utils/utils";
3
4
  import { Platform, View, ViewStyle } from "react-native";
4
5
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
5
6
  import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
@@ -83,30 +84,22 @@ const removeAdsFromEntry = (entry) => {
83
84
  return entry;
84
85
  }
85
86
 
86
- const newEntry = R.clone(entry);
87
+ const newEntry = clone(entry);
87
88
 
88
89
  delete newEntry.extensions.video_ads;
89
90
 
90
91
  return newEntry;
91
92
  };
92
93
 
93
- const prepareEntry = (entry) => {
94
+ export const prepareEntry = (entry: ZappEntry) => {
94
95
  if (!entry) {
95
96
  return null;
96
97
  }
97
98
 
98
- if (entry.extensions?.preview_playback) {
99
- return {
100
- ...entry,
101
- content: { ...entry?.content, src: entry?.extensions?.preview_playback },
102
- };
103
- }
99
+ const previewPlaybackOverride = entry.extensions?.preview_playback_override;
104
100
 
105
- if (entry.content?.teaser) {
106
- return {
107
- ...entry,
108
- content: { ...entry?.content, src: entry?.content?.teaser },
109
- };
101
+ if (previewPlaybackOverride) {
102
+ return merge({}, entry, previewPlaybackOverride);
110
103
  }
111
104
 
112
105
  const previewPlayback = entry.extensions?.brightcove?.preview_playback;
@@ -124,6 +117,20 @@ const prepareEntry = (entry) => {
124
117
  };
125
118
  }
126
119
 
120
+ if (entry.extensions?.preview_playback) {
121
+ return {
122
+ ...entry,
123
+ content: { ...entry?.content, src: entry?.extensions?.preview_playback },
124
+ };
125
+ }
126
+
127
+ if (entry.content?.teaser) {
128
+ return {
129
+ ...entry,
130
+ content: { ...entry?.content, src: entry?.content?.teaser },
131
+ };
132
+ }
133
+
127
134
  if (entry.extensions?.brightcove?.video_id) {
128
135
  return entry;
129
136
  }
@@ -167,14 +174,24 @@ function getFocusedState(
167
174
  const getPlayerConfig = (player_screen_id, actionIdentifier) => {
168
175
  if (player_screen_id) {
169
176
  const rivers = appStore.get("rivers");
177
+ const plugins = appStore.get("plugins");
170
178
  const playerScreen = rivers?.[player_screen_id] ?? null;
171
179
 
180
+ const preloadHooks = playerScreen?.hooks?.preload_plugins?.map((hook) => {
181
+ const configuration =
182
+ plugins.find((plugin) => plugin.identifier === hook.identifier)
183
+ ?.configuration || {};
184
+
185
+ return { ...hook, configuration, ...rivers[hook.screen_id] };
186
+ });
187
+
172
188
  // TODO: Check is it a player later
173
189
 
174
190
  // TODO: Add more dict if needed from the screen component, styles, data etc
175
191
  return {
176
192
  playerPluginId: playerScreen?.type ?? DEFAULT_PLAYER_IDENTIFIER,
177
193
  screenConfig: playerScreen?.general,
194
+ preloadHooks,
178
195
  };
179
196
  }
180
197
 
@@ -229,7 +246,7 @@ const LiveImageComponent = (props: LiveImageProps) => {
229
246
 
230
247
  const isCurrentlyFocused = useTrackCurrentAutoScrollingElement(componentId);
231
248
 
232
- const { playerPluginId, screenConfig } =
249
+ const { playerPluginId, screenConfig, preloadHooks } =
233
250
  getPlayerConfig(player_screen_id, actionIdentifier) ?? {};
234
251
 
235
252
  const playableEntry = playerPluginId ? getPlayableEntry(item) : null;
@@ -239,8 +256,8 @@ const LiveImageComponent = (props: LiveImageProps) => {
239
256
 
240
257
  const isShouldRender =
241
258
  playerPlugin &&
242
- getFocusedState(state, componentType, isCurrentlyFocused) &&
243
259
  playableEntry &&
260
+ getFocusedState(state, componentType, isCurrentlyFocused) &&
244
261
  cellUUID &&
245
262
  isSupportedTVForLiveImage() &&
246
263
  !isScreenReaderEnabled;
@@ -259,6 +276,7 @@ const LiveImageComponent = (props: LiveImageProps) => {
259
276
  imageKey={imageKey}
260
277
  item={playableEntry}
261
278
  audioMutedByDefault={audio_muted_by_default}
279
+ preloadHooks={preloadHooks}
262
280
  />
263
281
  </View>
264
282
  ) : null}
@@ -1,4 +1,4 @@
1
- import { renderHook, waitFor } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
  import { Image } from "react-native";
3
3
 
4
4
  import { useGetImageDimensions } from "../useGetImageDimensions";
@@ -13,16 +13,15 @@ jest.spyOn(Image, "getSize").mockImplementation((_uri, success) => {
13
13
 
14
14
  describe("useGetImageDimensions", () => {
15
15
  it("should return aspect ration initially when known dimensions", async () => {
16
- const { result } = renderHook(() =>
16
+ const { result, waitForNextUpdate } = renderHook(() =>
17
17
  useGetImageDimensions("https://some_url.com", WIDTH, undefined)
18
18
  );
19
19
 
20
20
  expect(result.current).toBeUndefined();
21
+ await waitForNextUpdate();
21
22
 
22
- await waitFor(() => {
23
- expect(result.current).toEqual(
24
- getDimension({ width: WIDTH, height: HEIGTH })
25
- );
26
- });
23
+ expect(result.current).toEqual(
24
+ getDimension({ width: WIDTH, height: HEIGTH })
25
+ );
27
26
  });
28
27
  });
@@ -7,7 +7,6 @@ import {
7
7
  } from "@applicaster/zapp-react-native-utils/cellUtils";
8
8
 
9
9
  import { useTextLabel, withFocusedStyles } from "./hooks";
10
- import { withScaledLineHeight } from "./utils";
11
10
  import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
12
11
  import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
13
12
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
@@ -70,12 +69,9 @@ const _Text = ({
70
69
 
71
70
  return (
72
71
  <Text
73
- style={[
74
- withScaledLineHeight(withFocusedStyles({ style, otherProps })),
75
- { height },
76
- ]}
77
- allowFontScaling={false}
72
+ style={[withFocusedStyles({ style, otherProps }), { height }]}
78
73
  {...withoutLabel(otherProps)}
74
+ allowFontScaling={false}
79
75
  >
80
76
  {textLabel}
81
77
  </Text>
@@ -4,7 +4,7 @@ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/nu
4
4
  import { Button } from "./Button";
5
5
  import {
6
6
  getButtonsCount,
7
- memoizedGetPluginIdentifier,
7
+ getPluginIdentifier,
8
8
  mapSelfAlignment,
9
9
  } from "./utils";
10
10
 
@@ -80,11 +80,7 @@ export const TvActionButtons = ({
80
80
  prefix: independentStyles ? prefixSpecificButton : buttonId(1),
81
81
  value,
82
82
  platformValue,
83
- pluginIdentifier: memoizedGetPluginIdentifier(
84
- configuration,
85
- PREFIX,
86
- index
87
- ),
83
+ pluginIdentifier: getPluginIdentifier(configuration, PREFIX, index),
88
84
  suffixId: prefixSpecificButton,
89
85
  preferredFocus: index === 0,
90
86
  });