add progressive thumbnail loading

This commit is contained in:
feruz 2021-01-18 13:49:57 +02:00
parent 245e6bd9ca
commit 6066c58d43
9 changed files with 114 additions and 35 deletions

View File

@ -75,6 +75,7 @@
"react-native-gesture-handler": "^1.4.1", "react-native-gesture-handler": "^1.4.1",
"react-native-iap": "3.4.15", "react-native-iap": "3.4.15",
"react-native-image-crop-picker": "^0.26.1", "react-native-image-crop-picker": "^0.26.1",
"react-native-image-size": "^1.1.3",
"react-native-image-zoom-viewer": "^2.2.27", "react-native-image-zoom-viewer": "^2.2.27",
"react-native-keyboard-aware-scroll-view": "^0.9.1", "react-native-keyboard-aware-scroll-view": "^0.9.1",
"react-native-linear-gradient": "^2.4.2", "react-native-linear-gradient": "^2.4.2",

View File

@ -33,6 +33,7 @@ import { PostForm } from './postForm';
import { PostHeaderDescription, PostBody, Tags } from './postElements'; import { PostHeaderDescription, PostBody, Tags } from './postElements';
import { PostListItem } from './postListItem'; import { PostListItem } from './postListItem';
import { ProfileSummary } from './profileSummary'; import { ProfileSummary } from './profileSummary';
import { ProgressiveImage } from './progressiveImage';
import { SearchInput } from './searchInput'; import { SearchInput } from './searchInput';
import { SearchModal } from './searchModal'; import { SearchModal } from './searchModal';

View File

