/* * Kyoo - A portable and vast media library solution. * Copyright (c) Kyoo. * * See AUTHORS.md and LICENSE file in the project root for full license information. * * Kyoo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * Kyoo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Kyoo. If not, see . */ import { type KyooImage, WatchStatusV } from "@kyoo/models"; import { Icon, Link, P, Poster, PosterBackground, Skeleton, SubP, focusReset, important, ts, } from "@kyoo/primitives"; import Done from "@material-symbols/svg-400/rounded/check-fill.svg"; import { useState } from "react"; import { type ImageStyle, Platform, View } from "react-native"; import { type Stylable, type Theme, max, percent, px, rem, useYoshiki } from "yoshiki/native"; import { ItemContext } from "../components/context-menus"; import type { Layout } from "../fetch"; export const ItemWatchStatus = ({ watchStatus, unseenEpisodesCount, ...props }: { watchStatus?: WatchStatusV | null; unseenEpisodesCount?: number | null; }) => { const { css } = useYoshiki(); if (watchStatus !== WatchStatusV.Completed && !unseenEpisodesCount) return null; return ( theme.darkOverlay, borderRadius: 999999, }, props, )} > {watchStatus === WatchStatusV.Completed ? ( ) : (

{unseenEpisodesCount}

)}
); }; export const ItemProgress = ({ watchPercent }: { watchPercent: number }) => { const { css } = useYoshiki("episodebox"); return ( <> theme.user.overlay0, width: percent(100), height: ts(0.5), position: "absolute", bottom: 0, })} /> theme.user.accent, width: percent(watchPercent), height: ts(0.5), position: "absolute", bottom: 0, })} /> ); }; export const ItemGrid = ({ href, slug, name, type, subtitle, poster, watchStatus, watchPercent, unseenEpisodesCount, ...props }: { href: string; slug: string; name: string; subtitle: string | null; poster: KyooImage | null; watchStatus: WatchStatusV | null; watchPercent: number | null; type: "movie" | "show" | "collection"; unseenEpisodesCount: number | null; } & Stylable<"text">) => { const [moreOpened, setMoreOpened] = useState(false); const { css } = useYoshiki("grid"); return ( setMoreOpened(true)} {...css( { flexDirection: "column", alignItems: "center", width: percent(100), child: { poster: { borderColor: (theme) => theme.background, borderWidth: ts(0.5), borderStyle: "solid", }, more: { display: "none", }, }, fover: { self: focusReset, poster: { borderColor: (theme: Theme) => theme.accent, }, title: { textDecorationLine: "underline", }, more: { display: "flex", }, }, }, props, )} > {type === "movie" && watchPercent && } {type !== "collection" && ( setMoreOpened(v)} {...css([ { position: "absolute", top: 0, right: 0, bg: (theme) => theme.dark.background, }, "more", Platform.OS === "web" && moreOpened && { display: important("flex") }, ])} /> )}

{name}

{subtitle && ( {subtitle} )} ); }; ItemGrid.Loader = (props: Stylable) => { const { css } = useYoshiki(); return ( theme.background, borderWidth: ts(0.5), borderStyle: "solid", })} /> ); }; ItemGrid.layout = { size: px(150), numColumns: { xs: 3, sm: 4, md: 5, lg: 6, xl: 8 }, gap: { xs: ts(1), sm: ts(2), md: ts(4) }, layout: "grid", } satisfies Layout;