@applicaster/zapp-react-native-utils 13.0.0-alpha.4515349201 → 13.0.0-alpha.4528460103

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 (111) hide show
  1. package/actionsExecutor/ActionExecutor.ts +16 -9
  2. package/actionsExecutor/ActionExecutorContext.tsx +74 -9
  3. package/alertUtils/alert.ts +148 -0
  4. package/alertUtils/index.ts +1 -148
  5. package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
  6. package/analyticsUtils/__tests__/__snapshots__/analyticsManagerNoModule.test.js.snap +9 -9
  7. package/analyticsUtils/helpers/hooks.ts +1 -1
  8. package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
  9. package/appUtils/HooksManager/__tests__/__snapshots__/hook.test.js.snap +10 -8
  10. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +693 -0
  11. package/appUtils/HooksManager/__tests__/hook.test.js +196 -184
  12. package/appUtils/HooksManager/__tests__/hooksManager.test.js +96 -99
  13. package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
  14. package/appUtils/HooksManager/{index.js → index.ts} +121 -112
  15. package/appUtils/HooksManager/types.ts +119 -0
  16. package/appUtils/RiverFocusManager/index.js +17 -12
  17. package/appUtils/accessibilityManager/index.ts +42 -1
  18. package/appUtils/contextKeysManager/contextResolver.ts +77 -0
  19. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -6
  20. package/appUtils/focusManager/index.ts +100 -65
  21. package/appUtils/focusManager/treeDataStructure/Tree/index.js +25 -3
  22. package/appUtils/focusManager/treeDataStructure/Utils/index.js +1 -3
  23. package/appUtils/focusManager/utils/enums.ts +4 -0
  24. package/appUtils/focusManagerAux/utils/index.ts +4 -3
  25. package/appUtils/{index.js → index.ts} +1 -1
  26. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +2 -2
  27. package/appUtils/keyCodes/__tests__/keyCodes.test.js +4 -4
  28. package/appUtils/keyCodes/{index.js → index.ts} +2 -0
  29. package/appUtils/keyCodes/keys/index.ts +3 -0
  30. package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
  31. package/appUtils/keyCodes/keys/keys.android.ts +18 -0
  32. package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
  33. package/appUtils/keyCodes/keys/keys.ts +15 -0
  34. package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +3 -3
  35. package/appUtils/keyCodes/keysByPlatform.ts +8 -0
  36. package/appUtils/orientationHelper.ts +1 -1
  37. package/appUtils/platform/const.ts +14 -0
  38. package/appUtils/platform/platformUtils.ts +47 -18
  39. package/appUtils/playerManager/player.ts +2 -2
  40. package/appUtils/playerManager/playerNative.ts +1 -1
  41. package/arrayUtils/__tests__/__snapshots__/arrayUtils.test.ts.snap +4 -4
  42. package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
  43. package/equalUtils/index.ts +10 -3
  44. package/focusManager/FocusManager.ts +11 -1
  45. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +4 -4
  46. package/functionUtils/index.ts +15 -11
  47. package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
  48. package/idleUtils/index.ts +1 -1
  49. package/idleUtils/withTimeout.ts +15 -2
  50. package/logger/const.ts +9 -67
  51. package/manifestUtils/__tests__/createConfig.test.js +16 -16
  52. package/modalState/index.ts +1 -5
  53. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +6 -6
  54. package/navigationUtils/__tests__/itemTypes.test.js +2 -2
  55. package/navigationUtils/__tests__/navigationUtils.test.js +2 -2
  56. package/navigationUtils/index.ts +17 -8
  57. package/package.json +3 -2
  58. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  59. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -1
  60. package/reactHooks/cell-click/__tests__/index.test.js +10 -2
  61. package/reactHooks/cell-click/index.ts +7 -1
  62. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +4 -4
  63. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -11
  64. package/reactHooks/feed/useFeedLoader.tsx +1 -1
  65. package/reactHooks/feed/usePipesCacheReset.ts +5 -5
  66. package/reactHooks/flatList/__tests__/index.test.tsx +30 -21
  67. package/reactHooks/layout/__tests__/index.test.tsx +10 -10
  68. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +14 -28
  69. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
  70. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  71. package/reactHooks/navigation/__tests__/index.test.tsx +0 -4
  72. package/reactHooks/navigation/index.ts +29 -218
  73. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +12 -0
  74. package/reactHooks/navigation/useGetNavBarTopBorderWidth.ts +14 -0
  75. package/reactHooks/navigation/useGetTabBarHeight.ts +10 -0
  76. package/reactHooks/navigation/useIsScreenActive.ts +17 -0
  77. package/reactHooks/navigation/useNavigationPluginData.ts +15 -0
  78. package/reactHooks/navigation/useNavigationType.ts +15 -0
  79. package/reactHooks/navigation/usePathname.ts +4 -0
  80. package/reactHooks/navigation/useRoute.ts +100 -0
  81. package/reactHooks/navigation/useUniqueRouteSuffix.ts +41 -0
  82. package/reactHooks/player/TVSeekControlller/ISeekController.ts +19 -2
  83. package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +20 -41
  84. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +80 -31
  85. package/reactHooks/player/TVSeekControlller/TapSeekController.ts +18 -40
  86. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +115 -0
  87. package/reactHooks/player/__tests__/useTapSeek._test.ts +94 -0
  88. package/reactHooks/player/const.ts +1 -1
  89. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  90. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  91. package/reactHooks/usePluginConfiguration.ts +1 -1
  92. package/reactHooks/useSubscriberFor.ts +4 -3
  93. package/reactUtils/__tests__/reactUtils.test.js +10 -28
  94. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +19 -19
  95. package/reactUtils/createContext/__tests__/__snapshots__/index.test.js.snap +2 -2
  96. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -3
  97. package/reactUtils/createContext/__tests__/helpers.test.js +8 -8
  98. package/reactUtils/index.ts +0 -14
  99. package/screenState/index.ts +24 -10
  100. package/theme/__mocks__/index.ts +4 -0
  101. package/theme/index.ts +1 -1
  102. package/time/Timer.ts +5 -2
  103. package/types.ts +1 -0
  104. package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
  105. package/appUtils/keyCodes/keys.ios.js +0 -46
  106. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  107. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  108. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  109. /package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +0 -0
  110. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  111. /package/domUtils/{logger.js → logger.ts} +0 -0
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { isWeb } from "../../reactUtils";
3
+ import { useNavigation } from "./useNavigation";
4
+ import { usePathname } from "./usePathname";
5
+ import {
6
+ QUICK_BRICK_CONTENT,
7
+ QUICK_BRICK_NAVBAR,
8
+ } from "@applicaster/quick-brick-core/const";
9
+
10
+ type Return = (prefix: string) => string;
11
+
12
+ export const useUniqueRouteSuffix = (): Return => {
13
+ const pathname = usePathname();
14
+ const { currentRoute } = useNavigation();
15
+
16
+ const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
17
+
18
+ const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
19
+ if (isWeb()) {
20
+ // we should use extended route prefix only on Web, where we have stack-navigator
21
+ return `${prefix}___${suffix}`;
22
+ }
23
+
24
+ // for other platforms use previous version
25
+ return prefix;
26
+ }, []);
27
+
28
+ return withUniqueRouteSuffix;
29
+ };
30
+
31
+ export const useNavbarId = () => {
32
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
33
+
34
+ return withUniqueRouteSuffix(QUICK_BRICK_NAVBAR);
35
+ };
36
+
37
+ export const useContentId = () => {
38
+ const withUniqueRouteSuffix = useUniqueRouteSuffix();
39
+
40
+ return withUniqueRouteSuffix(QUICK_BRICK_CONTENT);
41
+ };
@@ -1,15 +1,32 @@
1
- import { SEEK_TYPE } from "../const";
1
+ import { MAX_SEEK_TIME, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
2
2
 
3
3
  export interface ISeekControllerDelegate {
4
4
  handleDelayedSeek(offset: number): void;
5
5
  handleSeek(offset: number): void;
6
6
  handleSeekComplete(): void;
7
7
  handlePlayerPause(): void;
8
- delayedSeekOffset: number | null;
9
8
  }
10
9
 
11
10
  export interface ISeekController {
11
+ title: string;
12
12
  onStartSeek(seekType: SEEK_TYPE): void;
13
13
  onSeekEnd(): void;
14
14
  reset(): void;
15
15
  }
16
+
17
+ export class SeekIncrementalBaseStrategy {
18
+ private skipMultiplier = 1;
19
+
20
+ getSkipTime = () =>
21
+ Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * this.skipMultiplier);
22
+
23
+ increment = () => {
24
+ this.skipMultiplier++;
25
+ };
26
+
27
+ getSkipMultiplier = () => this.skipMultiplier;
28
+
29
+ reset = () => {
30
+ this.skipMultiplier = 1;
31
+ };
32
+ }
@@ -1,85 +1,64 @@
1
- import { ISeekController, ISeekControllerDelegate } from "./ISeekController";
1
+ import {
2
+ ISeekController,
3
+ ISeekControllerDelegate,
4
+ SeekIncrementalBaseStrategy,
5
+ } from "./ISeekController";
2
6
  import { SEEK_TYPE } from "../const";
