Merge pull request #142 from lbryio/uri-bar-improvements
Improvements to the UriBar implementation and some navigation tweaks.
This commit is contained in:
commit
51065d1b57
20 changed files with 206 additions and 43 deletions
|
@ -38,7 +38,6 @@ const discoverStack = StackNavigator({
|
|||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Discover',
|
||||
headerLeft: <Feather name="menu" size={24} style={discoverStyle.drawerHamburger} onPress={() => navigation.navigate('DrawerOpen')} />,
|
||||
headerRight: <Feather name="search" size={24} style={discoverStyle.rightHeaderIcon} onPress={() => navigation.navigate('Search')} />
|
||||
})
|
||||
},
|
||||
File: {
|
||||
|
@ -51,9 +50,7 @@ const discoverStack = StackNavigator({
|
|||
Search: {
|
||||
screen: SearchPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
drawerLockMode: 'locked-closed',
|
||||
headerTitle: <SearchInput style={searchStyle.searchInput} />,
|
||||
headerRight: <SearchRightHeaderIcon style={discoverStyle.rightHeaderIcon} size={24} navigation={navigation} />
|
||||
drawerLockMode: 'locked-closed'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
|
@ -66,7 +63,6 @@ const walletStack = StackNavigator({
|
|||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Wallet',
|
||||
headerLeft: <Feather name="menu" size={24} style={discoverStyle.drawerHamburger} onPress={() => navigation.navigate('DrawerOpen')} />,
|
||||
headerRight: <Feather name="search" size={24} style={discoverStyle.rightHeaderIcon} onPress={() => navigation.navigate('Search')} />
|
||||
})
|
||||
},
|
||||
TransactionHistory: {
|
||||
|
@ -185,6 +181,7 @@ class AppWithNavigationState extends React.Component {
|
|||
if (evt.url) {
|
||||
const navigateAction = NavigationActions.navigate({
|
||||
routeName: 'File',
|
||||
key: 'filePage',
|
||||
params: { uri: evt.url }
|
||||
});
|
||||
dispatch(navigateAction);
|
||||
|
|
|
@ -61,7 +61,7 @@ class FileItem extends React.PureComponent {
|
|||
if (NativeModules.Mixpanel) {
|
||||
NativeModules.Mixpanel.track('Discover Tap', { Uri: uri });
|
||||
}
|
||||
navigation.navigate('File', { uri: uri });
|
||||
navigation.navigate({ routeName: 'File', key: 'filePage', params: { uri } });
|
||||
}
|
||||
}>
|
||||
<FileItemMedia title={title} thumbnail={thumbnail} blurRadius={obscureNsfw ? 15 : 0} resizeMode="cover" />
|
||||
|
|
|
@ -23,7 +23,11 @@ class TransactionListItem extends React.PureComponent {
|
|||
{name && claimId && (
|
||||
<Link
|
||||
style={transactionListStyle.link}
|
||||
onPress={() => navigation && navigation.navigate('File', { uri: buildURI({ claimName: name, claimId }) })}
|
||||
onPress={() => navigation && navigation.navigate({
|
||||
routeName: 'File',
|
||||
key: 'filePage',
|
||||
params: { uri: buildURI({ claimName: name, claimId }) }})
|
||||
}
|
||||
text={name} />
|
||||
)}
|
||||
</View>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUpdateSearchQuery, selectSearchState as selectSearch } from 'lbry-redux';
|
||||
import UriBar from './view';
|
||||
|
||||
const select = state => ({
|
||||
const select = state => {
|
||||
const { ...searchState } = selectSearch(state);
|
||||
|
||||
});
|
||||
return {
|
||||
...searchState
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
||||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UriBar);
|
||||
|
|
38
app/src/component/uriBar/internal/uri-bar-item.js
Normal file
38
app/src/component/uriBar/internal/uri-bar-item.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { SEARCH_TYPES, normalizeURI } from 'lbry-redux';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import Feather from 'react-native-vector-icons/Feather';
|
||||
import uriBarStyle from '../../../styles/uriBar';
|
||||
|
||||
class UriBarItem extends React.PureComponent {
|
||||
render() {
|
||||
const { item, onPress } = this.props;
|
||||
const { type, value } = item;
|
||||
|
||||
let icon;
|
||||
switch (type) {
|
||||
case SEARCH_TYPES.CHANNEL:
|
||||
icon = <Feather name="at-sign" size={18} />
|
||||
break;
|
||||
|
||||
case SEARCH_TYPES.SEARCH:
|
||||
icon = <Feather name="search" size={18} />
|
||||
break;
|
||||
|
||||
case SEARCH_TYPES.FILE:
|
||||
default:
|
||||
icon = <Feather name="compass" size={18} />
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={uriBarStyle.item} onPress={onPress}>
|
||||
{icon}
|
||||
<Text style={uriBarStyle.itemText} numberOfLines={1}>{value}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UriBarItem;
|
|
@ -1,41 +1,96 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import { TextInput, View } from 'react-native';
|
||||
import { SEARCH_TYPES, isNameValid, normalizeURI } from 'lbry-redux';
|
||||
import { FlatList, Keyboard, TextInput, View } from 'react-native';
|
||||
import UriBarItem from './internal/uri-bar-item';
|
||||
import uriBarStyle from '../../styles/uriBar';
|
||||
|
||||
class UriBar extends React.PureComponent {
|
||||
static INPUT_TIMEOUT = 500;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
uri: null,
|
||||
currentValue: null
|
||||
changeTextTimeout: null,
|
||||
currentValue: null,
|
||||
inputText: null,
|
||||
focused: false
|
||||
};
|
||||
}
|
||||
|
||||
handleChangeText = text => {
|
||||
const newValue = text ? text : '';
|
||||
clearTimeout(this.state.changeTextTimeout);
|
||||
const { updateSearchQuery } = this.props;
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
updateSearchQuery(text);
|
||||
}, UriBar.INPUT_TIMEOUT);
|
||||
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
|
||||
}
|
||||
|
||||
handleItemPress = (item) => {
|
||||
const { navigation, updateSearchQuery } = this.props;
|
||||
const { type, value } = item;
|
||||
|
||||
Keyboard.dismiss();
|
||||
|
||||
if (SEARCH_TYPES.SEARCH === type) {
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
|
||||
} else {
|
||||
navigation.navigate({ routeName: 'File', key: 'filePage', params: { uri: normalizeURI(value) }});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, navigation } = this.props;
|
||||
if (!this.state.currentValue) {
|
||||
const { navigation, suggestions, updateSearchQuery, value } = this.props;
|
||||
if (this.state.currentValue === null) {
|
||||
this.setState({ currentValue: value });
|
||||
}
|
||||
|
||||
// TODO: Search and URI suggestions overlay
|
||||
let style = [uriBarStyle.overlay];
|
||||
if (this.state.focused) {
|
||||
style.push(uriBarStyle.inFocus);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={uriBarStyle.uriContainer}>
|
||||
<TextInput style={uriBarStyle.uriText}
|
||||
placeholder={'Enter a LBRY URI or some text'}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
value={this.state.currentValue}
|
||||
returnKeyType={'go'}
|
||||
onChangeText={(text) => this.setState({uri: text, currentValue: text})}
|
||||
onSubmitEditing={() => {
|
||||
if (this.state.uri) {
|
||||
let uri = this.state.uri;
|
||||
uri = uri.replace(/ /g, '-');
|
||||
navigation.navigate('File', { uri: normalizeURI(uri) });
|
||||
}
|
||||
}}/>
|
||||
<View style={style}>
|
||||
{this.state.focused && (
|
||||
<View style={uriBarStyle.suggestions}>
|
||||
<FlatList style={uriBarStyle.suggestionList}
|
||||
data={suggestions}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={(item, value) => item.value}
|
||||
renderItem={({item}) => <UriBarItem item={item}
|
||||
navigation={navigation}
|
||||
onPress={() => this.handleItemPress(item)} />} />
|
||||
</View>)}
|
||||
<View style={uriBarStyle.uriContainer}>
|
||||
<TextInput style={uriBarStyle.uriText}
|
||||
selectTextOnFocus={true}
|
||||
placeholder={'Search for videos, music, games and more'}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
clearButtonMode={'while-editing'}
|
||||
value={this.state.currentValue}
|
||||
returnKeyType={'go'}
|
||||
inlineImageLeft={'baseline_search_black_24'}
|
||||
inlineImagePadding={16}
|
||||
onFocus={() => this.setState({ focused: true })}
|
||||
onBlur={() => this.setState({ focused: false })}
|
||||
onChangeText={this.handleChangeText}
|
||||
onSubmitEditing={() => {
|
||||
if (this.state.inputText) {
|
||||
let inputText = this.state.inputText;
|
||||
if (isNameValid(inputText)) {
|
||||
navigation.navigate({ routeName: 'File', key: 'filePage', params: { uri: normalizeURI(inputText) }});
|
||||
} else {
|
||||
// Open the search page with the query populated
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText }});
|
||||
}
|
||||
}
|
||||
}}/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ class FilePage extends React.PureComponent {
|
|||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
|
||||
</View>}
|
||||
{ claim === null && !isResolvingUri &&
|
||||
{claim === null && !isResolvingUri &&
|
||||
<View style={filePageStyle.container}>
|
||||
<Text style={filePageStyle.emptyClaimText}>There's nothing at this location.</Text>
|
||||
</View>
|
||||
|
@ -220,7 +220,7 @@ class FilePage extends React.PureComponent {
|
|||
renderIndicator={() => null} />}
|
||||
|
||||
{!this.state.showWebView && (
|
||||
<View style={filePageStyle.pageContainer}>
|
||||
<View style={filePageStyle.innerPageContainer}>
|
||||
<View style={filePageStyle.mediaContainer}>
|
||||
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia))) &&
|
||||
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
|
||||
|
|
|
@ -8,14 +8,29 @@ import {
|
|||
View,
|
||||
ScrollView
|
||||
} from 'react-native';
|
||||
import SearchResultItem from '../../component/searchResultItem';
|
||||
import Colors from '../../styles/colors';
|
||||
import PageHeader from '../../component/pageHeader';
|
||||
import SearchResultItem from '../../component/searchResultItem';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import searchStyle from '../../styles/search';
|
||||
|
||||
class SearchPage extends React.PureComponent {
|
||||
static navigationOptions = {
|
||||
title: 'Search Results'
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation, search } = this.props;
|
||||
const { searchQuery } = navigation.state.params;
|
||||
if (searchQuery && searchQuery.trim().length > 0) {
|
||||
search(searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isSearching, navigation, uris } = this.props;
|
||||
|
||||
const { isSearching, navigation, uris, query } = this.props;
|
||||
const { searchQuery } = navigation.state.params;
|
||||
|
||||
return (
|
||||
<View style={searchStyle.container}>
|
||||
{!isSearching && (!uris || uris.length === 0) &&
|
||||
|
@ -26,10 +41,15 @@ class SearchPage extends React.PureComponent {
|
|||
uri={uri}
|
||||
style={searchStyle.resultItem}
|
||||
navigation={navigation}
|
||||
onPress={() => {navigation.navigate('File', { uri: uri }); }}/>)
|
||||
onPress={() => navigation.navigate({
|
||||
routeName: 'File',
|
||||
key: 'filePage',
|
||||
params: { uri }})
|
||||
}/>)
|
||||
) : null }
|
||||
</ScrollView>
|
||||
{isSearching && <ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> }
|
||||
<UriBar value={searchQuery} navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ class SplashScreen extends React.PureComponent {
|
|||
navigation.dispatch(resetAction);
|
||||
|
||||
if (this.state.launchUrl) {
|
||||
navigation.navigate('File', { uri: this.state.launchUrl });
|
||||
navigation.navigate({ routeName: 'File', key: 'filePage', params: { uri: this.state.launchUrl } });
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
|
|
@ -11,7 +11,8 @@ const channelPageStyle = StyleSheet.create({
|
|||
},
|
||||
fileList: {
|
||||
paddingTop: 30,
|
||||
flex: 1
|
||||
flex: 1,
|
||||
marginBottom: 60
|
||||
},
|
||||
title: {
|
||||
color: Colors.LbryGreen,
|
||||
|
|
|
@ -5,7 +5,8 @@ const discoverStyle = StyleSheet.create({
|
|||
flex: 1
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
marginBottom: 60
|
||||
},
|
||||
busyContainer: {
|
||||
flex: 1,
|
||||
|
|
|
@ -12,6 +12,10 @@ const filePageStyle = StyleSheet.create({
|
|||
pageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
innerPageContainer: {
|
||||
flex: 1,
|
||||
marginBottom: 60
|
||||
},
|
||||
mediaContainer: {
|
||||
alignItems: 'center',
|
||||
width: screenWidth,
|
||||
|
@ -150,7 +154,7 @@ const filePageStyle = StyleSheet.create({
|
|||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
bottom: 60,
|
||||
zIndex: 100
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,7 +10,8 @@ const searchStyle = StyleSheet.create({
|
|||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 16
|
||||
padding: 16,
|
||||
marginBottom: 60
|
||||
},
|
||||
scrollPadding: {
|
||||
paddingBottom: 16
|
||||
|
|
|
@ -6,6 +6,7 @@ const uriBarStyle = StyleSheet.create({
|
|||
backgroundColor: Colors.White,
|
||||
padding: 8,
|
||||
alignSelf: 'flex-end',
|
||||
height: 60,
|
||||
width: '100%',
|
||||
shadowColor: Colors.Black,
|
||||
shadowOpacity: 0.1,
|
||||
|
@ -24,6 +25,32 @@ const uriBarStyle = StyleSheet.create({
|
|||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 16,
|
||||
width: '100%'
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
backgroundColor: 'transparent',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
zIndex: 200,
|
||||
elevation: 16
|
||||
},
|
||||
inFocus: {
|
||||
height: '100%'
|
||||
},
|
||||
suggestions: {
|
||||
backgroundColor: 'white',
|
||||
flex: 1
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
padding: 12
|
||||
},
|
||||
itemText: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 16,
|
||||
marginLeft: 12,
|
||||
marginRight: 12
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 386 B |
Binary file not shown.
After Width: | Height: | Size: 236 B |
Binary file not shown.
After Width: | Height: | Size: 420 B |
Binary file not shown.
After Width: | Height: | Size: 636 B |
Binary file not shown.
After Width: | Height: | Size: 827 B |
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
Loading…
Reference in a new issue