@applicaster/quick-brick-player 13.0.0-rc.110 → 13.0.0-rc.111

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "13.0.0-rc.110",
3
+ "version": "13.0.0-rc.111",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "homepage": "https://github.com/applicaster/quickbrick#readme",
37
37
  "dependencies": {
38
- "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.106",
38
+ "@applicaster/quick-brick-mobile-transport-controls": "13.0.0-rc.111",
39
39
  "@applicaster/quick-brick-tv-transport-controls": "13.0.0-rc.56",
40
- "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.110",
41
- "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.110",
42
- "@applicaster/zapp-react-native-utils": "13.0.0-rc.110",
40
+ "@applicaster/zapp-react-native-tvos-app": "13.0.0-rc.111",
41
+ "@applicaster/zapp-react-native-ui-components": "13.0.0-rc.111",
42
+ "@applicaster/zapp-react-native-utils": "13.0.0-rc.111",
43
43
  "query-string": "7.1.3",
44
44
  "react-native-web-linear-gradient": "1.1.2",
45
45
  "shaka-player": "4.3.5",
@@ -52,7 +52,6 @@
52
52
  "@applicaster/applicaster-types": "*",
53
53
  "@applicaster/blur": "*",
54
54
  "@applicaster/quick-brick-xray": "*",
55
- "@applicaster/react-native-color-thief": "*",
56
55
  "@applicaster/zapp-pipes-v2-client": "*",
57
56
  "@applicaster/zapp-react-dom-ui-components": "*",
58
57
  "@applicaster/zapp-react-native-redux": "*",
@@ -1,12 +1,13 @@
1
1
  import * as React from "react";
2
+ import { useEffect, useState } from "react";
2
3
  import {
4
+ Dimensions as ScreenDimensions,
3
5
  StyleSheet,
4
- ViewStyle,
5
6
  View,
6
- Dimensions as ScreenDimensions,
7
+ ViewStyle,
7
8
  } from "react-native";
8
9
 
9
- import { SafeAreaView, Edge } from "react-native-safe-area-context";
10
+ import { Edge, SafeAreaView } from "react-native-safe-area-context";
10
11
  import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
11
12
  import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
12
13
  import * as R from "ramda";
@@ -20,11 +21,8 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
20
21
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
21
22
  import { PlayerDetails } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/PlayerDetails";
22
23
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
23
- import { useEffect, useState } from "react";
24
- import { MagicBG } from "./MagicBG";
25
24
  import { formatSpeed } from "./utils";
26
25
  import {
27
- addQueryParam,
28
26
  getBottomSheetModalDefaultItemIcon,
29
27
  getBottomSheetModalIconProps,
30
28
  getBottomSheetModalItemProps,
@@ -33,20 +31,18 @@ import {
33
31
  } from "../Utils";
34
32
  import { useTitleSummaryOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerTitleSummaryOverlay";
35
33
  import {
34
+ AnimatedScrollModal,
36
35
  AnimationComponent,
37
36
  ComponentAnimationType,
38
- AnimatedScrollModal,
39
37
  } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
40
38
  import { PADDING_HORIZONTAL } from "./Layout/const";
41
39
  import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
42
40
  import {
41
+ SLEEP_TIMER_OFF,
43
42
  SleepTimerController,
44
43
  SleepTimerState,
45
- SLEEP_TIMER_OFF,
46
44
  } from "./SleepTimerManager";
47
- import { SpeedController, PlaybackSpeed, DEFAULT_SPEED } from "./SpeedManager";
48
- import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
49
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
45
+ import { DEFAULT_SPEED, PlaybackSpeed, SpeedController } from "./SpeedManager";
50
46
  import { openBottomSheetModal } from "@applicaster/zapp-react-native-utils/modalState";
51
47
  import { getSleepTimerOptions } from "../Const/sleepTimerOptions";
52
48
  import { speedOptions } from "../Const/speedOptions";
@@ -65,7 +61,6 @@ type Dimensions = {
65
61
 
66
62
  type Configuration = {
67
63
  [key: string]: any;
68
- magic_background?: boolean;
69
64
  audio_player_background_image_query?: string;
70
65
  audio_player_background_image_default_color?: string;
71
66
  tablet_landscape_sidebar_width?: string;
@@ -75,7 +70,6 @@ type Configuration = {
75
70
  type Props = {
76
71
  children: React.ReactNode;
77
72
  inline?: boolean;
78
- docked?: boolean;
79
73
  configuration: Configuration;
80
74
  style: ViewStyle & Dimensions;
81
75
  entry: ZappEntry;
@@ -108,10 +102,8 @@ const edges: Edge[] = ["right", "left", "top"];
108
102
  export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
109
103
  const player = usePlayer();
110
104
  const playerState = usePlayerState("audio-player-wrapper");
111
- const theme = useTheme();
112
105
 
113
106
  const {
114
- docked,
115
107
  configuration,
116
108
  style,
117
109
  entry,
@@ -330,114 +322,61 @@ export const AudioPlayerWrapper: React.FC<Props> = (props: Props) => {
330
322
  player?.seekTo(0);
331
323
  }, [player]);
332
324
 
333
- const MainContent = React.useMemo(() => {
334
- return (
335
- <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
336
- <AnimationComponent
337
- animationType={ComponentAnimationType.moveUpComponent}
338
- additionalData={{ saveArea: true }}
339
- style={Styles.playerContainer}
340
- >
341
- <AnimatedScrollModal>
342
- <View style={Styles.playerContainer} onLayout={onLayout}>
343
- <Layout
344
- entry={entry}
345
- playerState={playerState}
346
- layoutState={layoutState}
347
- value={value}
348
- onPlaybackButtonPress={onPlaybackButtonPress}
349
- onRewindButtonPress={onRewindButtonPress}
350
- onForwardButtonPress={onForwardButtonPress}
351
- onPlayerSeek={onPlayerSeek}
352
- onSkipStart={onSkipStart}
353
- onSpeedButtonPress={onSpeedButtonPress}
354
- onSleepButtonPress={onSleepButtonPress}
355
- playbackSpeedName={formatSpeed(playbackSpeed.value)}
356
- playbackSleepName={
357
- sleepTimerState.timer.value === 0
358
- ? "sleep_timer"
359
- : "sleep_timer_active"
360
- }
361
- sleepTimerDate={sleepTimerState.timerDate}
362
- cancelSleepTimer={cancelSleepTimer}
363
- onSkipLive={onSkipLive}
364
- onCloseVideoModal={onCloseVideoModal}
365
- maximiseVideoModal={maximiseVideoModal}
366
- configuration={configuration}
367
- dimensions={dimensions}
368
- playNextData={playNextData}
369
- title={title}
370
- summary={summary}
371
- defaultDimensions={defaultDimensions}
372
- />
373
- <View style={Styles.paddingHorizontal}>
374
- {children}
375
- <AnimationComponent
376
- animationType={ComponentAnimationType.componentFade}
377
- >
378
- <PlayerDetails
379
- isAudioPlayer
380
- configuration={configuration}
381
- style={Styles.playerDetails}
382
- entry={entry}
383
- isTabletLandscape={isTabletLandscape}
384
- />
385
- </AnimationComponent>
386
- </View>
387
- </View>
388
- </AnimatedScrollModal>
389
- </AnimationComponent>
390
- </SafeAreaView>
391
- );
392
- }, [
393
- docked,
394
- style,
395
- entry,
396
- playerState,
397
- layoutState,
398
- value,
399
- onPlaybackButtonPress,
400
- onRewindButtonPress,
401
- onForwardButtonPress,
402
- onPlayerSeek,
403
- onCloseVideoModal,
404
- maximiseVideoModal,
405
- configuration,
406
- dimensions,
407
- children,
408
- playNextData,
409
- onSkipStart,
410
- onSkipLive,
411
- sleepTimerState.timer.value,
412
- sleepTimerState.timerDate,
413
- ]);
414
-
415
- if (configuration.magic_background) {
416
- const baseUrl = imageSrcFromMediaItem(entry, null);
417
-
418
- const queryParamName =
419
- configuration.audio_player_background_image_query || "width";
420
-
421
- const defaultBackgroundColor =
422
- configuration.audio_player_background_image_default_color ||
423
- theme?.app_background_color;
424
-
425
- const queryParamValue = 15;
426
-
427
- const url =
428
- baseUrl && addQueryParam(baseUrl, queryParamName, queryParamValue);
429
-
430
- return (
431
- <MagicBG
432
- image={url}
433
- fallbackUrl={baseUrl}
434
- colors={entry?.extensions?.backgroundColors}
435
- defaultColor={defaultBackgroundColor}
325
+ return (
326
+ <SafeAreaView style={[style, Styles.playerContainer]} edges={edges}>
327
+ <AnimationComponent
328
+ animationType={ComponentAnimationType.moveUpComponent}
329
+ additionalData={{ saveArea: true }}
330
+ style={Styles.playerContainer}
436
331
  >
437
- {MainContent}
438
- </MagicBG>
439
- );
440
- }
441
-
442
- return MainContent;
332
+ <AnimatedScrollModal>
333
+ <View style={Styles.playerContainer} onLayout={onLayout}>
334
+ <Layout
335
+ entry={entry}
336
+ playerState={playerState}
337
+ layoutState={layoutState}
338
+ value={value}
339
+ onPlaybackButtonPress={onPlaybackButtonPress}
340
+ onRewindButtonPress={onRewindButtonPress}
341
+ onForwardButtonPress={onForwardButtonPress}
342
+ onPlayerSeek={onPlayerSeek}
343
+ onSkipStart={onSkipStart}
344
+ onSpeedButtonPress={onSpeedButtonPress}
345
+ onSleepButtonPress={onSleepButtonPress}
346
+ playbackSpeedName={formatSpeed(playbackSpeed.value)}
347
+ playbackSleepName={
348
+ sleepTimerState.timer.value === 0
349
+ ? "sleep_timer"
350
+ : "sleep_timer_active"
351
+ }
352
+ sleepTimerDate={sleepTimerState.timerDate}
353
+ cancelSleepTimer={cancelSleepTimer}
354
+ onSkipLive={onSkipLive}
355
+ onCloseVideoModal={onCloseVideoModal}
356
+ maximiseVideoModal={maximiseVideoModal}
357
+ configuration={configuration}
358
+ dimensions={dimensions}
359
+ playNextData={playNextData}
360
+ title={title}
361
+ summary={summary}
362
+ />
363
+ <View style={Styles.paddingHorizontal}>
364
+ {children}
365
+ <AnimationComponent
366
+ animationType={ComponentAnimationType.componentFade}
367
+ >
368
+ <PlayerDetails
369
+ isAudioPlayer
370
+ configuration={configuration}
371
+ style={Styles.playerDetails}
372
+ entry={entry}
373
+ isTabletLandscape={isTabletLandscape}
374
+ />
375
+ </AnimationComponent>
376
+ </View>
377
+ </View>
378
+ </AnimatedScrollModal>
379
+ </AnimationComponent>
380
+ </SafeAreaView>
381
+ );
443
382
  };
@@ -30,7 +30,6 @@ type Props = {
30
30
  layoutState: QuickBrickPlayer.LayoutState;
31
31
  playerState: QuickBrickPlayer.PlayerState;
32
32
  value: GetValue;
33
- configuration: QuickBrickPlayer.AudioPlayerProps["configuration"];
34
33
  dimensions: QuickBrickPlayer.AudioPlayerProps["dimensions"];
35
34
  onCloseVideoModal: () => void;
36
35
  onPlayerSeek: (targetTime: number) => void;
@@ -181,7 +180,6 @@ export const DockedControls = (props: Props) => {
181
180
  layoutState,
182
181
  playerState,
183
182
  value,
184
- configuration,
185
183
  dimensions,
186
184
  onCloseVideoModal,
187
185
  onPlayerSeek,
@@ -254,12 +252,7 @@ export const DockedControls = (props: Props) => {
254
252
  ),
255
253
  ]}
256
254
  >
257
- <View
258
- style={[
259
- styles.contentInfo,
260
- !configuration.magic_background && backgroundColorStyle(value),
261
- ]}
262
- >
255
+ <View style={[styles.contentInfo, backgroundColorStyle(value)]}>
263
256
  <ContentInfo
264
257
  content={{ ...entry, title, summary }}
265
258
  value={value}
@@ -273,7 +266,7 @@ export const DockedControls = (props: Props) => {
273
266
  isTablet
274
267
  ? styles.controlsContainerForTablet
275
268
  : styles.controlsContainerForMobile,
276
- !configuration.magic_background && backgroundColorStyle(value),
269
+ backgroundColorStyle(value),
277
270
  ]}
278
271
  >
279
272
  <Controls
@@ -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
 
@@ -13,11 +13,11 @@ import { Controls } from "./Controls";
13
13
 
14
14
  import { useMobileLayoutImageSize, useTransportInfoHeight } from "./hooks";
15
15
  import { DockedControls } from "./DockedControls";
16
- import { withDebug, DEBUG } from "./debug";
16
+ import { DEBUG, withDebug } from "./debug";
17
17
  import {
18
+ BottomDebugSquare,
18
19
  LeftDebugSquare,
19
20
  RightDebugSquare,
20
- BottomDebugSquare,
21
21
  } from "./Debug/index";
22
22
 
23
23
  import { Props } from "./types";
@@ -218,7 +218,6 @@ export function MobileLayout({
218
218
  layoutState={layoutState}
219
219
  playerState={playerState}
220
220
  value={value}
221
- configuration={configuration}
222
221
  dimensions={dimensions}
223
222
  onCloseVideoModal={onCloseVideoModal}
224
223
  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 { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
@@ -203,7 +203,6 @@ export function TabletLandscapeLayout({
203
203
  layoutState={layoutState}
204
204
  playerState={playerState}
205
205
  value={value}
206
- configuration={configuration}
207
206
  dimensions={dimensions}
208
207
  onCloseVideoModal={onCloseVideoModal}
209
208
  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";
@@ -211,7 +211,6 @@ export function TabletPortraitLayout({
211
211
  layoutState={layoutState}
212
212
  playerState={playerState}
213
213
  value={value}
214
- configuration={configuration}
215
214
  dimensions={dimensions}
216
215
  onCloseVideoModal={onCloseVideoModal}
217
216
  onPlayerSeek={onPlayerSeek}
@@ -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
+ };
@@ -74,6 +74,7 @@ export default class VideoPlayer extends React.Component<
74
74
  state: QuickBrickPlayer.NativePlayerState = {
75
75
  audioTrackId: null,
76
76
  textTrackId: null,
77
+ isCasting: this.props.isCasting,
77
78
 
78
79
  // TODO: Can be deleted, on controller logic
79
80
  isAd: false,
@@ -698,6 +699,10 @@ export default class VideoPlayer extends React.Component<
698
699
  ];
699
700
  };
700
701
 
702
+ get supportNativeCast() {
703
+ return this.props.controller.supportNativeCast();
704
+ }
705
+
701
706
  render() {
702
707
  const {
703
708
  docked,
@@ -710,12 +715,9 @@ export default class VideoPlayer extends React.Component<
710
715
  muted,
711
716
  style,
712
717
  castAction,
713
- controller,
714
718
  startComponentsAnimation,
715
719
  } = this.props;
716
720
 
717
- const supportNativeCast = controller.supportNativeCast();
718
-
719
721
  const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
720
722
  this.state;
721
723
 
@@ -995,11 +997,14 @@ export default class VideoPlayer extends React.Component<
995
997
  const backgroundImageKey =
996
998
  this.getPluginConfiguration()?.player_preview_image_key;
997
999
 
1000
+ const shouldRenderNativePlayer =
1001
+ !this.props.isCasting || this.supportNativeCast;
1002
+
998
1003
  const renderMobilePlayer = () => {
999
1004
  return isFullScreenAudioPlayer && isAudioItem ? (
1000
1005
  // Ensure the View remains in the native hierarchy for proper rendering and interaction
1001
1006
  <View collapsable={false} style={containerStyle}>
1002
- {!castAction?.state?.connected || supportNativeCast ? (
1007
+ {shouldRenderNativePlayer ? (
1003
1008
  <this.props.PlayerComponent
1004
1009
  ref={this._assignRoot}
1005
1010
  {...nativeEvents}
@@ -1051,7 +1056,7 @@ export default class VideoPlayer extends React.Component<
1051
1056
  />
1052
1057
  ) : null}
1053
1058
 
1054
- {!castAction?.state?.connected || supportNativeCast ? (
1059
+ {shouldRenderNativePlayer ? (
1055
1060
  <AnimationComponent
1056
1061
  animationType={ComponentAnimationType.player}
1057
1062
  additionalData={{
@@ -5,6 +5,7 @@ import Player from "./Player";
5
5
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
6
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
7
7
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
8
+ import { useIsCasting } from "./Player/hooks/useIsCasting";
8
9
 
9
10
  const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
10
11
 
@@ -24,6 +25,7 @@ const QuickBrickPlayerComponent = (
24
25
  }, [props.PlayerComponent]);
25
26
 
26
27
  const castAction = useActions(CHROMECAST_IDENTIFIER);
28
+ const isCasting = useIsCasting();
27
29
 
28
30
  const isModal = Boolean(props?.isModal);
29
31
  const isScreenDisabled = !controller?.isFullScreenSupported() || !isModal;
@@ -48,6 +50,7 @@ const QuickBrickPlayerComponent = (
48
50
  {...props}
49
51
  ref={ref}
50
52
  castAction={!props?.disableCastAction && castAction}
53
+ isCasting={isCasting}
51
54
  PlayerComponent={props.PlayerComponent}
52
55
  openPiP={openPiP}
53
56
  closePiP={closePiP}
@@ -1,3 +0,0 @@
1
- import LinearGradient from "react-native-linear-gradient";
2
-
3
- export default LinearGradient;
@@ -1,3 +0,0 @@
1
- const WebLinearGradient = require("react-native-web-linear-gradient").default;
2
-
3
- export default WebLinearGradient;
@@ -1 +0,0 @@
1
- export { default } from "./LinearGradientProxy";
@@ -1,155 +0,0 @@
1
- /* eslint-disable react/prop-types */
2
- import * as React from "react";
3
- import { Animated, StyleSheet, View } from "react-native";
4
- import LinearGradient from "../LinearGradient";
5
- import { getDominantColor, getPalettes } from "../../../utils/utils";
6
- import { useEffect } from "react";
7
-
8
- type MagicBGProps = {
9
- children: React.ReactNode;
10
- image: string;
11
- fallbackUrl?: string;
12
- defaultColor: string;
13
- colors: string[];
14
- };
15
-
16
- const defaultColors = {
17
- overlay: "rgba(0, 0, 0, 0.35)",
18
- defaultGradientStart: "rgba(0, 0, 0, 0.25)",
19
- defaultGradientEnd: "rgba(0, 0, 0, 0)",
20
- };
21
-
22
- const styles = StyleSheet.create({
23
- container: {
24
- flex: 1,
25
- },
26
- fullScreenOverlay: {
27
- ...StyleSheet.absoluteFillObject,
28
- backgroundColor: defaultColors.overlay,
29
- },
30
- gradientOverlay: {
31
- position: "absolute",
32
- bottom: 0,
33
- left: 0,
34
- right: 0,
35
- height: "100%",
36
- },
37
- });
38
-
39
- const fetchPaletteAndDominantColor = async (imageUrl: string) => {
40
- const palette = await getPalettes(imageUrl);
41
-
42
- const colors = palette.map(
43
- (color: { r: number; g: number; b: number }) =>
44
- `rgb(${color.r}, ${color.g}, ${color.b})`
45
- );
46
-
47
- const dominant = await getDominantColor(imageUrl);
48
- const dominantColor = `rgb(${dominant.r}, ${dominant.g}, ${dominant.b})`;
49
-
50
- return { gradientColors: colors.slice(0, 2).reverse(), dominantColor };
51
- };
52
-
53
- export const MagicBG: React.FC<MagicBGProps> = ({
54
- children,
55
- image,
56
- fallbackUrl = "",
57
- colors = [],
58
- defaultColor,
59
- }) => {
60
- const [dominantColor, setDominantColor] =
61
- React.useState<string>(defaultColor);
62
-
63
- const [gradientColors, setGradientColors] = React.useState<string[]>([
64
- defaultColors.defaultGradientStart,
65
- defaultColors.defaultGradientEnd,
66
- ]);
67
-
68
- const [backgroundColorAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
69
- const [gradientOpacityAnim] = React.useState(new Animated.Value(0)); // from 0 to 1
70
-
71
- useEffect(() => {
72
- const animateColors = () => {
73
- Animated.timing(backgroundColorAnim, {
74
- toValue: 1,
75
- duration: 500,
76
- useNativeDriver: false,
77
- }).start();
78
-
79
- Animated.timing(gradientOpacityAnim, {
80
- toValue: 1,
81
- duration: 500,
82
- useNativeDriver: false,
83
- }).start();
84
- };
85
-
86
- const fetchColors = async () => {
87
- if (colors && colors.length > 0) {
88
- setGradientColors(colors.slice(0, 2));
89
- setDominantColor(colors[0]);
90
- animateColors();
91
- } else if (image) {
92
- try {
93
- const { gradientColors, dominantColor } =
94
- await fetchPaletteAndDominantColor(image);
95
-
96
- setGradientColors(gradientColors);
97
- setDominantColor(dominantColor);
98
- animateColors();
99
- } catch (error) {
100
- try {
101
- const { gradientColors, dominantColor } =
102
- await fetchPaletteAndDominantColor(fallbackUrl);
103
-
104
- setGradientColors(gradientColors);
105
- setDominantColor(dominantColor);
106
- animateColors();
107
- } catch {
108
- setGradientColors([
109
- defaultColors.defaultGradientStart,
110
- defaultColors.defaultGradientEnd,
111
- ]);
112
-
113
- setDominantColor(defaultColor);
114
- }
115
- }
116
- } else {
117
- setGradientColors([
118
- defaultColors.defaultGradientStart,
119
- defaultColors.defaultGradientEnd,
120
- ]);
121
-
122
- setDominantColor(defaultColor);
123
- }
124
- };
125
-
126
- fetchColors();
127
- }, []);
128
-
129
- const interpolatedBackgroundColor = backgroundColorAnim.interpolate({
130
- inputRange: [0, 1],
131
- outputRange: ["black", dominantColor],
132
- });
133
-
134
- return (
135
- <Animated.View
136
- style={[
137
- styles.container,
138
- { backgroundColor: interpolatedBackgroundColor },
139
- ]}
140
- >
141
- <View style={styles.fullScreenOverlay} />
142
-
143
- <Animated.View
144
- style={[styles.gradientOverlay, { opacity: gradientOpacityAnim }]}
145
- >
146
- <LinearGradient
147
- colors={gradientColors}
148
- style={StyleSheet.absoluteFill}
149
- />
150
- </Animated.View>
151
-
152
- {children}
153
- </Animated.View>
154
- );
155
- };
@@ -1,25 +0,0 @@
1
- import RNColorThief from "@applicaster/react-native-color-thief";
2
- import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
3
-
4
- const { log_error } = createLogger({
5
- subsystem: "UIComponent",
6
- category: "MagicBackground",
7
- });
8
-
9
- export const getDominantColor = async (imageUrl: string): Promise<any> => {
10
- try {
11
- return await RNColorThief.getColor(imageUrl, 1, false);
12
- } catch (error) {
13
- log_error("Failed to get the dominant color", { error });
14
- throw error;
15
- }
16
- };
17
-
18
- export const getPalettes = async (imageUrl: string): Promise<any> => {
19
- try {
20
- return await RNColorThief.getPalette(imageUrl, 3, 1, true);
21
- } catch (error) {
22
- log_error("Failed to get the palette", { error });
23
- throw error;
24
- }
25
- };