mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 13:44:33 -04:00
Finish a basic recommanded card
This commit is contained in:
parent
941137ff51
commit
e4562648ba
@ -162,12 +162,14 @@ export type QueryIdentifier<T = unknown, Ret = T> = {
|
|||||||
getNext?: (item: unknown) => string | undefined;
|
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>[];
|
getFetchUrls?: (route: { [key: string]: string }) => QueryIdentifier<any>[];
|
||||||
getLayout?:
|
getLayout?:
|
||||||
| QueryPage<{ page: ReactElement }>
|
| QueryPage<{ page: ReactElement }>
|
||||||
| { Layout: QueryPage<{ page: ReactElement }>; props: object };
|
| { Layout: QueryPage<{ page: ReactElement }>; props: object };
|
||||||
randomItems?: Items[]
|
randomItems?: Items[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const toQueryKey = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => {
|
const toQueryKey = <Data, Ret>(query: QueryIdentifier<Data, Ret>) => {
|
||||||
|
@ -26,6 +26,7 @@ export const catppuccin: ThemeBuilder = {
|
|||||||
// Catppuccin latte
|
// Catppuccin latte
|
||||||
overlay0: "#9ca0b0",
|
overlay0: "#9ca0b0",
|
||||||
overlay1: "#7c7f93",
|
overlay1: "#7c7f93",
|
||||||
|
lightOverlay: "#eff1f599",
|
||||||
darkOverlay: "#4c4f6999",
|
darkOverlay: "#4c4f6999",
|
||||||
link: "#1e66f5",
|
link: "#1e66f5",
|
||||||
default: {
|
default: {
|
||||||
@ -57,6 +58,7 @@ export const catppuccin: ThemeBuilder = {
|
|||||||
// Catppuccin mocha
|
// Catppuccin mocha
|
||||||
overlay0: "#6c7086",
|
overlay0: "#6c7086",
|
||||||
overlay1: "#9399b2",
|
overlay1: "#9399b2",
|
||||||
|
lightOverlay: "#f5f0f899",
|
||||||
darkOverlay: "#11111b99",
|
darkOverlay: "#11111b99",
|
||||||
link: "#89b4fa",
|
link: "#89b4fa",
|
||||||
default: {
|
default: {
|
||||||
|
@ -38,7 +38,9 @@ type Mode = {
|
|||||||
mode: "light" | "dark" | "auto";
|
mode: "light" | "dark" | "auto";
|
||||||
overlay0: Property.Color;
|
overlay0: Property.Color;
|
||||||
overlay1: Property.Color;
|
overlay1: Property.Color;
|
||||||
|
lightOverlay: Property.Color;
|
||||||
darkOverlay: Property.Color;
|
darkOverlay: Property.Color;
|
||||||
|
themeOverlay: Property.Color;
|
||||||
link: Property.Color;
|
link: Property.Color;
|
||||||
contrast: Property.Color;
|
contrast: Property.Color;
|
||||||
variant: Variant;
|
variant: Variant;
|
||||||
@ -73,8 +75,8 @@ declare module "yoshiki" {
|
|||||||
|
|
||||||
export type { Theme } from "yoshiki";
|
export type { Theme } from "yoshiki";
|
||||||
export type ThemeBuilder = {
|
export type ThemeBuilder = {
|
||||||
light: Omit<Mode, "contrast" | "mode"> & { default: Variant };
|
light: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
|
||||||
dark: Omit<Mode, "contrast" | "mode"> & { default: Variant };
|
dark: Omit<Mode, "contrast" | "mode" | "themeOverlay"> & { default: Variant };
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectMode = (
|
const selectMode = (
|
||||||
@ -86,12 +88,14 @@ const selectMode = (
|
|||||||
...lightBuilder,
|
...lightBuilder,
|
||||||
...lightBuilder.default,
|
...lightBuilder.default,
|
||||||
contrast: lightBuilder.colors.black,
|
contrast: lightBuilder.colors.black,
|
||||||
|
themeOverlay: lightBuilder.lightOverlay,
|
||||||
mode: "light",
|
mode: "light",
|
||||||
};
|
};
|
||||||
const dark: Mode & Variant = {
|
const dark: Mode & Variant = {
|
||||||
...darkBuilder,
|
...darkBuilder,
|
||||||
...darkBuilder.default,
|
...darkBuilder.default,
|
||||||
contrast: darkBuilder.colors.white,
|
contrast: darkBuilder.colors.white,
|
||||||
|
themeOverlay: darkBuilder.darkOverlay,
|
||||||
mode: "dark",
|
mode: "dark",
|
||||||
};
|
};
|
||||||
if (Platform.OS !== "web" || mode !== "auto") {
|
if (Platform.OS !== "web" || mode !== "auto") {
|
||||||
|
@ -36,6 +36,7 @@ import { Fetch } from "../fetch";
|
|||||||
import { ItemGrid } from "../browse/grid";
|
import { ItemGrid } from "../browse/grid";
|
||||||
import ChevronLeft from "@material-symbols/svg-400/rounded/chevron_left-fill.svg";
|
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 ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
|
||||||
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
|
|
||||||
export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
||||||
const ref = useRef<ScrollView>(null);
|
const ref = useRef<ScrollView>(null);
|
||||||
@ -43,7 +44,7 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}>
|
<View {...css({ marginX: ts(1), flexDirection: "row", justifyContent: "space-between" })}>
|
||||||
<H3>{genre}</H3>
|
<H3>{genre}</H3>
|
||||||
<View {...css({ flexDirection: "row" })}>
|
<View {...css({ flexDirection: "row" })}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -56,28 +57,30 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<ScrollView ref={ref} horizontal>
|
<InfiniteFetch
|
||||||
<Fetch query={GenreGrid.query(genre)}>
|
query={GenreGrid.query(genre)}
|
||||||
{(x, i) => (
|
layout={{ ...ItemGrid.layout, layout: "horizontal" }}
|
||||||
<ItemGrid
|
>
|
||||||
key={x.id ?? i}
|
{(x, i) => (
|
||||||
isLoading={x.isLoading as any}
|
<ItemGrid
|
||||||
href={x.href}
|
key={x.id ?? i}
|
||||||
name={x.name}
|
isLoading={x.isLoading as any}
|
||||||
subtitle={
|
href={x.href}
|
||||||
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
|
name={x.name}
|
||||||
}
|
subtitle={
|
||||||
poster={x.poster}
|
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
|
||||||
/>
|
}
|
||||||
)}
|
poster={x.poster}
|
||||||
</Fetch>
|
/>
|
||||||
</ScrollView>
|
)}
|
||||||
|
</InfiniteFetch>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
GenreGrid.query = (genre: Genre): QueryIdentifier<Page<LibraryItem>> => ({
|
GenreGrid.query = (genre: Genre): QueryIdentifier<LibraryItem> => ({
|
||||||
parser: Paged(LibraryItemP) as any,
|
parser: LibraryItemP,
|
||||||
|
infinite: true,
|
||||||
path: ["items"],
|
path: ["items"],
|
||||||
params: {
|
params: {
|
||||||
genres: genre,
|
genres: genre,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Genre,
|
||||||
ItemKind,
|
ItemKind,
|
||||||
KyooImage,
|
KyooImage,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
@ -28,11 +29,12 @@ import {
|
|||||||
QueryIdentifier,
|
QueryIdentifier,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
} from "@kyoo/models";
|
} 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 { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import { percent, useYoshiki } from "yoshiki/native";
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
import { Fetch, WithLoading } from "../fetch";
|
import { Fetch, Layout, WithLoading } from "../fetch";
|
||||||
|
import { InfiniteFetch } from "../fetch-infinite";
|
||||||
|
|
||||||
export const ItemDetails = ({
|
export const ItemDetails = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -41,35 +43,76 @@ export const ItemDetails = ({
|
|||||||
subtitle,
|
subtitle,
|
||||||
overview,
|
overview,
|
||||||
poster,
|
poster,
|
||||||
|
genres,
|
||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: WithLoading<{
|
||||||
name: string;
|
name: string;
|
||||||
tagline: string | null;
|
tagline: string | null;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
poster: KyooImage | null;
|
poster: KyooImage | null;
|
||||||
|
genres: Genre[] | null;
|
||||||
overview: string | null;
|
overview: string | null;
|
||||||
}>) => {
|
}>) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
return (
|
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
|
<ImageBackground
|
||||||
src={poster}
|
src={poster}
|
||||||
alt=""
|
alt=""
|
||||||
quality="low"
|
quality="low"
|
||||||
|
gradient={false}
|
||||||
{...css({ height: percent(100), aspectRatio: 2 / 3 })}
|
{...css({ height: percent(100), aspectRatio: 2 / 3 })}
|
||||||
>
|
>
|
||||||
<P>{name}</P>
|
<View
|
||||||
{subtitle && <SubP>{subtitle}</SubP>}
|
{...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>
|
</ImageBackground>
|
||||||
<View>
|
<View {...css({ flexShrink: 1, flexGrow: 1 })}>
|
||||||
{tagline && <P>{tagline}</P>}
|
{tagline && <P {...css({ p: ts(1) })}>{tagline}</P>}
|
||||||
{overview && <SubP>{overview}</SubP>}
|
{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>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ItemDetails.layout = {
|
||||||
|
size: ts(36),
|
||||||
|
numColumns: { xs: 1, md: 2, xl: 3 },
|
||||||
|
layout: "grid",
|
||||||
|
gap: ts(8),
|
||||||
|
} satisfies Layout;
|
||||||
|
|
||||||
export const Recommanded = () => {
|
export const Recommanded = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -77,7 +120,7 @@ export const Recommanded = () => {
|
|||||||
return (
|
return (
|
||||||
<View {...css({ marginX: ts(1) })}>
|
<View {...css({ marginX: ts(1) })}>
|
||||||
<H3>{t("home.recommanded")}</H3>
|
<H3>{t("home.recommanded")}</H3>
|
||||||
<Fetch query={Recommanded.query()}>
|
<InfiniteFetch query={Recommanded.query()} layout={ItemDetails.layout}>
|
||||||
{(x, i) => (
|
{(x, i) => (
|
||||||
<ItemDetails
|
<ItemDetails
|
||||||
key={x.id ?? i}
|
key={x.id ?? i}
|
||||||
@ -89,16 +132,17 @@ export const Recommanded = () => {
|
|||||||
subtitle={
|
subtitle={
|
||||||
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
|
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>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Recommanded.query = (): QueryIdentifier<Page<LibraryItem>> => ({
|
Recommanded.query = (): QueryIdentifier<LibraryItem> => ({
|
||||||
parser: Paged(LibraryItemP) as any,
|
parser: LibraryItemP,
|
||||||
|
infinite: true,
|
||||||
path: ["items"],
|
path: ["items"],
|
||||||
params: {
|
params: {
|
||||||
sortBy: "random",
|
sortBy: "random",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user