-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
74bf57c
commit 81f755b
Showing
12 changed files
with
48,205 additions
and
8,939 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Animated, Image, ImageBackground } from 'react-native'; | ||
|
||
function AnimatableImage(props) { | ||
const { animated, children, ...rest } = props; | ||
|
||
const ImageComponent = children | ||
? ImageBackground | ||
: animated | ||
? Animated.Image | ||
: Image; | ||
|
||
return <ImageComponent {...rest}>{children}</ImageComponent>; | ||
} | ||
|
||
AnimatableImage.propTypes = Image.propTypes | Animated.Image.propTypes; | ||
|
||
AnimatableImage.defaultProps = { | ||
animated: false, | ||
}; | ||
|
||
export default AnimatableImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @since 2017-04-11 19:10:08 | ||
* @author vivaxy | ||
*/ | ||
import React, { useEffect, useState, useRef } from 'react'; | ||
import ImagePolyfill from './ImagePolyfill'; | ||
import AnimatableImage from './AnimatableImage'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { getImageSizeFitWidth, getImageSizeFitWidthFromCache } from './cache'; | ||
import { NOOP, DEFAULT_HEIGHT } from './helpers'; | ||
|
||
// remove `resizeMode` props from `Image.propTypes` | ||
const { resizeMode, ...ImagePropTypes } = AnimatableImage.propTypes; | ||
|
||
function AutoHeightImage(props) { | ||
const { onHeightChange, source, width, style, maxHeight, onError, ...rest } = | ||
props; | ||
const [height, setHeight] = useState( | ||
getImageSizeFitWidthFromCache(source, width, maxHeight).height || | ||
DEFAULT_HEIGHT | ||
); | ||
const mountedRef = useRef(false); | ||
|
||
useEffect(function () { | ||
mountedRef.current = true; | ||
return function () { | ||
mountedRef.current = false; | ||
}; | ||
}, []); | ||
|
||
useEffect( | ||
function () { | ||
(async function () { | ||
try { | ||
const { height: newHeight } = await getImageSizeFitWidth( | ||
source, | ||
width, | ||
maxHeight | ||
); | ||
if (mountedRef.current) { | ||
// might trigger `onHeightChange` with same `height` value | ||
// dedupe maybe? | ||
setHeight(newHeight); | ||
onHeightChange(newHeight); | ||
} | ||
} catch (e) { | ||
onError(e); | ||
} | ||
})(); | ||
}, | ||
[source, onHeightChange, width, maxHeight] | ||
); | ||
|
||
// StyleSheet.create will cache styles, not what we want | ||
const imageStyles = { width, height }; | ||
|
||
// Since it only makes sense to use polyfill with remote images | ||
const ImageComponent = source.uri ? ImagePolyfill : AnimatableImage; | ||
return ( | ||
<ImageComponent | ||
source={source} | ||
style={[imageStyles, style]} | ||
onError={onError} | ||
{...rest} | ||
/> | ||
); | ||
} | ||
|
||
AutoHeightImage.propTypes = { | ||
...ImagePropTypes, | ||
width: PropTypes.number.isRequired, | ||
maxHeight: PropTypes.number, | ||
onHeightChange: PropTypes.func, | ||
animated: PropTypes.bool, | ||
}; | ||
|
||
AutoHeightImage.defaultProps = { | ||
maxHeight: Infinity, | ||
onHeightChange: NOOP, | ||
animated: false, | ||
}; | ||
|
||
export default AutoHeightImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import React, { useState } from 'react'; | ||
|
||
import AutoHeightImage from './AutoHeightImage'; | ||
|
||
function ErrorableImage(props) { | ||
const { source, fallbackSource, onError, ...rest } = props; | ||
|
||
const [error, setError] = useState(false); | ||
|
||
const shouldUseFallbackSource = error && fallbackSource; | ||
|
||
return ( | ||
<AutoHeightImage | ||
source={shouldUseFallbackSource ? fallbackSource : source} | ||
onError={(_e) => { | ||
// if an error hasn't already been seen, try to load the error image | ||
// instead | ||
if (!error) { | ||
setError(true); | ||
} | ||
|
||
// also propagate to error handler if it is specified | ||
onError && onError(_e); | ||
}} | ||
{...rest} | ||
/> | ||
); | ||
} | ||
|
||
export default ErrorableImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import React, { useEffect } from 'react'; | ||
import { Platform, Image } from 'react-native'; | ||
import AnimatableImage from './AnimatableImage'; | ||
|
||
const isAndroid = () => Platform.OS === 'android'; | ||
|
||
/** | ||
* An extension of the Image class which fixes an Android bug where remote images wouldn't fire the | ||
* Image#onError() callback when the image failed to load due to a 404 response. | ||
* | ||
* This component should only be used for loading remote images, not local resources. | ||
*/ | ||
function ImagePolyfill(props) { | ||
const { source, onError, ...rest } = props; | ||
|
||
const verifyImage = () => { | ||
const { uri } = source; | ||
Image.prefetch(uri).catch((e) => onError(e)); | ||
}; | ||
|
||
useEffect(() => { | ||
if (source && source.uri && onError && isAndroid()) { | ||
verifyImage(); | ||
} | ||
}, [source, onError]); | ||
|
||
return <AnimatableImage source={source} {...rest} />; | ||
} | ||
|
||
ImagePolyfill.propTypes = AnimatableImage.propTypes; | ||
ImagePolyfill.defaultProps = AnimatableImage.defaultProps; | ||
|
||
export default ImagePolyfill; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* @since 2017-04-24 20:50:41 | ||
* @author vivaxy | ||
*/ | ||
|
||
import { Image } from 'react-native'; | ||
// undocumented but part of react-native; see | ||
// https://github.com/facebook/react-native/issues/5603#issuecomment-297959695 | ||
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; | ||
|
||
/** | ||
* store with | ||
* key: image | ||
* value: { | ||
* width: 100, | ||
* height: 100, | ||
* } | ||
*/ | ||
const cache = new Map(); | ||
|
||
const getImageSizeFromCache = (image) => { | ||
if (typeof image === 'number') { | ||
return cache.get(image); | ||
} else { | ||
return cache.get(image.uri); | ||
} | ||
}; | ||
|
||
const loadImageSize = (image) => { | ||
return new Promise((resolve, reject) => { | ||
//number indicates import X or require(X) was used (i.e. local file) | ||
if (typeof image === 'number') { | ||
const { width, height } = resolveAssetSource(image); | ||
resolve({ width, height }); | ||
} else { | ||
Image.getSize( | ||
image.uri, | ||
(width, height) => { | ||
// success | ||
resolve({ width, height }); | ||
}, | ||
reject | ||
); | ||
} | ||
}); | ||
}; | ||
|
||
export const getImageSizeFitWidthFromCache = (image, toWidth, maxHeight) => { | ||
const size = getImageSizeFromCache(image); | ||
if (size) { | ||
const { width, height } = size; | ||
if (!width || !height) return { width: 0, height: 0 }; | ||
const scaledHeight = (toWidth * height) / width; | ||
return { | ||
width: toWidth, | ||
height: scaledHeight > maxHeight ? maxHeight : scaledHeight, | ||
}; | ||
} | ||
return {}; | ||
}; | ||
|
||
const getImageSizeMaybeFromCache = async (image) => { | ||
let size = getImageSizeFromCache(image); | ||
if (!size) { | ||
size = await loadImageSize(image); | ||
if (typeof image === 'number') { | ||
cache.set(image, size); | ||
} else { | ||
cache.set(image.uri, size); | ||
} | ||
} | ||
return size; | ||
}; | ||
|
||
export const getImageSizeFitWidth = async (image, toWidth, maxHeight) => { | ||
const { width, height } = await getImageSizeMaybeFromCache(image); | ||
if (!width || !height) return { width: 0, height: 0 }; | ||
const scaledHeight = (toWidth * height) / width; | ||
return { | ||
width: toWidth, | ||
height: scaledHeight > maxHeight ? maxHeight : scaledHeight, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const NOOP = () => {}; | ||
export const DEFAULT_HEIGHT = 0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as React from 'react'; | ||
import { ImageProps } from 'react-native'; | ||
|
||
interface TSource { | ||
uri: string; | ||
} | ||
|
||
export interface AutoHeightImageProps extends ImageProps { | ||
source: number | TSource; | ||
width: number; | ||
maxHeight?: number; | ||
fallbackSource?: number | TSource; | ||
onHeightChange?: (height: number) => void; | ||
animated?: boolean; | ||
} | ||
|
||
declare class AutoHeightImage extends React.Component< | ||
AutoHeightImageProps, | ||
any | ||
> {} | ||
|
||
export default AutoHeightImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import ErrorableImage from './ErrorableImage'; | ||
|
||
export default ErrorableImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.