This commit is contained in:
Feruz 2018-08-01 22:24:34 +03:00
parent 38ce6a741f
commit ce6376d50d
25 changed files with 2880 additions and 2271 deletions

View File

@ -1,280 +1,341 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { StyleSheet, Image, TouchableOpacity } from 'react-native'; import { StyleSheet, Image, TouchableOpacity } from 'react-native';
import { Button, Card, CardItem, import {
Left, Right, Thumbnail, View, Button,
Icon, Body, Text, Container, Content } from 'native-base'; Card,
CardItem,
Left,
Right,
Thumbnail,
View,
Icon,
Body,
Text,
Container,
Content,
} from 'native-base';
class PostCard extends React.Component { class PostCard extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {};
} }
}
componentDidMount() { componentDidMount() {}
} onError() {}
onError() { render() {
return (
} <Card style={styles.post}>
<CardItem style={styles.header}>
render() { <Left>
return ( <TouchableOpacity
<Card style={styles.post}> onPress={() =>
<CardItem style={styles.header}> this.props.navigate('Author', {
<Left> author: this.props.content.author,
<TouchableOpacity onPress={() => this.props.navigate('Author', { author: this.props.content.author })}> })
<Thumbnail }
style={styles.avatar} >
source={{ uri: this.props.content.avatar }} /> <Thumbnail
</TouchableOpacity> style={styles.avatar}
<Body style={styles.body}> source={{ uri: this.props.content.avatar }}
<View style={styles.author}> />
<Text style={styles.authorName}>{ this.props.content.author }</Text> </TouchableOpacity>
</View> <Body style={styles.body}>
<View style={ styles.badge }> <View style={styles.author}>
<Text style={styles.text}>{ this.props.content.author_reputation }</Text> <Text style={styles.authorName}>
</View> {this.props.content.author}
<View style={ styles.category }> </Text>
<Text style={styles.categoryText}>{ this.props.content.category }</Text> </View>
</View> <View style={styles.badge}>
<Text style={styles.timeAgo} note> { this.props.content.created } </Text> <Text style={styles.text}>
</Body> {this.props.content.author_reputation}
</Left> </Text>
<Right> </View>
<Icon name='md-more'/> <View style={styles.category}>
</Right> <Text style={styles.categoryText}>
</CardItem> {this.props.content.category}
<Image </Text>
source={{ uri: this.props.content.image }} </View>
defaultSource={require('../assets/no_image.png')} <Text style={styles.timeAgo} note>
style={styles.image}/> {' '}
<CardItem> {this.props.content.created}{' '}
<Body> </Text>
<Text style={styles.title}> </Body>
{ this.props.content.title } </Left>
</Text> <Right>
<Text style={styles.summary}> <Icon name="md-more" />
{ this.props.content.summary } </Right>
</Text> </CardItem>
</Body> <Image
</CardItem> source={{ uri: this.props.content.image }}
<CardItem> defaultSource={require('../assets/no_image.png')}
<Left> style={styles.image}
<TouchableOpacity start style={styles.upvoteButton}> />
<Icon style={styles.upvoteIcon} active name="ios-arrow-dropup-outline" /> <CardItem>
</TouchableOpacity> <Body>
<TouchableOpacity style={styles.payoutButton}> <Text style={styles.title}>
<Text style={styles.payout}>${ this.props.content.pending_payout_value }</Text> {this.props.content.title}
<Icon name='md-arrow-dropdown' style={styles.payoutIcon} /> </Text>
</TouchableOpacity> <Text style={styles.summary}>
</Left> {this.props.content.summary}
<Right> </Text>
<TouchableOpacity start style={styles.commentButton}> </Body>
<Icon style={styles.commentIcon} active name="ios-chatbubbles-outline" /> </CardItem>
<Text style={styles.comment}>{ this.props.content.children }</Text> <CardItem>
</TouchableOpacity> <Left>
</Right> <TouchableOpacity start style={styles.upvoteButton}>
</CardItem> <Icon
{ this.props.content.top_likers ? ( style={styles.upvoteIcon}
<CardItem style={styles.topLikers}> active
<Thumbnail source={{ uri: `https://steemitimages.com/u/${this.props.content.top_likers[0]}/avatar/small` }} style={styles.likers_1}/> name="ios-arrow-dropup-outline"
<Thumbnail source={{ uri: `https://steemitimages.com/u/${this.props.content.top_likers[1]}/avatar/small` }} style={styles.likers_2}/> />
<Thumbnail source={{ uri: `https://steemitimages.com/u/${this.props.content.top_likers[2]}/avatar/small` }} style={styles.likers_3}/> </TouchableOpacity>
<Text style={styles.footer}> <TouchableOpacity style={styles.payoutButton}>
@{ this.props.content.top_likers[0] }, <Text style={styles.payout}>
@{ this.props.content.top_likers[1] }, ${this.props.content.pending_payout_value}
@{ this.props.content.top_likers[2] } </Text>
<Text style={styles.footer}> & </Text> <Icon
{ this.props.content.vote_count - this.props.content.top_likers.length } others like this name="md-arrow-dropdown"
</Text> style={styles.payoutIcon}
</CardItem> />
) : ( </TouchableOpacity>
<CardItem> </Left>
<Text style={styles.footer}> <Right>
{ this.props.content.vote_count } likes <TouchableOpacity start style={styles.commentButton}>
</Text> <Icon
</CardItem> style={styles.commentIcon}
)} active
</Card> name="ios-chatbubbles-outline"
) />
} <Text style={styles.comment}>
{this.props.content.children}
</Text>
</TouchableOpacity>
</Right>
</CardItem>
{this.props.content.top_likers ? (
<CardItem style={styles.topLikers}>
<Thumbnail
source={{
uri: `https://steemitimages.com/u/${
this.props.content.top_likers[0]
}/avatar/small`,
}}
style={styles.likers_1}
/>
<Thumbnail
source={{
uri: `https://steemitimages.com/u/${
this.props.content.top_likers[1]
}/avatar/small`,
}}
style={styles.likers_2}
/>
<Thumbnail
source={{
uri: `https://steemitimages.com/u/${
this.props.content.top_likers[2]
}/avatar/small`,
}}
style={styles.likers_3}
/>
<Text style={styles.footer}>
@{this.props.content.top_likers[0]}, @
{this.props.content.top_likers[1]}, @
{this.props.content.top_likers[2]}
<Text style={styles.footer}> & </Text>
{this.props.content.vote_count -
this.props.content.top_likers.length}{' '}
others like this
</Text>
</CardItem>
) : (
<CardItem>
<Text style={styles.footer}>
{this.props.content.vote_count} likes
</Text>
</CardItem>
)}
</Card>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
post: { post: {
shadowColor: 'white', shadowColor: 'white',
padding: 0, padding: 0,
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 0, marginTop: 0,
marginBottom: 0, marginBottom: 0,
borderWidth: 0, borderWidth: 0,
borderColor: 'white', borderColor: 'white',
borderRadius: 5, borderRadius: 5,
}, },
avatar: { avatar: {
width: 30, width: 30,
height: 30, height: 30,
borderRadius: 15, borderRadius: 15,
borderColor: 'lightgray', borderColor: 'lightgray',
borderWidth: 1 borderWidth: 1,
}, },
author: { author: {
backgroundColor: 'white', backgroundColor: 'white',
alignSelf: 'flex-start', alignSelf: 'flex-start',
paddingVertical: 5 paddingVertical: 5,
}, },
timeAgo: { timeAgo: {
alignSelf: 'center', alignSelf: 'center',
fontSize: 9, fontSize: 9,
fontWeight: '100', fontWeight: '100',
marginHorizontal: 3 marginHorizontal: 3,
}, },
authorName: { authorName: {
color: '#222', color: '#222',
fontWeight: '600', fontWeight: '600',
fontSize: 10 fontSize: 10,
}, },
upvoteButton: { upvoteButton: {
margin: 0, margin: 0,
flexDirection: 'row', flexDirection: 'row',
paddingVertical: 0 paddingVertical: 0,
}, },
upvoteIcon: { upvoteIcon: {
alignSelf: 'flex-start', alignSelf: 'flex-start',
fontSize: 20, fontSize: 20,
color: '#007ee5', color: '#007ee5',
margin:0, margin: 0,
width: 18 width: 18,
}, },
payout: { payout: {
alignSelf: 'center', alignSelf: 'center',
fontSize: 10, fontSize: 10,
color: '#626262', color: '#626262',
marginLeft: 3 marginLeft: 3,
}, },
payoutIcon: { payoutIcon: {
fontSize: 15, fontSize: 15,
marginHorizontal: 3, marginHorizontal: 3,
color: '#a0a0a0', color: '#a0a0a0',
alignSelf: 'center' alignSelf: 'center',
}, },
payoutButton: { payoutButton: {
flexDirection: 'row', flexDirection: 'row',
alignSelf: 'flex-start', alignSelf: 'flex-start',
paddingVertical: 2 paddingVertical: 2,
}, },
commentButton: { commentButton: {
padding: 0, padding: 0,
margin: 0, margin: 0,
flexDirection: 'row' flexDirection: 'row',
}, },
comment: { comment: {
alignSelf: 'center', alignSelf: 'center',
fontSize: 10, fontSize: 10,
color: '#626262', color: '#626262',
marginLeft: 3 marginLeft: 3,
}, },
commentIcon: { commentIcon: {
alignSelf: 'flex-start', alignSelf: 'flex-start',
fontSize: 20, fontSize: 20,
color: '#007ee5', color: '#007ee5',
margin:0, margin: 0,
width: 20 width: 20,
}, },
title: { title: {
fontSize: 12, fontSize: 12,
fontWeight: '500', fontWeight: '500',
marginVertical: 5 marginVertical: 5,
}, },
summary: { summary: {
fontSize: 10, fontSize: 10,
fontWeight: '200', fontWeight: '200',
overflow: 'hidden' overflow: 'hidden',
}, },
header: { header: {
height: 50 height: 50,
}, },
body: { body: {
justifyContent: 'flex-start', justifyContent: 'flex-start',
flexDirection: 'row' flexDirection: 'row',
}, },
image: { image: {
margin: 0, margin: 0,
width: '100%', width: '100%',
height: 160, height: 160,
}, },
badge: { badge: {
alignSelf: 'center', alignSelf: 'center',
borderColor: 'lightgray', borderColor: 'lightgray',
borderWidth: 1, borderWidth: 1,
borderRadius: 10, borderRadius: 10,
width: 15, width: 15,
height: 15, height: 15,
padding: 2, padding: 2,
backgroundColor: 'lightgray', backgroundColor: 'lightgray',
marginHorizontal: 5 marginHorizontal: 5,
}, },
category: { category: {
alignSelf: 'center', alignSelf: 'center',
borderRadius: 10, borderRadius: 10,
height: 15, height: 15,
backgroundColor: '#007EE5', backgroundColor: '#007EE5',
paddingHorizontal: 5, paddingHorizontal: 5,
paddingVertical: 1.5 paddingVertical: 1.5,
}, },
categoryText: { categoryText: {
fontSize: 9, fontSize: 9,
color: 'white', color: 'white',
fontWeight: '600' fontWeight: '600',
}, },
text: { text: {
fontSize: 7, fontSize: 7,
alignSelf: 'center', alignSelf: 'center',
textAlignVertical: 'center', textAlignVertical: 'center',
color: 'white', color: 'white',
fontWeight: 'bold' fontWeight: 'bold',
}, },
topLikers: { topLikers: {
backgroundColor: '#f8f8f8', backgroundColor: '#f8f8f8',
borderWidth: 0, borderWidth: 0,
padding:0 padding: 0,
}, },
likers_1: { likers_1: {
width: 14, width: 14,
height: 14, height: 14,
borderRadius: 7, borderRadius: 7,
borderWidth: 0.5, borderWidth: 0.5,
borderColor: 'lightgray', borderColor: 'lightgray',
marginVertical: -5, marginVertical: -5,
}, },
likers_2: { likers_2: {
width: 14, width: 14,
height: 14, height: 14,
borderRadius: 7, borderRadius: 7,
borderWidth: 0.5, borderWidth: 0.5,
borderColor: 'lightgray', borderColor: 'lightgray',
marginVertical: -5, marginVertical: -5,
marginLeft: -3 marginLeft: -3,
}, },
likers_3: { likers_3: {
width: 14, width: 14,
height: 14, height: 14,
borderRadius: 7, borderRadius: 7,
borderWidth: 0.5, borderWidth: 0.5,
borderColor: 'lightgray', borderColor: 'lightgray',
marginVertical: -5, marginVertical: -5,
marginLeft: -3 marginLeft: -3,
}, },
footer: { footer: {
marginLeft: 5, marginLeft: 5,
fontSize: 7, fontSize: 7,
fontWeight: '100', fontWeight: '100',
color: '#777777' color: '#777777',
} },
}); });
export default PostCard; export default PostCard;

View File

