@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.
Files changed (39) 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 +46 -60
  6. package/src/Player/AudioLayer/Layout/Controls/utils.ts +5 -13
  7. package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
  8. package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +38 -19
  9. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +35 -25
  10. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +39 -40
  11. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +6 -22
  12. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +1 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +99 -0
  14. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +37 -20
  15. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +19 -0
  16. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +101 -103
  17. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +5 -14
  18. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +39 -13
  19. package/src/Player/AudioLayer/Layout/const.ts +6 -0
  20. package/src/Player/AudioLayer/Layout/debug.ts +1 -1
  21. package/src/Player/AudioLayer/Layout/hooks/index.ts +3 -1
  22. package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
  23. package/src/Player/AudioLayer/Layout/hooks/{useImageSize.ts → useTabletLayoutImageSize.ts} +10 -5
  24. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +1 -1
  25. package/src/Player/AudioLayer/Layout/utils.ts +7 -0
  26. package/src/Player/AudioLayer/SleepTimerManager.ts +22 -11
  27. package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
  28. package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
  29. package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
  30. package/src/Player/Const/sleepTimerOptions.ts +7 -5
  31. package/src/Player/Utils/index.tsx +6 -0
  32. package/src/Player/hooks/useIsCasting.tsx +57 -0
  33. package/src/Player/index.tsx +84 -37
  34. package/src/QuickBrickPlayer.tsx +3 -0
  35. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
  36. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
  37. package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
  38. package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
  39. package/src/utils/utils.ts +0 -25
@@ -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,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(height, TABLET_LANDSCAPE.MARGIN_VERTICAL);
56
- 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
+ );
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
- marginBottom: applyMarginBottom({ isTablet, marginBottom }),
100
+ style={[
101
+ styles.container,
102
+ {
103
+ marginBottom: applyMarginBottom({ isTablet, marginBottom }),
79
104
 
80
- ...withDebug({
81
- borderWidth: 2,
82
- borderColor: "red",
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.container}
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;
@@ -1,5 +1,5 @@
1
1
  // use this variable to activate borders on AudioPlayer. It will help you to fix layout if needed.
2
- const DEBUG = false;
2
+ export const DEBUG = false;
3
3
 
4
4
  export const withDebug = (style) => {
5
5
  return DEBUG ? style : {};
@@ -1,3 +1,5 @@
1
- export { useImageSize } from "./useImageSize";
1
+ export { useTabletLayoutImageSize } from "./useTabletLayoutImageSize";
2
+
3
+ export { useMobileLayoutImageSize } from "./useMobileLayoutImageSize";
2
4
 
3
5
  export { useTransportInfoHeight } from "./useTransportInfoHeight";
@@ -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 { take, last } from "rxjs/operators";
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 useImageSize = ({ isPortraitMode }: Props): Return => {
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(take(isPortraitMode ? 1 : 2), last())
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
- // wait the second layout change in order to get proper offsetY
67
- .pipe(take(2), last())
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(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.
@@ -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 = (configuration) => {
31
+ export const getSleepTimerOptions = (offLabel, sleepTimerValue) => {
32
32
  return [
33
- {
34
- label: configuration["playback_sleep_off_label"],
35
- value: 0,
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
+ };