@applicaster/quick-brick-player 13.0.0-rc.13 → 13.0.0-rc.130

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 (49) hide show
  1. package/package.json +27 -14
  2. package/src/CellPlayer/Assets/AssetsComponent.tsx +17 -3
  3. package/src/Player/AudioLayer/AudioDescription.tsx +107 -68
  4. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +142 -295
  5. package/src/Player/AudioLayer/Layout/Controls/VerticalGutter.tsx +45 -0
  6. package/src/Player/AudioLayer/Layout/Controls/index.tsx +291 -0
  7. package/src/Player/AudioLayer/Layout/{defaultLayoutsConts.ts → Controls/utils.ts} +9 -27
  8. package/src/Player/AudioLayer/Layout/Debug/index.tsx +55 -0
  9. package/src/Player/AudioLayer/Layout/{DockedLayout.tsx → DockedControls/index.tsx} +77 -81
  10. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +222 -0
  11. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +87 -0
  12. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +20 -0
  13. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +1 -0
  14. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +99 -0
  15. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +112 -0
  16. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +36 -0
  17. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +212 -0
  18. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +215 -0
  19. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +128 -0
  20. package/src/Player/AudioLayer/Layout/VerticalGutter/index.tsx +8 -0
  21. package/src/Player/AudioLayer/Layout/const.ts +26 -0
  22. package/src/Player/AudioLayer/Layout/debug.ts +6 -0
  23. package/src/Player/AudioLayer/Layout/hooks/index.ts +5 -0
  24. package/src/Player/AudioLayer/Layout/hooks/useMobileLayoutImageSize.ts +14 -0
  25. package/src/Player/AudioLayer/Layout/hooks/useTabletLayoutImageSize.ts +105 -0
  26. package/src/Player/AudioLayer/Layout/hooks/useTransportInfoHeight.ts +54 -0
  27. package/src/Player/AudioLayer/Layout/index.tsx +21 -48
  28. package/src/Player/AudioLayer/Layout/types.ts +7 -0
  29. package/src/Player/AudioLayer/Layout/utils.ts +7 -0
  30. package/src/Player/AudioLayer/SleepTimerManager.ts +130 -0
  31. package/src/Player/AudioLayer/SpeedManager.ts +52 -0
  32. package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +66 -0
  33. package/src/Player/AudioLayer/components/SleepTimerModalHeader/index.ts +1 -0
  34. package/src/Player/AudioLayer/useSleepTimerModal.ts +109 -0
  35. package/src/Player/AudioLayer/utils.ts +27 -0
  36. package/src/Player/Const/sleepTimerOptions.ts +41 -0
  37. package/src/Player/Const/speedOptions.ts +4 -0
  38. package/src/Player/Utils/Utils.ts +0 -5
  39. package/src/Player/Utils/index.tsx +123 -0
  40. package/src/Player/hooks/useIsCasting.tsx +57 -0
  41. package/src/Player/index.tsx +106 -49
  42. package/src/QuickBrickPlayer.tsx +3 -0
  43. package/src/Player/AudioLayer/Layout/DefaultLayout.tsx +0 -452
  44. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.ts +0 -3
  45. package/src/Player/AudioLayer/LinearGradient/LinearGradientProxy.web.ts +0 -3
  46. package/src/Player/AudioLayer/LinearGradient/index.ts +0 -1
  47. package/src/Player/AudioLayer/MagicBG/index.tsx +0 -155
  48. package/src/Player/Helpers/VideoResizeMode.ts +0 -7
  49. package/src/utils/utils.ts +0 -25
@@ -0,0 +1,222 @@
1
+ import * as React from "react";
2
+ import { StyleSheet, View, ViewStyle } from "react-native";
3
+ import {
4
+ useSafeAreaFrame,
5
+ useSafeAreaInsets,
6
+ } from "react-native-safe-area-context";
7
+ import { useModalAnimationContext } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
8
+
9
+ import { MOBILE, NAVBAR_HEIGHT } from "./const";
10
+ import { TransportInfo } from "./TransportInfo";
11
+ import { PlayerImage } from "./PlayerImage";
12
+ import { Controls } from "./Controls";
13
+
14
+ import { useMobileLayoutImageSize, useTransportInfoHeight } from "./hooks";
15
+ import { DockedControls } from "./DockedControls";
16
+ import { DEBUG, withDebug } from "./debug";
17
+ import {
18
+ BottomDebugSquare,
19
+ LeftDebugSquare,
20
+ RightDebugSquare,
21
+ } from "./Debug/index";
22
+
23
+ import { Props } from "./types";
24
+
25
+ const styles = StyleSheet.create({
26
+ container: {
27
+ flex: 1,
28
+ },
29
+
30
+ flex: {
31
+ flex: 1,
32
+ },
33
+
34
+ audioPlayerContainer: {
35
+ flex: 1,
36
+ justifyContent: "flex-start",
37
+
38
+ minHeight: MOBILE.MIN_HEIGHT,
39
+
40
+ marginLeft: MOBILE.MARGIN_HORIZONTAL,
41
+ marginRight: MOBILE.MARGIN_HORIZONTAL,
42
+
43
+ marginTop: MOBILE.MARGIN_TOP,
44
+
45
+ ...withDebug({
46
+ borderWidth: 2,
47
+ borderColor: "blue",
48
+ }),
49
+ },
50
+ });
51
+
52
+ const mobileStyles = ({
53
+ screenHeight,
54
+ topInset,
55
+ bottomInset,
56
+ imageSize,
57
+ }): ViewStyle => ({
58
+ height: screenHeight - topInset - bottomInset,
59
+
60
+ opacity: imageSize ? 1 : 0, // hide content until imageSize is calculated
61
+
62
+ ...withDebug({
63
+ borderWidth: 2,
64
+ borderColor: "magenta",
65
+ }),
66
+ });
67
+
68
+ export function MobileLayout({
69
+ entry,
70
+ layoutState,
71
+ playerState,
72
+ value,
73
+ onPlaybackButtonPress,
74
+ onRewindButtonPress,
75
+ onForwardButtonPress,
76
+ onPlayerSeek,
77
+ onSkipStart,
78
+ onSkipLive,
79
+ onSpeedButtonPress,
80
+ onCloseVideoModal,
81
+ playbackSpeedName,
82
+ configuration,
83
+ dimensions,
84
+ playNextData,
85
+ title,
86
+ summary,
87
+ }: Props) {
88
+ const { docked } = layoutState;
89
+
90
+ const { bottom: bottomInset, top: topInset } = useSafeAreaInsets();
91
+ const { height: screenHeight } = useSafeAreaFrame();
92
+ const { progressBarHeight } = useModalAnimationContext();
93
+
94
+ const { imageSize } = useMobileLayoutImageSize();
95
+
96
+ const { height: transportInfoHeight, onLayout: onTransportInfoLayout } =
97
+ useTransportInfoHeight();
98
+
99
+ const controlsContainerHeight = React.useMemo(() => {
100
+ return (
101
+ screenHeight -
102
+ topInset -
103
+ imageSize -
104
+ NAVBAR_HEIGHT -
105
+ MOBILE.MARGIN_BOTTOM -
106
+ MOBILE.MARGIN_TOP -
107
+ bottomInset
108
+ );
109
+ }, [screenHeight, topInset, imageSize, bottomInset]);
110
+
111
+ const verticalOffsetForImage = React.useMemo(() => {
112
+ return transportInfoHeight + MOBILE.MARGIN_TOP - progressBarHeight;
113
+ }, [transportInfoHeight, progressBarHeight]);
114
+
115
+ return (
116
+ <View
117
+ style={[
118
+ styles.container,
119
+ mobileStyles({
120
+ screenHeight,
121
+ topInset,
122
+ bottomInset,
123
+ imageSize,
124
+ }),
125
+ ]}
126
+ >
127
+ {DEBUG ? <LeftDebugSquare /> : null}
128
+ {DEBUG ? <RightDebugSquare /> : null}
129
+ {DEBUG ? <BottomDebugSquare /> : null}
130
+
131
+ {/* TransportInfo */}
132
+ <TransportInfo
133
+ entry={entry}
134
+ layoutState={layoutState}
135
+ value={value}
136
+ configuration={configuration}
137
+ onLayout={onTransportInfoLayout}
138
+ />
139
+
140
+ {/* TransportInfo */}
141
+
142
+ <View
143
+ style={[
144
+ styles.audioPlayerContainer,
145
+ {
146
+ width: imageSize,
147
+ },
148
+ ]}
149
+ >
150
+ {/* PlayerImage */}
151
+ <View
152
+ style={[
153
+ styles.flex,
154
+ {
155
+ maxHeight: imageSize,
156
+
157
+ ...withDebug({
158
+ borderWidth: 2,
159
+ borderColor: "yellow",
160
+ }),
161
+ },
162
+ ]}
163
+ >
164
+ <PlayerImage
165
+ entry={entry}
166
+ layoutState={layoutState}
167
+ playerState={playerState}
168
+ value={value}
169
+ configuration={configuration}
170
+ onPlayerSeek={onPlayerSeek}
171
+ onCloseVideoModal={onCloseVideoModal}
172
+ dimensions={dimensions}
173
+ playNextData={playNextData}
174
+ title={title}
175
+ summary={summary}
176
+ docked={docked}
177
+ imageWidth={imageSize}
178
+ verticalOffset={verticalOffsetForImage}
179
+ />
180
+ </View>
181
+ {/* PlayerImage */}
182
+
183
+ <View
184
+ style={{
185
+ height: controlsContainerHeight,
186
+ }}
187
+ >
188
+ <Controls
189
+ entry={entry}
190
+ layoutState={layoutState}
191
+ playerState={playerState}
192
+ value={value}
193
+ title={title}
194
+ summary={summary}
195
+ playbackSpeedName={playbackSpeedName}
196
+ onPlaybackButtonPress={onPlaybackButtonPress}
197
+ onRewindButtonPress={onRewindButtonPress}
198
+ onForwardButtonPress={onForwardButtonPress}
199
+ onPlayerSeek={onPlayerSeek}
200
+ onSkipStart={onSkipStart}
201
+ onSkipLive={onSkipLive}
202
+ onSpeedButtonPress={onSpeedButtonPress}
203
+ docked={docked}
204
+ />
205
+ </View>
206
+ </View>
207
+
208
+ <DockedControls
209
+ entry={entry}
210
+ layoutState={layoutState}
211
+ playerState={playerState}
212
+ value={value}
213
+ dimensions={dimensions}
214
+ onCloseVideoModal={onCloseVideoModal}
215
+ onPlayerSeek={onPlayerSeek}
216
+ playNextData={playNextData}
217
+ title={title}
218
+ summary={summary}
219
+ />
220
+ </View>
221
+ );
222
+ }
@@ -0,0 +1,87 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { Image, Animated, StyleSheet, ImageStyle } from "react-native";
3
+
4
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
5
+ import {
6
+ useModalAnimationContext,
7
+ PlayerAnimationStateEnum,
8
+ DURATION_TO_MINIMIZE,
9
+ DURATION_TO_MAXIMIZE,
10
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
11
+ import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
12
+
13
+ import { useChangePlayerState } from "./hooks";
14
+
15
+ const styles = StyleSheet.create({
16
+ container: {
17
+ // we have to specify flex:0 and dimensions to make animation work
18
+ flex: 0,
19
+ aspectRatio: 1,
20
+ },
21
+ });
22
+
23
+ type Props = PropsWithChildren<{
24
+ entry: ZappEntry;
25
+ style?: ImageStyle | ImageStyle[];
26
+ imageWidth: number;
27
+ docked: boolean;
28
+ }>;
29
+
30
+ export const AnimatedImage = ({ entry, style, imageWidth, docked }: Props) => {
31
+ const heightAnim = React.useRef(new Animated.Value(imageWidth)).current;
32
+
33
+ const source = React.useMemo(
34
+ () => ({ uri: imageSrcFromMediaItem(entry, undefined) }),
35
+ [entry]
36
+ );
37
+
38
+ const { minimisedHeight } = useModalAnimationContext();
39
+
40
+ const changePlayerState$ = useChangePlayerState({ docked });
41
+
42
+ React.useEffect(() => {
43
+ const subscription = changePlayerState$.subscribe({
44
+ next: (nextPlayerState: PlayerAnimationStateT) => {
45
+ if (nextPlayerState === PlayerAnimationStateEnum.minimize) {
46
+ Animated.timing(heightAnim, {
47
+ toValue: minimisedHeight,
48
+ duration: DURATION_TO_MINIMIZE,
49
+ useNativeDriver: false, // width/height can't use native driver
50
+ }).start();
51
+ }
52
+
53
+ if (nextPlayerState === PlayerAnimationStateEnum.maximize) {
54
+ Animated.timing(heightAnim, {
55
+ toValue: imageWidth,
56
+ duration: DURATION_TO_MAXIMIZE,
57
+ useNativeDriver: false, // width/height can't use native driver
58
+ }).start();
59
+ }
60
+
61
+ if (nextPlayerState === PlayerAnimationStateEnum.appear_as_docked) {
62
+ // minimize image immediatly
63
+ heightAnim.setValue(minimisedHeight);
64
+ }
65
+ },
66
+ });
67
+
68
+ return () => {
69
+ subscription.unsubscribe();
70
+ };
71
+ }, [changePlayerState$, minimisedHeight, imageWidth]);
72
+
73
+ if (!source) return null;
74
+
75
+ return (
76
+ <Animated.View
77
+ style={[
78
+ styles.container,
79
+ {
80
+ width: heightAnim,
81
+ },
82
+ ]}
83
+ >
84
+ <Image resizeMode="cover" style={style} source={source} />
85
+ </Animated.View>
86
+ );
87
+ };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ import { Image, ImageStyle } from "react-native";
3
+
4
+ import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
5
+
6
+ type Props = {
7
+ entry: ZappEntry;
8
+ style?: ImageStyle;
9
+ };
10
+
11
+ export const FlexImage = ({ entry, style }: Props) => {
12
+ const source = React.useMemo(
13
+ () => ({ uri: imageSrcFromMediaItem(entry, undefined) }),
14
+ [entry]
15
+ );
16
+
17
+ if (!source) return null;
18
+
19
+ return <Image resizeMode="cover" style={style} source={source} />;
20
+ };
@@ -0,0 +1 @@
1
+ export { useChangePlayerState } from "./useChangePlayerState";
@@ -0,0 +1,99 @@
1
+ import * as React from "react";
2
+ import { Subject, Observable } from "rxjs";
3
+
4
+ import { distinctUntilChanged } from "rxjs/operators";
5
+ import { isNil } from "@applicaster/zapp-react-native-utils/utils";
6
+ import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
7
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
8
+ import {
9
+ useModalAnimationContext,
10
+ PlayerAnimationStateEnum,
11
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
12
+ import { PlayerAnimationStateT } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation/ModalAnimationContext";
13
+
14
+ const builder = () => {
15
+ const changePlayerState$ = new Subject<PlayerAnimationStateT>();
16
+
17
+ const emitMaximized = (): void => {
18
+ changePlayerState$.next(PlayerAnimationStateEnum.maximize);
19
+ };
20
+
21
+ const emitMinimized = (): void => {
22
+ changePlayerState$.next(PlayerAnimationStateEnum.minimize);
23
+ };
24
+
25
+ const emitAppearInDocked = (): void => {
26
+ // if audio_player was presented in docked mode
27
+ changePlayerState$.next(PlayerAnimationStateEnum.appear_as_docked);
28
+ };
29
+
30
+ return {
31
+ emitMaximized,
32
+ emitMinimized,
33
+ emitAppearInDocked,
34
+ changePlayerState$: changePlayerState$.pipe(distinctUntilChanged()),
35
+ };
36
+ };
37
+
38
+ export const useChangePlayerState = ({
39
+ docked,
40
+ }): Observable<PlayerAnimationStateT> => {
41
+ const changePlayerStateRef =
42
+ useRefWithInitialValue<ReturnType<typeof builder>>(builder);
43
+
44
+ const { playerAnimationState } = useModalAnimationContext();
45
+
46
+ const previousPlayerAnimationState = usePrevious(playerAnimationState);
47
+
48
+ React.useEffect(() => {
49
+ if (
50
+ !docked &&
51
+ isNil(previousPlayerAnimationState) &&
52
+ playerAnimationState === PlayerAnimationStateEnum.minimize
53
+ ) {
54
+ changePlayerStateRef.current.emitMinimized();
55
+
56
+ return;
57
+ }
58
+
59
+ if (
60
+ !docked &&
61
+ isNil(previousPlayerAnimationState) &&
62
+ playerAnimationState === PlayerAnimationStateEnum.maximize
63
+ ) {
64
+ changePlayerStateRef.current.emitMaximized();
65
+
66
+ return;
67
+ }
68
+
69
+ if (
70
+ !docked &&
71
+ previousPlayerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
72
+ playerAnimationState === PlayerAnimationStateEnum.maximize
73
+ ) {
74
+ changePlayerStateRef.current.emitMaximized();
75
+
76
+ return;
77
+ }
78
+
79
+ if (
80
+ !docked &&
81
+ previousPlayerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
82
+ playerAnimationState === PlayerAnimationStateEnum.minimize
83
+ ) {
84
+ changePlayerStateRef.current.emitMinimized();
85
+
86
+ return;
87
+ }
88
+
89
+ if (
90
+ docked &&
91
+ isNil(previousPlayerAnimationState) &&
92
+ isNil(playerAnimationState)
93
+ ) {
94
+ changePlayerStateRef.current.emitAppearInDocked();
95
+ }
96
+ }, [previousPlayerAnimationState, playerAnimationState, docked]);
97
+
98
+ return changePlayerStateRef.current.changePlayerState$;
99
+ };
@@ -0,0 +1,112 @@
1
+ import * as React from "react";
2
+ import { LayoutChangeEvent, View } from "react-native";
3
+
4
+ import {
5
+ AnimationComponent,
6
+ ComponentAnimationType,
7
+ useModalAnimationContext,
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";
11
+
12
+ import { AnimatedImage } from "./AnimatedImage";
13
+ import { FlexImage } from "./FlexImage";
14
+
15
+ import { styles } from "./styles";
16
+ import { calculateBorderRadius } from "../../utils";
17
+ import { PlayNextData } from "../types";
18
+
19
+ type Props = {
20
+ entry: ZappEntry;
21
+ layoutState: QuickBrickPlayer.LayoutState;
22
+ playerState: QuickBrickPlayer.PlayerState;
23
+ value: GetValue;
24
+ configuration: QuickBrickPlayer.AudioPlayerProps["configuration"];
25
+ dimensions: QuickBrickPlayer.AudioPlayerProps["dimensions"];
26
+ onCloseVideoModal: Callback;
27
+ onPlayerSeek: (targetTime: number) => void;
28
+ playNextData: PlayNextData;
29
+ title: string | number;
30
+ summary: string | number;
31
+ docked: boolean;
32
+ imageWidth?: Option<number>;
33
+ verticalOffset: number;
34
+ onLayoutImage?: (event: LayoutChangeEvent) => void;
35
+ };
36
+
37
+ export const PlayerImage = (props: Props) => {
38
+ const {
39
+ entry,
40
+ dimensions,
41
+ docked,
42
+ verticalOffset,
43
+ imageWidth,
44
+ configuration,
45
+ onLayoutImage = noop,
46
+ } = props;
47
+
48
+ const {
49
+ isActiveGesture,
50
+ startComponentsAnimation,
51
+ playerAnimationState,
52
+ minimisedHeight,
53
+ } = useModalAnimationContext();
54
+
55
+ const isShadowEnabled = toBooleanWithDefaultTrue(
56
+ configuration?.audio_player_artwork_shadow_enabled
57
+ );
58
+
59
+ const shouldShowShadow = !docked && isShadowEnabled;
60
+
61
+ const borderRadius = calculateBorderRadius(
62
+ docked,
63
+ startComponentsAnimation,
64
+ isActiveGesture,
65
+ playerAnimationState,
66
+ configuration
67
+ );
68
+
69
+ return (
70
+ <AnimationComponent
71
+ animationType={ComponentAnimationType.moveUpComponent}
72
+ additionalData={{
73
+ marginTop: verticalOffset,
74
+ }}
75
+ style={styles.flex}
76
+ >
77
+ <AnimationComponent
78
+ animationType={ComponentAnimationType.moveComponentHorizontal}
79
+ additionalData={{
80
+ // we move image horiontally to this value due artwork is centered
81
+ moveValue: (dimensions.width - minimisedHeight) / 2,
82
+ }}
83
+ style={styles.alignItemsCenter}
84
+ >
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>
109
+ </AnimationComponent>
110
+ </AnimationComponent>
111
+ );
112
+ };
@@ -0,0 +1,36 @@
1
+ /* eslint-disable react-native/no-color-literals */
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)";
6
+
7
+ export const styles = StyleSheet.create({
8
+ backgroundImageContainer: {
9
+ flex: 1,
10
+ aspectRatio: 1,
11
+ overflow: "hidden",
12
+ },
13
+
14
+ alignItemsCenter: {
15
+ flex: 1,
16
+ alignItems: "center",
17
+ },
18
+
19
+ flex: {
20
+ flex: 1,
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
+ },
36
+ });