@ -1,6 +1,6 @@
import { import {
createStackNavigator, createStackNavigator,
createBottomTabNavigator createBottomTabNavigator,
} from 'react-navigation'; } from 'react-navigation';
import React from 'react'; import React from 'react';
@ -19,196 +19,196 @@ import SinglePostPage from '../screens/single-post/Post';
import LoginPage from '../screens/login/Login'; import LoginPage from '../screens/login/Login';
import AuthorPage from '../screens/author-profile/Author'; import AuthorPage from '../screens/author-profile/Author';
const HomeScreen = ({ navigation }) => ( const HomeScreen = ({ navigation }) => <HomePage navigation={navigation} />;
<HomePage navigation={navigation}></HomePage>
);
HomeScreen.navigationOptions = { HomeScreen.navigationOptions = {
tabBarLabel: 'Home', tabBarLabel: 'Home',
title: 'Home', title: 'Home',
tabBarIcon: ({ tintColor, focused }) => ( tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons <MaterialCommunityIcons
name={focused ? 'home' : 'home'} name={focused ? 'home' : 'home'}
size={26} size={26}
style={{ color: tintColor }} style={{ color: tintColor }}
/> />
), ),
}; };
const ProfileScreen = ({ navigation }) => ( const ProfileScreen = ({ navigation }) => (
<ProfilePage navigation={navigation}></ProfilePage> <ProfilePage navigation={navigation} />
); );
ProfileScreen.navigationOptions = { ProfileScreen.navigationOptions = {
tabBarLabel: 'Profile', tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor, focused }) => ( tabBarIcon: ({ tintColor, focused }) => (
<Ionicons <Ionicons
name={focused ? 'md-contact' : 'md-contact'} name={focused ? 'md-contact' : 'md-contact'}
size={26} size={26}
style={{ color: tintColor }} style={{ color: tintColor }}
/> />
), ),
}; };
const EditorScreen = ({ navigation }) => ( const EditorScreen = ({ navigation }) => <EditorPage navigation={navigation} />;
<EditorPage navigation={navigation}></EditorPage>
);
EditorScreen.navigationOptions = { EditorScreen.navigationOptions = {
tabBarLabel: 'Editor', tabBarLabel: 'Editor',
tabBarIcon: ({ tintColor, focused }) => ( tabBarIcon: ({ tintColor, focused }) => (
<Entypo <Entypo
name={focused ? 'pencil' : 'pencil'} name={focused ? 'pencil' : 'pencil'}
size={26} size={26}
style={styles.post} style={styles.post}
style={{ color: tintColor }} style={{ color: tintColor }}
/> />
), ),
}; };
const WalletScreen = ({ navigation }) => ( const WalletScreen = ({ navigation }) => <WalletPage navigation={navigation} />;
<WalletPage navigation={navigation}></WalletPage>
);
WalletScreen.navigationOptions = { WalletScreen.navigationOptions = {
tabBarLabel: 'Settings', tabBarLabel: 'Settings',
tabBarIcon: ({ tintColor, focused }) => ( tabBarIcon: ({ tintColor, focused }) => (
<Entypo <Entypo
name={focused ? 'wallet' : 'wallet'} name={focused ? 'wallet' : 'wallet'}
size={26} size={26}
style={{ color: tintColor }} style={{ color: tintColor }}
/> />
), ),
}; };
const NotificationScreen = ({ navigation }) => ( const NotificationScreen = ({ navigation }) => (
<NotificationPage navigation={navigation}></NotificationPage> <NotificationPage navigation={navigation} />
); );
NotificationScreen.navigationOptions = { NotificationScreen.navigationOptions = {
tabBarLabel: 'Notifications', tabBarLabel: 'Notifications',
tabBarIcon: ({ tintColor, focused }) => ( tabBarIcon: ({ tintColor, focused }) => (
<Ionicons <Ionicons
name={focused ? 'ios-notifications' : 'ios-notifications'} name={focused ? 'ios-notifications' : 'ios-notifications'}
size={26} size={26}
style={{ color: tintColor }} style={{ color: tintColor }}
/> />
), ),
}; };
const SinglePostScreen = ({ navigation }) => ( const SinglePostScreen = ({ navigation }) => (
<SinglePostPage navigation={navigation}></SinglePostPage> <SinglePostPage navigation={navigation} />
); );
const LoginScreen = ({ navigation }) => ( const LoginScreen = ({ navigation }) => <LoginPage navigation={navigation} />;
<LoginPage navigation={navigation}></LoginPage>
);
const AuthorScreen = ({ navigation }) => ( const AuthorScreen = ({ navigation }) => <AuthorPage navigation={navigation} />;
<AuthorPage navigation={navigation}></AuthorPage>
);
const BottomTabs = createBottomTabNavigator( const BottomTabs = createBottomTabNavigator(
{ {
Home: { Home: {
screen: HomeScreen, screen: HomeScreen,
path: '', path: '',
},
Profile: {
screen: ProfileScreen,
path: 'profile',
},
Editor: {
screen: EditorScreen,
path: 'editor',
},
Wallet: {
screen: WalletScreen,
path: 'wallet',
},
Notifications: {
screen: NotificationScreen,
path: 'settings',
},
}, },
Profile: { {
screen: ProfileScreen, tabBarOptions: {
path: 'profile', activeTintColor: '#373c3f',
}, inactiveTintColor: '#AFB1B3',
Editor: { style: {
screen: EditorScreen, backgroundColor: 'white',
path: 'editor', borderTopColor: '#dedede',
}, borderWidth: 0,
Wallet: { },
screen: WalletScreen, showLabel: false,
path: 'wallet', },
},
Notifications: {
screen: NotificationScreen,
path: 'settings',
} }
},
{
tabBarOptions: {
activeTintColor: '#373c3f',
inactiveTintColor: '#AFB1B3',
style: {
backgroundColor: 'white',
borderTopColor: '#dedede',
borderWidth: 0
},
showLabel: false,
}
},
); );
BottomTabs.navigationOptions = ({navigation})=> ({ BottomTabs.navigationOptions = ({ navigation }) => ({
header: null, header: null,
style: { style: {
backgroundColor: 'white', backgroundColor: 'white',
}, },
}); });
const StacksOverTabs = createStackNavigator({ const StacksOverTabs = createStackNavigator(
Root: { {
screen: BottomTabs, Root: {
}, screen: BottomTabs,
Post: { },
screen: SinglePostScreen, Post: {
path: '/:category/:user/:permlink', screen: SinglePostScreen,
navigationOptions: ({ navigation }) => ({ path: '/:category/:user/:permlink',
header: null navigationOptions: ({ navigation }) => ({
}), header: null,
}, }),
Login: { },
screen: LoginScreen, Login: {
path: '/login', screen: LoginScreen,
}, path: '/login',
Author: { },
screen: AuthorScreen, Author: {
path: '/author', screen: AuthorScreen,
} path: '/author',
}, { },
headerMode: 'none' },
}); {
headerMode: 'none',
}
);
class Tabs extends React.Component { class Tabs extends React.Component {
static router = StacksOverTabs.router; static router = StacksOverTabs.router;
_s0; _s0;
_s1; _s1;
_s2; _s2;
_s3; _s3;
componentDidMount() { componentDidMount() {
this._s0 = this.props.navigation.addListener('willFocus', this._onAction); this._s0 = this.props.navigation.addListener(
this._s1 = this.props.navigation.addListener('didFocus', this._onAction); 'willFocus',
this._s2 = this.props.navigation.addListener('willBlur', this._onAction); this._onAction
this._s3 = this.props.navigation.addListener('didBlur', this._onAction); );
} this._s1 = this.props.navigation.addListener(
componentWillUnmount() { 'didFocus',
this._s0.remove(); this._onAction
this._s1.remove(); );
this._s2.remove(); this._s2 = this.props.navigation.addListener(
this._s3.remove(); 'willBlur',
} this._onAction
_onAction = a => { );
console.log('TABS EVENT', a.type, a); this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
}; }
render() { componentWillUnmount() {
return <StacksOverTabs navigation={this.props.navigation} />; this._s0.remove();
} this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onAction = a => {
console.log('TABS EVENT', a.type, a);
};
render() {
return <StacksOverTabs navigation={this.props.navigation} />;
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
post: { post: {
borderWidth: 22, borderWidth: 22,
borderColor: 'blue', borderColor: 'blue',
} },
}); });
export default Tabs; export default Tabs;

View File

@ -3,103 +3,129 @@ import { getAccount } from './Dsteem';
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
export const Login = (username, password) => { export const Login = (username, password) => {
let account;
let publicKeys;
let privateKeys;
let isPassword;
let isPostingKey;
let account; return new Promise((resolve, reject) => {
let publicKeys; getAccount(username)
let privateKeys; .then(result => {
let isPassword; if (result.length < 1) {
let isPostingKey; reject(new Error('Wrong @username'));
}
return new Promise((resolve,reject) => { account = result[0];
getAccount(username).then((result) => {
if (result.length < 1) { // Public keys of user
reject(new Error('Wrong @username')); publicKeys = {
} active: account['active'].key_auths.map(x => x[0]),
memo: account['memo_key'],
account = result[0]; owner: account['owner'].key_auths.map(x => x[0]),
posting: account['posting'].key_auths.map(x => x[0]),
// Public keys of user };
publicKeys = { })
active: account['active'].key_auths.map(x => x[0]), .then(() => {
memo: account['memo_key'], try {
owner: account['owner'].key_auths.map(x => x[0]), // Set private keys of user
posting: account['posting'].key_auths.map(x => x[0]) privateKeys = {
}; active: dsteem.PrivateKey.fromLogin(
username,
}).then(() => { password,
'active'
try { ).toString(),
// Set private keys of user memo: dsteem.PrivateKey.fromLogin(
privateKeys = { username,
active: dsteem.PrivateKey.fromLogin(username, password, 'active').toString(), password,
memo: dsteem.PrivateKey.fromLogin(username, password, 'memo').toString(), 'memo'
owner: dsteem.PrivateKey.fromLogin(username, password, 'owner').toString(), ).toString(),
posting: dsteem.PrivateKey.fromLogin(username, password, 'posting').toString() owner: dsteem.PrivateKey.fromLogin(
} username,
password,
} catch (error) { 'owner'
reject(new Error('Wrong Key/Password')); ).toString(),
} posting: dsteem.PrivateKey.fromLogin(
username,
}).then(() => { password,
'posting'
try { ).toString(),
isPassword = (dsteem.PrivateKey.fromLogin(username, password, 'posting').createPublic()).toString() === (publicKeys.posting).toString() };
} catch (error) {
if (isPassword) { reject(new Error('Wrong Key/Password'));
}
let authType = { })
'username': username, .then(() => {
'auth_type': 'master_key', try {
'posting_key': privateKeys.posting, isPassword =
'active_key': privateKeys.active, dsteem.PrivateKey.fromLogin(
'memo_key': privateKeys.memo, username,
'owner_key': privateKeys.owner password,
} 'posting'
)
AsyncStorage.setItem('isLoggedIn', 'true', () => { .createPublic()
AsyncStorage.setItem('user', JSON.stringify(authType), () => { .toString() === publicKeys.posting.toString();
resolve(isPassword);
if (isPassword) {
let authType = {
username: username,
auth_type: 'master_key',
posting_key: privateKeys.posting,
active_key: privateKeys.active,
memo_key: privateKeys.memo,
owner_key: privateKeys.owner,
};
AsyncStorage.setItem('isLoggedIn', 'true', () => {
AsyncStorage.setItem(
'user',
JSON.stringify(authType),
() => {
resolve(isPassword);
}
);
});
} else {
isPostingKey =
publicKeys.posting.toString() ===
dsteem.PrivateKey.fromString(password)
.createPublic()
.toString();
let authType = {
username: username,
auth_type: 'posting_key',
posting_key: password,
};
try {
if (isPostingKey) {
AsyncStorage.setItem(
'isLoggedIn',
'true',
() => {
AsyncStorage.setItem(
'user',
JSON.stringify(authType),
() => {
resolve(isPostingKey);
}
);
}
);
} else {
reject(new Error('Wrong Key/Password'));
}
} catch (error) {
reject(new Error('Wrong Key/Password'));
}
}
} catch (error) {
reject(new Error('Wrong Key/Password'));
}
})
.catch(err => {
reject(new Error('Check your username'));
}); });
})
} else {
isPostingKey = (publicKeys.posting).toString() === (dsteem.PrivateKey.fromString(password).createPublic()).toString();
let authType = {
'username': username,
'auth_type': 'posting_key',
'posting_key': password
}
try {
if (isPostingKey) {
AsyncStorage.setItem('isLoggedIn', 'true', () => {
AsyncStorage.setItem('user', JSON.stringify(authType), () => {
resolve(isPostingKey)
})
})
} else {
reject(new Error('Wrong Key/Password'));
}
} catch (error) {
reject(new Error('Wrong Key/Password'));
}
}
} catch (error) {
reject(new Error('Wrong Key/Password'));
}
}).catch((err) => {
reject(new Error('Check your username'));
}); });
};
})
}

View File

@ -5,78 +5,84 @@ import { parsePosts } from '../../utils/PostParser';
import Remarkable from 'remarkable'; import Remarkable from 'remarkable';
var md = new Remarkable({ var md = new Remarkable({
html: true, // Enable HTML tags in source html: true, // Enable HTML tags in source
xhtmlOut: false, // Use '/' to close single tags (<br />) xhtmlOut: false, // Use '/' to close single tags (<br />)
breaks: true, // Convert '\n' in paragraphs into <br> breaks: true, // Convert '\n' in paragraphs into <br>
langPrefix: 'language-', // CSS language prefix for fenced blocks langPrefix: 'language-', // CSS language prefix for fenced blocks
linkify: true, // Autoconvert URL-like text to links linkify: true, // Autoconvert URL-like text to links
// Enable some language-neutral replacement + quotes beautification // Enable some language-neutral replacement + quotes beautification
typographer: true, typographer: true,
// Double + single quotes replacement pairs, when typographer enabled, // Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Set doubles to '«»' for Russian, '„“' for German. // and smartquotes on. Set doubles to '«»' for Russian, '„“' for German.
quotes: '“”‘’' quotes: '“”‘’',
}); });
/**
/** * @method getAccount get account data
* @method getAccount get account data * @param user username
* @param user username */
*/ export const getAccount = user => {
export const getAccount = (user) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let account = client.database.getAccounts([user]); let account = client.database.getAccounts([user]);
resolve(account); resolve(account);
}) });
} };
/** /**
* @method getPosts get posts method * @method getPosts get posts method
* @param by get discussions by trending, created, active etc. * @param by get discussions by trending, created, active etc.
* @param query tag, limit, start_author?, start_permalink? * @param query tag, limit, start_author?, start_permalink?
*/ */
export const getPosts = async (by, query) => { export const getPosts = async (by, query) => {
let posts = await client.database.getDiscussions(by, query); let posts = await client.database.getDiscussions(by, query);
posts = await parsePosts(posts); posts = await parsePosts(posts);
return posts; return posts;
} };
/** /**
* @method getUser get user data * @method getUser get user data
* @param user post author * @param user post author
* @param permlink post permlink * @param permlink post permlink
*/ */
export const getPost = (user, permlink) => { export const getPost = (user, permlink) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let post = client.database.call('get_content',[user,permlink]); let post = client.database.call('get_content', [user, permlink]);
resolve(post); resolve(post);
}); });
} };
/** /**
* @method getUser get user data * @method getUser get user data
* @param user post author * @param user post author
* @param permlink post permlink * @param permlink post permlink
*/ */
export const getComments = (user, permlink) => { export const getComments = (user, permlink) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let comments = client.database.call('get_content_replies',[user, permlink]); let comments = client.database.call('get_content_replies', [
resolve(comments); user,
permlink,
]);
resolve(comments);
}); });
} };
/** /**
* @method getPostWithComments get user data * @method getPostWithComments get user data
* @param user post author * @param user post author
* @param permlink post permlink * @param permlink post permlink
*/ */
export const getPostWithComments = async (user, permlink) => { export const getPostWithComments = async (user, permlink) => {
let post; let post;
let comments; let comments;
await getPost(user,permlink).then((result) => { post = result }); await getPost(user, permlink).then(result => {
await getComments(user,permlink).then((result) => { comments = result }); post = result;
});
await getComments(user, permlink).then(result => {
comments = result;
});
return([post, comments]) return [post, comments];
} };

