[NEW] Directory and Federation (#967)

* Initial

* Search working

* Refactor layout

* Layout and search working

* Navigate

* Remove inline styles and fix i18n

* Federation setting

* Missing i18n

* Fix android style

* Refactor
This commit is contained in:
Diego Mello 2019-06-10 13:22:35 -03:00 committed by GitHub
parent 4382eca8b6
commit b7e6d3615f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 704 additions and 51 deletions

View File

@ -14,6 +14,9 @@ export default {
CROWD_Enable: { CROWD_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
}, },
FEDERATION_Enabled: {
type: 'valueAsBoolean'
},
LDAP_Enable: { LDAP_Enable: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
}, },

18
app/containers/Check.js Normal file
View File

@ -0,0 +1,18 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({
icon: {
width: 22,
height: 22,
marginHorizontal: 15,
...sharedStyles.textColorDescription
}
});
const Check = React.memo(() => <CustomIcon style={styles.icon} size={22} name='check' />);
export default Check;

View File

@ -34,7 +34,7 @@ const styles = StyleSheet.create({
} }
}); });
const SearchBox = ({ onChangeText, testID }) => ( const SearchBox = ({ onChangeText, onSubmitEditing, testID }) => (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.searchBox}> <View style={styles.searchBox}>
<CustomIcon name='magnifier' size={14} color='#8E8E93' /> <CustomIcon name='magnifier' size={14} color='#8E8E93' />
@ -49,6 +49,7 @@ const SearchBox = ({ onChangeText, testID }) => (
testID={testID} testID={testID}
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
onChangeText={onChangeText} onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
/> />
</View> </View>
</View> </View>
@ -56,6 +57,7 @@ const SearchBox = ({ onChangeText, testID }) => (
SearchBox.propTypes = { SearchBox.propTypes = {
onChangeText: PropTypes.func.isRequired, onChangeText: PropTypes.func.isRequired,
onSubmitEditing: PropTypes.func,
testID: PropTypes.string testID: PropTypes.string
}; };

View File

@ -142,9 +142,10 @@ export default {
DELETE: 'DELETE', DELETE: 'DELETE',
description: 'description', description: 'description',
Description: 'Description', Description: 'Description',
Directory: 'Directory',
Direct_Messages: 'Direct Messages',
Disable_notifications: 'Disable notifications', Disable_notifications: 'Disable notifications',
Discussions: 'Discussions', Discussions: 'Discussions',
Direct_Messages: 'Direct Messages',
Dont_Have_An_Account: 'Don\'t have an account?', Dont_Have_An_Account: 'Don\'t have an account?',
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?', Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
edit: 'edit', edit: 'edit',
@ -294,6 +295,9 @@ export default {
saving_settings: 'saving settings', saving_settings: 'saving settings',
Search_Messages: 'Search Messages', Search_Messages: 'Search Messages',
Search: 'Search', Search: 'Search',
Search_by: 'Search by',
Search_global_users: 'Search for global users',
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
Select_Avatar: 'Select Avatar', Select_Avatar: 'Select Avatar',
Select_Users: 'Select Users', Select_Users: 'Select Users',
Send: 'Send', Send: 'Send',
@ -348,6 +352,7 @@ export default {
Updating: 'Updating...', Updating: 'Updating...',
Uploading: 'Uploading', Uploading: 'Uploading',
Upload_file_question_mark: 'Upload file?', Upload_file_question_mark: 'Upload file?',
Users: 'Users',
User_added_by: 'User {{userAdded}} added by {{userBy}}', User_added_by: 'User {{userAdded}} added by {{userBy}}',
User_has_been_key: 'User has been {{key}}!', User_has_been_key: 'User has been {{key}}!',
User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}', User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}',

View File

@ -146,11 +146,12 @@ export default {
delete: 'excluir', delete: 'excluir',
Delete: 'Excluir', Delete: 'Excluir',
DELETE: 'EXCLUIR', DELETE: 'EXCLUIR',
Direct_Messages: 'Mensagens Diretas',
Directory: 'Diretório',
description: 'descrição', description: 'descrição',
Description: 'Descrição', Description: 'Descrição',
Disable_notifications: 'Desabilitar notificações', Disable_notifications: 'Desabilitar notificações',
Discussions: 'Discussões', Discussions: 'Discussões',
Direct_Messages: 'Mensagens Diretas',
Dont_Have_An_Account: 'Não tem uma conta?', Dont_Have_An_Account: 'Não tem uma conta?',
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?', Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
edit: 'editar', edit: 'editar',
@ -293,6 +294,9 @@ export default {
saving_settings: 'salvando configurações', saving_settings: 'salvando configurações',
Search_Messages: 'Buscar Mensagens', Search_Messages: 'Buscar Mensagens',
Search: 'Buscar', Search: 'Buscar',
Search_by: 'Buscar por',
Search_global_users: 'Busca por usuários globais',
Search_global_users_description: 'Caso ativado, busca por usuários de outras empresas ou servidores.',
Select_Avatar: 'Selecionar Avatar', Select_Avatar: 'Selecionar Avatar',
Select_Users: 'Selecionar Usuários', Select_Users: 'Selecionar Usuários',
Send: 'Enviar', Send: 'Enviar',
@ -344,6 +348,7 @@ export default {
Updating: 'Atualizando...', Updating: 'Atualizando...',
Uploading: 'Subindo arquivo', Uploading: 'Subindo arquivo',
Upload_file_question_mark: 'Enviar arquivo?', Upload_file_question_mark: 'Enviar arquivo?',
Users: 'Usuários',
User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}', User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}',
User_has_been_key: 'Usuário foi {{key}}!', User_has_been_key: 'Usuário foi {{key}}!',
User_is_no_longer_role_by_: '{{user}} não pertence mais à {{role}} por {{userBy}}', User_is_no_longer_role_by_: '{{user}} não pertence mais à {{role}} por {{userBy}}',

View File

@ -16,6 +16,7 @@ import AuthLoadingView from './views/AuthLoadingView';
import RoomsListView from './views/RoomsListView'; import RoomsListView from './views/RoomsListView';
import RoomView from './views/RoomView'; import RoomView from './views/RoomView';
import NewMessageView from './views/NewMessageView'; import NewMessageView from './views/NewMessageView';
import DirectoryView from './views/DirectoryView';
import LoginView from './views/LoginView'; import LoginView from './views/LoginView';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import Sidebar from './views/SidebarView'; import Sidebar from './views/SidebarView';
@ -110,7 +111,8 @@ const ChatsStack = createStackNavigator({
SearchMessagesView, SearchMessagesView,
SelectedUsersView, SelectedUsersView,
ThreadMessagesView, ThreadMessagesView,
MessagesView MessagesView,
DirectoryView
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader
}); });

View File

@ -5,7 +5,7 @@ import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import reduxStore from './createStore'; import reduxStore from './createStore';
import defaultSettings from '../constants/settings'; import defaultSettings from '../constants/settings';
import messagesStatus from '../constants/messagesStatus'; import messagesStatus from '../constants/messagesStatus';
import database, { safeAddListener } from './realm'; import database from './realm';
import log from '../utils/log'; import log from '../utils/log';
import { isIOS, getBundleId } from '../utils/deviceInfo'; import { isIOS, getBundleId } from '../utils/deviceInfo';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
@ -57,23 +57,6 @@ const RocketChat = {
// RC 0.51.0 // RC 0.51.0
return this.sdk.methodCall(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast }); return this.sdk.methodCall(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
}, },
async createDirectMessageAndWait(username) {
const room = await RocketChat.createDirectMessage(username);
return new Promise((resolve) => {
const data = database.objects('subscriptions')
.filtered('rid = $1', room.rid);
if (data.length) {
return resolve(data[0]);
}
safeAddListener(data, () => {
if (!data.length) { return; }
data.removeAllListeners();
resolve(data[0]);
});
});
},
async getUserToken() { async getUserToken() {
try { try {
return await AsyncStorage.getItem(TOKEN_KEY); return await AsyncStorage.getItem(TOKEN_KEY);
@ -849,6 +832,14 @@ const RocketChat = {
this.sdk.subscribe('stream-notify-logged', 'user-status'); this.sdk.subscribe('stream-notify-logged', 'user-status');
} }
} }
},
getDirectory({
query, count, offset, sort
}) {
// RC 1.0
return this.sdk.get('directory', {
query, count, offset, sort
});
} }
}; };

View File

@ -0,0 +1,63 @@
import React from 'react';
import { Text, View } from 'react-native';
import PropTypes from 'prop-types';
import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch';
import RoomTypeIcon from '../../containers/RoomTypeIcon';
import styles from './styles';
const DirectoryItemLabel = React.memo(({ text }) => {
if (!text) {
return null;
}
return <Text style={styles.directoryItemLabel}>{text}</Text>;
});
const DirectoryItem = ({
title, description, avatar, onPress, testID, style, baseUrl, user, rightLabel, type
}) => (
<Touch onPress={onPress} style={styles.directoryItemButton} testID={testID}>
<View style={[styles.directoryItemContainer, style]}>
<Avatar
text={avatar}
size={30}
type={type}
style={styles.directoryItemAvatar}
baseUrl={baseUrl}
userId={user.id}
token={user.token}
/>
<View style={styles.directoryItemTextContainer}>
<View style={styles.directoryItemTextTitle}>
<RoomTypeIcon type='c' />
<Text style={styles.directoryItemName} numberOfLines={1}>{title}</Text>
</View>
<Text style={styles.directoryItemUsername} numberOfLines={1}>{description}</Text>
</View>
<DirectoryItemLabel text={rightLabel} />
</View>
</Touch>
);
DirectoryItem.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
avatar: PropTypes.string,
type: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
baseUrl: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired,
style: PropTypes.any,
rightLabel: PropTypes.string
};
DirectoryItemLabel.propTypes = {
text: PropTypes.string
};
export default DirectoryItem;

