[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:
parent
4382eca8b6
commit
b7e6d3615f
|
@ -14,6 +14,9 @@ export default {
|
|||
CROWD_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
FEDERATION_Enabled: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
LDAP_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
|
|
|
@ -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;
|
|
@ -34,7 +34,7 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const SearchBox = ({ onChangeText, testID }) => (
|
||||
const SearchBox = ({ onChangeText, onSubmitEditing, testID }) => (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.searchBox}>
|
||||
<CustomIcon name='magnifier' size={14} color='#8E8E93' />
|
||||
|
@ -49,6 +49,7 @@ const SearchBox = ({ onChangeText, testID }) => (
|
|||
testID={testID}
|
||||
underlineColorAndroid='transparent'
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -56,6 +57,7 @@ const SearchBox = ({ onChangeText, testID }) => (
|
|||
|
||||
SearchBox.propTypes = {
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onSubmitEditing: PropTypes.func,
|
||||
testID: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -142,9 +142,10 @@ export default {
|
|||
DELETE: 'DELETE',
|
||||
description: 'description',
|
||||
Description: 'Description',
|
||||
Directory: 'Directory',
|
||||
Direct_Messages: 'Direct Messages',
|
||||
Disable_notifications: 'Disable notifications',
|
||||
Discussions: 'Discussions',
|
||||
Direct_Messages: 'Direct Messages',
|
||||
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?',
|
||||
edit: 'edit',
|
||||
|
@ -294,6 +295,9 @@ export default {
|
|||
saving_settings: 'saving settings',
|
||||
Search_Messages: 'Search Messages',
|
||||
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_Users: 'Select Users',
|
||||
Send: 'Send',
|
||||
|
@ -348,6 +352,7 @@ export default {
|
|||
Updating: 'Updating...',
|
||||
Uploading: 'Uploading',
|
||||
Upload_file_question_mark: 'Upload file?',
|
||||
Users: 'Users',
|
||||
User_added_by: 'User {{userAdded}} added by {{userBy}}',
|
||||
User_has_been_key: 'User has been {{key}}!',
|
||||
User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}',
|
||||
|
|
|
@ -146,11 +146,12 @@ export default {
|
|||
delete: 'excluir',
|
||||
Delete: 'Excluir',
|
||||
DELETE: 'EXCLUIR',
|
||||
Direct_Messages: 'Mensagens Diretas',
|
||||
Directory: 'Diretório',
|
||||
description: 'descrição',
|
||||
Description: 'Descrição',
|
||||
Disable_notifications: 'Desabilitar notificações',
|
||||
Discussions: 'Discussões',
|
||||
Direct_Messages: 'Mensagens Diretas',
|
||||
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?',
|
||||
edit: 'editar',
|
||||
|
@ -293,6 +294,9 @@ export default {
|
|||
saving_settings: 'salvando configurações',
|
||||
Search_Messages: 'Buscar Mensagens',
|
||||
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_Users: 'Selecionar Usuários',
|
||||
Send: 'Enviar',
|
||||
|
@ -344,6 +348,7 @@ export default {
|
|||
Updating: 'Atualizando...',
|
||||
Uploading: 'Subindo arquivo',
|
||||
Upload_file_question_mark: 'Enviar arquivo?',
|
||||
Users: 'Usuários',
|
||||
User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}',
|
||||
User_has_been_key: 'Usuário foi {{key}}!',
|
||||
User_is_no_longer_role_by_: '{{user}} não pertence mais à {{role}} por {{userBy}}',
|
||||
|
|
|
@ -16,6 +16,7 @@ import AuthLoadingView from './views/AuthLoadingView';
|
|||
import RoomsListView from './views/RoomsListView';
|
||||
import RoomView from './views/RoomView';
|
||||
import NewMessageView from './views/NewMessageView';
|
||||
import DirectoryView from './views/DirectoryView';
|
||||
import LoginView from './views/LoginView';
|
||||
import Navigation from './lib/Navigation';
|
||||
import Sidebar from './views/SidebarView';
|
||||
|
@ -110,7 +111,8 @@ const ChatsStack = createStackNavigator({
|
|||
SearchMessagesView,
|
||||
SelectedUsersView,
|
||||
ThreadMessagesView,
|
||||
MessagesView
|
||||
MessagesView,
|
||||
DirectoryView
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
|||
import reduxStore from './createStore';
|
||||
import defaultSettings from '../constants/settings';
|
||||
import messagesStatus from '../constants/messagesStatus';
|
||||
import database, { safeAddListener } from './realm';
|
||||
import database from './realm';
|
||||
import log from '../utils/log';
|
||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||
import EventEmitter from '../utils/events';
|
||||
|
@ -57,23 +57,6 @@ const RocketChat = {
|
|||
// RC 0.51.0
|
||||
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() {
|
||||
try {
|
||||
return await AsyncStorage.getItem(TOKEN_KEY);
|
||||
|
@ -849,6 +832,14 @@ const RocketChat = {
|
|||
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
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -40,7 +40,8 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
createChannelIcon: {
|
||||
color: COLOR_PRIMARY,
|
||||
marginHorizontal: 18
|
||||
marginLeft: 18,
|
||||
marginRight: 15
|
||||
},
|
||||
createChannelText: {
|
||||
color: COLOR_PRIMARY,
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -2,13 +2,15 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import SearchBar from './SearchBar';
|
||||
import Directory from './Directory';
|
||||
import Sort from './Sort';
|
||||
|
||||
const ListHeader = React.memo(({
|
||||
searchLength, sortBy, onChangeSearchText, toggleSort
|
||||
searchLength, sortBy, onChangeSearchText, toggleSort, goDirectory
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
<SearchBar onChangeSearchText={onChangeSearchText} />
|
||||
<Directory goDirectory={goDirectory} />
|
||||
<Sort searchLength={searchLength} sortBy={sortBy} toggleSort={toggleSort} />
|
||||
</React.Fragment>
|
||||
));
|
||||
|
@ -17,7 +19,8 @@ ListHeader.propTypes = {
|
|||
searchLength: PropTypes.number,
|
||||
sortBy: PropTypes.string,
|
||||
onChangeSearchText: PropTypes.func,
|
||||
toggleSort: PropTypes.func
|
||||
toggleSort: PropTypes.func,
|
||||
goDirectory: PropTypes.func
|
||||
};
|
||||
|
||||
export default ListHeader;
|
||||
|
|
|
@ -16,7 +16,7 @@ import Touch from '../../utils/touch';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import Check from './Check';
|
||||
import Check from '../../containers/Check';
|
||||
|
||||
const ROW_HEIGHT = 68;
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { setPreference } from '../../actions/sortPreferences';
|
|||
import log from '../../utils/log';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import Check from './Check';
|
||||
import Check from '../../containers/Check';
|
||||
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
||||
|
@ -106,7 +106,7 @@ export default class Sort extends PureComponent {
|
|||
render() {
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-245, 41]
|
||||
outputRange: [-326, 0]
|
||||
});
|
||||
const backdropOpacity = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
|
@ -117,14 +117,24 @@ export default class Sort extends PureComponent {
|
|||
} = this.props;
|
||||
|
||||
return (
|
||||
[
|
||||
<React.Fragment>
|
||||
<TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}>
|
||||
<Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]} />
|
||||
</TouchableWithoutFeedback>,
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
key='sort-container'
|
||||
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}>
|
||||
<View style={styles.sortItemContainer}>
|
||||
<CustomIcon style={styles.sortIcon} size={22} name='sort' />
|
||||
|
@ -161,18 +171,8 @@ export default class Sort extends PureComponent {
|
|||
{showUnread ? <Check /> : null}
|
||||
</View>
|
||||
</Touch>
|
||||
</Animated.View>,
|
||||
<Touch
|
||||
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>
|
||||
]
|
||||
</Animated.View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -379,6 +379,11 @@ export default class RoomsListView extends React.Component {
|
|||
}, 100);
|
||||
}
|
||||
|
||||
goDirectory = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('DirectoryView');
|
||||
}
|
||||
|
||||
getScrollRef = ref => this.scroll = ref
|
||||
|
||||
renderListHeader = () => {
|
||||
|
@ -390,6 +395,7 @@ export default class RoomsListView extends React.Component {
|
|||
sortBy={sortBy}
|
||||
onChangeSearchText={this.search}
|
||||
toggleSort={this.toggleSort}
|
||||
goDirectory={this.goDirectory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import {
|
||||
COLOR_SEPARATOR, COLOR_TEXT, COLOR_PRIMARY, COLOR_WHITE
|
||||
COLOR_SEPARATOR, COLOR_TEXT, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
|
||||
} from '../../constants/colors';
|
||||
|
||||
import sharedStyles from '../Styles';
|
||||
|
@ -147,5 +147,17 @@ export default StyleSheet.create({
|
|||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
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
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue