Uri bar suggestions #59

Closed
akinwale wants to merge 2 commits from uri-bar-suggestions into master
9 changed files with 110 additions and 48 deletions

4
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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),
}; };
}; };

View file

@ -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>

View file

@ -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,16 +53,18 @@ 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);
let timeout = -1;
if (!showUriBarSuggestions) {
timeout = setTimeout(() => {
if (text.trim().length === 0) { if (text.trim().length === 0) {
// don't do anything if the text is empty // don't do anything if the text is empty
return; return;
} }
updateSearchQuery(text);
if (!text.startsWith('lbry://')) { if (!text.startsWith('lbry://')) {
// not a URI input, so this is a search, perform a direct search // not a URI input, so this is a search, perform a direct search
if (onSearchSubmitted) { if (onSearchSubmitted) {
@ -68,6 +74,7 @@ class UriBar extends React.PureComponent {
} }
} }
}, UriBar.INPUT_TIMEOUT); }, 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,21 +273,22 @@ class UriBar extends React.PureComponent {
onSubmitEditing={this.handleSubmitEditing} onSubmitEditing={this.handleSubmitEditing}
/> />
)} )}
{this.state.focused && !this.state.directSearch && ( </View>
<View style={uriBarStyle.suggestions}> {this.state.focused && showUriBarSuggestions && (
<FlatList <FlatList
style={uriBarStyle.suggestionList} style={[
uriBarStyle.suggestions,
{ height: Dimensions.get('window').height - this.state.keyboardHeight - 60 },
]}
data={suggestions} data={suggestions}
keyboardShouldPersistTaps={'handled'} keyboardShouldPersistTaps={'handled'}
keyExtractor={(item, value) => item.value} keyExtractor={(item, value) => `${item.value}-${item.type}`}
renderItem={({ item }) => ( renderItem={({ item }) => (
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} /> <UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
)} )}
/> />
</View>
)} )}
</View> </View>
</View>
); );
} }
} }

View file

@ -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 => ({

View file

@ -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)}
/>
kauffj commented 2019-10-17 00:12:17 +02:00 (Migrated from github.com)
Review

👍 good change, missing i18n

:+1: good change, missing i18n
</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>

View file

@ -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;

View file

@ -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,