View File

@ -0,0 +1,121 @@
import React, { PureComponent } from 'react';
import {
View, Text, Animated, Easing, TouchableWithoutFeedback, Switch
} from 'react-native';
import PropTypes from 'prop-types';
import Touch from '../../utils/touch';
import styles from './styles';
import { CustomIcon } from '../../lib/Icons';
import Check from '../../containers/Check';
import I18n from '../../i18n';
const ANIMATION_DURATION = 200;
const ANIMATION_PROPS = {
duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true
};
export default class DirectoryOptions extends PureComponent {
static propTypes = {
type: PropTypes.string,
globalUsers: PropTypes.bool,
isFederationEnabled: PropTypes.bool,
close: PropTypes.func,
changeType: PropTypes.func,
toggleWorkspace: PropTypes.func
}
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(
this.animatedValue,
{
toValue: 1,
...ANIMATION_PROPS
},
).start();
}
close = () => {
const { close } = this.props;
Animated.timing(
this.animatedValue,
{
toValue: 0,
...ANIMATION_PROPS
},
).start(() => close());
}
renderItem = (itemType) => {
const { changeType, type: propType } = this.props;
let text = 'Users';
let icon = 'user';
if (itemType === 'channels') {
text = 'Channels';
icon = 'hashtag';
}
return (
<Touch style={styles.dropdownItemButton} onPress={() => changeType(itemType)}>
<View style={styles.dropdownItemContainer}>
<CustomIcon style={styles.dropdownItemIcon} size={22} name={icon} />
<Text style={styles.dropdownItemText}>{I18n.t(text)}</Text>
{propType === itemType ? <Check /> : null}
</View>
</Touch>
);
}
render() {
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-326, 0]
});
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.3]
});
const { globalUsers, toggleWorkspace, isFederationEnabled } = this.props;
return (
<React.Fragment>
<TouchableWithoutFeedback onPress={this.close}>
<Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]} />
</TouchableWithoutFeedback>
<Animated.View style={[styles.dropdownContainer, { transform: [{ translateY }] }]}>
<Touch
onPress={this.close}
style={styles.dropdownContainerHeader}
>
<View style={styles.dropdownItemContainer}>
<Text style={styles.dropdownToggleText}>{I18n.t('Search_by')}</Text>
<CustomIcon style={[styles.dropdownItemIcon, styles.inverted]} size={22} name='arrow-down' />
</View>
</Touch>
{this.renderItem('channels')}
{this.renderItem('users')}
{isFederationEnabled
? (
<React.Fragment>
<View style={styles.dropdownSeparator} />
<View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}>
<View style={styles.globalUsersTextContainer}>
<Text style={styles.dropdownItemText}>{I18n.t('Search_global_users')}</Text>
<Text style={styles.dropdownItemDescription}>{I18n.t('Search_global_users_description')}</Text>
</View>
<Switch value={globalUsers} onValueChange={toggleWorkspace} />
</View>
</React.Fragment>
)
: null}
</Animated.View>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,248 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
View, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation';
import RocketChat from '../../lib/rocketchat';
import DirectoryItem from './DirectoryItem';
import sharedStyles from '../Styles';
import I18n from '../../i18n';
import Touch from '../../utils/touch';
import SearchBox from '../../containers/SearchBox';
import { CustomIcon } from '../../lib/Icons';
import StatusBar from '../../containers/StatusBar';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import debounce from '../../utils/debounce';
import log from '../../utils/log';
import Options from './Options';
import styles from './styles';
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
user: {
id: state.login.user && state.login.user.id,
token: state.login.user && state.login.user.token
},
isFederationEnabled: state.settings.FEDERATION_Enabled
}))
export default class DirectoryView extends React.Component {
static navigationOptions = () => ({
title: I18n.t('Directory')
})
static propTypes = {
navigation: PropTypes.object,
baseUrl: PropTypes.string,
isFederationEnabled: PropTypes.bool,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
})
};
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
text: '',
total: -1,
showOptionsDropdown: false,
globalUsers: true,
type: 'channels'
};
}
componentDidMount() {
this.load({});
}
onSearchChangeText = (text) => {
this.setState({ text });
}
onPressItem = (item) => {
const { navigation } = this.props;
try {
const onPressItem = navigation.getParam('onPressItem', () => {});
onPressItem(item);
} catch (error) {
console.log('DirectoryView -> onPressItem -> error', error);
}
}
// eslint-disable-next-line react/sort-comp
load = debounce(async({ newSearch = false }) => {
if (newSearch) {
this.setState({ data: [], total: -1, loading: false });
}
const {
loading, text, total, data: { length }
} = this.state;
if (loading || length === total) {
return;
}
this.setState({ loading: true });
try {
const { data, type, globalUsers } = this.state;
const query = { text, type, workspace: globalUsers ? 'all' : 'local' };
const directories = await RocketChat.getDirectory({
query,
offset: data.length,
count: 50,
sort: (type === 'users') ? { username: 1 } : { usersCount: -1 }
});
if (directories.success) {
this.setState({
data: [...data, ...directories.result],
loading: false,
total: directories.total
});
} else {
this.setState({ loading: false });
}
} catch (error) {
log('err_load_directory', error);
this.setState({ loading: false });
}
}, 200)
search = () => {
this.load({ newSearch: true });
}
changeType = (type) => {
this.setState({ type, data: [] }, () => this.search());
}
toggleWorkspace = () => {
this.setState(({ globalUsers }) => ({ globalUsers: !globalUsers, data: [] }), () => this.search());
}
toggleDropdown = () => {
this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown }));
}
goRoom = async({ rid, name, t }) => {
const { navigation } = this.props;
await navigation.navigate('RoomsListView');
navigation.navigate('RoomView', { rid, name, t });
}
onPressItem = async(item) => {
const { type } = this.state;
if (type === 'users') {
const result = await RocketChat.createDirectMessage(item.username);
if (result.success) {
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
}
} else {
this.goRoom({ rid: item._id, name: item.name, t: 'c' });
}
}
renderHeader = () => {
const { type } = this.state;
return (
<React.Fragment>
<SearchBox
onChangeText={this.onSearchChangeText}
onSubmitEditing={this.search}
testID='federation-view-search'
/>
<Touch onPress={this.toggleDropdown} testID='federation-view-create-channel'>
<View style={[sharedStyles.separatorVertical, styles.toggleDropdownContainer]}>
<CustomIcon style={styles.toggleDropdownIcon} size={20} name={type === 'users' ? 'user' : 'hashtag'} />
<Text style={styles.toggleDropdownText}>{type === 'users' ? I18n.t('Users') : I18n.t('Channels')}</Text>
<CustomIcon name='arrow-down' size={20} style={styles.toggleDropdownArrow} />
</View>
</Touch>
</React.Fragment>
);
}
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
renderItem = ({ item, index }) => {
const { data, type } = this.state;
const { baseUrl, user } = this.props;
let style;
if (index === data.length - 1) {
style = sharedStyles.separatorBottom;
}
const commonProps = {
title: item.name,
onPress: () => this.onPressItem(item),
baseUrl,
testID: `federation-view-item-${ item.name }`,
style,
user
};
if (type === 'users') {
return (
<DirectoryItem
avatar={item.username}
description={item.username}
rightLabel={item.federation && item.federation.peer}
type='d'
{...commonProps}
/>
);
}
return (
<DirectoryItem
avatar={item.name}
description={item.topic}
rightLabel={I18n.t('N_users', { n: item.usersCount })}
type='c'
{...commonProps}
/>
);
}
render = () => {
const {
data, loading, showOptionsDropdown, type, globalUsers
} = this.state;
const { isFederationEnabled } = this.props;
return (
<SafeAreaView style={styles.safeAreaView} testID='directory-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={data}
style={styles.list}
contentContainerStyle={styles.listContainer}
extraData={this.state}
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
keyboardShouldPersistTaps='always'
ListFooterComponent={loading ? <RCActivityIndicator /> : null}
onEndReached={() => this.load({})}
/>
{showOptionsDropdown
? (
<Options
type={type}
globalUsers={globalUsers}
close={this.toggleDropdown}
changeType={this.changeType}
toggleWorkspace={this.toggleWorkspace}
isFederationEnabled={isFederationEnabled}
/>
)
: null}
</SafeAreaView>
);
}
}

