@applicaster/quick-brick-player 14.0.0-alpha.2482261241 → 14.0.0-alpha.2802628909

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "14.0.0-alpha.2482261241",
3
+ "version": "14.0.0-alpha.2802628909",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
37
37
  "dependencies": {
38
- "@applicaster/quick-brick-mobile-transport-controls": "14.0.0-alpha.2482261241",
39
- "@applicaster/quick-brick-tv-transport-controls": "14.0.0-rc.48",
40
- "@applicaster/zapp-react-native-tvos-app": "14.0.0-alpha.2482261241",
41
- "@applicaster/zapp-react-native-ui-components": "14.0.0-alpha.2482261241",
42
- "@applicaster/zapp-react-native-utils": "14.0.0-alpha.2482261241",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "14.0.0-rc.83",
39
+ "@applicaster/quick-brick-tv-transport-controls": "14.0.0-rc.71",
40
+ "@applicaster/zapp-react-native-tvos-app": "14.0.0-alpha.2802628909",
41
+ "@applicaster/zapp-react-native-ui-components": "14.0.0-alpha.2802628909",
42
+ "@applicaster/zapp-react-native-utils": "14.0.0-alpha.2802628909",
43
43
  "query-string": "7.1.3",
44
44
  "shaka-player": "4.3.5",
45
45
  "typeface-montserrat": "^0.0.54",
@@ -61,6 +61,7 @@ type Props = {
61
61
  inline: boolean;
62
62
  docked: boolean;
63
63
  isModal: boolean;
64
+ pip: boolean;
64
65
  };
65
66
  playNextData: PlayNextData;
66
67
  isTabletPortrait?: boolean;
@@ -99,11 +100,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
99
100
  );
100
101
 
101
102
  useEffect(() => {
102
- if (player) {
103
- setSpeedManager(new SpeedController(player));
104
- } else {
105
- setSpeedManager(null);
106
- }
103
+ setSpeedManager(player ? new SpeedController(player) : null);
107
104
  }, [player]);
108
105
 
109
106
  useEffect(() => {
@@ -217,7 +214,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
217
214
  items: speedOptions,
218
215
  current_selection: playbackSpeed,
219
216
  onPress: onSpeedSelect,
220
- title: configuration["playback_speed_title"],
217
+ title: configuration.playback_speed_title,
221
218
  itemProps: getBottomSheetModalItemProps,
222
219
  iconProps: getBottomSheetModalIconProps,
223
220
  labelProps: getBottomSheetModalLabelProps,
@@ -233,7 +230,7 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
233
230
  ],
234
231
  },
235
232
  });
236
- }, [playbackSpeed, onSpeedSelect, configuration["playback_speed_title"]]);
233
+ }, [playbackSpeed, onSpeedSelect, configuration.playback_speed_title]);
237
234
 
238
235
  const onPlayerSeek = React.useCallback(
239
236
  (targetTime: number) => {
@@ -250,13 +247,28 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
250
247
  player?.seekTo(0);
251
248
  }, [player]);
252
249
 
250
+ const layoutStateMemo = React.useMemo(
251
+ () => ({
252
+ inline: layoutState.inline,
253
+ docked: layoutState.docked,
254
+ isModal: layoutState.isModal,
255
+ pip: layoutState.pip,
256
+ }),
257
+ [
258
+ layoutState.inline,
259
+ layoutState.docked,
260
+ layoutState.isModal,
261
+ layoutState.pip,
262
+ ]
263
+ );
264
+
253
265
  return (
254
266
  <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
255
267
  <View style={Styles.playerContainer} onLayout={onLayout}>
256
268
  <Layout
257
269
  entry={entry}
258
270
  playerState={playerState}
259
- layoutState={layoutState}
271
+ layoutState={layoutStateMemo}
260
272
  value={value}
261
273
  onPlaybackButtonPress={onPlaybackButtonPress}
262
274
  onRewindButtonPress={onRewindButtonPress}
@@ -1,6 +1,5 @@
1
1
  import * as React from "react";
2
- import { StyleSheet, View, ViewStyle } from "react-native";
3
- import * as R from "ramda";
2
+ import { StyleSheet, View } from "react-native";
4
3
  import {
5
4
  useIsTablet,
6
5
  useZStore,
@@ -16,7 +15,6 @@ import {
16
15
 
17
16
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
18
17
 
19
- import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
20
18
  import { useSafeAreaInsets } from "react-native-safe-area-context";
21
19
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
22
20
  import { getTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/getTabBarHeight";
@@ -26,22 +24,18 @@ import { useConfiguration } from "@applicaster/zapp-react-native-ui-components/C
26
24
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
27
25
  import { PROGRESS_BAR_HEIGHT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/utils";
28
26
  import { isMenuVisible } from "@applicaster/zapp-react-native-ui-components/Components/Screen/navigationHandler";
27
+ import { usePlugins } from "@applicaster/zapp-react-native-redux";
28
+ import { partial } from "@applicaster/zapp-react-native-utils/utils";
29
29
 
30
30
  const bottomTabBarHeight = getTabBarHeight();
31
31
 
32
32
  type Props = {
33
33
  entry: ZappEntry;
34
34
  layoutState: QuickBrickPlayer.LayoutState;
35
- playerState: QuickBrickPlayer.PlayerState;
36
- value: GetValue;
37
- onCloseVideoModal: () => void;
38
- onPlayerSeek: (targetTime: number) => void;
35
+ onPlayerSeek?: (targetTime: number) => void;
39
36
  playNextData: PlayNextData;
40
- title: string | number;
41
- summary: string | number;
42
37
  ImageComponent?: React.ReactNode;
43
38
  isActiveGesture?: boolean;
44
- minimisedHeight: number;
45
39
  aspectRatio?: number;
46
40
  };
47
41
 
@@ -50,7 +44,6 @@ const styles = StyleSheet.create({
50
44
  flex: 1,
51
45
  flexDirection: "column",
52
46
  alignItems: "stretch",
53
- marginTop: PROGRESS_BAR_HEIGHT,
54
47
  },
55
48
  content: {
56
49
  flex: 1,
@@ -93,14 +86,12 @@ const styles = StyleSheet.create({
93
86
  buttons: {
94
87
  marginVertical: 10,
95
88
  },
89
+ liveMarginTop: { marginTop: 0 },
90
+ marginTop: { marginTop: PROGRESS_BAR_HEIGHT },
96
91
  });
97
92
 
98
- const playerDimensionsStyle = (
99
- minimisedHeight: number,
100
- addPadding: boolean
101
- ): ViewStyle => ({
102
- width: addPadding ? toNumberWithDefault(100, minimisedHeight) : 0,
103
- });
93
+ const getValue = (key: string, stylesObj: Record<string, any>) =>
94
+ stylesObj?.[key] ?? null;
104
95
 
105
96
  const controlButtons = (
106
97
  live: boolean,
@@ -166,7 +157,6 @@ export const DockedControls = (props: Props) => {
166
157
  onPlayerSeek = noop,
167
158
  playNextData,
168
159
  ImageComponent,
169
- isActiveGesture = true,
170
160
  aspectRatio,
171
161
  } = props;
172
162
 
@@ -176,16 +166,14 @@ export const DockedControls = (props: Props) => {
176
166
 
177
167
  const { currentRoute, screenData } = useNavigation();
178
168
 
179
- const menuVisible = isMenuVisible(currentRoute, screenData);
169
+ const plugins = usePlugins();
170
+ const menuVisible = isMenuVisible(currentRoute, screenData, plugins);
180
171
 
181
172
  const { minimised_height: minimisedHeight } = useConfiguration();
182
173
 
183
174
  const configuration = useZStore("playerConfiguration")();
184
175
  const playerState = usePlayerState("shared-player-wrapper-docked");
185
176
 
186
- const getValue = (key: string, stylesObj: {}) =>
187
- R.propOr(null, key, stylesObj);
188
-
189
177
  const value = React.useCallback(
190
178
  (key: string) => {
191
179
  const retVal = getValue(key, configuration);
@@ -204,7 +192,7 @@ export const DockedControls = (props: Props) => {
204
192
 
205
193
  return retVal;
206
194
  },
207
- [...Object.values(configuration), playerState]
195
+ [...Object.values(configuration), playerState.seekableDuration]
208
196
  );
209
197
 
210
198
  const { title, summary } = useTitleSummaryOverlay(
@@ -221,93 +209,119 @@ export const DockedControls = (props: Props) => {
221
209
 
222
210
  const insets = useSafeAreaInsets();
223
211
 
212
+ const progressBarLayoutState = React.useMemo(
213
+ () => ({
214
+ inline: layoutState.inline,
215
+ docked: true,
216
+ isModal: layoutState.isModal,
217
+ pip: layoutState.pip,
218
+ }),
219
+ [layoutState.inline, layoutState.isModal, layoutState.pip]
220
+ );
221
+
222
+ const _controlButtons = React.useMemo(
223
+ () => partial(controlButtons, playerState.isLive && liveButton),
224
+ [playerState.isLive, liveButton]
225
+ );
226
+
227
+ const containerStyles = React.useMemo(
228
+ () => ({
229
+ height: minimisedHeight,
230
+ maxHeight: minimisedHeight,
231
+ }),
232
+ [minimisedHeight]
233
+ );
234
+
235
+ const imageComponentWrapperStyles = React.useMemo(
236
+ () => ({
237
+ aspectRatio: aspectRatio,
238
+ height: minimisedHeight,
239
+ }),
240
+ [aspectRatio, minimisedHeight]
241
+ );
242
+
243
+ const backgroundColorStyle = React.useMemo(
244
+ () => ({
245
+ backgroundColor: dockedBackgroundColor,
246
+ }),
247
+ [dockedBackgroundColor]
248
+ );
249
+
250
+ const bottomSpacerStyle = React.useMemo(() => {
251
+ const tabBarHeight = menuVisible ? bottomTabBarHeight : 0;
252
+ const safeAreaBottomInset = insets.bottom || 0;
253
+
254
+ return {
255
+ height: safeAreaBottomInset + tabBarHeight,
256
+ };
257
+ }, [menuVisible, insets.bottom]);
258
+
259
+ const contentInfoContent = React.useMemo(
260
+ () => ({ ...entry, title, summary }),
261
+ [title, summary, entry]
262
+ );
263
+
224
264
  return (
225
- <View
226
- style={[
227
- styles.container,
228
- {
229
- height:
230
- minimisedHeight +
231
- (menuVisible ? bottomTabBarHeight : 0) +
232
- insets.bottom,
233
- },
234
- ]}
235
- >
236
- {!playerState.isLive ? (
237
- <ProgressBar
238
- name={"shared-docked-layout"}
239
- content={entry}
240
- value={value}
241
- visible
242
- layoutState={layoutState}
243
- onPlayerSeek={onPlayerSeek}
244
- playerState={playerState}
245
- startComponentsAnimation={noop}
246
- docked
247
- />
248
- ) : null}
249
-
250
- <View style={styles.content}>
251
- <View style={styles.touchableArea}>
252
- {isActiveGesture || layoutState.docked ? (
253
- <View style={{ aspectRatio: aspectRatio, height: minimisedHeight }}>
265
+ <>
266
+ <View
267
+ style={[
268
+ styles.container,
269
+ containerStyles,
270
+ playerState.isLive ? styles.liveMarginTop : styles.marginTop,
271
+ ]}
272
+ >
273
+ {!playerState.isLive ? (
274
+ <ProgressBar
275
+ name={"shared-docked-layout"}
276
+ content={entry}
277
+ value={value}
278
+ visible
279
+ layoutState={progressBarLayoutState}
280
+ onPlayerSeek={onPlayerSeek}
281
+ playerState={playerState}
282
+ docked
283
+ />
284
+ ) : null}
285
+
286
+ <View style={styles.content}>
287
+ <View style={styles.touchableArea}>
288
+ <View style={imageComponentWrapperStyles}>
254
289
  {ImageComponent || null}
255
290
  </View>
256
- ) : (
257
291
  <View
258
- style={playerDimensionsStyle(
259
- minimisedHeight,
260
- isActiveGesture || layoutState.docked
261
- )}
262
- />
263
- )}
292
+ testID="content-info"
293
+ style={[styles.contentInfo, backgroundColorStyle]}
294
+ >
295
+ <ContentInfo content={contentInfoContent} value={value} docked />
296
+ </View>
297
+ </View>
264
298
  <View
265
- testID="content-info"
266
299
  style={[
267
- styles.contentInfo,
268
- { backgroundColor: dockedBackgroundColor },
300
+ isTablet
301
+ ? styles.controlsContainerForTablet
302
+ : styles.controlsContainerForMobile,
303
+ backgroundColorStyle,
269
304
  ]}
270
305
  >
271
- <ContentInfo
272
- content={{ ...entry, title, summary }}
306
+ <Controls
307
+ value={value}
308
+ visible
309
+ buttons={_controlButtons}
310
+ style={styles.playPauseButton}
311
+ playNextData={playNextData}
312
+ playerState={playerState}
313
+ />
314
+ <Button
315
+ icon={"close_mini"}
316
+ size={"medium"}
273
317
  value={value}
274
- docked
318
+ onPress={onCloseVideoModal}
319
+ style={styles.buttons}
275
320
  />
276
321
  </View>
277
322
  </View>
278
- <View
279
- style={[
280
- isTablet
281
- ? styles.controlsContainerForTablet
282
- : styles.controlsContainerForMobile,
283
- { backgroundColor: dockedBackgroundColor },
284
- ]}
285
- >
286
- <Controls
287
- value={value}
288
- visible
289
- buttons={R.partial(controlButtons, [
290
- playerState.isLive && liveButton,
291
- ])}
292
- style={styles.playPauseButton}
293
- playNextData={playNextData}
294
- playerState={playerState}
295
- />
296
- <Button
297
- icon={"close_mini"}
298
- size={"medium"}
299
- value={value}
300
- onPress={onCloseVideoModal}
301
- style={styles.buttons}
302
- />
303
- </View>
304
323
  </View>
305
- <View
306
- style={{
307
- height: insets.bottom + (menuVisible ? bottomTabBarHeight : 0),
308
- backgroundColor: dockedBackgroundColor,
309
- }}
310
- />
311
- </View>
324
+ <View style={[bottomSpacerStyle, backgroundColorStyle]} />
325
+ </>
312
326
  );
313
327
  };
@@ -151,12 +151,7 @@ export function MobileLayout({
151
151
  },
152
152
  ]}
153
153
  >
154
- <PlayerImage
155
- entry={entry}
156
- docked={false}
157
- imageWidth={imageSize}
158
- configuration={configuration}
159
- />
154
+ <PlayerImage entry={entry} configuration={configuration} />
160
155
  </View>
161
156
  {/* PlayerImage */}
162
157
 
@@ -4,7 +4,6 @@ import { LayoutChangeEvent, View } from "react-native";
4
4
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
5
5
  import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
6
6
 
7
- import { AnimatedImage } from "./AnimatedImage";
8
7
  import { FlexImage } from "./FlexImage";
9
8
 
10
9
  import { styles } from "./styles";
@@ -13,19 +12,12 @@ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/nu
13
12
  type Props = {
14
13
  entry: ZappEntry;
15
14
  configuration: QuickBrickPlayer.AudioPlayerProps["configuration"];
16
- docked: boolean;
17
- imageWidth?: Option<number>;
15
+ docked?: boolean;
18
16
  onLayoutImage?: (event: LayoutChangeEvent) => void;
19
17
  };
20
18
 
21
19
  export const PlayerImage = (props: Props) => {
22
- const {
23
- entry,
24
- docked,
25
- imageWidth,
26
- configuration,
27
- onLayoutImage = noop,
28
- } = props;
20
+ const { entry, docked = false, configuration, onLayoutImage = noop } = props;
29
21
 
30
22
  const isShadowEnabled = toBooleanWithDefaultTrue(
31
23
  configuration?.audio_player_artwork_shadow_enabled
@@ -43,19 +35,9 @@ export const PlayerImage = (props: Props) => {
43
35
  onLayout={onLayoutImage}
44
36
  style={[shouldShowShadow ? styles.shadow : undefined, { borderRadius }]}
45
37
  >
46
- {!imageWidth ? (
47
- <View style={[styles.defaultImageWrapperView, { borderRadius }]}>
48
- <FlexImage entry={entry} style={styles.backgroundImageContainer} />
49
- </View>
50
- ) : (
51
- // we have imageSize here and now we could animate resizing of this image
52
- <AnimatedImage
53
- entry={entry}
54
- style={[styles.backgroundImageContainer, { borderRadius }]}
55
- imageWidth={imageWidth}
56
- docked={docked}
57
- />
58
- )}
38
+ <View style={[styles.defaultImageWrapperView, { borderRadius }]}>
39
+ <FlexImage entry={entry} style={styles.backgroundImageContainer} />
40
+ </View>
59
41
  </View>
60
42
  </View>
61
43
  );
@@ -117,14 +117,11 @@ export function TabletLandscapeLayout({
117
117
  <View style={[styles.flex, styles.audioPlayerContainerHeightLimit]}>
118
118
  {/* PlayerImage non-docked */}
119
119
  <View style={[styles.flex, withDebug(styles.debugYellow)]}>
120
- {!docked ? (
121
- <PlayerImage
122
- docked={false}
123
- entry={entry}
124
- configuration={configuration}
125
- onLayoutImage={onLayoutImage}
126
- />
127
- ) : null}
120
+ <PlayerImage
121
+ entry={entry}
122
+ configuration={configuration}
123
+ onLayoutImage={onLayoutImage}
124
+ />
128
125
  </View>
129
126
  {/* PlayerImage */}
130
127
 
@@ -144,12 +144,7 @@ export function TabletPortraitLayout({
144
144
  ]}
145
145
  onLayout={onLayout}
146
146
  >
147
- <PlayerImage
148
- entry={entry}
149
- configuration={configuration}
150
- docked={docked}
151
- imageWidth={imageSize}
152
- />
147
+ <PlayerImage entry={entry} configuration={configuration} />
153
148
  </View>
154
149
 
155
150
  <View
@@ -1,10 +1,6 @@
1
1
  import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
2
- import {
3
- PlayerAnimationStateEnum,
4
- PlayerAnimationStateT,
5
- } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
2
+
6
3
  import { parseJsonIfNeeded } from "@applicaster/zapp-react-native-utils/functionUtils";
7
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
8
4
 
9
5
  export const NAMESPACE = "audio-player";
10
6
 
@@ -36,25 +32,3 @@ export const formatSpeed = (value: number) => {
36
32
  // Return the formatted string with "speed_" prefix
37
33
  return `speed_${formattedValue}`;
38
34
  };
39
-
40
- export const calculateBorderRadius = (
41
- docked: boolean,
42
- startComponentsAnimation: boolean,
43
- isActiveGesture: boolean,
44
- playerAnimationState: PlayerAnimationStateT,
45
- configuration: Record<string, any>
46
- ): number => {
47
- const shouldRemoveBorderRadius =
48
- (startComponentsAnimation &&
49
- isActiveGesture &&
50
- playerAnimationState !== PlayerAnimationStateEnum.maximize) ||
51
- docked;
52
-
53
- if (shouldRemoveBorderRadius) {
54
- return 0;
55
- }
56
-
57
- return toNumberWithDefaultZero(
58
- configuration.audio_player_artwork_border_radius
59
- );
60
- };
@@ -4,8 +4,9 @@ import { Animated, Pressable } from "react-native";
4
4
  import { PanGestureHandler } from "react-native-gesture-handler";
5
5
  import { useStyles } from "./styles";
6
6
  import { useVideoModalState } from "./hooks";
7
- import { MODAL_COLLAPSE_START, MODAL_RADIUS, SCREEN_HEIGHT } from "./consts";
8
- import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
7
+ import { MODAL_COLLAPSE_RATIO, MODAL_RADIUS } from "./consts";
8
+ import { getWindowHeight } from "./utils";
9
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
9
10
 
10
11
  type AnimatedModalProps = {
11
12
  content?: ReactElement;
@@ -16,33 +17,34 @@ export function AnimatedModal({
16
17
  content,
17
18
  collapsedContent,
18
19
  }: AnimatedModalProps) {
19
- const translateYRef = useModalAnimationContext().yTranslate;
20
+ const { translateY, collapsed, gestureHandlerProps, expand, offset } =
21
+ useVideoModalState();
20
22
 
21
- const {
22
- translateY,
23
- collapsed,
24
- collapsedHeight,
25
- gestureHandlerProps,
26
- expand,
27
- } = useVideoModalState(translateYRef);
23
+ const frame = useSafeAreaFrame();
24
+ const collapsedHeight = frame.height - offset.current;
25
+
26
+ const height = getWindowHeight();
27
+ const heightAboveMinimised = height - collapsedHeight;
28
28
 
29
29
  const styles = useStyles({ height: collapsedHeight, type: "audio" });
30
30
 
31
+ const MODAL_COLLAPSE_START = height * MODAL_COLLAPSE_RATIO;
32
+
31
33
  // Interpolated opacities for smooth cross-fade
32
34
  const collapsedOpacity = translateY.interpolate({
33
- inputRange: [MODAL_COLLAPSE_START, SCREEN_HEIGHT - collapsedHeight],
35
+ inputRange: [MODAL_COLLAPSE_START, heightAboveMinimised],
34
36
  outputRange: [0, 1],
35
37
  extrapolate: "clamp",
36
38
  });
37
39
 
38
40
  const expandedOpacity = translateY.interpolate({
39
- inputRange: [0, SCREEN_HEIGHT - collapsedHeight],
41
+ inputRange: [0, heightAboveMinimised],
40
42
  outputRange: [1, 0],
41
43
  extrapolate: "clamp",
42
44
  });
43
45
 
44
46
  const borderTopRadiusAnimated = translateY.interpolate({
45
- inputRange: [0, SCREEN_HEIGHT - collapsedHeight],
47
+ inputRange: [0, heightAboveMinimised],
46
48
  outputRange: [MODAL_RADIUS, 0],
47
49
  extrapolate: "clamp",
48
50
  });
@@ -57,7 +59,7 @@ export function AnimatedModal({
57
59
  borderTopLeftRadius: borderTopRadiusAnimated,
58
60
  borderTopRightRadius: borderTopRadiusAnimated,
59
61
  transform: [{ translateY }],
60
- height: SCREEN_HEIGHT,
62
+ height: height,
61
63
  },
62
64
  ]}
63
65
  >
@@ -67,7 +69,6 @@ export function AnimatedModal({
67
69
  {
68
70
  borderTopLeftRadius: borderTopRadiusAnimated,
69
71
  borderTopRightRadius: borderTopRadiusAnimated,
70
- height: SCREEN_HEIGHT,
71
72
  },
72
73
  ]}
73
74
  >