View File

@ -1,58 +1,69 @@
import * as React from 'react'; import * as React from 'react';
import { StatusBar } from 'react-native'; import { StatusBar } from 'react-native';
import { Container, Header, Left, Body, Right, Button, Icon, Title, Content, Text } from 'native-base'; import {
Container,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
Content,
Text,
} from 'native-base';
import { getAccount } from '../../providers/steem/Dsteem'; import { getAccount } from '../../providers/steem/Dsteem';
class AuthorPage extends React.Component { class AuthorPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
author: 'author' author: 'author',
};
} }
}
componentDidMount() { componentDidMount() {
getAccount(this.props.navigation.state.params.author).then((author) => { getAccount(this.props.navigation.state.params.author)
this.setState({ .then(author => {
author: author[0] this.setState({
}) author: author[0],
}).catch((err) => { });
})
}); .catch(err => {});
} }
render() { render() {
return ( return (
<Container style={{ top: StatusBar.currentHeight }}> <Container style={{ top: StatusBar.currentHeight }}>
<Header> <Header>
<Left> <Left>
<Button transparent <Button
onPress={() => this.props.navigation.goBack()}> transparent
<Icon name='arrow-back' /> onPress={() => this.props.navigation.goBack()}
</Button> >
</Left> <Icon name="arrow-back" />
<Body> </Button>
<Title> { this.state.author.name } </Title> </Left>
</Body> <Body>
<Right> <Title> {this.state.author.name} </Title>
<Button transparent> </Body>
<Icon name='heart' /> <Right>
</Button> <Button transparent>
<Button transparent> <Icon name="heart" />
<Icon name='more' /> </Button>
</Button> <Button transparent>
</Right> <Icon name="more" />
</Header> </Button>
</Right>
</Header>
<Content> <Content>
<Text> <Text>{JSON.stringify(this.state.author)}</Text>
{ JSON.stringify(this.state.author) } </Content>
</Text> </Container>
</Content> );
</Container> }
)
}
} }
export default AuthorPage; export default AuthorPage;

View File

@ -1,41 +1,49 @@
import * as React from 'react'; import * as React from 'react';
import { Container, Header, Left, Body, Right, Button, Icon, Title } from 'native-base'; import {
Container,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
} from 'native-base';
class EditorPage extends React.Component { class EditorPage extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
} }
componentDidMount() { componentDidMount() {}
}
render() { render() {
return ( return (
<Container> <Container>
<Header> <Header>
<Left> <Left>
<Button transparent> <Button transparent>
<Icon name='menu' /> <Icon name="menu" />
</Button> </Button>
</Left> </Left>
<Body> <Body>
<Title>Editor</Title> <Title>Editor</Title>
</Body> </Body>
<Right> <Right>
<Button transparent> <Button transparent>
<Icon name='search' /> <Icon name="search" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='heart' /> <Icon name="heart" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='more' /> <Icon name="more" />
</Button> </Button>
</Right> </Right>
</Header> </Header>
</Container> </Container>
) );
} }
} }
export default EditorPage; export default EditorPage;

View File

@ -1,15 +1,14 @@
import React,{Component} from 'react' import React, { Component } from 'react';
import {StyleSheet, import {
StyleSheet,
Text, Text,
View, View,
Animated, Animated,
TouchableNativeFeedback, TouchableNativeFeedback,
TouchableOpacity, TouchableOpacity,
Platform, Platform,
Dimensions Dimensions,
} from 'react-native' } from 'react-native';
export default class CustomTabBar extends Component { export default class CustomTabBar extends Component {
constructor(props) { constructor(props) {
@ -17,39 +16,48 @@ export default class CustomTabBar extends Component {
this.state = { this.state = {
activeDefaultColor: '#08086b', activeDefaultColor: '#08086b',
inactiveDefaultColor: '#666666' inactiveDefaultColor: '#666666',
} };
} }
_renderTab(name, page, isTabActive, onPressHandler) { _renderTab(name, page, isTabActive, onPressHandler) {
const { textStyle } = this.props; const { textStyle } = this.props;
const textColor = isTabActive ? this.props.activeColor : this.props.inactiveColor; const textColor = isTabActive
? this.props.activeColor
: this.props.inactiveColor;
const fontWeight = isTabActive ? 'bold' : 'normal'; const fontWeight = isTabActive ? 'bold' : 'normal';
const Button = Platform.OS == 'ios' ? ButtonIos : ButtonAndroid; const Button = Platform.OS == 'ios' ? ButtonIos : ButtonAndroid;
return (<Button
style={{flex: 1}}
key={name}
accessible={true}
accessibilityLabel={name}
accessibilityTraits='button'
onPress={() => onPressHandler(page)}>
<View style={styles.tab}>
<Text style={[{color: textColor, fontWeight } ]}>
{name}
</Text>
</View>
</Button>);
}
_renderUnderline() { return (
<Button
style={{ flex: 1 }}
key={name}
accessible={true}
accessibilityLabel={name}
accessibilityTraits="button"
onPress={() => onPressHandler(page)}
>
<View style={styles.tab}>
<Text style={[{ color: textColor, fontWeight }]}>
{name}
</Text>
</View>
</Button>
);
}
_renderUnderline() {
const containerWidth = Dimensions.get('window').width / 1; const containerWidth = Dimensions.get('window').width / 1;
const numberOfTabs = this.props.tabs.length; const numberOfTabs = this.props.tabs.length;
const underlineWidth = this.props.tabUnderlineDefaultWidth ? this.props.tabUnderlineDefaultWidth : containerWidth / (numberOfTabs * 2); const underlineWidth = this.props.tabUnderlineDefaultWidth
const scale = this.props.tabUnderlineScaleX ? this.props.tabUnderlineScaleX : 3; ? this.props.tabUnderlineDefaultWidth
const deLen = (containerWidth / numberOfTabs - underlineWidth ) / 2; : containerWidth / (numberOfTabs * 2);
const scale = this.props.tabUnderlineScaleX
? this.props.tabUnderlineScaleX
: 3;
const deLen = (containerWidth / numberOfTabs - underlineWidth) / 2;
const tabUnderlineStyle = { const tabUnderlineStyle = {
position: 'absolute', position: 'absolute',
width: underlineWidth, width: underlineWidth,
@ -57,87 +65,98 @@ export default class CustomTabBar extends Component {
borderRadius: 2, borderRadius: 2,
backgroundColor: this.props.activeColor, backgroundColor: this.props.activeColor,
bottom: 3, bottom: 3,
left: deLen left: deLen,
}; };
const translateX = this.props.scrollValue.interpolate({ const translateX = this.props.scrollValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, containerWidth / numberOfTabs], outputRange: [0, containerWidth / numberOfTabs],
}); });
const scaleValue = (defaultScale) => { const scaleValue = defaultScale => {
let number = 4 let number = 4;
let arr = new Array(number * 2) let arr = new Array(number * 2);
return arr.fill(0).reduce(function(pre, cur, idx){ return arr.fill(0).reduce(
idx == 0 ? pre.inputRange.push(cur) : pre.inputRange.push(pre.inputRange[idx-1] + 0.5); function(pre, cur, idx) {
idx%2 ? pre.outputRange.push(defaultScale) : pre.outputRange.push(1) idx == 0
return pre ? pre.inputRange.push(cur)
}, {inputRange: [], outputRange: []}) : pre.inputRange.push(pre.inputRange[idx - 1] + 0.5);
} idx % 2
? pre.outputRange.push(defaultScale)
: pre.outputRange.push(1);
return pre;
},
{ inputRange: [], outputRange: [] }
);
};
const scaleX = this.props.scrollValue.interpolate(scaleValue(scale)); const scaleX = this.props.scrollValue.interpolate(scaleValue(scale));
return(
<Animated.View
style={[
tabUnderlineStyle,
{
transform: [
{ translateX },
{ scaleX }
],
},
this.props.underlineStyle,
]}
/>
)
}
render() {
return ( return (
<View style={[styles.tabs, {backgroundColor: this.props.backgroundColor}, this.props.style]}> <Animated.View
style={[
tabUnderlineStyle,
{
transform: [{ translateX }, { scaleX }],
},
this.props.underlineStyle,
]}
/>
);
}
render() {
return (
<View
style={[
styles.tabs,
{ backgroundColor: this.props.backgroundColor },
this.props.style,
]}
>
{this.props.tabs.map((name, page) => { {this.props.tabs.map((name, page) => {
const isTabActive = this.props.activeTab === page; const isTabActive = this.props.activeTab === page;
return this._renderTab(name, page, isTabActive, this.props.goToPage) return this._renderTab(
name,
page,
isTabActive,
this.props.goToPage
);
})} })}
{ {this._renderUnderline()}
this._renderUnderline()
}
</View> </View>
); );
}; }
} }
const ButtonAndroid = props => (
<TouchableNativeFeedback
delayPressIn={0}
background={TouchableNativeFeedback.SelectableBackground()}
{...props}
>
{props.children}
</TouchableNativeFeedback>
);
const ButtonIos = props => (
const ButtonAndroid = (props) => ( <TouchableOpacity {...props}>{props.children}</TouchableOpacity>
<TouchableNativeFeedback );
delayPressIn={0}
background={TouchableNativeFeedback.SelectableBackground()}
{...props}
>
{props.children}
</TouchableNativeFeedback>);
const ButtonIos = (props) => (<TouchableOpacity {...props}>
{props.children}
</TouchableOpacity>);
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tab: { tab: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
tabs: { tabs: {
height: 50, height: 50,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-around', justifyContent: 'space-around',
borderWidth: 1, borderWidth: 1,
borderTopWidth: 0, borderTopWidth: 0,
borderLeftWidth: 0, borderLeftWidth: 0,
borderRightWidth: 0, borderRightWidth: 0,
borderColor: '#f4f4f4', borderColor: '#f4f4f4',
}, },
}); });

View File

@ -1,10 +1,28 @@
import React from 'react'; import React from 'react';
import { StyleSheet, FlatList, View, AsyncStorage, StatusBar, Dimensions, TouchableHighlight, ActivityIndicator } from 'react-native'; import {
import { Container, Header, Button, StyleSheet,
Thumbnail, Right, Text, Tabs, FlatList,
Tab, Icon, ScrollableTab } from "native-base"; View,
AsyncStorage,
StatusBar,
Dimensions,
TouchableHighlight,
ActivityIndicator,
} from 'react-native';
import {
Container,
Header,
Button,
Thumbnail,
Right,
Text,
Tabs,
Tab,
Icon,
ScrollableTab,
} from 'native-base';
// STEEM // STEEM
import { getPosts, getAccount } from '../../providers/steem/Dsteem'; import { getPosts, getAccount } from '../../providers/steem/Dsteem';
// LIBRARIES // LIBRARIES
@ -18,195 +36,216 @@ import HotPage from './hot';
import TrendingPage from './trending'; import TrendingPage from './trending';
class FeedPage extends React.Component { class FeedPage extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
isReady: false, isReady: false,
posts: [], posts: [],
user: [], user: [],
start_author: '', start_author: '',
start_permlink: '', start_permlink: '',
refreshing: false, refreshing: false,
loading: false, loading: false,
isLoggedIn: false, isLoggedIn: false,
};
} }
}
componentDidMount() { componentDidMount() {
AsyncStorage.getItem('isLoggedIn').then((result) => { AsyncStorage.getItem('isLoggedIn').then(result => {
let res = JSON.parse(result); let res = JSON.parse(result);
if (res) { if (res) {
this.setState({ this.setState(
isLoggedIn: true {
}, () => { isLoggedIn: true,
AsyncStorage.getItem('user').then((result) => { },
let user = JSON.parse(result); () => {
getAccount(user.username).then((result) => { AsyncStorage.getItem('user').then(result => {
this.setState({ let user = JSON.parse(result);
user: result[0] getAccount(user.username).then(result => {
}, () => { this.setState(
this.getFeed(); {
}) user: result[0],
},
() => {
this.getFeed();
}
);
});
});
}
);
}
});
}
getFeed = () => {
getPosts('feed', { tag: this.state.user.name, limit: 5 })
.then(result => {
this.setState({
isReady: true,
posts: result,
start_author: result[result.length - 1].author,
start_permlink: result[result.length - 1].permlink,
refreshing: false,
});
}) })
}); .catch(err => {
}) alert(err);
} });
}) };
}
getMore = () => {
this.setState({ loading: true });
getPosts('feed', {
tag: this.state.user.name,
limit: 10,
start_author: this.state.start_author,
start_permlink: this.state.start_permlink,
}).then(result => {
let posts = result;
posts.shift();
this.setState({
posts: [...this.state.posts, ...posts],
start_author: result[result.length - 1].author,
start_permlink: result[result.length - 1].permlink,
});
});
};
getFeed = () => { refreshPosts = () => {
getPosts('feed', { "tag": this.state.user.name, "limit": 5 }).then((result) => { this.setState(
this.setState({ {
isReady: true, refreshing: true,
posts: result, },
start_author: result[result.length - 1].author, () => {
start_permlink: result[result.length - 1].permlink, this.getFeed();
refreshing: false }
}); );
}).catch((err) => { };
alert(err);
});
}
getMore = () => { renderFooter = () => {
this.setState({ loading: true }) if (!this.state.loading) return null;
getPosts('feed', { "tag": this.state.user.name, "limit": 10, "start_author": this.state.start_author, "start_permlink": this.state.start_permlink }).then((result) => {
let posts = result;
posts.shift();
this.setState({
posts: [...this.state.posts, ...posts],
start_author: result[result.length - 1].author,
start_permlink: result[result.length - 1].permlink
});
});
}
refreshPosts = () => { return (
this.setState({ <View
refreshing: true style={{
}, () => { alignContent: 'center',
this.getFeed(); alignItems: 'center',
}); marginTop: 10,
} borderColor: '#CED0CE',
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
renderFooter = () => { render() {
if (!this.state.loading) return null; const navigate = this.props.navigation;
return (
return ( <View style={{ flex: 1 }}>
<View {this.state.isReady ? (
style={{ <FlatList
alignContent: 'center', style={{ flex: 1 }}
alignItems: 'center', data={this.state.posts}
marginTop: 10, showsVerticalScrollIndicator={false}
borderColor: "#CED0CE" renderItem={({ item }) => (
}} <View style={styles.card}>
> <TouchableHighlight
<ActivityIndicator animating size="large" /> onPress={() => {
</View> navigate('Post', { content: item });
); }}
}; >
<PostCard
render() { navigate={navigate}
const navigate = this.props.navigation; content={item}
return ( />
<View style={{ flex: 1 }}> </TouchableHighlight>
{ this.state.isReady ? ( </View>
<FlatList )}
style={{ flex: 1 }} keyExtractor={(post, index) => index.toString()}
data={this.state.posts} onEndReached={this.getMore}
showsVerticalScrollIndicator={false} refreshing={this.state.refreshing}
renderItem={({item}) => onRefresh={() => this.refreshPosts()}
<View style={styles.card}> onEndThreshold={0}
<TouchableHighlight ListFooterComponent={this.renderFooter}
onPress={() => { navigate('Post',{ content: item }) }}> />
<PostCard navigate={navigate} content={item}></PostCard> ) : (
</TouchableHighlight> <View>
</View> <View style={styles.placeholder}>
} <Placeholder.ImageContent
keyExtractor={(post, index) => index.toString()} size={60}
onEndReached={this.getMore} animate="fade"
refreshing={this.state.refreshing} lineNumber={4}
onRefresh={() => this.refreshPosts()} lineSpacing={5}
onEndThreshold={0} lastLineWidth="30%"
ListFooterComponent={this.renderFooter} onReady={this.state.isReady}
/> />
) : ( </View>
<View> <View style={styles.placeholder}>
<View style={styles.placeholder} > <Placeholder.ImageContent
<Placeholder.ImageContent size={60}
size={60} animate="fade"
animate="fade" lineNumber={4}
lineNumber={4} lineSpacing={5}
lineSpacing={5} lastLineWidth="30%"
lastLineWidth="30%" onReady={this.state.isReady}
onReady={this.state.isReady} />
></Placeholder.ImageContent> </View>
</View> <View style={styles.placeholder}>
<View style={styles.placeholder} > <Placeholder.ImageContent
<Placeholder.ImageContent size={60}
size={60} animate="fade"
animate="fade" lineNumber={4}
lineNumber={4} lineSpacing={5}
lineSpacing={5} lastLineWidth="30%"
lastLineWidth="30%" onReady={this.state.isReady}
onReady={this.state.isReady} />
></Placeholder.ImageContent> </View>
</View> </View>
<View style={styles.placeholder} > )}
<Placeholder.ImageContent </View>
size={60} );
animate="fade" }
lineNumber={4}
lineSpacing={5}
lastLineWidth="30%"
onReady={this.state.isReady}
></Placeholder.ImageContent>
</View>
</View>
) }
</View>
)
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: '#F9F9F9', backgroundColor: '#F9F9F9',
flex: 1, flex: 1,
top: StatusBar.currentHeight top: StatusBar.currentHeight,
}, },
placeholder: { placeholder: {
backgroundColor: 'white', backgroundColor: 'white',
padding: 20, padding: 20,
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: 1, borderWidth: 1,
borderTopWidth: 1, borderTopWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 0, borderRadius: 0,
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
}, },
card: { card: {
backgroundColor: 'white', backgroundColor: 'white',
shadowColor: 'white', shadowColor: 'white',
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
marginBottom: 0, marginBottom: 0,
borderWidth: 1, borderWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 5, borderRadius: 5,
paddingHorizontal: 0, paddingHorizontal: 0,
paddingVertical: 0, paddingVertical: 0,
}, },
tabs: { tabs: {
position: 'absolute', position: 'absolute',
top: Dimensions.get("window").width / 30, top: Dimensions.get('window').width / 30,
alignItems: 'center' alignItems: 'center',
}, },
}); });
export default FeedPage; export default FeedPage;

