diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6b31fd313..7e71f36f4 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -21,6 +21,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:resizeableActivity="true"
android:requestLegacyExternalStorage="true"
+ android:usesCleartextTraffic="true"
>
{this.renderMoreImagesOverlay(nonVisibleImagesCount, theme)}
diff --git a/app/components/file_attachment_list/file_attachment_image.js b/app/components/file_attachment_list/file_attachment_image.js
index 2258abfb8..482b9fb19 100644
--- a/app/components/file_attachment_list/file_attachment_image.js
+++ b/app/components/file_attachment_list/file_attachment_image.js
@@ -40,6 +40,7 @@ export default class FileAttachmentImage extends PureComponent {
isSingleImage: PropTypes.bool,
imageDimensions: PropTypes.object,
backgroundColor: PropTypes.string,
+ inViewPort: PropTypes.bool,
};
static defaultProps = {
@@ -76,8 +77,13 @@ export default class FileAttachmentImage extends PureComponent {
if (file.localPath) {
imageProps.defaultSource = {uri: file.localPath};
} else if (file.id) {
- imageProps.thumbnailUri = Client4.getFileThumbnailUrl(file.id);
+ if (file.mini_preview && file.mime_type) {
+ imageProps.thumbnailUri = `data:${file.mime_type};base64,${file.mini_preview}`;
+ } else {
+ imageProps.thumbnailUri = Client4.getFileThumbnailUrl(file.id);
+ }
imageProps.imageUri = Client4.getFilePreviewUrl(file.id);
+ imageProps.inViewPort = this.props.inViewPort;
}
return imageProps;
};
diff --git a/app/components/file_attachment_list/file_attachment_list.js b/app/components/file_attachment_list/file_attachment_list.js
index 6ac9f90dc..d45471529 100644
--- a/app/components/file_attachment_list/file_attachment_list.js
+++ b/app/components/file_attachment_list/file_attachment_list.js
@@ -3,7 +3,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {StyleSheet, View} from 'react-native';
+import {StyleSheet, View, DeviceEventEmitter} from 'react-native';
import ImageViewPort from '@components/image_viewport';
import {Client4} from '@mm-redux/client';
@@ -33,7 +33,9 @@ export default class FileAttachmentList extends ImageViewPort {
constructor(props) {
super(props);
-
+ this.state = {
+ inViewPort: false,
+ };
this.items = [];
this.filesForGallery = this.getFilesForGallery(props);
@@ -42,6 +44,16 @@ export default class FileAttachmentList extends ImageViewPort {
});
}
+ componentDidMount() {
+ this.onScrollEnd = DeviceEventEmitter.addListener('scrolled', (viewableItems) => {
+ if (this.props.postId in viewableItems) {
+ this.setState({
+ inViewPort: true,
+ });
+ }
+ });
+ }
+
componentDidUpdate(prevProps) {
if (prevProps.files.length !== this.props.files.length) {
this.filesForGallery = this.getFilesForGallery(this.props);
@@ -51,6 +63,12 @@ export default class FileAttachmentList extends ImageViewPort {
}
}
+ componentWillUnmount() {
+ if (this.onScrollEnd && this.onScrollEnd.remove) {
+ this.onScrollEnd.remove();
+ }
+ }
+
attachmentIndex = (fileId) => {
return this.filesForGallery.findIndex((file) => file.id === fileId) || 0;
};
@@ -165,6 +183,7 @@ export default class FileAttachmentList extends ImageViewPort {
isSingleImage={isSingleImage}
nonVisibleImagesCount={nonVisibleImagesCount}
wrapperWidth={getViewPortWidth(isReplyPost, this.hasPermanentSidebar())}
+ inViewPort={this.state.inViewPort}
/>
);
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index eec8c8561..d9fb35b6b 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -3,7 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {Alert, FlatList, Platform, RefreshControl, StyleSheet} from 'react-native';
+import {Alert, DeviceEventEmitter, FlatList, Platform, RefreshControl, StyleSheet} from 'react-native';
import {intlShape} from 'react-intl';
import {Posts} from '@mm-redux/constants';
@@ -454,7 +454,13 @@ export default class PostList extends PureComponent {
if (!this.onViewableItemsChangedListener || !viewableItems.length || this.props.deepLinkURL) {
return;
}
-
+ const viewableItemsMap = viewableItems.reduce((acc, {item, isViewable}) => {
+ if (isViewable) {
+ acc[item] = true;
+ }
+ return acc;
+ }, {});
+ DeviceEventEmitter.emit('scrolled', viewableItemsMap);
this.onViewableItemsChangedListener(viewableItems);
}
diff --git a/app/components/progressive_image/__snapshots__/progressive_image.test.js.snap b/app/components/progressive_image/__snapshots__/progressive_image.test.js.snap
index 23f0b5da9..90687592a 100644
--- a/app/components/progressive_image/__snapshots__/progressive_image.test.js.snap
+++ b/app/components/progressive_image/__snapshots__/progressive_image.test.js.snap
@@ -102,6 +102,7 @@ exports[`ProgressiveImage should match snapshot for Image 1`] = `
},
]
}
+ testID="progressive_image.thumbnail"
/>
`;
diff --git a/app/components/progressive_image/progressive_image.js b/app/components/progressive_image/progressive_image.js
index c27c72ef2..0cab5fbb0 100644
--- a/app/components/progressive_image/progressive_image.js
+++ b/app/components/progressive_image/progressive_image.js
@@ -3,7 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {Animated, ImageBackground, View, StyleSheet} from 'react-native';
+import {Animated, ImageBackground, Image, Platform, View, StyleSheet} from 'react-native';
import FastImage from 'react-native-fast-image';
import thumb from '@assets/images/thumb.png';
@@ -20,11 +20,13 @@ export default class ProgressiveImage extends PureComponent {
defaultSource: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.number]), // this should be provided by the component
imageUri: PropTypes.string,
imageStyle: CustomPropTypes.Style,
+ inViewPort: PropTypes.bool,
onError: PropTypes.func,
resizeMethod: PropTypes.string,
resizeMode: PropTypes.string,
style: CustomPropTypes.Style,
theme: PropTypes.object.isRequired,
+ thumbnailUri: PropTypes.string,
tintDefaultSource: PropTypes.bool,
};
@@ -39,9 +41,22 @@ export default class ProgressiveImage extends PureComponent {
this.state = {
intensity: new Animated.Value(0),
+ showHighResImage: false,
};
}
+ componentDidUpdate(prevProps) {
+ if (prevProps.inViewPort !== this.props.inViewPort && this.props.inViewPort) {
+ this.startLoadingOriginalImage();
+ }
+ }
+
+ startLoadingOriginalImage = () => {
+ this.setState({
+ showHighResImage: true,
+ });
+ }
+
onLoadImageEnd = () => {
Animated.timing(this.state.intensity, {
duration: 300,
@@ -61,9 +76,12 @@ export default class ProgressiveImage extends PureComponent {
resizeMethod,
style,
theme,
+ thumbnailUri,
tintDefaultSource,
} = this.props;
+ const {showHighResImage} = this.state;
+
let DefaultComponent;
let ImageComponent;
if (isBackgroundImage) {
@@ -119,15 +137,60 @@ export default class ProgressiveImage extends PureComponent {
backgroundColor: changeOpacity(theme.centerChannelColor, defaultOpacity),
};
- return (
-
+ let thumbnail;
+ let image;
+ if (thumbnailUri) {
+ const ImageElement = thumbnailUri.startsWith('data:') ? Image : ImageComponent;
+
+ thumbnail = (
+
+ {this.props.children}
+
+ );
+
+ if (showHighResImage) {
+ image = (
+
+ {this.props.children}
+
+ );
+ }
+ } else {
+ thumbnail = (
+ );
+
+ image = (
{this.props.children}
+ );
+ }
+
+ return (
+
+ {thumbnail}
+ {image}
);
}
diff --git a/app/components/progressive_image/progressive_image.test.js b/app/components/progressive_image/progressive_image.test.js
index 5587485f2..9dae1408d 100644
--- a/app/components/progressive_image/progressive_image.test.js
+++ b/app/components/progressive_image/progressive_image.test.js
@@ -57,4 +57,45 @@ describe('ProgressiveImage', () => {
const wrapper = shallow();
expect(wrapper.getElement()).toMatchSnapshot();
});
-});
\ No newline at end of file
+});
+
+describe('MiniPreview', () => {
+ test('should show mini preview when supported & image not in viewport', () => {
+ const baseProps = {
+ isBackgroundImage: false,
+ imageUri: 'https://images.com/image.png',
+ onError: jest.fn(),
+ resizeMethod: 'auto',
+ resizeMode: 'contain',
+ theme: Preferences.THEMES.default,
+ tintDefaultSource: false,
+ defaultSource: null,
+ inViewPort: false,
+ thumbnailUri: 'somebase64data',
+ };
+ const wrapper = shallow();
+ expect(wrapper.find({testID: 'progressive_image.miniPreview'}).length).toEqual(1);
+ });
+
+ test('should load and show high res image with animation when component comes into viewport', () => {
+ const baseProps = {
+ isBackgroundImage: false,
+ imageUri: 'https://images.com/image.png',
+ onError: jest.fn(),
+ resizeMethod: 'auto',
+ resizeMode: 'contain',
+ theme: Preferences.THEMES.default,
+ tintDefaultSource: false,
+ defaultSource: null,
+ inViewPort: false,
+ thumbnailUri: 'somebase64data',
+ };
+ const wrapper = shallow();
+ const selectHighResImage = () => wrapper.find({testID: 'progressive_image.highResImage'});
+ expect(selectHighResImage().length).toEqual(0);
+ wrapper.setProps({
+ inViewPort: true,
+ });
+ expect(selectHighResImage().length).toEqual(1);
+ });
+});