@ -1,8 +1,8 @@
import React, { Component, useState, useEffect } from 'react'; import React, { Component, useState, useEffect } from 'react';
import get from 'lodash/get'; import get from 'lodash/get';
import { TouchableOpacity, Text, View, Dimensions } from 'react-native'; import { TouchableOpacity, Text, View, Dimensions } from 'react-native';
import FastImage from 'react-native-fast-image';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import ImageSize from 'react-native-image-size';
// Utils // Utils
import { getTimeFromNow } from '../../../utils/time'; import { getTimeFromNow } from '../../../utils/time';
@ -19,10 +19,13 @@ import { Upvote } from '../../upvote';
import styles from './postCardStyles'; import styles from './postCardStyles';
// Defaults // Defaults
import DEFAULT_IMAGE from '../../../assets/no_image.png'; import ProgressiveImage from '../../progressiveImage';
import NSFW_IMAGE from '../../../assets/nsfw.png';
const { width, height } = Dimensions.get('window'); const dim = Dimensions.get('window');
const DEFAULT_IMAGE =
'https://images.ecency.com/DQmT8R33geccEjJfzZEdsRHpP3VE8pu3peRCnQa1qukU4KR/no_image_3x.png';
const NSFW_IMAGE =
'https://images.ecency.com/DQmZ1jW4p7o5GyoqWyCib1fSLE2ftbewsMCt2GvbmT9kmoY/nsfw_3x.png';
const PostCardView = ({ const PostCardView = ({
handleOnUserPress, handleOnUserPress,
@ -39,7 +42,7 @@ const PostCardView = ({
}) => { }) => {
const [rebloggedBy, setRebloggedBy] = useState(get(content, 'reblogged_by[0]', null)); const [rebloggedBy, setRebloggedBy] = useState(get(content, 'reblogged_by[0]', null));
const [activeVot, setActiveVot] = useState(activeVotes); const [activeVot, setActiveVot] = useState(activeVotes);
const [calcImgHeight, setCalcImgHeight] = useState(0); const [calcImgHeight, setCalcImgHeight] = useState(300);
//console.log(activeVotes); //console.log(activeVotes);
// Component Functions // Component Functions
@ -66,11 +69,16 @@ const PostCardView = ({
const _getPostImage = (content, isNsfwPost) => { const _getPostImage = (content, isNsfwPost) => {
if (content && content.image) { if (content && content.image) {
if (isNsfwPost && content.nsfw) { if (isNsfwPost && content.nsfw) {
return NSFW_IMAGE; return { image: NSFW_IMAGE, thumbnail: NSFW_IMAGE };
} }
return { uri: content.image, priority: FastImage.priority.high }; //console.log(content)
ImageSize.getSize(content.image).then((size) => {
setCalcImgHeight((size.height / size.width) * dim.width);
});
return { image: content.image, thumbnail: content.thumbnail };
} else {
return { image: DEFAULT_IMAGE, thumbnail: DEFAULT_IMAGE };
} }
return DEFAULT_IMAGE;
}; };
useEffect(() => { useEffect(() => {
@ -106,17 +114,13 @@ const PostCardView = ({
<View style={styles.postBodyWrapper}> <View style={styles.postBodyWrapper}>
<TouchableOpacity style={styles.hiddenImages} onPress={_handleOnContentPress}> <TouchableOpacity style={styles.hiddenImages} onPress={_handleOnContentPress}>
{!isHideImage && ( {!isHideImage && (
<FastImage <ProgressiveImage
source={{ uri: _image.image }}
thumbnailSource={{ uri: _image.thumbnail }}
style={[ style={[
styles.thumbnail, styles.thumbnail,
{ width: scalePx(width - 16), height: scalePx(Math.min(calcImgHeight, height)) }, { width: scalePx(dim.width - 16), height: Math.min(calcImgHeight, dim.height) },
]} ]}
source={_image}
resizeMode={FastImage.resizeMode.cover}
defaultSource={DEFAULT_IMAGE}
onLoad={(evt) =>
setCalcImgHeight((evt.nativeEvent.height / evt.nativeEvent.width) * width)
}
/> />
)} )}
<View style={[styles.postDescripton]}> <View style={[styles.postDescripton]}>

View File

@ -1,8 +1,8 @@
import React, { useRef, useState, Fragment } from 'react'; import React, { useRef, useState, useEffect, Fragment } from 'react';
import ActionSheet from 'react-native-actionsheet'; import ActionSheet from 'react-native-actionsheet';
import { View, Text, TouchableOpacity, Dimensions } from 'react-native'; import { View, Text, TouchableOpacity, Dimensions } from 'react-native';
import { injectIntl } from 'react-intl'; import { injectIntl } from 'react-intl';
import FastImage from 'react-native-fast-image'; import ImageSize from 'react-native-image-size';
// Utils // Utils
import { getTimeFromNow } from '../../../utils/time'; import { getTimeFromNow } from '../../../utils/time';
@ -11,14 +11,16 @@ import scalePx from '../../../utils/scalePx';
// Components // Components
import { PostHeaderDescription } from '../../postElements'; import { PostHeaderDescription } from '../../postElements';
import { IconButton } from '../../iconButton'; import { IconButton } from '../../iconButton';
import ProgressiveImage from '../../progressiveImage';
// Defaults
import DEFAULT_IMAGE from '../../../assets/no_image.png';
// Styles // Styles
import styles from './postListItemStyles'; import styles from './postListItemStyles';
const { width, height } = Dimensions.get('window'); // Defaults
const DEFAULT_IMAGE =
'https://images.ecency.com/DQmT8R33geccEjJfzZEdsRHpP3VE8pu3peRCnQa1qukU4KR/no_image_3x.png';
const dim = Dimensions.get('window');
const PostListItemView = ({ const PostListItemView = ({
title, title,
@ -28,6 +30,7 @@ const PostListItemView = ({
reputation, reputation,
created, created,
image, image,
thumbnail,
handleOnPressItem, handleOnPressItem,
handleOnRemoveItem, handleOnRemoveItem,
id, id,
@ -35,9 +38,15 @@ const PostListItemView = ({
isFormatedDate, isFormatedDate,
}) => { }) => {
const actionSheet = useRef(null); const actionSheet = useRef(null);
const [calcImgHeight, setCalcImgHeight] = useState(0); const [calcImgHeight, setCalcImgHeight] = useState(300);
// Component Life Cycles // Component Life Cycles
useEffect(() => {
if (image) {
ImageSize.getSize(image.uri).then((size) => {
setCalcImgHeight((size.height / size.width) * dim.width);
});
}
}, []);
// Component Functions // Component Functions
return ( return (
@ -63,16 +72,13 @@ const PostListItemView = ({
</View> </View>
<View style={styles.body}> <View style={styles.body}>
<TouchableOpacity onPress={() => handleOnPressItem(id)}> <TouchableOpacity onPress={() => handleOnPressItem(id)}>
<FastImage <ProgressiveImage
source={image} source={image}
thumbnailSource={thumbnail}
style={[ style={[
styles.image, styles.thumbnail,
{ width: scalePx(width - 16), height: scalePx(Math.min(calcImgHeight, height)) }, { width: scalePx(dim.width - 16), height: Math.min(calcImgHeight, dim.height) },
]} ]}
defaultSource={DEFAULT_IMAGE}
onLoad={(evt) =>
setCalcImgHeight((evt.nativeEvent.height / evt.nativeEvent.width) * width)
}
/> />
<View style={[styles.postDescripton]}> <View style={[styles.postDescripton]}>
<Text style={styles.title}>{title}</Text> <Text style={styles.title}>{title}</Text>

View File

@ -0,0 +1,57 @@
import React from 'react';
import { View, StyleSheet, Animated } from 'react-native';
const styles = StyleSheet.create({
imageOverlay: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
},
container: {
backgroundColor: '#f6f6f6',
},
});
class ProgressiveImage extends React.Component {
thumbnailAnimated = new Animated.Value(0);
imageAnimated = new Animated.Value(0);
handleThumbnailLoad = () => {
Animated.timing(this.thumbnailAnimated, {
toValue: 1,
}).start();
};
onImageLoad = () => {
Animated.timing(this.imageAnimated, {
toValue: 1,
}).start();
};
render() {
const { thumbnailSource, source, style, ...props } = this.props;
return (
<View style={styles.container}>
<Animated.Image
{...props}
source={thumbnailSource}
style={[style, { opacity: this.thumbnailAnimated }]}
onLoad={this.handleThumbnailLoad}
blurRadius={1}
/>
<Animated.Image
{...props}
source={source}
style={[styles.imageOverlay, { opacity: this.imageAnimated }, style]}
onLoad={this.onImageLoad}
/>
</View>
);
}
}
export default ProgressiveImage;

View File

@ -47,6 +47,7 @@ const DraftsScreen = ({
const tags = item.tags ? item.tags.split(/[ ,]+/) : []; const tags = item.tags ? item.tags.split(/[ ,]+/) : [];
const tag = tags[0] || ''; const tag = tags[0] || '';
const image = catchDraftImage(item.body); const image = catchDraftImage(item.body);
const thumbnail = catchDraftImage(item.body, 'match', true);
const summary = postBodySummary({ item, last_update: item.created }, 100); const summary = postBodySummary({ item, last_update: item.created }, 100);
const isSchedules = type === 'schedules'; const isSchedules = type === 'schedules';
@ -57,7 +58,8 @@ const DraftsScreen = ({
title={item.title} title={item.title}
summary={summary} summary={summary}
isFormatedDate={isSchedules} isFormatedDate={isSchedules}
image={image ? { uri: catchDraftImage(item.body) } : null} image={image ? { uri: image } : null}
thumbnail={thumbnail ? { uri: thumbnail } : null}
username={currentAccount.name} username={currentAccount.name}
reputation={currentAccount.reputation} reputation={currentAccount.reputation}
handleOnPressItem={() => (isSchedules ? setSelectedId(item._id) : editDraft(item._id))} handleOnPressItem={() => (isSchedules ? setSelectedId(item._id) : editDraft(item._id))}

View File

@ -63,14 +63,16 @@ export const catchEntryImage = (entry, width = 0, height = 0, format = 'match')
return null; return null;
}; };
export const catchDraftImage = (body, format = 'match') => { export const catchDraftImage = (body, format = 'match', thumbnail = false) => {
const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|PNG|GIF|JPG))/g; const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|PNG|GIF|JPG))/g;
format = whatOs === 'android' ? 'webp' : 'match'; format = whatOs === 'android' ? 'webp' : 'match';
if (body && imgRegex.test(body)) { if (body && imgRegex.test(body)) {
const imageMatch = body.match(imgRegex); const imageMatch = body.match(imgRegex);
if (thumbnail) {
return proxifyImageSrc(imageMatch[0], 0, 0, format); return proxifyImageSrc(imageMatch[0], 60, 50, format);
}
return proxifyImageSrc(imageMatch[0], 600, 500, format);
} }
return null; return null;
}; };

View File

@ -33,6 +33,7 @@ export const parsePost = (post, currentUserName, isPromoted) => {
post.json_metadata = {}; post.json_metadata = {};
} }
post.image = catchPostImage(post.body, 600, 500, webp ? 'webp' : 'match'); post.image = catchPostImage(post.body, 600, 500, webp ? 'webp' : 'match');
post.thumbnail = catchPostImage(post.body, 60, 50, webp ? 'webp' : 'match');
post.author_reputation = getReputation(post.author_reputation); post.author_reputation = getReputation(post.author_reputation);
post.avatar = getResizedAvatar(get(post, 'author')); post.avatar = getResizedAvatar(get(post, 'author'));

View File

@ -7538,6 +7538,11 @@ react-native-image-pan-zoom@^2.1.9:
resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2" resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2"
integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q== integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==
react-native-image-size@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/react-native-image-size/-/react-native-image-size-1.1.3.tgz#7d69c2cd4e1d1632947867e47643ed8cabb9de27"
integrity sha512-jJvN6CjXVAm69LAVZNV7m7r50Qk9vuPZwLyrbs/k31/3Xs8bZyVCdvfP44FuBisITn/yFsiOo6i8NPrFBPH20w==
react-native-image-zoom-viewer@^2.2.27: react-native-image-zoom-viewer@^2.2.27:
version "2.2.27" version "2.2.27"
resolved "https://registry.yarnpkg.com/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-2.2.27.tgz#fb3314c5dc86ac33da48cb31bf4920d97eecb6eb" resolved "https://registry.yarnpkg.com/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-2.2.27.tgz#fb3314c5dc86ac33da48cb31bf4920d97eecb6eb"