1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-13 22:12:50 +02:00

Added search page

This commit is contained in:
Laurent Cozic
2017-07-22 23:52:24 +01:00
parent 729b8f7c79
commit 1c3bf21539
8 changed files with 264 additions and 46 deletions

View File

@@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 27
versionName "0.9.14"
versionCode 28
versionName "0.9.15"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

@@ -25,4 +25,9 @@ globalStyle.icon = {
fontSize: 30,
};
globalStyle.lineInput = {
color: globalStyle.color,
backgroundColor: globalStyle.backgroundColor,
};
export { globalStyle }

View File

@@ -4,21 +4,13 @@ import { ListView, Text, TouchableHighlight, Switch, View, StyleSheet } from 're
import { Log } from 'lib/log.js';
import { _ } from 'lib/locale.js';
import { Checkbox } from 'lib/components/checkbox.js';
import { NoteItem } from 'lib/components/note-item.js';
import { reg } from 'lib/registry.js';
import { Note } from 'lib/models/note.js';
import { time } from 'lib/time-utils.js';
import { globalStyle } from 'lib/components/global-style.js';
const styles = StyleSheet.create({
listItem: {
flexDirection: 'row',
height: 40,
borderBottomWidth: 1,
borderBottomColor: globalStyle.dividerColor,
alignItems: 'center',
paddingLeft: globalStyle.marginLeft,
backgroundColor: globalStyle.backgroundColor,
},
noItemMessage: {
paddingLeft: globalStyle.marginLeft,
paddingRight: globalStyle.marginRight,
@@ -64,36 +56,18 @@ class ItemListComponent extends Component {
listView_itemPress(itemId) {}
render() {
let renderRow = (item) => {
let onPress = () => {
this.listView_itemPress(item.id);
}
let onLongPress = () => {
this.listView_itemLongPress(item.id);
}
const listItemStyle = { color: globalStyle.color };
const checkboxStyle = Object.assign({}, listItemStyle);
if (!Number(item.is_todo)) checkboxStyle.display = 'none';
const checkboxChecked = !!Number(item.todo_completed);
return (
<TouchableHighlight onPress={onPress} onLongPress={onLongPress} underlayColor="#0066FF">
<View style={ styles.listItem }>
<Checkbox style={checkboxStyle} checked={checkboxChecked} onChange={(checked) => { this.todoCheckbox_change(item.id, checked) }}/><Text style={listItemStyle}>{item.title}</Text>
</View>
</TouchableHighlight>
);
}
// `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39
if (this.state.dataSource.getRowCount()) {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={renderRow}
renderRow={(note) => {
return <NoteItem
note={note}
onPress={(note) => this.listView_itemPress(note.id) }
onCheckboxChange={(note, checked) => this.todoCheckbox_change(note.id, checked) }
/> }}
enableEmptySections={true}
/>
);

View File

@@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { ListView, Text, TouchableHighlight, Switch, View, StyleSheet } from 'react-native';
import { Log } from 'lib/log.js';
import { _ } from 'lib/locale.js';
import { Checkbox } from 'lib/components/checkbox.js';
import { reg } from 'lib/registry.js';
import { Note } from 'lib/models/note.js';
import { time } from 'lib/time-utils.js';
import { globalStyle } from 'lib/components/global-style.js';
const styleObject = {
listItem: {
flexDirection: 'row',
height: 40,
borderBottomWidth: 1,
borderBottomColor: globalStyle.dividerColor,
alignItems: 'center',
paddingLeft: globalStyle.marginLeft,
backgroundColor: globalStyle.backgroundColor,
},
listItemText: {
color: globalStyle.color,
},
};
const styles = StyleSheet.create(styleObject);
class NoteItemComponent extends Component {
noteItem_press(noteId) {
this.props.dispatch({
type: 'Navigation/NAVIGATE',
routeName: 'Note',
noteId: noteId,
});
}
render() {
const note = this.props.note ? this.props.note : {};
const onPress = this.props.onPress;
const onLongPress = this.props.onLongPress;
const onCheckboxChange = this.props.onCheckboxChange;
const checkboxStyle = !Number(note.is_todo) ? { display: 'none' } : { color: globalStyle.color };
const checkboxChecked = !!Number(note.todo_completed);
return (
<TouchableHighlight onPress={() => onPress ? onPress(note) : this.noteItem_press(note.id)} onLongPress={() => onLongPress(note)} underlayColor="#0066FF">
<View style={ styles.listItem }>
<Checkbox style={checkboxStyle} checked={checkboxChecked} onChange={(checked) => { onCheckboxChange(note, checked) }}/><Text style={styles.listItemText}>{note.title}</Text>
</View>
</TouchableHighlight>
);
}
}
const NoteItem = connect(
(state) => {
return {};
}
)(NoteItemComponent)
export { NoteItem }

View File

@@ -40,12 +40,11 @@ let styleObject = {
paddingRight: 5,
marginRight: 2,
},
backButton: {
iconButton: {
flex: 1,
backgroundColor: globalStyle.backgroundColor,
paddingLeft: 15,
paddingRight: 15,
marginRight: 1,
},
saveButton: {
flex: 1,
@@ -100,6 +99,9 @@ styleObject.topIcon = Object.assign({}, globalStyle.icon);
styleObject.topIcon.flex = 1;
styleObject.topIcon.textAlignVertical = 'center';
styleObject.backButton = Object.assign({}, styleObject.iconButton);
styleObject.backButton.marginRight = 1;
styleObject.backButtonDisabled = Object.assign({}, styleObject.backButton, { opacity: globalStyle.disabledOpacity });
styleObject.saveButtonDisabled = Object.assign({}, styleObject.saveButton, { opacity: globalStyle.disabledOpacity });
@@ -115,6 +117,13 @@ class ScreenHeaderComponent extends Component {
this.props.dispatch({ type: 'Navigation/BACK' });
}
searchButton_press() {
this.props.dispatch({
type: 'Navigation/NAVIGATE',
routeName: 'Search',
});
}
menu_select(value) {
if (typeof(value) == 'function') {
value();
@@ -170,6 +179,16 @@ class ScreenHeaderComponent extends Component {
);
}
function searchButton(styles, onPress) {
return (
<TouchableOpacity onPress={onPress}>
<View style={styles.iconButton}>
<Icon name='md-search' style={styles.topIcon} />
</View>
</TouchableOpacity>
);
}
let key = 0;
let menuOptionComponents = [];
for (let i = 0; i < this.props.menuOptions.length; i++) {
@@ -220,10 +239,11 @@ class ScreenHeaderComponent extends Component {
{ sideMenuButton(styles, () => this.sideMenuButton_press()) }
{ backButton(styles, () => this.backButton_press(), !this.props.historyCanGoBack) }
{ saveButton(styles, () => { if (this.props.onSaveButtonPress) this.props.onSaveButtonPress() }, this.props.saveButtonDisabled === true, this.props.showSaveButton === true) }
{ titleComp }
{ titleComp }
{ searchButton(styles, () => this.searchButton_press()) }
<Menu onSelect={(value) => this.menu_select(value)} style={styles.contextMenu}>
<MenuTrigger>
<Text style={styles.contextMenuTrigger}> &#8942;</Text>
<Text style={styles.contextMenuTrigger}> &#8942;</Text>
</MenuTrigger>
<MenuOptions>
{ menuOptionComponents }

View File

@@ -0,0 +1,147 @@
import React, { Component } from 'react';
import { ListView, StyleSheet, View, TextInput, FlatList, TouchableHighlight } from 'react-native';
import { connect } from 'react-redux'
import { ScreenHeader } from 'lib/components/screen-header.js';
import Icon from 'react-native-vector-icons/Ionicons';
import { _ } from 'lib/locale.js';
import { Note } from 'lib/models/note.js';
import { NoteItem } from 'lib/components/note-item.js';
import { BaseScreenComponent } from 'lib/components/base-screen.js';
import { globalStyle } from 'lib/components/global-style.js';
let styles = {
body: {
flex: 1,
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: globalStyle.dividerColor,
}
}
styles.searchTextInput = Object.assign({}, globalStyle.lineInput);
styles.searchTextInput.paddingLeft = globalStyle.marginLeft;
styles.searchTextInput.flex = 1;
styles.clearIcon = Object.assign({}, globalStyle.icon);
styles.clearIcon.color = globalStyle.colorFaded;
styles.clearIcon.paddingRight = globalStyle.marginRight;
styles.clearIcon.backgroundColor = globalStyle.backgroundColor;
styles = StyleSheet.create(styles);
class SearchScreenComponent extends BaseScreenComponent {
static navigationOptions(options) {
return { header: null };
}
constructor() {
super();
this.state = {
query: '',
notes: [],
};
this.isMounted_ = false;
}
componentDidMount() {
this.setState({ query: this.props.query });
this.refreshSearch(this.props.query);
this.isMounted_ = true;
}
componentWillUnmount() {
this.isMounted_ = false;
}
componentWillReceiveProps(newProps) {
let newState = {};
if ('query' in newProps) newState.query = newProps.query;
if (Object.getOwnPropertyNames(newState).length) {
this.setState(newState);
this.refreshSearch(newState.query);
}
}
searchTextInput_submit() {
const query = this.state.query.trim();
if (!query) return;
this.props.dispatch({
type: 'SEARCH_QUERY',
query: query,
});
}
clearButton_press() {
this.props.dispatch({
type: 'SEARCH_QUERY',
query: '',
});
}
async refreshSearch(query = null) {
query = query === null ? this.state.query.trim : query.trim();
let notes = []
if (query) {
notes = await Note.previews(null, {
anywherePattern: '*' + query + '*',
});
}
if (!this.isMounted_) return;
this.setState({ notes: notes });
}
searchTextInput_changeText(text) {
this.setState({ query: text });
}
render() {
return (
<View style={this.styles().screen}>
<ScreenHeader navState={this.props.navigation.state} />
<View style={styles.body}>
<View style={styles.searchContainer}>
<TextInput
style={styles.searchTextInput}
underlineColorAndroid="#ffffff00"
onSubmitEditing={() => { this.searchTextInput_submit() }}
onChangeText={(text) => this.searchTextInput_changeText(text) }
value={this.state.query}
/>
<TouchableHighlight onPress={() => this.clearButton_press() }>
<Icon name='md-close-circle' style={styles.clearIcon} />
</TouchableHighlight>
</View>
<FlatList
data={this.state.notes}
keyExtractor={(item, index) => item.id}
renderItem={(event) => <NoteItem
note={event.item}
/>}
/>
</View>
</View>
);
}
}
const SearchScreen = connect(
(state) => {
return {
query: state.searchQuery,
};
}
)(SearchScreenComponent)
export { SearchScreen };

View File

@@ -130,8 +130,6 @@ class SideMenuContentComponent extends Component {
if (items.length) items.push(<View style={{ height: 30, flex: -1 }} key='divider_1'></View>); // DIVIDER
const syncTitle = this.props.syncStarted ? _('Cancel sync') : _('Synchronize');
let lines = Synchronizer.reportToLines(this.props.syncReport);
const syncReportText = lines.join("\n");

View File

@@ -25,6 +25,7 @@ import { FoldersScreen } from 'lib/components/screens/folders.js'
import { LogScreen } from 'lib/components/screens/log.js'
import { StatusScreen } from 'lib/components/screens/status.js'
import { WelcomeScreen } from 'lib/components/screens/welcome.js'
import { SearchScreen } from 'lib/components/screens/search.js'
import { OneDriveLoginScreen } from 'lib/components/screens/onedrive-login.js'
import { Setting } from 'lib/models/setting.js'
import { MenuContext } from 'react-native-popup-menu';
@@ -52,6 +53,7 @@ let defaultState = {
},
syncStarted: false,
syncReport: {},
searchQuery: '',
};
const initialRoute = {
@@ -298,6 +300,11 @@ const reducer = (state = defaultState, action) => {
newState.syncReport = action.report;
break;
case 'SEARCH_QUERY':
newState = Object.assign({}, state);
newState.searchQuery = action.query.trim();
}
} catch (error) {
error.message = 'In reducer: ' + error.message;
@@ -420,6 +427,12 @@ async function initialize(dispatch, backButtonHandler) {
reg.scheduleSync(1);
}, 1000 * 60 * 5);
if (Setting.value('env') == 'dev') {
} else {
reg.scheduleSync();
}
initializationState_ = 'done';
reg.logger().info('Application initialized');
@@ -434,11 +447,6 @@ class AppComponent extends React.Component {
async componentDidMount() {
await initialize(this.props.dispatch, this.backButtonHandler.bind(this));
if (Setting.value('env') == 'dev') {
} else {
reg.scheduleSync();
}
}
backButtonHandler() {
@@ -481,6 +489,7 @@ class AppComponent extends React.Component {
OneDriveLogin: { screen: OneDriveLoginScreen },
Log: { screen: LogScreen },
Status: { screen: StatusScreen },
Search: { screen: SearchScreen },
};
return (