@applicaster/quick-brick-player 13.0.0-rc.99 → 13.0.0

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 (34) hide show
  1. package/package.json +6 -7
  2. package/src/Player/AudioLayer/AudioDescription.tsx +109 -68
  3. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +81 -189
  4. package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +23 -5
  5. package/src/Player/AudioLayer/Layout/Controls/index.tsx +24 -49
  6. package/src/Player/AudioLayer/Layout/Controls/utils.ts +2 -2
  7. package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +38 -19
  8. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +4 -13
  9. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +39 -40
  10. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +6 -22
  11. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +1 -0
  12. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +99 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +37 -20
  14. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +19 -0
  15. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +101 -105
  16. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +2 -11
  17. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +19 -5
  18. package/src/Player/AudioLayer/Layout/const.ts +4 -0
  19. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +1 -1
  20. package/src/Player/AudioLayer/Layout/utils.ts +7 -0
  21. package/src/Player/AudioLayer/SleepTimerManager.ts +22 -11
  22. package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
  23. package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
  24. package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
  25. package/src/Player/Const/sleepTimerOptions.ts +7 -5
  26. package/src/Player/Utils/index.tsx +6 -0
  27. package/src/Player/hooks/useIsCasting.tsx +57 -0
  28. package/src/Player/index.tsx +84 -37
  29. package/src/QuickBrickPlayer.tsx +3 -0
  30. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
  31. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
  32. package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
  33. package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
  34. package/src/utils/utils.ts +0 -25
@@ -1,10 +1,13 @@
1
1
  import * as React from "react";
2
+ import { LayoutChangeEvent, View } from "react-native";
2
3
 