3
7
  import { log_debug, log_error, log_verbose } from "./TVSeekController";
4
8
  import { interval, Subscription } from "rxjs";
5
9
  import { tap } from "rxjs/operators";
6
10
 
7
11
  interface LongPressConfig {
8
- TIMER_INTERVAL_MS: number; // ms
9
- SKIP_INCREMENT_STEP: number; // seconds
10
- MAX_SKIP_PER_TICK: number; // seconds
11
- INITIAL_SKIP_AMOUNT: number; // e.g., 10s to start
12
+ TIMER_INTERVAL_MS: number;
12
13
  }
13
14
 
14
15
  export class LongPressSeekController implements ISeekController {
15
- isLongPressActive = false;
16
- private currentSkipAmount = 0;
17
16
  private subscription: Subscription | null = null;
18
-
17
+ readonly title = "LongPressSeek";
19
18
  constructor(
20
19
  private seekControllerDelegate: ISeekControllerDelegate,
20
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
21
21
  private config: LongPressConfig
22
22
  ) {}
23
23
 
24
24
  onStartSeek(seekType: SEEK_TYPE) {
25
25
  this.cleanUpSubscription();
26
26
 
27
- log_debug(
28
- `LongPressSeekController: start long press seek type:${seekType}.`
29
- );
27
+ log_debug(`${this.title}: start long press seek type: ${seekType}.`);
30
28
 
31
29
  this.seekControllerDelegate.handlePlayerPause();
32
30
 
33
- this.isLongPressActive = true;
34
- this.currentSkipAmount = this.config.INITIAL_SKIP_AMOUNT;
35
-
36
31
  this.subscription = interval(this.config.TIMER_INTERVAL_MS)
37
32
  .pipe(
38
33
  tap(() => {
39
- this.seekControllerDelegate.handleDelayedSeek(this.currentSkipAmount);
34
+ this.seekControllerDelegate.handleDelayedSeek(
35
+ this.seekIncrementalStrategy.getSkipTime()
36
+ );
40
37
 
41
38
  log_debug(
42
- `LongPressSeekController: handle delayed seek type: ${seekType} - currentSkipAmount: ${this.currentSkipAmount}.`
39
+ `${
40
+ this.title
41
+ }: handle delayed seek type: ${seekType} - multiplier: ${this.seekIncrementalStrategy.getSkipMultiplier()}, currentSkipAmount: ${this.seekIncrementalStrategy.getSkipTime()}.`
43
42
  );
44
43
 
45
- this.currentSkipAmount = Math.min(
46
- this.currentSkipAmount + this.config.SKIP_INCREMENT_STEP,
47
- this.config.MAX_SKIP_PER_TICK
48
- );
44
+ this.seekIncrementalStrategy.increment();
49
45
  })
50
46
  )
51
47
  .subscribe({
52
48
  complete: () =>
53
- log_verbose(
54
- "LongPressSeekController: Long press subscription complete"
55
- ),
49
+ log_verbose(`${this.title}: Long press subscription complete`),
56
50
  error: (err) =>
57
- log_error(
58
- "LongPressSeekController: Long press subscription error",
59
- err
60
- ),
51
+ log_error(`${this.title}: Long press subscription error`, err),
61
52
  });
62
53
  }
63
54
 
64
55
  onSeekEnd() {
65
- if (this.isLongPressActive) {
66
- const offset = this.seekControllerDelegate.delayedSeekOffset;
67
-
68
- if (offset !== null) {
69
- log_debug(
70
- `LongPressSeekController: onPressEnd for LongPressSeekController, seek offset: ${offset}`
71
- );
72
-
73
- this.seekControllerDelegate.handleSeek(offset);
74
- }
75
- }
76
-
56
+ this.cleanUpSubscription();
77
57
  this.seekControllerDelegate.handleSeekComplete();
78
58
  }
79
59
 
80
60
  reset() {
81
- this.isLongPressActive = false;
82
- this.currentSkipAmount = 0;
61
+ this.seekIncrementalStrategy.reset();
83
62
  this.cleanUpSubscription();
84
63
  }
85
64
 
@@ -5,7 +5,11 @@ import { SEEK_TYPE } from "../const";
5
5
  import { createLogger } from "../../../logger";
6
6
  import { Player } from "../../../appUtils/playerManager/player";
7
7
  import { SeekKeyHandlerListener } from "../../../appUtils/keyInputHandler/KeyInputHandler";
8
- import { ISeekController, ISeekControllerDelegate } from "./ISeekController";
8
+ import {
9
+ ISeekController,
10
+ ISeekControllerDelegate,
11
+ SeekIncrementalBaseStrategy,
12
+ } from "./ISeekController";
9
13
 
10
14
  export const { log_warning, log_debug, log_info, log_error, log_verbose } =
11
15
  createLogger({
@@ -23,17 +27,16 @@ const CONFIG_TAP_SEEK = {
23
27
  };
24
28
 
25
29
  const CONFIG_LONG_PRESS = {
26
- TIMER_INTERVAL_MS: 300,
27
- SKIP_INCREMENT_STEP: 5,
28
- MAX_SKIP_PER_TICK: 300,
29
- INITIAL_SKIP_AMOUNT: 10,
30
+ TIMER_INTERVAL_MS: 200,
30
31
  };
31
32
 
32
33
  const LONG_PRESS_THRESHOLD = 500;
34
+ const SEEK_END_TIMEOUT_DURATION = 700;
35
+ // prettier-ignore
33
36
 
34
37
  export class TVSeekController
35
38
  implements SeekKeyHandlerListener, ISeekControllerDelegate {
36
- public delayedSeekOffset: number | null = null;
39
+ public delayedSeekPosition: number | null = null;
37
40
  private readonly playerController: Player;
38
41
  private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null);
39
42
  isPlayerInitiallyPaused: null | boolean = null;
@@ -43,14 +46,20 @@ export class TVSeekController
43
46
  private readonly tapSeekController: TapSeekController;
44
47
  private readonly longPressSeekController: LongPressSeekController;
45
48
  private longPressThresholdTimeout: any | null = null;
49
+ private seekEndTimeout: any | null = null;
46
50
 
47
51
  constructor(playerController: Player) {
48
52
  this.playerController = playerController;
49
53
 
50
- this.tapSeekController = new TapSeekController(this, CONFIG_TAP_SEEK);
54
+ this.tapSeekController = new TapSeekController(
55
+ this,
56
+ new SeekIncrementalBaseStrategy(),
57
+ CONFIG_TAP_SEEK
58
+ );
51
59
 
52
60
  this.longPressSeekController = new LongPressSeekController(
53
61
  this,
62
+ new SeekIncrementalBaseStrategy(),
54
63
  CONFIG_LONG_PRESS
55
64
  );
56
65
 
@@ -64,30 +73,28 @@ export class TVSeekController
64
73
  setStrategy = (seekStrategy: SEEK_STRATEGY) => {
65
74
  switch (seekStrategy) {
66
75
  case SEEK_STRATEGY.TAP_SEEK:
67
- clearTimeout(this.longPressThresholdTimeout);
68
- this.longPressThresholdTimeout = null;
69
76
  this.currentSeekController = this.tapSeekController;
77
+ log_debug("TVSeekController: setStrategy - TAP_SEEK");
70
78
 
71
79
  break;
72
80
  case SEEK_STRATEGY.AUTO_SEEK:
73
- this.currentSeekController.reset();
74
81
  this.currentSeekController = this.longPressSeekController;
82
+ log_debug("TVSeekController: setStrategy - AUTO_SEEK");
83
+
75
84
  break;
76
85
  default:
77
86
  }
78
87
  };
79
88
 
80
89
  handleStartSeek = (seekType: SEEK_TYPE) => {
81
- if (this.currentSeekType !== seekType) {
82
- this.resetAll();
83
- }
84
-
85
90
  this.currentSeekType = seekType;
86
91
 
87
92
  if (this.isPlayerInitiallyPaused === null) {
88
93
  this.isPlayerInitiallyPaused = this.playerController.isPaused();
89
94
  }
90
95
 
96
+ this.clearLongPressThresholdTimeout();
97
+ this.clearSeekCompleteTimeout();
91
98
  this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
92
99
  this.currentSeekController.onStartSeek(seekType);
93
100
 
@@ -110,8 +117,8 @@ export class TVSeekController
110
117
  };
111
118
 
112
119
  onSeekEnd = () => {
120
+ this.clearLongPressThresholdTimeout();
113
121
  this.currentSeekController.onSeekEnd();
114
- this.setStrategy(SEEK_STRATEGY.TAP_SEEK);
115
122
  };
116
123
 
117
124
  onRewindPressOut = () => {
@@ -122,40 +129,39 @@ export class TVSeekController
122
129
  this.onSeekEnd();
123
130
  };
124
131
 
125
- public onVideoProgress = (position: number) => {
126
- if (this.delayedSeekOffset) {
132
+ onVideoProgress = (position: number) => {
133
+ if (this.delayedSeekPosition !== null) {
127
134
  return;
128
135
  }
129
136
 
130
- // TODO: If 1 second after seek, send
131
-
132
137
  this.delayedSeekProgressSubject.next(position);
133
138
  };
134
139
 
135
- handleDelayedSeek = (offset: number) => {
140
+ handleDelayedSeek = (offset: number = 0) => {
136
141
  this.handlePlayerPause();
137
142
 
138
- const newOffset = (this.delayedSeekOffset || 0) + offset;
139
- const currentPos = this.playerController.getPosition();
143
+ const currentPos =
144
+ this.delayedSeekPosition === null
145
+ ? this.playerController.getPosition()
146
+ : this.delayedSeekPosition;
147
+
140
148
  let targetPos = currentPos;
141
149
 
142
150
  if (this.currentSeekType === SEEK_TYPE.FORWARD) {
143
151
  targetPos = Math.min(
144
- currentPos + newOffset,
152
+ currentPos + offset,
145
153
  this.playerController.getSeekableDuration()
146
154
  );
147
155
  } else if (this.currentSeekType === SEEK_TYPE.REWIND) {
148
- targetPos = Math.max(0, currentPos - newOffset);
156
+ targetPos = Math.max(0, currentPos - offset);
149
157
  } else {
150
158
  log_warning(
151
159
  `TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}`
152
160
  );
153
161
  }
154
162
 
155
- this.delayedSeekOffset = newOffset;
156
-
157
163
  log_debug(
158
- `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, offset: ${newOffset}, skipTime: ${offset}`
164
+ `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}`
159
165
  );
160
166
 
161
167
  const prevValue = this.delayedSeekProgressSubject.getValue();
@@ -168,9 +174,18 @@ export class TVSeekController
168
174
  }
169
175
  }
170
176
 
177
+ this.delayedSeekPosition = targetPos;
171
178
  this.delayedSeekProgressSubject.next(targetPos);
172
179
  };
173
180
 
181
+ handleSeekTo = (newPosition: number) => {
182
+ this.playerController.seekTo(newPosition);
183
+
184
+ if (!this.isPlayerInitiallyPaused) {
185
+ this.playerController.play();
186
+ }
187
+ };
188
+
174
189
  handleSeek = (offset: number) => {
175
190
  this.playerController[this.currentSeekType](offset);
176
191
 
@@ -180,11 +195,23 @@ export class TVSeekController
180
195
  };
181
196
 
182
197
  handleSeekComplete = () => {
183
- this.resetAll();
198
+ this.clearSeekCompleteTimeout();
199
+
200
+ this.seekEndTimeout = setTimeout(() => {
201
+ if (this.delayedSeekPosition !== null) {
202
+ this.handleSeekTo(this.delayedSeekPosition);
203
+ }
204
+
205
+ log_debug(
206
+ `handleSeekComplete: Seek finished, type: ${this.currentSeekController.title}, seekPosition: ${this.delayedSeekPosition}. Reset all states and seek position.`
207
+ );
208
+
209
+ this.resetAll();
210
+ }, SEEK_END_TIMEOUT_DURATION);
184
211
  };
185
212
 
186
- isAutoSeeking = (): boolean => {
187
- return this.longPressSeekController.isLongPressActive;
213
+ isSeeking = (): boolean => {
214
+ return this.delayedSeekPosition !== null;
188
215
  };
189
216
 
190
217
  handlePlayerPause = () => {
@@ -195,12 +222,34 @@ export class TVSeekController
195
222
  this.playerController.pause();
196
223
  };
197
224
 
225
+ clearSeekCompleteTimeout = () => {
226
+ if (this.seekEndTimeout === null) {
227
+ return;
228
+ }
229
+
230
+ clearTimeout(this.seekEndTimeout);
231
+ this.seekEndTimeout = null;
232
+ };
233
+
234
+ clearLongPressThresholdTimeout = () => {
235
+ if (this.longPressThresholdTimeout === null) {
236
+ return;
237
+ }
238
+
239
+ clearTimeout(this.longPressThresholdTimeout);
240
+ this.longPressThresholdTimeout = null;
241
+ };
242
+
198
243
  resetAll() {
199
244
  log_debug("TVSeekController: Reset all states");
200
245
  this.currentSeekType = null;
201
- this.delayedSeekOffset = null;
246
+
247
+ this.delayedSeekPosition = null;
202
248
  this.isPlayerInitiallyPaused = null;
203
249
 
250
+ this.clearSeekCompleteTimeout();
251
+ this.clearLongPressThresholdTimeout();
252
+
204
253
  this.tapSeekController.reset();
205
254
  this.longPressSeekController.reset();
206
255
 
@@ -1,75 +1,53 @@
1
- import { ISeekController, ISeekControllerDelegate } from "./ISeekController";
2
- import { SEEK_TYPE, MAX_SEEK_TIME, SKIP_TIME_BASE } from "../const";
1
+ import {
2
+ ISeekController,
3
+ ISeekControllerDelegate,
4
+ SeekIncrementalBaseStrategy,
5
+ } from "./ISeekController";
6
+ import { SEEK_TYPE } from "../const";
3
7
  import { log_debug } from "./TVSeekController";
4
8
 
5
9
  interface TapPressConfig {
6
10
  CLICKS_BEFORE_PAUSE: number;
7
11
  }
8
12
 
9
- const getSkipTime = (multiplier: number) =>
10
- Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
11
-
12
13
  export class TapSeekController implements ISeekController {
13
- private tapSequenceTimeout: any | null = null;
14
- private seekMultiplier = 1;
15
- private readonly ON_PRESS_RESET_DURATION = 500; // ms
16
-
14
+ readonly title = "TapSeek";
17
15
  constructor(
18
16
  private seekControllerDelegate: ISeekControllerDelegate,
17
+ private seekIncrementalStrategy: SeekIncrementalBaseStrategy,
19
18
  private config: TapPressConfig
20
19
  ) {}
21
20
 
22
21
  onStartSeek = (seekType: SEEK_TYPE) => {
23
- log_debug(`TapSeekController: start tap seek type: ${seekType}.`);
22
+ log_debug(`${this.title}: start tap seek type: ${seekType}.`);
24
23
 
25
- if (this.tapSequenceTimeout) {
26
- clearTimeout(this.tapSequenceTimeout);
27
- this.tapSequenceTimeout = null;
28
- }
24
+ const skipTime = this.seekIncrementalStrategy.getSkipTime();
25
+ const seekMultiplier = this.seekIncrementalStrategy.getSkipMultiplier();
29
26
 
30
- const skipTime = getSkipTime(this.seekMultiplier);
31
-
32
- if (this.seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
27
+ if (seekMultiplier > this.config.CLICKS_BEFORE_PAUSE) {
33
28
  log_debug(
34
- `TapSeekController: handle delayed seek type:${seekType} - skipTime: ${skipTime}.`
29
+ `${this.title}: handle delayed seek type:${seekType} - skipTime: ${skipTime}, seekMultiplier: ${seekMultiplier}.`
35
30
  );
36
31
 
37
32
  this.seekControllerDelegate.handleDelayedSeek(skipTime);
38
33
  } else {
39
34
  log_debug(
40
- `TapSeekController: handle seek type:${seekType} - skipTime: ${skipTime}.`
35
+ `${this.title}: handle seek type:${seekType} - skipTime: ${skipTime}.`
41
36
  );
42
37
 
43
38
  this.seekControllerDelegate.handleSeek(skipTime);
44
39
  }
45
40
 
46
- this.seekMultiplier++;
41
+ this.seekIncrementalStrategy.increment();
47
42
  };
48
43
 
49
44
  onSeekEnd = () => {
50
- this.tapSequenceTimeout = setTimeout(() => {
51
- if (this.seekControllerDelegate.delayedSeekOffset !== null) {
52
- this.seekControllerDelegate.handleSeek(
53
- this.seekControllerDelegate.delayedSeekOffset
54
- );
55
- }
56
-
57
- log_debug(
58
- `TapSeekController: Tap sequence timeout fired, delayedSeekOffset: ${this.seekControllerDelegate.delayedSeekOffset}.`
59
- );
60
-
61
- this.seekControllerDelegate.handleSeekComplete();
62
- }, this.ON_PRESS_RESET_DURATION);
45
+ this.seekControllerDelegate.handleSeekComplete();
63
46
  };
64
47
 
65
48
  reset = () => {
66
- log_debug("TapSeekController: Reset states");
49
+ log_debug("{this.title}: Reset states");
67
50
 
68
- this.seekMultiplier = 1;
69
-
70
- if (this.tapSequenceTimeout) {
71
- clearTimeout(this.tapSequenceTimeout);
72
- this.tapSequenceTimeout = null;
73
- }
51
+ this.seekIncrementalStrategy.reset();
74
52
  };
75
53
  }
@@ -0,0 +1,115 @@
1
+ import { act, renderHook } from "@testing-library/react-hooks";
2
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
3
+
4
+ import { ON_HOLD_INTERVAL, SEEK_TYPE, SKIP_TIME_BASE } from "../const";
5
+
6
+ import { useAutoSeek } from "../useAutoSeek";
7
+
8
+ jest.useFakeTimers({ legacyFakeTimers: true });
9
+
10
+ const rewind = jest.fn();
11
+ const forward = jest.fn();
12
+
13
+ jest
14
+ .spyOn(playerManager, "getInstanceController")
15
+ .mockReturnValue({ rewind, forward });
16
+
17
+ describe("useAutoSeek", () => {
18
+ beforeEach(() => {
19
+ rewind.mockReset();
20
+ forward.mockReset();
21
+ });
22
+
23
+ describe("autoSeekStart", () => {
24
+ it("should call forward method on playerInstance", () => {
25
+ const { result } = renderHook(() => useAutoSeek());
26
+
27
+ act(() => {
28
+ result.current.autoSeekStart(SEEK_TYPE.FORWARD);
29
+ });
30
+
31
+ expect(forward).toBeCalled();
32
+ result.current.autoSeekStop();
33
+ });
34
+
35
+ it("should call rewind method on playerManager", () => {
36
+ const { result } = renderHook(() => useAutoSeek());
37
+
38
+ act(() => {
39
+ result.current.autoSeekStart(SEEK_TYPE.REWIND);
40
+ });
41
+
42
+ expect(rewind).toBeCalled();
43
+ result.current.autoSeekStop();
44
+ });
45
+
46
+ it("should call rewind with default skip time", () => {
47
+ const { result } = renderHook(() => useAutoSeek());
48
+
49
+ act(() => {
50
+ result.current.autoSeekStart(SEEK_TYPE.REWIND);
51
+ });
52
+
53
+ expect(rewind).toBeCalledWith(SKIP_TIME_BASE);
54
+ result.current.autoSeekStop();
55
+ });
56
+
57
+ it("should call forward with default skip time", () => {
58
+ const { result } = renderHook(() => useAutoSeek());
59
+
60
+ act(() => {
61
+ result.current.autoSeekStart(SEEK_TYPE.FORWARD);
62
+ });
63
+
64
+ expect(forward).toBeCalledWith(SKIP_TIME_BASE);
65
+ result.current.autoSeekStop();
66
+ });
67
+
68
+ it("should call rewind in intervals set in configuration", () => {
69
+ const { result } = renderHook(() => useAutoSeek());
70
+
71
+ act(() => {
72
+ result.current.autoSeekStart(SEEK_TYPE.REWIND);
73
+ });
74
+
75
+ expect(rewind).toBeCalled();
76
+ jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
77
+ expect(rewind).toBeCalledTimes(4);
78
+ result.current.autoSeekStop();
79
+ });
80
+
81
+ it("should call forward in intervals set in configuration", () => {
82
+ const { result } = renderHook(() => useAutoSeek());
83
+
84
+ act(() => {
85
+ result.current.autoSeekStart(SEEK_TYPE.FORWARD);
86
+ });
87
+
88
+ expect(forward).toBeCalled();
89
+ jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
90
+ expect(forward).toBeCalledTimes(4);
91
+ result.current.autoSeekStop();
92
+ });
93
+ });
94
+
95
+ describe("autoSeekStop", () => {
96
+ it("should stop calling playerManager method after autoSeekStop is called", () => {
97
+ const { result } = renderHook(() => useAutoSeek());
98
+
99
+ act(() => {
100
+ result.current.autoSeekStart(SEEK_TYPE.FORWARD);
101
+ });
102
+
103
+ expect(forward).toBeCalled();
104
+ jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
105
+ expect(forward).toBeCalledTimes(4);
106
+
107
+ act(() => {
108
+ result.current.autoSeekStop();
109
+ });
110
+
111
+ jest.advanceTimersByTime(ON_HOLD_INTERVAL * 3);
112
+ expect(forward).toBeCalledTimes(4);
113
+ });
114
+ });
115
+ });