enhanced notifcaiton points editor side menu etc.

This commit is contained in:
u-e 2019-05-18 19:46:59 +03:00
parent 53b59a7033
commit 9370b89b5d
12 changed files with 315 additions and 202 deletions

View File

@ -1,25 +1,31 @@
import { Card } from './view/card/cardView';
import Card from './view/card/cardView';
import Chip from './view/chip/chipView';
import GrayWrapper from './view/grayWrapper/grayWrapperView';
import LineBreak from './view/lineBreak/lineBreakView';
import NoInternetConnection from './view/noInternetConnection/noInternetConnectionView';
import NoPost from './view/noPost/noPostView';
import PostCardPlaceHolder from './view/placeHolder/postCardPlaceHolderView';
import PostPlaceHolder from './view/placeHolder/postPlaceHolderView';
import ProfileSummaryPlaceHolder from './view/placeHolder/profileSummaryPlaceHolder';
import StickyBar from './view/stickyBar/stickyBarView';
import Tag from './view/tag/tagContainer';
import TextWithIcon from './view/textWithIcon/textWithIconView';
import UserListItem from './view/userListItem/userListItem';
import WalletDetailsPlaceHolder from './view/placeHolder/walletDetailsPlaceHolder';
import WalletLineItem from './view/walletLineItem/walletLineItemView';
// Placeholders
import ListItemPlaceHolder from './view/placeHolder/listItemPlaceHolderView';
import PostCardPlaceHolder from './view/placeHolder/postCardPlaceHolderView';
import PostPlaceHolder from './view/placeHolder/postPlaceHolderView';
import ProfileSummaryPlaceHolder from './view/placeHolder/profileSummaryPlaceHolder';
import WalletDetailsPlaceHolder from './view/placeHolder/walletDetailsPlaceHolder';
import WalletUnclaimedPlaceHolder from './view/placeHolder/walletUnclaimedPlaceHolder';
import ListPlaceHolder from './view/placeHolder/listPlaceHolderView';
export {
Card,
Chip,
GrayWrapper,
LineBreak,
ListItemPlaceHolder,
ListPlaceHolder,
NoInternetConnection,
NoPost,
PostCardPlaceHolder,

View File

@ -0,0 +1,19 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
backgroundColor: '$primaryBackgroundColor',
padding: 20,
borderColor: '$primaryLightBackground',
marginRight: 0,
marginLeft: 0,
marginTop: 10,
flex: 1,
flexDirection: 'row',
},
paragraphWrapper: {
marginLeft: 20,
marginTop: 3,
flex: 1,
},
});

View File

@ -0,0 +1,34 @@
import React from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
import Placeholder from 'rn-placeholder';
import styles from './listItemPlaceHolderStyles';
// TODO: make container for place holder wrapper after alpha
const ListItemPlaceHolderView = ({ isDarkTheme }) => {
const color = isDarkTheme ? '#2e3d51' : '#f5f5f5';
return (
<View style={styles.container}>
<Placeholder.Media size={40} hasRadius animate="fade" color={color} />
<View style={styles.paragraphWrapper}>
<Placeholder.Paragraph
color={color}
lineNumber={2}
textSize={12}
lineSpacing={8}
width="100%"
lastLineWidth="70%"
firstLineWidth="50%"
animate="fade"
/>
</View>
</View>
);
};
const mapStateToProps = state => ({
isDarkTheme: state.application.isDarkTheme,
});
export default connect(mapStateToProps)(ListItemPlaceHolderView);

View File

@ -0,0 +1,30 @@
/* eslint-disable radix */
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { Dimensions } from 'react-native';
import times from 'lodash/times';
import ListItemPlaceHolder from './listItemPlaceHolderView';
const HEIGHT = Dimensions.get('window').height;
const ListPlaceHolderView = () => {
const ratio = (HEIGHT - 300) / 50;
const listElements = [];
times(parseInt(ratio), (i) => {
listElements.push(<ListItemPlaceHolder key={i} />);
});
return (
<Fragment>
{listElements}
</Fragment>
);
};
const mapStateToProps = state => ({
isDarkTheme: state.application.isDarkTheme,
});
export default connect(mapStateToProps)(ListPlaceHolderView);

