mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 13:44:33 -04:00
Use react-native fast images and blurhash on mobile
This commit is contained in:
parent
c06afcd56d
commit
1faf234255
@ -43,6 +43,7 @@
|
|||||||
"react-i18next": "^13.0.3",
|
"react-i18next": "^13.0.3",
|
||||||
"react-native": "0.72.3",
|
"react-native": "0.72.3",
|
||||||
"react-native-blurhash": "^1.1.11",
|
"react-native-blurhash": "^1.1.11",
|
||||||
|
"react-native-fast-image": "^8.6.3",
|
||||||
"react-native-mmkv": "^2.10.1",
|
"react-native-mmkv": "^2.10.1",
|
||||||
"react-native-reanimated": "~3.3.0",
|
"react-native-reanimated": "~3.3.0",
|
||||||
"react-native-safe-area-context": "4.6.3",
|
"react-native-safe-area-context": "4.6.3",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*",
|
"react-native": "*",
|
||||||
"react-native-blurhash": "*",
|
"react-native-blurhash": "*",
|
||||||
|
"react-native-fast-image": "*",
|
||||||
"react-native-reanimated": "*",
|
"react-native-reanimated": "*",
|
||||||
"react-native-svg": "*",
|
"react-native-svg": "*",
|
||||||
"yoshiki": "*"
|
"yoshiki": "*"
|
||||||
@ -35,6 +36,9 @@
|
|||||||
"react-native-blurhash": {
|
"react-native-blurhash": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"react-native-fast-image": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"react-native-web": {
|
"react-native-web": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@ -45,6 +49,8 @@
|
|||||||
"solito": "^4.0.1"
|
"solito": "^4.0.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"blurhash": "^2.0.5"
|
"blurhash": "^2.0.5",
|
||||||
|
"react-native-blurhash": "^1.1.11",
|
||||||
|
"react-native-fast-image": "^8.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ export const Avatar = forwardRef<
|
|||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
as?: ComponentType<{ style?: ViewStyle } & RefAttributes<View>>;
|
as?: ComponentType<{ style?: ViewStyle } & RefAttributes<View>>;
|
||||||
} & Stylable
|
} & Stylable
|
||||||
>(function _Avatar(
|
>(function Avatar(
|
||||||
{ src, alt, size = px(24), color, placeholder, isLoading = false, fill = false, as, ...props },
|
{ src, alt, size = px(24), color, placeholder, isLoading = false, fill = false, as, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
|
@ -19,10 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { KyooImage } from "@kyoo/models";
|
import { KyooImage } from "@kyoo/models";
|
||||||
import {
|
import { ReactElement } from "react";
|
||||||
Image as Img,
|
import { ImageStyle } from "react-native";
|
||||||
ImageStyle,
|
|
||||||
} from "react-native";
|
|
||||||
import { YoshikiStyle } from "yoshiki/src/type";
|
import { YoshikiStyle } from "yoshiki/src/type";
|
||||||
|
|
||||||
export type YoshikiEnhanced<Style> = Style extends any
|
export type YoshikiEnhanced<Style> = Style extends any
|
||||||
@ -31,12 +29,13 @@ export type YoshikiEnhanced<Style> = Style extends any
|
|||||||
}
|
}
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export type WithLoading<T> = (T & { isLoading?: boolean }) | (Partial<T> & { isLoading: true });
|
type WithLoading<T> = (T & { isLoading?: false }) | (Partial<T> & { isLoading: true });
|
||||||
|
|
||||||
export type Props = WithLoading<{
|
export type Props = WithLoading<{
|
||||||
src?: KyooImage | null;
|
src?: KyooImage | null;
|
||||||
alt: string;
|
|
||||||
quality: "low" | "medium" | "high";
|
quality: "low" | "medium" | "high";
|
||||||
|
alt: string;
|
||||||
|
Error?: ReactElement | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ImageLayout = YoshikiEnhanced<
|
export type ImageLayout = YoshikiEnhanced<
|
||||||
|
@ -19,9 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ImageProps, ImageStyle, Platform, View, ViewStyle } from "react-native";
|
import { FlexStyle, ImageStyle, View, ViewStyle } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import FastImage from "react-native-fast-image";
|
||||||
import { YoshikiEnhanced, WithLoading, Props, ImageLayout } from "./base-image";
|
import { Blurhash } from "react-native-blurhash";
|
||||||
|
import { percent, useYoshiki } from "yoshiki/native";
|
||||||
|
import { Props, ImageLayout } from "./base-image";
|
||||||
import { Skeleton } from "../skeleton";
|
import { Skeleton } from "../skeleton";
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
@ -30,6 +32,7 @@ export const Image = ({
|
|||||||
alt,
|
alt,
|
||||||
isLoading: forcedLoading = false,
|
isLoading: forcedLoading = false,
|
||||||
layout,
|
layout,
|
||||||
|
Error,
|
||||||
...props
|
...props
|
||||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
@ -45,41 +48,37 @@ export const Image = ({
|
|||||||
setOldSource(src);
|
setOldSource(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
const border = { borderRadius: 6 } 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 || state === "errored") {
|
||||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
return Error !== undefined ? (
|
||||||
|
Error
|
||||||
const nativeProps = Platform.select<Partial<ImageProps>>({
|
) : (
|
||||||
web: {
|
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||||
defaultSource: typeof src === "string" ? { uri: src } : Array.isArray(src) ? src[0] : src,
|
);
|
||||||
},
|
}
|
||||||
default: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...css(layout)}>
|
<View {...css([layout, border], props)}>
|
||||||
<Blurhash src={src.high} blurhash={src.blurhash} />
|
{state !== "finished" && (
|
||||||
|
<Blurhash
|
||||||
|
blurhash={src.blurhash}
|
||||||
|
resizeMode="cover"
|
||||||
|
{...css({ width: percent(100), height: percent(100) })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FastImage
|
||||||
|
source={{ uri: src[quality ?? "high"] }}
|
||||||
|
accessibilityLabel={alt}
|
||||||
|
onLoad={() => setState("finished")}
|
||||||
|
onError={() => setState("errored")}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
{...(css({
|
||||||
|
width: percent(100),
|
||||||
|
height: percent(100),
|
||||||
|
}) as { style: FlexStyle })}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Skeleton variant="custom" show={state === "loading"} {...css([layout, border], props)}>
|
|
||||||
// <Img
|
|
||||||
// source={{ uri: src[quality || "high"] }}
|
|
||||||
// accessibilityLabel={alt}
|
|
||||||
// onLoad={() => setState("finished")}
|
|
||||||
// onError={() => setState("errored")}
|
|
||||||
// {...nativeProps}
|
|
||||||
// {...css([
|
|
||||||
// {
|
|
||||||
// width: percent(100),
|
|
||||||
// height: percent(100),
|
|
||||||
// resizeMode: "cover",
|
|
||||||
// },
|
|
||||||
// ])}
|
|
||||||
// />
|
|
||||||
// </Skeleton>
|
|
||||||
// );
|
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
import { useLayoutEffect, useState } from "react";
|
import { useLayoutEffect, useState } from "react";
|
||||||
import { ImageStyle, View, ViewStyle } from "react-native";
|
import { ImageStyle, View, ViewStyle } from "react-native";
|
||||||
import { StyleList, processStyleList } from "yoshiki/src/type";
|
import { StyleList, processStyleList } from "yoshiki/src/type";
|
||||||
|
import { useYoshiki as useWebYoshiki } from "yoshiki/web";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
import { Props, ImageLayout } from "./base-image";
|
import { Props, ImageLayout } from "./base-image";
|
||||||
import { blurHashToDataURL } from "./blurhash-web";
|
import { blurHashToDataURL } from "./blurhash-web";
|
||||||
@ -41,9 +42,11 @@ export const Image = ({
|
|||||||
alt,
|
alt,
|
||||||
isLoading: forcedLoading = false,
|
isLoading: forcedLoading = false,
|
||||||
layout,
|
layout,
|
||||||
|
Error,
|
||||||
...props
|
...props
|
||||||
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
}: Props & { style?: ImageStyle } & { layout: ImageLayout }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
const { css: wCss } = useWebYoshiki();
|
||||||
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
const [state, setState] = useState<"loading" | "errored" | "finished">(
|
||||||
src ? "finished" : "errored",
|
src ? "finished" : "errored",
|
||||||
);
|
);
|
||||||
@ -55,8 +58,13 @@ export const Image = ({
|
|||||||
const border = { borderRadius: 6 } satisfies ViewStyle;
|
const border = { borderRadius: 6 } 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 || state === "errored") {
|
||||||
return <View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />;
|
return Error !== undefined ? (
|
||||||
|
Error
|
||||||
|
) : (
|
||||||
|
<View {...css([{ bg: (theme) => theme.overlay0 }, layout, border], props)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const blurhash = blurHashToDataURL(src.blurhash);
|
const blurhash = blurHashToDataURL(src.blurhash);
|
||||||
return (
|
return (
|
||||||
@ -79,10 +87,9 @@ export const Image = ({
|
|||||||
width: (layout as any).width,
|
width: (layout as any).width,
|
||||||
height: (layout as any).height,
|
height: (layout as any).height,
|
||||||
aspectRatio: (layout as any).aspectRatio,
|
aspectRatio: (layout as any).aspectRatio,
|
||||||
...border,
|
|
||||||
}}
|
}}
|
||||||
// Gather classnames from props (to support parent's hover for example).
|
// Gather classnames from props (to support parent's hover for example).
|
||||||
className={extractClassNames(props)}
|
{...wCss({ ...border, borderRadius: "6px" }, { className: extractClassNames(props) })}
|
||||||
>
|
>
|
||||||
<NextImage
|
<NextImage
|
||||||
src={src[quality ?? "high"]}
|
src={src[quality ?? "high"]}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageProps, ImageStyle, Platform, View, ViewProps, ViewStyle } from "react-native";
|
import { ImageProps, ImageStyle, Platform, View, ViewProps, ViewStyle } from "react-native";
|
||||||
import { Props, ImageLayout, YoshikiEnhanced } from "./base-image";
|
import { Props, YoshikiEnhanced } from "./base-image";
|
||||||
import { Image } from "./image";
|
import { Image } from "./image";
|
||||||
import { ComponentType, ReactNode, useState } from "react";
|
import { ComponentType, ReactNode, useState } from "react";
|
||||||
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
import { LinearGradient, LinearGradientProps } from "expo-linear-gradient";
|
||||||
@ -36,12 +36,18 @@ export const Poster = ({
|
|||||||
}: Props & { style?: ImageStyle } & {
|
}: Props & { style?: ImageStyle } & {
|
||||||
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
layout: YoshikiEnhanced<{ width: ImageStyle["width"] } | { height: ImageStyle["height"] }>;
|
||||||
}) => (
|
}) => (
|
||||||
<Image isLoading={isLoading} alt={alt} layout={{ aspectRatio: 2 / 3, ...layout }} {...props} />
|
<Image
|
||||||
|
isLoading={isLoading as any}
|
||||||
|
alt={alt!}
|
||||||
|
layout={{ aspectRatio: 2 / 3, ...layout }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ImageBackground = <AsProps = ViewProps,>({
|
export const ImageBackground = <AsProps = ViewProps,>({
|
||||||
src,
|
src,
|
||||||
alt,
|
alt,
|
||||||
|
quality,
|
||||||
gradient = true,
|
gradient = true,
|
||||||
as,
|
as,
|
||||||
children,
|
children,
|
||||||
@ -57,14 +63,6 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
|||||||
imageStyle?: YoshikiEnhanced<ImageStyle>;
|
imageStyle?: YoshikiEnhanced<ImageStyle>;
|
||||||
} & AsProps &
|
} & AsProps &
|
||||||
Props) => {
|
Props) => {
|
||||||
const [isErrored, setErrored] = useState(false);
|
|
||||||
|
|
||||||
const nativeProps = Platform.select<Partial<ImageProps>>({
|
|
||||||
web: {
|
|
||||||
defaultSource: typeof src === "string" ? { uri: src! } : Array.isArray(src) ? src[0] : src!,
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
});
|
|
||||||
const Container = as ?? View;
|
const Container = as ?? View;
|
||||||
return (
|
return (
|
||||||
<ContrastArea contrastText>
|
<ContrastArea contrastText>
|
||||||
@ -84,16 +82,14 @@ export const ImageBackground = <AsProps = ViewProps,>({
|
|||||||
containerStyle,
|
containerStyle,
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
{src && !isErrored && (
|
{src && (
|
||||||
<Image
|
<Image
|
||||||
source={typeof src === "string" ? { uri: src } : src}
|
src={src}
|
||||||
accessibilityLabel={alt}
|
quality={quality}
|
||||||
onError={() => setErrored(true)}
|
alt={alt!}
|
||||||
{...nativeProps}
|
layout={{ width: percent(100), height: percent(100) }}
|
||||||
{...css([
|
Error={null}
|
||||||
{ width: percent(100), height: percent(100), resizeMode: "cover" },
|
{...(css([{ borderWidth: 0, borderRadius: 0 }, imageStyle]) as ImageProps)}
|
||||||
imageStyle,
|
|
||||||
])}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{gradient && (
|
{gradient && (
|
||||||
|
@ -2526,6 +2526,8 @@ __metadata:
|
|||||||
"@tanstack/react-query": ^4.32.6
|
"@tanstack/react-query": ^4.32.6
|
||||||
"@types/react": 18.2.0
|
"@types/react": 18.2.0
|
||||||
blurhash: ^2.0.5
|
blurhash: ^2.0.5
|
||||||
|
react-native-blurhash: ^1.1.11
|
||||||
|
react-native-fast-image: ^8.6.3
|
||||||
solito: ^4.0.1
|
solito: ^4.0.1
|
||||||
typescript: ^5.1.6
|
typescript: ^5.1.6
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2538,12 +2540,17 @@ __metadata:
|
|||||||
react: "*"
|
react: "*"
|
||||||
react-native: "*"
|
react-native: "*"
|
||||||
react-native-blurhash: "*"
|
react-native-blurhash: "*"
|
||||||
|
react-native-fast-image: "*"
|
||||||
react-native-reanimated: "*"
|
react-native-reanimated: "*"
|
||||||
react-native-svg: "*"
|
react-native-svg: "*"
|
||||||
yoshiki: "*"
|
yoshiki: "*"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
blurhash:
|
blurhash:
|
||||||
optional: true
|
optional: true
|
||||||
|
react-native-blurhash:
|
||||||
|
optional: true
|
||||||
|
react-native-fast-image:
|
||||||
|
optional: true
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@gorhom/portal":
|
"@gorhom/portal":
|
||||||
optional: true
|
optional: true
|
||||||
@ -2553,6 +2560,8 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
react-native-blurhash:
|
react-native-blurhash:
|
||||||
optional: true
|
optional: true
|
||||||
|
react-native-fast-image:
|
||||||
|
optional: true
|
||||||
react-native-web:
|
react-native-web:
|
||||||
optional: true
|
optional: true
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
@ -10565,6 +10574,7 @@ __metadata:
|
|||||||
react-i18next: ^13.0.3
|
react-i18next: ^13.0.3
|
||||||
react-native: 0.72.3
|
react-native: 0.72.3
|
||||||
react-native-blurhash: ^1.1.11
|
react-native-blurhash: ^1.1.11
|
||||||
|
react-native-fast-image: ^8.6.3
|
||||||
react-native-mmkv: ^2.10.1
|
react-native-mmkv: ^2.10.1
|
||||||
react-native-reanimated: ~3.3.0
|
react-native-reanimated: ~3.3.0
|
||||||
react-native-safe-area-context: 4.6.3
|
react-native-safe-area-context: 4.6.3
|
||||||
@ -11882,6 +11892,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-native-fast-image@npm:^8.6.3":
|
||||||
|
version: 8.6.3
|
||||||
|
resolution: "react-native-fast-image@npm:8.6.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^17 || ^18
|
||||||
|
react-native: ">=0.60.0"
|
||||||
|
checksum: 29289cb6b2eae0983c8922b22e2d9de3be07322bb7991c5def19f95eadefaedb0e308ff0b38cc1d0444e8bd4fe94a7621a99a2d3d9298100bcb60b3144677234
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-native-mmkv@npm:^2.10.1":
|
"react-native-mmkv@npm:^2.10.1":
|
||||||
version: 2.10.1
|
version: 2.10.1
|
||||||
resolution: "react-native-mmkv@npm:2.10.1"
|
resolution: "react-native-mmkv@npm:2.10.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user