View File

@ -0,0 +1,151 @@
import { StyleSheet } from 'react-native';
import { COLOR_WHITE, COLOR_SEPARATOR, COLOR_PRIMARY } from '../../constants/colors';
import { isIOS } from '../../utils/deviceInfo';
import sharedStyles from '../Styles';
export default StyleSheet.create({
safeAreaView: {
flex: 1,
backgroundColor: isIOS ? '#F7F8FA' : '#E1E5E8'
},
list: {
flex: 1
},
listContainer: {
paddingBottom: 30
},
separator: {
marginLeft: 60
},
toggleDropdownContainer: {
height: 47,
backgroundColor: COLOR_WHITE,
flexDirection: 'row',
alignItems: 'center'
},
toggleDropdownIcon: {
color: COLOR_PRIMARY,
marginLeft: 20,
marginRight: 17
},
toggleDropdownText: {
flex: 1,
color: COLOR_PRIMARY,
fontSize: 17,
...sharedStyles.textRegular
},
toggleDropdownArrow: {
...sharedStyles.textColorDescription,
marginRight: 15
},
dropdownContainer: {
backgroundColor: COLOR_WHITE,
width: '100%',
position: 'absolute',
top: 0
},
backdrop: {
...StyleSheet.absoluteFill,
backgroundColor: '#000000'
},
dropdownContainerHeader: {
height: 47,
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: COLOR_SEPARATOR,
alignItems: 'center',
backgroundColor: isIOS ? COLOR_WHITE : '#54585E',
flexDirection: 'row'
},
dropdownItemButton: {
height: 57,
justifyContent: 'center'
},
dropdownItemContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
dropdownItemText: {
fontSize: 18,
flex: 1,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular
},
dropdownItemDescription: {
fontSize: 14,
flex: 1,
marginTop: 2,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular
},
dropdownToggleText: {
fontSize: 15,
flex: 1,
marginLeft: 15,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular
},
dropdownItemIcon: {
width: 22,
height: 22,
marginHorizontal: 15,
...sharedStyles.textColorDescription
},
dropdownSeparator: {
height: StyleSheet.hairlineWidth,
backgroundColor: COLOR_SEPARATOR,
marginHorizontal: 15,
flex: 1
},
directoryItemButton: {
height: 54,
backgroundColor: COLOR_WHITE
},
directoryItemContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 15
},
directoryItemAvatar: {
marginRight: 12
},
directoryItemTextTitle: {
flexDirection: 'row',
alignItems: 'center'
},
directoryItemTextContainer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
},
directoryItemName: {
flex: 1,
fontSize: 17,
...sharedStyles.textMedium,
...sharedStyles.textColorNormal
},
directoryItemUsername: {
fontSize: 14,
...sharedStyles.textRegular,
...sharedStyles.textColorDescription
},
directoryItemLabel: {
fontSize: 14,
paddingLeft: 10,
...sharedStyles.textRegular,
...sharedStyles.textColorDescription
},
inverted: {
transform: [{ scaleY: -1 }]
},
globalUsersContainer: {
padding: 15
},
globalUsersTextContainer: {
flex: 1,
flexDirection: 'column'
}
});

View File

@ -40,7 +40,8 @@ const styles = StyleSheet.create({
}, },
createChannelIcon: { createChannelIcon: {
color: COLOR_PRIMARY, color: COLOR_PRIMARY,
marginHorizontal: 18 marginLeft: 18,
marginRight: 15
}, },
createChannelText: { createChannelText: {
color: COLOR_PRIMARY, color: COLOR_PRIMARY,

View File

@ -1,8 +0,0 @@
import React from 'react';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
const Check = React.memo(() => <CustomIcon style={styles.sortIcon} size={22} name='check' />);
export default Check;

View File

@ -0,0 +1,30 @@
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';
import DisclosureIndicator from '../../../containers/DisclosureIndicator';
const Directory = React.memo(({ goDirectory }) => (
<Touch
key='rooms-list-view-sort'
onPress={goDirectory}
style={styles.dropdownContainerHeader}
>
<View style={styles.sortItemContainer}>
<CustomIcon style={styles.directoryIcon} size={22} name='discover' />
<Text style={styles.directoryText}>{I18n.t('Directory')}</Text>
<DisclosureIndicator />
</View>
</Touch>
));
Directory.propTypes = {
goDirectory: PropTypes.func
};
export default Directory;

View File

@ -2,13 +2,15 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
import Directory from './Directory';
import Sort from './Sort'; import Sort from './Sort';
const ListHeader = React.memo(({ const ListHeader = React.memo(({
searchLength, sortBy, onChangeSearchText, toggleSort searchLength, sortBy, onChangeSearchText, toggleSort, goDirectory
}) => ( }) => (
<React.Fragment> <React.Fragment>
<SearchBar onChangeSearchText={onChangeSearchText} /> <SearchBar onChangeSearchText={onChangeSearchText} />
<Directory goDirectory={goDirectory} />
<Sort searchLength={searchLength} sortBy={sortBy} toggleSort={toggleSort} /> <Sort searchLength={searchLength} sortBy={sortBy} toggleSort={toggleSort} />
</React.Fragment> </React.Fragment>
)); ));
@ -17,7 +19,8 @@ ListHeader.propTypes = {
searchLength: PropTypes.number, searchLength: PropTypes.number,
sortBy: PropTypes.string, sortBy: PropTypes.string,
onChangeSearchText: PropTypes.func, onChangeSearchText: PropTypes.func,
toggleSort: PropTypes.func toggleSort: PropTypes.func,
goDirectory: PropTypes.func
}; };
export default ListHeader; export default ListHeader;

View File

@ -16,7 +16,7 @@ import Touch from '../../utils/touch';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n'; import I18n from '../../i18n';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import Check from './Check'; import Check from '../../containers/Check';
const ROW_HEIGHT = 68; const ROW_HEIGHT = 68;
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;

View File

@ -12,7 +12,7 @@ import { setPreference } from '../../actions/sortPreferences';
import log from '../../utils/log'; import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import Check from './Check'; import Check from '../../containers/Check';
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
@ -106,7 +106,7 @@ export default class Sort extends PureComponent {
render() { render() {
const translateY = this.animatedValue.interpolate({ const translateY = this.animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-245, 41] outputRange: [-326, 0]
}); });
const backdropOpacity = this.animatedValue.interpolate({ const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
@ -117,14 +117,24 @@ export default class Sort extends PureComponent {
} = this.props; } = this.props;
return ( return (
[ <React.Fragment>
<TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}> <TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}>
<Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]} /> <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]} />
</TouchableWithoutFeedback>, </TouchableWithoutFeedback>
<Animated.View <Animated.View
key='sort-container' key='sort-container'
style={[styles.dropdownContainer, { transform: [{ translateY }] }]} style={[styles.dropdownContainer, { transform: [{ translateY }] }]}
> >
<Touch
key='sort-toggle'
onPress={this.close}
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>
<Touch key='sort-alphabetical' style={styles.sortItemButton} onPress={this.sortByName}> <Touch key='sort-alphabetical' style={styles.sortItemButton} onPress={this.sortByName}>
<View style={styles.sortItemContainer}> <View style={styles.sortItemContainer}>
<CustomIcon style={styles.sortIcon} size={22} name='sort' /> <CustomIcon style={styles.sortIcon} size={22} name='sort' />
@ -161,18 +171,8 @@ export default class Sort extends PureComponent {
{showUnread ? <Check /> : null} {showUnread ? <Check /> : null}
</View> </View>
</Touch> </Touch>
</Animated.View>, </Animated.View>
<Touch </React.Fragment>
key='sort-toggle'
onPress={this.close}
style={[styles.dropdownContainerHeader, styles.sortToggleContainerClose]}
>
<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>
]
); );
} }
} }

