diff --git a/native/media/multimedia.react.js b/native/media/multimedia.react.js --- a/native/media/multimedia.react.js +++ b/native/media/multimedia.react.js @@ -6,9 +6,20 @@ import { type MediaInfo } from 'lib/types/media-types.js'; +import EncryptedImage from './encrypted-image.react.js'; import RemoteImage from './remote-image.react.js'; import { type InputState, InputStateContext } from '../input/input-state.js'; +type Source = + | { + kind: 'uri', + uri: string, + } + | { + kind: 'encrypted', + holder: string, + encryptionKey: string, + }; type BaseProps = { +mediaInfo: MediaInfo, +spinnerColor: string, @@ -19,8 +30,8 @@ +inputState: ?InputState, }; type State = { - +currentURI: string, - +departingURI: ?string, + +currentSource: Source, + +departingSource: ?Source, }; class Multimedia extends React.PureComponent { static defaultProps = { @@ -30,17 +41,9 @@ constructor(props: Props) { super(props); - invariant( - props.mediaInfo.type === 'image' || props.mediaInfo.type === 'video', - ' supports only unencrypted images and videos', - ); - this.state = { - currentURI: - props.mediaInfo.type === 'video' - ? props.mediaInfo.thumbnailURI - : props.mediaInfo.uri, - departingURI: null, + currentSource: Multimedia.sourceFromMediaInfo(props.mediaInfo), + departingSource: null, }; } @@ -51,66 +54,79 @@ } componentDidMount() { - this.inputState.reportURIDisplayed(this.state.currentURI, true); + this.reportSourceDisplayed(this.state.currentSource, true); } componentWillUnmount() { - const { inputState } = this; - const { currentURI, departingURI } = this.state; - inputState.reportURIDisplayed(currentURI, false); - if (departingURI) { - inputState.reportURIDisplayed(departingURI, false); + const { currentSource, departingSource } = this.state; + this.reportSourceDisplayed(currentSource, false); + if (departingSource) { + this.reportSourceDisplayed(departingSource, false); } } componentDidUpdate(prevProps: Props, prevState: State) { - const { inputState } = this; - invariant( - this.props.mediaInfo.type === 'image' || - this.props.mediaInfo.type === 'video', - ' supports only unencrypted images and videos', - ); - - const newURI = - this.props.mediaInfo.type === 'video' - ? this.props.mediaInfo.thumbnailURI - : this.props.mediaInfo.uri; - const oldURI = this.state.currentURI; - if (newURI !== oldURI) { - inputState.reportURIDisplayed(newURI, true); - - const { departingURI } = this.state; - if (departingURI && oldURI !== departingURI) { - // If there's currently an existing departingURI, that means that oldURI - // hasn't loaded yet. Since it's being replaced anyways we don't need to - // display it anymore, so we can unlink it now - inputState.reportURIDisplayed(oldURI, false); - this.setState({ currentURI: newURI }); + const sourcesEqual = (src1: Source, src2: Source) => + src1.kind === src2.kind && + (src1.kind === 'uri' + ? src1.uri === src2.uri + : src1.holder === src2.holder); + + const newSource = Multimedia.sourceFromMediaInfo(this.props.mediaInfo); + const oldSource = this.state.currentSource; + if (!sourcesEqual(newSource, oldSource)) { + this.reportSourceDisplayed(newSource, true); + + const { departingSource } = this.state; + if (departingSource && !sourcesEqual(oldSource, departingSource)) { + // If there's currently an existing departingSource, that means that + // oldSource hasn't loaded yet. Since it's being replaced anyways + // we don't need to display it anymore, so we can unlink it now + this.reportSourceDisplayed(oldSource, false); + this.setState({ currentSource: newSource }); } else { - this.setState({ currentURI: newURI, departingURI: oldURI }); + this.setState({ currentSource: newSource, departingSource: oldSource }); } } - const newDepartingURI = this.state.departingURI; - const oldDepartingURI = prevState.departingURI; - if (oldDepartingURI && oldDepartingURI !== newDepartingURI) { - inputState.reportURIDisplayed(oldDepartingURI, false); + const newDepartingSource = this.state.departingSource; + const oldDepartingSource = prevState.departingSource; + if ( + oldDepartingSource && + (!newDepartingSource || + !sourcesEqual(oldDepartingSource, newDepartingSource)) + ) { + this.reportSourceDisplayed(oldDepartingSource, false); } } render() { const images = []; - const { currentURI, departingURI } = this.state; - if (departingURI) { - images.push(this.renderURI(currentURI, true)); - images.push(this.renderURI(departingURI, true)); + const { currentSource, departingSource } = this.state; + if (departingSource) { + images.push(this.renderSource(currentSource, true)); + images.push(this.renderSource(departingSource, true)); } else { - images.push(this.renderURI(currentURI)); + images.push(this.renderSource(currentSource)); } return {images}; } - renderURI(uri: string, invisibleLoad?: boolean = false) { + renderSource(source: Source, invisibleLoad?: boolean = false) { + if (source.kind === 'encrypted') { + return ( + + ); + } + const { uri } = source; if (uri.startsWith('http')) { return ( ); } else { - const source = { uri }; return ( { - this.setState({ departingURI: null }); + this.setState({ departingSource: null }); + }; + + reportSourceDisplayed = (source: Source, loaded: boolean) => { + const uri = source.kind === 'uri' ? source.uri : source.holder; + this.inputState.reportURIDisplayed(uri, loaded); }; + + static sourceFromMediaInfo(mediaInfo: MediaInfo): Source { + if (mediaInfo.type === 'photo') { + return { kind: 'uri', uri: mediaInfo.uri }; + } else if (mediaInfo.type === 'video') { + return { kind: 'uri', uri: mediaInfo.thumbnailURI }; + } else if (mediaInfo.type === 'encrypted_photo') { + return { + kind: 'encrypted', + holder: mediaInfo.holder, + encryptionKey: mediaInfo.encryptionKey, + }; + } else if (mediaInfo.type === 'encrypted_video') { + return { + kind: 'encrypted', + holder: mediaInfo.thumbnailHolder, + encryptionKey: mediaInfo.thumbnailEncryptionKey, + }; + } else { + invariant(false, 'Invalid mediaInfo type'); + } + } } const styles = StyleSheet.create({