Use expo image on mobile

This commit is contained in:
Zoe Roux 2024-10-30 22:25:06 +01:00
parent 18f7bda090
commit f0ae541eb2
No known key found for this signature in database
7 changed files with 31 additions and 86 deletions

View File

@ -33,6 +33,7 @@
"expo-dev-client": "~5.0.0-preview.4", "expo-dev-client": "~5.0.0-preview.4",
"expo-file-system": "~18.0.0", "expo-file-system": "~18.0.0",
"expo-font": "~13.0.0", "expo-font": "~13.0.0",
"expo-image": "^1.13.0",
"expo-image-picker": "~16.0.0", "expo-image-picker": "~16.0.0",
"expo-linear-gradient": "~14.0.1", "expo-linear-gradient": "~14.0.1",
"expo-linking": "~7.0.2", "expo-linking": "~7.0.2",
@ -49,8 +50,6 @@
"react": "18.3.1", "react": "18.3.1",
"react-i18next": "^15.1.0", "react-i18next": "^15.1.0",
"react-native": "0.76.1", "react-native": "0.76.1",
"react-native-blurhash": "^2.0.3",
"react-native-fast-image": "^8.6.3",
"react-native-mmkv": "^3.1.0", "react-native-mmkv": "^3.1.0",
"react-native-reanimated": "~3.16.1", "react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0", "react-native-safe-area-context": "4.12.0",

Binary file not shown.

View File

@ -17,8 +17,6 @@
"moti": "*", "moti": "*",
"react": "*", "react": "*",
"react-native": "*", "react-native": "*",
"react-native-blurhash": "*",
"react-native-fast-image": "*",
"react-native-reanimated": "*", "react-native-reanimated": "*",
"react-native-safe-area-context": "*", "react-native-safe-area-context": "*",
"react-native-svg": "*", "react-native-svg": "*",

View File

@ -19,7 +19,6 @@
*/ */
import type { KyooImage } from "@kyoo/models"; import type { KyooImage } from "@kyoo/models";
import type { ReactElement } from "react";
import type { ImageStyle } from "react-native"; import type { ImageStyle } from "react-native";
import type { YoshikiStyle } from "yoshiki/src/type"; import type { YoshikiStyle } from "yoshiki/src/type";
@ -33,7 +32,6 @@ export type Props = {
src?: KyooImage | null; src?: KyooImage | null;
quality: "low" | "medium" | "high"; quality: "low" | "medium" | "high";
alt?: string; alt?: string;
Err?: ReactElement | null;
forcedLoading?: boolean; forcedLoading?: boolean;
}; };

View File

@ -18,12 +18,10 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getCurrentToken } from "@kyoo/models"; import { type ReactElement } from "react";
import { type ReactElement, useState } from "react"; import { type ImageStyle, View, type ViewStyle } from "react-native";
import { type FlexStyle, type ImageStyle, View, type ViewStyle } from "react-native"; import { Image as ExpoImage } from "expo-image";
import { Blurhash } from "react-native-blurhash"; import { useYoshiki } from "yoshiki/native";
import FastImage from "react-native-fast-image";
import { percent, useYoshiki } from "yoshiki/native";
import { Skeleton } from "../skeleton"; import { Skeleton } from "../skeleton";
import type { ImageLayout, Props } from "./base-image"; import type { ImageLayout, Props } from "./base-image";
@ -33,64 +31,24 @@ export const Image = ({
alt, alt,
forcedLoading = false, forcedLoading = false,
layout, layout,
Err,
...props ...props
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => { }: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
const { css } = useYoshiki(); 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, overflow: "hidden" } satisfies ViewStyle; const border = { borderRadius: 6, overflow: "hidden" } satisfies ViewStyle;
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />; if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
if (!src || state === "errored") { if (!src) return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
return Err !== undefined ? (
Err
) : (
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
);
}
quality ??= "high";
const token = getCurrentToken();
return ( return (
<View {...css([layout, border], props)}> <ExpoImage
{state !== "finished" && ( alt={alt}
<Blurhash contentFit="cover"
blurhash={src.blurhash} placeholderContentFit="cover"
resizeMode="cover" placeholder={{ blurhash: src.blurhash }}
{...css({ width: percent(100), height: percent(100) })} source={src[quality ?? "high"]}
/> recyclingKey={src.high}
)} {...css([layout, border])}
<FastImage />
source={{
uri: src[quality],
headers: token
? {
Authorization: token,
}
: {},
priority: FastImage.priority[quality === "medium" ? "normal" : quality],
}}
accessibilityLabel={alt}
onLoad={() => setState("finished")}
onError={() => setState("errored")}
resizeMode={FastImage.resizeMode.cover}
{...(css({
width: percent(100),
height: percent(100),
}) as { style: FlexStyle })}
/>
</View>
); );
}; };

View File

@ -18,14 +18,13 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import NextImage from "next/image";
import { type ReactElement, useState } from "react"; import { type ReactElement, useState } from "react";
import { type ImageStyle, View, type ViewStyle } from "react-native"; import { type ImageStyle, View, type ViewStyle } from "react-native";
import { useYoshiki } from "yoshiki/native"; import { useYoshiki } from "yoshiki/native";
import { imageBorderRadius } from "../constants"; import { imageBorderRadius } from "../constants";
import { Skeleton } from "../skeleton"; import { Skeleton } from "../skeleton";
import type { ImageLayout, Props } from "./base-image"; import type { ImageLayout, Props } from "./base-image";
import { BlurhashContainer, useRenderType } from "./blurhash.web"; import { BlurhashContainer } from "./blurhash.web";
export const Image = ({ export const Image = ({
src, src,
@ -33,7 +32,6 @@ export const Image = ({
alt, alt,
forcedLoading = false, forcedLoading = false,
layout, layout,
Err,
...props ...props
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => { }: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
@ -45,11 +43,7 @@ export const Image = ({
if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />; if (forcedLoading) return <Skeleton variant="custom" {...css([layout, border], props)} />;
if (!src || state === "errored") { if (!src || state === "errored") {
return Err !== undefined ? ( return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
Err
) : (
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
);
} }
return ( return (

View File

@ -18,18 +18,14 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Image, View } from "react-native"; import { Image as ExpoImage } from "expo-image";
export const Sprite = ({ export const Sprite = ({
src, src,
alt, alt,
width, style,
height,
x, x,
y, y,
rows,
columns,
style,
...props ...props
}: { }: {
src: string; src: string;
@ -43,15 +39,17 @@ export const Sprite = ({
style?: object; style?: object;
}) => { }) => {
return ( return (
<View style={{ width, height, overflow: "hidden", flexGrow: 0, flexShrink: 0 }}> <ExpoImage
<Image source={src}
source={{ uri: src }} alt={alt}
alt={alt} contentFit="none"
width={width * columns} contentPosition={{left: -x, top: -y}}
height={height * rows} style={{
style={{ transform: [{ translateX: -x }, { translateY: -y }] }} flexGrow: 0,
{...props} flexShrink: 0,
/> ...style
</View> }}
{...props}
/>
); );
}; };