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 = [];
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 = {
tag,
limit: 3,
};
} else {
// TODO: implement filtering of reblogs on `blog` and `feed` posts
if (filter == 'reblogs') {
options = {
tag,
limit: 3,
};
} else {
options = {
limit: 3,
};
}
options = {
limit: 3,
};
}
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
import { search } from '../../../providers/esteem/esteem';
import { lookupAccounts } from '../../../providers/steem/dsteem';
import { lookupAccounts, getTrendingTags } from '../../../providers/steem/dsteem';
// Middleware
@ -39,15 +39,31 @@ class SearchModalContainer extends PureComponent {
};
_handleOnChangeSearchInput = (text) => {
if (text && text !== '@') {
if (text && text !== '@' && text !== '#') {
if (text[0] === '@') {
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 } });
});
} 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 {
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 } });
});
}
@ -56,24 +72,45 @@ class SearchModalContainer extends PureComponent {
_handleOnPressListItem = (type, item) => {
const { navigation, handleOnClose } = this.props;
let routeName = null;
let params = null;
let key = null;
handleOnClose();
this.setState({ searchResults: {} });
if (type === 'user') {
navigation.navigate({
routeName: ROUTES.SCREENS.PROFILE,
params: {
switch (type) {
case 'user':
routeName = ROUTES.SCREENS.PROFILE;
params = {
username: item.author,
},
key: item.author,
});
} else if (type === 'content') {
navigation.navigate({
routeName: ROUTES.SCREENS.POST,
params: {
};
key = item.author;
break;
case 'content':
routeName = ROUTES.SCREENS.POST;
params = {
author: item.author,
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',
alignItems: 'center',
},
searchItemImageWrapper: {
flex: 1,
},
searchItemTextWrapper: {
flex: 7,
},
searchItemImage: {
width: 40,
height: 40,
@ -61,7 +67,7 @@ export default EStyleSheet.create({
borderColor: '$primaryGray',
},
searchItemText: {
color: '$primaryDarkGray',
color: '$white',
marginLeft: 10,
},
});

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import {
Settings,
SteemConnect,
Voters,
SearchResult,
} from '../screens';
// Components
@ -92,6 +93,12 @@ const stackNavigatior = createStackNavigator(
header: () => null,
},
},
[ROUTES.SCREENS.SEARCH_RESULT]: {
screen: RootComponent()(SearchResult),
navigationOptions: {
header: () => null,
},
},
},
{
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
* @param comment comment object { author, permlink, ... }

View File

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

View File

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