Add loader for movie details page

This commit is contained in:
Zoe Roux 2025-07-13 23:53:31 +02:00
parent 512b378702
commit 69838493bc
No known key found for this signature in database
3 changed files with 290 additions and 75 deletions

View File

@ -9,7 +9,7 @@ import { useSetError } from "./error-provider";
import { useStoreValue } from "./settings";
export const AccountProvider = ({ children }: { children: ReactNode }) => {
const [setError, clearError] = useSetError("account");
const [setError, clearError] = useSetError("connection");
const accounts = useStoreValue("accounts", z.array(Account)) ?? [];
const ret = useMemo(() => {

View File

@ -1,9 +1,11 @@
import Refresh from "@material-symbols/svg-400/rounded/autorenew.svg";
import BookmarkAdd from "@material-symbols/svg-400/rounded/bookmark_add.svg";
import Download from "@material-symbols/svg-400/rounded/download.svg";
import MoreHoriz from "@material-symbols/svg-400/rounded/more_horiz.svg";
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
import Theaters from "@material-symbols/svg-400/rounded/theaters-fill.svg";
import { LinearGradient } from "expo-linear-gradient";
import { Stack } from "expo-router";
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
@ -379,6 +381,117 @@ export const TitleLine = ({
);
};
TitleLine.Loader = ({
kind,
...props
}: {
kind: "serie" | "movie" | "collection";
}) => {
const { css, theme } = useYoshiki();
return (
<Container
{...css(
{
flexDirection: { xs: "column", md: "row" },
},
props,
)}
>
<View
{...css({
flexDirection: { xs: "column", sm: "row" },
alignItems: { xs: "center", sm: "flex-start" },
flexGrow: 1,
maxWidth: percent(100),
})}
>
<Poster.Loader
layout={{
width: { xs: percent(50), md: percent(25) },
}}
{...(css({
maxWidth: {
xs: px(175),
sm: Platform.OS === "web" ? ("unset" as any) : 99999999,
},
flexShrink: 0,
}) as { style: ImageStyle })}
/>
<View
{...css({
alignSelf: { xs: "center", sm: "flex-end", md: "center" },
alignItems: { xs: "center", sm: "flex-start" },
paddingLeft: { sm: em(2.5) },
flexShrink: 1,
flexGrow: 1,
})}
>
<Skeleton
variant="header"
{...css({ width: rem(15), height: rem(2.5), marginBottom: rem(1) })}
/>
<Skeleton
{...css({
width: rem(5),
height: rem(1.5),
marginBottom: rem(0.5),
})}
/>
<View
{...css({
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap",
justifyContent: "center",
})}
>
<IconFab
icon={PlayArrow}
color={{ xs: theme.user.colors.black, md: theme.colors.black }}
{...css({
bg: theme.user.accent,
fover: { self: { bg: theme.user.accent } },
})}
/>
<IconButton
icon={Theaters}
color={{ xs: theme.user.contrast, md: theme.colors.white }}
/>
{kind !== "collection" && (
<IconButton
icon={BookmarkAdd}
color={{ xs: theme.user.contrast, md: theme.colors.white }}
/>
)}
{kind === "movie" && <IconButton icon={MoreHoriz} />}
<DottedSeparator
{...css({
color: {
xs: theme.user.contrast,
md: theme.colors.white,
},
})}
/>
<Rating.Loader
color={{ xs: theme.user.contrast, md: theme.colors.white }}
/>
<DottedSeparator
{...css({
color: {
xs: theme.user.contrast,
md: theme.colors.white,
},
})}
/>
<Skeleton {...css({ width: rem(3) })} />
</View>
</View>
</View>
</Container>
);
};
const Description = ({
description,
tags,
@ -436,7 +549,7 @@ const Description = ({
marginTop: ts(0.5),
})}
>
<P {...css({ marginRight: ts(0.5) })}>{t("show.tags")}:</P>
<P {...(css({ marginRight: ts(0.5) }) as any)}>{t("show.tags")}:</P>
{tags.map((tag) => (
<Chip
key={tag}
@ -477,6 +590,79 @@ const Description = ({
);
};
Description.Loader = ({ ...props }: object) => {
const { t } = useTranslation();
const { css } = useYoshiki();
return (
<Container
{...css(
{ paddingBottom: ts(1), flexDirection: { xs: "column", sm: "row" } },
props,
)}
>
<P
{...css({
display: { xs: "flex", sm: "none" },
flexWrap: "wrap",
color: (theme: Theme) => theme.user.paragraph,
})}
>
{t("show.genre")}:{" "}
{[...Array<Genre>(3)].map((_, i) => (
<Fragment key={i.toString()}>
<P {...(css({ m: 0 }) as any)}>{i !== 0 && ", "}</P>
<Skeleton {...css({ width: rem(5) })} />
</Fragment>
))}
</P>
<View
{...css({
flexDirection: "column",
flexGrow: 1,
flexBasis: { sm: 0 },
paddingTop: ts(4),
})}
>
<Skeleton lines={4} />
<View
{...css({
flexWrap: "wrap",
flexDirection: "row",
alignItems: "center",
marginTop: ts(0.5),
})}
>
<P {...(css({ marginRight: ts(0.5) }) as any)}>{t("show.tags")}:</P>
{[...Array<string>(3)].map((_, i) => (
<Chip.Loader key={i} size="small" {...css({ m: ts(0.5) })} />
))}
</View>
</View>
<HR
orientation="vertical"
{...css({ marginX: ts(2), display: { xs: "none", sm: "flex" } })}
/>
<View
{...css({
flexBasis: percent(25),
display: { xs: "none", sm: "flex" },
})}
>
<H2>{t("show.genre")}</H2>
<UL>
{[...Array<Genre>(3)].map((_, i) => (
<LI key={i}>
<Skeleton {...css({ marginBottom: 0 })} />
</LI>
))}
</UL>
</View>
</Container>
);
};
export const Header = ({
kind,
slug,
@ -484,7 +670,7 @@ export const Header = ({
kind: "movie" | "serie";
slug: string;
}) => {
const { css } = useYoshiki();
const { css, theme } = useYoshiki();
const { t } = useTranslation();
return (
@ -497,9 +683,8 @@ export const Header = ({
/>
<Fetch
query={Header.query(kind, slug)}
Loader={() => <p>loading</p>}
Render={(data) => (
<>
<View {...css({ flex: 1 })}>
<Head
title={data.name}
description={data.description}
@ -519,6 +704,10 @@ export const Header = ({
},
}}
{...(css({
position: "absolute",
top: 0,
left: 0,
right: 0,
minHeight: {
xs: px(350),
sm: px(300),
@ -526,7 +715,7 @@ export const Header = ({
lg: px(600),
},
}) as any)}
>
/>
<TitleLine
kind={kind}
slug={slug}
@ -582,10 +771,49 @@ export const Header = ({
/>
))}
</Container>
</GradientImageBackground>
{/* {type === "show" && ( */}
{/* <ShowWatchStatusCard {...(data?.watchStatus as any)} /> */}
{/* )} */}
</View>
)}
Loader={() => (
<>
<LinearGradient
start={{ x: 0, y: 0.25 }}
end={{ x: 0, y: 1 }}
colors={["transparent", theme.darkOverlay]}
{...(css({
width: percent(100),
height: {
xs: vh(40),
sm: min(vh(60), px(750)),
md: min(vh(60), px(680)),
lg: vh(65),
},
minHeight: {
xs: px(350),
sm: px(300),
md: px(400),
lg: px(600),
},
position: "absolute",
top: 0,
left: 0,
right: 0,
}) as any)}
/>
<TitleLine.Loader
kind={kind}
{...css({
marginTop: {
xs: max(vh(20), px(200)),
sm: vh(45),
md: max(vh(30), px(150)),
lg: max(vh(35), px(200)),
},
})}
/>
<Description.Loader />
</>
)}
/>

View File

@ -10,21 +10,8 @@ export const MovieDetails = () => {
const { css } = useYoshiki();
return (
<ScrollView
//{...css(
// Platform.OS === "web" && {
// // @ts-ignore Web only property
// overflow: "auto" as any,
// // @ts-ignore Web only property
// overflowX: "hidden",
// // @ts-ignore Web only property
// overflowY: "overlay",
// },
//)}
>
<ScrollView>
<Header kind="movie" slug={slug} />
{/* <DetailsCollections type="movie" slug={slug} /> */}
{/* <Staff slug={slug} /> */}
</ScrollView>
);
};