Add placeholders for the homepage

This commit is contained in:
Zoe Roux 2023-11-08 17:08:16 +01:00
parent 3be409ec70
commit a72691a81f
8 changed files with 53 additions and 23 deletions

View File

@ -66,6 +66,7 @@ export const EpisodeBox = ({
href={href} href={href}
{...css( {...css(
{ {
alignItems: "center",
child: { child: {
poster: { poster: {
borderColor: (theme) => theme.background, borderColor: (theme) => theme.background,
@ -90,17 +91,18 @@ export const EpisodeBox = ({
src={thumbnail} src={thumbnail}
quality="low" quality="low"
alt="" alt=""
forcedLoading={isLoading}
layout={{ width: percent(100), aspectRatio: 16 / 9 }} layout={{ width: percent(100), aspectRatio: 16 / 9 }}
{...(css("poster") as any)} {...(css("poster") as any)}
/> />
<Skeleton> <Skeleton {...css({ width: percent(50) })}>
{isLoading || ( {isLoading || (
<P {...css([{ marginY: 0, textAlign: "center" }, "title"])}> <P {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
{name ?? t("show.episodeNoMetadata")} {name ?? t("show.episodeNoMetadata")}
</P> </P>
)} )}
</Skeleton> </Skeleton>
<Skeleton> <Skeleton {...css({ width: percent(75), height: rem(0.8) })}>
{isLoading || ( {isLoading || (
<SubP <SubP
numberOfLines={3} numberOfLines={3}

View File

@ -56,7 +56,7 @@ export const InfiniteFetchList = <Data, Props, _>({
}): JSX.Element | null => { }): JSX.Element | null => {
const { numColumns, size } = useBreakpointMap(layout); const { numColumns, size } = useBreakpointMap(layout);
const oldItems = useRef<Data[] | undefined>(); const oldItems = useRef<Data[] | undefined>();
let { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } = query; let { items, error, fetchNextPage, hasNextPage, isFetching, refetch, isRefetching } = query;
if (incremental && items) oldItems.current = items; if (incremental && items) oldItems.current = items;
if (error) return <ErrorView error={error} />; if (error) return <ErrorView error={error} />;
@ -76,7 +76,7 @@ export const InfiniteFetchList = <Data, Props, _>({
return ( return (
<FlashList <FlashList
renderItem={({ item, index }) => children({ isLoading: false, ...item } as any, index)} renderItem={({ item, index }) => children({ isLoading: false, ...item } as any, index)}
data={hasNextPage !== false ? [...(items || []), ...placeholders] : items} data={hasNextPage || isFetching ? [...(items || []), ...placeholders] : items}
horizontal={layout.layout === "horizontal"} horizontal={layout.layout === "horizontal"}
keyExtractor={(item: any) => item.id?.toString()} keyExtractor={(item: any) => item.id?.toString()}
numColumns={numColumns} numColumns={numColumns}

View File

@ -114,7 +114,7 @@ const InfiniteScroll = <Props,>({
)} )}
> >
{children} {children}
{hasMore && isFetching && loader} {((hasMore && fetchMore) || isFetching) && loader}
</div> </div>
); );
@ -178,7 +178,7 @@ export const InfiniteFetchList = <Data, _, HeaderProps>({
loadMore={fetchNextPage} loadMore={fetchNextPage}
hasMore={hasNextPage!} hasMore={hasNextPage!}
isFetching={isFetching} isFetching={isFetching}
loader={[...Array(12)].map((_, i) => ( loader={[...Array(placeholderCount)].map((_, i) => (
<Fragment key={i.toString()}> <Fragment key={i.toString()}>
{Divider && i !== 0 && (Divider === true ? <HR /> : <Divider />)} {Divider && i !== 0 && (Divider === true ? <HR /> : <Divider />)}
{children({ isLoading: true } as any, i)} {children({ isLoading: true } as any, i)}

View File

@ -76,6 +76,7 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
<InfiniteFetchList <InfiniteFetchList
query={query} query={query}
layout={{ ...ItemGrid.layout, layout: "horizontal" }} layout={{ ...ItemGrid.layout, layout: "horizontal" }}
placeholderCount={10}
empty={displayEmpty.current ? t("home.none") : undefined} empty={displayEmpty.current ? t("home.none") : undefined}
> >
{(x, i) => { {(x, i) => {

View File

@ -27,11 +27,12 @@ import {
ImageBackground, ImageBackground,
Link, Link,
P, P,
Skeleton,
tooltip, tooltip,
ts, ts,
} from "@kyoo/primitives"; } from "@kyoo/primitives";
import { View } from "react-native"; import { View } from "react-native";
import { percent, useYoshiki } from "yoshiki/native"; import { percent, rem, useYoshiki } from "yoshiki/native";
import { WithLoading } from "../fetch"; import { WithLoading } from "../fetch";
import { Header as DetailsHeader } from "../details/header"; import { Header as DetailsHeader } from "../details/header";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -68,8 +69,10 @@ export const Header = ({
<View <View
{...css({ width: { md: percent(70) }, position: "absolute", bottom: 0, margin: ts(2) })} {...css({ width: { md: percent(70) }, position: "absolute", bottom: 0, margin: ts(2) })}
> >
<H1>{name}</H1> <Skeleton {...css({ width: rem(8), height: rem(2.5) })}>
<View {...css({ flexDirection: "row" })}> {isLoading || <H1>{name}</H1>}
</Skeleton>
<View {...css({ flexDirection: "row", alignItems: "center" })}>
{link !== null && ( {link !== null && (
<IconFab <IconFab
icon={PlayArrow} icon={PlayArrow}
@ -87,9 +90,13 @@ export const Header = ({
{...tooltip(t("home.info"))} {...tooltip(t("home.info"))}
{...css({ marginRight: ts(2) })} {...css({ marginRight: ts(2) })}
/> />
<H2>{tagline}</H2> <Skeleton {...css({ width: rem(25), height: rem(2) })}>
{isLoading || <H2>{tagline}</H2>}
</Skeleton>
</View> </View>
<P {...css({ display: { xs: "none", md: "flex" } })}>{overview}</P> <Skeleton lines={4} {...css({ marginTop: ts(1) })}>
{isLoading || <P {...css({ display: { xs: "none", md: "flex" } })}>{overview}</P>}
</Skeleton>
</View> </View>
</ImageBackground> </ImageBackground>
); );

View File

@ -27,11 +27,11 @@ import { GenreGrid } from "./genre";
import { Recommanded } from "./recommanded"; import { Recommanded } from "./recommanded";
import { VerticalRecommanded } from "./vertical"; import { VerticalRecommanded } from "./vertical";
import { NewsList } from "./news"; import { NewsList } from "./news";
import { useLayoutEffect, useState } from "react"; import { useEffect, useState } from "react";
export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => { export const HomePage: QueryPage<{}, Genre> = ({ randomItems }) => {
const [isClient, setClient] = useState(false); const [isClient, setClient] = useState(false);
useLayoutEffect(() => setClient(true), []); useEffect(() => setClient(true), []);
return ( return (
<ScrollView> <ScrollView>

View File

@ -34,6 +34,7 @@ import {
ImageBackground, ImageBackground,
Link, Link,
P, P,
Skeleton,
SubP, SubP,
focusReset, focusReset,
tooltip, tooltip,
@ -42,7 +43,7 @@ import {
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Pressable, ScrollView, View } from "react-native"; import { Pressable, ScrollView, View } from "react-native";
import { useRouter } from "solito/router"; import { useRouter } from "solito/router";
import { Theme, percent, px, useYoshiki } from "yoshiki/native"; import { Theme, percent, px, rem, useYoshiki } from "yoshiki/native";
import { Layout, WithLoading } from "../fetch"; import { Layout, WithLoading } from "../fetch";
import { InfiniteFetch } from "../fetch-infinite"; import { InfiniteFetch } from "../fetch-infinite";
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg"; import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
@ -104,6 +105,7 @@ export const ItemDetails = ({
alt="" alt=""
quality="low" quality="low"
gradient={false} gradient={false}
forcedLoading={isLoading}
{...css({ height: percent(100), aspectRatio: 2 / 3 })} {...css({ height: percent(100), aspectRatio: 2 / 3 })}
> >
<View <View
@ -116,17 +118,29 @@ export const ItemDetails = ({
p: ts(1), p: ts(1),
})} })}
> >
<P {...css([{ m: 0 }, "title"])}>{name}</P> <Skeleton {...css({ width: percent(100) })}>
{subtitle && <SubP {...css({ m: 0 })}>{subtitle}</SubP>} {isLoading || <P {...css([{ m: 0 }, "title"])}>{name}</P>}
</Skeleton>
{(subtitle || isLoading) && (
<Skeleton {...css({ height: rem(0.8) })}>
{isLoading || <SubP {...css({ m: 0 })}>{subtitle}</SubP>}
</Skeleton>
)}
</View> </View>
</ImageBackground> </ImageBackground>
<View {...css({ flexShrink: 1, flexGrow: 1 })}> <View {...css({ flexShrink: 1, flexGrow: 1, justifyContent: "flex-end" })}>
{tagline && <P {...css({ p: ts(1) })}>{tagline}</P>} {(isLoading || tagline) && (
{overview && ( <Skeleton {...css({ m: ts(1), marginVertical: ts(2) })}>
<ScrollView> {isLoading || <P {...css({ p: ts(1) })}>{tagline}</P>}
<SubP {...css({ pX: ts(1) })}>{overview}</SubP> </Skeleton>
</ScrollView>
)} )}
<ScrollView {...css({ pX: ts(1) })}>
<Skeleton lines={5} {...css({ height: rem(0.8) })}>
{isLoading || (
<SubP {...css({ textAlign: "justify" })}>{overview ?? t("show.noOverview")}</SubP>
)}
</Skeleton>
</ScrollView>
<View <View
{...css({ {...css({
bg: (theme) => theme.themeOverlay, bg: (theme) => theme.themeOverlay,
@ -136,7 +150,11 @@ export const ItemDetails = ({
})} })}
> >
<ScrollView horizontal {...css({ alignItems: "center" })}> <ScrollView horizontal {...css({ alignItems: "center" })}>
{genres?.map((x) => <Chip key={x} size="small" label={x} {...css({ mX: ts(0.5) })} />)} {(genres || [...Array(3)])?.map((x, i) => (
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}>
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
</Chip>
))}
</ScrollView> </ScrollView>
{playHref !== null && ( {playHref !== null && (
<IconFab <IconFab
@ -171,6 +189,7 @@ export const Recommanded = () => {
<InfiniteFetch <InfiniteFetch
query={Recommanded.query()} query={Recommanded.query()}
layout={ItemDetails.layout} layout={ItemDetails.layout}
placeholderCount={6}
fetchMore={false} fetchMore={false}
{...css({ padding: 0 })} {...css({ padding: 0 })}
> >

View File

@ -36,6 +36,7 @@ export const VerticalRecommanded = () => {
<H3 {...css({ mX: ItemGrid.layout.gap })}>{t("home.recommanded")}</H3> <H3 {...css({ mX: ItemGrid.layout.gap })}>{t("home.recommanded")}</H3>
<InfiniteFetch <InfiniteFetch
query={VerticalRecommanded.query()} query={VerticalRecommanded.query()}
placeholderCount={3}
layout={{ ...ItemList.layout, layout: "vertical" }} layout={{ ...ItemList.layout, layout: "vertical" }}
fetchMore={false} fetchMore={false}
> >