Merge pull request #627 from esteemapp/feature/tag-searching

Feature/tag searching
This commit is contained in:
uğur erdal 2019-02-27 23:50:24 +03:00 committed by GitHub
commit d515c4c10a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 350 additions and 9195 deletions

View File

@ -93,23 +93,20 @@ class PostsView extends Component {
let newPosts = []; let newPosts = [];
this.setState({ isLoading: true }); this.setState({ isLoading: true });
if ((!filter && tag) || filter === 'feed' || filter === 'blog' || getFor === 'blog') { if (tag || filter === 'feed' || filter === 'blog' || getFor === 'blog') {
options = {
tag,
limit: 3,
};
} else if (filter === 'reblogs') {
options = { options = {
tag, tag,
limit: 3, limit: 3,
}; };
} else { } else {
// TODO: implement filtering of reblogs on `blog` and `feed` posts options = {
if (filter == 'reblogs') { limit: 3,
options = { };
tag,
limit: 3,
};
} else {
options = {
limit: 3,
};
}
} }
if (startAuthor && startPermlink && !refreshing) { if (startAuthor && startPermlink && !refreshing) {

View File

@ -0,0 +1,3 @@
import SearchInput from './view/searchInputView';
export default SearchInput;

View File

@ -0,0 +1,38 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
inputWrapper: {
marginTop: 20,
backgroundColor: '$primaryLightBackground',
flexDirection: 'row',
height: 44,
borderRadius: 8,
padding: 5,
justifyContent: 'center',
marginHorizontal: 16,
},
icon: {
alignSelf: 'center',
color: '$iconColor',
marginLeft: 16,
},
input: {
color: '$primaryDarkGray',
fontSize: 14,
flexGrow: 1,
padding: 7,
maxWidth: '$deviceWidth - 100',
},
closeIconButton: {
width: 20,
height: 20,
borderRadius: 20 / 2,
justifyContent: 'center',
alignSelf: 'center',
marginRight: 16,
},
closeIcon: {
color: '$iconColor',
fontSize: 22,
},
});

View File

@ -0,0 +1,43 @@
import React from 'react';
import { View, SafeAreaView } from 'react-native';
// Components
import { Icon } from '../../icon';
import { IconButton } from '../../iconButton';
import { TextInput } from '../../textInput';
// Styles
import styles from './searchInputStyles';
/* Props
* ------------------------------------------------
* @prop { func } onChangeText - The function will trigger when input on change
* @prop { func } handleOnModalClose - Handle on click method for close button
* @prop { string } placeholder - Placeholder for input
* @prop { bool } editable - Editable value for input. Default value is true.
*/
const SearchInputView = ({
onChangeText, handleOnModalClose, placeholder, editable = true,
}) => (
<SafeAreaView style={styles.inputWrapper}>
<Icon style={styles.icon} iconType="FontAwesome" name="search" size={15} />
<TextInput
style={styles.input}
onChangeText={text => onChangeText(text)}
placeholder={placeholder}
placeholderTextColor="#c1c5c7"
autoCapitalize="none"
autoFocus
editable={editable}
/>
<IconButton
iconStyle={styles.closeIcon}
iconType="Ionicons"
style={styles.closeIconButton}
name="ios-close-circle-outline"
onPress={() => handleOnModalClose()}
/>
</SafeAreaView>
);
export default SearchInputView;

View File

