Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support expo image #968

Merged
merged 15 commits into from
Dec 4, 2024
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import ThemeExample from "./ThemeExample";
import LoadingIndicatorExample from "./LoadingIndicatorExample";
import TimerExample from "./TimerExample";
import LottieAnimationExample from "./LottieAnimationExample";
import ImageExample from "./ImageExample";

const ROUTES = {
LottieAnimationExample: LottieAnimationExample,
Expand Down Expand Up @@ -120,6 +121,7 @@ const ROUTES = {
VideoPlayer: VideoPlayerExample,
PinInput: PinInputExample,
KeyboardAvoidingView: KeyboardAvoidingViewExample,
Image: ImageExample,
};

let customFonts = {
Expand Down
133 changes: 133 additions & 0 deletions example/src/ImageExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as React from "react";
import { View, StyleSheet, Text } from "react-native";
import { Image, withTheme } from "@draftbit/ui";
import Section, { Container } from "./Section";

interface WrapperProps {
label: string;
children: React.ReactNode;
}

const Wrapper: React.FC<WrapperProps> = ({ label, children }) => {
return (
<View style={styles.wrapper}>
<View style={styles.boxLabel}>
<Text>{label}</Text>
</View>
<View>{children}</View>
</View>
);
};

const ImageExample: React.FC = () => {
return (
<Container style={{}}>
<Section title="Image Examples" style={{}}>
<View style={{ flexDirection: "row" }}>
<Wrapper label="Basic remote image">
<Image
source={{
uri: "https://picsum.photos/1100",
}}
style={{
width: 200,
height: 200,
}}
/>
</Wrapper>
<Wrapper label="Local image">
<Image
source={require("./assets/images/hamburger.png")}
style={{
width: 200,
height: 200,
}}
/>
</Wrapper>
</View>
<View style={{ flexDirection: "row" }}>
<Wrapper label="With aspectRatio">
<Image
source={{
uri: "https://picsum.photos/1200",
}}
style={{
width: 300,
aspectRatio: 16 / 9,
}}
/>
</Wrapper>
</View>
<View style={{ flexDirection: "row" }}>
<Wrapper label="Content fit: contain">
<Image
source={{
uri: "https://picsum.photos/1300",
}}
contentFit="contain"
style={{
width: 200,
height: 200,
backgroundColor: "#f0f0f0",
}}
/>
</Wrapper>
<Wrapper label="With blur hash">
<Image
source={{
uri: "https://picsum.photos/1400",
}}
placeholder={{
blurhash: "L6PZfSi_.AyE_3t7t7R**0o#DgR4",
}}
transition={1000}
style={{
width: 200,
height: 200,
}}
/>
</Wrapper>
</View>
</Section>
<Section title="SVG Image" style={styles.wrapper}>
<Wrapper label="Remote SVG Image">
<Image
source={{
uri: "https://upload.wikimedia.org/wikipedia/commons/3/30/Vector-based_example.svg",
}}
style={{
width: 200,
height: 200,
}}
/>
</Wrapper>
<Wrapper label="Local SVG Image">
<Image
source={require("./assets/images/example.svg")}
style={{
width: 200,
height: 200,
}}
/>
</Wrapper>
</Section>
</Container>
);
};

const styles = StyleSheet.create({
wrapper: {
flex: 1,
display: "flex",
flexDirection: "column",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
},
boxLabel: {
margin: 10,
flex: 1,
},
});

export default withTheme(ImageExample);
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"date-fns": "^2.16.1",
"dateformat": "^3.0.3",
"expo-av": "~13.10.6",
"expo-image": "1.10.6",
"lodash.isequal": "^4.5.0",
"lodash.isnumber": "^3.0.3",
"lodash.omit": "^4.5.0",
Expand Down
85 changes: 73 additions & 12 deletions packages/core/src/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/* README: Internal Image component used for stuff like Card. DO NOT EXPORT! */
import React from "react";
import { StyleSheet, ImageSourcePropType, DimensionValue } from "react-native";
import {
Image as NativeImage,
ImageProps,
StyleSheet,
ImageSourcePropType,
DimensionValue,
} from "react-native";
Image as ExpoImage,
ImageContentPosition,
ImageProps as ExpoImageProps,
ImageContentFit,
} from "expo-image";
import Config from "./Config";

import AspectRatio from "./AspectRatio";

type ImageStyleProp = {
Expand All @@ -17,6 +16,32 @@ type ImageStyleProp = {
aspectRatio?: number;
};

interface ExtendedImageProps extends ExpoImageProps {
placeholder?: {
blurhash?: string;
thumbhash?: string;
};
transition?:
| number
| {
duration?: number;
effect?:
| "cross-dissolve"
| "flip-from-top"
| "flip-from-right"
| "flip-from-bottom"
| "flip-from-left"
| "curl-up"
| "curl-down";
timing?: "ease-in-out" | "ease-in" | "ease-out" | "linear";
};
contentFit?: "cover" | "contain" | "fill" | "none" | "scale-down";
contentPosition?: ImageContentPosition;
cachePolicy?: "none" | "disk" | "memory" | "memory-disk";
allowDownscaling?: boolean;
recyclingKey?: string;
}

const generateDimensions = ({
aspectRatio,
width,
Expand Down Expand Up @@ -52,10 +77,30 @@ const generateDimensions = ({
return { width, height };
};

const Image: React.FC<ImageProps> = ({
const resizeModeToContentFit = (
resizeMode: "cover" | "contain" | "stretch" | "repeat" | "center"
): ImageContentFit => {
const mapping: Record<typeof resizeMode, ImageContentFit> = {
cover: "cover",
contain: "contain",
stretch: "fill",
repeat: "none",
center: "scale-down",
} as const;
return mapping[resizeMode] ?? "cover";
draftbitbuildservices marked this conversation as resolved.
Show resolved Hide resolved
};

const Image: React.FC<ExtendedImageProps> = ({
source,
resizeMode = "cover",
style,
placeholder,
transition = 300,
contentFit = "cover",
contentPosition = "center",
cachePolicy = "memory-disk",
allowDownscaling = true,
recyclingKey,
...props
}) => {
let imageSource =
Expand All @@ -68,13 +113,23 @@ const Image: React.FC<ImageProps> = ({
styles as ImageStyleProp
draftbitbuildservices marked this conversation as resolved.
Show resolved Hide resolved
);

const finalContentFit = resizeMode
? resizeModeToContentFit(resizeMode)
: contentFit;

if (aspectRatio) {
return (
<AspectRatio style={[style, { width, height, aspectRatio }]}>
<NativeImage
<ExpoImage
{...props}
source={imageSource as ImageSourcePropType}
resizeMode={resizeMode}
contentFit={finalContentFit}
placeholder={placeholder}
transition={transition}
contentPosition={contentPosition}
cachePolicy={cachePolicy}
allowDownscaling={allowDownscaling}
recyclingKey={recyclingKey}
style={[
style,
{
Expand All @@ -88,10 +143,16 @@ const Image: React.FC<ImageProps> = ({
}

return (
<NativeImage
<ExpoImage
{...props}
source={source as ImageSourcePropType}
resizeMode={resizeMode}
contentFit={finalContentFit}
placeholder={placeholder}
transition={transition}
contentPosition={contentPosition}
cachePolicy={cachePolicy}
allowDownscaling={allowDownscaling}
recyclingKey={recyclingKey}
style={style}
/>
);
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export {
LoadingIndicator,
LottieAnimation,
Timer,
Image,
} from "@draftbit/core";

export {
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7529,6 +7529,13 @@ expo-font@~11.10.3:
dependencies:
fontfaceobserver "^2.1.0"

[email protected]:
version "1.10.6"
resolved "https://registry.yarnpkg.com/expo-image/-/expo-image-1.10.6.tgz#b0e54d31d97742505296c076a5f18d094ba9a8cc"
integrity sha512-vcnAIym1eU8vQgV1re1E7rVQZStJimBa4aPDhjFfzMzbddAF7heJuagyewiUkTzbZUwYzPaZAie6VJPyWx9Ueg==
dependencies:
"@react-native/assets-registry" "~0.73.1"

expo-keep-awake@~12.8.2:
version "12.8.2"
resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz#6cfdf8ad02b5fa130f99d4a1eb98e459d5b4332e"
Expand Down
Loading