View File

@ -5,13 +5,13 @@ import Placeholder from 'rn-placeholder';
import styles from './postCardPlaceHolderStyles';
// TODO: make container for place holder wrapper after alpha
const PostCardPlaceHolder = (props) => {
const color = props.isDarkTheme ? '#2e3d51' : '#f5f5f5';
const PostCardPlaceHolder = ({ isDarkTheme }) => {
const color = isDarkTheme ? '#2e3d51' : '#f5f5f5';
return (
<View style={styles.container}>
<View style={styles.textWrapper}>
<Placeholder.Media size={25} hasRadius animate="fade" color={color} />
<Placeholder.Media size={35} hasRadius animate="fade" color={color} />
<Placeholder.Line width="30%" lastLineWidth="30%" animate="fade" color={color} />
</View>
<Placeholder.Box animate="fade" height={200} width="100%" radius={5} color={color} />

View File

@ -3,7 +3,7 @@ import { View, FlatList, Text } from 'react-native';
import { injectIntl } from 'react-intl';
// Components
import { UserListItem } from '../../basicUIElements';
import { UserListItem, ListPlaceHolder } from '../../basicUIElements';
// Styles
import styles from './leaderboardStyles';
@ -45,14 +45,20 @@ class LeaderboardView extends PureComponent {
id: 'notification.leaderboard_title',
})}
</Text>
<FlatList
data={users}
refreshing={refreshing}
keyExtractor={item => item.voter}
removeClippedSubviews={false}
onRefresh={() => fetchLeaderBoard()}
renderItem={({ item, index }) => this._renderItem(item, index)}
/>
{!users
? <ListPlaceHolder />
: (
<FlatList
data={users}
refreshing={refreshing}
keyExtractor={item => item.voter}
removeClippedSubviews={false}
onRefresh={() => fetchLeaderBoard()}
renderItem={({ item, index }) => this._renderItem(item, index)}
/>
)
}
</View>
);
}

View File

