@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.
- package/package.json +6 -7
- package/src/Player/AudioLayer/AudioDescription.tsx +109 -68
- package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +81 -189
- package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +23 -5
- package/src/Player/AudioLayer/Layout/Controls/index.tsx +24 -49
- package/src/Player/AudioLayer/Layout/Controls/utils.ts +2 -2
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +38 -19
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +4 -13
- package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +39 -40
- package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +6 -22
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +1 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +99 -0
- package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +37 -20
- package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +19 -0
- package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +101 -105
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +2 -11
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +19 -5
- package/src/Player/AudioLayer/Layout/const.ts +4 -0
- package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +1 -1
- package/src/Player/AudioLayer/Layout/utils.ts +7 -0
- package/src/Player/AudioLayer/SleepTimerManager.ts +22 -11
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
- package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
- package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
- package/src/Player/Const/sleepTimerOptions.ts +7 -5
- package/src/Player/Utils/index.tsx +6 -0
- package/src/Player/hooks/useIsCasting.tsx +57 -0
- package/src/Player/index.tsx +84 -37
- package/src/QuickBrickPlayer.tsx +3 -0
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
- package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
- package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
- 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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
|
108
|
-
|
|
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
|
|
138
|
-
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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,
|
|
3
|
-
import { StyleSheet, View
|
|
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 {
|
|
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(
|
|
65
|
-
|
|
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(
|
|
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
|
-
|
|
70
|
+
const futureSleepDate =
|
|
71
|
+
prevTimerSleepDate || Date.now() + item.value * 60000;
|
|
68
72
|
|
|
69
|
-
|
|
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
|
-
|
|
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, ...
|
|
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
|
-
|
|
105
|
-
log_debug(`Sleep state restored, will go to sleep at ${futureSleepDate}`);
|
|
119
|
+
log_debug("Sleep state restored");
|
|
106
120
|
|
|
107
|
-
|
|
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.
|