View File

@ -1,10 +1,26 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View, AsyncStorage, StatusBar, Dimensions } from 'react-native'; import {
import { Container, Header, Button, Left, StyleSheet,
Thumbnail, Right, Text, Tabs, View,
Tab, Icon, ScrollableTab } from "native-base"; AsyncStorage,
StatusBar,
Dimensions,
} from 'react-native';
import {
Container,
Header,
Button,
Left,
Thumbnail,
Right,
Text,
Tabs,
Tab,
Icon,
ScrollableTab,
} from 'native-base';
// STEEM // STEEM
import { getAccount } from '../../providers/steem/Dsteem'; import { getAccount } from '../../providers/steem/Dsteem';
// SCREENS // SCREENS
@ -12,113 +28,180 @@ import FeedPage from './feed';
import HotPage from './hot'; import HotPage from './hot';
import TrendingPage from './trending'; import TrendingPage from './trending';
import ScrollableTabView, {
import ScrollableTabView, { DefaultTabBar, ScrollableTabBar } from 'react-native-scrollable-tab-view' DefaultTabBar,
import CustomTabBar from './CustomTabBar' ScrollableTabBar,
} from 'react-native-scrollable-tab-view';
import CustomTabBar from './CustomTabBar';
class HomePage extends React.Component { class HomePage extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
user: [], user: [],
isLoggedIn: false, isLoggedIn: false,
};
} }
}
componentDidMount() { componentDidMount() {
AsyncStorage.getItem('isLoggedIn').then((result) => { AsyncStorage.getItem('isLoggedIn').then(result => {
let res = JSON.parse(result); let res = JSON.parse(result);
if (res) { if (res) {
this.setState({ this.setState(
isLoggedIn: true {
}, () => { isLoggedIn: true,
AsyncStorage.getItem('user').then((result) => { },
let user = JSON.parse(result); () => {
getAccount(user.username).then((result) => { AsyncStorage.getItem('user').then(result => {
this.setState({ let user = JSON.parse(result);
user: result[0] getAccount(user.username).then(result => {
}, () => { this.setState(
}) {
}) user: result[0],
}); },
}) () => {}
} );
}) });
} });
}
);
}
});
}
render() { render() {
const { navigate } = this.props.navigation; const { navigate } = this.props.navigation;
return ( return (
<Container style={{ flex: 1, top: StatusBar.currentHeight }}> <Container style={{ flex: 1, top: StatusBar.currentHeight }}>
<StatusBar translucent={true} backgroundColor={'transparent'}/> <StatusBar translucent={true} backgroundColor={'transparent'} />
<Header noShadow style={{ backgroundColor: '#284b78', borderBottomWidth: 0, borderColor: '#284b78' }}> <Header
<Left> noShadow
<Button style={{
transparent backgroundColor: '#284b78',
style={{ zIndex: 2 }} borderBottomWidth: 0,
onPress={() => this.props.navigation.toggleDrawer()}> borderColor: '#284b78',
<Thumbnail square small source={{uri: `https://steemitimages.com/u/${this.state.user.name}/avatar/small` }} style={{ width: 30, height: 30, borderRadius: 15, borderWidth: 1, borderColor: 'white' }}/> }}
</Button> >
</Left> <Left>
<Right> <Button
<Button transparent> transparent
<Icon style={{ color: 'white', fontWeight: 'bold' }} name='search' /> style={{ zIndex: 2 }}
</Button> onPress={() => this.props.navigation.toggleDrawer()}
</Right> >
</Header> <Thumbnail
square
small
source={{
uri: `https://steemitimages.com/u/${
this.state.user.name
}/avatar/small`,
}}
style={{
width: 30,
height: 30,
borderRadius: 15,
borderWidth: 1,
borderColor: 'white',
}}
/>
</Button>
</Left>
<Right>
<Button transparent>
<Icon
style={{ color: 'white', fontWeight: 'bold' }}
name="search"
/>
</Button>
</Right>
</Header>
<ScrollableTabView <ScrollableTabView
style={{ alignSelf: 'center', backgroundColor: 'transparent' }} style={{
renderTabBar={() => ( alignSelf: 'center',
<CustomTabBar backgroundColor: 'transparent',
style={{ alignSelf: 'center', height: 40, backgroundColor: '#284b78' }} }}
tabUnderlineDefaultWidth={30} // default containerWidth / (numberOfTabs * 4) renderTabBar={() => (
tabUnderlineScaleX={3} // default 3 <CustomTabBar
activeColor={"#fff"} style={{
inactiveColor={"#fff"}/> alignSelf: 'center',
)}> height: 40,
backgroundColor: '#284b78',
<View tabLabel='Feed' }}
style={{ paddingHorizontal: 7, backgroundColor: '#f9f9f9', flex: 1, minWidth: Dimensions.get('window').width / 1 }}> tabUnderlineDefaultWidth={30} // default containerWidth / (numberOfTabs * 4)
{ this.state.isLoggedIn ? tabUnderlineScaleX={3} // default 3
<FeedPage navigation={navigate}/> activeColor={'#fff'}
: ( inactiveColor={'#fff'}
<View style={{ alignItems: 'center' }}> />
<Button light )}
onPress={() => this.props.navigation.navigate('Login')} >
style={{ alignSelf: 'center', marginTop: 100 }}> <View
<Text> tabLabel="Feed"
Login to setup your custom Feed! style={{
</Text> paddingHorizontal: 7,
</Button> backgroundColor: '#f9f9f9',
</View> ) flex: 1,
} minWidth: Dimensions.get('window').width / 1,
</View> }}
<View tabLabel='Hot' >
style={{ paddingHorizontal: 7, backgroundColor: '#f9f9f9', flex: 1, minWidth: Dimensions.get('window').width / 1 }}> {this.state.isLoggedIn ? (
<HotPage navigation={navigate}/> <FeedPage navigation={navigate} />
</View> ) : (
<View tabLabel='Trending' <View style={{ alignItems: 'center' }}>
style={{ paddingHorizontal: 7, backgroundColor: '#f9f9f9', flex: 1, minWidth: Dimensions.get('window').width / 1 }}> <Button
<TrendingPage navigation={navigate}/> light
</View> onPress={() =>
</ScrollableTabView> this.props.navigation.navigate('Login')
}
</Container> style={{
) alignSelf: 'center',
} marginTop: 100,
}}
>
<Text>
Login to setup your custom Feed!
</Text>
</Button>
</View>
)}
</View>
<View
tabLabel="Hot"
style={{
paddingHorizontal: 7,
backgroundColor: '#f9f9f9',
flex: 1,
minWidth: Dimensions.get('window').width / 1,
}}
>
<HotPage navigation={navigate} />
</View>
<View
tabLabel="Trending"
style={{
paddingHorizontal: 7,
backgroundColor: '#f9f9f9',
flex: 1,
minWidth: Dimensions.get('window').width / 1,
}}
>
<TrendingPage navigation={navigate} />
</View>
</ScrollableTabView>
</Container>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: '#F9F9F9', backgroundColor: '#F9F9F9',
flex: 1 flex: 1,
}, },
tabs: { tabs: {
flex: 1 flex: 1,
} },
}); });
export default HomePage; export default HomePage;

View File

@ -1,8 +1,16 @@
import React from 'react'; import React from 'react';
import { StyleSheet, FlatList, View, StatusBar, Dimensions, TouchableHighlight, ActivityIndicator } from 'react-native'; import {
import { Container } from "native-base"; StyleSheet,
FlatList,
View,
StatusBar,
Dimensions,
TouchableHighlight,
ActivityIndicator,
} from 'react-native';
import { Container } from 'native-base';
// STEEM // STEEM
import { getPosts, getAccount } from '../../providers/steem/Dsteem'; import { getPosts, getAccount } from '../../providers/steem/Dsteem';
// LIBRARIES // LIBRARIES
@ -15,170 +23,186 @@ import PostCard from '../../components/PostCard';
import PostPage from '../../screens/single-post/Post'; import PostPage from '../../screens/single-post/Post';
class HotPage extends React.Component { class HotPage extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
isReady: false, isReady: false,
posts: [], posts: [],
user: [], user: [],
start_author: '', start_author: '',
start_permlink: '', start_permlink: '',
refreshing: false, refreshing: false,
loading: false, loading: false,
};
} }
}
componentDidMount() { componentDidMount() {
this.getHotPosts(); this.getHotPosts();
} }
getHotPosts = () => { getHotPosts = () => {
getPosts('hot', { "tag": "", "limit": 5 }).then((result) => { getPosts('hot', { tag: '', limit: 5 })
this.setState({ .then(result => {
isReady: true, this.setState({
posts: result, isReady: true,
start_author: result[result.length - 1].author, posts: result,
start_permlink: result[result.length - 1].permlink, start_author: result[result.length - 1].author,
refreshing: false start_permlink: result[result.length - 1].permlink,
}); refreshing: false,
}).catch((err) => { });
alert(err); })
}); .catch(err => {
} alert(err);
});
};
getMoreHot = () => { getMoreHot = () => {
this.setState({ loading: true }) this.setState({ loading: true });
getPosts('hot', { "tag": "", "limit": 10, "start_author": this.state.start_author, "start_permlink": this.state.start_permlink }).then((result) => { getPosts('hot', {
let posts = result; tag: '',
posts.shift(); limit: 10,
this.setState({ start_author: this.state.start_author,
posts: [...this.state.posts, ...posts], start_permlink: this.state.start_permlink,
start_author: result[result.length - 1].author, }).then(result => {
start_permlink: result[result.length - 1].permlink let posts = result;
}); posts.shift();
}); this.setState({
} posts: [...this.state.posts, ...posts],
start_author: result[result.length - 1].author,
start_permlink: result[result.length - 1].permlink,
});
});
};
refreshHotPosts = () => { refreshHotPosts = () => {
this.setState({ this.setState(
refreshing: true {
}, () => { refreshing: true,
this.getHotPosts(); },
}); () => {
} this.getHotPosts();
}
);
};
renderFooter = () => { renderFooter = () => {
if (!this.state.loading) return null; if (!this.state.loading) return null;
return ( return (
<View <View
style={{ style={{
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
marginTop: 10, marginTop: 10,
borderColor: "#CED0CE" borderColor: '#CED0CE',
}} }}
> >
<ActivityIndicator animating size="large" /> <ActivityIndicator animating size="large" />
</View> </View>
); );
}; };
render() { render() {
const navigate = this.props.navigation; const navigate = this.props.navigation;
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
{ this.state.isReady ? ( {this.state.isReady ? (
<FlatList <FlatList
style={{ flex: 1 }} style={{ flex: 1 }}
data={this.state.posts} data={this.state.posts}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({item}) => renderItem={({ item }) => (
<View style={styles.card}> <View style={styles.card}>
<TouchableHighlight <TouchableHighlight
onPress={() => { navigate('Post',{ content: item }) }}> onPress={() => {
<PostCard navigate={navigate} content={item}></PostCard> navigate('Post', { content: item });
</TouchableHighlight> }}
</View> >
} <PostCard
keyExtractor={(post, index) => index.toString()} navigate={navigate}
onEndReached={this.getMoreHot} content={item}
refreshing={this.state.refreshing} />
onRefresh={() => this.refreshHotPosts()} </TouchableHighlight>
onEndThreshold={0} </View>
ListFooterComponent={this.renderFooter} )}
/> keyExtractor={(post, index) => index.toString()}
) : ( onEndReached={this.getMoreHot}
<View> refreshing={this.state.refreshing}
<View style={styles.placeholder} > onRefresh={() => this.refreshHotPosts()}
<Placeholder.ImageContent onEndThreshold={0}
size={60} ListFooterComponent={this.renderFooter}
animate="fade" />
lineNumber={4} ) : (
lineSpacing={5} <View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
<View style={styles.placeholder} > lineNumber={4}
<Placeholder.ImageContent lineSpacing={5}
size={60} lastLineWidth="30%"
animate="fade" onReady={this.state.isReady}
lineNumber={4} />
lineSpacing={5} </View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
<View style={styles.placeholder} > lineNumber={4}
<Placeholder.ImageContent lineSpacing={5}
size={60} lastLineWidth="30%"
animate="fade" onReady={this.state.isReady}
lineNumber={4} />
lineSpacing={5} </View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
</View> lineNumber={4}
) } lineSpacing={5}
</View> lastLineWidth="30%"
) onReady={this.state.isReady}
} />
</View>
</View>
)}
</View>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: '#F9F9F9', backgroundColor: '#F9F9F9',
flex: 1, flex: 1,
top: StatusBar.currentHeight top: StatusBar.currentHeight,
}, },
placeholder: { placeholder: {
backgroundColor: 'white', backgroundColor: 'white',
padding: 20, padding: 20,
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: 1, borderWidth: 1,
borderTopWidth: 1, borderTopWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 0, borderRadius: 0,
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
}, },
card: { card: {
backgroundColor: 'white', backgroundColor: 'white',
shadowColor: 'white', shadowColor: 'white',
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
marginBottom: 0, marginBottom: 0,
borderWidth: 1, borderWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 5, borderRadius: 5,
paddingHorizontal: 0, paddingHorizontal: 0,
paddingVertical: 0, paddingVertical: 0,
}, },
}); });
export default HotPage; export default HotPage;

