Finish a basic recommanded card

This commit is contained in:
Zoe Roux 2023-10-18 22:56:07 +02:00
parent 941137ff51
commit e4562648ba
5 changed files with 93 additions and 38 deletions

View File

@ -162,12 +162,14 @@ export type QueryIdentifier<T = unknown, Ret = T> = {
getNext?: (item: unknown) => string | undefined;
};
export type QueryPage<Props = {}, Items = unknown> = ComponentType<Props & { randomItems: Items[]}> & {
export type QueryPage<Props = {}, Items = unknown> = ComponentType<
Props & { randomItems: Items[] }
> & {
getFetchUrls?: (route: { [key: string]: string }) => QueryIdentifier<any>[];
getLayout?:
| QueryPage<{ page: ReactElement }>
| { Layout: QueryPage<{ page: ReactElement }>; props: object };
randomItems?: Items[]
randomItems?: Items[];
};
const toQueryKey = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => {

View File

@ -26,6 +26,7 @@ export const catppuccin: ThemeBuilder = {
// Catppuccin latte
overlay0: "#9ca0b0",
overlay1: "#7c7f93",
lightOverlay: "#eff1f599",
darkOverlay: "#4c4f6999",
link: "#1e66f5",
default: {
@ -57,6 +58,7 @@ export const catppuccin: ThemeBuilder = {
// Catppuccin mocha
overlay0: "#6c7086",
overlay1: "#9399b2",
lightOverlay: "#f5f0f899",
darkOverlay: "#11111b99",
link: "#89b4fa",
default: {

View File

@ -38,7 +38,9 @@ type Mode = {
mode: "light" | "dark" | "auto";
overlay0: Property.Color;
overlay1: Property.Color;
lightOverlay: Property.Color;
darkOverlay: Property.Color;
themeOverlay: Property.Color;
link: Property.Color;
contrast: Property.Color;
variant: Variant;
@ -73,8 +75,8 @@ declare module "yoshiki" {
export type { Theme } from "yoshiki";
export type ThemeBuilder = {
light: Omit<Mode, "contrast" | "mode"> & { default: Variant };
dark: Omit<Mode, "contrast" | "mode"> & { default: Variant };
light: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
dark: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
};
const selectMode = (
@ -86,12 +88,14 @@ const selectMode = (
...lightBuilder,
...lightBuilder.default,
contrast: lightBuilder.colors.black,
themeOverlay: lightBuilder.lightOverlay,
mode: "light",
};
const dark: Mode & Variant = {
...darkBuilder,
...darkBuilder.default,
contrast: darkBuilder.colors.white,
themeOverlay: darkBuilder.darkOverlay,
mode: "dark",
};
if (Platform.OS !== "web" || mode !== "auto") {

View File

@ -36,6 +36,7 @@ import { Fetch } from "../fetch";
import { ItemGrid } from "../browse/grid";
import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg";
import ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
import { InfiniteFetch } from "../fetch-infinite";
export const GenreGrid = ({ genre }: { genre: Genre }) => {
const ref = useRef<ScrollView>(null);
@ -43,7 +44,7 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
return (
<View>
<View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}>
<View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}>
<H3>{genre}</H3>
<View {...css({ flexDirection: "row" })}>
<IconButton
@ -56,28 +57,30 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
/>
</View>
</View>
<ScrollView ref={ref} horizontal>
<Fetch query={GenreGrid.query(genre)}>
{(x, i) => (
<ItemGrid
key={x.id ?? i}
isLoading={x.isLoading as any}
href={x.href}
name={x.name}
subtitle={
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
}
poster={x.poster}
/>
)}
</Fetch>
</ScrollView>
<InfiniteFetch
query={GenreGrid.query(genre)}
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
>
{(x, i) => (
<ItemGrid
key={x.id ?? i}
isLoading={x.isLoading as any}
href={x.href}
name={x.name}
subtitle={
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
}
poster={x.poster}
/>
)}
</InfiniteFetch>
</View>
);
};
GenreGrid.query = (genre: Genre): QueryIdentifier<Page<LibraryItem>> => ({
parser: Paged(LibraryItemP) as any,
GenreGrid.query = (genre: Genre): QueryIdentifier<LibraryItem> => ({
parser: LibraryItemP,
infinite: true,
path: ["items"],
params: {
genres: genre,

View File

@ -19,6 +19,7 @@
*/
import {
Genre,
ItemKind,
KyooImage,
LibraryItem,
@ -28,11 +29,12 @@ import {
QueryIdentifier,
getDisplayDate,
} from "@kyoo/models";
import { Container, H3, ImageBackground, P, Poster, SubP, ts } from "@kyoo/primitives";
import { Chip, Container, H3, ImageBackground, P, Poster, SubP, alpha, ts } from "@kyoo/primitives";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { percent, useYoshiki } from "yoshiki/native";
import { Fetch, WithLoading } from "../fetch";
import { ScrollView, View } from "react-native";
import { percent, px, useYoshiki } from "yoshiki/native";
import { Fetch, Layout, WithLoading } from "../fetch";
import { InfiniteFetch } from "../fetch-infinite";
export const ItemDetails = ({
isLoading,
@ -41,35 +43,76 @@ export const ItemDetails = ({
subtitle,
overview,
poster,
genres,
...props
}: WithLoading<{
name: string;
tagline: string | null;
subtitle: string;
poster: KyooImage | null;
genres: Genre[] | null;
overview: string | null;
}>) => {
const { css } = useYoshiki();
return (
<View {...css({ flexDirection: "row", bg: (theme) => theme.variant.background }, props)}>
<View
{...css(
{
height: ItemDetails.layout.size,
flexDirection: "row",
bg: (theme) => theme.variant.background,
borderRadius: 6,
overflow: "hidden",
},
props,
)}
>
<ImageBackground
src={poster}
alt=""
quality="low"
gradient={false}
{...css({ height: percent(100), aspectRatio: 2 / 3 })}
>
<P>{name}</P>
{subtitle && <SubP>{subtitle}</SubP>}
<View
{...css({
bg: (theme) => theme.darkOverlay,
position: "absolute",
left: 0,
right: 0,
bottom: 0,
p: ts(1),
})}
>
<P {...css({ m: 0 })}>{name}</P>
{subtitle && <SubP {...css({ m: 0 })}>{subtitle}</SubP>}
</View>
</ImageBackground>
<View>
{tagline && <P>{tagline}</P>}
{overview && <SubP>{overview}</SubP>}
<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({ bg: (theme) => theme.themeOverlay, flexDirection: "row" })}>
{genres?.map((x) => (
<Chip key={x} label={x} {...css({ mX: ts(.5) })} />
))}
</View>
</View>
</View>
);
};
ItemDetails.layout = {
size: ts(36),
numColumns: { xs: 1, md: 2, xl: 3 },
layout: "grid",
gap: ts(8),
} satisfies Layout;
export const Recommanded = () => {
const { t } = useTranslation();
const { css } = useYoshiki();
@ -77,7 +120,7 @@ export const Recommanded = () => {
return (
<View {...css({ marginX: ts(1) })}>
<H3>{t("home.recommanded")}</H3>
<Fetch query={Recommanded.query()}>
<InfiniteFetch query={Recommanded.query()} layout={ItemDetails.layout}>
{(x, i) => (
<ItemDetails
key={x.id ?? i}
@ -89,16 +132,17 @@ export const Recommanded = () => {
subtitle={
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
}
{...css({ height: { xs: ts(15), md: ts(20) } })}
genres={"genres" in x ? x.genres : null}
/>
)}
</Fetch>
</InfiniteFetch>
</View>
);
};
Recommanded.query = (): QueryIdentifier<Page<LibraryItem>> => ({
parser: Paged(LibraryItemP) as any,
Recommanded.query = (): QueryIdentifier<LibraryItem> => ({
parser: LibraryItemP,
infinite: true,
path: ["items"],
params: {
sortBy: "random",