@ -1,6 +1,6 @@
import React, { PureComponent, Fragment } from 'react';
import {
View, ScrollView, FlatList, ActivityIndicator, RefreshControl,
View, FlatList, ActivityIndicator, RefreshControl,
} from 'react-native';
import { injectIntl } from 'react-intl';
@ -10,6 +10,7 @@ import { injectIntl } from 'react-intl';
import { ContainerHeader } from '../../containerHeader';
import { FilterBar } from '../../filterBar';
import NotificationLine from '../../notificationLine';
import { ListPlaceHolder } from '../../basicUIElements';
// Utils
import {
@ -60,15 +61,15 @@ class NotificationView extends PureComponent {
return (
<FlatList
data={data}
initialNumToRender={data && data.length}
maxToRenderPerBatch={data && data.length}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<NotificationLine
notification={item}
handleOnPressNotification={navigateToNotificationRoute}
/>
)}
initialNumToRender={data.length}
maxToRenderPerBatch={data.length}
keyExtractor={item => item.id}
/>
);
};
@ -154,18 +155,12 @@ class NotificationView extends PureComponent {
const {
readAllNotification,
getActivities,
loading,
readAllNotificationLoading,
isNotificationRefreshing,
isDarkTheme,
} = this.props;
const { filters, selectedFilter } = this.state;
const _notifications = this._getNotificationsArrays();
if (_notifications.length === 0) {
return this._getActivityIndicator();
}
return (
<View style={styles.container}>
<FilterBar
@ -177,47 +172,39 @@ class NotificationView extends PureComponent {
rightIconType="MaterialIcons"
onRightIconPress={readAllNotification}
/>
<ScrollView
style={styles.scrollView}
onScroll={(e) => {
let paddingToBottom = 1;
paddingToBottom += e.nativeEvent.layoutMeasurement.height;
if (
e.nativeEvent.contentOffset.y >= e.nativeEvent.contentSize.height - paddingToBottom
&& !loading
) {
getActivities(selectedFilter, true);
}
}}
>
<FlatList
data={_notifications}
refreshing={readAllNotificationLoading}
onRefresh={() => null}
refreshControl={(
<RefreshControl
refreshing={readAllNotificationLoading}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
)}
renderItem={({ item, index }) => (
<Fragment>
<ContainerHeader
hasSeperator={index !== 0}
isBoldTitle
title={item.title}
key={item.title}
{ _notifications.length === 0 && true
? <ListPlaceHolder />
: (
<FlatList
data={_notifications}
refreshing={isNotificationRefreshing}
onRefresh={() => getActivities()}
keyExtractor={item => item.title}
onEndReached={() => getActivities(selectedFilter, true)}
ListFooterComponent={this._renderFooterLoading}
refreshControl={(
<RefreshControl
refreshing={isNotificationRefreshing}
progressBackgroundColor="#357CE6"
tintColor={!isDarkTheme ? '#357ce6' : '#96c0ff'}
titleColor="#fff"
colors={['#fff']}
/>
{this._renderList(item.notifications)}
</Fragment>
)}
keyExtractor={item => item.title}
ListFooterComponent={this._renderFooterLoading}
/>
</ScrollView>
renderItem={({ item, index }) => (
<Fragment>
<ContainerHeader
hasSeperator={index !== 0}
isBoldTitle
title={item.title}
key={item.title}
/>
{this._renderList(item.notifications)}
</Fragment>
)}
/>
)
}
</View>
);
}

View File

@ -7,9 +7,13 @@ import { getUser, getUserPoints, claim } from '../../../providers/esteem/ePoint'
// Constant
import POINTS from '../../../constants/options/points';
// Component
import PointsView from '../view/pointsView';
// Constants
import ROUTES from '../../../constants/routeNames';
/*
* Props Name Description Value
*@props --> props name here description here Value Type Here
@ -21,19 +25,31 @@ class PointsContainer extends Component {
super(props);
this.state = {
userPoints: {},
userActivities: {},
userActivities: null,
refreshing: false,
isClaiming: false,
isLoading: true,
};
}
// Component Life Cycle Functions
componentDidMount() {
this._fetchuserPointActivities();
const { username } = this.props;
this._fetchuserPointActivities(username);
this.fetchInterval = setInterval(this._fetchuserPointActivities, 360000);
}
componentWillReceiveProps(nextProps) {
const { username } = this.props;
if ((nextProps.activeBottomTab === ROUTES.TABBAR.POINTS && nextProps.username)
|| (nextProps.username !== username && nextProps.username)) {
this._fetchuserPointActivities(nextProps.username);
}
}
componentWillUnmount() {
clearInterval(this.fetchInterval);
}
@ -47,9 +63,8 @@ class PointsContainer extends Component {
textKey: POINTS[item.type].textKey,
}));
_fetchuserPointActivities = async () => {
const { username } = this.props;
_fetchuserPointActivities = async (username) => {
if (!username) return;
this.setState({ refreshing: true });
await getUser(username)
@ -72,19 +87,23 @@ class PointsContainer extends Component {
Alert.alert(err);
});
this.setState({ refreshing: false });
this.setState({
refreshing: false,
isLoading: false,
});
};
_claimPoints = async () => {
const { username } = this.props;
this.setState({ isClaiming: true });
await claim(username)
.then(() => {
this._fetchuserPointActivities();
this._fetchuserPointActivities(username);
})
.catch((err) => {
Alert.alert(err);
.catch((error) => {
Alert.alert(`Fetching data from server failed, please try again or notify us at info@esteem.app \n${error.message.substr(0, 20)}`);
});
this.setState({ isClaiming: false });
@ -92,18 +111,19 @@ class PointsContainer extends Component {
render() {
const {
userPoints, userActivities, isDarkTheme, refreshing, isClaiming,
isClaiming, isDarkTheme, isLoading, refreshing, userActivities, userPoints,
} = this.state;
return (
<PointsView
userPoints={userPoints}
userActivities={userActivities}
isDarkTheme={isDarkTheme}
fetchUserActivity={this._fetchuserPointActivities}
refreshing={refreshing}
isClaiming={isClaiming}
claimPoints={this._claimPoints}
fetchUserActivity={this._fetchuserPointActivities}
isClaiming={isClaiming}
isDarkTheme={isDarkTheme}
isLoading={isLoading}
refreshing={refreshing}
userActivities={userActivities}
userPoints={userPoints}
/>
);
}
@ -112,6 +132,7 @@ class PointsContainer extends Component {
const mapStateToProps = state => ({
username: state.account.currentAccount.name,
isDarkTheme: state.application.isDarkTheme,
activeBottomTab: state.ui.activeBottomTab,
});
export default connect(mapStateToProps)(PointsContainer);

View File

@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import React, { Component, Fragment } from 'react';
import {
Text, View, FlatList, ScrollView, RefreshControl, TouchableOpacity,
@ -6,7 +7,7 @@ import { injectIntl } from 'react-intl';
import { Popover, PopoverController } from 'react-native-modal-popover';
// Components
import { LineBreak, WalletLineItem } from '../../basicUIElements';
import { LineBreak, WalletLineItem, ListPlaceHolder } from '../../basicUIElements';
import { IconButton } from '../../iconButton';
import { Icon } from '../../icon';
import { MainButton } from '../../mainButton';
@ -50,24 +51,32 @@ class PointsView extends Component {
);
}
render() {
const {
userActivities, userPoints, intl, isClaiming, claimPoints,
} = this.props;
// TODO: this feature temporarily closed.
const isActiveIcon = false;
_renderLoading = () => {
const { isLoading, intl } = this.props;
return (
if (isLoading) {
return <ListPlaceHolder />;
}
return <Text style={styles.subText}>{intl.formatMessage({ id: 'points.no_activity' })}</Text>;
}
<Fragment>
<LineBreak height={12} />
<ScrollView
style={styles.scrollContainer}
refreshControl={this.refreshControl()}
>
<Text style={styles.pointText}>{userPoints.points}</Text>
<Text style={styles.subText}>eSteem Points</Text>
{userPoints.unclaimed_points > 0
render() {
const {
claimPoints, intl, isClaiming, userActivities, userPoints,
} = this.props;
// TODO: this feature temporarily closed.
const isActiveIcon = false;
return (
<Fragment>
<LineBreak height={12} />
<ScrollView
style={styles.scrollContainer}
refreshControl={this.refreshControl()}
>
<Text style={styles.pointText}>{userPoints.points}</Text>
<Text style={styles.subText}>eSteem Points</Text>
{userPoints.unclaimed_points > 0
&& (
<MainButton
isLoading={isClaiming}
@ -86,89 +95,93 @@ class PointsView extends Component {
)
}
<View style={styles.iconsWrapper}>
<FlatList
style={styles.iconsList}
data={POINTS_KEYS}
horizontal
renderItem={({ item }) => (
<PopoverController key={item.type}>
{({
openPopover, closePopover, popoverVisible, setPopoverAnchor, popoverAnchorRect,
}) => (
<View styles={styles.iconWrapper} key={item.type}>
<View style={styles.iconWrapper}>
<TouchableOpacity
ref={setPopoverAnchor}
onPress={openPopover}
>
<IconButton
iconStyle={styles.icon}
style={styles.iconButton}
iconType={POINTS[item.type].iconType}
name={POINTS[item.type].icon}
badgeCount={POINTS[item.type].point}
badgeStyle={styles.badge}
badgeTextStyle={styles.badgeText}
disabled
/>
</TouchableOpacity>
</View>
<Text style={styles.subText}>{intl.formatMessage({ id: POINTS[item.type].nameKey })}</Text>
<Popover
backgroundStyle={styles.overlay}
contentStyle={styles.popoverDetails}
arrowStyle={styles.arrow}
visible={popoverVisible}
onClose={() => {
closePopover();
}}
fromRect={popoverAnchorRect}
placement="top"
supportedOrientations={['portrait', 'landscape']}
>
<View style={styles.popoverWrapper}>
<Text style={styles.popoverText}>{intl.formatMessage({ id: POINTS[item.type].descriptionKey })}</Text>
</View>
</Popover>
</View>
)}
</PopoverController>
)}
/>
</View>
<View style={styles.iconsWrapper}>
<FlatList
style={styles.iconsList}
data={POINTS_KEYS}
horizontal
renderItem={({ item }) => (
<PopoverController key={item.type}>
{({
openPopover, closePopover, popoverVisible, setPopoverAnchor, popoverAnchorRect,
}) => (
<View styles={styles.iconWrapper} key={item.type}>
<View style={styles.iconWrapper}>
<TouchableOpacity
ref={setPopoverAnchor}
onPress={openPopover}
>
<IconButton
iconStyle={styles.icon}
style={styles.iconButton}
iconType={POINTS[item.type].iconType}
name={POINTS[item.type].icon}
badgeCount={POINTS[item.type].point}
badgeStyle={styles.badge}
badgeTextStyle={styles.badgeText}
disabled
/>
</TouchableOpacity>
</View>
<Text style={styles.subText}>
{intl.formatMessage({ id: POINTS[item.type].nameKey })}
</Text>
<Popover
backgroundStyle={styles.overlay}
contentStyle={styles.popoverDetails}
arrowStyle={styles.arrow}
visible={popoverVisible}
onClose={() => {
closePopover();
}}
fromRect={popoverAnchorRect}
placement="top"
supportedOrientations={['portrait', 'landscape']}
>
<View style={styles.popoverWrapper}>
<Text style={styles.popoverText}>
{intl.formatMessage({ id: POINTS[item.type].descriptionKey })}
</Text>
</View>
</Popover>
</View>
)}
</PopoverController>
)}
/>
</View>
<View style={styles.listWrapper}>
{userActivities && userActivities.length < 1
? <Text style={styles.subText}>{intl.formatMessage({ id: 'points.no_activity' })}</Text>
: (
<FlatList
data={userActivities}
renderItem={({ item, index }) => (
<WalletLineItem
key={item.id.toString()}
index={index + 1}
text={intl.formatMessage({ id: item.textKey })}
description={getTimeFromNow(item.created)}
isCircleIcon
isThin
isBlackText
iconName={item.icon}
iconType={item.iconType}
rightText={`${item.amount} ESTM`}
/>
)}
/>
)
}
</View>
<View style={styles.listWrapper}>
{!userActivities
? this._renderLoading()
: (
<FlatList
data={userActivities}
renderItem={({ item, index }) => (
<WalletLineItem
key={item.id.toString()}
index={index + 1}
text={intl.formatMessage({ id: item.textKey })}
description={getTimeFromNow(item.created)}
isCircleIcon
isThin
isBlackText
iconName={item.icon}
iconType={item.iconType}
rightText={`${item.amount} ESTM`}
/>
)}
/>
)
}
</View>
</ScrollView>
</Fragment>
</ScrollView>
</Fragment>
);
}
);
}
}
export default injectIntl(PointsView);

View File

@ -59,7 +59,7 @@ export const getUserPoints = username => new Promise((resolve) => {
});
});
export const claim = username => new Promise((resolve) => {
export const claim = username => new Promise((resolve, reject) => {
ePointApi
.put('/claim', {
us: `${username}`,
@ -68,6 +68,6 @@ export const claim = username => new Promise((resolve) => {
resolve(res.data);
})
.catch((error) => {
Alert.alert('Error', error.message);
reject(error);
});
});

View File

@ -17,8 +17,7 @@ class NotificationContainer extends Component {
this.state = {
notifications: [],
lastNotificationId: null,
notificationLoading: false,
readAllNotificationLoading: false,
isNotificationRefreshing: false,
selectedFilter: 'activities',
};
}
@ -27,36 +26,38 @@ class NotificationContainer extends Component {
const { username } = this.props;
if (username) {
this._getAvtivities();
this._getAvtivities(username);
}
}
componentWillReceiveProps(nextProps) {
const { selectedFilter } = this.state;
const { username } = this.props;
if (nextProps.activeBottomTab === ROUTES.TABBAR.NOTIFICATION && nextProps.username) {
this._getAvtivities(selectedFilter);
if ((nextProps.activeBottomTab === ROUTES.TABBAR.NOTIFICATION && nextProps.username)
|| (nextProps.username !== username && nextProps.username)) {
this._getAvtivities(nextProps.username, selectedFilter);
}
}
_getAvtivities = (type = null, loadMore = false) => {
const { username } = this.props;
_getAvtivities = (user, type = null, loadMore = false) => {
const { lastNotificationId, notifications } = this.state;
const since = loadMore ? lastNotificationId : null;
const { username } = this.props;
this.setState({ notificationLoading: true });
this.setState({ isNotificationRefreshing: true });
getActivities({ user: username, type, since })
getActivities({ user: user || username, type, since })
.then((res) => {
const lastId = [...res].pop().id;
this.setState({
notifications: loadMore ? [...notifications, ...res] : res,
lastNotificationId: lastId,
notificationLoading: false,
isNotificationRefreshing: false,
});
})
.catch(() => this.setState({ notificationLoading: false }));
.catch(() => this.setState({ isNotificationRefreshing: false }));
};
_navigateToNotificationRoute = (data) => {
@ -98,12 +99,12 @@ class NotificationContainer extends Component {
const { username, dispatch } = this.props;
const { notifications } = this.state;
this.setState({ readAllNotificationLoading: true });
this.setState({ isNotificationRefreshing: true });
markActivityAsRead(username).then((result) => {
dispatch(updateUnreadActivityCount(result.unread));
const updatedNotifications = notifications.map(item => ({ ...item, read: 1 }));
this.setState({ notifications: updatedNotifications, readAllNotificationLoading: false });
this.setState({ notifications: updatedNotifications, isNotificationRefreshing: false });
});
};
@ -121,8 +122,7 @@ class NotificationContainer extends Component {
const { isLoggedIn } = this.props;
const {
notifications,
notificationLoading,
readAllNotificationLoading,
isNotificationRefreshing,
isDarkTheme,
} = this.state;
@ -134,8 +134,7 @@ class NotificationContainer extends Component {
navigateToNotificationRoute={this._navigateToNotificationRoute}
readAllNotification={this._readAllNotification}
handleLoginPress={this._handleOnPressLogin}
notificationLoading={notificationLoading}
readAllNotificationLoading={readAllNotificationLoading}
isNotificationRefreshing={isNotificationRefreshing}
isLoggedIn={isLoggedIn}
changeSelectedFilter={this._changeSelectedFilter}
/>

View File

@ -29,8 +29,7 @@ class NotificationScreen extends PureComponent {
readAllNotification,
handleLoginPress,
isLoggedIn,
notificationLoading,
readAllNotificationLoading,
isNotificationRefreshing,
changeSelectedFilter,
} = this.props;
@ -55,8 +54,7 @@ class NotificationScreen extends PureComponent {
notifications={notifications}
navigateToNotificationRoute={navigateToNotificationRoute}
readAllNotification={readAllNotification}
readAllNotificationLoading={readAllNotificationLoading}
loading={notificationLoading}
isNotificationRefreshing={isNotificationRefreshing}
changeSelectedFilter={changeSelectedFilter}
/>
) : (