View File

@ -1,7 +1,16 @@
import React from 'react'; import React from 'react';
import { StyleSheet, FlatList, View, AsyncStorage, StatusBar, Dimensions, TouchableHighlight, ActivityIndicator } from 'react-native'; import {
StyleSheet,
FlatList,
View,
AsyncStorage,
StatusBar,
Dimensions,
TouchableHighlight,
ActivityIndicator,
} from 'react-native';
// STEEM // STEEM
import { getPosts, getAccount } from '../../providers/steem/Dsteem'; import { getPosts, getAccount } from '../../providers/steem/Dsteem';
// LIBRARIES // LIBRARIES
@ -14,168 +23,185 @@ import PostCard from '../../components/PostCard';
import PostPage from '../../screens/single-post/Post'; import PostPage from '../../screens/single-post/Post';
class TrendingPage extends React.Component { class TrendingPage extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
isReady: false, isReady: false,
posts: [], posts: [],
user: [], user: [],
start_author: '', start_author: '',
start_permlink: '', start_permlink: '',
refreshing: false, refreshing: false,
loading: false, loading: false,
};
} }
}
componentDidMount() { componentDidMount() {
this.getTrending(); this.getTrending();
} }
getTrending = () => { getTrending = () => {
getPosts('trending', { "tag": "", "limit": 5 }).then((result) => { getPosts('trending', { tag: '', limit: 5 })
this.setState({ .then(result => {
isReady: true, this.setState({
posts: result, isReady: true,
start_author: result[result.length - 1].author, posts: result,
start_permlink: result[result.length - 1].permlink, start_author: result[result.length - 1].author,
refreshing: false start_permlink: result[result.length - 1].permlink,
}); refreshing: false,
}).catch((err) => { });
alert(err); })
}); .catch(err => {
} alert(err);
});
};
getMore = () => { getMore = () => {
this.setState({ loading: true }) this.setState({ loading: true });
getPosts('trending', { "tag": "", "limit": 10, "start_author": this.state.start_author, "start_permlink": this.state.start_permlink }).then((result) => { getPosts('trending', {
let posts = result; tag: '',
posts.shift(); limit: 10,
this.setState({ start_author: this.state.start_author,
posts: [...this.state.posts, ...posts], start_permlink: this.state.start_permlink,
start_author: result[result.length - 1].author, }).then(result => {
start_permlink: result[result.length - 1].permlink let posts = result;
}); posts.shift();
}); this.setState({
} posts: [...this.state.posts, ...posts],
start_author: result[result.length - 1].author,
start_permlink: result[result.length - 1].permlink,
});
});
};
refreshData = () => { refreshData = () => {
this.setState({ this.setState(
refreshing: true {
}, () => { refreshing: true,
this.getTrending(); },
}); () => {
} this.getTrending();
}
);
};
renderFooter = () => { renderFooter = () => {
if (!this.state.loading) return null; if (!this.state.loading) return null;
return ( return (
<View <View
style={{ style={{
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
marginTop: 10, marginTop: 10,
marginBottom: 40, marginBottom: 40,
borderColor: "#CED0CE" borderColor: '#CED0CE',
}}> }}
<ActivityIndicator animating size="large" /> >
</View> <ActivityIndicator animating size="large" />
); </View>
}; );
};
render() { render() {
const navigate = this.props.navigation; const navigate = this.props.navigation;
return ( return (
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
{ this.state.isReady ? ( {this.state.isReady ? (
<FlatList <FlatList
data={this.state.posts} data={this.state.posts}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({item}) => renderItem={({ item }) => (
<View style={styles.card}> <View style={styles.card}>
<TouchableHighlight <TouchableHighlight
onPress={() => { navigate('Post',{ content: item }) }}> onPress={() => {
<PostCard navigate={navigate} content={item}></PostCard> navigate('Post', { content: item });
</TouchableHighlight> }}
</View> >
} <PostCard
keyExtractor={(post, index) => index.toString()} navigate={navigate}
onEndReached={this.getMore} content={item}
refreshing={this.state.refreshing} />
onRefresh={() => this.refreshData()} </TouchableHighlight>
onEndThreshold={0} </View>
ListFooterComponent={this.renderFooter} )}
/> keyExtractor={(post, index) => index.toString()}
) : ( onEndReached={this.getMore}
<View> refreshing={this.state.refreshing}
<View style={styles.placeholder} > onRefresh={() => this.refreshData()}
<Placeholder.ImageContent onEndThreshold={0}
size={60} ListFooterComponent={this.renderFooter}
animate="fade" />
lineNumber={4} ) : (
lineSpacing={5} <View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
<View style={styles.placeholder} > lineNumber={4}
<Placeholder.ImageContent lineSpacing={5}
size={60} lastLineWidth="30%"
animate="fade" onReady={this.state.isReady}
lineNumber={4} />
lineSpacing={5} </View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
<View style={styles.placeholder} > lineNumber={4}
<Placeholder.ImageContent lineSpacing={5}
size={60} lastLineWidth="30%"
animate="fade" onReady={this.state.isReady}
lineNumber={4} />
lineSpacing={5} </View>
lastLineWidth="30%" <View style={styles.placeholder}>
onReady={this.state.isReady} <Placeholder.ImageContent
></Placeholder.ImageContent> size={60}
</View> animate="fade"
</View> lineNumber={4}
) } lineSpacing={5}
</View> lastLineWidth="30%"
) onReady={this.state.isReady}
} />
</View>
</View>
)}
</View>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: '#F9F9F9', backgroundColor: '#F9F9F9',
flex: 1, flex: 1,
}, },
placeholder: { placeholder: {
backgroundColor: 'white', backgroundColor: 'white',
padding: 20, padding: 20,
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: 1, borderWidth: 1,
borderTopWidth: 1, borderTopWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 0, borderRadius: 0,
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
}, },
card: { card: {
backgroundColor: 'white', backgroundColor: 'white',
shadowColor: 'white', shadowColor: 'white',
marginRight: 0, marginRight: 0,
marginLeft: 0, marginLeft: 0,
marginTop: 10, marginTop: 10,
marginBottom: 0, marginBottom: 0,
borderWidth: 1, borderWidth: 1,
borderColor: '#e2e5e8', borderColor: '#e2e5e8',
borderRadius: 5, borderRadius: 5,
paddingHorizontal: 0, paddingHorizontal: 0,
paddingVertical: 0, paddingVertical: 0,
} },
}); });
export default TrendingPage; export default TrendingPage;

View File

@ -1,157 +1,317 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View, ActivityIndicator, Text, StyleSheet, Image, StatusBar } from 'react-native'; import {
import { Item, Header, Input, Card, Button, Container, Icon, Left, Right, Body, Label, Thumbnail } from 'native-base'; View,
ActivityIndicator,
Text,
StyleSheet,
Image,
StatusBar,
} from 'react-native';
import {
Item,
Header,
Input,
Card,
Button,
Container,
Icon,
Left,
Right,
Body,
Label,
Thumbnail,
} from 'native-base';
import { Login } from '../../providers/steem/Auth'; import { Login } from '../../providers/steem/Auth';
class LoginPage extends Component { class LoginPage extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
username: '', username: '',
password: '', password: '',
isLoading: false isLoading: false,
};
} }
}
doLogin = () => { doLogin = () => {
this.setState({ isLoading: true }); this.setState({ isLoading: true });
let password = this.state.password;
let username = this.state.username;
Login(username, password).then((result) => { let password = this.state.password;
let username = this.state.username;
if (result === true) { Login(username, password)
this.props.navigation.navigate('LoggedIn'); .then(result => {
} if (result === true) {
this.props.navigation.navigate('LoggedIn');
}).catch((err) => { }
alert(err) })
this.setState({ isLoading: false }); .catch(err => {
}) alert(err);
} this.setState({ isLoading: false });
});
};
render() { render() {
return (
return ( <Container style={styles.container}>
<Container style={styles.container}> <StatusBar translucent={true} backgroundColor={'transparent'} />
<StatusBar translucent={true} backgroundColor={'transparent'}/> <Header style={{ backgroundColor: 'white', height: 80 }}>
<Header style={{ backgroundColor: 'white', height: 80 }}> <Left>
<Left> <Button
<Button transparent transparent
onPress={() => this.props.navigation.toggleDrawer()}> onPress={() => this.props.navigation.toggleDrawer()}
<Thumbnail >
style={{ width: 32, height: 32, borderRadius: 16, margin:10 }} <Thumbnail
source={require('../../assets/esteem.jpg')}/> style={{
</Button> width: 32,
</Left> height: 32,
<Body> borderRadius: 16,
</Body> margin: 10,
<Right> }}
<Text style={{ color: '#a7adaf', marginHorizontal: 20, fontWeight: 'bold' }}>Sign Up</Text> source={require('../../assets/esteem.jpg')}
</Right> />
</Header> </Button>
<View style={styles.header}> </Left>
<View style={{ flex: 0.5, alignItems: 'center', paddingLeft: 10 }}> <Body />
<Text style={{ fontSize: 40, fontWeight: '600', color: '#626262', marginTop: 35 }}>Sign in</Text> <Right>
<Text style={{ color: '#a7adaf', marginTop: 20 }}>with your username {"\n"} and password {"\n"} to get all the {"\n"} <Text style={{ fontWeight: 'bold',color: '#a7adaf' }}>benefits of eSteem</Text> </Text> <Text
style={{
</View> color: '#a7adaf',
<View style={{ flex: 0.5, overflow: 'hidden', padding: 0 }}> marginHorizontal: 20,
<Image fontWeight: 'bold',
style={{ width: 220, height: 304, marginTop: 10, marginLeft: 20 }} }}
source={require('../../assets/love_mascot.png')}/> >
</View> Sign Up
</View> </Text>
</Right>
</Header>
<View style={styles.header}>
<View
style={{
flex: 0.5,
alignItems: 'center',
paddingLeft: 10,
}}
>
<Text
style={{
fontSize: 40,
fontWeight: '600',
color: '#626262',
marginTop: 35,
}}
>
Sign in
</Text>
<Text style={{ color: '#a7adaf', marginTop: 20 }}>
with your username {'\n'} and password {'\n'} to get
all the {'\n'}{' '}
<Text
style={{ fontWeight: 'bold', color: '#a7adaf' }}
>
benefits of eSteem
</Text>{' '}
</Text>
</View>
<View style={{ flex: 0.5, overflow: 'hidden', padding: 0 }}>
<Image
style={{
width: 220,
height: 304,
marginTop: 10,
marginLeft: 20,
}}
source={require('../../assets/love_mascot.png')}
/>
</View>
</View>
<View style={{ padding: 30, backgroundColor: 'white', flex:0.4 }}> <View
<View> style={{ padding: 30, backgroundColor: 'white', flex: 0.4 }}
<Item rounded >
style={{ margin: 5, backgroundColor: '#f6f6f6', height: 40, marginVertical: 10, overflow: 'hidden', borderColor: 'white' }}> <View>
<Icon <Item
name='at' rounded
style={{ backgroundColor: '#ececec', height: 40, width: 40, alignItems: 'center', padding: 8, color: '#a7adaf', fontWeight: 'bold' }}/> style={{
<Input margin: 5,
autoCapitalize='none' backgroundColor: '#f6f6f6',
placeholder='username' height: 40,
onChangeText={(text) => this.setState({username: text})} value={this.state.username}/> marginVertical: 10,
</Item> overflow: 'hidden',
borderColor: 'white',
}}
>
<Icon
name="at"
style={{
backgroundColor: '#ececec',
height: 40,
width: 40,
alignItems: 'center',
padding: 8,
color: '#a7adaf',
fontWeight: 'bold',
}}
/>
<Input
autoCapitalize="none"
placeholder="username"
onChangeText={text =>
this.setState({ username: text })
}
value={this.state.username}
/>
</Item>
<Item rounded <Item
style={{ margin: 5, backgroundColor: '#f6f6f6', height: 40, marginVertical: 10, overflow: 'hidden', borderColor: 'white' }}> rounded
<Icon style={{
name='md-lock' margin: 5,
style={{ backgroundColor: '#ececec', height: 40, width: 40, alignItems: 'center', paddingVertical: 7, paddingLeft: 13, color: '#a7adaf', fontWeight: 'bold' }}/> backgroundColor: '#f6f6f6',
<Input secureTextEntry={true} placeholder='Password or WIF' onChangeText={(text) => this.setState({password: text})} value={this.state.password}/> height: 40,
</Item> marginVertical: 10,
<View> overflow: 'hidden',
</View> borderColor: 'white',
}}
>
<Icon
name="md-lock"
style={{
backgroundColor: '#ececec',
height: 40,
width: 40,
alignItems: 'center',
paddingVertical: 7,
paddingLeft: 13,
color: '#a7adaf',
fontWeight: 'bold',
}}
/>
<Input
secureTextEntry={true}
placeholder="Password or WIF"
onChangeText={text =>
this.setState({ password: text })
}
value={this.state.password}
/>
</Item>
<View />
</View>
<View
style={{
borderBottomColor: 'lightgray',
borderBottomWidth: 0.7,
marginVertical: 20,
}}
/>
<View
style={{ flexDirection: 'row', alignItems: 'center' }}
>
<Icon
name="information-circle"
style={{
flex: 0.15,
color: '#a7adaf',
fontSize: 25,
paddingLeft: 10,
}}
/>
<Text style={{ flex: 0.85, color: '#a7adaf' }}>
Don't worry! {'\n'}
Your password is kept locally on your device and
removed upon logout!
</Text>
</View>
</View>
</View> <View style={styles.footer}>
<View style={{ borderBottomColor: 'lightgray', borderBottomWidth: 0.7, marginVertical: 20 }}/> <View style={{ flex: 0.6, alignItems: 'flex-end' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <Text
<Icon name='information-circle' style={{ flex: 0.15, color: '#a7adaf', fontSize: 25, paddingLeft: 10 }}/> onPress={() => {
<Text style={{ flex: 0.85, color: '#a7adaf' }}> this.props.navigation.goBack();
Don't worry! {"\n"} }}
Your password is kept locally on your device and removed upon logout! style={{
</Text> color: '#a7adaf',
</View> fontSize: 18,
</View> margin: 25,
}}
<View style={styles.footer}> >
<View style={{ flex: 0.6, alignItems: 'flex-end' }}> Skip this screen
<Text </Text>
onPress={() => { this.props.navigation.goBack() }} </View>
style={{ color: '#a7adaf', fontSize: 18, margin: 25 }}> <View style={{ flex: 0.4, alignItems: 'center' }}>
Skip this screen {this.state.isLoading ? (
</Text> <Button
</View> style={{
<View style={{ flex: 0.4, alignItems: 'center' }}> borderRadius: 25,
{ this.state.isLoading ? ( padding: 5,
<Button backgroundColor: '#007EE5',
style={{ borderRadius: 25, padding: 5, backgroundColor: '#007EE5', width: 130, height: 35, marginTop: 20 }}> width: 130,
<ActivityIndicator color='white' style={{ marginHorizontal: 50 }}/> height: 35,
</Button> marginTop: 20,
) : ( }}
<Button >
style={{ borderRadius: 25, padding: 5, backgroundColor: '#007EE5', width: 130, height: 35, marginTop: 20 }} <ActivityIndicator
onPress={() => { this.doLogin() }}> color="white"
<Text style={{ marginHorizontal: 50 }}
style={{ color: 'white', fontWeight: 'bold', marginHorizontal: 40 }}> />
Login </Button>
</Text> ) : (
</Button> <Button
)} style={{
</View> borderRadius: 25,
</View> padding: 5,
</Container> backgroundColor: '#007EE5',
) width: 130,
} height: 35,
marginTop: 20,
}}
onPress={() => {
this.doLogin();
}}
>
<Text
style={{
color: 'white',
fontWeight: 'bold',
marginHorizontal: 40,
}}
>
Login
</Text>
</Button>
)}
</View>
</View>
</Container>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
margin: 0, margin: 0,
padding: 0, padding: 0,
backgroundColor: '#f1f1f1', backgroundColor: '#f1f1f1',
top: StatusBar.currentHeight, top: StatusBar.currentHeight,
flexDirection: 'column' flexDirection: 'column',
}, },
header: { header: {
flexDirection: 'row', flexDirection: 'row',
padding: 0, padding: 0,
backgroundColor: 'white', backgroundColor: 'white',
marginVertical: 10, marginVertical: 10,
height: 200, height: 200,
flex: 0.4 flex: 0.4,
}, },
footer: { footer: {
flex: 0.2, flex: 0.2,
bottom: 0, bottom: 0,
marginTop: 10, marginTop: 10,
height: 80, height: 80,
backgroundColor: 'white', backgroundColor: 'white',
flexDirection: 'row', flexDirection: 'row',
} },
}); });
export default LoginPage; export default LoginPage;

