RoomsListView improvements (#819)

This commit is contained in:
Diego Mello 2019-04-17 15:57:46 -03:00 committed by GitHub
parent 9cf81bbab9
commit a891ee14ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 138 additions and 109 deletions

View File

@ -441,6 +441,9 @@ class DB {
setActiveDB(database = '') { setActiveDB(database = '') {
const path = database.replace(/(^\w+:|^)\/\//, ''); const path = database.replace(/(^\w+:|^)\/\//, '');
if (this.database) {
this.database.close();
}
return this.databases.activeDB = new Realm({ return this.databases.activeDB = new Realm({
path: `${ path }.realm`, path: `${ path }.realm`,
schema, schema,

View File

@ -8,7 +8,6 @@ import { RectButton } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-navigation'; import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal'; import equal from 'deep-equal';
import moment from 'moment'; import moment from 'moment';
import 'react-native-console-time-polyfill';
import EJSON from 'ejson'; import EJSON from 'ejson';
import { import {

View File

@ -0,0 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import SearchBox from '../../../containers/SearchBox';
import { isIOS } from '../../../utils/deviceInfo';
const SearchBar = React.memo(({ onChangeSearchText }) => {
if (isIOS) {
return <SearchBox onChangeText={onChangeSearchText} testID='rooms-list-view-search' key='rooms-list-view-search' />;
}
return null;
});
SearchBar.propTypes = {
onChangeSearchText: PropTypes.func
};
export default SearchBar;

View File

@ -0,0 +1,35 @@
import React from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';
import { CustomIcon } from '../../../lib/Icons';
import I18n from '../../../i18n';
import Touch from '../../../utils/touch';
import styles from '../styles';
const Sort = React.memo(({ searchLength, sortBy, toggleSort }) => {
if (searchLength > 0) {
return null;
}
return (
<Touch
key='rooms-list-view-sort'
onPress={toggleSort}
style={styles.dropdownContainerHeader}
>
<View style={styles.sortItemContainer}>
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
<CustomIcon style={styles.sortIcon} size={22} name='sort1' />
</View>
</Touch>
);
});
Sort.propTypes = {
searchLength: PropTypes.number,
sortBy: PropTypes.string,
toggleSort: PropTypes.func
};
export default Sort;

View File

@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import SearchBar from './SearchBar';
import Sort from './Sort';
const ListHeader = React.memo(({
searchLength, sortBy, onChangeSearchText, toggleSort
}) => (
<React.Fragment>
<SearchBar onChangeSearchText={onChangeSearchText} />
<Sort searchLength={searchLength} sortBy={sortBy} toggleSort={toggleSort} />
</React.Fragment>
));
ListHeader.propTypes = {
searchLength: PropTypes.number,
sortBy: PropTypes.string,
onChangeSearchText: PropTypes.func,
toggleSort: PropTypes.func
};
export default ListHeader;

View File

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
View, FlatList, BackHandler, ActivityIndicator, Text, ScrollView, Keyboard, LayoutAnimation View, FlatList, BackHandler, ActivityIndicator, Text, ScrollView, Keyboard, LayoutAnimation, InteractionManager
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { SafeAreaView, NavigationEvents } from 'react-navigation'; import { SafeAreaView, NavigationEvents } from 'react-navigation';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import SearchBox from '../../containers/SearchBox';
import ConnectionBadge from '../../containers/ConnectionBadge'; import ConnectionBadge from '../../containers/ConnectionBadge';
import database, { safeAddListener } from '../../lib/realm'; import database, { safeAddListener } from '../../lib/realm';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
@ -19,7 +18,6 @@ import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import SortDropdown from './SortDropdown'; import SortDropdown from './SortDropdown';
import ServerDropdown from './ServerDropdown'; import ServerDropdown from './ServerDropdown';
import Touch from '../../utils/touch';
import { import {
toggleSortDropdown as toggleSortDropdownAction, toggleSortDropdown as toggleSortDropdownAction,
openSearchHeader as openSearchHeaderAction, openSearchHeader as openSearchHeaderAction,
@ -29,10 +27,10 @@ import {
import { appStart as appStartAction } from '../../actions'; import { appStart as appStartAction } from '../../actions';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { isIOS, isAndroid } from '../../utils/deviceInfo'; import { isIOS, isAndroid } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons';
import RoomsListHeaderView from './Header'; import RoomsListHeaderView from './Header';
import { DrawerButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton'; import { DrawerButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListHeader from './ListHeader';
const SCROLL_OFFSET = 56; const SCROLL_OFFSET = 56;
@ -119,6 +117,8 @@ export default class RoomsListView extends LoggedView {
constructor(props) { constructor(props) {
super('RoomsListView', props); super('RoomsListView', props);
console.time(`${ this.constructor.name } init`);
console.time(`${ this.constructor.name } mount`);
this.data = []; this.data = [];
this.state = { this.state = {
@ -143,6 +143,7 @@ export default class RoomsListView extends LoggedView {
navigation.setParams({ navigation.setParams({
onPressItem: this._onPressItem, initSearchingAndroid: this.initSearchingAndroid, cancelSearchingAndroid: this.cancelSearchingAndroid onPressItem: this._onPressItem, initSearchingAndroid: this.initSearchingAndroid, cancelSearchingAndroid: this.cancelSearchingAndroid
}); });
console.timeEnd(`${ this.constructor.name } mount`);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -240,16 +241,19 @@ export default class RoomsListView extends LoggedView {
} }
componentWillUnmount() { componentWillUnmount() {
this.removeListener(this.data); if (this.data && this.data.removeAllListeners) {
this.removeListener(this.unread); this.data.removeAllListeners();
this.removeListener(this.favorites); }
this.removeListener(this.discussions); if (this.getSubscriptions && this.getSubscriptions.stop) {
this.removeListener(this.channels); this.getSubscriptions.stop();
this.removeListener(this.privateGroup); }
this.removeListener(this.direct); if (this.updateStateInteraction && this.updateStateInteraction.cancel) {
this.removeListener(this.livechat); this.updateStateInteraction.cancel();
}
console.countReset(`${ this.constructor.name }.render calls`);
} }
// eslint-disable-next-line react/sort-comp
internalSetState = (...args) => { internalSetState = (...args) => {
const { navigation } = this.props; const { navigation } = this.props;
if (isIOS && navigation.isFocused()) { if (isIOS && navigation.isFocused()) {
@ -258,7 +262,11 @@ export default class RoomsListView extends LoggedView {
this.setState(...args); this.setState(...args);
} }
getSubscriptions = () => { getSubscriptions = debounce(() => {
if (this.data && this.data.removeAllListeners) {
this.data.removeAllListeners();
}
const { const {
server, sortBy, showUnread, showFavorites, groupByType server, sortBy, showUnread, showFavorites, groupByType
} = this.props; } = this.props;
@ -271,92 +279,57 @@ export default class RoomsListView extends LoggedView {
this.data = this.data.sorted('roomUpdatedAt', true); this.data = this.data.sorted('roomUpdatedAt', true);
} }
let chats = [];
let unread = [];
let favorites = [];
let discussions = [];
let channels = [];
let privateGroup = [];
let direct = [];
let livechat = [];
// unread // unread
if (showUnread) { if (showUnread) {
this.unread = this.data.filtered('(unread > 0 || alert == true)'); this.unread = this.data.filtered('(unread > 0 || alert == true)');
unread = this.removeRealmInstance(this.unread);
safeAddListener(this.unread, debounce(() => this.internalSetState({ unread: this.removeRealmInstance(this.unread) }), 300));
} else { } else {
this.removeListener(unread); this.unread = [];
} }
// favorites // favorites
if (showFavorites) { if (showFavorites) {
this.favorites = this.data.filtered('f == true'); this.favorites = this.data.filtered('f == true');
favorites = this.removeRealmInstance(this.favorites);
safeAddListener(this.favorites, debounce(() => this.internalSetState({ favorites: this.removeRealmInstance(this.favorites) }), 300));
} else { } else {
this.removeListener(favorites); this.favorites = [];
} }
// type // type
if (groupByType) { if (groupByType) {
// discussions
this.discussions = this.data.filtered('prid != null'); this.discussions = this.data.filtered('prid != null');
discussions = this.removeRealmInstance(this.discussions);
// channels
this.channels = this.data.filtered('t == $0 AND prid == null', 'c'); this.channels = this.data.filtered('t == $0 AND prid == null', 'c');
channels = this.removeRealmInstance(this.channels);
// private
this.privateGroup = this.data.filtered('t == $0 AND prid == null', 'p'); this.privateGroup = this.data.filtered('t == $0 AND prid == null', 'p');
privateGroup = this.removeRealmInstance(this.privateGroup);
// direct
this.direct = this.data.filtered('t == $0 AND prid == null', 'd'); this.direct = this.data.filtered('t == $0 AND prid == null', 'd');
direct = this.removeRealmInstance(this.direct);
// livechat
this.livechat = this.data.filtered('t == $0 AND prid == null', 'l'); this.livechat = this.data.filtered('t == $0 AND prid == null', 'l');
livechat = this.removeRealmInstance(this.livechat); } else if (showUnread) {
safeAddListener(this.discussions, debounce(() => this.internalSetState({ discussions: this.removeRealmInstance(this.discussions) }), 300));
safeAddListener(this.channels, debounce(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) }), 300));
safeAddListener(this.privateGroup, debounce(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) }), 300));
safeAddListener(this.direct, debounce(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) }), 300));
safeAddListener(this.livechat, debounce(() => this.internalSetState({ livechat: this.removeRealmInstance(this.livechat) }), 300));
this.removeListener(this.chats);
} else {
// chats
if (showUnread) {
this.chats = this.data.filtered('(unread == 0 && alert == false)'); this.chats = this.data.filtered('(unread == 0 && alert == false)');
} else { } else {
this.chats = this.data; this.chats = this.data;
} }
chats = this.removeRealmInstance(this.chats); safeAddListener(this.data, this.updateState);
safeAddListener(this.chats, debounce(() => this.internalSetState({ chats: this.removeRealmInstance(this.chats) }), 300));
this.removeListener(this.discussions);
this.removeListener(this.channels);
this.removeListener(this.privateGroup);
this.removeListener(this.direct);
this.removeListener(this.livechat);
} }
}, 300);
// setState // eslint-disable-next-line react/sort-comp
updateState = debounce(() => {
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
this.internalSetState({ this.internalSetState({
chats, unread, favorites, discussions, channels, privateGroup, direct, livechat, loading: false chats: this.getSnapshot(this.chats),
unread: this.getSnapshot(this.unread),
favorites: this.getSnapshot(this.favorites),
discussions: this.getSnapshot(this.discussions),
channels: this.getSnapshot(this.channels),
privateGroup: this.getSnapshot(this.privateGroup),
direct: this.getSnapshot(this.direct),
livechat: this.getSnapshot(this.livechat),
loading: false
}); });
} });
} }, 300);
removeRealmInstance = (data) => { getSnapshot = (data) => {
if (data && data.length) {
const array = Array.from(data); const array = Array.from(data);
return JSON.parse(JSON.stringify(array)); return JSON.parse(JSON.stringify(array));
} }
return [];
removeListener = (data) => {
if (data && data.removeAllListeners) {
data.removeAllListeners();
}
} }
initSearchingAndroid = () => { initSearchingAndroid = () => {
@ -449,44 +422,19 @@ export default class RoomsListView extends LoggedView {
getScrollRef = ref => this.scroll = ref getScrollRef = ref => this.scroll = ref
renderHeader = () => { renderListHeader = () => {
const { search } = this.state; const { search } = this.state;
if (search.length > 0) {
return null;
}
return this.renderSort();
}
renderSort = () => {
const { sortBy } = this.props; const { sortBy } = this.props;
return ( return (
<Touch <ListHeader
key='rooms-list-view-sort' searchLength={search.length}
onPress={this.toggleSort} sortBy={sortBy}
style={styles.dropdownContainerHeader} onChangeSearchText={this.search}
> toggleSort={this.toggleSort}
<View style={styles.sortItemContainer}> />
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
<CustomIcon style={styles.sortIcon} size={22} name='sort1' />
</View>
</Touch>
); );
} }
renderSearchBar = () => {
if (isIOS) {
return <SearchBox onChangeText={this.search} testID='rooms-list-view-search' key='rooms-list-view-search' />;
}
}
renderListHeader = () => (
[
this.renderSearchBar(),
this.renderHeader()
]
)
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { const {
userId, baseUrl, StoreLastMessage userId, baseUrl, StoreLastMessage
@ -634,6 +582,7 @@ export default class RoomsListView extends LoggedView {
} }
render = () => { render = () => {
console.count(`${ this.constructor.name }.render calls`);
const { const {
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown
} = this.props; } = this.props;
@ -654,7 +603,7 @@ export default class RoomsListView extends LoggedView {
) )
: null : null
} }
{showServerDropdown ? <ServerDropdown navigator={navigator} /> : null} {showServerDropdown ? <ServerDropdown /> : null}
<ConnectionBadge /> <ConnectionBadge />
<NavigationEvents <NavigationEvents
onDidFocus={() => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)} onDidFocus={() => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)}

View File

@ -1,3 +1,5 @@
import 'react-native-console-time-polyfill';
import './app/ReactotronConfig'; import './app/ReactotronConfig';
import { AppRegistry } from 'react-native'; import { AppRegistry } from 'react-native';
import App from './app/index'; import App from './app/index';