From 58e5781ed28b1a47c2a4dc79722d075f5ee7e694 Mon Sep 17 00:00:00 2001
From: Diego Mello <diegolmello@gmail.com>
Date: Thu, 13 Feb 2020 16:24:39 -0300
Subject: [PATCH] [FIX] Spotlight (#1719)

---
 app/containers/SearchBox.js                   |   2 +-
 app/lib/rocketchat.js                         |  34 +++---
 app/views/RoomsListView/Header/index.js       |   3 +-
 .../RoomsListView/ListHeader/Directory.js     |   6 +-
 .../RoomsListView/ListHeader/SearchBar.js     |  12 ++-
 app/views/RoomsListView/ListHeader/Sort.js    |   6 +-
 app/views/RoomsListView/ListHeader/index.js   |  27 +++--
 app/views/RoomsListView/index.js              | 102 ++++++++++++------
 8 files changed, 133 insertions(+), 59 deletions(-)

diff --git a/app/containers/SearchBox.js b/app/containers/SearchBox.js
index afdb5867e..7c3e337ee 100644
--- a/app/containers/SearchBox.js
+++ b/app/containers/SearchBox.js
@@ -37,7 +37,7 @@ const styles = StyleSheet.create({
 		...sharedStyles.textRegular
 	},
 	cancel: {
-		marginRight: 10
+		marginRight: 15
 	},
 	cancelText: {
 		...sharedStyles.textRegular,
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index b2e8edcde..634cbb812 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -565,18 +565,28 @@ const RocketChat = {
 					RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
 					new Promise((resolve, reject) => this.oldPromise = reject)
 				]);
-
-				data = data.concat(users.map(user => ({
-					...user,
-					rid: user.username,
-					name: user.username,
-					t: 'd',
-					search: true
-				})), rooms.map(room => ({
-					rid: room._id,
-					...room,
-					search: true
-				})));
+				if (filterUsers) {
+					data = data.concat(users.map(user => ({
+						...user,
+						rid: user.username,
+						name: user.username,
+						t: 'd',
+						search: true
+					})));
+				}
+				if (filterRooms) {
+					rooms.forEach((room) => {
+						// Check if it exists on local database
+						const index = data.findIndex(item => item.rid === room._id);
+						if (index === -1) {
+							data.push({
+								rid: room._id,
+								...room,
+								search: true
+							});
+						}
+					});
+				}
 			}
 			delete this.oldPromise;
 			return data;
diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js
index 294cf9eb6..badcb59ac 100644
--- a/app/views/RoomsListView/Header/index.js
+++ b/app/views/RoomsListView/Header/index.js
@@ -38,6 +38,7 @@ class RoomsListHeaderView extends PureComponent {
 		}
 	}
 