@ -3,7 +3,7 @@ import { withNavigation } from 'react-navigation';
// Services and Actions // Services and Actions
import { search } from '../../../providers/esteem/esteem'; import { search } from '../../../providers/esteem/esteem';
import { lookupAccounts } from '../../../providers/steem/dsteem'; import { lookupAccounts, getTrendingTags } from '../../../providers/steem/dsteem';
// Middleware // Middleware
@ -39,15 +39,31 @@ class SearchModalContainer extends PureComponent {
}; };
_handleOnChangeSearchInput = (text) => { _handleOnChangeSearchInput = (text) => {
if (text && text !== '@') { if (text && text !== '@' && text !== '#') {
if (text[0] === '@') { if (text[0] === '@') {
lookupAccounts(text.substr(1)).then((res) => { lookupAccounts(text.substr(1)).then((res) => {
const users = res.map(item => ({ author: item })); const users = res.map(item => ({
image: `https://steemitimages.com/u/${item}/avatar/small`,
text: item,
}));
this.setState({ searchResults: { type: 'user', data: users } }); this.setState({ searchResults: { type: 'user', data: users } });
}); });
} else if (text[0] === '#') {
getTrendingTags(text.substr(1)).then((res) => {
const tags = res.map(item => ({
text: `#${item.name}`,
}));
this.setState({ searchResults: { type: 'tag', data: tags } });
});
} else { } else {
search({ q: text }).then((res) => { search({ q: text }).then((res) => {
res.results = res.results.filter(item => item.title !== ''); res.results = res.results
.filter(item => item.title !== '')
.map(item => ({
image: item.img_url || `https://steemitimages.com/u/${item.author}/avatar/small`,
text: item.title,
}));
this.setState({ searchResults: { type: 'content', data: res.results } }); this.setState({ searchResults: { type: 'content', data: res.results } });
}); });
} }
@ -56,24 +72,45 @@ class SearchModalContainer extends PureComponent {
_handleOnPressListItem = (type, item) => { _handleOnPressListItem = (type, item) => {
const { navigation, handleOnClose } = this.props; const { navigation, handleOnClose } = this.props;
let routeName = null;
let params = null;
let key = null;
handleOnClose(); handleOnClose();
this.setState({ searchResults: {} }); this.setState({ searchResults: {} });
if (type === 'user') {
navigation.navigate({ switch (type) {
routeName: ROUTES.SCREENS.PROFILE, case 'user':
params: { routeName = ROUTES.SCREENS.PROFILE;
params = {
username: item.author, username: item.author,
}, };
key: item.author, key = item.author;
}); break;
} else if (type === 'content') { case 'content':
navigation.navigate({ routeName = ROUTES.SCREENS.POST;
routeName: ROUTES.SCREENS.POST, params = {
params: {
author: item.author, author: item.author,
permlink: item.permlink, permlink: item.permlink,
}, };
key: item.permlink, key = item.permlink;
break;
case 'tag':
routeName = ROUTES.SCREENS.SEARCH_RESULT;
params = {
tag: item.text.substr(1),
};
break;
default:
break;
}
if (routeName) {
navigation.navigate({
routeName,
params,
key,
}); });
} }
}; };

View File

@ -53,6 +53,12 @@ export default EStyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
}, },
searchItemImageWrapper: {
flex: 1,
},
searchItemTextWrapper: {
flex: 7,
},
searchItemImage: { searchItemImage: {
width: 40, width: 40,
height: 40, height: 40,
@ -61,7 +67,7 @@ export default EStyleSheet.create({
borderColor: '$primaryGray', borderColor: '$primaryGray',
}, },
searchItemText: { searchItemText: {
color: '$primaryDarkGray', color: '$white',
marginLeft: 10, marginLeft: 10,
}, },
}); });

View File

@ -1,18 +1,16 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { import {
View, Text, FlatList, TouchableHighlight, SafeAreaView, View, Text, FlatList, TouchableHighlight,
} from 'react-native'; } from 'react-native';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
// Constants // Constants
// Components // Components
import { Icon } from '../../icon';
import { IconButton } from '../../iconButton';
import { Modal } from '../..'; import { Modal } from '../..';
import { TextInput } from '../../textInput'; import SearchInput from '../../searchInput';
// Styles // Styles
// eslint-disable-next-line
import styles from './searchModalStyles'; import styles from './searchModalStyles';
class SearchModalView extends PureComponent { class SearchModalView extends PureComponent {
@ -49,27 +47,11 @@ class SearchModalView extends PureComponent {
isTransparent isTransparent
> >
<View style={styles.container}> <View style={styles.container}>
<SafeAreaView style={styles.safeArea}> <SearchInput
<View style={styles.inputWrapper}> onChangeText={handleOnChangeSearchInput}
<Icon style={styles.icon} iconType="FontAwesome" name="search" size={15} /> handleOnModalClose={handleOnClose}
<TextInput placeholder={placeholder}
style={styles.input} />
onChangeText={text => handleOnChangeSearchInput(text)}
placeholder={placeholder}
placeholderTextColor="#c1c5c7"
autoCapitalize="none"
autoFocus
/>
<IconButton
iconStyle={styles.closeIcon}
iconType="Ionicons"
style={styles.closeIconButton}
name="ios-close-circle-outline"
onPress={() => handleOnClose()}
/>
</View>
</SafeAreaView>
<View style={styles.body}> <View style={styles.body}>
<FlatList <FlatList
data={searchResults.data} data={searchResults.data}
@ -77,19 +59,19 @@ class SearchModalView extends PureComponent {
renderItem={({ item }) => ( renderItem={({ item }) => (
<TouchableHighlight onPress={() => handleOnPressListItem(searchResults.type, item)}> <TouchableHighlight onPress={() => handleOnPressListItem(searchResults.type, item)}>
<View style={styles.searhItems}> <View style={styles.searhItems}>
<FastImage <View style={styles.searchItemImageWrapper}>
source={{ {item.image && (
uri: <FastImage
searchResults.type === 'user' source={{
? `https://steemitimages.com/u/${item.author}/avatar/small` uri: item.image,
: item.img_url }}
|| `https://steemitimages.com/u/${item.author}/avatar/small`, style={styles.searchItemImage}
}} />
style={styles.searchItemImage} )}
/> </View>
<Text style={styles.searchItemText}> <View style={styles.searchItemTextWrapper}>
{searchResults.type === 'user' ? item.author : item.title} {item.text && <Text style={styles.searchItemText}>{item.text}</Text>}
</Text> </View>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
)} )}

View File

@ -61,7 +61,7 @@ class UpvoteView extends Component {
_calculateEstimatedAmount = async () => { _calculateEstimatedAmount = async () => {
const { currentAccount, globalProps } = this.props; const { currentAccount, globalProps } = this.props;
if (currentAccount) { if (currentAccount && Object.entries(currentAccount).length !== 0) {
const { sliderValue } = this.state; const { sliderValue } = this.state;
const { const {
fundRecentClaims, fundRewardBalance, base, quote, fundRecentClaims, fundRewardBalance, base, quote,

View File

@ -200,6 +200,10 @@
"no_existing_user": "No existing user", "no_existing_user": "No existing user",
"no_existing_post": "No existing post" "no_existing_post": "No existing post"
}, },
"search": {
"posts": "Posts",
"comments": "Comments"
},
"comment_filter": { "comment_filter": {
"trending": "trending", "trending": "trending",
"reputation": "reputation", "reputation": "reputation",

View File

@ -17,6 +17,7 @@ export default {
STEEM_CONNECT: `SteemConnect${SCREEN_SUFFIX}`, STEEM_CONNECT: `SteemConnect${SCREEN_SUFFIX}`,
VOTERS: `Voters${SCREEN_SUFFIX}`, VOTERS: `Voters${SCREEN_SUFFIX}`,
BOOKMARKS: `Bookmarks${SCREEN_SUFFIX}`, BOOKMARKS: `Bookmarks${SCREEN_SUFFIX}`,
SEARCH_RESULT: `SearchResult${SCREEN_SUFFIX}`,
}, },
DRAWER: { DRAWER: {
MAIN: `Main${DRAWER_SUFFIX}`, MAIN: `Main${DRAWER_SUFFIX}`,

View File

@ -20,6 +20,7 @@ import {
Settings, Settings,
SteemConnect, SteemConnect,
Voters, Voters,
SearchResult,
} from '../screens'; } from '../screens';
// Components // Components
@ -92,6 +93,12 @@ const stackNavigatior = createStackNavigator(
header: () => null, header: () => null,
}, },
}, },
[ROUTES.SCREENS.SEARCH_RESULT]: {
screen: RootComponent()(SearchResult),
navigationOptions: {
header: () => null,
},
},
}, },
{ {
headerMode: 'none', headerMode: 'none',

View File

@ -622,6 +622,15 @@ export const lookupAccounts = async (username) => {
} }
}; };
export const getTrendingTags = async (tag) => {
try {
const users = await client.database.call('get_trending_tags', [tag, 20]);
return users;
} catch (error) {
throw error;
}
};
/** /**
* @method postComment post a comment/reply * @method postComment post a comment/reply
* @param comment comment object { author, permlink, ... } * @param comment comment object { author, permlink, ... }

View File

@ -22,7 +22,7 @@ class HomeScreen extends PureComponent {
render() { render() {
const { const {
currentAccount, intl, isLoggedIn, isLoginDone, currentAccount, intl, isLoggedIn,
} = this.props; } = this.props;
let tag; let tag;

View File

@ -14,6 +14,7 @@ import { Settings } from './settings';
import { Voters } from './voters'; import { Voters } from './voters';
import RootComponent from './root'; import RootComponent from './root';
import SteemConnect from './steem-connect/steemConnect'; import SteemConnect from './steem-connect/steemConnect';
import { SearchResult } from './searchResult';
export { export {
Bookmarks, Bookmarks,
@ -32,4 +33,5 @@ export {
Settings, Settings,
SteemConnect, SteemConnect,
Voters, Voters,
SearchResult,
}; };

View File

@ -0,0 +1,44 @@
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
// Component
import SearchResultScreen from '../screen/searchResultScreen';
/*
* Props Name Description Value
*@props --> props name here description here Value Type Here
*
*/
class SearchResultContainer extends PureComponent {
constructor(props) {
super(props);
this.state = {};
}
_navigationGoBack = () => {
const { navigation } = this.props;
navigation.goBack();
};
render() {
const { currentAccount, navigation } = this.props;
const tag = navigation.getParam('tag', 'esteem');
return (
<SearchResultScreen
currentAccount={currentAccount}
tag={tag}
navigationGoBack={this._navigationGoBack}
/>
);
}
}
const mapStateToProps = state => ({
currentAccount: state.account.currentAccount,
});
export default connect(mapStateToProps)(SearchResultContainer);

View File

@ -0,0 +1,5 @@
import SearchResultScreen from './screen/searchResultScreen';
import SearchResult from './container/searchResultContainer';
export { SearchResultScreen, SearchResult };
export default SearchResult;

View File

@ -0,0 +1,70 @@
import React, { PureComponent, Fragment } from 'react';
import { View } from 'react-native';
import ScrollableTabView from '@esteemapp/react-native-scrollable-tab-view';
import { injectIntl } from 'react-intl';
// Components
import { TabBar } from '../../../components/tabBar';
import { Posts } from '../../../components/posts';
import SearchInput from '../../../components/searchInput';
// Styles
import styles from './searchResultStyles';
import globalStyles from '../../../globalStyles';
class SearchResultScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { intl, tag, navigationGoBack } = this.props;
return (
<View style={styles.container}>
<SearchInput
onChangeText={() => {}}
handleOnModalClose={navigationGoBack}
placeholder={tag}
editable={false}
/>
<ScrollableTabView
style={globalStyles.tabView}
renderTabBar={() => (
<TabBar
style={styles.tabbar}
tabUnderlineDefaultWidth={80}
tabUnderlineScaleX={2}
tabBarPosition="overlayTop"
/>
)}
>
<View
tabLabel={intl.formatMessage({
id: 'search.posts',
})}
style={styles.tabbarItem}
>
<Posts pageType="posts" tag={tag} />
</View>
{/* <View
tabLabel={intl.formatMessage({
id: 'search.comments',
})}
style={styles.tabbarItem}
>
<Posts
filterOptions={POPULAR_FILTERS}
getFor={POPULAR_FILTERS[0].toLowerCase()}
selectedOptionIndex={0}
pageType="posts"
/>
</Fragment> */}
</ScrollableTabView>
</View>
);
}
}
export default injectIntl(SearchResultScreen);

View File

@ -0,0 +1,31 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
buttonContainer: {
width: '50%',
alignItems: 'center',
},
tabbar: {
alignSelf: 'center',
height: 40,
backgroundColor: '$primaryBackgroundColor',
shadowOpacity: 0.2,
shadowColor: '$shadowColor',
shadowOffset: { height: 4 },
zIndex: 99,
borderBottomColor: '$shadowColor',
borderBottomWidth: 0.1,
},
tabbarItem: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
minWidth: '$deviceWidth',
},
tabs: {
flex: 1,
},
});

9124
yarn.lock

File diff suppressed because it is too large Load Diff