From 4a3a2e496b80744cef04a785ba2947ff051f450b Mon Sep 17 00:00:00 2001 From: codinsonn Date: Sun, 7 Apr 2024 23:31:05 +0200 Subject: [PATCH] feat: Add Universal Image component --- apps/expo/app/(public)/images/index.tsx | 3 + apps/expo/package.json | 3 +- apps/next/app/(public)/images/page.tsx | 4 + apps/next/next.config.js | 8 + features/app-core/assets/aetherspaceLogo.png | Bin 0 -> 54421 bytes features/app-core/components/Image.tsx | 90 ++++++++ features/app-core/components/Image.types.tsx | 218 +++++++++++++++++++ features/app-core/components/Image.web.tsx | 78 +++++++ features/app-core/screens/HomeScreen.tsx | 5 +- features/app-core/screens/ImagesScreen.tsx | 67 ++++++ package-lock.json | 12 + 11 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 apps/expo/app/(public)/images/index.tsx create mode 100644 apps/next/app/(public)/images/page.tsx create mode 100644 features/app-core/assets/aetherspaceLogo.png create mode 100644 features/app-core/components/Image.tsx create mode 100644 features/app-core/components/Image.types.tsx create mode 100644 features/app-core/components/Image.web.tsx create mode 100644 features/app-core/screens/ImagesScreen.tsx diff --git a/apps/expo/app/(public)/images/index.tsx b/apps/expo/app/(public)/images/index.tsx new file mode 100644 index 0000000..bbf362b --- /dev/null +++ b/apps/expo/app/(public)/images/index.tsx @@ -0,0 +1,3 @@ +import ImagesScreen from '@app/core/screens/ImagesScreen' + +export default ImagesScreen diff --git a/apps/expo/package.json b/apps/expo/package.json index 8c87c54..5efa52d 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -15,7 +15,8 @@ "react-native": "0.73.2", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", - "react-native-web": "~0.19.6" + "react-native-web": "~0.19.6", + "expo-image": "~1.10.6" }, "devDependencies": { "@babel/core": "^7.19.3", diff --git a/apps/next/app/(public)/images/page.tsx b/apps/next/app/(public)/images/page.tsx new file mode 100644 index 0000000..2af31ea --- /dev/null +++ b/apps/next/app/(public)/images/page.tsx @@ -0,0 +1,4 @@ +'use client' +import ImagesScreen from '@app/core/screens/ImagesScreen' + +export default ImagesScreen diff --git a/apps/next/next.config.js b/apps/next/next.config.js index 38c2cb7..ec00977 100644 --- a/apps/next/next.config.js +++ b/apps/next/next.config.js @@ -13,6 +13,14 @@ const nextConfig = withExpo({ experimental: { forceSwcTransforms: true, }, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "codinsonn.dev", + } + ] + } }); module.exports = nextConfig; diff --git a/features/app-core/assets/aetherspaceLogo.png b/features/app-core/assets/aetherspaceLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd198920a825070bd502d6f3714b2cd4ecacc16 GIT binary patch literal 54421 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�{KX@WeZVTHcB$zkpn{!p0n|-KMZQ455Db z0+HLbHuu!>?7Bt@=%oWJb4;BXVgswCFHpOHYDgm9j}sO)zYVDWDRCN^I&Hph7mkCh z^1$3xDW*-v91@hlXEtF*Wq_WjDh=pbjzg>(%jC;zg53=AdkE0}_>}z67mnULVSaR^ ziRm%035T!3*T`k8qlv^;=8^dJo3k&op`)@~t5-4XVBeHES%nTImBlM0kGbR{F7Ch- z<7dnc%vYQSE}SkRg2q16&Dh9n3^?VV^~n>#U#KzGGZi}+nv#j?^tRP^_l{-yYX3gD zo+1Yirx_yKXvkR%oxCPk0Bm9KZ)Y3%y3Y|QMcJ^k2n@@poWO^@pp^#?%Hss4R8^%+b{1hvl>)xYJN;{mjarZmmeFPD!X~Dg)e4us zIt^@SgOiO50gN_6%ZRq#%?6?2no>ZNAKX1&V&2u!(WKV9g^Ow_zyq`z$cUW8`5`n) z$aw>nwh|SJaQgNQk%Wn^%}-L|F#GpARC{2zijl?s&hIrWcSsq}x=+MV$frE{nSK-p z7d~ZFJfD8AK+NmL`d@kyB34So6h!oW^b%S^W}Y#yEeZvTez*bBP_fc#?#gP>aqd~Z z&`sT_#UcoBW!@lw9{-%CTsQ$=$MJ(@>0u45#pnj{#dVAAvM1yE)_oF&LN2xTZY;iE zJs16}TvY z6?`U2Nvuh?*=|FVdKa$t3qcvCS-sig-YWs-?wEt~Wve$2KgXlAw+{e_ejCLuR7GuN z`5$RNk>yii9|df!5|7>~%ktV}HIi?#CR&?Kq09gC#5ioZTTcV_k9_18-fax6jWW5) z(*G66x@V8IO249`ALx^sIjIt?qw;eWIf23qJ&1G5h$w zajxj`Ys36XBLR9W>4ANimXVI4TFgi+CB%diNk*c#{pDuMR`oOZb^3m z?!O2`?#P8J3#_)k;+6G>q>)iL*;n(I7ZRTJcEEn16rwlCi#TxnsDdWJkq*-ve%+x3 z;x!zr;1A(Y0Mr -------------------------------------------------------------------------------- */ + +const Image = (props: UniversalImageProps): JSX.Element => { + // Props + const { + /* - Universal - */ + src, + alt, + width, + height, + style, + priority, + onError, + onLoadEnd, + /* - Split - */ + expoPlaceholder, + /* - Next.js - */ + onLoad, + fill, + /* - Expo - */ + accessibilityLabel, + accessible, + allowDownscaling, + autoplay, + blurRadius, + cachePolicy, + contentFit, + contentPosition, + enableLiveTextInteraction, + focusable, + onLoadStart, + onProgress, + placeholderContentFit, + recyclingKey, + responsivePolicy, + } = props + + // -- Overrides -- + + // @ts-ignore + const finalStyle = { width, height, ...style } + if (fill) finalStyle.height = '100%' + if (fill) finalStyle.width = '100%' + + // -- Render -- + + return ( + + ) +} + +/* --- Static Methods -------------------------------------------------------------------------- */ + +Image.clearDiskCache = ExpoImage.clearDiskCache as UniversalImageMethods['clearDiskCache'] +Image.clearMemoryCache = ExpoImage.clearMemoryCache as UniversalImageMethods['clearMemoryCache'] +Image.getCachePathAsync = ExpoImage.getCachePathAsync as UniversalImageMethods['getCachePathAsync'] +Image.prefetch = ExpoImage.prefetch as UniversalImageMethods['prefetch'] + +/* --- Exports --------------------------------------------------------------------------------- */ + +export { Image } diff --git a/features/app-core/components/Image.types.tsx b/features/app-core/components/Image.types.tsx new file mode 100644 index 0000000..e4a3ce6 --- /dev/null +++ b/features/app-core/components/Image.types.tsx @@ -0,0 +1,218 @@ +import type { ImageProps as ExpoImageProps, Image as ExpoImage } from 'expo-image' +import type { ImageProps as NextImageProps } from 'next/image' + +/* --- Types ----------------------------------------------------------------------------------- */ + +export type UniversalImageProps = { + /** + * Universal, will affect both Expo & Next.js - Must be one of the following: + * - A path string like `'/assets/logo.png'`. This can be either an absolute external URL, or an internal path depending on the loader prop. + * - A statically imported image file, like `import logo from './logo.png'` or `require('./logo.png')`. + * + * When using an external URL, you must add it to `remotePatterns` in `next.config.js`. + * @platform web, android, ios @framework expo, next.js */ + src: NextImageProps['src'] | ExpoImageProps['source'] + + /** + * Universal, will affect both Expo & Next.js + * - Next.js - Used to describe the image for screen readers and search engines. It is also the fallback text if images have been disabled or an error occurs while loading the image. It should contain text that could replace the image without changing the meaning of the page. It is not meant to supplement the image and should not repeat information that is already provided in the captions above or below the image. If the image is purely decorative or not intended for the user, the alt property should be an empty string (`alt=""`). + * - Expo - The text that's read by the screen reader when the user interacts with the image. Sets the the alt tag on web which is used for web crawlers and link traversal. Is an alias for `accessibilityLabel`. + * @alias accessibilityLabel + */ + alt?: NextImageProps['alt'] | ExpoImageProps['alt'] + + /** Universal, will affect both Expo & Next.js + * - Expo - Priorities for completing loads. If more than one load is queued at a time, the load with the higher priority will be started first. Priorities are considered best effort, there are no guarantees about the order in which loads will start or finish. + * - Next.js - Lazy loading is automatically disabled for images using `'high'` priority. You should use the priority property on any image detected as the Largest Contentful Paint (LCP) element. It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes. Should only be used when the image is visible above the fold. + * @default 'normal' + */ + priority?: ExpoImageProps['priority'] + + /** Rendered width in pixels, will affect how large the image appears. Required, except for statically imported images or images with the `fill` property. */ + width?: NextImageProps['width'] + + /** Rendered height in pixels, will affect how large the image appears. Required, except for statically imported images or images with the `fill` property. @default 'auto' */ + height?: NextImageProps['height'] + + /** Universal, will affect both Expo & Next.js + * - Remember that the required width and height props can interact with your styling. If you use styling to modify an image's width, you should also style its height to auto to preserve its intrinsic aspect ratio, or your image will be distorted. */ + style?: ExpoImageProps['style'] + + /** Universal, will affect both Expo & Next.js - Called on an image fetching error. */ + onError?: ExpoImageProps['onError'] + + /** Universal, will affect both Expo & Next.js - Called when the image load either succeeds or fails. */ + onLoadEnd?: ExpoImageProps['onLoadEnd'] + + // - Split - + + /** + * Split, please use Expo or Next.js specific props instead: + * - `nextPlaceholder` (Next.js) - A placeholder to use while the image is loading. Possible values are `blur`, `empty`, or `data:image/...`. Defaults to empty. When `blur`, the `blurDataURL` property will be used as the placeholder. If `src` is an object from a static import and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated, except when the image is detected to be animated. For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation. When `data:image/...`, the Data URL will be used as the placeholder while the image is loading. When empty, there will be no placeholder while the image is loading, only empty space. + * - `expoPlaceholder` (Expo) - An image to display while loading the proper image and no image has been displayed yet or the source is unset. + */ + placeholder?: never + + // - Next - + + /** Used to describe the image for screen readers and search engines. It is also the fallback text if images have been disabled or an error occurs while loading the image. It should contain text that could replace the image without changing the meaning of the page. It is not meant to supplement the image and should not repeat information that is already provided in the captions above or below the image. If the image is purely decorative or not intended for the user, the alt property should be an empty string (alt=""). */ + // alt?: NextImageProps['alt'] + + /** Custom function used to resolve image URLs. A loader is a function returning a URL string for the image, given the following parameters: `src`, `width`, `quality` (`number` from 0 - 1) Alternatively, you can use the [loaderFile](https://nextjs.org/docs/pages/api-reference/components/image#loaderfile) configuration in next.config.js to configure every instance of next/image in your application, without passing a prop. */ + loader?: NextImageProps['loader'] + + /** + * A boolean that causes the image to fill the parent element, which is useful when the `width` and `height` are unknown. + * + * The parent element must assign `position: "relative"`, `position: "fixed"`, or `position: "absolute"` style. + * + * By default, the img element will automatically be assigned the `position: "absolute"` style. + * + * If no styles are applied to the image, the image will stretch to fit the container. + * + * You may prefer to set `object-fit: "contain"` for an image which is letterboxed to fit the container and preserve aspect ratio. + * + * Alternatively, `object-fit: "cover"` will cause the image to fill the entire container and be cropped to preserve aspect ratio. + * + * For this to look correct, the `overflow: "hidden"` style should be assigned to the parent element. + */ + fill?: NextImageProps['fill'] + + /** + * A string, which, similar to a media query, provides information about how wide the image will be at different breakpoints. The value of sizes will greatly affect performance for images using fill or which are styled to have a responsive size. + * + * The `sizes` property serves two important purposes related to image performance: + * + * - Used by the browser to determine which size of the image to download, from next/image's automatically generated srcset. When the browser chooses, it does not yet know the size of the image on the page, so it selects an image that is the same size or larger than the viewport. The sizes property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a sizes value in an image with the fill property, a default value of 100vw (full screen width) is used. + * + * - Changes the behavior of the automatically generated srcset value. If no sizes value is present, a small srcset is generated, suitable for a fixed-size image (1x/2x/etc). If sizes is defined, a large srcset is generated, suitable for a responsive image (640w/750w/etc). If the sizes property includes sizes such as 50vw, which represent a percentage of the viewport width, then the srcset is trimmed to not include any values which are too small to ever be necessary. + * + * Learn more about srcset and sizes on [web.dev](https://web.dev/learn/design/responsive-images/#sizes) or [MDN](https://developer.mozilla.org/docs/Web/HTML/Element/img#sizes) + */ + sizes?: NextImageProps['sizes'] + + /** The quality of the optimized image, an integer between `1` and `100`, where `100` is the best quality and therefore largest file size. Defaults to `75`. @default 75 */ + quality?: NextImageProps['quality'] + + /** When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority. You should use the priority property on any image detected as the Largest Contentful Paint (LCP) element. It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes. Should only be used when the image is visible above the fold. Defaults to false. @default false */ + // priority?: NextImageProps['priority'] + + /** A placeholder to use while the image is loading. Possible values are `blur`, `empty`, or `data:image/...`. Defaults to empty. When `blur`, the blurDataURL property will be used as the placeholder. If `src` is an object from a static import and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated, except when the image is detected to be animated. For dynamic images, you must provide the blurDataURL property. Solutions such as Plaiceholder can help with base64 generation. When `data:image/...`, the Data URL will be used as the placeholder while the image is loading. When empty, there will be no placeholder while the image is loading, only empty space. */ + nextPlaceholder?: NextImageProps['placeholder'] + // placeholder?: NextImageProps['placeholder'] + + /** Alias for `onLoadEnd` - A callback function that is invoked once the image is completely loaded and the `placeholder` has been removed. The callback function will be called with one argument, the Event which has a target that references the underlying element. */ + onLoad?: ExpoImageProps['onLoad'] + + /** + * Next.js only - The loading strategy to use. + * Recommendation: This property is only meant for advanced use cases. Switching an image to load with eager will normally hurt performance. We recommend using the priority property instead, which will eagerly preload the image: + * - `lazy` - defer loading the image until it reaches a calculated distance from the viewport. + * - `eager` - load the image immediately. + * @default 'lazy' + */ + loading?: NextImageProps['loading'] + + /** Next.js only - A Data URL to be used as a placeholder image before the `src` image successfully loads. Only takes effect when combined with `placeholder="blur"`. Must be a base64-encoded image. It will be enlarged and blurred, so a very small image (10px or less) is recommended. Including larger images as placeholders may harm your application performance. */ + blurDataURL?: NextImageProps['blurDataURL'] + + /** Next.js only - When true, the source image will be served as-is instead of changing quality, size, or format. Defaults to false. @default false */ + unoptimized?: NextImageProps['unoptimized'] + + // - Expo - + + /** The text that's read by the screen reader when the user interacts with the image. Sets the the `alt` tag on web which is used for web crawlers and link traversal. @default undefined */ + accessibilityLabel?: ExpoImageProps['accessibilityLabel'] + + /** When true, indicates that the view is an accessibility element. When a view is an accessibility element, it groups its children into a single selectable component. On Android, the `accessible` property will be translated into the native `isScreenReaderFocusable`, so it's only affecting the screen readers behaviour. @default false @platform android @platform ios */ + accessible?: ExpoImageProps['accessible'] + + /** Whether the image should be downscaled to match the size of the view container. Turning off this functionality could negatively impact the application's performance, particularly when working with large assets. However, it would result in smoother image resizing, and end-users would always have access to the highest possible asset quality. Downscaling is never used when the `contentFit` prop is set to `none` or `fill`. @default true */ + allowDownscaling?: ExpoImageProps['allowDownscaling'] + + /** The text that's read by the screen reader when the user interacts with the image. Sets the the alt tag on web which is used for web crawlers and link traversal. Is an alias for `accessibilityLabel`. @alias accessibilityLabel */ + // alt?: ExpoImageProps['alt'] + + /** Determines if an image should automatically begin playing if it is an animated image. @default true @platform — ios */ + autoplay?: ExpoImageProps['autoplay'] + + /** The radius of the blur in points, `0` means no blur effect. This effect is not applied to placeholders. @default 0 */ + blurRadius?: ExpoImageProps['blurRadius'] + + /** Determines whether to cache the image and where: on the disk, in the memory or both. + * + * - `'none'` - Image is not cached at all. + * - `'disk'` - Image is queried from the disk cache if exists, otherwise it's downloaded and then stored on the disk. + * - `'memory'` - Image is cached in memory. Might be useful when you render a high-resolution picture many times. Memory cache may be purged very quickly to prevent high memory usage and the risk of out of memory exceptions. + * - `'memory-disk'` - Image is cached in memory, but with a fallback to the disk cache. + * + * @default 'disk' */ + cachePolicy?: ExpoImageProps['cachePolicy'] + + /** Determines how the image should be resized to fit its container. This property tells the image to fill the container in a variety of ways; such as "preserve that aspect ratio" or "stretch up and take up as much space as possible". It mirrors the CSS `object-fit` property. + * + * - `'cover'` - The image is sized to maintain its aspect ratio while filling the container box. If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit. + * - `'contain'` - The image is scaled down or up to maintain its aspect ratio while fitting within the container box. + * - `'fill'` - The image is sized to entirely fill the container box. If necessary, the image will be stretched or squished to fit. + * - `'none'` - The image is not resized and is centered by default. When specified, the exact position can be controlled with `contentPosition` prop. + * - `'scale-down'` - The image is sized as if none or contain were specified, whichever would result in a smaller concrete image size. + * + * @default 'cover' */ + contentFit?: ExpoImageProps['contentFit'] + + /** It is used together with `contentFit` to specify how the image should be positioned with x/y coordinates inside its own container. An equivalent of the CSS `object-position` property. @default 'center' */ + contentPosition?: ExpoImageProps['contentPosition'] + + /** Enables Live Text interaction with the image. Check official Apple documentation for more details. @default false @platform ios 16.0+ */ + enableLiveTextInteraction?: ExpoImageProps['enableLiveTextInteraction'] + + /** Whether this View should be focusable with a non-touch input device and receive focus with a hardware keyboard. @default false @platform android */ + focusable?: ExpoImageProps['focusable'] + + /** An image to display while loading the proper image and no image has been displayed yet or the source is unset. */ + expoPlaceholder?: ExpoImageProps['placeholder'] + // placeholder?: ExpoImageProps['placeholder'] + + /** Expo only - Called when the image starts to load. */ + onLoadStart?: ExpoImageProps['onLoadStart'] + + /** Expo only - Called when the image is loading. Can be called multiple times before the image has finished loading. The event object provides details on how many bytes were loaded so far and what's the expected total size. */ + onProgress?: ExpoImageProps['onProgress'] + + /** Determines how the placeholder should be resized to fit its container. Available resize modes are the same as for the `contentFit` prop. @default 'scale-down' */ + placeholderContentFit?: ExpoImageProps['placeholderContentFit'] + + /** Priorities for completing loads. If more than one load is queued at a time, the load with the higher priority will be started first. Priorities are considered best effort, there are no guarantees about the order in which loads will start or finish. @default 'normal' */ + // priority?: ExpoImageProps['priority'] + + /** Changing this prop resets the image view content to blank or a placeholder before loading and rendering the final image. This is especially useful for any kinds of recycling views like FlashList to prevent showing the previous source before the new one fully loads. @default null @platform android @platform ios */ + recyclingKey?: ExpoImageProps['recyclingKey'] + + /** Controls the selection of the image source based on the container or viewport size on the web. + * - `'static'` - the browser selects the correct source based on user's viewport width. Works with static rendering. Make sure to set the `'webMaxViewportWidth'` property on each source for best results. For example, if an image occupies 1/3 of the screen width, set the `'webMaxViewportWidth'` to 3x the image width. The source with the largest `'webMaxViewportWidth'` is used even for larger viewports. + * - `'initial'` - the component will select the correct source during mount based on container size. Does not work with static rendering. + * - `'live'` - the component will select the correct source on every resize based on container size. Does not work with static rendering. + * @default 'static' + * @platform web */ + responsivePolicy?: ExpoImageProps['responsivePolicy'] + + /** Never: Use `src` instead - The image source, either a remote URL, a local file resource or a number that is the result of the require() function. When provided as an array of sources, the source that fits best into the container size and is closest to the screen scale will be chosen. In this case it is important to provide `width`, `height` and `scale` properties. @framework none */ + source?: never + // source?: ExpoImageProps['source'] +} + +export type UniversalImageMethods = { + // - Expo - + + /** Asynchronously clears all images from the disk cache. @platform android @platform ios @return A promise resolving to true when the operation succeeds. It may resolve to false on Android when the activity is no longer available. Resolves to `false` on Web. */ + clearDiskCache: typeof ExpoImage.clearDiskCache + + /** Asynchronously clears all images stored in memory. @platform android @platform ios @return A promise resolving to true when the operation succeeds. It may resolve to false on Android when the activity is no longer available. Resolves to `false` on Web. */ + clearMemoryCache: typeof ExpoImage.clearMemoryCache + + /** Asynchronously checks if an image exists in the disk cache and resolves to the path of the cached image if it does. @param cacheKey The cache key for the requested image. Unless you have set a custom cache key, this will be the source URL of the image. @platform android @platform ios @return A promise resolving to the path of the cached image. It will resolve to `null` if the image does not exist in the cache. */ + getCachePathAsync: typeof ExpoImage.getCachePathAsync + + /** Preloads images at the given URLs that can be later used in the image view. Preloaded images are cached to the memory and disk by default, so make sure to use disk (default) or memory-disk cache policy. @param urls A URL string or an array of URLs of images to prefetch. @param cachePolicy The cache policy for prefetched images. @return A promise resolving to true as soon as all images have been successfully prefetched. If an image fails to be prefetched, the promise will immediately resolve to false regardless of whether other images have finished prefetching. */ + prefetch: typeof ExpoImage.prefetch +} diff --git a/features/app-core/components/Image.web.tsx b/features/app-core/components/Image.web.tsx new file mode 100644 index 0000000..f37b907 --- /dev/null +++ b/features/app-core/components/Image.web.tsx @@ -0,0 +1,78 @@ +import NextImage from 'next/image' +import { UniversalImageProps, UniversalImageMethods } from './Image.types' + +/* --- -------------------------------------------------------------------------------- */ + +const Image = (props: UniversalImageProps): JSX.Element => { + // Props + const { + /* - Universal - */ + src, + alt, + width, + height, + style = {}, + priority = 'normal', + onError, + onLoadEnd, + /* - Split - */ + nextPlaceholder, + /* - Next.js - */ + loader, + fill, + sizes, + quality, + onLoad, + loading, + blurDataURL, + unoptimized, + /* - Expo - */ + accessibilityLabel, + contentFit, + } = props + + // -- Overrides -- + + // @ts-ignore + const finalStyle = { width, height, ...style } + if (fill) finalStyle.height = '100%' + if (fill) finalStyle.width = '100%' + if (fill) finalStyle.objectFit = contentFit || 'cover' + + // -- Render -- + + return ( + + ) +} + +/* --- Static Methods -------------------------------------------------------------------------- */ + +Image.clearDiskCache = (() => {}) as UniversalImageMethods['clearDiskCache'] +Image.clearMemoryCache = (() => {}) as UniversalImageMethods['clearMemoryCache'] +Image.getCachePathAsync = ((cacheKey: string) => {}) as UniversalImageMethods['getCachePathAsync'] // prettier-ignore +Image.prefetch = ((urls: string | string[], cachePolicy?: "memory" | "memory-disk") => {}) as UniversalImageMethods['prefetch'] // prettier-ignore + +/* --- Exports --------------------------------------------------------------------------------- */ + +export { Image } diff --git a/features/app-core/screens/HomeScreen.tsx b/features/app-core/screens/HomeScreen.tsx index fa3e451..373c185 100644 --- a/features/app-core/screens/HomeScreen.tsx +++ b/features/app-core/screens/HomeScreen.tsx @@ -1,15 +1,18 @@ import React from 'react' import { StyleSheet, Text, View } from 'react-native' import { Link } from '../navigation/Link' +import { Image } from '../components/Image' /* --- --------------------------------------------------------------------------- */ const HomeScreen = () => { return ( - Expo + Next.js app routing 👋 + + Expo + Next.js app routing 🚀 Open HomeScreen.tsx in features/app-core/screens to start working on your app Test navigation + Test images ) } diff --git a/features/app-core/screens/ImagesScreen.tsx b/features/app-core/screens/ImagesScreen.tsx new file mode 100644 index 0000000..81bd68e --- /dev/null +++ b/features/app-core/screens/ImagesScreen.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import { StyleSheet, Text, View } from 'react-native' +import { Link } from '../navigation/Link' +import { Image } from '../components/Image' + +/* --- --------------------------------------------------------------------------- */ + +const ImagesScreen = () => { + return ( + + + {`< Back`} + + {/* - 1 - */} + + src=static-require | width: 60 | height: 60 + {/* - 2 - */} + + src=external-url | width: 60 | height: 60 + {/* - 3 - */} + + + + wrapper=50x80, relative | fill=true + {/* - 4 - */} + + + + wrapper=80x60, relative | fill | contentFit=contain + + ) +} + +/* --- Styles ---------------------------------------------------------------------------------- */ + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + backButton: { + position: 'absolute', + top: 16, + left: 16, + }, + subtitle: { + marginTop: 8, + marginBottom: 16, + fontSize: 16, + textAlign: 'center', + }, + link: { + marginTop: 16, + fontSize: 16, + color: 'blue', + textAlign: 'center', + textDecorationLine: 'underline', + }, +}) + +/* --- Exports --------------------------------------------------------------------------------- */ + +export default ImagesScreen diff --git a/package-lock.json b/package-lock.json index a108ce7..2babd0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@expo/metro-runtime": "^3.1.1", "expo": "^50.0.1", "expo-constants": "~15.4.5", + "expo-image": "~1.10.6", "expo-linking": "~6.2.2", "expo-router": "~3.4.4", "expo-status-bar": "~1.11.1", @@ -8474,6 +8475,17 @@ "expo": "*" } }, + "node_modules/expo-image": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-1.10.6.tgz", + "integrity": "sha512-vcnAIym1eU8vQgV1re1E7rVQZStJimBa4aPDhjFfzMzbddAF7heJuagyewiUkTzbZUwYzPaZAie6VJPyWx9Ueg==", + "dependencies": { + "@react-native/assets-registry": "~0.73.1" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-keep-awake": { "version": "12.8.2", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz",