mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add watch percent display on episodes
This commit is contained in:
parent
fe155898bb
commit
e70174cb24
@ -18,11 +18,11 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { ImageStyle, View, ViewStyle } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import { Props, ImageLayout } from "./base-image";
|
||||
import { BlurhashContainer, blurHashToDataURL } from "./blurhash.web";
|
||||
import { BlurhashContainer } from "./blurhash.web";
|
||||
import { Skeleton } from "../skeleton";
|
||||
import NextImage from "next/image";
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
import { ImageStyle, View, ViewProps, ViewStyle } from "react-native";
|
||||
import { Props, YoshikiEnhanced } from "./base-image";
|
||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
||||
import { Image } from "./image";
|
||||
import { ComponentType, ReactNode } from "react";
|
||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||
@ -47,6 +47,8 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
||||
containerStyle,
|
||||
imageStyle,
|
||||
forcedLoading,
|
||||
hideLoad = true,
|
||||
layout,
|
||||
...asProps
|
||||
}: {
|
||||
as?: ComponentType<AsProps>;
|
||||
@ -54,13 +56,20 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
||||
children: ReactNode;
|
||||
containerStyle?: YoshikiEnhanced<ViewStyle>;
|
||||
imageStyle?: YoshikiEnhanced<ImageStyle>;
|
||||
hideLoad?: boolean;
|
||||
layout?: ImageLayout;
|
||||
} & AsProps &
|
||||
Props) => {
|
||||
const Container = as ?? View;
|
||||
return (
|
||||
<ContrastArea contrastText>
|
||||
{({ css, theme }) => (
|
||||
<Container {...(asProps as AsProps)}>
|
||||
<Container
|
||||
{...(css(
|
||||
[layout, !hideLoad && { borderRadius: 6, overflow: "hidden" }],
|
||||
asProps,
|
||||
) as AsProps)}
|
||||
>
|
||||
<View
|
||||
{...css([
|
||||
{
|
||||
@ -75,14 +84,14 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
||||
containerStyle,
|
||||
])}
|
||||
>
|
||||
{src && (
|
||||
{(src || !hideLoad) && (
|
||||
<Image
|
||||
src={src}
|
||||
quality={quality}
|
||||
forcedLoading={forcedLoading}
|
||||
alt={alt!}
|
||||
layout={{ width: percent(100), height: percent(100) }}
|
||||
Error={null}
|
||||
Error={hideLoad ? null : undefined}
|
||||
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as {
|
||||
style: ImageStyle;
|
||||
})}
|
||||
|
@ -18,12 +18,22 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { focusReset, H6, Image, ImageProps, Link, P, Skeleton, SubP, ts } from "@kyoo/primitives";
|
||||
import {
|
||||
focusReset,
|
||||
H6,
|
||||
ImageBackground,
|
||||
ImageProps,
|
||||
Link,
|
||||
P,
|
||||
Skeleton,
|
||||
SubP,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImageStyle, View } from "react-native";
|
||||
import { Layout, WithLoading } from "../fetch";
|
||||
import { percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { KyooImage } from "@kyoo/models";
|
||||
import { percent, rem, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
||||
import { KyooImage, WatchStatusV } from "@kyoo/models";
|
||||
|
||||
export const episodeDisplayNumber = (
|
||||
episode: {
|
||||
@ -50,6 +60,8 @@ export const EpisodeBox = ({
|
||||
thumbnail,
|
||||
isLoading,
|
||||
href,
|
||||
watchedPercent,
|
||||
watchedStatus,
|
||||
...props
|
||||
}: Stylable &
|
||||
WithLoading<{
|
||||
@ -57,6 +69,8 @@ export const EpisodeBox = ({
|
||||
overview: string | null;
|
||||
href: string;
|
||||
thumbnail?: ImageProps["src"] | null;
|
||||
watchedPercent: number | null;
|
||||
watchedStatus: WatchStatusV | null;
|
||||
}>) => {
|
||||
const { css } = useYoshiki("episodebox");
|
||||
const { t } = useTranslation();
|
||||
@ -87,14 +101,39 @@ export const EpisodeBox = ({
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
<ImageBackground
|
||||
src={thumbnail}
|
||||
quality="low"
|
||||
alt=""
|
||||
graditent={false}
|
||||
hideLoad={false}
|
||||
forcedLoading={isLoading}
|
||||
layout={{ width: percent(100), aspectRatio: 16 / 9 }}
|
||||
{...(css("poster") as any)}
|
||||
/>
|
||||
>
|
||||
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
|
||||
<>
|
||||
<View
|
||||
{...css({
|
||||
backgroundColor: (theme) => theme.overlay0,
|
||||
width: percent(100),
|
||||
height: ts(0.5),
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
{...css({
|
||||
backgroundColor: (theme) => theme.accent,
|
||||
width: percent(watchedPercent ?? 100),
|
||||
height: ts(0.5),
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ImageBackground>
|
||||
<Skeleton {...css({ width: percent(50) })}>
|
||||
{isLoading || (
|
||||
<P {...css([{ marginY: 0, textAlign: "center" }, "title"])}>
|
||||
@ -132,8 +171,11 @@ export const EpisodeLine = ({
|
||||
seasonNumber,
|
||||
releaseDate,
|
||||
runtime,
|
||||
watchedPercent,
|
||||
watchedStatus,
|
||||
...props
|
||||
}: WithLoading<{
|
||||
id: string;
|
||||
slug: string;
|
||||
displayNumber: string;
|
||||
name: string | null;
|
||||
@ -144,7 +186,8 @@ export const EpisodeLine = ({
|
||||
seasonNumber: number | null;
|
||||
releaseDate: Date | null;
|
||||
runtime: number | null;
|
||||
id: string;
|
||||
watchedPercent: number | null;
|
||||
watchedStatus: WatchStatusV | null;
|
||||
}> &
|
||||
Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
@ -157,18 +200,8 @@ export const EpisodeLine = ({
|
||||
{
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
child: {
|
||||
poster: {
|
||||
borderColor: "transparent",
|
||||
borderWidth: px(4),
|
||||
},
|
||||
},
|
||||
focus: {
|
||||
fover: {
|
||||
self: focusReset,
|
||||
poster: {
|
||||
transform: "scale(1.1)" as any,
|
||||
borderColor: (theme: Theme) => theme.accent,
|
||||
},
|
||||
title: {
|
||||
textDecorationLine: "underline",
|
||||
},
|
||||
@ -180,16 +213,41 @@ export const EpisodeLine = ({
|
||||
<P {...css({ width: rem(4), flexShrink: 0, m: ts(1), textAlign: "center" })}>
|
||||
{isLoading ? <Skeleton variant="filltext" /> : displayNumber}
|
||||
</P>
|
||||
<Image
|
||||
<ImageBackground
|
||||
src={thumbnail}
|
||||
quality="low"
|
||||
alt=""
|
||||
gradient={false}
|
||||
hideLoad={false}
|
||||
layout={{
|
||||
width: percent(18),
|
||||
aspectRatio: 16 / 9,
|
||||
}}
|
||||
{...(css(["poster", { flexShrink: 0, m: ts(1) }]) as { style: ImageStyle })}
|
||||
/>
|
||||
{...(css({ flexShrink: 0, m: ts(1) }) as { style: ImageStyle })}
|
||||
>
|
||||
{(watchedPercent || watchedStatus === WatchStatusV.Completed) && (
|
||||
<>
|
||||
<View
|
||||
{...css({
|
||||
backgroundColor: (theme) => theme.overlay0,
|
||||
width: percent(100),
|
||||
height: ts(0.5),
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
{...css({
|
||||
backgroundColor: (theme) => theme.accent,
|
||||
width: percent(watchedPercent ?? 100),
|
||||
height: ts(0.5),
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ImageBackground>
|
||||
<View {...css({ flexGrow: 1, flexShrink: 1, m: ts(1) })}>
|
||||
<View
|
||||
{...css({
|
||||
|
@ -497,6 +497,8 @@ export const Header = ({
|
||||
<EpisodeLine
|
||||
isLoading={false}
|
||||
{...(data.watchStatus as ShowWatchStatus).nextEpisode!}
|
||||
watchedPercent={data.watchStatus?.watchedPercent || null}
|
||||
watchedStatus={data.watchStatus?.status || null}
|
||||
displayNumber={episodeDisplayNumber((data.watchStatus as ShowWatchStatus).nextEpisode!)!}
|
||||
/>
|
||||
</Container>
|
||||
|
@ -146,6 +146,8 @@ export const EpisodeList = <Props,>({
|
||||
<EpisodeLine
|
||||
{...item}
|
||||
displayNumber={item.isLoading ? undefined! : episodeDisplayNumber(item)!}
|
||||
watchedPercent={item.watchStatus?.watchedPercent ?? null}
|
||||
watchedStatus={item.watchStatus?.status ?? null}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -162,6 +164,7 @@ EpisodeList.query = (
|
||||
path: ["show", slug, "episode"],
|
||||
params: {
|
||||
seasonNumber: season ? `gte:${season}` : undefined,
|
||||
fields: ["watchStatus"],
|
||||
},
|
||||
infinite: {
|
||||
value: true,
|
||||
|
@ -18,23 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Genre,
|
||||
ItemKind,
|
||||
News,
|
||||
NewsKind,
|
||||
NewsP,
|
||||
QueryIdentifier,
|
||||
getDisplayDate,
|
||||
} from "@kyoo/models";
|
||||
import { H3, IconButton, ts } from "@kyoo/primitives";
|
||||
import { ReactElement, forwardRef, useRef } from "react";
|
||||
import { View } from "react-native";
|
||||
import { px, useYoshiki } from "yoshiki/native";
|
||||
import { News, NewsKind, NewsP, QueryIdentifier, getDisplayDate } from "@kyoo/models";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
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, InfiniteFetchList } from "../fetch-infinite";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Header } from "./genre";
|
||||
import { EpisodeBox, episodeDisplayNumber } from "../details/episode";
|
||||
@ -74,6 +61,8 @@ export const NewsList = () => {
|
||||
overview={x.name}
|
||||
thumbnail={x.thumbnail}
|
||||
href={x.href}
|
||||
watchedPercent={x.watchStatus?.watchedPercent || null}
|
||||
watchedStatus={x.watchStatus?.status || null}
|
||||
// TODO: support this on mobile too
|
||||
// @ts-expect-error This is a web only property
|
||||
{...css({ gridColumnEnd: "span 2" })}
|
||||
@ -92,6 +81,6 @@ NewsList.query = (): QueryIdentifier<News> => ({
|
||||
params: {
|
||||
// Limit the inital numbers of items
|
||||
limit: 10,
|
||||
fields: ["show"],
|
||||
fields: ["show", "watchStatus"],
|
||||
},
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user