Changeset View
Changeset View
Standalone View
Standalone View
native/media/remote-image.react.js
// @flow | // @flow | ||||
import { Image } from 'expo-image'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { View, StyleSheet, ActivityIndicator } from 'react-native'; | |||||
import { type ConnectionStatus } from 'lib/types/socket-types.js'; | import { type ConnectionStatus } from 'lib/types/socket-types.js'; | ||||
import LoadableImage from './loadable-image.react.js'; | |||||
import { useSelector } from '../redux/redux-utils.js'; | import { useSelector } from '../redux/redux-utils.js'; | ||||
import type { ImageStyle } from '../types/styles.js'; | import type { ImageStyle } from '../types/styles.js'; | ||||
type BaseProps = { | type BaseProps = { | ||||
+uri: string, | +uri: string, | ||||
+onLoad: (uri: string) => void, | +onLoad: (uri: string) => void, | ||||
+spinnerColor: string, | +spinnerColor: string, | ||||
+style: ImageStyle, | +style: ImageStyle, | ||||
+invisibleLoad: boolean, | +invisibleLoad: boolean, | ||||
}; | }; | ||||
type Props = { | type Props = { | ||||
...BaseProps, | ...BaseProps, | ||||
+connectionStatus: ConnectionStatus, | +connectionStatus: ConnectionStatus, | ||||
}; | }; | ||||
type State = { | type State = { | ||||
+attempt: number, | +attempt: number, | ||||
+loaded: boolean, | |||||
}; | }; | ||||
class RemoteImage extends React.PureComponent<Props, State> { | class RemoteImage extends React.PureComponent<Props, State> { | ||||
loaded: boolean = false; | |||||
state: State = { | state: State = { | ||||
attempt: 0, | attempt: 0, | ||||
loaded: false, | |||||
}; | }; | ||||
componentDidUpdate(prevProps: Props, prevState: State) { | componentDidUpdate(prevProps: Props) { | ||||
if ( | if ( | ||||
!this.state.loaded && | !this.loaded && | ||||
this.props.connectionStatus === 'connected' && | this.props.connectionStatus === 'connected' && | ||||
prevProps.connectionStatus !== 'connected' | prevProps.connectionStatus !== 'connected' | ||||
) { | ) { | ||||
this.setState(otherPrevState => ({ | this.setState(otherPrevState => ({ | ||||
attempt: otherPrevState.attempt + 1, | attempt: otherPrevState.attempt + 1, | ||||
})); | })); | ||||
} | } | ||||
if (this.state.loaded && !prevState.loaded) { | |||||
this.props.onLoad && this.props.onLoad(this.props.uri); | |||||
} | |||||
} | } | ||||
render() { | render() { | ||||
const source = { uri: this.props.uri }; | const { style, spinnerColor, invisibleLoad, uri } = this.props; | ||||
if (this.state.loaded) { | const source = { uri }; | ||||
return ( | |||||
<Image | |||||
source={source} | |||||
onLoad={this.onLoad} | |||||
style={this.props.style} | |||||
key={this.state.attempt} | |||||
/> | |||||
); | |||||
} | |||||
if (this.props.invisibleLoad) { | |||||
return ( | |||||
<Image | |||||
source={source} | |||||
onLoad={this.onLoad} | |||||
style={[this.props.style, styles.invisible]} | |||||
key={this.state.attempt} | |||||
/> | |||||
); | |||||
} | |||||
return ( | return ( | ||||
<View style={styles.container}> | <LoadableImage | ||||
<View style={styles.spinnerContainer}> | |||||
<ActivityIndicator color={this.props.spinnerColor} size="large" /> | |||||
</View> | |||||
<Image | |||||
source={source} | source={source} | ||||
onLoad={this.onLoad} | onLoad={this.onLoad} | ||||
style={this.props.style} | spinnerColor={spinnerColor} | ||||
style={style} | |||||
invisibleLoad={invisibleLoad} | |||||
key={this.state.attempt} | key={this.state.attempt} | ||||
/> | /> | ||||
</View> | |||||
); | ); | ||||
} | } | ||||
onLoad = () => { | onLoad = () => { | ||||
this.setState({ loaded: true }); | this.loaded = true; | ||||
this.props.onLoad && this.props.onLoad(this.props.uri); | |||||
}; | }; | ||||
} | } | ||||
const styles = StyleSheet.create({ | |||||
container: { | |||||
flex: 1, | |||||
}, | |||||
invisible: { | |||||
opacity: 0, | |||||
}, | |||||
spinnerContainer: { | |||||
alignItems: 'center', | |||||
bottom: 0, | |||||
justifyContent: 'center', | |||||
left: 0, | |||||
position: 'absolute', | |||||
right: 0, | |||||
top: 0, | |||||
}, | |||||
}); | |||||
const ConnectedRemoteImage: React.ComponentType<BaseProps> = | const ConnectedRemoteImage: React.ComponentType<BaseProps> = | ||||
React.memo<BaseProps>(function ConnectedRemoteImage(props: BaseProps) { | React.memo<BaseProps>(function ConnectedRemoteImage(props: BaseProps) { | ||||
const connectionStatus = useSelector(state => state.connection.status); | const connectionStatus = useSelector(state => state.connection.status); | ||||
return <RemoteImage {...props} connectionStatus={connectionStatus} />; | return <RemoteImage {...props} connectionStatus={connectionStatus} />; | ||||
}); | }); | ||||
export default ConnectedRemoteImage; | export default ConnectedRemoteImage; |