mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
wip front blurhash
This commit is contained in:
parent
30d52c6061
commit
36f4bbc7e7
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -124,7 +124,7 @@ export const queryFn = async <Data,>(
|
||||
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;
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -18,17 +18,23 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<typeof GenreP>;
|
||||
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",
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./library";
|
||||
export * from "./library-item";
|
||||
export * from "./show";
|
||||
export * from "./movie";
|
||||
|
@ -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).
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<typeof LibraryP>;
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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(),
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -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<typeof ImagesP>;
|
||||
export type KyooImage = z.infer<typeof Img>;
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,10 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Style> = Style extends any
|
||||
type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading: true });
|
||||
|
||||
type Props = WithLoading<{
|
||||
src?: string | ImageSourcePropType | null;
|
||||
src?: KyooImage | null;
|
||||
alt?: string;
|
||||
}>;
|
||||
|
||||
type ImageLayout = YoshikiEnhanced<
|
||||
| { width: ViewStyle["width"]; height: ViewStyle["height"] }
|
||||
| { width: ViewStyle["width"]; aspectRatio: ViewStyle["aspectRatio"] }
|
||||
| { height: ViewStyle["height"]; aspectRatio: ViewStyle["aspectRatio"] }
|
||||
| { width: ImageStyle["width"]; height: ImageStyle["height"] }
|
||||
| { width: ImageStyle["width"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||
| { height: ImageStyle["height"]; aspectRatio: ImageStyle["aspectRatio"] }
|
||||
>;
|
||||
|
||||
export const Image = ({
|
||||
@ -62,49 +63,67 @@ export const Image = ({
|
||||
...props
|
||||
}: Props & { style?: ViewStyle } & { layout: ImageLayout }) => {
|
||||
const { css } = useYoshiki();
|
||||
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||
src ? "loading" : "errored",
|
||||
);
|
||||
|
||||
// This could be done with a key but this makes the API easier to use.
|
||||
// This unsures that the state is resetted when the source change (useful for recycler lists.)
|
||||
const [oldSource, setOldSource] = useState(src);
|
||||
if (oldSource !== src) {
|
||||
setState("loading");
|
||||
setOldSource(src);
|
||||
}
|
||||
|
||||
const border = { borderRadius: 6 } satisfies ViewStyle;
|
||||
|
||||
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||
if (!src || state === "errored")
|
||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
||||
|
||||
const nativeProps = Platform.select<Partial<ImageProps>>({
|
||||
web: {
|
||||
defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src,
|
||||
},
|
||||
default: {},
|
||||
});
|
||||
console.log(src);
|
||||
|
||||
return (
|
||||
<Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
||||
<Img
|
||||
source={typeof src === "string" ? { uri: src } : src}
|
||||
accessibilityLabel={alt}
|
||||
onLoad={() => setState("finished")}
|
||||
onError={() => setState("errored")}
|
||||
{...nativeProps}
|
||||
{...css([
|
||||
{
|
||||
width: percent(100),
|
||||
height: percent(100),
|
||||
resizeMode: "cover",
|
||||
},
|
||||
])}
|
||||
/>
|
||||
</Skeleton>
|
||||
<Img
|
||||
source={src?.source}
|
||||
placeholder={src?.blurhash}
|
||||
accessibilityLabel={alt}
|
||||
{...css([
|
||||
layout,
|
||||
// {
|
||||
// // width: percent(100),
|
||||
// // height: percent(100),
|
||||
// // resizeMode: "cover",
|
||||
// borderRadius: 6
|
||||
// },
|
||||
]) as ImageStyle}
|
||||
/>
|
||||
);
|
||||
// const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||
// src ? "loading" : "errored",
|
||||
// );
|
||||
//
|
||||
// // This could be done with a key but this makes the API easier to use.
|
||||
// // This unsures that the state is resetted when the source change (useful for recycler lists.)
|
||||
// const [oldSource, setOldSource] = useState(src);
|
||||
// if (oldSource !== src) {
|
||||
// setState("loading");
|
||||
// setOldSource(src);
|
||||
// }
|
||||
//
|
||||
// const border = { borderRadius: 6 } satisfies ViewStyle;
|
||||
//
|
||||
// if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
|
||||
// if (!src || state === "errored")
|
||||
// return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
||||
//
|
||||
// const nativeProps = Platform.select<Partial<ImageProps>>({
|
||||
// web: {
|
||||
// defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src,
|
||||
// },
|
||||
// default: {},
|
||||
// });
|
||||
//
|
||||
// return (
|
||||
// <Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
||||
// <Img
|
||||
// source={typeof src === "string" ? { uri: src } : src}
|
||||
// accessibilityLabel={alt}
|
||||
// onLoad={() => setState("finished")}
|
||||
// onError={() => setState("errored")}
|
||||
// {...nativeProps}
|
||||
// {...css([
|
||||
// {
|
||||
// width: percent(100),
|
||||
// height: percent(100),
|
||||
// resizeMode: "cover",
|
||||
// },
|
||||
// ])}
|
||||
// />
|
||||
// </Skeleton>
|
||||
// );
|
||||
};
|
||||
|
||||
export const Poster = ({
|
||||
|
@ -5,7 +5,8 @@
|
||||
"packageManager": "yarn@3.2.4",
|
||||
"dependencies": {
|
||||
"@kyoo/models": "workspace:^",
|
||||
"@kyoo/primitives": "workspace:^"
|
||||
"@kyoo/primitives": "workspace:^",
|
||||
"expo-image": "^1.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shopify/flash-list": "^1.4.1",
|
||||
|
@ -18,6 +18,7 @@
|
||||
* 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 { Platform } from "react-native";
|
||||
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
||||
@ -34,7 +35,7 @@ export const ItemGrid = ({
|
||||
href: string;
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
poster?: string | null;
|
||||
poster?: KyooImage | null;
|
||||
}> &
|
||||
Stylable<"text">) => {
|
||||
const { css } = useYoshiki("grid");
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
QueryPage,
|
||||
LibraryItem,
|
||||
LibraryItemP,
|
||||
ItemType,
|
||||
ItemKind,
|
||||
getDisplayDate,
|
||||
} from "@kyoo/models";
|
||||
import { ComponentProps, useState } from "react";
|
||||
@ -44,14 +44,14 @@ export const itemMap = (
|
||||
if (item.isLoading) return item;
|
||||
|
||||
let href;
|
||||
if (item?.type === ItemType.Movie) href = `/movie/${item.slug}`;
|
||||
else if (item?.type === ItemType.Show) href = `/show/${item.slug}`;
|
||||
if (item?.kind === ItemKind.Movie) href = `/movie/${item.slug}`;
|
||||
else if (item?.kind === ItemKind.Show) href = `/show/${item.slug}`;
|
||||
else href = `/collection/${item.slug}`;
|
||||
|
||||
return {
|
||||
isLoading: item.isLoading,
|
||||
name: item.name,
|
||||
subtitle: item.type !== ItemType.Collection ? getDisplayDate(item) : undefined,
|
||||
subtitle: item.kind !== ItemKind.Collection ? getDisplayDate(item) : undefined,
|
||||
href,
|
||||
poster: item.poster,
|
||||
thumbnail: item.thumbnail,
|
||||
@ -67,10 +67,9 @@ const query = (
|
||||
path: slug ? ["library", slug, "items"] : ["items"],
|
||||
infinite: true,
|
||||
params: {
|
||||
// The API still uses title isntead of name
|
||||
sortBy: sortKey
|
||||
? `${sortKey === SortBy.Name ? "title" : sortKey}:${sortOrd ?? "asc"}`
|
||||
: "title:asc",
|
||||
? `${sortKey}:${sortOrd ?? "asc"}`
|
||||
: "namek:asc",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { KyooImage } from "@kyoo/models";
|
||||
import { Link, P, Skeleton, ts, ImageBackground, Poster, Heading } from "@kyoo/primitives";
|
||||
import { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
@ -35,8 +36,8 @@ export const ItemList = ({
|
||||
href: string;
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
poster?: string | null;
|
||||
thumbnail?: string | null;
|
||||
poster?: KyooImage | null;
|
||||
thumbnail?: KyooImage | null;
|
||||
}>) => {
|
||||
const { css } = useYoshiki();
|
||||
const [isHovered, setHovered] = useState(0);
|
||||
|
@ -2344,6 +2344,7 @@ __metadata:
|
||||
"@gorhom/portal": ^1.0.14
|
||||
"@tanstack/react-query": ^4.26.1
|
||||
"@types/react": ^18.0.28
|
||||
expo-image: ^1.3.2
|
||||
solito: ^3.0.0
|
||||
typescript: ^4.9.5
|
||||
peerDependencies:
|
||||
@ -2375,6 +2376,7 @@ __metadata:
|
||||
"@kyoo/primitives": "workspace:^"
|
||||
"@shopify/flash-list": ^1.4.1
|
||||
"@types/react": ^18.0.28
|
||||
expo-image: ^1.3.2
|
||||
react-native-uuid: ^2.0.1
|
||||
typescript: ^4.9.5
|
||||
peerDependencies:
|
||||
@ -6830,6 +6832,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-image@npm:^1.3.2":
|
||||
version: 1.4.1
|
||||
resolution: "expo-image@npm:1.4.1"
|
||||
peerDependencies:
|
||||
expo: "*"
|
||||
checksum: 8eda22cba8ec12f647532b5562913c33adb2e206a4cea426e2d7325c34ab7b3fcb2d1effbf4e0b5df685dfd156e435d4429873a1ceb966e73885944cb6634005
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-image@npm:~1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "expo-image@npm:1.0.1"
|
||||
peerDependencies:
|
||||
expo: "*"
|
||||
checksum: 4370c442df51dda577e2e1a5daf67267d9336fd89afb2b8ee6b0f74b4fdffd9a3b71a5f5580583310d1a71610eabba9ec40d5cf007a01ac6d8a6200ac8ee6a21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-json-utils@npm:~0.5.0":
|
||||
version: 0.5.1
|
||||
resolution: "expo-json-utils@npm:0.5.1"
|
||||
@ -6913,6 +6933,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-modules-core@npm:^1.5.9":
|
||||
version: 1.6.0
|
||||
resolution: "expo-modules-core@npm:1.6.0"
|
||||
dependencies:
|
||||
compare-versions: ^3.4.0
|
||||
invariant: ^2.2.4
|
||||
checksum: 7e3d697cdeb7e1246216f9f4024f16d1a2966805b8cb46fd938cec1feec5bffb5aa166f0b25a6edb5bdf881ca318e142957d25ab4fb48420d8f060fc3742f4a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expo-navigation-bar@npm:~2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "expo-navigation-bar@npm:2.1.1"
|
||||
@ -10386,6 +10416,7 @@ __metadata:
|
||||
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
|
||||
@ -14194,7 +14225,9 @@ __metadata:
|
||||
copy-webpack-plugin: ^11.0.0
|
||||
eslint: ^8.36.0
|
||||
eslint-config-next: 13.2.4
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user