@applicaster/zapp-react-native-utils 12.0.0-alpha.8242130253 → 12.0.0-alpha.8254217837

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 (36) hide show
  1. package/analyticsUtils/AnalyticsEvents/index.ts +6 -0
  2. package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
  3. package/analyticsUtils/events.ts +6 -0
  4. package/analyticsUtils/index.tsx +6 -0
  5. package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +177 -0
  6. package/appUtils/playerManager/player.ts +6 -0
  7. package/appUtils/playerManager/playerNative.ts +5 -0
  8. package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -2
  9. package/cellUtils/__tests__/cellUtils.test.ts +9 -20
  10. package/cellUtils/index.ts +6 -12
  11. package/dateUtils/index.ts +11 -0
  12. package/headersUtils/index.ts +1 -0
  13. package/inflatedUrlUtils/index.ts +4 -4
  14. package/localizationUtils/__tests__/localizationUtils.test.ts +72 -53
  15. package/localizationUtils/index.ts +13 -22
  16. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  17. package/numberUtils/index.ts +4 -0
  18. package/package.json +2 -2
  19. package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +16 -1
  20. package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
  21. package/reactHooks/feed/useInflatedUrl.ts +44 -15
  22. package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +43 -29
  23. package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
  24. package/reactHooks/layout/types/index.ts +1 -0
  25. package/reactHooks/layout/useDimensions/useDimensions.ts +5 -2
  26. package/reactHooks/navigation/__mocks__/index.ts +13 -0
  27. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  28. package/reactHooks/navigation/index.ts +5 -0
  29. package/reactHooks/player/useAutoSeek.tsx +23 -7
  30. package/reactHooks/player/useTapSeek.tsx +6 -0
  31. package/reactHooks/screen/useScreenContext.ts +1 -1
  32. package/screenState/index.ts +0 -8
  33. package/stringUtils/index.ts +5 -0
  34. package/testUtils/getByTestId.ts +14 -0
  35. package/testUtils/index.tsx +24 -22
  36. package/types.ts +4 -0
@@ -7,6 +7,10 @@ import { sendHeaderClickEvent } from "./sendHeaderClickEvent";
7
7
  import { sendHardwareBackButtonClickEvent } from "./sendHardwareBackButtonClickEvent";
8
8
  import { sendDownloadsEvent } from "./sendDownloadsEvent";
9
9
  import { sendScreenEvent } from "./sendScreenEvent";
10
+ import {
11
+ sendSearchCanceledEvent,
12
+ sendSearchExecutedEvent,
13
+ } from "./searchEvents";
10
14
 
11
15
  export {
12
16
  sendScreenEvent,
@@ -18,4 +22,6 @@ export {
18
22
  sendHeaderClickEvent,
19
23
  sendHardwareBackButtonClickEvent,
20
24
  sendDownloadsEvent,
25
+ sendSearchCanceledEvent,
26
+ sendSearchExecutedEvent,
21
27
  };
@@ -0,0 +1,21 @@
1
+ import { log_debug } from "../logger";
2
+ import { postAnalyticEvent } from "../manager";
3
+ import { ANALYTICS_CORE_EVENTS } from "../events";
4
+
5
+ export const sendSearchCanceledEvent = (analyticsProps) => {
6
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_CANCELED;
7
+ postAnalyticEvent(eventName, analyticsProps);
8
+
9
+ log_debug(`sendSearchCanceledEvent: send event: ${eventName}`, {
10
+ analyticsProps,
11
+ });
12
+ };
13
+
14
+ export const sendSearchExecutedEvent = (analyticsProps) => {
15
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_EXECUTED;
16
+ postAnalyticEvent(eventName, analyticsProps);
17
+
18
+ log_debug(`sendSearchExecutedEvent: send event: ${eventName}`, {
19
+ analyticsProps,
20
+ });
21
+ };
@@ -23,10 +23,16 @@ export const TAPPING_EVENTS = {
23
23
  TAP_NAVBAR_BACK_BUTTON: "tap_navbar_back_button",
24
24
  };
25
25
 
26
+ export const SEARCH_EVENTS = {
27
+ SEARCH_EXECUTED: "search_executed",
28
+ SEARCH_CANCELED: "search_canceled",
29
+ };
30
+
26
31
  export const ANALYTICS_CORE_EVENTS = {
27
32
  ...LEGACY_EVENTS,
28
33
  ...SCREEN_VIEW_EVENTS,
29
34
  ...TAPPING_EVENTS,
35
+ ...SEARCH_EVENTS,
30
36
  };
31
37
 
32
38
  export const WEBVIEW_EVENTS = {
@@ -18,6 +18,8 @@ import {
18
18
  sendHardwareBackButtonClickEvent,
19
19
  sendDownloadsEvent,
20
20
  sendScreenEvent,
21
+ sendSearchCanceledEvent,
22
+ sendSearchExecutedEvent,
21
23
  } from "./AnalyticsEvents";
22
24
 
23
25
  import { postAnalyticEvent } from "./manager";
@@ -66,6 +68,8 @@ type AnalyticsFunctions = {
66
68
  item: ZappEntry;
67
69
  state: OfflineItemState;
68
70
  }) => void;
71
+ sendSearchCanceledEvent: (props: AnalyticsDefaultProperties) => void;
72
+ sendSearchExecutedEvent: (props: AnalyticsDefaultProperties) => void;
69
73
  };