View File

@ -379,6 +379,11 @@ export default class RoomsListView extends React.Component {
}, 100); }, 100);
} }
goDirectory = () => {
const { navigation } = this.props;
navigation.navigate('DirectoryView');
}
getScrollRef = ref => this.scroll = ref getScrollRef = ref => this.scroll = ref
renderListHeader = () => { renderListHeader = () => {
@ -390,6 +395,7 @@ export default class RoomsListView extends React.Component {
sortBy={sortBy} sortBy={sortBy}
onChangeSearchText={this.search} onChangeSearchText={this.search}
toggleSort={this.toggleSort} toggleSort={this.toggleSort}
goDirectory={this.goDirectory}
/> />
); );
} }

View File

@ -1,7 +1,7 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { import {
COLOR_SEPARATOR, COLOR_TEXT, COLOR_PRIMARY, COLOR_WHITE COLOR_SEPARATOR, COLOR_TEXT, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
} from '../../constants/colors'; } from '../../constants/colors';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
@ -147,5 +147,17 @@ export default StyleSheet.create({
height: StyleSheet.hairlineWidth, height: StyleSheet.hairlineWidth,
backgroundColor: COLOR_SEPARATOR, backgroundColor: COLOR_SEPARATOR,
marginLeft: 72 marginLeft: 72
},
directoryIcon: {
width: 22,
height: 22,
marginHorizontal: 15,
color: isIOS ? COLOR_PRIMARY : COLOR_TEXT_DESCRIPTION
},
directoryText: {
fontSize: 15,
flex: 1,
color: isIOS ? COLOR_PRIMARY : COLOR_TEXT_DESCRIPTION,
...sharedStyles.textRegular
} }
}); });