View File

@ -1,37 +1,46 @@
import * as React from 'react'; import * as React from 'react';
import { Container, Header, Left, Body, Right, Button, Icon, Title } from 'native-base'; import {
Container,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
} from 'native-base';
class NotificationPage extends React.Component { class NotificationPage extends React.Component {
static navigationOptions = { static navigationOptions = {
title: 'Notifications', title: 'Notifications',
}; };
render() { render() {
return ( return (
<Container> <Container>
<Header> <Header>
<Left> <Left>
<Button transparent> <Button transparent>
<Icon name='menu' /> <Icon name="menu" />
</Button> </Button>
</Left> </Left>
<Body> <Body>
<Title>Notifications</Title> <Title>Notifications</Title>
</Body> </Body>
<Right> <Right>
<Button transparent> <Button transparent>
<Icon name='search' /> <Icon name="search" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='heart' /> <Icon name="heart" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='more' /> <Icon name="more" />
</Button> </Button>
</Right> </Right>
</Header> </Header>
</Container> </Container>
) );
} }
} }
export default NotificationPage; export default NotificationPage;

View File

@ -1,40 +1,50 @@
import * as React from 'react'; import * as React from 'react';
import { StatusBar} from 'react-native'; import { StatusBar } from 'react-native';
import { Container, Header, Left, Body, Right, Button, Icon, Title, Text } from 'native-base'; import {
Container,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
Text,
} from 'native-base';
class ProfilePage extends React.Component { class ProfilePage extends React.Component {
static navigationOptions = { static navigationOptions = {
title: 'Profile', title: 'Profile',
}; };
render() { render() {
return ( return (
<Container style={{ flex: 1, top: StatusBar.currentHeight }}> <Container style={{ flex: 1, top: StatusBar.currentHeight }}>
<Header> <Header>
<Left> <Left>
<Button transparent> <Button transparent>
<Icon name='menu' /> <Icon name="menu" />
</Button> </Button>
</Left> </Left>
<Body> <Body>
<Title>Profile</Title> <Title>Profile</Title>
</Body> </Body>
<Right> <Right>
<Button transparent> <Button transparent>
<Icon name='search' /> <Icon name="search" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='heart' /> <Icon name="heart" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='more' /> <Icon name="more" />
</Button> </Button>
</Right> </Right>
</Header> </Header>
<Text>Profile</Text> <Text>Profile</Text>
</Container> </Container>
) );
} }
} }
export default ProfilePage; export default ProfilePage;

View File

@ -1,33 +1,33 @@
import React from 'react'; import React from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
AsyncStorage, AsyncStorage,
StatusBar, StatusBar,
StyleSheet, StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
export class AuthLoadingScreen extends React.Component { export class AuthLoadingScreen extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.checkAuth(); this.checkAuth();
} }
// Fetch the login state from storage then navigate to our appropriate place // Fetch the login state from storage then navigate to our appropriate place
checkAuth = async () => { checkAuth = async () => {
isLoggedIn = await AsyncStorage.getItem('isLoggedIn'); isLoggedIn = await AsyncStorage.getItem('isLoggedIn');
// This will switch to the App screen or Auth screen and this loading // This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. // screen will be unmounted and thrown away.
this.props.navigation.navigate(isLoggedIn ? 'LoggedIn' : 'LoggedOut'); this.props.navigation.navigate(isLoggedIn ? 'LoggedIn' : 'LoggedOut');
} };
// Render any loading content that you like here // Render any loading content that you like here
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ActivityIndicator /> <ActivityIndicator />
</View> </View>
); );
} }
} }

View File

@ -1,5 +1,12 @@
import React from 'react'; import React from 'react';
import { AsyncStorage, StyleSheet, StatusBar, ActivityIndicator, View, Dimensions } from 'react-native'; import {
AsyncStorage,
StyleSheet,
StatusBar,
ActivityIndicator,
View,
Dimensions,
} from 'react-native';
import { createDrawerNavigator, createSwitchNavigator } from 'react-navigation'; import { createDrawerNavigator, createSwitchNavigator } from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
@ -8,87 +15,91 @@ import { LoggedInSideBar } from './LoggedInMenu';
import { LoggedOutSideBar } from './LoggedOutMenu'; import { LoggedOutSideBar } from './LoggedOutMenu';
import LoginPage from '../login/Login'; import LoginPage from '../login/Login';
const LoggedInMenu = createDrawerNavigator({ const LoggedInMenu = createDrawerNavigator(
Tabs: { {
screen: Tabs, Tabs: {
navigationOptions: { screen: Tabs,
drawer: () => ({ navigationOptions: {
label: 'Home', drawer: () => ({
icon: ({ tintColor }) => ( label: 'Home',
<MaterialIcons icon: ({ tintColor }) => (
name="Home" <MaterialIcons
size={24} name="Home"
style={{ color: tintColor }} size={24}
/> style={{ color: tintColor }}
), />
}), ),
}),
},
},
}, },
} {
}, contentComponent: LoggedInSideBar,
{ drawerWidth: Dimensions.get('window').width / 1.2,
contentComponent: LoggedInSideBar, }
drawerWidth: Dimensions.get('window').width / 1.2 );
});
const LoggedOutMenu = createDrawerNavigator({ const LoggedOutMenu = createDrawerNavigator(
Tabs: { {
screen: Tabs, Tabs: {
}, screen: Tabs,
Login: { },
screen: LoginPage, Login: {
navigationOptions: ({ navigation }) => ({ screen: LoginPage,
navigationOptions: ({ navigation }) => ({}),
}), },
}, },
}, {
{ contentComponent: LoggedOutSideBar,
contentComponent: LoggedOutSideBar, drawerWidth: Dimensions.get('window').width / 1.2,
drawerWidth: Dimensions.get('window').width / 1.2 }
}); );
class AuthLoadingScreen extends React.Component { class AuthLoadingScreen extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.checkAuth(); this.checkAuth();
} }
// Fetch the login state from storage then navigate to our appropriate place // Fetch the login state from storage then navigate to our appropriate place
checkAuth = async () => { checkAuth = async () => {
const isLoggedIn = await AsyncStorage.getItem('isLoggedIn'); const isLoggedIn = await AsyncStorage.getItem('isLoggedIn');
// This will switch to the App screen or Auth screen and this loading // This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. // screen will be unmounted and thrown away.
this.props.navigation.navigate(isLoggedIn === null ? 'LoggedOut' : 'LoggedIn'); this.props.navigation.navigate(
} isLoggedIn === null ? 'LoggedOut' : 'LoggedIn'
);
};
// Render any loading content that you like here // Render any loading content that you like here
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ActivityIndicator /> <ActivityIndicator />
<StatusBar barStyle="default" /> <StatusBar barStyle="default" />
</View> </View>
); );
} }
} }
export default createSwitchNavigator( export default createSwitchNavigator(
{ {
AuthLoading: AuthLoadingScreen, AuthLoading: AuthLoadingScreen,
LoggedIn: LoggedInMenu, LoggedIn: LoggedInMenu,
LoggedOut: LoggedOutMenu, LoggedOut: LoggedOutMenu,
}, },
{ {
initialRouteName: 'AuthLoading', initialRouteName: 'AuthLoading',
} }
); );
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
}, },
text: { text: {
fontSize: 32 fontSize: 32,
} },
}) });

View File

