mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-11-22 23:28:56 +03:00
add progressive thumbnail loading
This commit is contained in:
parent
245e6bd9ca
commit
6066c58d43
@ -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",
|
||||||
|
@ -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';
|
||||||
|
@ -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]}>
|
||||||
|
@ -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>
|
||||||
|
57
src/components/progressiveImage/index.js
Normal file
57
src/components/progressiveImage/index.js
Normal 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;
|
@ -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))}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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'));
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user