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

View File

@ -56,7 +56,7 @@ export const InfiniteFetchList = <Data, Props, _>({
}): JSX.Element | null => {
const { numColumns, size } = useBreakpointMap(layout);
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 (error) return <ErrorView error={error} />;
@ -76,7 +76,7 @@ export const InfiniteFetchList = <Data, Props, _>({
return (
<FlashList
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"}
keyExtractor={(item: any) => item.id?.toString()}
numColumns={numColumns}

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,7 @@ import {
ImageBackground,
Link,
P,
Skeleton,
SubP,
focusReset,
tooltip,
@ -42,7 +43,7 @@ import {
import { useTranslation } from "react-i18next";
import { Pressable, ScrollView, View } from "react-native";
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 { InfiniteFetch } from "../fetch-infinite";
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
@ -104,6 +105,7 @@ export const ItemDetails = ({
alt=""
quality="low"
gradient={false}
forcedLoading={isLoading}
{...css({ height: percent(100), aspectRatio: 2 / 3 })}
>
<View
@ -116,17 +118,29 @@ export const ItemDetails = ({
p: ts(1),
})}
>
<P {...css([{ m: 0 }, "title"])}>{name}</P>
{subtitle && <SubP {...css({ m: 0 })}>{subtitle}</SubP>}
<Skeleton {...css({ width: percent(100) })}>
{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>
</ImageBackground>
<View {...css({ flexShrink: 1, flexGrow: 1 })}>
{tagline && <P {...css({ p: ts(1) })}>{tagline}</P>}
{overview && (
<ScrollView>
<SubP {...css({ pX: ts(1) })}>{overview}</SubP>
</ScrollView>
<View {...css({ flexShrink: 1, flexGrow: 1, justifyContent: "flex-end" })}>
{(isLoading || tagline) && (
<Skeleton {...css({ m: ts(1), marginVertical: ts(2) })}>
{isLoading || <P {...css({ p: ts(1) })}>{tagline}</P>}
</Skeleton>
)}
<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
{...css({
bg: (theme) => theme.themeOverlay,
@ -136,7 +150,11 @@ export const ItemDetails = ({
})}
>
<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>
{playHref !== null && (
<IconFab
@ -171,6 +189,7 @@ export const Recommanded = () => {
<InfiniteFetch
query={Recommanded.query()}
layout={ItemDetails.layout}
placeholderCount={6}
fetchMore={false}
{...css({ padding: 0 })}
>

View File

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