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 { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
||||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
||||||
import { Image } from "./image";
|
import { Image } from "./image";
|
||||||
import { ComponentType, ReactNode } from "react";
|
import { ComponentProps, ComponentType, ReactNode } from "react";
|
||||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||||
import { ContrastArea } from "../themes";
|
import { ContrastArea } from "../themes";
|
||||||
import { percent } from "yoshiki/native";
|
import { percent } from "yoshiki/native";
|
||||||
@ -37,6 +37,22 @@ export const Poster = ({
|
|||||||
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
||||||
}) => <Image alt={alt!} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />;
|
}) => <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,>({
|
export const ImageBackground = <AsProps = ViewProps,>({
|
||||||
src,
|
src,
|
||||||
alt,
|
alt,
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* 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 { Platform, Text, TextProps, TextStyle, StyleProp } from "react-native";
|
||||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||||
import {
|
import {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* 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
|
// Ref: https://github.com/catppuccin/catppuccin
|
||||||
export const catppuccin: ThemeBuilder = {
|
export const catppuccin: ThemeBuilder = {
|
||||||
|
@ -18,11 +18,22 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage } from "@kyoo/models";
|
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||||
import { Link, Skeleton, Poster, ts, focusReset, P, SubP } from "@kyoo/primitives";
|
import {
|
||||||
import { ImageStyle } from "react-native";
|
Link,
|
||||||
import { percent, px, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
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 { Layout, WithLoading } from "../fetch";
|
||||||
|
import Done from "@material-symbols/svg-400/rounded/done-fill.svg";
|
||||||
|
|
||||||
export const ItemGrid = ({
|
export const ItemGrid = ({
|
||||||
href,
|
href,
|
||||||
@ -30,12 +41,14 @@ export const ItemGrid = ({
|
|||||||
subtitle,
|
subtitle,
|
||||||
poster,
|
poster,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
watchInfo,
|
||||||
...props
|
...props
|
||||||
}: WithLoading<{
|
}: WithLoading<{
|
||||||
href: string;
|
href: string;
|
||||||
name: string;
|
name: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
poster?: KyooImage | null;
|
poster?: KyooImage | null;
|
||||||
|
watchInfo: WatchStatusV | string | null;
|
||||||
}> &
|
}> &
|
||||||
Stylable<"text">) => {
|
Stylable<"text">) => {
|
||||||
const { css } = useYoshiki("grid");
|
const { css } = useYoshiki("grid");
|
||||||
@ -68,14 +81,37 @@ export const ItemGrid = ({
|
|||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Poster
|
<PosterBackground
|
||||||
src={poster}
|
src={poster}
|
||||||
alt={name}
|
alt={name}
|
||||||
quality="low"
|
quality="low"
|
||||||
forcedLoading={isLoading}
|
forcedLoading={isLoading}
|
||||||
layout={{ width: percent(100) }}
|
layout={{ width: percent(100) }}
|
||||||
{...(css("poster") as { style: ImageStyle })}
|
{...(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>
|
<Skeleton>
|
||||||
{isLoading || (
|
{isLoading || (
|
||||||
<P numberOfLines={1} {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
|
<P numberOfLines={1} {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
LibraryItemP,
|
LibraryItemP,
|
||||||
ItemKind,
|
ItemKind,
|
||||||
getDisplayDate,
|
getDisplayDate,
|
||||||
|
WatchStatusV,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { ComponentProps, useState } from "react";
|
import { ComponentProps, useState } from "react";
|
||||||
import { createParam } from "solito";
|
import { createParam } from "solito";
|
||||||
@ -43,6 +44,15 @@ export const itemMap = (
|
|||||||
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {
|
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {
|
||||||
if (item.isLoading) return item;
|
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 {
|
return {
|
||||||
isLoading: item.isLoading,
|
isLoading: item.isLoading,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@ -50,6 +60,7 @@ export const itemMap = (
|
|||||||
href: item.href,
|
href: item.href,
|
||||||
poster: item.poster,
|
poster: item.poster,
|
||||||
thumbnail: item.thumbnail,
|
thumbnail: item.thumbnail,
|
||||||
|
watchInfo: watchInfo,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,6 +70,7 @@ const query = (sortKey?: SortBy, sortOrd?: SortOrd): QueryIdentifier<LibraryItem
|
|||||||
infinite: true,
|
infinite: true,
|
||||||
params: {
|
params: {
|
||||||
sortBy: sortKey ? `${sortKey}:${sortOrd ?? "asc"}` : "name:asc",
|
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 ChevronRight from "@material-symbols/svg-400/rounded/chevron_right-fill.svg";
|
||||||
import { InfiniteFetch, InfiniteFetchList } from "../fetch-infinite";
|
import { InfiniteFetch, InfiniteFetchList } from "../fetch-infinite";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { itemMap } from "../browse";
|
||||||
|
|
||||||
export const Header = ({ title }: { title: string }) => {
|
export const Header = ({ title }: { title: string }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -85,13 +86,7 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
|
|||||||
return (
|
return (
|
||||||
<ItemGrid
|
<ItemGrid
|
||||||
key={x.id ?? i}
|
key={x.id ?? i}
|
||||||
isLoading={x.isLoading as any}
|
{...itemMap(x)}
|
||||||
href={x.href}
|
|
||||||
name={x.name}
|
|
||||||
subtitle={
|
|
||||||
x.kind !== ItemKind.Collection && !x.isLoading ? getDisplayDate(x) : undefined
|
|
||||||
}
|
|
||||||
poster={x.poster}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@ -105,6 +100,7 @@ GenreGrid.query = (genre: Genre): QueryIdentifier<LibraryItem> => ({
|
|||||||
infinite: true,
|
infinite: true,
|
||||||
path: ["items"],
|
path: ["items"],
|
||||||
params: {
|
params: {
|
||||||
|
fields: ["watchStatus", "episodesCount"],
|
||||||
filter: `genres has ${genre}`,
|
filter: `genres has ${genre}`,
|
||||||
sortBy: "random",
|
sortBy: "random",
|
||||||
// Limit the inital numbers of items
|
// Limit the inital numbers of items
|
||||||
|
Loading…
x
Reference in New Issue
Block a user