70
74
 
71
75
  type GetAnalyticsFunctions = (
@@ -102,6 +106,8 @@ export function getAnalyticsFunctions({
102
106
  sendHardwareBackButtonClickEvent,
103
107
  sendScreenEvent,
104
108
  sendDownloadsEvent,
109
+ sendSearchCanceledEvent,
110
+ sendSearchExecutedEvent,
105
111
  };
106
112
  }
107
113
 
@@ -0,0 +1,177 @@
1
+ import { Player } from "./player";
2
+
3
+ export class DelayedSeekPlayerWrapper extends Player {
4
+ private wrappedPlayer: Player;
5
+ private delayedSeek: boolean;
6
+ private wasPlaying: boolean;
7
+ private delayedSeekTimer: NodeJS.Timeout;
8
+
9
+ constructor(props, player: Player) {
10
+ super(props);
11
+ // this.playerState = this.wrappedPlayer.playerState;
12
+ this.wrappedPlayer = player;
13
+
14
+ this.wrappedPlayer.addListener({
15
+ id: "Master-Wrapper-Listener",
16
+ listener: this,
17
+ });
18
+ }
19
+
20
+ getListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
21
+ return this.wrappedPlayer.getListener();
22
+ };
23
+
24
+ togglePlayPause = () => {
25
+ this.wrappedPlayer.togglePlayPause();
26
+ };
27
+
28
+ // Implement all default methods by delegating them to wrapped player
29
+ public seekTo = (position: number) => {
30
+ this.wrappedPlayer.seekTo(position);
31
+ };
32
+
33
+ public isPlaying = () => {
34
+ return this.wrappedPlayer.isPlaying();
35
+ };
36
+
37
+ public isPaused = () => {
38
+ return this.wrappedPlayer.isPaused();
39
+ };
40
+
41
+ public play = () => {
42
+ this.wrappedPlayer.play();
43
+ };
44
+
45
+ public pause = () => {
46
+ this.wrappedPlayer.pause();
47
+ };
48
+
49
+ getState = () => {
50
+ const currentState = this.wrappedPlayer.getState();
51
+
52
+ if (!this.delayedSeek) {
53
+ return currentState;
54
+ }
55
+
56
+ return {
57
+ ...currentState,
58
+ contentPosition: this.playerState.seekPosition,
59
+ isPaused: false,
60
+ seekPosition: this.playerState.seekPosition,
61
+ };
62
+ };
63
+
64
+ forward = (deltaTime: number) => {
65
+ if (this.delayedSeek || this.delayedSeekTimer) {
66
+ if (this.playerState.seekPosition == null) {
67
+ return;
68
+ }
69
+
70
+ this.playerState.seekPosition += deltaTime;
71
+
72
+ this.onVideoProgress({
73
+ currentTime: this.playerState.seekPosition,
74
+ duration: this.getDuration(),
75
+ seekableDuration: this.playerState.seekableDuration,
76
+ isLive: this.playerState.isLive,
77
+ });
78
+
79
+ return;
80
+ }
81
+
82
+ if (this.playerState.seekPosition == null) {
83
+ this.playerState.seekPosition = this.playerState.contentPosition;
84
+ }
85
+
86
+ this.playerState.seekPosition += deltaTime;
87
+
88
+ this.delayedSeekTimer && clearTimeout(this.delayedSeekTimer);
89
+
90
+ this.delayedSeekTimer = setTimeout(() => {
91
+ this.delayedSeekTimer = null;
92
+ this.wrappedPlayer.seekTo(this.playerState.seekPosition);
93
+
94
+ this.logState("forward", { deltaTime });
95
+ }, 3000);
96
+ };
97
+
98
+ rewind = (deltaTime: number) => {
99
+ if (this.delayedSeek || this.delayedSeekTimer) {
100
+ if (this.playerState.seekPosition == null) {
101
+ return;
102
+ }
103
+
104
+ this.playerState.seekPosition -= deltaTime;
105
+
106
+ this.onVideoProgress({
107
+ currentTime: this.playerState.seekPosition,
108
+ duration: this.getDuration(),
109
+ seekableDuration: this.playerState.seekableDuration,
110
+ isLive: this.playerState.isLive,
111
+ });
112
+
113
+ return;
114
+ }
115
+
116
+ this.delayedSeekTimer && clearTimeout(this.delayedSeekTimer);
117
+
118
+ if (this.playerState.seekPosition == null) {
119
+ this.playerState.seekPosition = this.playerState.contentPosition;
120
+ }
121
+
122
+ this.playerState.seekPosition -= deltaTime;
123
+
124
+ this.delayedSeekTimer = setTimeout(() => {
125
+ this.delayedSeekTimer = null;
126
+ this.wrappedPlayer.seekTo(this.playerState.seekPosition);
127
+
128
+ // this.currentPlayerComponent()?.["rewind"]?.(deltaTime);
129
+ this.logState("rewind", { deltaTime });
130
+ }, 3000);
131
+ };
132
+
133
+ public enterDelayedSeek() {
134
+ this.delayedSeekTimer && clearTimeout(this.delayedSeekTimer);
135
+ this.delayedSeekTimer = null;
136
+
137
+ if (this.delayedSeek) {
138
+ return;
139
+ }
140
+
141
+ this.delayedSeek = true;
142
+
143
+ console.log(
144
+ `Anton enterDelayedSeek wasPlaying: ${this.wasPlaying}`,
145
+ this.playerState.seekPosition
146
+ );
147
+
148
+ this.playerState.seekPosition = this.playerState.contentPosition;
149
+
150
+ if (!this.isPaused()) {
151
+ this.wasPlaying = true;
152
+ this.wrappedPlayer.pause();
153
+ }
154
+ }
155
+
156
+ public exitDelayedSeek() {
157
+ if (!this.delayedSeek) {
158
+ return;
159
+ }
160
+
161
+ if (this.delayedSeekTimer) {
162
+ return;
163
+ }
164
+
165
+ this.delayedSeek = false;
166
+
167
+ if (this.playerState.seekPosition != null) {
168
+ this.wrappedPlayer.seekTo(this.playerState.seekPosition);
169
+ }
170
+
171
+ if (this.wasPlaying) {
172
+ // Disabled by ticket definition
173
+ // this.wrappedPlayer.play();
174
+ this.wasPlaying = true;
175
+ }
176
+ }
177
+ }
@@ -340,6 +340,7 @@ export class Player {
340
340
  const { isLive, seekableDuration, currentTime, duration } = event;
341
341
 
342
342
  this.setPlaybackState({ currentTime, duration, seekableDuration, isLive });
343
+
343
344
  this.playerState.seekPosition = null;
344
345
 
345
346
  if (!isLive) {
@@ -727,4 +728,9 @@ export class Player {
727
728
  }
728
729
 
729
730
  public supportsNativeControls = (): boolean => false;
731
+ public supportNativeCast = (): boolean => false;
732
+
733
+ public enterDelayedSeek() {}
734
+
735
+ public exitDelayedSeek() {}
730
736
  }
@@ -295,4 +295,9 @@ export class PlayerNative extends Player {
295
295
 
296
296
  supportsNativeControls = (): boolean =>
297
297
  toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
298
+
299
+ supportNativeCast = (): boolean =>
300
+ toBooleanWithDefaultFalse(
301
+ this.playerPluginId === "quick-brick-player-brightcove"
302
+ );
298
303
  }
@@ -4,6 +4,8 @@ import { useEffect } from "react";
4
4
  import { playerManager } from "./index";
5
5
  import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
6
6
  import { PlayerRole } from "./conts";
7
+ import { isTV } from "../../reactUtils";
8
+ import { DelayedSeekPlayerWrapper } from "./DelayedSeekPlayerWrapper";
7
9
 
8
10
  // TODO: Rename to ControllerType
9
11
  export const usePlayerControllerSetup = ({
@@ -34,7 +36,7 @@ export const usePlayerControllerSetup = ({
34
36
  useValidatePlayerConfig(config);
35
37
 
36
38
  useEffect(() => {
37
- const newPlayerController = new ControllerType({
39
+ const combinedProps = {
38
40
  player: ref,
39
41
  listener: props as any,
40
42
  config,
@@ -42,9 +44,21 @@ export const usePlayerControllerSetup = ({
42
44
  entry,
43
45
  playerPluginId: pluginId,
44
46
  playerRole: PlayerRole.Primary,
47
+ };
48
+
49
+ const newPlayerController = new ControllerType({
50
+ ...combinedProps,
51
+ playerRole: PlayerRole.Unspecified,
52
+ playerId: playerId + "." + "wrapped",
45
53
  });
46
54
 
47
- setPlayerController(newPlayerController);
55
+ if (isTV()) {
56
+ setPlayerController(
57
+ new DelayedSeekPlayerWrapper(combinedProps, newPlayerController)
58
+ );
59
+ } else {
60
+ setPlayerController(newPlayerController);
61
+ }
48
62
  }, [playerId, entry?.id]);
49
63
 
50
64
  React.useEffect(() => {
@@ -24,7 +24,6 @@ import {
24
24
  EMPTY,
25
25
  FALLBACK,
26
26
  TRANSFORM,
27
- defaultColor,
28
27
  } from "./fixtures";
29
28
 
30
29
  describe("getKeyForLabel", () => {
@@ -395,9 +394,7 @@ describe("getColorFromData", () => {
395
394
  it("should return value directly if valid color", () => {
396
395
  const result = getColorFromData({
397
396
  data: entry,
398
- key: "background_color",
399
- getValue: getValue(componentStyle),
400
- defaultColor,
397
+ valueFromLayout: "#123456",
401
398
  });
402
399
 
403
400
  expect(result).toBe("#123456");
@@ -406,9 +403,7 @@ describe("getColorFromData", () => {
406
403
  it("should return valid rgba color name", () => {
407
404
  const result = getColorFromData({
408
405
  data: entry,
409
- key: "component_background_color",
410
- getValue: getValue(componentStyle),
411
- defaultColor,
406
+ valueFromLayout: "rgba(255, 255, 255, 0)",
412
407
  });
413
408
 
414
409
  expect(result).toBe("rgba(255, 255, 255, 0)");
@@ -417,33 +412,27 @@ describe("getColorFromData", () => {
417
412
  it("should return path value if path value is set and is valid color", () => {
418
413
  const result = getColorFromData({
419
414
  data: entry,
420
- key: VAL.colorKeyPath,
421
- getValue: getValue(componentStyle),
422
- defaultColor,
415
+ valueFromLayout: "background_color",
423
416
  });
424
417
 
425
418
  expect(result).toBe("#123456");
426
419
  });
427
420
 
428
- it("should return defaultColor if key value and path value are invalid", () => {
421
+ it("should return path value if path value is set, nested and is valid color", () => {
429
422
  const result = getColorFromData({
430
423
  data: entry,
431
- key: VAL.invalidColorKey,
432
- getValue: getValue(componentStyle),
433
- defaultColor,
424
+ valueFromLayout: "extensions.background_color",
434
425
  });
435
426
 
436
- expect(result).toBe(defaultColor);
427
+ expect(result).toBe("#123456");
437
428
  });
438
429
 
439
- it("should return defaultColor if path does not exist", () => {
430
+ it("should return valueFromLayout if path does not exist", () => {
440
431
  const result = getColorFromData({
441
432
  data: entry,
442
- key: VAL.invalidColorKeyPath,
443
- getValue: getValue(componentStyle),
444
- defaultColor,
433
+ valueFromLayout: "invalid_color",
445
434
  });
446
435
 
447
- expect(result).toBe(defaultColor);
436
+ expect(result).toBe("invalid_color");
448
437
  });
449
438
  });
@@ -447,30 +447,24 @@ export const toResizeMode = (
447
447
 
448
448
  type GetColorFromData = {
449
449
  data: Record<string, any>;
450
- key: string;
451
- getValue: Function;
452
- defaultColor: string;
450
+ valueFromLayout: string;
453
451
  };
454
452
 
455
453
  export const getColorFromData = ({
456
454
  data,
457
- key,
458
- getValue,
459
- defaultColor,
455
+ valueFromLayout,
460
456
  }: GetColorFromData): string => {
461
- const value = getValue(key);
462
-
463
457
  // Temporary hack to fix color validation when alpha is floating point number
464
458
  // https://github.com/dreamyguy/validate-color/issues/44
465
- if (validateColor(value.replace(".00", ""))) {
466
- return value;
459
+ if (validateColor(valueFromLayout.replace(".00", ""))) {
460
+ return valueFromLayout;
467
461
  }
468
462
 
469
- const pathValue = R.path(value.split("."), data);
463
+ const pathValue = R.path(valueFromLayout.split("."), data);
470
464
 
471
465
  if (pathValue && validateColor(pathValue)) {
472
466
  return pathValue;
473
467
  }
474
468
 
475
- return defaultColor;
469
+ return valueFromLayout;
476
470
  };
@@ -0,0 +1,11 @@
1
+ import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc";
3
+ import timezone from "dayjs/plugin/timezone";
4
+ import isoWeek from "dayjs/plugin/isoWeek";
5
+
6
+ // Extend dayjs with plugins
7
+ dayjs.extend(utc);
8
+ dayjs.extend(timezone);
9
+ dayjs.extend(isoWeek);
10
+
11
+ export { dayjs };
@@ -28,5 +28,6 @@ export const getAppDataHeaders = (screenId: string) => {
28
28
  "x-applicaster-signed-device-info-token": appData.signedDeviceInfoToken,
29
29
  "x-applicaster-device-make": appData.deviceMake,
30
30
  "x-applicaster-device-model": appData.deviceModel,
31
+ "x-applicaster-uuid": appData?.uuid,
31
32
  };
32
33
  };
@@ -1,4 +1,4 @@
1
- import { last, head, isNil, includes, all, pathOr } from "ramda";
1
+ import { last, head, isNil, includes, pathOr } from "ramda";
2
2
 
3
3
  const stripBrackets = (path) => {
4
4
  const withoutPrepened = last(path.split("{{"));
@@ -13,15 +13,15 @@ const hasValue = (context) => (path) => {
13
13
  return !isNil(value);
14
14
  };
15
15
 
16
- export const isEnoughDataToInflateString = (context, string): boolean => {
16
+ export const getMissingValues = (context, string): string[] => {
17
17
  if (!includes("{{", string)) {
18
- return true;
18
+ return [];
19
19
  }
20
20
 
21
21
  const SEARCH_REGEX = new RegExp(/{{.*?}}/g);
22
22
  const paths = string.toString().match(SEARCH_REGEX);
23
23
 
24
- return all(hasValue(context), paths);
24
+ return paths.filter((path) => !hasValue(context)(path));
25
25
  };
26
26
 
27
27
  export const isNonInflatedUrl = (url: string): boolean =>