@ -1,249 +1,274 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Image, AsyncStorage } from "react-native"; import { Image, AsyncStorage } from 'react-native';
import { import {
Content, Content,
Text, Text,
List, List,
ListItem, ListItem,
Icon, Icon,
Container, Container,
Left, Left,
Right, Right,
View, View,
Badge, Badge,
Thumbnail Thumbnail,
} from "native-base"; } from 'native-base';
import styles from "./style"; import styles from './style';
import { getAccount } from '../../providers/steem/Dsteem' import { getAccount } from '../../providers/steem/Dsteem';
const drawerCover = require("../../assets/drawer-cover.png"); const drawerCover = require('../../assets/drawer-cover.png');
const masterKeyMenuOptions = [ const masterKeyMenuOptions = [
{ {
name: "Home", name: 'Home',
route: "Home", route: 'Home',
icon: "home", icon: 'home',
bg: "#C5F442" bg: '#C5F442',
}, },
{ {
name: "Bookmarks", name: 'Bookmarks',
route: "bookmarks", route: 'bookmarks',
icon: "bookmarks", icon: 'bookmarks',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Drafts", name: 'Drafts',
route: "drafts", route: 'drafts',
icon: "create", icon: 'create',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Favorites", name: 'Favorites',
route: "favorites", route: 'favorites',
icon: "heart", icon: 'heart',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Schedules", name: 'Schedules',
route: "schedules", route: 'schedules',
icon: "time", icon: 'time',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Transfer", name: 'Transfer',
route: "transfer", route: 'transfer',
icon: "md-send", icon: 'md-send',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Exchange", name: 'Exchange',
route: "exchange", route: 'exchange',
icon: "repeat", icon: 'repeat',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Marketplace", name: 'Marketplace',
route: "marketplace", route: 'marketplace',
icon: "cube", icon: 'cube',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Settings", name: 'Settings',
route: "settings", route: 'settings',
icon: "settings", icon: 'settings',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "FAQ", name: 'FAQ',
route: "faq", route: 'faq',
icon: "ios-information-circle-outline", icon: 'ios-information-circle-outline',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "About", name: 'About',
route: "about", route: 'about',
icon: "information-circle", icon: 'information-circle',
bg: "#DA4437", bg: '#DA4437',
}, },
]; ];
const postingKeyMenuOptions = [ const postingKeyMenuOptions = [
{ {
name: "Home", name: 'Home',
route: "Home", route: 'Home',
icon: "home", icon: 'home',
bg: "#C5F442" bg: '#C5F442',
}, },
{ {
name: "Bookmarks", name: 'Bookmarks',
route: "bookmarks", route: 'bookmarks',
icon: "bookmarks", icon: 'bookmarks',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Drafts", name: 'Drafts',
route: "drafts", route: 'drafts',
icon: "create", icon: 'create',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Favorites", name: 'Favorites',
route: "favorites", route: 'favorites',
icon: "heart", icon: 'heart',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Schedules", name: 'Schedules',
route: "schedules", route: 'schedules',
icon: "time", icon: 'time',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Marketplace", name: 'Marketplace',
route: "marketplace", route: 'marketplace',
icon: "cube", icon: 'cube',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "Settings", name: 'Settings',
route: "settings", route: 'settings',
icon: "settings", icon: 'settings',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "FAQ", name: 'FAQ',
route: "faq", route: 'faq',
icon: "ios-information-circle-outline", icon: 'ios-information-circle-outline',
bg: "#DA4437", bg: '#DA4437',
}, },
{ {
name: "About", name: 'About',
route: "about", route: 'about',
icon: "information-circle", icon: 'information-circle',
bg: "#DA4437", bg: '#DA4437',
}, },
]; ];
export class LoggedInSideBar extends Component { export class LoggedInSideBar extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
shadowOffsetWidth: 1, shadowOffsetWidth: 1,
shadowRadius: 4, shadowRadius: 4,
user: [], user: [],
loginType: '', loginType: '',
json_metadata: {} json_metadata: {},
};
}
componentDidMount() {
AsyncStorage.getItem('user')
.then(result => {
let res = JSON.parse(result);
if (res.auth_type === 'master_key') {
this.setState({ loginType: 'master_key' });
} else {
this.setState({ loginType: 'posting_key' });
}
getAccount(res.username)
.then(result => {
let json_metadata = JSON.parse(result[0].json_metadata);
this.setState({
user: result[0],
avatar: `https://steemitimages.com/u/${
result[0].name
}/avatar/small`,
json_metadata: json_metadata.profile,
});
})
.catch(err => {});
})
.catch(err => {});
}
Logout = () => {
AsyncStorage.clear().then(() => {
this.props.navigation.navigate('LoggedOut');
});
}; };
}
componentDidMount() { render() {
AsyncStorage.getItem('user').then((result) => { return (
let res = JSON.parse(result); <Container>
if (res.auth_type === 'master_key') { <Content
this.setState({ loginType: 'master_key' }); bounces={false}
} else { style={{ flex: 1, backgroundColor: '#fff', top: -1 }}
this.setState({ loginType: 'posting_key' }); >
} <Image source={drawerCover} style={styles.drawerCover} />
getAccount(res.username).then((result) => { <Thumbnail
let json_metadata = JSON.parse(result[0].json_metadata) square
this.setState({ style={styles.drawerImage}
user: result[0], source={{ uri: this.state.avatar }}
avatar:`https://steemitimages.com/u/${result[0].name}/avatar/small`, />
json_metadata: json_metadata.profile <View style={styles.info}>
}) <Text style={styles.userLabel}>
}).catch((err) => { {this.state.json_metadata.name || ''}
</Text>
}); <Text style={styles.userLabel}>
{this.state.user.name}
}).catch((err) => { </Text>
</View>
}); <List
} dataArray={
this.state.loginType === 'master_key'
Logout = () => { ? masterKeyMenuOptions
AsyncStorage.clear().then(() => { : postingKeyMenuOptions
this.props.navigation.navigate('LoggedOut'); }
}); renderRow={data => (
} <ListItem
button
render() { noBorder
return ( onPress={() =>
<Container> this.props.navigation.navigate(data.route)
<Content }
bounces={false} >
style={{ flex: 1, backgroundColor: "#fff", top: -1 }} <Left>
> <Icon
<Image source={drawerCover} style={styles.drawerCover} /> active
<Thumbnail square style={styles.drawerImage} source={{uri: this.state.avatar}} /> name={data.icon}
<View style={styles.info}> style={{
<Text style={styles.userLabel}>{ this.state.json_metadata.name || '' }</Text> color: '#777',
<Text style={styles.userLabel}>{ this.state.user.name }</Text> fontSize: 26,
</View> width: 30,
<List }}
dataArray={ this.state.loginType === 'master_key' ? masterKeyMenuOptions : postingKeyMenuOptions } />
renderRow={data => <Text style={styles.text}>{data.name}</Text>
<ListItem </Left>
button {data.types && (
noBorder <Right style={{ flex: 1 }}>
onPress={() => this.props.navigation.navigate(data.route)} <Badge
> style={{
<Left> borderRadius: 3,
<Icon height: 25,
active width: 72,
name={data.icon} backgroundColor: data.bg,
style={{ color: "#777", fontSize: 26, width: 30 }} }}
/> >
<Text style={styles.text}> <Text style={styles.badgeText}>{`${
{data.name} data.types
</Text> } Types`}</Text>
</Left> </Badge>
{data.types && </Right>
<Right style={{ flex: 1 }}> )}
<Badge </ListItem>
style={{ )}
borderRadius: 3, />
height: 25, <ListItem noBorder onPress={() => this.Logout()}>
width: 72, <Left>
backgroundColor: data.bg <Icon
}} active
> name="log-out"
<Text style={{
style={styles.badgeText} color: '#777',
>{`${data.types} Types`}</Text> fontSize: 26,
</Badge> width: 30,
</Right>} }}
</ListItem>} />
/> <Text style={styles.text}>Logout</Text>
<ListItem noBorder onPress={() => this.Logout() }> </Left>
<Left> </ListItem>
<Icon active name="log-out" style={{ color: "#777", fontSize: 26, width: 30 }}/> </Content>
<Text style={styles.text}> </Container>
Logout );
</Text> }
</Left> }
</ListItem>
</Content>
</Container>
);
}
}

View File

@ -1,91 +1,101 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Image } from "react-native"; import { Image } from 'react-native';
import { import {
Content, Content,
Text, Text,
List, List,
ListItem, ListItem,
Icon, Icon,
Container, Container,
Left, Left,
Right, Right,
Badge Badge,
} from "native-base"; } from 'native-base';
import styles from "./style"; import styles from './style';
const drawerCover = require("../../assets/drawer-cover.png"); const drawerCover = require('../../assets/drawer-cover.png');
const drawerImage = require("../../assets/esteem.jpg"); const drawerImage = require('../../assets/esteem.jpg');
const datas = [ const datas = [
{ {
name: "Home", name: 'Home',
route: "Home", route: 'Home',
icon: "home", icon: 'home',
bg: "#C5F442" bg: '#C5F442',
}, },
{ {
name: "Login", name: 'Login',
route: "Login", route: 'Login',
icon: "log-in", icon: 'log-in',
bg: "#C5F442" bg: '#C5F442',
} },
]; ];
export class LoggedOutSideBar extends Component { export class LoggedOutSideBar extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
shadowOffsetWidth: 1, shadowOffsetWidth: 1,
shadowRadius: 4 shadowRadius: 4,
}; };
} }
render() { render() {
return ( return (
<Container> <Container>
<Content <Content
bounces={false} bounces={false}
style={{ flex: 1, backgroundColor: "#fff", top: -1 }} style={{ flex: 1, backgroundColor: '#fff', top: -1 }}
> >
<Image source={drawerCover} style={styles.drawerCover} /> <Image source={drawerCover} style={styles.drawerCover} />
<Image square style={styles.drawerImage} source={drawerImage} /> <Image
square
style={styles.drawerImage}
source={drawerImage}
/>
<List <List
dataArray={datas} dataArray={datas}
renderRow={data => renderRow={data => (
<ListItem <ListItem
button button
noBorder noBorder
onPress={() => this.props.navigation.navigate(data.route)} onPress={() =>
> this.props.navigation.navigate(data.route)
<Left> }
<Icon >
active <Left>
name={data.icon} <Icon
style={{ color: "#777", fontSize: 26, width: 30 }} active
/> name={data.icon}
<Text style={styles.text}> style={{
{data.name} color: '#777',
</Text> fontSize: 26,
</Left> width: 30,
{data.types && }}
<Right style={{ flex: 1 }}> />
<Badge <Text style={styles.text}>{data.name}</Text>
style={{ </Left>
borderRadius: 3, {data.types && (
height: 25, <Right style={{ flex: 1 }}>
width: 72, <Badge
backgroundColor: data.bg style={{
}} borderRadius: 3,
> height: 25,
<Text width: 72,
style={styles.badgeText} backgroundColor: data.bg,
>{`${data.types} Types`}</Text> }}
</Badge> >
</Right>} <Text style={styles.badgeText}>{`${
</ListItem>} data.types
/> } Types`}</Text>
</Content> </Badge>
</Container> </Right>
); )}
} </ListItem>
} )}
/>
</Content>
</Container>
);
}
}

View File

@ -1,48 +1,47 @@
const React = require("react-native"); const React = require('react-native');
const { Platform, Dimensions } = React; const { Platform, Dimensions } = React;
const deviceHeight = Dimensions.get("window").height; const deviceHeight = Dimensions.get('window').height;
const deviceWidth = Dimensions.get("window").width; const deviceWidth = Dimensions.get('window').width;
export default { export default {
drawerCover: { drawerCover: {
alignSelf: "stretch", alignSelf: 'stretch',
height: deviceHeight / 4, height: deviceHeight / 4,
width: null, width: null,
position: "relative", position: 'relative',
marginBottom: 10 marginBottom: 10,
}, },
drawerImage: { drawerImage: {
position: "absolute", position: 'absolute',
left: Platform.OS === "android" ? deviceWidth / 10 : deviceWidth / 30, left: Platform.OS === 'android' ? deviceWidth / 10 : deviceWidth / 30,
top: Platform.OS === "android" ? deviceHeight / 13 : deviceHeight / 15, top: Platform.OS === 'android' ? deviceHeight / 13 : deviceHeight / 15,
width: 60, width: 60,
height: 60, height: 60,
resizeMode: "cover", resizeMode: 'cover',
borderWidth: 1, borderWidth: 1,
borderColor: 'white', borderColor: 'white',
borderRadius: 30, borderRadius: 30,
}, },
text: { text: {
fontWeight: Platform.OS === "ios" ? "500" : "400", fontWeight: Platform.OS === 'ios' ? '500' : '400',
fontSize: 16, fontSize: 16,
marginLeft: 20 marginLeft: 20,
}, },
badgeText: { badgeText: {
fontSize: Platform.OS === "ios" ? 13 : 11, fontSize: Platform.OS === 'ios' ? 13 : 11,
fontWeight: "400", fontWeight: '400',
textAlign: "center", textAlign: 'center',
marginTop: Platform.OS === "android" ? -3 : undefined marginTop: Platform.OS === 'android' ? -3 : undefined,
}, },
info: { info: {
position: 'absolute', position: 'absolute',
top: Platform.OS === "android" ? deviceHeight / 13 : deviceHeight / 5.8, top: Platform.OS === 'android' ? deviceHeight / 13 : deviceHeight / 5.8,
left: Platform.OS === "android" ? deviceWidth / 10 : deviceWidth / 40, left: Platform.OS === 'android' ? deviceWidth / 10 : deviceWidth / 40,
},
}, userLabel: {
userLabel: { fontWeight: 'bold',
fontWeight: 'bold', color: 'white',
color: 'white', marginBottom: 3,
marginBottom: 3 },
}
}; };

View File

@ -1,6 +1,16 @@
import React from 'react'; import React from 'react';
import { Dimensions, StyleSheet, StatusBar } from 'react-native'; import { Dimensions, StyleSheet, StatusBar } from 'react-native';
import { Container, Content, Header, Left, Body, Right, Button, Icon, Title } from 'native-base'; import {
Container,
Content,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
} from 'native-base';
import HTMLView from 'react-native-htmlview'; import HTMLView from 'react-native-htmlview';
import HTML from 'react-native-render-html'; import HTML from 'react-native-render-html';
import { Client } from 'dsteem'; import { Client } from 'dsteem';
@ -9,98 +19,106 @@ const client = new Client('https://api.steemit.com');
import { parsePost, protocolUrl2Obj } from '../../utils/PostParser'; import { parsePost, protocolUrl2Obj } from '../../utils/PostParser';
class SinglePostPage extends React.Component { class SinglePostPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
}
componentDidMount() {
}
onLinkPress(evt, href, attribs) {
let steemPost = href.match(/^https?:\/\/(.*)\/(.*)\/(@[\w\.\d-]+)\/(.*)/i)
if (attribs.class === "markdown-author-link") {
this.props.navigation.navigate('Author', { author: href })
} else if (steemPost.length > 3) {
steemPost[3] = steemPost[3].replace('@', '')
client.database.call('get_content', [steemPost[3], steemPost[4]]).then((result) => {
let content = parsePost(result);
this.props.navigation.push('Post',{ content: content })
}).catch((err) => {
alert(err)
});
} }
}
alterNode(node) { componentDidMount() {}
if (node.name == 'img' || node.name == 'a') {
// console.log(node); onLinkPress(evt, href, attribs) {
} else if (node.name == 'iframe') { let steemPost = href.match(
node.attribs.height = 200 /^https?:\/\/(.*)\/(.*)\/(@[\w\.\d-]+)\/(.*)/i
);
if (attribs.class === 'markdown-author-link') {
this.props.navigation.navigate('Author', { author: href });
} else if (steemPost.length > 3) {
steemPost[3] = steemPost[3].replace('@', '');
client.database
.call('get_content', [steemPost[3], steemPost[4]])
.then(result => {
let content = parsePost(result);
this.props.navigation.push('Post', { content: content });
})
.catch(err => {
alert(err);
});
}
} }
}
render() { alterNode(node) {
return ( if (node.name == 'img' || node.name == 'a') {
<Container style={{ top: StatusBar.currentHeight }}> // console.log(node);
<Header> } else if (node.name == 'iframe') {
<Left> node.attribs.height = 200;
<Button transparent onPress={() => this.props.navigation.goBack()}> }
<Icon name='arrow-back' /> }
</Button>
</Left> render() {
<Body> return (
<Title></Title> <Container style={{ top: StatusBar.currentHeight }}>
</Body> <Header>
<Right> <Left>
<Button transparent> <Button
<Icon name='bookmark' /> transparent
</Button> onPress={() => this.props.navigation.goBack()}
<Button transparent> >
<Icon name='more' /> <Icon name="arrow-back" />
</Button> </Button>
</Right> </Left>
</Header> <Body>
<Content> <Title />
<HTML </Body>
html={this.props.navigation.state.params.content.body} <Right>
staticContentMaxWidth={ Dimensions.get('window').width - 20 } <Button transparent>
onLinkPress={ (evt, href, hrefatr) => this.onLinkPress(evt, href, hrefatr) } <Icon name="bookmark" />
containerStyle={{ padding: 10 }} </Button>
textSelectable={true} <Button transparent>
tagsStyles={styles} <Icon name="more" />
ignoredTags={['script']} </Button>
debug={true} </Right>
alterNode={ (node) => { this.alterNode(node) } } </Header>
imagesMaxWidth={ Dimensions.get('window').width } /> <Content>
</Content> <HTML
</Container> html={this.props.navigation.state.params.content.body}
) staticContentMaxWidth={
} Dimensions.get('window').width - 20
}
onLinkPress={(evt, href, hrefatr) =>
this.onLinkPress(evt, href, hrefatr)
}
containerStyle={{ padding: 10 }}
textSelectable={true}
tagsStyles={styles}
ignoredTags={['script']}
debug={true}
alterNode={node => {
this.alterNode(node);
}}
imagesMaxWidth={Dimensions.get('window').width}
/>
</Content>
</Container>
);
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center',
}, },
iframe: { iframe: {
maxWidth: Dimensions.get('window').width, maxWidth: Dimensions.get('window').width,
marginVertical: 10, marginVertical: 10,
left: -10 left: -10,
}, },
p: { p: {},
img: {
}, left: -10,
img: { marginVertical: 10,
left: -10, },
marginVertical: 10 });
}
})
export default SinglePostPage; export default SinglePostPage;

