diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 5dd99093..a8f88fa2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -24,10 +24,10 @@ services: dockerfile: Dockerfile.dev volumes: - ./front:/app - - /app/.yarn - - /app/node_modules - - /app/apps/web/.next/ - - /app/apps/mobile/.expo/ + # - /app/.yarn + # - /app/node_modules + # - /app/apps/web/.next/ + # - /app/apps/mobile/.expo/ ports: - "3000:3000" - "19000:19000" diff --git a/front/apps/mobile/package.json b/front/apps/mobile/package.json index 4d21e273..900e16a5 100644 --- a/front/apps/mobile/package.json +++ b/front/apps/mobile/package.json @@ -26,6 +26,7 @@ "expo-constants": "~14.2.1", "expo-dev-client": "~2.2.1", "expo-font": "~11.1.1", + "expo-image": "~1.0.0", "expo-linear-gradient": "~12.1.2", "expo-linking": "~4.0.1", "expo-localization": "~14.1.1", diff --git a/front/apps/web/package.json b/front/apps/web/package.json index 17434ba9..e1331b76 100644 --- a/front/apps/web/package.json +++ b/front/apps/web/package.json @@ -17,7 +17,9 @@ "@material-symbols/svg-400": "^0.5.0", "@radix-ui/react-dropdown-menu": "^2.0.4", "@tanstack/react-query": "^4.26.1", + "expo-image": "^1.3.2", "expo-linear-gradient": "^12.1.2", + "expo-modules-core": "^1.5.9", "hls.js": "^1.3.4", "i18next": "^22.4.11", "jotai": "^2.0.3", diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx index 4f5fe001..0773907b 100644 --- a/front/packages/models/src/query.tsx +++ b/front/packages/models/src/query.tsx @@ -124,7 +124,7 @@ export const queryFn = async ( const parsed = await type.safeParseAsync(data); if (!parsed.success) { console.log("Parse error: ", parsed.error); - throw { errors: parsed.error.errors.map((x) => x.message) } as KyooErrors; + throw { errors: ["Invalid response from kyoo. Possible version mismatch between the server and the application."] } as KyooErrors; } return parsed.data; }; diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts index 8df707ba..10789a19 100644 --- a/front/packages/models/src/resources/episode.ts +++ b/front/packages/models/src/resources/episode.ts @@ -23,44 +23,51 @@ import { zdate } from "../utils"; import { ImagesP } from "../traits"; import { ResourceP } from "../traits/resource"; -export const EpisodeP = z.preprocess( - (x: any) => { - if (!x) return x; - x.name = x.title; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The season in witch this episode is in. - */ - seasonNumber: z.number().nullable(), +const BaseEpisodeP = ResourceP.merge(ImagesP).extend({ + /** + * The season in witch this episode is in. + */ + seasonNumber: z.number().nullable(), - /** - * The number of this episode in it's season. - */ - episodeNumber: z.number().nullable(), + /** + * The number of this episode in it's season. + */ + episodeNumber: z.number().nullable(), - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. - */ - absoluteNumber: z.number().nullable(), + /** + * The absolute number of this episode. It's an episode number that is not reset to 1 after a new + * season. + */ + absoluteNumber: z.number().nullable(), - /** - * The title of this episode. - */ - name: z.string().nullable(), + /** + * The title of this episode. + */ + name: z.string().nullable(), - /** - * The overview of this episode. - */ - overview: z.string().nullable(), + /** + * The overview of this episode. + */ + overview: z.string().nullable(), - /** - * The release date of this episode. It can be null if unknown. - */ - releaseDate: zdate().nullable(), - }), -); + /** + * The release date of this episode. It can be null if unknown. + */ + releaseDate: zdate().nullable(), +}); + +export const EpisodeP = BaseEpisodeP.extend({ + /** + * The episode that come before this one if you follow usual watch orders. If this is the first + * episode or this is a movie, it will be null. + */ + previousEpisode: BaseEpisodeP.nullable().optional(), + /** + * The episode that come after this one if you follow usual watch orders. If this is the last + * aired episode or this is a movie, it will be null. + */ + nextEpisode: BaseEpisodeP.nullable().optional(), +}) /** * A class to represent a single show's episode. diff --git a/front/packages/models/src/resources/genre.ts b/front/packages/models/src/resources/genre.ts index 60686494..dfb04d9a 100644 --- a/front/packages/models/src/resources/genre.ts +++ b/front/packages/models/src/resources/genre.ts @@ -18,17 +18,23 @@ * along with Kyoo. If not, see . */ -import { z } from "zod"; -import { ResourceP } from "../traits/resource"; - -export const GenreP = ResourceP.extend({ - /** - * The name of this genre. - */ - name: z.string(), -}); - -/** - * A genre that allow one to specify categories for shows. - */ -export type Genre = z.infer; +export enum Genre { + Action = "Action", + Adventure = "Adventure", + Animation = "Animation", + Comedy = "Comedy", + Crime = "Crime", + Documentary = "Documentary", + Drama = "Drama", + Family = "Family", + Fantasy = "Fantasy", + History = "History", + Horror = "Horror", + Music = "Music", + Mystery = "Mystery", + Romance = "Romance", + ScienceFiction = "ScienceFiction", + Thriller = "Thriller", + War = "War", + Western = "Western", +} diff --git a/front/packages/models/src/resources/index.ts b/front/packages/models/src/resources/index.ts index 39332330..64aa2385 100644 --- a/front/packages/models/src/resources/index.ts +++ b/front/packages/models/src/resources/index.ts @@ -18,7 +18,6 @@ * along with Kyoo. If not, see . */ -export * from "./library"; export * from "./library-item"; export * from "./show"; export * from "./movie"; diff --git a/front/packages/models/src/resources/library-item.ts b/front/packages/models/src/resources/library-item.ts index 6ceb1555..d3f648b7 100644 --- a/front/packages/models/src/resources/library-item.ts +++ b/front/packages/models/src/resources/library-item.ts @@ -26,32 +26,26 @@ import { ShowP } from "./show"; /** * The type of item, ether a show, a movie or a collection. */ -export enum ItemType { - Show = 0, - Movie = 1, - Collection = 2, +export enum ItemKind { + Show = "Show", + Movie = "Movie", + Collection = "Collection", } -export const LibraryItemP = z.preprocess( - (x: any) => { - if (!x.aliases) x.aliases = []; - return x; - }, - z.union([ - /* - * Either a Show - */ - ShowP.and(z.object({ type: z.literal(ItemType.Show) })), - /* - * Or a Movie - */ - MovieP.and(z.object({ type: z.literal(ItemType.Movie) })), - /* - * Or a Collection - */ - CollectionP.and(z.object({ type: z.literal(ItemType.Collection) })), - ]), -); +export const LibraryItemP = z.union([ + /* + * Either a Show + */ + ShowP.and(z.object({ kind: z.literal(ItemKind.Show) })), + /* + * Or a Movie + */ + MovieP.and(z.object({ kind: z.literal(ItemKind.Movie) })), + /* + * Or a Collection + */ + CollectionP.and(z.object({ kind: z.literal(ItemKind.Collection) })), +]); /** * An item that can be contained by a Library (so a Show, a Movie or a Collection). diff --git a/front/packages/models/src/resources/library.ts b/front/packages/models/src/resources/library.ts deleted file mode 100644 index 8453b72a..00000000 --- a/front/packages/models/src/resources/library.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { z } from "zod"; -import { ResourceP } from "../traits/resource"; - -/** - * The library that will contain Shows, Collections... - */ -export const LibraryP = ResourceP.extend({ - /** - * The name of this library. - */ - name: z.string(), - - /** - * The list of paths that this library is responsible for. This is mainly used by the Scan task. - */ - paths: z.array(z.string()), -}); - -export type Library = z.infer; diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts index afa59735..402583c0 100644 --- a/front/packages/models/src/resources/movie.ts +++ b/front/packages/models/src/resources/movie.ts @@ -21,58 +21,53 @@ import { z } from "zod"; import { zdate } from "../utils"; import { ImagesP, ResourceP } from "../traits"; -import { GenreP } from "./genre"; +import { Genre } from "./genre"; import { StudioP } from "./studio"; +import { Status } from "./show"; -/** - * The enum containing movie's status. - */ -export enum MovieStatus { - Unknown = 0, - Finished = 1, - Planned = 3, -} - -export const MovieP = z.preprocess( - (x: any) => { - // Waiting for the API to be updaded - x.name = x.title; - if (x.aliases === null) x.aliases = []; - x.airDate = x.startAir; - x.trailer = x.images["3"]; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The title of this movie. - */ - name: z.string(), - /** - * The list of alternative titles of this movie. - */ - aliases: z.array(z.string()), - /** - * The summary of this movie. - */ - overview: z.string().nullable(), - /** - * Is this movie not aired yet or finished? - */ - status: z.nativeEnum(MovieStatus), - /** - * The date this movie aired. It can also be null if this is unknown. - */ - airDate: zdate().nullable(), - /** - * The list of genres (themes) this movie has. - */ - genres: z.array(GenreP).optional(), - /** - * The studio that made this movie. - */ - studio: StudioP.optional().nullable(), - }), -); +export const MovieP = ResourceP.merge(ImagesP).extend({ + /** + * The title of this movie. + */ + name: z.string(), + /** + * A catchphrase for this show. + */ + tagline: z.string().nullable(), + /** + * The list of alternative titles of this movie. + */ + aliases: z.array(z.string()), + /** + * The summary of this movie. + */ + overview: z.string().nullable(), + /** + * A list of tags that match this movie. + */ + tags: z.array(z.string()), + /** + /** + * Is this movie not aired yet or finished? + */ + status: z.nativeEnum(Status), + /** + * The date this movie aired. It can also be null if this is unknown. + */ + airDate: zdate().nullable(), + /** + * A youtube url for the trailer. + */ + trailer: z.string().optional().nullable(), + /** + * The list of genres (themes) this movie has. + */ + genres: z.array(z.nativeEnum(Genre)), + /** + * The studio that made this movie. + */ + studio: StudioP.optional().nullable(), +}); /** * A Movie type diff --git a/front/packages/models/src/resources/season.ts b/front/packages/models/src/resources/season.ts index e858f7eb..d187a5e3 100644 --- a/front/packages/models/src/resources/season.ts +++ b/front/packages/models/src/resources/season.ts @@ -23,37 +23,28 @@ import { zdate } from "../utils"; import { ImagesP } from "../traits"; import { ResourceP } from "../traits/resource"; -export const SeasonP = z.preprocess( - (x: any) => { - x.name = x.title; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The name of this season. - */ - name: z.string(), - /** - * The number of this season. This can be set to 0 to indicate specials. - */ - seasonNumber: z.number(), - - /** - * A quick overview of this season. - */ - overview: z.string().nullable(), - - /** - * The starting air date of this season. - */ - startDate: zdate().nullable(), - - /** - * The ending date of this season. - */ - endDate: zdate().nullable(), - }), -); +export const SeasonP = ResourceP.merge(ImagesP).extend({ + /** + * The name of this season. + */ + name: z.string(), + /** + * The number of this season. This can be set to 0 to indicate specials. + */ + seasonNumber: z.number(), + /** + * A quick overview of this season. + */ + overview: z.string().nullable(), + /** + * The starting air date of this season. + */ + startDate: zdate().nullable(), + /** + * The ending date of this season. + */ + endDate: zdate().nullable(), +}); /** * A season of a Show. diff --git a/front/packages/models/src/resources/show.ts b/front/packages/models/src/resources/show.ts index aa82c299..30bbeba8 100644 --- a/front/packages/models/src/resources/show.ts +++ b/front/packages/models/src/resources/show.ts @@ -21,7 +21,7 @@ import { z } from "zod"; import { zdate } from "../utils"; import { ImagesP, ResourceP } from "../traits"; -import { GenreP } from "./genre"; +import { Genre } from "./genre"; import { SeasonP } from "./season"; import { StudioP } from "./studio"; @@ -29,60 +29,62 @@ import { StudioP } from "./studio"; * The enum containing show's status. */ export enum Status { - Unknown = 0, - Finished = 1, - Airing = 2, - Planned = 3, + Unknown = "Unknown", + Finished = "Finished", + Airing = "Airing", + Planned = "Planned", } -export const ShowP = z.preprocess( - (x: any) => { - if (!x) return x; - // Waiting for the API to be updaded - x.name = x.title; - if (x.aliases === null) x.aliases = []; - x.trailer = x.images["3"]; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The title of this show. - */ - name: z.string(), - /** - * The list of alternative titles of this show. - */ - aliases: z.array(z.string()), - /** - * The summary of this show. - */ - overview: z.string().nullable(), - /** - * Is this show airing, not aired yet or finished? - */ - status: z.nativeEnum(Status), - /** - * The date this show started airing. It can be null if this is unknown. - */ - startAir: zdate().nullable(), - /** - * The date this show finished airing. It can also be null if this is unknown. - */ - endAir: zdate().nullable(), - /** - * The list of genres (themes) this show has. - */ - genres: z.array(GenreP).optional(), - /** - * The studio that made this show. - */ - studio: StudioP.optional().nullable(), - /** - * The list of seasons of this show. - */ - seasons: z.array(SeasonP).optional(), - }), -); +export const ShowP = ResourceP.merge(ImagesP).extend({ + /** + * The title of this show. + */ + name: z.string(), + /** + * A catchphrase for this show. + */ + tagline: z.string().nullable(), + /** + * The list of alternative titles of this show. + */ + aliases: z.array(z.string()), + /** + * The summary of this show. + */ + overview: z.string().nullable(), + /** + * A list of tags that match this movie. + */ + tags: z.array(z.string()), + /** + * Is this show airing, not aired yet or finished? + */ + status: z.nativeEnum(Status), + /** + * The date this show started airing. It can be null if this is unknown. + */ + startAir: zdate().nullable(), + /** + * The date this show finished airing. It can also be null if this is unknown. + */ + endAir: zdate().nullable(), + /** + * The list of genres (themes) this show has. + */ + genres: z.array(z.nativeEnum(Genre)), + /** + * A youtube url for the trailer. + */ + trailer: z.string().optional().nullable(), + /** + * The studio that made this show. + */ + studio: StudioP.optional().nullable(), + /** + * The list of seasons of this show. + */ + seasons: z.array(SeasonP).optional(), +}); /** * A tv serie or an anime. diff --git a/front/packages/models/src/resources/watch-item.ts b/front/packages/models/src/resources/watch-item.ts index cf20b8dc..66901505 100644 --- a/front/packages/models/src/resources/watch-item.ts +++ b/front/packages/models/src/resources/watch-item.ts @@ -176,16 +176,6 @@ const WatchEpisodeP = WatchMovieP.and( * new season. */ absoluteNumber: z.number().nullable(), - /** - * The episode that come before this one if you follow usual watch orders. If this is the first - * episode or this is a movie, it will be null. - */ - previousEpisode: EpisodeP.nullable(), - /** - * The episode that come after this one if you follow usual watch orders. If this is the last - * aired episode or this is a movie, it will be null. - */ - nextEpisode: EpisodeP.nullable(), }), ); diff --git a/front/packages/models/src/traits/images.ts b/front/packages/models/src/traits/images.ts index c4dec740..e8e26a7f 100644 --- a/front/packages/models/src/traits/images.ts +++ b/front/packages/models/src/traits/images.ts @@ -27,37 +27,35 @@ export const imageFn = (url: string) => ? `/api${url}` : kyooApiUrl + url; +const Img = z.object({ + source: z.string(), + blurhash: z.string() +}); + export const ImagesP = z.object({ /** * An url to the poster of this resource. If this resource does not have an image, the link will * be null. If the kyoo's instance is not capable of handling this kind of image for the specific * resource, this field won't be present. */ - poster: z.string().transform(imageFn).optional().nullable(), + poster: Img.nullable(), /** * An url to the thumbnail of this resource. If this resource does not have an image, the link * will be null. If the kyoo's instance is not capable of handling this kind of image for the * specific resource, this field won't be present. */ - thumbnail: z.string().transform(imageFn).optional().nullable(), + thumbnail: Img.nullable(), /** * An url to the logo of this resource. If this resource does not have an image, the link will be * null. If the kyoo's instance is not capable of handling this kind of image for the specific * resource, this field won't be present. */ - logo: z.string().transform(imageFn).optional().nullable(), - - /** - * An url to the thumbnail of this resource. If this resource does not have an image, the link - * will be null. If the kyoo's instance is not capable of handling this kind of image for the - * specific resource, this field won't be present. - */ - trailer: z.string().optional().nullable(), + logo: Img.nullable(), }); /** * Base traits for items that has image resources. */ -export type Images = z.infer; +export type KyooImage = z.infer; diff --git a/front/packages/primitives/package.json b/front/packages/primitives/package.json index f33302cd..65221037 100644 --- a/front/packages/primitives/package.json +++ b/front/packages/primitives/package.json @@ -34,6 +34,7 @@ "dependencies": { "@expo/html-elements": "^0.4.1", "@tanstack/react-query": "^4.26.1", + "expo-image": "^1.3.2", "solito": "^3.0.0" } } diff --git a/front/packages/primitives/src/image.tsx b/front/packages/primitives/src/image.tsx index 62c3f1eb..ceafdad3 100644 --- a/front/packages/primitives/src/image.tsx +++ b/front/packages/primitives/src/image.tsx @@ -18,10 +18,10 @@ * along with Kyoo. If not, see . */ +import { KyooImage } from "@kyoo/models"; import { ComponentType, ReactNode, useState } from "react"; import { View, - Image as Img, ImageSourcePropType, ImageStyle, Platform, @@ -29,6 +29,7 @@ import { ViewProps, ViewStyle, } from "react-native"; +import {Image as Img} from "expo-image" import { percent, useYoshiki } from "yoshiki/native"; import { YoshikiStyle } from "yoshiki/dist/type"; import { Skeleton } from "./skeleton"; @@ -44,14 +45,14 @@ type YoshikiEnhanced