mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-12-02 11:15:35 +03:00
Merge pull request #627 from esteemapp/feature/tag-searching
Feature/tag searching
This commit is contained in:
commit
d515c4c10a
@ -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) {
|
||||
|
3
src/components/searchInput/index.js
Normal file
3
src/components/searchInput/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import SearchInput from './view/searchInputView';
|
||||
|
||||
export default SearchInput;
|
38
src/components/searchInput/view/searchInputStyles.js
Normal file
38
src/components/searchInput/view/searchInputStyles.js
Normal 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,
|
||||
},
|
||||
});
|
43
src/components/searchInput/view/searchInputView.js
Normal file
43
src/components/searchInput/view/searchInputView.js
Normal 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;
|
@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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}`,
|
||||
|
@ -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',
|
||||
|
@ -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, ... }
|
||||
|
@ -22,7 +22,7 @@ class HomeScreen extends PureComponent {
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentAccount, intl, isLoggedIn, isLoginDone,
|
||||
currentAccount, intl, isLoggedIn,
|
||||
} = this.props;
|
||||
|
||||
let tag;
|
||||
|
@ -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,
|
||||
};
|
||||
|
44
src/screens/searchResult/container/searchResultContainer.js
Normal file
44
src/screens/searchResult/container/searchResultContainer.js
Normal 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);
|
5
src/screens/searchResult/index.js
Normal file
5
src/screens/searchResult/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import SearchResultScreen from './screen/searchResultScreen';
|
||||
import SearchResult from './container/searchResultContainer';
|
||||
|
||||
export { SearchResultScreen, SearchResult };
|
||||
export default SearchResult;
|
70
src/screens/searchResult/screen/searchResultScreen.js
Normal file
70
src/screens/searchResult/screen/searchResultScreen.js
Normal 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);
|
31
src/screens/searchResult/screen/searchResultStyles.js
Normal file
31
src/screens/searchResult/screen/searchResultStyles.js
Normal 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,
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user