Merge pull request #116 from esteemapp/search-bar

Search feature
This commit is contained in:
Feruz M 2018-11-26 09:50:18 +02:00 committed by GitHub
commit b5d75d16b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 324 additions and 26 deletions

View File

@ -1,3 +1,3 @@
BACKEND_URL = https://myapi.com
SEARCH_API_URL = https://search.com
SEARCH_API_TOKEN = abcde
BACKEND_URL=https://myapi.com
SEARCH_API_URL=https://search.com
SEARCH_API_TOKEN=abcde

View File

@ -1,4 +1,5 @@
apply plugin: "com.android.application"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
import com.android.build.OutputFile

View File

@ -199,13 +199,6 @@
remoteGlobalIDString = EB2648DF1C7BE17A00B8F155;
remoteInfo = ReactNativeConfig;
};
0CA1AD3C21A818CF001EAFBD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C1AC2A99CB0143E580183824 /* RNViewOverflow.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNViewOverflow;
};
139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
@ -402,7 +395,7 @@
remoteGlobalIDString = 3D3CD9181DE5FBD800167DC4;
remoteInfo = "jschelpers-tvOS";
};
47241C6321A85341001094B9 /* PBXContainerItemProxy */ = {
58C9351B21AB3BA000D2EB1F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C1AC2A99CB0143E580183824 /* RNViewOverflow.xcodeproj */;
proxyType = 2;
@ -757,7 +750,7 @@
0CE2BA1E21A7301B0052071F /* Products */ = {
isa = PBXGroup;
children = (
0CE2BA2221A7301B0052071F /* libRNViewOverflow.a */,
58C9351C21AB3BA000D2EB1F /* libRNViewOverflow.a */,
);
name = Products;
sourceTree = "<group>";
@ -1084,7 +1077,7 @@
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "eSteem" */;
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "esteem" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
@ -1309,13 +1302,6 @@
remoteRef = 0C09B6DB21A3393A00536C9D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
0CE2BA2221A7301B0052071F /* libRNViewOverflow.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNViewOverflow.a;
remoteRef = 0CE2BA2121A7301B0052071F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -1505,11 +1491,11 @@
remoteRef = 3DAD3EAE1DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
47241C6421A85341001094B9 /* libRNViewOverflow.a */ = {
58C9351C21AB3BA000D2EB1F /* libRNViewOverflow.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNViewOverflow.a;
remoteRef = 47241C6321A85341001094B9 /* PBXContainerItemProxy */;
remoteRef = 58C9351B21AB3BA000D2EB1F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */ = {
@ -2195,6 +2181,9 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_OTHER_PREPROCESSOR_FLAGS = "-traditional";
INFOPLIST_PREFIX_HEADER = "${BUILD_DIR}/GeneratedInfoPlistDotEnv.h";
INFOPLIST_PREPROCESS = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@ -2241,6 +2230,9 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_OTHER_PREPROCESSOR_FLAGS = "-traditional";
INFOPLIST_PREFIX_HEADER = "${BUILD_DIR}/GeneratedInfoPlistDotEnv.h";
INFOPLIST_PREPROCESS = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
@ -2287,7 +2279,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "eSteem" */ = {
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "esteem" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,

View File

@ -4,6 +4,7 @@ import {
} from 'react-native';
import FastImage from 'react-native-fast-image';
import LinearGradient from 'react-native-linear-gradient';
import { SearchModal } from '../../searchModal';
// Utils
import { getReputation } from '../../../utils/user';
@ -25,7 +26,9 @@ class HeaderView extends Component {
constructor(props) {
super(props);
this.state = {};
this.state = {
isSearchModalOpen: false,
};
}
// Component Life Cycles
@ -52,6 +55,10 @@ class HeaderView extends Component {
return DEFAULT_IMAGE;
};
_handleOnCloseSearch = () => {
this.setState({ isSearchModalOpen: false });
}
render() {
const {
handleOpenDrawer,
@ -60,12 +67,14 @@ class HeaderView extends Component {
isReverse,
currentAccount,
} = this.props;
const { isSearchModalOpen } = this.state;
const _name = this._getNameOfUser();
const _reputation = getReputation(currentAccount.reputation);
return (
<SafeAreaView style={[styles.container, isReverse && styles.containerReverse]}>
<StatusBar hidden={hideStatusBar} translucent />
<SearchModal isOpen={isSearchModalOpen} handleOnClose={this._handleOnCloseSearch} />
<TouchableOpacity
style={styles.avatarWrapper}
onPress={() => !isReverse && handleOpenDrawer()}
@ -112,6 +121,24 @@ class HeaderView extends Component {
/>
</View>
)}
{!isReverse && (
<View
style={{
alignItems: 'flex-end',
justifyContent: 'center',
flex: 1,
marginRight: 16,
}}
>
<IconButton
style={styles.searchButton}
iconStyle={styles.backIcon}
name="md-search"
onPress={() => this.setState({ isSearchModalOpen: true })}
/>
</View>
)}
</SafeAreaView>
);
}

View File

@ -32,7 +32,7 @@ export default class Modal extends Component {
render() {
const {
isFullScreen, isOpen, children, isRadius,
isFullScreen, isOpen, children, isRadius, isTransparent,
} = this.props;
return (
<ModalBox
@ -40,6 +40,7 @@ export default class Modal extends Component {
isRadius && styles.borderTopRadius,
isFullScreen ? styles.fullModal : styles.centerModal,
]}
transparent={isTransparent}
animationType="fade"
visible={isOpen}
onRequestClose={() => this._handleOnClose(this)}

View File

@ -0,0 +1,96 @@
import React, { Component } from 'react';
import { withNavigation } from 'react-navigation';
// Services and Actions
import { search } from '../../../providers/esteem/esteem';
import { lookupAccounts } from '../../../providers/steem/dsteem';
// Middleware
// Constants
import { default as ROUTES } from '../../../constants/routeNames';
// Utilities
// Component
import { SearchModalView } from '..';
/*
* Props Name Description Value
*@props --> props name here description here Value Type Here
*
*/
class SearchModalContainer extends Component {
constructor(props) {
super(props);
this.state = {
searchResults: {},
};
}
// Component Life Cycle Functions
// Component Functions
_handleCloseButton = () => {
const { navigation } = this.props;
navigation.goBack();
};
_handleOnChangeSearchInput = (text) => {
if (text && text !== '@') {
if (text[0] === '@') {
lookupAccounts(text.substr(1)).then((res) => {
const users = res.map(item => ({ author: item }));
this.setState({ searchResults: { type: 'user', data: users } });
});
} else {
search({ q: text }).then((res) => {
res.results = res.results.filter(item => item.title !== '');
this.setState({ searchResults: { type: 'content', data: res.results } });
});
}
}
};
_handleOnPressListItem = (type, item) => {
const { navigation, handleOnClose } = this.props;
handleOnClose();
this.setState({ searchResults: {} });
if (type === 'user') {
navigation.navigate({
routeName: ROUTES.SCREENS.PROFILE,
params: {
username: item.author,
},
key: item.author,
});
} else if (type === 'content') {
navigation.navigate({
routeName: ROUTES.SCREENS.POST,
params: {
author: item.author,
permlink: item.permlink,
},
key: item.permlink,
});
}
};
render() {
const { searchResults } = this.state;
const { handleOnClose, isOpen } = this.props;
return (
<SearchModalView
searchResults={searchResults}
handleCloseButton={this._handleCloseButton}
handleOnChangeSearchInput={this._handleOnChangeSearchInput}
handleOnPressListItem={this._handleOnPressListItem}
isOpen={isOpen}
handleOnClose={handleOnClose}
/>
);
}
}
export default withNavigation(SearchModalContainer);

View File

@ -0,0 +1,5 @@
import SearchModalView from './view/searchModalView';
import SearchModal from './container/searchModalContainer';
export { SearchModalView, SearchModal };
export default SearchModal;

View File

@ -0,0 +1,64 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'rgba(0,0,0,0.8)',
},
inputWrapper: {
backgroundColor: '$primaryLightBackground',
flexDirection: 'row',
height: 44,
margin: 16,
borderRadius: 8,
marginTop: 20,
padding: 5,
justifyContent: 'center',
},
icon: {
alignSelf: 'center',
color: '$primaryDarkGray',
marginLeft: 16,
},
input: {
color: '$primaryDarkGray',
fontSize: 14,
flexGrow: 1,
paddingHorizontal: 10,
},
closeIconButton: {
backgroundColor: '$iconColor',
width: 20,
height: 20,
borderRadius: 20 / 2,
justifyContent: 'center',
alignSelf: 'center',
marginRight: 16,
},
closeIcon: {
color: '$white',
fontSize: 16,
},
body: {
marginTop: 16,
marginRight: 24,
},
searhItems: {
marginHorizontal: 30,
marginVertical: 10,
flexDirection: 'row',
alignItems: 'center',
},
searchItemImage: {
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 1,
borderColor: '$primaryGray',
},
searchItemText: {
color: '$white',
marginLeft: 10,
},
});

View File

@ -0,0 +1,99 @@
import React, { Component, Fragment } from 'react';
import {
View, Text, TextInput, FlatList, TouchableHighlight, Image,
} from 'react-native';
// Constants
// Components
import { Icon } from '../../icon';
import { IconButton } from '../../iconButton';
import { Modal } from '../..';
// Styles
// eslint-disable-next-line
import styles from './searchModalStyles';
class SearchModalView extends Component {
/* Props
* ------------------------------------------------
* @prop { type } name - Description....
*/
constructor(props) {
super(props);
this.state = {};
}
// Component Life Cycles
// Component Functions
render() {
const {
isOpen,
handleOnClose,
searchResults,
handleOnPressListItem,
handleOnChangeSearchInput,
} = this.props;
return (
<Fragment>
<Modal isOpen={isOpen} isFullScreen swipeToClose backButtonClose isTransparent>
<View style={styles.container}>
<View style={styles.inputWrapper}>
<Icon style={styles.icon} iconType="FontAwesome" name="search" size={20} />
<TextInput
style={styles.input}
onChangeText={text => handleOnChangeSearchInput(text)}
placeholder="Search..."
placeholderTextColor="#c1c5c7"
/>
<IconButton
iconStyle={styles.closeIcon}
iconType="FontAwesome"
style={styles.closeIconButton}
name="close"
onPress={() => handleOnClose()}
/>
</View>
<View style={styles.body}>
<FlatList
data={searchResults.data}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => (
<TouchableHighlight
onPress={() => handleOnPressListItem(searchResults.type, item)}
>
<View style={styles.searhItems}>
<Image
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>
</TouchableHighlight>
)}
keyExtractor={(post, index) => index.toString()}
removeClippedSubviews
onEndThreshold={0}
initialNumToRender={20}
/>
</View>
</View>
</Modal>
</Fragment>
);
}
}
export default SearchModalView;

View File

@ -4,7 +4,7 @@ import Config from 'react-native-config';
const search = axios.create({
baseURL: Config.SEARCH_API_URL,
headers: {
'Authorization': Config.SEARCH_API_TOKEN,
Authorization: Config.SEARCH_API_TOKEN,
'Content-Type': 'application/json',
},
});

View File

@ -1,4 +1,5 @@
import api from '../../config/api';
import searchApi from '../../config/search';
export const getDrafts = data => new Promise((resolve, reject) => {
api
@ -93,3 +94,14 @@ export const getUnreadActivityCount = data => new Promise((resolve, reject) => {
reject(error);
});
});
export const search = data => new Promise((resolve, reject) => {
searchApi
.post('/search', data)
.then((res) => {
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});

View File

@ -34,6 +34,7 @@ export default class HomeScreen extends PureComponent {
return (
<Fragment>
<Header />
<View style={styles.container} key="overlay">
<ScrollableTabView
style={styles.tabView}