You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-05-22 09:05:38 +02:00
Added search page
This commit is contained in:
@@ -90,8 +90,8 @@ android {
|
|||||||
applicationId "net.cozic.joplin"
|
applicationId "net.cozic.joplin"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode 27
|
versionCode 28
|
||||||
versionName "0.9.14"
|
versionName "0.9.15"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a", "x86"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,9 @@ globalStyle.icon = {
|
|||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalStyle.lineInput = {
|
||||||
|
color: globalStyle.color,
|
||||||
|
backgroundColor: globalStyle.backgroundColor,
|
||||||
|
};
|
||||||
|
|
||||||
export { globalStyle }
|
export { globalStyle }
|
||||||
@@ -4,21 +4,13 @@ import { ListView, Text, TouchableHighlight, Switch, View, StyleSheet } from 're
|
|||||||
import { Log } from 'lib/log.js';
|
import { Log } from 'lib/log.js';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
import { Checkbox } from 'lib/components/checkbox.js';
|
import { Checkbox } from 'lib/components/checkbox.js';
|
||||||
|
import { NoteItem } from 'lib/components/note-item.js';
|
||||||
import { reg } from 'lib/registry.js';
|
import { reg } from 'lib/registry.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
import { globalStyle } from 'lib/components/global-style.js';
|
import { globalStyle } from 'lib/components/global-style.js';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listItem: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
height: 40,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: globalStyle.dividerColor,
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: globalStyle.marginLeft,
|
|
||||||
backgroundColor: globalStyle.backgroundColor,
|
|
||||||
},
|
|
||||||
noItemMessage: {
|
noItemMessage: {
|
||||||
paddingLeft: globalStyle.marginLeft,
|
paddingLeft: globalStyle.marginLeft,
|
||||||
paddingRight: globalStyle.marginRight,
|
paddingRight: globalStyle.marginRight,
|
||||||
@@ -64,36 +56,18 @@ class ItemListComponent extends Component {
|
|||||||
listView_itemPress(itemId) {}
|
listView_itemPress(itemId) {}
|
||||||
|
|
||||||
render() {
|
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
|
// `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39
|
||||||
|
|
||||||
if (this.state.dataSource.getRowCount()) {
|
if (this.state.dataSource.getRowCount()) {
|
||||||
return (
|
return (
|
||||||
<ListView
|
<ListView
|
||||||
dataSource={this.state.dataSource}
|
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}
|
enableEmptySections={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -40,12 +40,11 @@ let styleObject = {
|
|||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
marginRight: 2,
|
marginRight: 2,
|
||||||
},
|
},
|
||||||
backButton: {
|
iconButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: globalStyle.backgroundColor,
|
backgroundColor: globalStyle.backgroundColor,
|
||||||
paddingLeft: 15,
|
paddingLeft: 15,
|
||||||
paddingRight: 15,
|
paddingRight: 15,
|
||||||
marginRight: 1,
|
|
||||||
},
|
},
|
||||||
saveButton: {
|
saveButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -100,6 +99,9 @@ styleObject.topIcon = Object.assign({}, globalStyle.icon);
|
|||||||
styleObject.topIcon.flex = 1;
|
styleObject.topIcon.flex = 1;
|
||||||
styleObject.topIcon.textAlignVertical = 'center';
|
styleObject.topIcon.textAlignVertical = 'center';
|
||||||
|
|
||||||
|
styleObject.backButton = Object.assign({}, styleObject.iconButton);
|
||||||
|
styleObject.backButton.marginRight = 1;
|
||||||
|
|
||||||
styleObject.backButtonDisabled = Object.assign({}, styleObject.backButton, { opacity: globalStyle.disabledOpacity });
|
styleObject.backButtonDisabled = Object.assign({}, styleObject.backButton, { opacity: globalStyle.disabledOpacity });
|
||||||
styleObject.saveButtonDisabled = Object.assign({}, styleObject.saveButton, { 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' });
|
this.props.dispatch({ type: 'Navigation/BACK' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchButton_press() {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'Navigation/NAVIGATE',
|
||||||
|
routeName: 'Search',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menu_select(value) {
|
menu_select(value) {
|
||||||
if (typeof(value) == 'function') {
|
if (typeof(value) == 'function') {
|
||||||
value();
|
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 key = 0;
|
||||||
let menuOptionComponents = [];
|
let menuOptionComponents = [];
|
||||||
for (let i = 0; i < this.props.menuOptions.length; i++) {
|
for (let i = 0; i < this.props.menuOptions.length; i++) {
|
||||||
@@ -220,10 +239,11 @@ class ScreenHeaderComponent extends Component {
|
|||||||
{ sideMenuButton(styles, () => this.sideMenuButton_press()) }
|
{ sideMenuButton(styles, () => this.sideMenuButton_press()) }
|
||||||
{ backButton(styles, () => this.backButton_press(), !this.props.historyCanGoBack) }
|
{ 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) }
|
{ 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}>
|
<Menu onSelect={(value) => this.menu_select(value)} style={styles.contextMenu}>
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Text style={styles.contextMenuTrigger}> ⋮</Text>
|
<Text style={styles.contextMenuTrigger}> ⋮</Text>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
<MenuOptions>
|
<MenuOptions>
|
||||||
{ menuOptionComponents }
|
{ menuOptionComponents }
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -130,8 +130,6 @@ class SideMenuContentComponent extends Component {
|
|||||||
|
|
||||||
if (items.length) items.push(<View style={{ height: 30, flex: -1 }} key='divider_1'></View>); // DIVIDER
|
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);
|
let lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||||
const syncReportText = lines.join("\n");
|
const syncReportText = lines.join("\n");
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { FoldersScreen } from 'lib/components/screens/folders.js'
|
|||||||
import { LogScreen } from 'lib/components/screens/log.js'
|
import { LogScreen } from 'lib/components/screens/log.js'
|
||||||
import { StatusScreen } from 'lib/components/screens/status.js'
|
import { StatusScreen } from 'lib/components/screens/status.js'
|
||||||
import { WelcomeScreen } from 'lib/components/screens/welcome.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 { OneDriveLoginScreen } from 'lib/components/screens/onedrive-login.js'
|
||||||
import { Setting } from 'lib/models/setting.js'
|
import { Setting } from 'lib/models/setting.js'
|
||||||
import { MenuContext } from 'react-native-popup-menu';
|
import { MenuContext } from 'react-native-popup-menu';
|
||||||
@@ -52,6 +53,7 @@ let defaultState = {
|
|||||||
},
|
},
|
||||||
syncStarted: false,
|
syncStarted: false,
|
||||||
syncReport: {},
|
syncReport: {},
|
||||||
|
searchQuery: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialRoute = {
|
const initialRoute = {
|
||||||
@@ -298,6 +300,11 @@ const reducer = (state = defaultState, action) => {
|
|||||||
newState.syncReport = action.report;
|
newState.syncReport = action.report;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'SEARCH_QUERY':
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.searchQuery = action.query.trim();
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = 'In reducer: ' + error.message;
|
error.message = 'In reducer: ' + error.message;
|
||||||
@@ -420,6 +427,12 @@ async function initialize(dispatch, backButtonHandler) {
|
|||||||
reg.scheduleSync(1);
|
reg.scheduleSync(1);
|
||||||
}, 1000 * 60 * 5);
|
}, 1000 * 60 * 5);
|
||||||
|
|
||||||
|
if (Setting.value('env') == 'dev') {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
reg.scheduleSync();
|
||||||
|
}
|
||||||
|
|
||||||
initializationState_ = 'done';
|
initializationState_ = 'done';
|
||||||
|
|
||||||
reg.logger().info('Application initialized');
|
reg.logger().info('Application initialized');
|
||||||
@@ -434,11 +447,6 @@ class AppComponent extends React.Component {
|
|||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await initialize(this.props.dispatch, this.backButtonHandler.bind(this));
|
await initialize(this.props.dispatch, this.backButtonHandler.bind(this));
|
||||||
if (Setting.value('env') == 'dev') {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
reg.scheduleSync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backButtonHandler() {
|
backButtonHandler() {
|
||||||
@@ -481,6 +489,7 @@ class AppComponent extends React.Component {
|
|||||||
OneDriveLogin: { screen: OneDriveLoginScreen },
|
OneDriveLogin: { screen: OneDriveLoginScreen },
|
||||||
Log: { screen: LogScreen },
|
Log: { screen: LogScreen },
|
||||||
Status: { screen: StatusScreen },
|
Status: { screen: StatusScreen },
|
||||||
|
Search: { screen: SearchScreen },
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user