Uri bar suggestions #59
9 changed files with 110 additions and 48 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -5640,8 +5640,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lbry-redux": {
|
"lbry-redux": {
|
||||||
"version": "github:lbryio/lbry-redux#7ec72a737bcd336f000c5f5085891643110298c3",
|
"version": "github:lbryio/lbry-redux#4c4f926ee2cd77d750df0a95fad669257aa31ae7",
|
||||||
"from": "github:lbryio/lbry-redux#7ec72a737bcd336f000c5f5085891643110298c3",
|
"from": "github:lbryio/lbry-redux#4c4f926ee2cd77d750df0a95fad669257aa31ae7",
|
||||||
"requires": {
|
"requires": {
|
||||||
"proxy-polyfill": "0.1.6",
|
"proxy-polyfill": "0.1.6",
|
||||||
"reselect": "^3.0.0",
|
"reselect": "^3.0.0",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"base-64": "^0.1.0",
|
"base-64": "^0.1.0",
|
||||||
"@expo/vector-icons": "^8.1.0",
|
"@expo/vector-icons": "^8.1.0",
|
||||||
"gfycat-style-urls": "^1.0.3",
|
"gfycat-style-urls": "^1.0.3",
|
||||||
"lbry-redux": "lbryio/lbry-redux#7ec72a737bcd336f000c5f5085891643110298c3",
|
"lbry-redux": "lbryio/lbry-redux#4c4f926ee2cd77d750df0a95fad669257aa31ae7",
|
||||||
"lbryinc": "lbryio/lbryinc#c55a2c98ab92c72149c824ee5906aed4404fd89b",
|
"lbryinc": "lbryio/lbryinc#c55a2c98ab92c72149c824ee5906aed4404fd89b",
|
||||||
"lodash": ">=4.17.11",
|
"lodash": ">=4.17.11",
|
||||||
"merge": ">=1.2.1",
|
"merge": ">=1.2.1",
|
||||||
|
|
|
@ -4,8 +4,10 @@ import {
|
||||||
selectSearchState as selectSearch,
|
selectSearchState as selectSearch,
|
||||||
selectSearchValue,
|
selectSearchValue,
|
||||||
selectSearchSuggestions,
|
selectSearchSuggestions,
|
||||||
|
SETTINGS,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import UriBar from './view';
|
import UriBar from './view';
|
||||||
|
|
||||||
const select = state => {
|
const select = state => {
|
||||||
|
@ -16,6 +18,7 @@ const select = state => {
|
||||||
query: selectSearchValue(state),
|
query: selectSearchValue(state),
|
||||||
currentRoute: selectCurrentRoute(state),
|
currentRoute: selectCurrentRoute(state),
|
||||||
suggestions: selectSearchSuggestions(state),
|
suggestions: selectSearchSuggestions(state),
|
||||||
|
showUriBarSuggestions: makeSelectClientSetting(SETTINGS.SHOW_URI_BAR_SUGGESTIONS)(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ class UriBarItem extends React.PureComponent {
|
||||||
icon = <Icon name="search" size={18} />;
|
icon = <Icon name="search" size={18} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SEARCH_TYPES.TAG:
|
||||||
|
icon = <Icon name="hashtag" size={18} />;
|
||||||
|
break;
|
||||||
|
|
||||||
case SEARCH_TYPES.FILE:
|
case SEARCH_TYPES.FILE:
|
||||||
default:
|
default:
|
||||||
icon = <Icon name="file" size={18} />;
|
icon = <Icon name="file" size={18} />;
|
||||||
|
@ -37,6 +41,7 @@ class UriBarItem extends React.PureComponent {
|
||||||
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
|
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
|
||||||
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
|
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
|
||||||
{type === SEARCH_TYPES.FILE && `View content at ${value}`}
|
{type === SEARCH_TYPES.FILE && `View content at ${value}`}
|
||||||
|
{type === SEARCH_TYPES.TAG && `Explore the '${value}' tag`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
import { FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
import { Dimensions, FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
import { navigateToUri, transformUrl } from 'utils/helper';
|
import { navigateToUri, transformUrl } from 'utils/helper';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import UriBarItem from './internal/uri-bar-item';
|
import UriBarItem from './internal/uri-bar-item';
|
||||||
|
@ -14,6 +14,8 @@ class UriBar extends React.PureComponent {
|
||||||
|
|
||||||
textInput = null;
|
textInput = null;
|
||||||
|
|
||||||
|
keyboardDidShowListener = null;
|
||||||
|
|
||||||
keyboardDidHideListener = null;
|
keyboardDidHideListener = null;
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -21,17 +23,19 @@ class UriBar extends React.PureComponent {
|
||||||
currentValue: null,
|
currentValue: null,
|
||||||
inputText: null,
|
inputText: null,
|
||||||
focused: false,
|
focused: false,
|
||||||
|
keyboardHeight: 0,
|
||||||
// TODO: Add a setting to enable / disable direct search?
|
|
||||||
directSearch: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
|
||||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||||
this.setSelection();
|
this.setSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
if (this.keyboardDidShowListener) {
|
||||||
|
this.keyboardDidShowListener.remove();
|
||||||
|
}
|
||||||
if (this.keyboardDidHideListener) {
|
if (this.keyboardDidHideListener) {
|
||||||
this.keyboardDidHideListener.remove();
|
this.keyboardDidHideListener.remove();
|
||||||
}
|
}
|
||||||
|
@ -49,25 +53,28 @@ class UriBar extends React.PureComponent {
|
||||||
handleChangeText = text => {
|
handleChangeText = text => {
|
||||||
const newValue = text || '';
|
const newValue = text || '';
|
||||||
clearTimeout(this.state.changeTextTimeout);
|
clearTimeout(this.state.changeTextTimeout);
|
||||||
const { updateSearchQuery, onSearchSubmitted, navigation } = this.props;
|
const { updateSearchQuery, onSearchSubmitted, showUriBarSuggestions, navigation } = this.props;
|
||||||
|
|
||||||
let timeout = setTimeout(() => {
|
updateSearchQuery(text);
|
||||||
if (text.trim().length === 0) {
|
|
||||||
// don't do anything if the text is empty
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSearchQuery(text);
|
let timeout = -1;
|
||||||
|
if (!showUriBarSuggestions) {
|
||||||
if (!text.startsWith('lbry://')) {
|
timeout = setTimeout(() => {
|
||||||
// not a URI input, so this is a search, perform a direct search
|
if (text.trim().length === 0) {
|
||||||
if (onSearchSubmitted) {
|
// don't do anything if the text is empty
|
||||||
onSearchSubmitted(text);
|
return;
|
||||||
} else {
|
|
||||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, UriBar.INPUT_TIMEOUT);
|
if (!text.startsWith('lbry://')) {
|
||||||
|
// not a URI input, so this is a search, perform a direct search
|
||||||
|
if (onSearchSubmitted) {
|
||||||
|
onSearchSubmitted(text);
|
||||||
|
} else {
|
||||||
|
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, UriBar.INPUT_TIMEOUT);
|
||||||
|
}
|
||||||
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
|
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -86,18 +93,34 @@ class UriBar extends React.PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value } });
|
navigation.navigate({
|
||||||
|
routeName: Constants.DRAWER_ROUTE_SEARCH,
|
||||||
|
key: 'searchPage',
|
||||||
|
params: { searchQuery: value },
|
||||||
|
});
|
||||||
|
} else if (SEARCH_TYPES.TAG === type) {
|
||||||
|
navigation.navigate({
|
||||||
|
routeName: Constants.DRAWER_ROUTE_TAG,
|
||||||
|
key: 'tagPage',
|
||||||
|
params: {
|
||||||
|
tag: value.toLowerCase(),
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const uri = normalizeURI(value);
|
const uri = normalizeURI(value);
|
||||||
navigateToUri(navigation, uri);
|
navigateToUri(navigation, uri);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_keyboardDidShow = evt => {
|
||||||
|
this.setState({ keyboardHeight: evt.endCoordinates.height });
|
||||||
|
};
|
||||||
|
|
||||||
_keyboardDidHide = () => {
|
_keyboardDidHide = () => {
|
||||||
if (this.textInput) {
|
if (this.textInput) {
|
||||||
this.textInput.blur();
|
this.textInput.blur();
|
||||||
}
|
}
|
||||||
this.setState({ focused: false });
|
this.setState({ focused: false, keyboardHeight: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
setSelection() {
|
setSelection() {
|
||||||
|
@ -152,16 +175,18 @@ class UriBar extends React.PureComponent {
|
||||||
selectedItemCount,
|
selectedItemCount,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
suggestions,
|
suggestions,
|
||||||
|
showUriBarSuggestions,
|
||||||
value,
|
value,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (this.state.currentValue === null) {
|
if (this.state.currentValue === null) {
|
||||||
this.setState({ currentValue: value });
|
this.setState({ currentValue: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
let style = [uriBarStyle.overlay, belowOverlay ? null : uriBarStyle.overlayElevated];
|
let style = [
|
||||||
|
uriBarStyle.overlay,
|
||||||
// TODO: Add optional setting to enable URI / search bar suggestions
|
belowOverlay ? null : uriBarStyle.overlayElevated,
|
||||||
/* if (this.state.focused) { style.push(uriBarStyle.inFocus); } */
|
this.state.focused && showUriBarSuggestions ? uriBarStyle.inFocus : null,
|
||||||
|
];
|
||||||
|
|
||||||
// TODO: selectionModeActions should be dynamically created / specified
|
// TODO: selectionModeActions should be dynamically created / specified
|
||||||
return (
|
return (
|
||||||
|
@ -248,20 +273,21 @@ class UriBar extends React.PureComponent {
|
||||||
onSubmitEditing={this.handleSubmitEditing}
|
onSubmitEditing={this.handleSubmitEditing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.state.focused && !this.state.directSearch && (
|
|
||||||
<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>
|
</View>
|
||||||
|
{this.state.focused && showUriBarSuggestions && (
|
||||||
|
<FlatList
|
||||||
|
style={[
|
||||||
|
uriBarStyle.suggestions,
|
||||||
|
{ height: Dimensions.get('window').height - this.state.keyboardHeight - 60 },
|
||||||
|
]}
|
||||||
|
data={suggestions}
|
||||||
|
keyboardShouldPersistTaps={'handled'}
|
||||||
|
keyExtractor={(item, value) => `${item.value}-${item.type}`}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/a
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectCurrentRoute, selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectCurrentRoute, selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import SettingsPage from './view';
|
import SettingsPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -13,6 +13,7 @@ const select = state => ({
|
||||||
drawerStack: selectDrawerStack(state),
|
drawerStack: selectDrawerStack(state),
|
||||||
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||||
|
showUriBarSuggestions: makeSelectClientSetting(SETTINGS.SHOW_URI_BAR_SUGGESTIONS)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -51,6 +51,7 @@ class SettingsPage extends React.PureComponent {
|
||||||
navigation,
|
navigation,
|
||||||
popDrawerStack,
|
popDrawerStack,
|
||||||
showNsfw,
|
showNsfw,
|
||||||
|
showUriBarSuggestions,
|
||||||
setClientSetting,
|
setClientSetting,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ class SettingsPage extends React.PureComponent {
|
||||||
<View style={settingsStyle.container}>
|
<View style={settingsStyle.container}>
|
||||||
<PageHeader title={'Settings'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
<PageHeader title={'Settings'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
||||||
<ScrollView style={settingsStyle.scrollContainer}>
|
<ScrollView style={settingsStyle.scrollContainer}>
|
||||||
|
<Text style={settingsStyle.sectionTitle}>Content</Text>
|
||||||
<View style={settingsStyle.row}>
|
<View style={settingsStyle.row}>
|
||||||
<View style={settingsStyle.switchText}>
|
<View style={settingsStyle.switchText}>
|
||||||
<Text style={settingsStyle.label}>Enable background media playback</Text>
|
<Text style={settingsStyle.label}>Enable background media playback</Text>
|
||||||
|
@ -79,13 +81,29 @@ class SettingsPage extends React.PureComponent {
|
||||||
|
|
||||||
<View style={settingsStyle.row}>
|
<View style={settingsStyle.row}>
|
||||||
<View style={settingsStyle.switchText}>
|
<View style={settingsStyle.switchText}>
|
||||||
<Text style={settingsStyle.label}>Show NSFW content</Text>
|
<Text style={settingsStyle.label}>Show mature content</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={settingsStyle.switchContainer}>
|
<View style={settingsStyle.switchContainer}>
|
||||||
<Switch value={showNsfw} onValueChange={value => setClientSetting(SETTINGS.SHOW_NSFW, value)} />
|
<Switch value={showNsfw} onValueChange={value => setClientSetting(SETTINGS.SHOW_NSFW, value)} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={settingsStyle.sectionDivider} />
|
||||||
|
<Text style={settingsStyle.sectionTitle}>Search</Text>
|
||||||
|
<View style={settingsStyle.row}>
|
||||||
|
<View style={settingsStyle.switchText}>
|
||||||
|
<Text style={settingsStyle.label}>Show URL suggestions</Text>
|
||||||
|
</View>
|
||||||
|
<View style={settingsStyle.switchContainer}>
|
||||||
|
<Switch
|
||||||
|
value={showUriBarSuggestions}
|
||||||
|
onValueChange={value => setClientSetting(SETTINGS.SHOW_URI_BAR_SUGGESTIONS, value)}
|
||||||
|
/>
|
||||||
|
|||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={settingsStyle.sectionDivider} />
|
||||||
|
<Text style={settingsStyle.sectionTitle}>Other</Text>
|
||||||
<View style={settingsStyle.row}>
|
<View style={settingsStyle.row}>
|
||||||
<View style={settingsStyle.switchText}>
|
<View style={settingsStyle.switchText}>
|
||||||
<Text style={settingsStyle.label}>Keep the daemon background service running after closing the app</Text>
|
<Text style={settingsStyle.label}>Keep the daemon background service running after closing the app</Text>
|
||||||
|
|
|
@ -39,6 +39,14 @@ const settingsStyle = StyleSheet.create({
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
},
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 20,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
sectionDivider: {
|
||||||
|
marginTop: 24,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default settingsStyle;
|
export default settingsStyle;
|
||||||
|
|
|
@ -36,7 +36,7 @@ const uriBarStyle = StyleSheet.create({
|
||||||
},
|
},
|
||||||
overlay: {
|
overlay: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'red',
|
||||||
top: 0,
|
top: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
|
@ -54,9 +54,10 @@ const uriBarStyle = StyleSheet.create({
|
||||||
item: {
|
item: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 12,
|
paddingLeft: 16,
|
||||||
paddingTop: 8,
|
paddingRight: 16,
|
||||||
paddingBottom: 8,
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12,
|
||||||
},
|
},
|
||||||
itemContent: {
|
itemContent: {
|
||||||
marginLeft: 12,
|
marginLeft: 12,
|
||||||
|
|
Loading…
Reference in a new issue
👍 good change, missing i18n