View File

@ -1,37 +1,46 @@
import * as React from 'react'; import * as React from 'react';
import { Container, Header, Left, Body, Right, Button, Icon, Title } from 'native-base'; import {
Container,
Header,
Left,
Body,
Right,
Button,
Icon,
Title,
} from 'native-base';
class WalletPage extends React.Component { class WalletPage extends React.Component {
static navigationOptions = { static navigationOptions = {
title: 'Wallet', title: 'Wallet',
}; };
render() { render() {
return ( return (
<Container> <Container>
<Header> <Header>
<Left> <Left>
<Button transparent> <Button transparent>
<Icon name='menu' /> <Icon name="menu" />
</Button> </Button>
</Left> </Left>
<Body> <Body>
<Title>Wallet</Title> <Title>Wallet</Title>
</Body> </Body>
<Right> <Right>
<Button transparent> <Button transparent>
<Icon name='search' /> <Icon name="search" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='heart' /> <Icon name="heart" />
</Button> </Button>
<Button transparent> <Button transparent>
<Icon name='more' /> <Icon name="more" />
</Button> </Button>
</Right> </Right>
</Header> </Header>
</Container> </Container>
) );
} }
} }
export default WalletPage; export default WalletPage;

View File

@ -1,34 +1,71 @@
// TODO: Refactor // TODO: Refactor
export const sanitizeNode = (node) => { export const sanitizeNode = node => {
const ALLOWED_TAGS = [
'A',
'STRONG',
'B',
'I',
'EM',
'CODE',
'BLOCKQUOTE',
'SUP',
'SUB',
'H2',
'H1',
'H3',
'H4',
'H5',
'H6',
'DIV',
'P',
'IFRAME',
'CENTER',
'UL',
'OL',
'LI',
'TABLE',
'THEAD',
'TBODY',
'TR',
'TD',
'TH',
'HR',
'BR',
'IMG',
];
const ALLOWED_TAGS = [ const ALLOWED_ATTRS = [
'A', 'STRONG', 'B', 'I', 'EM', 'CODE', 'BLOCKQUOTE', 'SUP', 'SUB', 'data-permlink',
'H2', 'H1', 'H3', 'H4', 'H5', 'H6', 'data-tag',
'DIV', 'P', 'IFRAME', 'CENTER', 'data-author',
'UL', 'OL', 'LI', 'data-href',
'TABLE', 'THEAD', 'TBODY', 'TR', 'TD', 'TH', 'class',
'HR', 'BR', 'IMG' 'src',
]; 'alt',
'title',
'width',
'height',
'border',
'frameborder',
'allowfullscreen',
'mozallowfullscreen',
'webkitallowfullscreen',
];
const ALLOWED_ATTRS = [ const allElems = node.querySelectorAll('*');
'data-permlink', 'data-tag', 'data-author', 'data-href', allElems.forEach(el => {
'class', 'src', 'alt', 'title', 'width', 'height', 'border', if (ALLOWED_TAGS.indexOf(el.tagName) === -1) {
'frameborder', 'allowfullscreen', 'mozallowfullscreen', 'webkitallowfullscreen' el.outerHTML = `<span>${el.innerText
]; .replace('>', '&gt;')
.replace('<', '&lt;')}</span>`;
}
const allElems = node.querySelectorAll('*'); for (let attr of el.attributes) {
allElems.forEach((el) => { if (ALLOWED_ATTRS.indexOf(attr.name) === -1) {
el.removeAttribute(attr.name);
}
}
});
if (ALLOWED_TAGS.indexOf(el.tagName) === -1) { return node;
el.outerHTML = `<span>${el.innerText.replace('>', '&gt;').replace('<', '&lt;')}</span>`; };
}
for (let attr of el.attributes) {
if (ALLOWED_ATTRS.indexOf(attr.name) === -1) {
el.removeAttribute(attr.name)
}
}
});
return node;
};

View File

@ -3,105 +3,123 @@ import { postSummary } from './PostSummary';
import { reputation } from './Reputation'; import { reputation } from './Reputation';
import moment from 'moment'; import moment from 'moment';
const md = new Remarkable({html: true, breaks: true, linkify: true}); const md = new Remarkable({ html: true, breaks: true, linkify: true });
export const replaceAuthorNames = (input) => { export const replaceAuthorNames = input => {
return input.replace( return input.replace(
/(^|[^a-zA-Z0-9_!#$%&*@\/]|(^|[^a-zA-Z0-9_+~.-\/]))[@]([a-z][-\.a-z\d]+[a-z\d])/gi, /(^|[^a-zA-Z0-9_!#$%&*@\/]|(^|[^a-zA-Z0-9_+~.-\/]))[@]([a-z][-\.a-z\d]+[a-z\d])/gi,
(match, preceeding1, preceeding2, user) => { (match, preceeding1, preceeding2, user) => {
const userLower = user.toLowerCase(); const userLower = user.toLowerCase();
const preceedings = (preceeding1 || '') + (preceeding2 || ''); const preceedings = (preceeding1 || '') + (preceeding2 || '');
return `${preceedings}<a class="markdown-author-link" href="${userLower}" data-author="${userLower}">@${user}</a>` return `${preceedings}<a class="markdown-author-link" href="${userLower}" data-author="${userLower}">@${user}</a>`;
} }
); );
}; };
export const replaceTags = (input) => { export const replaceTags = input => {
return input.replace(/(^|\s|>)(#[-a-z\d]+)/gi, tag => { return input.replace(/(^|\s|>)(#[-a-z\d]+)/gi, tag => {
if (/#[\d]+$/.test(tag)) return tag; // do not allow only numbers (like #1) if (/#[\d]+$/.test(tag)) return tag; // do not allow only numbers (like #1)
const preceding = /^\s|>/.test(tag) ? tag[0] : ''; // space or closing tag (>) const preceding = /^\s|>/.test(tag) ? tag[0] : ''; // space or closing tag (>)
tag = tag.replace('>', ''); // remove closing tag tag = tag.replace('>', ''); // remove closing tag
const tag2 = tag.trim().substring(1); const tag2 = tag.trim().substring(1);
const tagLower = tag2.toLowerCase(); const tagLower = tag2.toLowerCase();
return preceding + `<a class="markdown-tag-link" href="${tagLower}" data-tag="${tagLower}">${tag.trim()}</a>`; return (
}); preceding +
}; `<a class="markdown-tag-link" href="${tagLower}" data-tag="${tagLower}">${tag.trim()}</a>`
);
export const markDown2Html = (input) => {
if (!input) {
return '';
}
// Start replacing user names
let output = replaceAuthorNames(input);
// Replace tags
output = replaceTags(output);
output = md.render(output);
const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico))/gim;
const postRegex = /^https?:\/\/(.*)\/(.*)\/(@[\w\.\d-]+)\/(.*)/i;
const youTubeRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g;
const vimeoRegex = /(https?:\/\/)?(www\.)?(?:vimeo)\.com.*(?:videos|video|channels|)\/([\d]+)/i;
const dTubeRegex = /(https?:\/\/d.tube.#!\/v\/)(\w+)\/(\w+)/g;
// TODO: Implement Regex
return output;
};
export const parsePosts = (posts) => {
posts.map(post => {
post.json_metadata = JSON.parse(post.json_metadata);
(post.json_metadata.image) ? post.image = post.json_metadata.image[0] : '';
post.pending_payout_value = parseFloat(post.pending_payout_value).toFixed(2);
post.created = moment.utc(post.created).local().fromNow();
post.vote_count = post.active_votes.length;
post.author_reputation = reputation(post.author_reputation);
post.avatar = `https://steemitimages.com/u/${post.author}/avatar/small`;
post.body = markDown2Html(post.body)
post.summary = postSummary(post.body, 100);
post.raw_body = post.body;
post.active_votes.sort((a,b) => {
return b.rshares - a.rshares
}); });
if (post.active_votes.length > 2) { };
post.top_likers = [post.active_votes[0].voter, post.active_votes[1].voter, post.active_votes[2].voter]
export const markDown2Html = input => {
if (!input) {
return '';
} }
});
return posts;
}
export const protocolUrl2Obj = (url) => { // Start replacing user names
let urlPart = url.split('://')[1]; let output = replaceAuthorNames(input);
// remove last char if / // Replace tags
if(urlPart.endsWith('/')){ output = replaceTags(output);
urlPart = urlPart.substring(0, urlPart.length - 1);
}
const parts = urlPart.split('/'); output = md.render(output);
// filter const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico))/gim;
if (parts.length === 1 && filters.includes(parts[0])) { const postRegex = /^https?:\/\/(.*)\/(.*)\/(@[\w\.\d-]+)\/(.*)/i;
return {type: 'filter', filter: parts[0]}; const youTubeRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g;
} const vimeoRegex = /(https?:\/\/)?(www\.)?(?:vimeo)\.com.*(?:videos|video|channels|)\/([\d]+)/i;
const dTubeRegex = /(https?:\/\/d.tube.#!\/v\/)(\w+)\/(\w+)/g;
// filter with tag // TODO: Implement Regex
if (parts.length === 2 && filters.includes(parts[0])) {
return {type: 'filter-tag', filter: parts[0], tag: parts[1]};
}
// account return output;
if (parts.length === 1 && parts[0].startsWith('@')) { };
return {type: 'account', account: parts[0].replace('@', '')};
}
// post export const parsePosts = posts => {
if (parts.length === 3 && parts[1].startsWith('@')) { posts.map(post => {
return {type: 'post', cat: parts[0], author: parts[1].replace('@', ''), permlink: parts[2]}; post.json_metadata = JSON.parse(post.json_metadata);
} post.json_metadata.image
}; ? (post.image = post.json_metadata.image[0])
: '';
post.pending_payout_value = parseFloat(
post.pending_payout_value
).toFixed(2);
post.created = moment
.utc(post.created)
.local()
.fromNow();
post.vote_count = post.active_votes.length;
post.author_reputation = reputation(post.author_reputation);
post.avatar = `https://steemitimages.com/u/${post.author}/avatar/small`;
post.body = markDown2Html(post.body);
post.summary = postSummary(post.body, 100);
post.raw_body = post.body;
post.active_votes.sort((a, b) => {
return b.rshares - a.rshares;
});
if (post.active_votes.length > 2) {
post.top_likers = [
post.active_votes[0].voter,
post.active_votes[1].voter,
post.active_votes[2].voter,
];
}
});
return posts;
};
export const protocolUrl2Obj = url => {
let urlPart = url.split('://')[1];
// remove last char if /
if (urlPart.endsWith('/')) {
urlPart = urlPart.substring(0, urlPart.length - 1);
}
const parts = urlPart.split('/');
// filter
if (parts.length === 1 && filters.includes(parts[0])) {
return { type: 'filter', filter: parts[0] };
}
// filter with tag
if (parts.length === 2 && filters.includes(parts[0])) {
return { type: 'filter-tag', filter: parts[0], tag: parts[1] };
}
// account
if (parts.length === 1 && parts[0].startsWith('@')) {
return { type: 'account', account: parts[0].replace('@', '') };
}
// post
if (parts.length === 3 && parts[1].startsWith('@')) {
return {
type: 'post',
cat: parts[0],
author: parts[1].replace('@', ''),
permlink: parts[2],
};
}
};

View File

@ -1,19 +1,19 @@
export const postSummary = (postBody, length) => { export const postSummary = (postBody, length) => {
if (!postBody) { if (!postBody) {
return ''; return '';
} }
postBody = postBody postBody = postBody
.replace(/(<([^>]+)>)/ig, '') // Remove html tags .replace(/(<([^>]+)>)/gi, '') // Remove html tags
.replace(/\r?\n|\r/g, ' ') // Remove new lines .replace(/\r?\n|\r/g, ' ') // Remove new lines
.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') // Remove urls .replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') // Remove urls
.trim() .trim()
.replace(/ +(?= )/g, ''); // Remove all multiple spaces .replace(/ +(?= )/g, ''); // Remove all multiple spaces
if (length) { if (length) {
// Truncate // Truncate
postBody = postBody.substring(0, length); postBody = postBody.substring(0, length);
} }
return postBody; return postBody;
}; };

View File

@ -1,10 +1,10 @@
export const reputation = (reputation) => { export const reputation = reputation => {
if (reputation == null) return reputation; if (reputation == null) return reputation;
reputation = parseInt(reputation); reputation = parseInt(reputation);
let log = Math.log10(reputation); let log = Math.log10(reputation);
log = log - 9; log = log - 9;
log = log * 9; log = log * 9;
log = log + 25; log = log + 25;
log = Math.floor(log); log = Math.floor(log);
return log; return log;
} };