@applicaster/quick-brick-player 13.0.0-rc.98 → 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 +46 -60
- package/src/Player/AudioLayer/Layout/Controls/utils.ts +5 -13
- package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
- package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +38 -19
- package/src/Player/AudioLayer/Layout/MobileLayout.tsx +35 -25
- 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 -103
- package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +5 -14
- package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +39 -13
- package/src/Player/AudioLayer/Layout/const.ts +6 -0
- package/src/Player/AudioLayer/Layout/debug.ts +1 -1
- package/src/Player/AudioLayer/Layout/hooks/index.ts +3 -1
- package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
- package/src/Player/AudioLayer/Layout/hooks/{useImageSize.ts → useTabletLayoutImageSize.ts} +10 -5
- 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,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,14 +12,26 @@ 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 } 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: {
|
|
22
|
+
height: NAVBAR_HEIGHT,
|
|
23
|
+
justifyContent: "center",
|
|
24
|
+
marginHorizontal: 10,
|
|
25
|
+
},
|
|
26
|
+
animatedContainer: {
|
|
20
27
|
width: "100%",
|
|
21
28
|
alignItems: "center",
|
|
22
29
|
justifyContent: "space-between",
|
|
30
|
+
|
|
31
|
+
...withDebug({
|
|
32
|
+
borderWidth: 2,
|
|
33
|
+
borderColor: "yellow",
|
|
34
|
+
}),
|
|
23
35
|
},
|
|
24
36
|
});
|
|
25
37
|
|
|
@@ -42,6 +54,7 @@ export const TransportInfo = ({
|
|
|
42
54
|
onLayout = noop,
|
|
43
55
|
}: Props) => {
|
|
44
56
|
const isTablet = useIsTablet();
|
|
57
|
+
const safeAreaFrame = useSafeAreaFrame();
|
|
45
58
|
const languageData = useLanguageData("audio-player");
|
|
46
59
|
|
|
47
60
|
const [marginBottom, setMarginBottom] = React.useState(0);
|
|
@@ -52,8 +65,18 @@ export const TransportInfo = ({
|
|
|
52
65
|
event?.nativeEvent?.layout?.height
|
|
53
66
|
);
|
|
54
67
|
|
|
55
|
-
const minHeight = Math.min(
|
|
56
|
-
|
|
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
|
+
);
|
|
57
80
|
|
|
58
81
|
// pass layoutChangeEvent to upper level
|
|
59
82
|
onLayout(event);
|
|
@@ -74,19 +97,22 @@ export const TransportInfo = ({
|
|
|
74
97
|
|
|
75
98
|
return (
|
|
76
99
|
<View
|
|
77
|
-
style={
|
|
78
|
-
|
|
100
|
+
style={[
|
|
101
|
+
styles.container,
|
|
102
|
+
{
|
|
103
|
+
marginBottom: applyMarginBottom({ isTablet, marginBottom }),
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
105
|
+
...withDebug({
|
|
106
|
+
borderWidth: 2,
|
|
107
|
+
borderColor: "red",
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
]}
|
|
85
111
|
onLayout={handleLayout}
|
|
86
112
|
>
|
|
87
113
|
<AnimationComponent
|
|
88
114
|
animationType={ComponentAnimationType.componentFade}
|
|
89
|
-
style={styles.
|
|
115
|
+
style={styles.animatedContainer}
|
|
90
116
|
>
|
|
91
117
|
<MobileTransportInfo
|
|
92
118
|
content={entry}
|
|
@@ -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 = {
|
|
@@ -18,3 +22,5 @@ export const MOBILE = {
|
|
|
18
22
|
MARGIN_TOP: 10,
|
|
19
23
|
MIN_HEIGHT: 458,
|
|
20
24
|
};
|
|
25
|
+
|
|
26
|
+
export const NAVBAR_HEIGHT = 60;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
|
|
2
|
+
import { MOBILE } from "../const";
|
|
3
|
+
|
|
4
|
+
type Return = {
|
|
5
|
+
imageSize: Option<number>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const useMobileLayoutImageSize = (): Return => {
|
|
9
|
+
const { width } = useDimensions("screen");
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
imageSize: width - 2 * MOBILE.MARGIN_HORIZONTAL,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -3,9 +3,11 @@ import { LayoutChangeEvent } from "react-native";
|
|
|
3
3
|
|
|
4
4
|
import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
5
5
|
import { Subject } from "rxjs";
|
|
6
|
-
import {
|
|
6
|
+
import { first, debounceTime, filter } from "rxjs/operators";
|
|
7
7
|
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
8
8
|
|
|
9
|
+
const TIMEOUT = 200; // ms
|
|
10
|
+
|
|
9
11
|
type Layout = {
|
|
10
12
|
x: number;
|
|
11
13
|
y: number;
|
|
@@ -36,7 +38,7 @@ type Return = {
|
|
|
36
38
|
onLayout: (event: LayoutChangeEvent) => void;
|
|
37
39
|
};
|
|
38
40
|
|
|
39
|
-
export const
|
|
41
|
+
export const useTabletLayoutImageSize = ({ isPortraitMode }: Props): Return => {
|
|
40
42
|
const [imageSize, setImageSize] = React.useState(undefined);
|
|
41
43
|
const [offsetY, setOffsetY] = React.useState(undefined);
|
|
42
44
|
|
|
@@ -45,7 +47,7 @@ export const useImageSize = ({ isPortraitMode }: Props): Return => {
|
|
|
45
47
|
React.useEffect(() => {
|
|
46
48
|
const subscription = layoutRef.current.layoutChange$
|
|
47
49
|
// for landscape layout we need to wait the second layout change in order to get proper size of image
|
|
48
|
-
.pipe(
|
|
50
|
+
.pipe(debounceTime(TIMEOUT), first())
|
|
49
51
|
.subscribe({
|
|
50
52
|
next: (layout: Layout) => {
|
|
51
53
|
const size = isPortraitMode
|
|
@@ -63,8 +65,11 @@ export const useImageSize = ({ isPortraitMode }: Props): Return => {
|
|
|
63
65
|
|
|
64
66
|
React.useEffect(() => {
|
|
65
67
|
const subscription = layoutRef.current.layoutChange$
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
.pipe(
|
|
69
|
+
filter((layout: Layout) => layout.y > 0),
|
|
70
|
+
debounceTime(TIMEOUT),
|
|
71
|
+
first()
|
|
72
|
+
)
|
|
68
73
|
.subscribe({
|
|
69
74
|
next: (layout: Layout) => {
|
|
70
75
|
setOffsetY(layout.y);
|
|
@@ -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.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ModalHeader } from "@applicaster/zapp-react-native-ui-components/Components/ModalComponent/Header";
|
|
3
|
+
import { isNil } from "lodash";
|
|
4
|
+
|
|
5
|
+
const formatTime = (milliseconds: number) => {
|
|
6
|
+
const totalSeconds = Math.floor(milliseconds / 1000);
|
|
7
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
9
|
+
const seconds = totalSeconds % 60;
|
|
10
|
+
|
|
11
|
+
if (milliseconds < 3600000) {
|
|
12
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds
|
|
13
|
+
.toString()
|
|
14
|
+
.padStart(2, "0")}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `${hours.toString().padStart(2, "0")}:${minutes
|
|
18
|
+
.toString()
|
|
19
|
+
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const SleepTimerModalHeader = ({
|
|
23
|
+
summary,
|
|
24
|
+
...props
|
|
25
|
+
}: {
|
|
26
|
+
summary?: string;
|
|
27
|
+
}) => {
|
|
28
|
+
const sleepTimerDate = +summary;
|
|
29
|
+
|
|
30
|
+
const [timeLeft, setTimeLeft] = React.useState(
|
|
31
|
+
sleepTimerDate !== 0 ? sleepTimerDate - Date.now() : 0
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const showCounter = React.useMemo(() => sleepTimerDate > 0, [sleepTimerDate]);
|
|
35
|
+
|
|
36
|
+
React.useLayoutEffect(() => {
|
|
37
|
+
if (showCounter && sleepTimerDate !== 0) {
|
|
38
|
+
setTimeLeft(sleepTimerDate - Date.now());
|
|
39
|
+
|
|
40
|
+
const intervalId = setInterval(() => {
|
|
41
|
+
setTimeLeft(() => {
|
|
42
|
+
const newTimeLeft = sleepTimerDate - Date.now();
|
|
43
|
+
|
|
44
|
+
if (newTimeLeft <= 0) {
|
|
45
|
+
clearInterval(intervalId);
|
|
46
|
+
|
|
47
|
+
return 0;
|
|
48
|
+
} else {
|
|
49
|
+
return newTimeLeft;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}, 1000);
|
|
53
|
+
|
|
54
|
+
return () => clearInterval(intervalId);
|
|
55
|
+
} else {
|
|
56
|
+
setTimeLeft(0);
|
|
57
|
+
}
|
|
58
|
+
}, [sleepTimerDate, showCounter]);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ModalHeader
|
|
62
|
+
summary={!isNil(summary) ? formatTime(timeLeft) : summary}
|
|
63
|
+
{...(props as any)}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SleepTimerModalHeader";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
SLEEP_TIMER_OFF,
|
|
5
|
+
SleepTimerController,
|
|
6
|
+
SleepTimerState,
|
|
7
|
+
} from "./SleepTimerManager";
|
|
8
|
+
|
|
9
|
+
import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
10
|
+
|
|
11
|
+
import { SleepTimerModalHeader } from "./components/SleepTimerModalHeader";
|
|
12
|
+
import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
|
|
13
|
+
import {
|
|
14
|
+
getBottomSheetModalLabelProps,
|
|
15
|
+
getBottomSheetModalIconProps,
|
|
16
|
+
getBottomSheetModalItemProps,
|
|
17
|
+
getBottomSheetModalSelectedItemIcon,
|
|
18
|
+
selectSleepTitle,
|
|
19
|
+
selectSleepOffLabel,
|
|
20
|
+
} from "../Utils";
|
|
21
|
+
|
|
22
|
+
export const useSleepTimerModal = (player, configuration) => {
|
|
23
|
+
const playerState = usePlayerState("audio-player-sleep-timer-modal");
|
|
24
|
+
|
|
25
|
+
const title = configuration(selectSleepTitle);
|
|
26
|
+
const offLabel = configuration(selectSleepOffLabel);
|
|
27
|
+
|
|
28
|
+
const [sleepTimerManager, setSleepTimerManager] =
|
|
29
|
+
useState<SleepTimerController | null>(null);
|
|
30
|
+
|
|
31
|
+
const [sleepTimerState, setSleepTimerState] = useState<SleepTimerState>(
|
|
32
|
+
new SleepTimerState(SLEEP_TIMER_OFF, 0)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (player) {
|
|
37
|
+
setSleepTimerManager(new SleepTimerController(player));
|
|
38
|
+
} else {
|
|
39
|
+
setSleepTimerManager(null);
|
|
40
|
+
}
|
|
41
|
+
}, [player]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (playerState?.isLive) {
|
|
45
|
+
// Resetting a sleep timer when audio content is live (Timer not supported on live video)
|
|
46
|
+
sleepTimerManager.setSleepTimer(SLEEP_TIMER_OFF).catch();
|
|
47
|
+
}
|
|
48
|
+
}, [playerState?.isLive]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!playerState.isReadyToPlay) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void sleepTimerManager?.loadSleep();
|
|
56
|
+
}, [playerState.isReadyToPlay, sleepTimerManager]);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const sleepSubscription =
|
|
60
|
+
sleepTimerManager?.sleepTimerState$.subscribe(setSleepTimerState);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
sleepSubscription?.unsubscribe();
|
|
64
|
+
};
|
|
65
|
+
}, [sleepTimerManager]);
|
|
66
|
+
|
|
67
|
+
const onSleepSelect = useCallback(
|
|
68
|
+
async (item) => {
|
|
69
|
+
await sleepTimerManager?.setSleepTimer(item);
|
|
70
|
+
},
|
|
71
|
+
[sleepTimerManager]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const onSleepButtonPress = useCallback(() => {
|
|
75
|
+
const timerDate = sleepTimerState.timerDate;
|
|
76
|
+
|
|
77
|
+
const timerRemaining =
|
|
78
|
+
timerDate !== 0 && +timerDate > Date.now() ? timerDate : null;
|
|
79
|
+
|
|
80
|
+
openBottomSheetModal({
|
|
81
|
+
modalBottomSheetContentProps: {
|
|
82
|
+
headerComponent: SleepTimerModalHeader,
|
|
83
|
+
items: getSleepTimerOptions(offLabel, timerRemaining),
|
|
84
|
+
onPress: onSleepSelect,
|
|
85
|
+
title,
|
|
86
|
+
summary: timerRemaining,
|
|
87
|
+
itemProps: getBottomSheetModalItemProps,
|
|
88
|
+
iconProps: getBottomSheetModalIconProps,
|
|
89
|
+
labelProps: getBottomSheetModalLabelProps,
|
|
90
|
+
getSelectedItemIcon: getBottomSheetModalSelectedItemIcon,
|
|
91
|
+
},
|
|
92
|
+
options: {
|
|
93
|
+
supportedOrientations: [
|
|
94
|
+
"portrait",
|
|
95
|
+
"landscape",
|
|
96
|
+
"portrait-upside-down",
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}, [sleepTimerState.timerDate, sleepTimerState.timer, title, onSleepSelect]);
|
|
101
|
+
|
|
102
|
+
return useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
onSleepButtonPress,
|
|
105
|
+
sleepTimerState,
|
|
106
|
+
}),
|
|
107
|
+
[onSleepButtonPress, sleepTimerState]
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -28,12 +28,14 @@ const createTimerOptions = (value) => {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
export const getSleepTimerOptions = (
|
|
31
|
+
export const getSleepTimerOptions = (offLabel, sleepTimerValue) => {
|
|
32
32
|
return [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
sleepTimerValue
|
|
34
|
+
? {
|
|
35
|
+
label: offLabel,
|
|
36
|
+
value: 0,
|
|
37
|
+
}
|
|
38
|
+
: null,
|
|
37
39
|
...[5, 10, 15, 30, 45, 60, 120].map(createTimerOptions),
|
|
38
40
|
];
|
|
39
41
|
};
|
|
@@ -116,3 +116,9 @@ export const getBottomSheetModalSelectedItemIcon = (theme) =>
|
|
|
116
116
|
|
|
117
117
|
export const getBottomSheetModalDefaultItemIcon = (theme) =>
|
|
118
118
|
theme["audio_modal_bottom_sheet_item_default_icon"] || null;
|
|
119
|
+
|
|
120
|
+
export const selectSleepTitle = (configuration) =>
|
|
121
|
+
configuration.playback_sleep_title;
|
|
122
|
+
|
|
123
|
+
export const selectSleepOffLabel = (configuration) =>
|
|
124
|
+
configuration.playback_sleep_off_label;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
3
|
+
import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";
|
|
4
|
+
import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
|
+
|
|
6
|
+
const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
|
|
7
|
+
|
|
8
|
+
const fakeEntry: any = {
|
|
9
|
+
title: "Chromecast use is casting fake entry",
|
|
10
|
+
id: "quick-brick-use-is-casting-fake-entry",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the cast action state connection status, does not update when app is in background on Apple platforms
|
|
15
|
+
* On other platforms, it continues to check the cast action state even when the app is in background
|
|
16
|
+
*/
|
|
17
|
+
export const useIsCasting = () => {
|
|
18
|
+
const actionContext = useActions(CHROMECAST_IDENTIFIER);
|
|
19
|
+
const appState = useAppState();
|
|
20
|
+
const isAppInForeground = appState === "active";
|
|
21
|
+
const shouldOnlyUpdateInForeground = isApplePlatform();
|
|
22
|
+
|
|
23
|
+
const [castActionState, setCastActionState] = React.useState(
|
|
24
|
+
!actionContext ? null : actionContext.initialEntryState(fakeEntry)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (!shouldOnlyUpdateInForeground || isAppInForeground) {
|
|
29
|
+
if (actionContext && castActionState === null) {
|
|
30
|
+
setCastActionState(actionContext.initialEntryState(fakeEntry));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, [
|
|
34
|
+
actionContext,
|
|
35
|
+
setCastActionState,
|
|
36
|
+
isAppInForeground,
|
|
37
|
+
shouldOnlyUpdateInForeground,
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
if (typeof actionContext?.addListener === "function") {
|
|
42
|
+
return actionContext?.addListener(String(fakeEntry.id), (state) => {
|
|
43
|
+
// Only check foreground state on Apple platforms
|
|
44
|
+
if (!shouldOnlyUpdateInForeground || isAppInForeground) {
|
|
45
|
+
setCastActionState(state);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
setCastActionState,
|
|
51
|
+
actionContext,
|
|
52
|
+
isAppInForeground,
|
|
53
|
+
shouldOnlyUpdateInForeground,
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return !!castActionState?.connected;
|
|
57
|
+
};
|