Split image backround and gradient image background

This commit is contained in:
Zoe Roux 2024-05-24 11:53:32 +02:00
parent cc50444384
commit 95086777af
No known key found for this signature in database
8 changed files with 108 additions and 76 deletions

View File

@ -22,6 +22,7 @@ import { LinearGradient, type LinearGradientProps } from "expo-linear-gradient";
import type { ComponentProps, ComponentType, ReactElement, ReactNode } from "react"; import type { ComponentProps, ComponentType, ReactElement, ReactNode } from "react";
import { type ImageStyle, View, type ViewProps, type ViewStyle } from "react-native"; import { type ImageStyle, View, type ViewProps, type ViewStyle } from "react-native";
import { percent } from "yoshiki/native"; import { percent } from "yoshiki/native";
import { useYoshiki } from "yoshiki/native";
import { imageBorderRadius } from "../constants"; import { imageBorderRadius } from "../constants";
import { ContrastArea } from "../themes"; import { ContrastArea } from "../themes";
import type { ImageLayout, Props, YoshikiEnhanced } from "./base-image"; import type { ImageLayout, Props, YoshikiEnhanced } from "./base-image";
@ -53,51 +54,50 @@ export const PosterBackground = ({
...props ...props
}: Omit<ComponentProps<typeof ImageBackground>, "layout"> & { style?: ImageStyle } & { }: Omit<ComponentProps<typeof ImageBackground>, "layout"> & { style?: ImageStyle } & {
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>; layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
}) => ( }) => {
const { css } = useYoshiki();
return (
<ImageBackground <ImageBackground
alt={alt!} alt={alt!}
layout={{ aspectRatio: 2 / 3, ...layout }} layout={{ aspectRatio: 2 / 3, ...layout }}
hideLoad={false} {...css({ borderRadius: imageBorderRadius }, props)}
gradient={false}
{...props}
/> />
); );
};
type ImageBackgroundProps = {
children?: ReactNode;
containerStyle?: YoshikiEnhanced<ViewStyle>;
imageStyle?: YoshikiEnhanced<ImageStyle>;
layout?: ImageLayout;
contrast?: "light" | "dark" | "user";
};
export const ImageBackground = <AsProps = ViewProps>({ export const ImageBackground = <AsProps = ViewProps>({
src, src,
alt, alt,
quality, quality,
gradient = true,
as, as,
children, children,
containerStyle, containerStyle,
imageStyle, imageStyle,
forcedLoading,
hideLoad = true,
contrast = "dark",
layout, layout,
contrast = "dark",
imageSibling,
...asProps ...asProps
}: { }: {
as?: ComponentType<AsProps>; as?: ComponentType<AsProps>;
gradient?: Partial<LinearGradientProps> | boolean; imageSibling?: ReactElement;
children?: ReactNode;
containerStyle?: YoshikiEnhanced<ViewStyle>;
imageStyle?: YoshikiEnhanced<ImageStyle>;
hideLoad?: boolean;
contrast?: "light" | "dark" | "user";
layout?: ImageLayout;
} & AsProps & } & AsProps &
ImageBackgroundProps &
Props) => { Props) => {
const Container = as ?? View; const Container = as ?? View;
return ( return (
<ContrastArea contrastText mode={contrast}> <ContrastArea contrastText mode={contrast}>
{({ css, theme }) => ( {({ css }) => (
<Container <Container {...(css([layout, { overflow: "hidden" }], asProps) as AsProps)}>
{...(css(
[layout, { borderRadius: imageBorderRadius, overflow: "hidden" }],
asProps,
) as AsProps)}
>
<View <View
{...css([ {...css([
{ {
@ -112,24 +112,46 @@ export const ImageBackground = <AsProps = ViewProps>({
containerStyle, containerStyle,
])} ])}
> >
{(src || !hideLoad) && ( {src && (
<Image <Image
src={src} src={src}
quality={quality} quality={quality}
forcedLoading={forcedLoading}
alt={alt!} alt={alt!}
layout={{ width: percent(100), height: percent(100) }} layout={{ width: percent(100), height: percent(100) }}
Err={hideLoad ? null : undefined}
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as { {...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as {
style: ImageStyle; style: ImageStyle;
})} })}
/> />
)} )}
{gradient && ( {imageSibling}
</View>
{children}
</Container>
)}
</ContrastArea>
);
};
export const GradientImageBackground = <AsProps = ViewProps>({
contrast = "dark",
gradient,
...props
}: {
as?: ComponentType<AsProps>;
gradient?: Partial<LinearGradientProps>;
} & AsProps &
ImageBackgroundProps &
Props) => {
const { css, theme } = useYoshiki();
return (
<ImageBackground
contrast={contrast}
imageSibling={
<LinearGradient <LinearGradient
start={{ x: 0, y: 0.25 }} start={{ x: 0, y: 0.25 }}
end={{ x: 0, y: 1 }} end={{ x: 0, y: 1 }}
colors={["transparent", theme.darkOverlay]} colors={["transparent", theme[contrast].darkOverlay]}
{...css( {...css(
{ {
position: "absolute", position: "absolute",
@ -141,11 +163,8 @@ export const ImageBackground = <AsProps = ViewProps>({
typeof gradient === "object" ? gradient : undefined, typeof gradient === "object" ? gradient : undefined,
)} )}
/> />
)} }
</View> {...(props as any)}
{children} />
</Container>
)}
</ContrastArea>
); );
}; };

View File

@ -20,8 +20,8 @@
import type { KyooImage, WatchStatusV } from "@kyoo/models"; import type { KyooImage, WatchStatusV } from "@kyoo/models";
import { import {
GradientImageBackground,
Heading, Heading,
ImageBackground,
Link, Link,
P, P,
Poster, Poster,
@ -33,7 +33,6 @@ import {
} from "@kyoo/primitives"; } from "@kyoo/primitives";
import { useState } from "react"; import { useState } from "react";
import { Platform, View } from "react-native"; import { Platform, View } from "react-native";
import type { Stylable } from "yoshiki";
import { percent, px, rem, useYoshiki } from "yoshiki/native"; import { percent, px, rem, useYoshiki } from "yoshiki/native";
import { ItemContext } from "../components/context-menus"; import { ItemContext } from "../components/context-menus";
import type { Layout } from "../fetch"; import type { Layout } from "../fetch";
@ -65,19 +64,13 @@ export const ItemList = ({
const [moreOpened, setMoreOpened] = useState(false); const [moreOpened, setMoreOpened] = useState(false);
return ( return (
<ImageBackground <GradientImageBackground
src={thumbnail} src={thumbnail}
alt={name} alt={name}
quality="medium" quality="medium"
as={Link} as={Link}
href={moreOpened ? undefined : href} href={moreOpened ? undefined : href}
onLongPress={() => setMoreOpened(true)} onLongPress={() => setMoreOpened(true)}
containerStyle={{
borderRadius: px(imageBorderRadius),
}}
imageStyle={{
borderRadius: px(imageBorderRadius),
}}
{...css( {...css(
{ {
alignItems: "center", alignItems: "center",
@ -162,7 +155,7 @@ export const ItemList = ({
<PosterBackground src={poster} alt="" quality="low" layout={{ height: percent(80) }}> <PosterBackground src={poster} alt="" quality="low" layout={{ height: percent(80) }}>
<ItemWatchStatus watchStatus={watchStatus} unseenEpisodesCount={unseenEpisodesCount} /> <ItemWatchStatus watchStatus={watchStatus} unseenEpisodesCount={unseenEpisodesCount} />
</PosterBackground> </PosterBackground>
</ImageBackground> </GradientImageBackground>
); );
}; };

View File

@ -27,7 +27,15 @@ import {
type QueryPage, type QueryPage,
getDisplayDate, getDisplayDate,
} from "@kyoo/models"; } from "@kyoo/models";
import { Container, Head, ImageBackground, P, Skeleton, ts, usePageStyle } from "@kyoo/primitives"; import {
Container,
GradientImageBackground,
Head,
P,
Skeleton,
ts,
usePageStyle,
} from "@kyoo/primitives";
import { forwardRef } from "react"; import { forwardRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Platform, View, type ViewProps } from "react-native"; import { Platform, View, type ViewProps } from "react-native";
@ -50,7 +58,7 @@ const Header = ({ slug }: { slug: string }) => {
<> <>
<Head title={data?.name} description={data?.overview} image={data?.thumbnail?.high} /> <Head title={data?.name} description={data?.overview} image={data?.thumbnail?.high} />
<ImageBackground <GradientImageBackground
src={data?.thumbnail} src={data?.thumbnail}
quality="high" quality="high"
alt="" alt=""
@ -70,7 +78,7 @@ const Header = ({ slug }: { slug: string }) => {
studio={null} studio={null}
{...css(ShowHeader.childStyle)} {...css(ShowHeader.childStyle)}
/> />
</ImageBackground> </GradientImageBackground>
<Container <Container
{...css({ {...css({

View File

@ -25,7 +25,16 @@ import {
type QueryIdentifier, type QueryIdentifier,
useInfiniteFetch, useInfiniteFetch,
} from "@kyoo/models"; } from "@kyoo/models";
import { Container, H2, ImageBackground, Link, P, focusReset, ts } from "@kyoo/primitives"; import {
Container,
GradientImageBackground,
H2,
ImageBackground,
Link,
P,
focusReset,
ts,
} from "@kyoo/primitives";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { type Theme, useYoshiki } from "yoshiki/native"; import { type Theme, useYoshiki } from "yoshiki/native";
import { ErrorView } from "../errors"; import { ErrorView } from "../errors";
@ -59,11 +68,15 @@ export const PartOf = ({
}, },
})} })}
> >
<ImageBackground <GradientImageBackground
src={thumbnail} src={thumbnail}
alt="" alt=""
quality="medium" quality="medium"
gradient={{ colors: [theme.darkOverlay, theme.darkOverlay] }} gradient={{
colors: [theme.darkOverlay, "transparent"],
start: { x: 0, y: 0 },
end: { x: 1, y: 0 },
}}
{...css({ {...css({
padding: ts(3), padding: ts(3),
})} })}
@ -72,7 +85,7 @@ export const PartOf = ({
{t("show.partOf")} {name} {t("show.partOf")} {name}
</H2> </H2>
<P {...css({ textAlign: "justify" })}>{overview}</P> <P {...css({ textAlign: "justify" })}>{overview}</P>
</ImageBackground> </GradientImageBackground>
</Link> </Link>
); );
}; };

View File

@ -30,6 +30,7 @@ import {
Skeleton, Skeleton,
SubP, SubP,
focusReset, focusReset,
imageBorderRadius,
important, important,
tooltip, tooltip,
ts, ts,
@ -98,6 +99,7 @@ export const EpisodeBox = ({
borderColor: (theme) => theme.background, borderColor: (theme) => theme.background,
borderWidth: ts(0.5), borderWidth: ts(0.5),
borderStyle: "solid", borderStyle: "solid",
borderRadius: imageBorderRadius,
}, },
more: { more: {
opacity: 0, opacity: 0,
@ -123,9 +125,8 @@ export const EpisodeBox = ({
src={thumbnail} src={thumbnail}
quality="low" quality="low"
alt="" alt=""
gradient={false}
layout={{ width: percent(100), aspectRatio: 16 / 9 }} layout={{ width: percent(100), aspectRatio: 16 / 9 }}
{...(css("poster") as any)} {...css("poster")}
> >
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( {(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
<ItemProgress watchPercent={watchedPercent ?? 100} /> <ItemProgress watchPercent={watchedPercent ?? 100} />
@ -254,12 +255,11 @@ export const EpisodeLine = ({
src={thumbnail} src={thumbnail}
quality="low" quality="low"
alt="" alt=""
gradient={false}
layout={{ layout={{
width: percent(18), width: percent(18),
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
}} }}
{...(css({ flexShrink: 0, m: ts(1) }) as { style: ImageStyle })} {...css({ flexShrink: 0, m: ts(1) })}
> >
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( {(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
<> <>

View File

@ -35,13 +35,13 @@ import {
Chip, Chip,
Container, Container,
DottedSeparator, DottedSeparator,
GradientImageBackground,
H1, H1,
H2, H2,
HR, HR,
Head, Head,
IconButton, IconButton,
IconFab, IconFab,
ImageBackground,
LI, LI,
Link, Link,
Menu, Menu,
@ -509,7 +509,7 @@ export const Header = ({
{({ isLoading, ...data }) => ( {({ isLoading, ...data }) => (
<> <>
<Head title={data?.name} description={data?.overview} image={data?.thumbnail?.high} /> <Head title={data?.name} description={data?.overview} image={data?.thumbnail?.high} />
<ImageBackground <GradientImageBackground
src={data?.thumbnail} src={data?.thumbnail}
quality="high" quality="high"
alt="" alt=""
@ -531,7 +531,7 @@ export const Header = ({
watchStatus={data?.watchStatus?.status ?? null} watchStatus={data?.watchStatus?.status ?? null}
{...css(Header.childStyle)} {...css(Header.childStyle)}
/> />
</ImageBackground> </GradientImageBackground>
<Description <Description
isLoading={isLoading} isLoading={isLoading}
overview={data?.overview} overview={data?.overview}

View File

@ -86,13 +86,11 @@ const DownloadedItem = ({
src={image} src={image}
quality="low" quality="low"
alt="" alt=""
gradient={false}
hideLoad={false}
layout={{ layout={{
width: percent(25), width: percent(25),
aspectRatio: kind === "episode" ? 16 / 9 : 2 / 3, aspectRatio: kind === "episode" ? 16 / 9 : 2 / 3,
}} }}
{...(css({ flexShrink: 0, m: ts(1) }) as { style: ImageStyle })} {...css({ flexShrink: 0, m: ts(1) })}
> >
{/* {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( */} {/* {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( */}
{/* <> */} {/* <> */}

View File

@ -20,6 +20,7 @@
import { type KyooImage, type LibraryItem, LibraryItemP, type QueryIdentifier } from "@kyoo/models"; import { type KyooImage, type LibraryItem, LibraryItemP, type QueryIdentifier } from "@kyoo/models";
import { import {
GradientImageBackground,
H1, H1,
H2, H2,
IconButton, IconButton,
@ -60,7 +61,7 @@ export const Header = ({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<ImageBackground <GradientImageBackground
src={thumbnail} src={thumbnail}
alt="" alt=""
quality="high" quality="high"
@ -108,7 +109,7 @@ export const Header = ({
)} )}
</Skeleton> </Skeleton>
</View> </View>
</ImageBackground> </GradientImageBackground>
); );
}; };