wip front blurhash

This commit is contained in:
Zoe Roux 2023-08-08 13:31:11 +09:00
parent 30d52c6061
commit 36f4bbc7e7
No known key found for this signature in database
21 changed files with 328 additions and 327 deletions

View File

@ -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"

View File

@ -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",

View File

@ -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",

View File

@ -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;
};

View File

@ -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.

View File

@ -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",
}

View File

@ -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";

View File

@ -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).

View File

@ -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>;

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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(),
}),
);

View File

@ -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>;

View File

@ -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"
}
}

View File

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

View File

@ -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",

View File

@ -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");

View File

@ -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",
},
});

View File

@ -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);

View File

@ -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