+	// eslint-disable-next-line react/sort-comp
 	handleCommands = ({ event }) => {
 		if (handleCommandOpenServerDropdown(event)) {
 			this.onPress();
@@ -79,7 +80,7 @@ class RoomsListHeaderView extends PureComponent {
 				connecting={connecting}
 				isFetching={isFetching}
 				onPress={this.onPress}
-				onSearchChangeText={text => this.onSearchChangeText(text)}
+				onSearchChangeText={this.onSearchChangeText}
 			/>
 		);
 	}
diff --git a/app/views/RoomsListView/ListHeader/Directory.js b/app/views/RoomsListView/ListHeader/Directory.js
index 477f1a6cb..339a94a69 100644
--- a/app/views/RoomsListView/ListHeader/Directory.js
+++ b/app/views/RoomsListView/ListHeader/Directory.js
@@ -10,7 +10,10 @@ import DisclosureIndicator from '../../../containers/DisclosureIndicator';
 import { themes } from '../../../constants/colors';
 import { withTheme } from '../../../theme';
 
-const Directory = React.memo(({ goDirectory, theme }) => {
+const Directory = React.memo(({ goDirectory, theme, searching }) => {
+	if (searching > 0) {
+		return null;
+	}
 	const color = { color: themes[theme].headerSecondaryText };
 	return (
 		<Touch
@@ -33,6 +36,7 @@ const Directory = React.memo(({ goDirectory, theme }) => {
 });
 
 Directory.propTypes = {
+	searching: PropTypes.bool,
 	goDirectory: PropTypes.func,
 	theme: PropTypes.string
 };
diff --git a/app/views/RoomsListView/ListHeader/SearchBar.js b/app/views/RoomsListView/ListHeader/SearchBar.js
index d18393793..6446d4cf4 100644
--- a/app/views/RoomsListView/ListHeader/SearchBar.js
+++ b/app/views/RoomsListView/ListHeader/SearchBar.js
@@ -5,7 +5,9 @@ import SearchBox from '../../../containers/SearchBox';
 import { isIOS } from '../../../utils/deviceInfo';
 import { withTheme } from '../../../theme';
 
-const SearchBar = React.memo(({ theme, onChangeSearchText, inputRef }) => {
+const SearchBar = React.memo(({
+	theme, onChangeSearchText, inputRef, searching, onCancelSearchPress, onSearchFocus
+}) => {
 	if (isIOS) {
 		return (
 			<SearchBox
@@ -13,6 +15,9 @@ const SearchBar = React.memo(({ theme, onChangeSearchText, inputRef }) => {
 				testID='rooms-list-view-search'
 				inputRef={inputRef}
 				theme={theme}
+				hasCancel={searching}
+				onCancelPress={onCancelSearchPress}
+				onFocus={onSearchFocus}
 			/>
 		);
 	}
@@ -21,8 +26,11 @@ const SearchBar = React.memo(({ theme, onChangeSearchText, inputRef }) => {
 
 SearchBar.propTypes = {
 	theme: PropTypes.string,
+	searching: PropTypes.bool,
 	inputRef: PropTypes.func,
-	onChangeSearchText: PropTypes.func
+	onChangeSearchText: PropTypes.func,
+	onCancelSearchPress: PropTypes.func,
+	onSearchFocus: PropTypes.func
 };
 
 export default withTheme(SearchBar);
diff --git a/app/views/RoomsListView/ListHeader/Sort.js b/app/views/RoomsListView/ListHeader/Sort.js
index f5ec8820c..e0ca2d106 100644
--- a/app/views/RoomsListView/ListHeader/Sort.js
+++ b/app/views/RoomsListView/ListHeader/Sort.js
@@ -11,9 +11,9 @@ import { withTheme } from '../../../theme';
 
 
 const Sort = React.memo(({
-	searchLength, sortBy, toggleSort, theme
+	searching, sortBy, toggleSort, theme
 }) => {
-	if (searchLength > 0) {
+	if (searching > 0) {
 		return null;
 	}
 	return (
@@ -36,7 +36,7 @@ const Sort = React.memo(({
 });
 
 Sort.propTypes = {
-	searchLength: PropTypes.number,
+	searching: PropTypes.bool,
 	sortBy: PropTypes.string,
 	theme: PropTypes.string,
 	toggleSort: PropTypes.func
diff --git a/app/views/RoomsListView/ListHeader/index.js b/app/views/RoomsListView/ListHeader/index.js
index 106d80c71..63d20ca9e 100644
--- a/app/views/RoomsListView/ListHeader/index.js
+++ b/app/views/RoomsListView/ListHeader/index.js
@@ -6,22 +6,37 @@ import Directory from './Directory';
 import Sort from './Sort';
 
 const ListHeader = React.memo(({
-	searchLength, sortBy, onChangeSearchText, toggleSort, goDirectory, inputRef
+	searching,
+	sortBy,
+	onChangeSearchText,
+	toggleSort,
+	goDirectory,
+	inputRef,
+	onCancelSearchPress,
+	onSearchFocus
 }) => (
 	<>
-		<SearchBar onChangeSearchText={onChangeSearchText} inputRef={inputRef} />
-		<Directory goDirectory={goDirectory} />
-		<Sort searchLength={searchLength} sortBy={sortBy} toggleSort={toggleSort} />
+		<SearchBar
+			inputRef={inputRef}
+			searching={searching}
+			onChangeSearchText={onChangeSearchText}
+			onCancelSearchPress={onCancelSearchPress}
+			onSearchFocus={onSearchFocus}
+		/>
+		<Directory searching={searching} goDirectory={goDirectory} />
+		<Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} />
 	</>
 ));
 
 ListHeader.propTypes = {
-	searchLength: PropTypes.number,
+	searching: PropTypes.bool,
 	sortBy: PropTypes.string,
 	onChangeSearchText: PropTypes.func,
 	toggleSort: PropTypes.func,
 	goDirectory: PropTypes.func,
-	inputRef: PropTypes.func
+	inputRef: PropTypes.func,
+	onCancelSearchPress: PropTypes.func,
+	onSearchFocus: PropTypes.func
 };
 
 export default ListHeader;
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index e8dca1d81..c2a99673f 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -101,23 +101,21 @@ const keyExtractor = item => item.rid;
 class RoomsListView extends React.Component {
 	static navigationOptions = ({ navigation, screenProps }) => {
 		const searching = navigation.getParam('searching');
-		const cancelSearchingAndroid = navigation.getParam(
-			'cancelSearchingAndroid'
-		);
+		const cancelSearch = navigation.getParam('cancelSearch', () => {});
 		const onPressItem = navigation.getParam('onPressItem', () => {});
-		const initSearchingAndroid = navigation.getParam(
-			'initSearchingAndroid',
+		const initSearching = navigation.getParam(
+			'initSearching',
 			() => {}
 		);
 
 		return {
 			...themedHeader(screenProps.theme),
-			headerLeft: searching ? (
+			headerLeft: searching && isAndroid ? (
 				<CustomHeaderButtons left>
 					<Item
 						title='cancel'
 						iconName='cross'
-						onPress={cancelSearchingAndroid}
+						onPress={cancelSearch}
 					/>
 				</CustomHeaderButtons>
 			) : (
@@ -127,13 +125,13 @@ class RoomsListView extends React.Component {
 				/>
 			),
 			headerTitle: <RoomsListHeaderView />,
-			headerRight: searching ? null : (
+			headerRight: searching && isAndroid ? null : (
 				<CustomHeaderButtons>
 					{isAndroid ? (
 						<Item
 							title='search'
 							iconName='magnifier'
-							onPress={initSearchingAndroid}
+							onPress={initSearching}
 						/>
 					) : null}
 					<Item
@@ -200,8 +198,8 @@ class RoomsListView extends React.Component {
 		const { navigation, closeServerDropdown } = this.props;
 		navigation.setParams({
 			onPressItem: this._onPressItem,
-			initSearchingAndroid: this.initSearchingAndroid,
-			cancelSearchingAndroid: this.cancelSearchingAndroid
+			initSearching: this.initSearching,
+			cancelSearch: this.cancelSearch
 		});
 		if (isTablet) {
 			EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands);
@@ -247,7 +245,7 @@ class RoomsListView extends React.Component {
 	}
 
 	shouldComponentUpdate(nextProps, nextState) {
-		const { allChats } = this.state;
+		const { allChats, searching } = this.state;
 		// eslint-disable-next-line react/destructuring-assignment
 		const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]);
 		if (propsUpdated) {
@@ -262,6 +260,10 @@ class RoomsListView extends React.Component {
 			this.shouldUpdate = true;
 		}
 
+		if (nextState.searching !== searching) {
+			return true;
+		}
+
 		// Abort if it's not focused
 		if (!nextProps.navigation.isFocused()) {
 			return false;
@@ -269,16 +271,12 @@ class RoomsListView extends React.Component {
 
 		const {
 			loading,
-			searching,
 			width,
 			search
 		} = this.state;
 		if (nextState.loading !== loading) {
 			return true;
 		}
-		if (nextState.searching !== searching) {
-			return true;
-		}
 		if (nextState.width !== width) {
 			return true;
 		}
@@ -452,29 +450,50 @@ class RoomsListView extends React.Component {
 		});
 	}
 
-	initSearchingAndroid = () => {
+	initSearching = () => {
 		const { openSearchHeader, navigation } = this.props;
-		this.setState({ searching: true });
-		navigation.setParams({ searching: true });
-		openSearchHeader();
+		this.internalSetState({ searching: true });
+		if (isAndroid) {
+			navigation.setParams({ searching: true });
+			openSearchHeader();
+		}
 	};
 
-	cancelSearchingAndroid = () => {
+	cancelSearch = () => {
+		const { searching } = this.state;
+		const { closeSearchHeader, navigation } = this.props;
+
+		if (!searching) {
+			return;
+		}
+
+		if (isIOS && this.inputRef) {
+			this.inputRef.blur();
+			this.inputRef.clear();
+		}
 		if (isAndroid) {
-			const { closeSearchHeader, navigation } = this.props;
-			this.setState({ searching: false });
 			navigation.setParams({ searching: false });
 			closeSearchHeader();
-			this.internalSetState({ search: [] });
 		}
 		Keyboard.dismiss();
+
+		this.setState({ searching: false, search: [] }, () => {
+			setTimeout(() => {
+				const offset = isAndroid ? 0 : SCROLL_OFFSET;
+				if (this.scroll.scrollTo) {
+					this.scroll.scrollTo({ x: 0, y: offset, animated: true });
+				} else if (this.scroll.scrollToOffset) {
+					this.scroll.scrollToOffset({ offset });
+				}
+			}, 200);
+		});
 	};
 
 	handleBackPress = () => {
 		const { searching } = this.state;
 		const { appStart } = this.props;
 		if (searching) {
-			this.cancelSearchingAndroid();
+			this.cancelSearch();
 			return true;
 		}
 		appStart('background');
@@ -483,10 +502,19 @@ class RoomsListView extends React.Component {
 
 	// eslint-disable-next-line react/sort-comp
 	search = debounce(async(text) => {
+		const { searching } = this.state;
 		const result = await RocketChat.search({ text });
+		// if the search was cancelled before the promise is resolved
+		if (!searching) {
+			return;
+		}
 		this.internalSetState({
-			search: result
+			search: result,
+			searching: true
 		});
+		if (this.scroll && this.scroll.scrollTo) {
+			this.scroll.scrollTo({ x: 0, y: 0, animated: true });
+		}
 	}, 300);
 
 	getRoomTitle = item => RocketChat.getRoomTitle(item)
@@ -494,8 +522,8 @@ class RoomsListView extends React.Component {
 	getRoomAvatar = item => RocketChat.getRoomAvatar(item)
 
 	goRoom = (item) => {
-		this.cancelSearchingAndroid();
 		const { navigation } = this.props;
+		this.cancelSearch();
 		this.item = item;
 		navigation.navigate('RoomView', {
 			rid: item.rid,
@@ -678,21 +706,29 @@ class RoomsListView extends React.Component {
 	};
 
 	onRefresh = () => {
+		const { searching } = this.state;
 		const { roomsRequest } = this.props;
+		if (searching) {
+			return;
+		}
 		roomsRequest({ allData: true });
 	}
 
 	getScrollRef = ref => (this.scroll = ref);
 
+	getInputRef = ref => (this.inputRef = ref);
+
 	renderListHeader = () => {
-		const { search } = this.state;
+		const { searching } = this.state;
 		const { sortBy } = this.props;
 		return (
 			<ListHeader
-				inputRef={(ref) => { this.inputRef = ref; }}
-				searchLength={search.length}
+				inputRef={this.getInputRef}
+				searching={searching}
 				sortBy={sortBy}
 				onChangeSearchText={this.search}
+				onCancelSearchPress={this.cancelSearch}
+				onSearchFocus={this.initSearching}
 				toggleSort={this.toggleSort}
 				goDirectory={this.goDirectory}
 			/>
@@ -768,7 +804,7 @@ class RoomsListView extends React.Component {
 
 	renderScroll = () => {
 		const {
-			loading, chats, search
+			loading, chats, search, searching
 		} = this.state;
 		const { theme, refreshing } = this.props;
 
@@ -779,8 +815,8 @@ class RoomsListView extends React.Component {
 		return (
 			<FlatList
 				ref={this.getScrollRef}
-				data={search.length ? search : chats}
-				extraData={search.length ? search : chats}
+				data={searching ? search : chats}
+				extraData={searching ? search : chats}
 				contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
 				keyExtractor={keyExtractor}
 				style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
-- 
GitLab