Merge pull request #300 from esteemapp/enhancment/ui

updated posts && filter && scroll to top feature
This commit is contained in:
uğur erdal 2018-12-24 15:24:50 +03:00 committed by GitHub
commit 1e0c8e5c6f
15 changed files with 175 additions and 87 deletions

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { TouchableWithoutFeedback, TouchableOpacity, SafeAreaView } from 'react-native';
import React, { PureComponent } from 'react';
import { TouchableOpacity, SafeAreaView } from 'react-native';
import { connect } from 'react-redux';
import ViewOverflow from 'react-native-view-overflow';
@ -13,7 +13,7 @@ import { updateActiveBottomTab } from '../../../redux/actions/uiAction';
// Styles
import styles from './bottomTabBarStyles';
class BottomTabBarView extends Component {
class BottomTabBarView extends PureComponent {
/* Props
* ------------------------------------------------
* @prop { type } name - Description....

View File

@ -103,7 +103,7 @@ export default class TagAreaView extends Component {
{chips.map((chip, i) => (
<Chip
key={i}
refs={(input) => {
ref={(input) => {
this.inputs[i] = input;
}}
isPin={i === 0 && chips[1]}

View File

@ -23,7 +23,7 @@ class PostCardContainer extends PureComponent {
};
}
_handleOnUserPress = (username) => {
_handleOnUserPress = () => {
const { navigation, currentAccount, content } = this.props;
if (content && currentAccount.name !== content.author) {
navigation.navigate({
@ -32,7 +32,7 @@ class PostCardContainer extends PureComponent {
username: content.author,
reputation: content.author_reputation,
},
key: username,
key: content.author,
});
}
};

View File

@ -28,4 +28,8 @@ export default EStyleSheet.create({
marginBottom: 40,
borderColor: '$borderColor',
},
noImage: {
width: 193,
height: 189,
},
});

View File

@ -37,7 +37,11 @@ class PostsView extends Component {
}
componentWillReceiveProps(nextProps) {
const { currentAccountUsername } = this.props;
const { currentAccountUsername, isScrollToTop } = this.props;
if (this.flatList && isScrollToTop !== nextProps.isScrollToTop && nextProps.isScrollToTop) {
this.flatList.scrollToOffset({ x: 0, y: 0, animated: true });
}
if (
currentAccountUsername !== nextProps.currentAccountUsername
@ -63,14 +67,18 @@ class PostsView extends Component {
}
}
_loadPosts = (filter = null) => {
_loadPosts = () => {
const { getFor, tag, currentAccountUsername } = this.props;
const { posts, startAuthor, startPermlink } = this.state;
const {
posts, startAuthor, startPermlink, refreshing, selectedFilterIndex,
} = this.state;
const filter = selectedFilterIndex !== 0 ? filters[selectedFilterIndex] : getFor;
let options;
let newPosts = [];
this.setState({ isLoading: true });
if (!filter && tag) {
if ((!filter && tag) || filter === 'feed' || getFor === 'blog') {
options = {
tag,
limit: 3,
@ -81,25 +89,40 @@ class PostsView extends Component {
};
}
if (startAuthor && startPermlink) {
if (startAuthor && startPermlink && !refreshing) {
options.start_author = startAuthor;
options.start_permlink = startPermlink;
}
getPostsSummary(filter || getFor, options, currentAccountUsername)
getPostsSummary(filter, options, currentAccountUsername)
.then((result) => {
if (result.length > 0) {
let _posts = result;
if (_posts.length > 0) {
if (posts.length > 0) {
_posts.shift();
_posts = [...posts, ..._posts];
if (refreshing) {
newPosts = _posts.filter(post => posts.includes(post));
_posts = [...newPosts, ...posts];
} else {
_posts.shift();
_posts = [...posts, ..._posts];
}
}
if (refreshing && newPosts.length > 0) {
this.setState({
posts: _posts,
});
} else if (!refreshing) {
this.setState({
posts: _posts,
startAuthor: result[result.length - 1] && result[result.length - 1].author,
startPermlink: result[result.length - 1] && result[result.length - 1].permlink,
});
}
this.setState({
posts: _posts,
startAuthor: result[result.length - 1] && result[result.length - 1].author,
startPermlink: result[result.length - 1] && result[result.length - 1].permlink,
refreshing: false,
isPostsLoading: false,
});
@ -140,12 +163,16 @@ class PostsView extends Component {
return null;
};
_handleOnDropdownSelect = (index) => {
this.setState({
_handleOnDropdownSelect = async (index) => {
await this.setState({
isPostsLoading: true,
selectedFilterIndex: index,
posts: [],
startAuthor: '',
startPermlink: '',
isNoPost: false,
});
this._loadPosts(filters[index]);
this._loadPosts();
};
_onRightIconPress = () => {
@ -197,6 +224,7 @@ class PostsView extends Component {
&& !isLoggedIn
&& isLoginDone && (
<NoPost
imageStyle={styles.noImage}
isButtonText
defaultText={intl.formatMessage({
id: 'profile.login_to_see',
@ -220,9 +248,13 @@ class PostsView extends Component {
initialNumToRender={10}
ListFooterComponent={this._renderFooter}
onScrollBeginDrag={() => this._handleOnScrollStart()}
ref={(ref) => {
this.flatList = ref;
}}
/>
) : isNoPost ? (
<NoPost
imageStyle={styles.noImage}
name={tag}
text={intl.formatMessage({
id: 'profile.havent_posted',

View File

@ -99,7 +99,6 @@ class ProfileSummaryView extends PureComponent {
)}
{!!date && <TextWithIcon text={date} iconName="md-calendar" />}
</View>
<View />
<Image
style={styles.longImage}
source={{ uri: coverImage }}

View File

@ -258,8 +258,9 @@ export const getActiveVotes = (author, permlink) => client.database.call('get_ac
export const getPostsSummary = async (by, query, currentUserName) => {
try {
let posts = await client.database.getDiscussions(by, query);
if (posts) {
posts = await parsePosts(posts, currentUserName);
posts = await parsePosts(posts, currentUserName, true);
}
return posts;
} catch (error) {

View File

@ -1,7 +1,7 @@
import { UPDATE_ACTIVE_BOTTOM_TAB, IS_COLLAPSE_POST_BUTTON } from '../constants/constants';
const initialState = {
activeBottomTab: 'Home',
activeBottomTab: 'HomeTabbar',
isCollapsePostButton: false,
};

View File

@ -13,17 +13,17 @@ import { getFollowers, getFollowing, getFollowSearch } from '../../../providers/
import FollowsScreen from '../screen/followsScreen';
/*
* Props Name Description Value
*@props --> props name here description here Value Type Here
*
*/
* Props Name Description Value
*@props --> props name here description here Value Type Here
*
*/
class FollowsContainer extends Component {
constructor(props) {
super(props);
this.state = {
username: null,
users: [],
users: null,
count: null,
isFollowingPress: null,
startWith: '',
@ -44,7 +44,7 @@ class FollowsContainer extends Component {
isFollowingPress,
});
await this._loadFollows(username, isFollowingPress);
this._loadFollows(username, isFollowingPress);
}
}
@ -56,7 +56,7 @@ class FollowsContainer extends Component {
username, users, isFollowingPress, startWith, count,
} = this.state;
if (users && count === users.length + 1) return;
if ((users && count < 100) || (users && count === users.length + 1)) return;
const name = username || _username;
const isFollowing = isFollowingPress || _isFollowingPress;
@ -66,16 +66,16 @@ class FollowsContainer extends Component {
if (!isFollowing) {
await getFollowers(name, startWith).then((result) => {
_users = result;
_startWith = users && users[users.length - 1] && users[users.length - 1].follower;
_startWith = result && result[result.length - 1] && result[result.length - 1].follower;
});
} else {
await getFollowing(name, startWith).then((result) => {
_users = result;
_startWith = users && users[users.length - 1] && users[users.length - 1].following;
_startWith = result && result[result.length - 1] && result[result.length - 1].following;
});
}
!_username && _users.shift();
if (!_username) _users.shift();
this.setState({
users: !_username ? [...users, ..._users] : _users,
@ -99,7 +99,10 @@ class FollowsContainer extends Component {
newData = await getFollowSearch(username, text);
}
this.setState({ filterResult: newData });
this.setState({
filterResult: newData,
isLoading: false,
});
};
_handleOnUserPress = (username) => {
@ -133,7 +136,6 @@ class FollowsContainer extends Component {
isLoading={isLoading}
handleSearch={this._handleSearch}
handleOnUserPress={this._handleOnUserPress}
{...this.props}
/>
);
}

View File

@ -21,9 +21,7 @@ class FollowsScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
data: props.data,
};
this.state = {};
}
// Component Life Cycles
@ -78,8 +76,8 @@ class FollowsScreen extends PureComponent {
{(filterResult && data && filterResult.length > 0) || data.length > 0 ? (
<FlatList
data={filterResult || data}
keyExtractor={item => item.voter}
onEndReached={loadMore}
keyExtractor={(item, index) => index.toString()}
onEndReached={() => loadMore()}
removeClippedSubviews={false}
renderItem={({ item, index }) => this._renderItem(item, index)}
ListFooterComponent={this._renderFooter}

View File

@ -4,6 +4,8 @@ import { connect } from 'react-redux';
// Component
import HomeScreen from '../screen/homeScreen';
// Constants
import { default as ROUTES } from '../../../constants/routeNames';
/*
* Props Name Description Value
*@props --> props name here description here Value Type Here
@ -13,21 +15,34 @@ import HomeScreen from '../screen/homeScreen';
class HomeContainer extends PureComponent {
constructor(props) {
super(props);
this.state = {};
this.state = {
isScrollToTop: false,
};
}
// Component Life Cycle Functions
componentWillReceiveProps(nextProps) {
const { activeBottomTab } = this.props;
if (
activeBottomTab === nextProps.activeBottomTab
&& nextProps.activeBottomTab === ROUTES.TABBAR.HOME
) {
this.setState({ isScrollToTop: true }, () => this.setState({ isScrollToTop: false }));
}
}
// Component Functions
render() {
const { isLoggedIn, isLoginDone, currentAccount } = this.props;
const { isScrollToTop } = this.state;
return (
<HomeScreen
isLoggedIn={isLoggedIn}
isLoginDone={isLoginDone}
currentAccount={currentAccount}
isScrollToTop={isScrollToTop}
/>
);
}
@ -36,8 +51,10 @@ class HomeContainer extends PureComponent {
const mapStateToProps = state => ({
isLoggedIn: state.application.isLoggedIn,
isLoginDone: state.application.isLoginDone,
nav: state.nav,
currentAccount: state.account.currentAccount,
activeBottomTab: state.ui.activeBottomTab,
});
export default connect(mapStateToProps)(HomeContainer);

View File

@ -20,7 +20,7 @@ class HomeScreen extends PureComponent {
render() {
const {
currentAccount, intl, isLoggedIn, isLoginDone,
currentAccount, intl, isLoggedIn, isLoginDone, isScrollToTop,
} = this.props;
const _filterOptions = [
'NEW POSTS',
@ -65,6 +65,7 @@ class HomeScreen extends PureComponent {
filterOptions={_filterOptions}
getFor="feed"
tag={tag || currentAccount.name}
isScrollToTop={isScrollToTop}
/>
</View>
<View
@ -73,7 +74,7 @@ class HomeScreen extends PureComponent {
})}
style={styles.tabbarItem}
>
<Posts filterOptions={_filterOptions} getFor="trending" />
<Posts filterOptions={_filterOptions} getFor="trending" isScrollToTop={isScrollToTop} />
</View>
</ScrollableTabView>
</View>

View File

@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { withNavigation } from 'react-navigation';
// Utilitites
import {
@ -37,7 +37,7 @@ class ProfileContainer extends Component {
}
componentDidMount = () => {
const { navigation, isLoggedIn, currentAccount } = this.props;
const { navigation, isLoggedIn } = this.props;
const selectedUser = navigation.state && navigation.state.params;
if (!isLoggedIn && !selectedUser) {
@ -58,13 +58,11 @@ class ProfileContainer extends Component {
}
this.setState({ isReverseHeader: true });
} else {
this._loadProfile(currentAccount.name);
}
};
componentWillReceiveProps(nextProps) {
const { navigation, currentAccount } = this.props;
const { navigation, currentAccount, activeBottomTab } = this.props;
const currentUsername = currentAccount.name !== nextProps.currentAccount.name && nextProps.currentAccount.name;
const isParamsChange = nextProps.navigation.state
&& navigation.state
@ -75,6 +73,10 @@ class ProfileContainer extends Component {
this._loadProfile(currentUsername);
}
if (activeBottomTab !== nextProps.activeBottomTab && nextProps.activeBottomTab === 'ProfileTabbar') {
this._loadProfile(currentAccount.name);
}
if (isParamsChange) {
const selectedUser = nextProps.navigation.state && nextProps.navigation.state.params;
@ -296,6 +298,7 @@ const mapStateToProps = state => ({
isLoggedIn: state.application.isLoggedIn,
currentAccount: state.account.currentAccount,
isDarkTheme: state.application.isDarkTheme,
activeBottomTab: state.ui.activeBottomTab,
});
export default connect(mapStateToProps)(ProfileContainer);
export default connect(mapStateToProps)(withNavigation(ProfileContainer));

View File

@ -18,6 +18,7 @@ const pullRightLeftRegex = /(<div class="[^"]*?pull-[^"]*?">(.*?)(<[/]div>))/g;
const linkRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;
const markdownImageRegex = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g;
const urlRegex = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gm;
const aTagRegex = /(<\s*a[^>]*>(.*?)<\s*[/]\s*a>)/g;
export const markDown2Html = (input) => {
if (!input) {
@ -37,8 +38,12 @@ export const markDown2Html = (input) => {
output = createYoutubeIframe(output);
}
if (dTubeRegex.test(output)) {
output = createDtubeIframe(output);
// if (dTubeRegex.test(output)) {
// output = createDtubeIframe(output);
// }
if (aTagRegex.test(output)) {
output = handleATag(output);
}
if (pullRightLeftRegex.test(output)) {
@ -46,7 +51,7 @@ export const markDown2Html = (input) => {
}
// if (imgRegex.test(output)) {
// output = createImage(output);
// output = handleImage(output);
// }
if (vimeoRegex.test(output)) {
@ -86,13 +91,13 @@ export const markDown2Html = (input) => {
return output;
};
export const replaceAuthorNames = input => input.replace(authorNameRegex, (match, preceeding1, preceeding2, user) => {
const replaceAuthorNames = input => input.replace(authorNameRegex, (match, preceeding1, preceeding2, user) => {
const userLower = user.toLowerCase();
const preceedings = (preceeding1 || '') + (preceeding2 || '');
return `${preceedings}<a class="markdown-author-link" href="${userLower}" data-author="${userLower}">@${user}</a>`;
});
export const replaceTags = input => input.replace(tagsRegex, (tag) => {
const replaceTags = input => input.replace(tagsRegex, (tag) => {
if (/#[\d]+$/.test(tag)) return tag;
const preceding = /^\s|>/.test(tag) ? tag[0] : '';
tag = tag.replace('>', '');
@ -101,7 +106,24 @@ export const replaceTags = input => input.replace(tagsRegex, (tag) => {
return `${preceding}<a class="markdown-tag-link" href="${tagLower}" data-tag="${tagLower}">${tag.trim()}</a>`;
});
export const removeOnlyPTag = input => input;
const handleATag = input => input.replace(aTagRegex, (link) => {
if (dTubeRegex.test(link)) {
const dTubeMatch = link.match(dTubeRegex)[0];
const execLink = dTubeRegex.exec(dTubeMatch);
if (execLink[2] && execLink[3]) {
const embedLink = `https://emb.d.tube/#!/${execLink[2]}/${execLink[3]}`;
return iframeBody(embedLink);
}
if (dTubeMatch) {
return iframeBody(dTubeMatch);
}
return link;
}
return link;
});
const changeMarkdownImage = input => input.replace(markdownImageRegex, (link) => {
const markdownMatch = link.match(markdownImageRegex);
@ -121,7 +143,7 @@ const createCenterImage = input => input.replace(imgCenterRegex, (link) => {
_link = _link.split('>')[1];
_link = _link.split('<')[0];
return `><img data-href="${_link}" src="${_link}"><`;
return `><img data-href="${`https://img.esteem.app/500x0/${_link}`}" src="${`https://img.esteem.app/500x0/${_link}`}"><`;
});
const changePullRightLeft = input => input.replace(pullRightLeftRegex, (item) => {
@ -144,6 +166,11 @@ const createImage = input => input.replace(
link => `<img data-href="${`https://img.esteem.app/300x0/${link}`}" src="${`https://img.esteem.app/500x0/${link}`}">`,
);
const handleImage = input => input.replace(
imgRegex,
link => `<img data-href="${`https://img.esteem.app/300x0/${link}`}" src="${`https://img.esteem.app/500x0/${link}`}">`,
);
const createFromDoubleImageLink = input => input.replace(onlyImageDoubleLinkRegex, (link) => {
const _link = link.trim();
return `<img data-href="https://img.esteem.app/300x0/${_link}" src="https://img.esteem.app/500x0/${_link}">`;

View File

@ -4,50 +4,54 @@ import { getPostSummary } from './formatter';
import { getReputation } from './reputation';
import { getTimeFromNow } from './time';
export const parsePosts = (posts, currentUserName) => (!posts ? null : posts.map(post => parsePost(post, currentUserName)));
export const parsePosts = (posts, currentUserName, isSummary) => (!posts ? null : posts.map(post => parsePost(post, currentUserName, isSummary)));
export const parsePost = (post, currentUserName) => {
export const parsePost = (post, currentUserName, isSummary = false) => {
if (!post) {
return null;
}
const _post = post;
post.json_metadata = JSON.parse(post.json_metadata);
post.image = postImage(post.json_metadata, post.body);
post.pending_payout_value = parseFloat(post.pending_payout_value).toFixed(2);
post.created = getTimeFromNow(post.created);
post.vote_count = post.active_votes.length;
post.author_reputation = getReputation(post.author_reputation);
post.avatar = `https://steemitimages.com/u/${post.author}/avatar/small`;
post.body = markDown2Html(post.body);
post.summary = getPostSummary(post.body, 150);
post.raw_body = post.body;
post.active_votes.sort((a, b) => b.rshares - a.rshares);
const totalPayout = parseFloat(post.pending_payout_value)
+ parseFloat(post.total_payout_value)
+ parseFloat(post.curator_payout_value);
_post.json_metadata = JSON.parse(post.json_metadata);
_post.image = postImage(post.json_metadata, post.body);
_post.pending_payout_value = parseFloat(post.pending_payout_value).toFixed(2);
_post.created = getTimeFromNow(post.created);
_post.vote_count = post.active_votes.length;
_post.author_reputation = getReputation(post.author_reputation);
_post.avatar = `https://steemitimages.com/u/${post.author}/avatar/small`;
_post.active_votes.sort((a, b) => b.rshares - a.rshares);
const voteRshares = post.active_votes.reduce((a, b) => a + parseFloat(b.rshares), 0);
const ratio = totalPayout / voteRshares;
_post.body = markDown2Html(post.body);
if (isSummary) _post.summary = getPostSummary(post.body, 150);
if (currentUserName) {
post.is_voted = isVoted(post.active_votes, currentUserName);
_post.is_voted = isVoted(_post.active_votes, currentUserName);
} else {
post.is_voted = false;
_post.is_voted = false;
}
for (const i in post.active_votes) {
post.vote_perecent = post.active_votes[i].voter === currentUserName ? post.active_votes[i].percent : null;
post.active_votes[i].value = (post.active_votes[i].rshares * ratio).toFixed(2);
post.active_votes[i].reputation = getReputation(post.active_votes[i].reputation);
post.active_votes[i].percent = post.active_votes[i].percent / 100;
post.active_votes[i].created = getTimeFromNow(post.active_votes[i].time);
post.active_votes[i].is_down_vote = Math.sign(post.active_votes[i].percent) < 0;
post.active_votes[i].avatar = `https://steemitimages.com/u/${
post.active_votes[i].voter
}/avatar/small`;
const totalPayout = parseFloat(_post.pending_payout_value)
+ parseFloat(_post.total_payout_value)
+ parseFloat(_post.curator_payout_value);
const voteRshares = _post.active_votes.reduce((a, b) => a + parseFloat(b.rshares), 0);
const ratio = totalPayout / voteRshares;
if (_post.active_votes && _post.active_votes.length > 0) {
for (const i in _post.active_votes) {
_post.vote_perecent = post.active_votes[i].voter === currentUserName ? post.active_votes[i].percent : null;
_post.active_votes[i].value = (post.active_votes[i].rshares * ratio).toFixed(2);
_post.active_votes[i].reputation = getReputation(post.active_votes[i].reputation);
_post.active_votes[i].percent = post.active_votes[i].percent / 100;
_post.active_votes[i].created = getTimeFromNow(post.active_votes[i].time);
_post.active_votes[i].is_down_vote = Math.sign(post.active_votes[i].percent) < 0;
_post.active_votes[i].avatar = `https://steemitimages.com/u/${
_post.active_votes[i].voter
}/avatar/small`;
}
}
return post;
return _post;
};
const isVoted = (activeVotes, currentUserName) => activeVotes.some(v => v.voter === currentUserName && v.percent > 0);