3
4
  import {
4
5
  AnimationComponent,
5
6
  ComponentAnimationType,
6
7
  useModalAnimationContext,
7
8
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
9
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
10
+ import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
8
11
 
9
12
  import { AnimatedImage } from "./AnimatedImage";
10
13
  import { FlexImage } from "./FlexImage";
@@ -26,8 +29,9 @@ type Props = {
26
29
  title: string | number;
27
30
  summary: string | number;
28
31
  docked: boolean;
29
- imageWidth: Option<number>;
32
+ imageWidth?: Option<number>;
30
33
  verticalOffset: number;
34
+ onLayoutImage?: (event: LayoutChangeEvent) => void;
31
35
  };
32
36
 
33
37
  export const PlayerImage = (props: Props) => {
@@ -35,9 +39,10 @@ export const PlayerImage = (props: Props) => {
35
39
  entry,
36
40
  dimensions,
37
41
  docked,
38
- imageWidth,
39
42
  verticalOffset,
43
+ imageWidth,
40
44
  configuration,
45
+ onLayoutImage = noop,
41
46
  } = props;
42
47
 
43
48
  const {
@@ -47,6 +52,12 @@ export const PlayerImage = (props: Props) => {
47
52
  minimisedHeight,
48
53
  } = useModalAnimationContext();
49
54
 
55
+ const isShadowEnabled = toBooleanWithDefaultTrue(
56
+ configuration?.audio_player_artwork_shadow_enabled
57
+ );
58
+
59
+ const shouldShowShadow = !docked && isShadowEnabled;
60
+
50
61
  const borderRadius = calculateBorderRadius(
51
62
  docked,
52
63
  startComponentsAnimation,
@@ -71,24 +82,30 @@ export const PlayerImage = (props: Props) => {
71
82
  }}
72
83
  style={styles.alignItemsCenter}
73
84
  >
74
- {!imageWidth ? (
75
- // we render image with flex until we get size of this image
76
- <FlexImage
77
- entry={entry}
78
- style={styles.backgroundImageContainer}
79
- imageStyle={{ borderRadius }}
80
- />
81
- ) : null}
82
-
83
- {imageWidth ? (
84
- // we have imageSize here and now we could animate resizing of this image
85
- <AnimatedImage
86
- entry={entry}
87
- style={styles.backgroundImageContainer}
88
- imageStyle={{ borderRadius }}
89
- imageWidth={imageWidth}
90
- />
91
- ) : null}
85
+ <View
86
+ onLayout={onLayoutImage}
87
+ style={[
88
+ shouldShowShadow ? styles.shadow : undefined,
89
+ { borderRadius },
90
+ ]}
91
+ >
92
+ {!imageWidth ? (
93
+ <View style={[styles.defaultImageWrapperView, { borderRadius }]}>
94
+ <FlexImage
95
+ entry={entry}
96
+ style={styles.backgroundImageContainer}
97
+ />
98
+ </View>
99
+ ) : (
100
+ // we have imageSize here and now we could animate resizing of this image
101
+ <AnimatedImage
102
+ entry={entry}
103
+ style={[styles.backgroundImageContainer, { borderRadius }]}
104
+ imageWidth={imageWidth}
105
+ docked={docked}
106
+ />
107
+ )}
108
+ </View>
92
109
  </AnimationComponent>
93
110
  </AnimationComponent>
94
111
  );
@@ -1,9 +1,14 @@
1
+ /* eslint-disable react-native/no-color-literals */
1
2
  import { StyleSheet } from "react-native";
3
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
+
5
+ const SHADOW_COLOR = "rgba(0, 0, 0, 1)";
2
6
 
3
7
  export const styles = StyleSheet.create({
4
8
  backgroundImageContainer: {
5
9
  flex: 1,
6
10
  aspectRatio: 1,
11
+ overflow: "hidden",
7
12
  },
8
13
 
9
14
  alignItemsCenter: {
@@ -14,4 +19,18 @@ export const styles = StyleSheet.create({
14
19
  flex: {
15
20
  flex: 1,
16
21
  },
22
+ defaultImageWrapperView: {
23
+ overflow: "hidden",
24
+ },
25
+ shadow: {
26
+ borderColor: "transparent",
27
+ shadowColor: SHADOW_COLOR,
28
+ shadowOffset: { width: 0, height: 24 },
29
+ shadowOpacity: platformSelect({
30
+ ios: 0.35,
31
+ default: 0.2,
32
+ }),
33
+ shadowRadius: 32,
34
+ elevation: 12,
35
+ },
17
36
  });
@@ -1,28 +1,36 @@
1
1
  import * as React from "react";
2
- import { View, ViewStyle, StyleSheet } from "react-native";
2
+ import { StyleSheet, View } from "react-native";
3
3
  import {
4
- useSafeAreaInsets,
5
4
  useSafeAreaFrame,
5
+ useSafeAreaInsets,
6
6
  } from "react-native-safe-area-context";
7
7
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
8
  import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
 
10
- import { TABLET_LANDSCAPE } from "./const";
11
10
  import { TransportInfo } from "./TransportInfo";
12
11
  import { PlayerImage } from "./PlayerImage";
13
12
  import { Controls } from "./Controls";
14
13
 
15
- import { useTabletLayoutImageSize, useTransportInfoHeight } from "./hooks";
16
14
  import { DockedControls } from "./DockedControls";
17
15
  import { withDebug } from "./debug";
18
16
 
19
17
  import { Props } from "./types";
18
+ import { isSmallTabletHeight as getIsSmallTabletHeight } from "./utils";
19
+ import { NAVBAR_HEIGHT, TABLET_LANDSCAPE } from "./const";
20
+ import { FlexImage } from "./PlayerImage/FlexImage";
20
21
 
21
22
  const isIOS = isApplePlatform();
22
23
 
24
+ const MIN_WIDTH = TABLET_LANDSCAPE.MIN_WIDTH; // Minimum width for the audio player container
25
+ const MAX_SPACING = TABLET_LANDSCAPE.MARGIN_VERTICAL; // Maximum spacing for the audio player container
26
+ const MIN_SPACING = TABLET_LANDSCAPE.MARGIN_VERTICAL_S; // Minimum spacing for the audio player container
27
+ const transportInfoHeight = NAVBAR_HEIGHT;
28
+
23
29
  const styles = StyleSheet.create({
24
30
  container: {
25
31
  flex: 1,
32
+ alignItems: "center",
33
+ height: "100%",
26
34
  ...withDebug({
27
35
  borderWidth: 2,
28
36
  borderColor: "magenta",
@@ -32,30 +40,34 @@ const styles = StyleSheet.create({
32
40
  flex: 1,
33
41
  },
34
42
  audioPlayerContainer: {
35
- flex: 1,
36
- maxHeight: TABLET_LANDSCAPE.MAX_HEIGHT,
37
43
  flexDirection: "column",
38
44
  alignItems: "center",
45
+ justifyContent: "center",
46
+ minWidth: MIN_WIDTH,
39
47
 
40
48
  ...withDebug({
41
49
  borderWidth: 2,
42
50
  borderColor: "blue",
43
51
  }),
44
52
  },
45
- });
46
-
47
- const tabletStyles = ({
48
- screenHeight,
49
- topInset,
50
- bottomInset,
51
- imageSize,
52
- }): ViewStyle => ({
53
- alignItems: "center",
54
-
55
- height: screenHeight - topInset - bottomInset,
56
- paddingBottom: TABLET_LANDSCAPE.MARGIN_VERTICAL,
57
-
58
- opacity: imageSize ? 1 : 0, // hide content until imageSize is calculated
53
+ audioPlayerContainerHeightLimit: {
54
+ maxHeight: 750,
55
+ },
56
+ audioPlayerContainerPaddings: {
57
+ paddingBottom: MAX_SPACING,
58
+ paddingTop: MAX_SPACING,
59
+ },
60
+ // For small screens, we reduce the padding to fit better
61
+ audioPlayerContainerPaddingsSmall: {
62
+ paddingBottom: MIN_SPACING,
63
+ paddingTop: MIN_SPACING,
64
+ },
65
+ controlsContainer: { flexGrow: 0, alignSelf: "stretch" },
66
+ // eslint-disable-next-line react-native/no-color-literals
67
+ debugYellow: {
68
+ borderWidth: 2,
69
+ borderColor: "yellow",
70
+ },
59
71
  });
60
72
 
61
73
  const isTabletWithHomeButton = (bottomInset: number): boolean =>
@@ -86,124 +98,108 @@ export function TabletLandscapeLayout({
86
98
  onSkipStart,
87
99
  onSkipLive,
88
100
  onSpeedButtonPress,
89
- onSleepButtonPress,
90
101
  onCloseVideoModal,
91
102
  playbackSpeedName,
92
- playbackSleepName,
93
103
  configuration,
94
104
  dimensions,
95
105
  playNextData,
96
- cancelSleepTimer,
97
- sleepTimerDate,
98
106
  title,
99
107
  summary,
100
108
  }: Props) {
109
+ const [imageWidth, setImageWidth] = React.useState(0);
101
110
  const { docked } = layoutState;
102
111
 
103
- const { bottom: bottomInset, top: topInset } = useSafeAreaInsets();
112
+ const { bottom: bottomInset } = useSafeAreaInsets();
104
113
  const { height: screenHeight } = useSafeAreaFrame();
114
+ const isSmallScreen = getIsSmallTabletHeight(screenHeight);
105
115
  const { progressBarHeight } = useModalAnimationContext();
106
116
 
107
- const { imageSize, onLayout } = useTabletLayoutImageSize({
108
- isPortraitMode: false,
109
- });
110
-
111
- const { height: transportInfoHeight, onLayout: onTransportInfoLayout } =
112
- useTransportInfoHeight();
117
+ const onLayoutImage = React.useCallback((e) => {
118
+ setImageWidth(e.nativeEvent.layout.width);
119
+ }, []);
113
120
 
114
121
  return (
115
- <View
116
- style={[
117
- styles.container,
118
- tabletStyles({
119
- screenHeight,
120
- topInset,
121
- bottomInset,
122
- imageSize,
123
- }),
124
- ]}
125
- >
122
+ <View style={styles.container}>
126
123
  {/* TransportInfo */}
127
124
  <TransportInfo
128
125
  entry={entry}
129
126
  layoutState={layoutState}
130
127
  value={value}
131
128
  configuration={configuration}
132
- onLayout={onTransportInfoLayout}
133
129
  />
134
130
 
135
131
  {/* TransportInfo */}
136
132
 
137
- <View style={styles.audioPlayerContainer}>
138
- {/* PlayerImage */}
139
- <View
140
- style={[
141
- styles.flex,
142
- withDebug({
143
- borderWidth: 2,
144
- borderColor: "yellow",
145
- }),
146
- ]}
147
- onLayout={onLayout}
148
- >
149
- <PlayerImage
150
- entry={entry}
151
- layoutState={layoutState}
152
- playerState={playerState}
153
- value={value}
154
- configuration={configuration}
155
- onPlayerSeek={onPlayerSeek}
156
- onCloseVideoModal={onCloseVideoModal}
157
- dimensions={dimensions}
158
- playNextData={playNextData}
159
- title={title}
160
- summary={summary}
161
- docked={docked}
162
- imageWidth={imageSize}
163
- verticalOffset={
164
- transportInfoHeight +
165
- topInset +
166
- withAdditionalOffset({ progressBarHeight, bottomInset })
167
- }
168
- />
169
- </View>
170
- {/* PlayerImage */}
171
-
172
- <View
173
- style={{
174
- maxWidth: imageSize,
175
- }}
176
- >
177
- <Controls
178
- entry={entry}
179
- layoutState={layoutState}
180
- playerState={playerState}
181
- value={value}
182
- title={title}
183
- summary={summary}
184
- sleepTimerDate={sleepTimerDate}
185
- cancelSleepTimer={cancelSleepTimer}
186
- playbackSpeedName={playbackSpeedName}
187
- playbackSleepName={playbackSleepName}
188
- onPlaybackButtonPress={onPlaybackButtonPress}
189
- onRewindButtonPress={onRewindButtonPress}
190
- onForwardButtonPress={onForwardButtonPress}
191
- onPlayerSeek={onPlayerSeek}
192
- onSkipStart={onSkipStart}
193
- onSkipLive={onSkipLive}
194
- onSpeedButtonPress={onSpeedButtonPress}
195
- onSleepButtonPress={onSleepButtonPress}
196
- docked={docked}
197
- />
133
+ <View
134
+ style={[
135
+ styles.audioPlayerContainer,
136
+ {
137
+ marginTop: isSmallScreen
138
+ ? -TABLET_LANDSCAPE.MARGIN_VERTICAL_S
139
+ : -TABLET_LANDSCAPE.MARGIN_VERTICAL,
140
+ height: screenHeight - transportInfoHeight,
141
+ },
142
+ isSmallScreen
143
+ ? styles.audioPlayerContainerPaddingsSmall
144
+ : styles.audioPlayerContainerPaddings,
145
+ ]}
146
+ >
147
+ <View style={[styles.flex, styles.audioPlayerContainerHeightLimit]}>
148
+ {/* PlayerImage non-docked */}
149
+ <View style={[styles.flex, withDebug(styles.debugYellow)]}>
150
+ {!docked ? (
151
+ <PlayerImage
152
+ docked={false}
153
+ entry={entry}
154
+ layoutState={layoutState}
155
+ playerState={playerState}
156
+ value={value}
157
+ configuration={configuration}
158
+ onPlayerSeek={onPlayerSeek}
159
+ onCloseVideoModal={onCloseVideoModal}
160
+ dimensions={dimensions}
161
+ playNextData={playNextData}
162
+ title={title}
163
+ summary={summary}
164
+ verticalOffset={withAdditionalOffset({
165
+ progressBarHeight,
166
+ bottomInset,
167
+ })}
168
+ onLayoutImage={onLayoutImage}
169
+ />
170
+ ) : null}
171
+ </View>
172
+ {/* PlayerImage */}
173
+
174
+ <View style={styles.controlsContainer}>
175
+ <Controls
176
+ maxWidth={imageWidth}
177
+ entry={entry}
178
+ layoutState={layoutState}
179
+ playerState={playerState}
180
+ value={value}
181
+ title={title}
182
+ summary={summary}
183
+ playbackSpeedName={playbackSpeedName}
184
+ onPlaybackButtonPress={onPlaybackButtonPress}
185
+ onRewindButtonPress={onRewindButtonPress}
186
+ onForwardButtonPress={onForwardButtonPress}
187
+ onPlayerSeek={onPlayerSeek}
188
+ onSkipStart={onSkipStart}
189
+ onSkipLive={onSkipLive}
190
+ onSpeedButtonPress={onSpeedButtonPress}
191
+ docked={docked}
192
+ />
193
+ </View>
198
194
  </View>
199
195
  </View>
200
196
 
201
197
  <DockedControls
198
+ ImageComponent={<FlexImage entry={entry} style={styles.flex} />}
202
199
  entry={entry}
203
200
  layoutState={layoutState}
204
201
  playerState={playerState}
205
202
  value={value}
206
- configuration={configuration}
207
203
  dimensions={dimensions}
208
204
  onCloseVideoModal={onCloseVideoModal}
209
205
  onPlayerSeek={onPlayerSeek}
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
- import { View, ViewStyle, StyleSheet } from "react-native";
2
+ import { StyleSheet, View, ViewStyle } from "react-native";
3
3
  import {
4
- useSafeAreaInsets,
5
4
  useSafeAreaFrame,
5
+ useSafeAreaInsets,
6
6
  } from "react-native-safe-area-context";
7
7
  import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
8
  import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
@@ -82,15 +82,11 @@ export function TabletPortraitLayout({
82
82
  onSkipStart,
83
83
  onSkipLive,
84
84
  onSpeedButtonPress,
85
- onSleepButtonPress,
86
85
  onCloseVideoModal,
87
86
  playbackSpeedName,
88
- playbackSleepName,
89
87
  configuration,
90
88
  dimensions,
91
89
  playNextData,
92
- cancelSleepTimer,
93
- sleepTimerDate,
94
90
  title,
95
91
  summary,
96
92
  }: Props) {
@@ -187,10 +183,7 @@ export function TabletPortraitLayout({
187
183
  value={value}
188
184
  title={title}
189
185
  summary={summary}
190
- sleepTimerDate={sleepTimerDate}
191
- cancelSleepTimer={cancelSleepTimer}
192
186
  playbackSpeedName={playbackSpeedName}
193
- playbackSleepName={playbackSleepName}
194
187
  onPlaybackButtonPress={onPlaybackButtonPress}
195
188
  onRewindButtonPress={onRewindButtonPress}
196
189
  onForwardButtonPress={onForwardButtonPress}
@@ -198,7 +191,6 @@ export function TabletPortraitLayout({
198
191
  onSkipStart={onSkipStart}
199
192
  onSkipLive={onSkipLive}
200
193
  onSpeedButtonPress={onSpeedButtonPress}
201
- onSleepButtonPress={onSleepButtonPress}
202
194
  docked={docked}
203
195
  />
204
196
  </View>
@@ -211,7 +203,6 @@ export function TabletPortraitLayout({
211
203
  layoutState={layoutState}
212
204
  playerState={playerState}
213
205
  value={value}
214
- configuration={configuration}
215
206
  dimensions={dimensions}
216
207
  onCloseVideoModal={onCloseVideoModal}
217
208
  onPlayerSeek={onPlayerSeek}
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
- import { compose, take, map, split, propOr, trim } from "ramda";
3
- import { StyleSheet, View, LayoutChangeEvent } from "react-native";
2
+ import { compose, map, propOr, split, take, trim } from "ramda";
3
+ import { LayoutChangeEvent, StyleSheet, View } from "react-native";
4
4
 
5
5
  import { TransportInfo as MobileTransportInfo } from "@applicaster/quick-brick-mobile-transport-controls";
6
6
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
@@ -12,13 +12,16 @@ import {
12
12
  import { toPositiveNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
13
13
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
14
14
 
15
- import { TABLET_LANDSCAPE, NAVBAR_HEIGHT } from "../const";
15
+ import { NAVBAR_HEIGHT, TABLET_LANDSCAPE } from "../const";
16
16
  import { withDebug } from "../debug";
17
+ import { useSafeAreaFrame } from "react-native-safe-area-context";
18
+ import { isSmallTabletHeight } from "../utils";
17
19
 
18
20
  const styles = StyleSheet.create({
19
21
  container: {
20
22
  height: NAVBAR_HEIGHT,
21
23
  justifyContent: "center",
24
+ marginHorizontal: 10,
22
25
  },
23
26
  animatedContainer: {
24
27
  width: "100%",
@@ -51,6 +54,7 @@ export const TransportInfo = ({
51
54
  onLayout = noop,
52
55
  }: Props) => {
53
56
  const isTablet = useIsTablet();
57
+ const safeAreaFrame = useSafeAreaFrame();
54
58
  const languageData = useLanguageData("audio-player");
55
59
 
56
60
  const [marginBottom, setMarginBottom] = React.useState(0);
@@ -61,8 +65,18 @@ export const TransportInfo = ({
61
65
  event?.nativeEvent?.layout?.height
62
66
  );
63
67
 
64
- const minHeight = Math.min(height, TABLET_LANDSCAPE.MARGIN_VERTICAL);
65
- setMarginBottom(TABLET_LANDSCAPE.MARGIN_VERTICAL - minHeight);
68
+ const minHeight = Math.min(
69
+ height,
70
+ isTablet && isSmallTabletHeight(safeAreaFrame.height)
71
+ ? TABLET_LANDSCAPE.MARGIN_VERTICAL_S
72
+ : TABLET_LANDSCAPE.MARGIN_VERTICAL
73
+ );
74
+
75
+ setMarginBottom(
76
+ (isTablet && isSmallTabletHeight(safeAreaFrame.height)
77
+ ? TABLET_LANDSCAPE.MARGIN_VERTICAL_S
78
+ : TABLET_LANDSCAPE.MARGIN_VERTICAL) - minHeight
79
+ );
66
80
 
67
81
  // pass layoutChangeEvent to upper level
68
82
  onLayout(event);
@@ -2,9 +2,13 @@ export const PADDING_HORIZONTAL = 15;
2
2
 
3
3
  export const TABLET_VERTICAL_SEPARATOR_HEIGHT = 32;
4
4
 
5
+ export const TABLET_VERTICAL_SEPARATOR_HEIGHT_S = 16;
6
+
5
7
  export const TABLET_LANDSCAPE = {
6
8
  MARGIN_VERTICAL: 80, // gap between safe-area edge and audio-player container
9
+ MARGIN_VERTICAL_S: 60, // gap between safe-area edge and audio-player container
7
10
  MAX_HEIGHT: 750,
11
+ MIN_WIDTH: 320, // Minimum width for the audio player container
8
12
  };
9
13
 
10
14
  export const TABLET_PORTRAIT = {
@@ -19,7 +19,7 @@ const builder = () => {
19
19
  };
20
20
 
21
21
  export const useTransportInfoHeight = () => {
22
- const [height, setHeight] = React.useState(undefined);
22
+ const [height, setHeight] = React.useState(0);
23
23
 
24
24
  const layoutRef = useRefWithInitialValue<ReturnType<typeof builder>>(builder);
25
25
 
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks whether the screen height is matches with our threshold for small tablets.
3
+ * @param height screen height
4
+ * @returns boolean indicating if the height is less than 800 pixels
5
+ */
6
+ export const isSmallTabletHeight = (height: number): boolean =>
7
+ !(height >= 800);
@@ -30,6 +30,7 @@ export class SleepTimerState {
30
30
  const SLEEP_PREFERENCE_KEY = "sleep";
31
31
 
32
32
  export class SleepTimerController {
33
+ // TODO cancel timer on reach time
33
34
  private sleepTimerStateSubject = new BehaviorSubject<SleepTimerState>(
34
35
  new SleepTimerState(SLEEP_TIMER_OFF, 0)
35
36
  );
@@ -55,18 +56,23 @@ export class SleepTimerController {
55
56
  }
56
57
  }
57
58
 
58
- async setSleepTimer(item: SleepTimer) {
59
+ async setSleepTimer(item: SleepTimer, prevTimerSleepDate?: number) {
59
60
  // User has selected Off state
60
61
  if (item.value === 0) {
61
62
  log_debug("Cancelling sleep timer");
63
+
64
+ // Removing scheduled pause on timer expiration
62
65
  this.player.cancelSleepTimer?.();
63
66
 
64
67
  return this.onTimerExpired();
65
68
  }
66
69
 
67
- log_debug(`Starting sleep timer in ${item.value} minutes`);
70
+ const futureSleepDate =
71
+ prevTimerSleepDate || Date.now() + item.value * 60000;
68
72
 
69
- const futureSleepDate = Date.now() + item.value * 60000;
73
+ log_debug(
74
+ `Starting sleep timer in ${(futureSleepDate - Date.now()) / 1000}s`
75
+ );
70
76
 
71
77
  if (await this.scheduleSleep(futureSleepDate)) {
72
78
  await localStorage.setItem(
@@ -82,17 +88,26 @@ export class SleepTimerController {
82
88
  }
83
89
 
84
90
  async onTimerExpired() {
85
- await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
91
+ this.removeSleepTimerFromLocalStorage().catch((error) => {
92
+ log_error("Failed to remove sleep timer from local storage", {
93
+ error: error.message,
94
+ });
95
+ });
86
96
 
97
+ // Update UI state
87
98
  this.sleepTimerStateSubject.next(new SleepTimerState(SLEEP_TIMER_OFF, 0));
88
99
  }
89
100
 
101
+ async removeSleepTimerFromLocalStorage() {
102
+ await localStorage.removeItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
103
+ }
104
+
90
105
  async loadSleep() {
91
106
  const sleep = await localStorage.getItem(SLEEP_PREFERENCE_KEY, NAMESPACE);
92
107
 
93
108
  if (!sleep) return;
94
109
 
95
- const { futureSleepDate, ...rest } = sleep;
110
+ const { futureSleepDate, ...item } = sleep;
96
111
 
97
112
  if (!futureSleepDate || hasDatePassed(futureSleepDate)) {
98
113
  log_debug("Sleep date has already passed");
@@ -101,13 +116,9 @@ export class SleepTimerController {
101
116
  return;
102
117
  }
103
118
 
104
- if (await this.scheduleSleep(futureSleepDate)) {
105
- log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
119
+ log_debug("Sleep state restored");
106
120
 
107
- this.sleepTimerStateSubject.next(
108
- new SleepTimerState(rest as SleepTimer, futureSleepDate)
109
- );
110
- }
121
+ this.setSleepTimer(item as SleepTimer, futureSleepDate);
111
122
  }
112
123
 
113
124
  // Will be called if user closed the player explicitly.