mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add watch status indicator on items grid
This commit is contained in:
parent
0bc6512bcc
commit
7ccfab4d8b
@ -21,7 +21,7 @@
|
||||
import { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
||||
import { Image } from "./image";
|
||||
import { ComponentType, ReactNode } from "react";
|
||||
import { ComponentProps, ComponentType, ReactNode } from "react";
|
||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||
import { ContrastArea } from "../themes";
|
||||
import { percent } from "yoshiki/native";
|
||||
@ -37,6 +37,22 @@ export const Poster = ({
|
||||
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
||||
}) => <Image alt={alt!} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />;
|
||||
|
||||
export const PosterBackground = ({
|
||||
alt,
|
||||
layout,
|
||||
...props
|
||||
}: Omit<ComponentProps<typeof ImageBackground>, "layout"> & { style?: ImageStyle } & {
|
||||
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
||||
}) => (
|
||||
<ImageBackground
|
||||
alt={alt!}
|
||||
layout={{ aspectRatio: 2 / 3, ...layout }}
|
||||
hideLoad={false}
|
||||
gradient={false}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export const ImageBackground = <AsProps = ViewProps,>({
|
||||
src,
|
||||
alt,
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType, ComponentProps, ReactNode } from "react";
|
||||
import { ComponentType, ComponentProps } from "react";
|
||||
import { Platform, Text, TextProps, TextStyle, StyleProp } from "react-native";
|
||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import {
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ThemeBuilder, alpha } from "./theme";
|
||||
import { ThemeBuilder } from "./theme";
|
||||
|
||||
// Ref: https://github.com/catppuccin/catppuccin
|
||||
export const catppuccin: ThemeBuilder = {
|
||||
|
@ -18,11 +18,22 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { KyooImage } from "@kyoo/models";
|
||||
import { Link, Skeleton, Poster, ts, focusReset, P, SubP } from "@kyoo/primitives";
|
||||
import { ImageStyle } from "react-native";
|
||||
import { percent, px, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||
import {
|
||||
Link,
|
||||
Skeleton,
|
||||
Poster,
|
||||
ts,
|
||||
focusReset,
|
||||
P,
|
||||
SubP,
|
||||
PosterBackground,
|
||||
Icon,
|
||||
} from "@kyoo/primitives";
|
||||
import { ImageStyle, View } from "react-native";
|
||||
import { percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { Layout, WithLoading } from "../fetch";
|
||||
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
||||
|
||||
export const ItemGrid = ({
|
||||
href,
|
||||
@ -30,12 +41,14 @@ export const ItemGrid = ({
|
||||
subtitle,
|
||||
poster,
|
||||
isLoading,
|
||||
watchInfo,
|
||||
...props
|
||||
}: WithLoading<{
|
||||
href: string;
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
poster?: KyooImage | null;
|
||||
watchInfo: WatchStatusV | string | null;
|
||||
}> &
|
||||
Stylable<"text">) => {
|
||||
const { css } = useYoshiki("grid");
|
||||
@ -68,14 +81,37 @@ export const ItemGrid = ({
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<Poster
|
||||
<PosterBackground
|
||||
src={poster}
|
||||
alt={name}
|
||||
quality="low"
|
||||
forcedLoading={isLoading}
|
||||
layout={{ width: percent(100) }}
|
||||
{...(css("poster") as { style: ImageStyle })}
|
||||
/>
|
||||
>
|
||||
{watchInfo && (
|
||||
<View
|
||||
{...css({
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
minWidth: ts(3.5),
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
m: ts(0.5),
|
||||
pX: ts(0.5),
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
borderRadius: 999999,
|
||||
})}
|
||||
>
|
||||
{watchInfo === WatchStatusV.Completed ? (
|
||||
<Icon icon={Done} size={16} />
|
||||
) : (
|
||||
<P {...css({ m: 0, textAlign: "center" })}>{watchInfo}</P>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</PosterBackground>
|
||||
<Skeleton>
|
||||
{isLoading || (
|
||||
<P numberOfLines={1} {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
LibraryItemP,
|
||||
ItemKind,
|
||||
getDisplayDate,
|
||||
WatchStatusV,
|
||||
} from "@kyoo/models";
|
||||
import { ComponentProps, useState } from "react";
|
||||
import { createParam } from "solito";
|
||||
@ -43,6 +44,15 @@ export const itemMap = (
|
||||
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {
|
||||
if (item.isLoading) return item;
|
||||
|
||||
let watchInfo: string | WatchStatusV | null = null;
|
||||
if (item.kind !== ItemKind.Collection && item.watchStatus?.status === WatchStatusV.Completed)
|
||||
watchInfo = WatchStatusV.Completed;
|
||||
else if (item.kind === ItemKind.Show) {
|
||||
if (!item.watchStatus) watchInfo = item.episodesCount!.toString();
|
||||
else if (item.watchStatus.status === WatchStatusV.Watching)
|
||||
watchInfo = item.watchStatus.unseenEpisodesCount.toString();
|
||||
}
|
||||
|
||||
return {
|
||||
isLoading: item.isLoading,
|
||||
name: item.name,
|
||||
@ -50,6 +60,7 @@ export const itemMap = (
|
||||
href: item.href,
|
||||
poster: item.poster,
|
||||
thumbnail: item.thumbnail,
|
||||
watchInfo: watchInfo,
|
||||
};
|
||||
};
|
||||
|
||||
@ -59,6 +70,7 @@ const query = (sortKey?: SortBy, sortOrd?: SortOrd): QueryIdentifier<LibraryItem
|
||||
infinite: true,
|
||||
params: {
|
||||
sortBy: sortKey ? `${sortKey}:${sortOrd ?? "asc"}` : "name:asc",
|
||||
fields: ["watchStatus", "episodesCount"],
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -36,6 +36,7 @@ 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, InfiniteFetchList } from "../fetch-infinite";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { itemMap } from "../browse";
|
||||
|
||||
export const Header = ({ title }: { title: string }) => {
|
||||
const { css } = useYoshiki();
|
||||
@ -85,13 +86,7 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
||||
return (
|
||||
<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}
|
||||
{...itemMap(x)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
@ -105,6 +100,7 @@ GenreGrid.query = (genre: Genre): QueryIdentifier<LibraryItem> => ({
|
||||
infinite: true,
|
||||
path: ["items"],
|
||||
params: {
|
||||
fields: ["watchStatus", "episodesCount"],
|
||||
filter: `genres has ${genre}`,
|
||||
sortBy: "random",
|
||||
// Limit the inital numbers of items
|
||||
|
Loading…
x
Reference in New Issue
Block a user