diff --git a/front/packages/primitives/src/image/image.web.tsx b/front/packages/primitives/src/image/image.web.tsx index 8ca25916..ac3403de 100644 --- a/front/packages/primitives/src/image/image.web.tsx +++ b/front/packages/primitives/src/image/image.web.tsx @@ -18,11 +18,11 @@ * along with Kyoo. If not, see . */ -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"; diff --git a/front/packages/primitives/src/image/index.tsx b/front/packages/primitives/src/image/index.tsx index 59d69122..8471570c 100644 --- a/front/packages/primitives/src/image/index.tsx +++ b/front/packages/primitives/src/image/index.tsx @@ -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 = ({ containerStyle, imageStyle, forcedLoading, + hideLoad = true, + layout, ...asProps }: { as?: ComponentType; @@ -54,13 +56,20 @@ export const ImageBackground = ({ children: ReactNode; containerStyle?: YoshikiEnhanced; imageStyle?: YoshikiEnhanced; + hideLoad?: boolean; + layout?: ImageLayout; } & AsProps & Props) => { const Container = as ?? View; return ( {({ css, theme }) => ( - + ({ containerStyle, ])} > - {src && ( + {(src || !hideLoad) && ( {alt!}. */ -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, )} > - + > + {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( + <> + theme.overlay0, + width: percent(100), + height: ts(0.5), + position: "absolute", + bottom: 0, + })} + /> + theme.accent, + width: percent(watchedPercent ?? 100), + height: ts(0.5), + position: "absolute", + bottom: 0, + })} + /> + + )} + {isLoading || (

@@ -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 = ({

{isLoading ? : displayNumber}

- + {...(css({ flexShrink: 0, m: ts(1) }) as { style: ImageStyle })} + > + {(watchedPercent || watchedStatus === WatchStatusV.Completed) && ( + <> + theme.overlay0, + width: percent(100), + height: ts(0.5), + position: "absolute", + bottom: 0, + })} + /> + theme.accent, + width: percent(watchedPercent ?? 100), + height: ts(0.5), + position: "absolute", + bottom: 0, + })} + /> + + )} +
diff --git a/front/packages/ui/src/details/season.tsx b/front/packages/ui/src/details/season.tsx index 80f310bd..10312dac 100644 --- a/front/packages/ui/src/details/season.tsx +++ b/front/packages/ui/src/details/season.tsx @@ -146,6 +146,8 @@ export const EpisodeList = ({ ); @@ -162,6 +164,7 @@ EpisodeList.query = ( path: ["show", slug, "episode"], params: { seasonNumber: season ? `gte:${season}` : undefined, + fields: ["watchStatus"], }, infinite: { value: true, diff --git a/front/packages/ui/src/home/news.tsx b/front/packages/ui/src/home/news.tsx index ee3907b0..47e5d30d 100644 --- a/front/packages/ui/src/home/news.tsx +++ b/front/packages/ui/src/home/news.tsx @@ -18,23 +18,10 @@ * along with Kyoo. If not, see . */ -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 => ({ params: { // Limit the inital numbers of items limit: 10, - fields: ["show"], + fields: ["show", "watchStatus"], }, });