parent
d991fd99bc
commit
c84c5a3565
|
@ -35,15 +35,16 @@ exports[`render channel 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"borderRadius": 4,
|
|
||||||
"height": 40,
|
|
||||||
"justifyContent": "center",
|
"justifyContent": "center",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
"width": 40,
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#00BCD4",
|
"backgroundColor": "#00BCD4",
|
||||||
|
"borderRadius": 4,
|
||||||
|
"height": 40,
|
||||||
|
"width": 40,
|
||||||
},
|
},
|
||||||
|
undefined,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -57,10 +58,14 @@ exports[`render channel 1`] = `
|
||||||
"color": undefined,
|
"color": undefined,
|
||||||
"fontSize": 12,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"color": "#fff",
|
Object {
|
||||||
"fontSize": 20,
|
"color": "#ffffff",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"fontSize": 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"fontFamily": "Material Design Icons",
|
"fontFamily": "Material Design Icons",
|
||||||
"fontStyle": "normal",
|
"fontStyle": "normal",
|
||||||
|
@ -151,6 +156,53 @@ exports[`render no icon 1`] = `
|
||||||
testID={undefined}
|
testID={undefined}
|
||||||
tvParallaxProperties={undefined}
|
tvParallaxProperties={undefined}
|
||||||
>
|
>
|
||||||
|
<View
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"justifyContent": "center",
|
||||||
|
"overflow": "hidden",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#3F51B5",
|
||||||
|
"borderRadius": 4,
|
||||||
|
"height": 40,
|
||||||
|
"width": 40,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
accessible={true}
|
||||||
|
allowFontScaling={false}
|
||||||
|
ellipsizeMode="tail"
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"color": undefined,
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"color": "#ffffff",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fontSize": 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Object {
|
||||||
|
"fontFamily": "Material Design Icons",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
@ -230,6 +282,53 @@ exports[`render private group 1`] = `
|
||||||
testID={undefined}
|
testID={undefined}
|
||||||
tvParallaxProperties={undefined}
|
tvParallaxProperties={undefined}
|
||||||
>
|
>
|
||||||
|
<View
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"alignItems": "center",
|
||||||
|
"justifyContent": "center",
|
||||||
|
"overflow": "hidden",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#FF9800",
|
||||||
|
"borderRadius": 4,
|
||||||
|
"height": 40,
|
||||||
|
"width": 40,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
accessible={true}
|
||||||
|
allowFontScaling={false}
|
||||||
|
ellipsizeMode="tail"
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"color": undefined,
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"color": "#ffffff",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fontSize": 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Object {
|
||||||
|
"fontFamily": "Material Design Icons",
|
||||||
|
"fontStyle": "normal",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
|
||||||
'INIT'
|
'INIT'
|
||||||
]);
|
]);
|
||||||
export const USER = createRequestTypes('USER', ['SET']);
|
export const USER = createRequestTypes('USER', ['SET']);
|
||||||
export const ROOMS = createRequestTypes('ROOMS');
|
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'SET_SEARCH']);
|
||||||
export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'SOMEONE_TYPING', 'OPEN', 'USER_TYPING']);
|
export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'SOMEONE_TYPING', 'OPEN', 'USER_TYPING']);
|
||||||
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
|
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', [
|
export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||||
|
@ -75,6 +75,7 @@ export const SERVER = createRequestTypes('SERVER', [
|
||||||
]);
|
]);
|
||||||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||||
|
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
|
||||||
|
|
||||||
export const INCREMENT = 'INCREMENT';
|
export const INCREMENT = 'INCREMENT';
|
||||||
export const DECREMENT = 'DECREMENT';
|
export const DECREMENT = 'DECREMENT';
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function requestActiveUser(user) {
|
||||||
|
return {
|
||||||
|
type: types.ACTIVE_USERS.REQUEST,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setActiveUser(data) {
|
||||||
|
return {
|
||||||
|
type: types.ACTIVE_USERS.SET,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
|
@ -19,3 +19,10 @@ export function roomsFailure(err) {
|
||||||
err
|
err
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSearch(searchText) {
|
||||||
|
return {
|
||||||
|
type: types.ROOMS.SET_SEARCH,
|
||||||
|
searchText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { CachedImage } from 'react-native-img-cache';
|
import { CachedImage } from 'react-native-img-cache';
|
||||||
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -21,7 +22,7 @@ const styles = StyleSheet.create({
|
||||||
class Avatar extends React.PureComponent {
|
class Avatar extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar
|
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||||
|
|
||||||
|
@ -42,19 +43,32 @@ class Avatar extends React.PureComponent {
|
||||||
borderRadius
|
borderRadius
|
||||||
};
|
};
|
||||||
|
|
||||||
const uri = avatar || `${ baseUrl }/avatar/${ text }`;
|
if (type === 'd') {
|
||||||
const image = (avatar || baseUrl) && (
|
const uri = avatar || `${ baseUrl }/avatar/${ text }`;
|
||||||
<CachedImage
|
const image = (avatar || baseUrl) && (
|
||||||
style={[styles.avatar, avatarStyle]}
|
<CachedImage
|
||||||
source={{ uri }}
|
style={[styles.avatar, avatarStyle]}
|
||||||
/>
|
source={{ uri }}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
||||||
|
<Text style={[styles.avatarInitials, avatarInitialsStyle]}>{initials}</Text>
|
||||||
|
{image}
|
||||||
|
</View>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = {
|
||||||
|
c: 'pound',
|
||||||
|
p: 'lock',
|
||||||
|
l: 'account'
|
||||||
|
}[type];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
||||||
<Text style={[styles.avatarInitials, avatarInitialsStyle]}>{initials}</Text>
|
<MaterialCommunityIcons name={icon} style={[styles.avatarInitials, avatarInitialsStyle]} />
|
||||||
{image}
|
</View>
|
||||||
</View>);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +78,7 @@ Avatar.propTypes = {
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
borderRadius: PropTypes.number
|
borderRadius: PropTypes.number,
|
||||||
|
type: PropTypes.string
|
||||||
};
|
};
|
||||||
export default Avatar;
|
export default Avatar;
|
||||||
|
|
|
@ -1,18 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, StyleSheet, Platform, TouchableOpacity, Dimensions } from 'react-native';
|
import { View, StyleSheet, Platform } from 'react-native';
|
||||||
import Icon from 'react-native-vector-icons/Ionicons';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Modal from 'react-native-modal';
|
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
|
||||||
import DrawerMenuButton from '../presentation/DrawerMenuButton';
|
|
||||||
import Avatar from './Avatar';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
|
||||||
import { STATUS_COLORS } from '../constants/colors';
|
|
||||||
|
|
||||||
const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
|
|
||||||
|
|
||||||
let platformContainerStyles;
|
let platformContainerStyles;
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
platformContainerStyles = {
|
platformContainerStyles = {
|
||||||
|
@ -32,7 +22,6 @@ if (Platform.OS === 'ios') {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appBarHeight = Platform.OS === 'ios' ? 44 : 56;
|
const appBarHeight = Platform.OS === 'ios' ? 44 : 56;
|
||||||
const { width } = Dimensions.get('window');
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
||||||
|
@ -41,171 +30,20 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
appBar: {
|
appBar: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
left: TITLE_OFFSET,
|
|
||||||
right: TITLE_OFFSET,
|
|
||||||
position: 'absolute',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
borderRadius: 4,
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
marginRight: 10
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
marginRight: 10
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
|
||||||
left: {
|
|
||||||
left: 0,
|
|
||||||
position: 'absolute'
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
right: 0,
|
|
||||||
position: 'absolute'
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
width: width - 60,
|
|
||||||
height: width - 60,
|
|
||||||
backgroundColor: '#F7F7F7',
|
|
||||||
borderRadius: 4,
|
|
||||||
flexDirection: 'column'
|
|
||||||
},
|
|
||||||
modalButton: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
||||||
borderBottomColor: 'rgba(0, 0, 0, .3)',
|
|
||||||
paddingHorizontal: 20
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(state => ({
|
|
||||||
user: state.login.user,
|
|
||||||
Site_Url: state.settings.Site_Url
|
|
||||||
}))
|
|
||||||
export default class extends React.PureComponent {
|
export default class extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object.isRequired,
|
subview: PropTypes.object.isRequired
|
||||||
user: PropTypes.object.isRequired,
|
|
||||||
Site_Url: PropTypes.string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isModalVisible: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressModalButton(status) {
|
|
||||||
RocketChat.setUserPresenceDefaultStatus(status);
|
|
||||||
this.hideModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal() {
|
|
||||||
this.setState({ isModalVisible: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
hideModal() {
|
|
||||||
this.setState({ isModalVisible: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
createChannel() {
|
|
||||||
this.props.navigation.navigate('SelectUsers');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTitle() {
|
|
||||||
if (!this.props.user.username) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TouchableOpacity style={styles.titleContainer} onPress={() => this.showModal()}>
|
|
||||||
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.props.user.status || 'offline'] }]} />
|
|
||||||
<Avatar
|
|
||||||
text={this.props.user.username}
|
|
||||||
size={24}
|
|
||||||
style={{ marginRight: 5 }}
|
|
||||||
baseUrl={this.props.Site_Url}
|
|
||||||
/>
|
|
||||||
<Text style={styles.title}>{this.props.user.username}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRight() {
|
|
||||||
if (Platform.OS !== 'ios') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.right}>
|
|
||||||
<Icon.Button
|
|
||||||
name='ios-create-outline'
|
|
||||||
color='blue'
|
|
||||||
size={26}
|
|
||||||
backgroundColor='transparent'
|
|
||||||
onPress={() => this.createChannel()}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderModalButton = (status, text) => {
|
|
||||||
const statusStyle = [styles.status, { backgroundColor: STATUS_COLORS[status] }];
|
|
||||||
const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' };
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.modalButton}
|
|
||||||
onPress={() => this.onPressModalButton(status)}
|
|
||||||
>
|
|
||||||
<View style={statusStyle} />
|
|
||||||
<Text style={textStyle}>
|
|
||||||
{text || status.charAt(0).toUpperCase() + status.slice(1)}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.appBar}>
|
<View style={styles.appBar}>
|
||||||
<View style={styles.header}>
|
{this.props.subview}
|
||||||
<View style={styles.left}>
|
|
||||||
<DrawerMenuButton navigation={this.props.navigation} />
|
|
||||||
</View>
|
|
||||||
{this.renderTitle()}
|
|
||||||
{this.renderRight()}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
<Modal
|
|
||||||
isVisible={this.state.isModalVisible}
|
|
||||||
supportedOrientations={['portrait', 'landscape']}
|
|
||||||
style={{ alignItems: 'center' }}
|
|
||||||
onModalHide={() => this.hideModal()}
|
|
||||||
onBackdropPress={() => this.hideModal()}
|
|
||||||
>
|
|
||||||
<View style={styles.modal}>
|
|
||||||
{this.renderModalButton('online')}
|
|
||||||
{this.renderModalButton('busy')}
|
|
||||||
{this.renderModalButton('away')}
|
|
||||||
{this.renderModalButton('offline', 'Invisible')}
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,19 @@
|
||||||
import React from 'react';
|
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import { StackNavigator, DrawerNavigator } from 'react-navigation';
|
import { StackNavigator, DrawerNavigator } from 'react-navigation';
|
||||||
|
|
||||||
import Sidebar from '../../containers/Sidebar';
|
import Sidebar from '../../containers/Sidebar';
|
||||||
import DrawerMenuButton from '../../presentation/DrawerMenuButton';
|
|
||||||
import Header from '../../containers/Header';
|
|
||||||
|
|
||||||
import RoomsListView from '../../views/RoomsListView';
|
import RoomsListView from '../../views/RoomsListView';
|
||||||
import RoomView from '../../views/RoomView';
|
import RoomView from '../../views/RoomView';
|
||||||
import CreateChannelView from '../../views/CreateChannelView';
|
import CreateChannelView from '../../views/CreateChannelView';
|
||||||
import SelectUsersView from '../../views/SelectUsersView';
|
import SelectUsersView from '../../views/SelectUsersView';
|
||||||
|
|
||||||
const drawerPosition = 'left';
|
|
||||||
const drawerIconPosition = 'headerLeft';
|
|
||||||
|
|
||||||
const AuthRoutes = StackNavigator(
|
const AuthRoutes = StackNavigator(
|
||||||
{
|
{
|
||||||
RoomsList: {
|
RoomsList: {
|
||||||
screen: RoomsListView,
|
screen: RoomsListView
|
||||||
navigationOptions({ navigation }) {
|
|
||||||
return {
|
|
||||||
title: 'Rooms',
|
|
||||||
header: <Header navigation={navigation} />
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Room: {
|
Room: {
|
||||||
screen: RoomView,
|
screen: RoomView
|
||||||
navigationOptions({ navigation }) {
|
|
||||||
return {
|
|
||||||
title: navigation.state.params.name || navigation.state.params.room.name || 'Room'
|
|
||||||
// [drawerIconPosition]: (<DrawerMenuButton navigation={navigation} />)÷
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
CreateChannel: {
|
CreateChannel: {
|
||||||
screen: CreateChannelView,
|
screen: CreateChannelView,
|
||||||
|
@ -53,18 +34,11 @@ const AuthRoutes = StackNavigator(
|
||||||
const Routes = DrawerNavigator(
|
const Routes = DrawerNavigator(
|
||||||
{
|
{
|
||||||
Home: {
|
Home: {
|
||||||
screen: AuthRoutes,
|
screen: AuthRoutes
|
||||||
navigationOptions({ navigation }) {
|
|
||||||
return {
|
|
||||||
title: 'Rooms',
|
|
||||||
[drawerIconPosition]: <DrawerMenuButton navigation={navigation} />
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
contentComponent: Sidebar,
|
contentComponent: Sidebar,
|
||||||
drawerPosition,
|
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
drawerLockMode: Platform.OS === 'ios' ? 'locked-closed' : 'unlocked'
|
drawerLockMode: Platform.OS === 'ios' ? 'locked-closed' : 'unlocked'
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as actions from '../actions';
|
||||||
import { someoneTyping } from '../actions/room';
|
import { someoneTyping } from '../actions/room';
|
||||||
import { setUser } from '../actions/login';
|
import { setUser } from '../actions/login';
|
||||||
import { disconnect, connectSuccess } from '../actions/connect';
|
import { disconnect, connectSuccess } from '../actions/connect';
|
||||||
|
import { requestActiveUser } from '../actions/activeUsers';
|
||||||
|
|
||||||
export { Accounts } from 'react-native-meteor';
|
export { Accounts } from 'react-native-meteor';
|
||||||
|
|
||||||
|
@ -47,6 +48,23 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
throw new Error({ error: 'invalid server' });
|
throw new Error({ error: 'invalid server' });
|
||||||
},
|
},
|
||||||
|
_setUser(ddpMessage) {
|
||||||
|
let status;
|
||||||
|
if (!ddpMessage.fields) {
|
||||||
|
status = 'offline';
|
||||||
|
} else {
|
||||||
|
status = ddpMessage.fields.status || 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = reduxStore.getState().login;
|
||||||
|
if (user && user.id === ddpMessage.id) {
|
||||||
|
return reduxStore.dispatch(setUser({ status }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeUser = {};
|
||||||
|
activeUser[ddpMessage.id] = status;
|
||||||
|
return reduxStore.dispatch(requestActiveUser(activeUser));
|
||||||
|
},
|
||||||
connect(_url) {
|
connect(_url) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const url = `${ _url }/websocket`;
|
const url = `${ _url }/websocket`;
|
||||||
|
@ -63,6 +81,16 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.ddp.on('connected', async() => {
|
Meteor.ddp.on('connected', async() => {
|
||||||
|
Meteor.ddp.on('added', (ddpMessage) => {
|
||||||
|
if (ddpMessage.collection === 'users') {
|
||||||
|
return RocketChat._setUser(ddpMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Meteor.ddp.on('removed', (ddpMessage) => {
|
||||||
|
if (ddpMessage.collection === 'users') {
|
||||||
|
return RocketChat._setUser(ddpMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
Meteor.ddp.on('changed', (ddpMessage) => {
|
Meteor.ddp.on('changed', (ddpMessage) => {
|
||||||
if (ddpMessage.collection === 'stream-room-messages') {
|
if (ddpMessage.collection === 'stream-room-messages') {
|
||||||
return realm.write(() => {
|
return realm.write(() => {
|
||||||
|
@ -96,7 +124,7 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ddpMessage.collection === 'users') {
|
if (ddpMessage.collection === 'users') {
|
||||||
return reduxStore.dispatch(setUser({ status: ddpMessage.fields.status || ddpMessage.fields.statusDefault }));
|
return RocketChat._setUser(ddpMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RocketChat.getSettings();
|
RocketChat.getSettings();
|
||||||
|
@ -436,7 +464,7 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
|
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
|
||||||
Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
|
Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
|
||||||
Meteor.subscribe('userData', null, false);
|
Meteor.subscribe('activeUsers', null, false);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
logout({ server }) {
|
logout({ server }) {
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { TouchableOpacity } from 'react-native';
|
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
|
||||||
|
|
||||||
const DrawerMenuButton = ({ navigation }) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => navigation.navigate('DrawerOpen')}
|
|
||||||
>
|
|
||||||
<Icon name='bars' style={{ padding: 10, marginLeft: 10 }} size={20} color='black' />
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawerMenuButton.propTypes = {
|
|
||||||
navigation: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DrawerMenuButton;
|
|
|
@ -1,10 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
import Avatar from '../containers/Avatar';
|
import Avatar from '../containers/Avatar';
|
||||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -49,22 +47,6 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
color: '#888'
|
color: '#888'
|
||||||
},
|
|
||||||
iconContainer: {
|
|
||||||
height: 40,
|
|
||||||
width: 40,
|
|
||||||
borderRadius: 4,
|
|
||||||
overflow: 'hidden',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
fontSize: 20,
|
|
||||||
color: '#fff'
|
|
||||||
},
|
|
||||||
avatarInitials: {
|
|
||||||
fontSize: 20,
|
|
||||||
color: '#ffffff'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,30 +65,7 @@ export default class RoomItem extends React.PureComponent {
|
||||||
|
|
||||||
get icon() {
|
get icon() {
|
||||||
const { type, name, baseUrl } = this.props;
|
const { type, name, baseUrl } = this.props;
|
||||||
|
return <Avatar text={name} baseUrl={baseUrl} size={40} type={type} />;
|
||||||
const icon = {
|
|
||||||
d: 'at',
|
|
||||||
c: 'pound',
|
|
||||||
p: 'lock',
|
|
||||||
l: 'account'
|
|
||||||
}[type];
|
|
||||||
|
|
||||||
if (!icon) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'd') {
|
|
||||||
return (
|
|
||||||
<Avatar text={name} baseUrl={baseUrl} size={40} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { color } = avatarInitialsAndColor(name);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.iconContainer, { backgroundColor: color }]}>
|
|
||||||
<MaterialCommunityIcons name={icon} style={styles.icon} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDate = date => moment(date).calendar(null, {
|
formatDate = date => moment(date).calendar(null, {
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as types from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {};
|
||||||
|
|
||||||
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case types.ACTIVE_USERS.SET:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.data
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,12 +4,14 @@ import login from './login';
|
||||||
import meteor from './connect';
|
import meteor from './connect';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import room from './room';
|
import room from './room';
|
||||||
|
import rooms from './rooms';
|
||||||
import server from './server';
|
import server from './server';
|
||||||
import navigator from './navigator';
|
import navigator from './navigator';
|
||||||
import createChannel from './createChannel';
|
import createChannel from './createChannel';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import permissions from './permissions';
|
import permissions from './permissions';
|
||||||
|
import activeUsers from './activeUsers';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings, login, meteor, messages, server, navigator, createChannel, app, room, permissions
|
settings, login, meteor, messages, server, navigator, createChannel, app, room, rooms, permissions, activeUsers
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,8 @@ import * as types from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
failure: false
|
failure: false,
|
||||||
|
searchText: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function login(state = initialState, action) {
|
export default function login(state = initialState, action) {
|
||||||
|
@ -24,8 +25,11 @@ export default function login(state = initialState, action) {
|
||||||
failure: true,
|
failure: true,
|
||||||
errorMessage: action.err
|
errorMessage: action.err
|
||||||
};
|
};
|
||||||
// case types.LOGOUT:
|
case types.ROOMS.SET_SEARCH:
|
||||||
// return initialState;
|
return {
|
||||||
|
...state,
|
||||||
|
searchText: action.searchText
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { put, take, race, fork } from 'redux-saga/effects';
|
||||||
|
import { delay } from 'redux-saga';
|
||||||
|
import * as types from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
import { setActiveUser } from '../actions/activeUsers';
|
||||||
|
|
||||||
|
const watchActiveUsers = function* handleInput() {
|
||||||
|
let obj = {};
|
||||||
|
while (true) {
|
||||||
|
const { status, timeout } = yield race({
|
||||||
|
status: take(types.ACTIVE_USERS.REQUEST),
|
||||||
|
timeout: delay(3000)
|
||||||
|
});
|
||||||
|
if (timeout && Object.keys(obj).length > 0) {
|
||||||
|
yield put(setActiveUser(obj));
|
||||||
|
obj = {};
|
||||||
|
}
|
||||||
|
if (status) {
|
||||||
|
obj = {
|
||||||
|
...obj,
|
||||||
|
...status.user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = function* root() {
|
||||||
|
yield fork(watchActiveUsers);
|
||||||
|
};
|
||||||
|
export default root;
|
|
@ -8,6 +8,7 @@ import selectServer from './selectServer';
|
||||||
import createChannel from './createChannel';
|
import createChannel from './createChannel';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import state from './state';
|
import state from './state';
|
||||||
|
import activeUsers from './activeUsers';
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
|
@ -19,7 +20,8 @@ const root = function* root() {
|
||||||
connect(),
|
connect(),
|
||||||
messages(),
|
messages(),
|
||||||
selectServer(),
|
selectServer(),
|
||||||
state()
|
state(),
|
||||||
|
activeUsers()
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View, Platform, TouchableOpacity } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { HeaderBackButton } from 'react-navigation';
|
||||||
|
|
||||||
|
import realm from '../../../lib/realm';
|
||||||
|
import Avatar from '../../../containers/Avatar';
|
||||||
|
import { STATUS_COLORS } from '../../../constants/colors';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
user: state.login.user,
|
||||||
|
baseUrl: state.settings.Site_Url,
|
||||||
|
activeUsers: state.activeUsers
|
||||||
|
}))
|
||||||
|
export default class extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object.isRequired,
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
activeUsers: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
room: {},
|
||||||
|
roomName: props.navigation.state.params.name
|
||||||
|
};
|
||||||
|
this.rid = props.navigation.state.params.room.rid;
|
||||||
|
this.room = realm.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
this.room.addListener(this.updateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.updateState();
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.room.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserStatus() {
|
||||||
|
const userId = this.rid.replace(this.props.user.id, '').trim();
|
||||||
|
return this.props.activeUsers[userId] || 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserStatusLabel() {
|
||||||
|
const status = this.getUserStatus();
|
||||||
|
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState = () => {
|
||||||
|
this.setState({ room: this.room[0] });
|
||||||
|
};
|
||||||
|
|
||||||
|
isDirect = () => this.state.room && this.state.room.t === 'd';
|
||||||
|
|
||||||
|
renderLeft = () => <HeaderBackButton onPress={() => this.props.navigation.goBack(null)} tintColor='#292E35' />;
|
||||||
|
|
||||||
|
renderTitle() {
|
||||||
|
if (!this.state.roomName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={styles.titleContainer}>
|
||||||
|
{this.isDirect() ?
|
||||||
|
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.getUserStatus()] }]} />
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<Avatar
|
||||||
|
text={this.state.roomName}
|
||||||
|
size={24}
|
||||||
|
style={{ marginRight: 5 }}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
|
type={this.state.room.t}
|
||||||
|
/>
|
||||||
|
<View style={{ flexDirection: 'column' }}>
|
||||||
|
<Text style={styles.title}>{this.state.roomName}</Text>
|
||||||
|
{this.isDirect() ?
|
||||||
|
<Text style={styles.userStatus}>{this.getUserStatusLabel()}</Text>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRight = () => (
|
||||||
|
<View style={styles.right}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerButton}
|
||||||
|
onPress={() => {}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={Platform.OS === 'ios' ? 'ios-more' : 'md-more'}
|
||||||
|
color='#292E35'
|
||||||
|
size={24}
|
||||||
|
backgroundColor='transparent'
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.header}>
|
||||||
|
{this.renderLeft()}
|
||||||
|
{this.renderTitle()}
|
||||||
|
{this.renderRight()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
|
const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
|
||||||
|
export default StyleSheet.create({
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
left: TITLE_OFFSET,
|
||||||
|
right: TITLE_OFFSET,
|
||||||
|
position: 'absolute',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 44
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
borderRadius: 4,
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
userStatus: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#888'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#292E35'
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
left: 0,
|
||||||
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
right: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
headerButton: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
height: 44,
|
||||||
|
width: 44,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,54 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View, StyleSheet, Button, SafeAreaView } from 'react-native';
|
import { Text, View, Button, SafeAreaView } from 'react-native';
|
||||||
import { ListView } from 'realm/react-native';
|
import { ListView } from 'realm/react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../../actions';
|
||||||
import { openRoom } from '../actions/room';
|
import { openRoom } from '../../actions/room';
|
||||||
import { editCancel } from '../actions/messages';
|
import { editCancel } from '../../actions/messages';
|
||||||
import realm from '../lib/realm';
|
import realm from '../../lib/realm';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import Message from '../containers/message';
|
import Message from '../../containers/message';
|
||||||
import MessageActions from '../containers/MessageActions';
|
import MessageActions from '../../containers/MessageActions';
|
||||||
import MessageBox from '../containers/MessageBox';
|
import MessageBox from '../../containers/MessageBox';
|
||||||
import Typing from '../containers/Typing';
|
import Typing from '../../containers/Typing';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../../presentation/KeyboardView';
|
||||||
|
import Header from '../../containers/Header';
|
||||||
|
import RoomsHeader from './Header';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
||||||
const styles = StyleSheet.create({
|
|
||||||
typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff'
|
|
||||||
},
|
|
||||||
safeAreaView: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
flex: 1,
|
|
||||||
transform: [{ scaleY: -1 }]
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
height: 1,
|
|
||||||
backgroundColor: '#CED0CE'
|
|
||||||
},
|
|
||||||
bannerContainer: {
|
|
||||||
backgroundColor: 'orange'
|
|
||||||
},
|
|
||||||
bannerText: {
|
|
||||||
margin: 5,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#a00'
|
|
||||||
},
|
|
||||||
loadingMore: {
|
|
||||||
transform: [{ scaleY: -1 }],
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: 5,
|
|
||||||
color: '#ccc'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const typing = () => <Typing />;
|
const typing = () => <Typing />;
|
||||||
@connect(
|
@connect(
|
||||||
state => ({
|
state => ({
|
||||||
|
@ -78,6 +50,10 @@ export default class RoomView extends React.Component {
|
||||||
loading: PropTypes.bool
|
loading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static navigationOptions = ({ navigation }) => ({
|
||||||
|
header: <Header subview={<RoomsHeader navigation={navigation} />} />
|
||||||
|
});
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.rid =
|
this.rid =
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff'
|
||||||
|
},
|
||||||
|
safeAreaView: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
flex: 1,
|
||||||
|
transform: [{ scaleY: -1 }]
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: '#CED0CE'
|
||||||
|
},
|
||||||
|
bannerContainer: {
|
||||||
|
backgroundColor: 'orange'
|
||||||
|
},
|
||||||
|
bannerText: {
|
||||||
|
margin: 5,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#a00'
|
||||||
|
},
|
||||||
|
loadingMore: {
|
||||||
|
transform: [{ scaleY: -1 }],
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: 5,
|
||||||
|
color: '#ccc'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,210 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View, Platform, TouchableOpacity, TextInput } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Modal from 'react-native-modal';
|
||||||
|
import { CachedImage } from 'react-native-img-cache';
|
||||||
|
import { HeaderBackButton } from 'react-navigation';
|
||||||
|
|
||||||
|
import Avatar from '../../../containers/Avatar';
|
||||||
|
import RocketChat from '../../../lib/rocketchat';
|
||||||
|
import { STATUS_COLORS } from '../../../constants/colors';
|
||||||
|
import { setSearch } from '../../../actions/rooms';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
user: state.login.user,
|
||||||
|
baseUrl: state.settings.Site_Url
|
||||||
|
}), dispatch => ({
|
||||||
|
setSearch: searchText => dispatch(setSearch(searchText))
|
||||||
|
}))
|
||||||
|
export default class extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object.isRequired,
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
setSearch: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isModalVisible: false,
|
||||||
|
searching: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressModalButton(status) {
|
||||||
|
RocketChat.setUserPresenceDefaultStatus(status);
|
||||||
|
this.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchChangeText(text) {
|
||||||
|
this.props.setSearch(text.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressCancelSearchButton() {
|
||||||
|
this.setState({ searching: false });
|
||||||
|
this.props.setSearch('');
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressSearchButton() {
|
||||||
|
this.setState({ searching: true });
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.inputSearch.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal() {
|
||||||
|
this.setState({ isModalVisible: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
hideModal() {
|
||||||
|
this.setState({ isModalVisible: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
createChannel() {
|
||||||
|
this.props.navigation.navigate('SelectUsers');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLeft() {
|
||||||
|
return (
|
||||||
|
<View style={styles.left}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerButton}
|
||||||
|
onPress={() => this.props.navigation.navigate('DrawerOpen')}
|
||||||
|
>
|
||||||
|
<CachedImage
|
||||||
|
style={styles.serverImage}
|
||||||
|
source={{ uri: encodeURI(`${ this.props.baseUrl }/assets/favicon_32.png`) }}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle() {
|
||||||
|
if (!this.props.user.username) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={styles.titleContainer} onPress={() => this.showModal()}>
|
||||||
|
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.props.user.status || 'offline'] }]} />
|
||||||
|
<Avatar
|
||||||
|
text={this.props.user.username}
|
||||||
|
size={24}
|
||||||
|
style={{ marginRight: 5 }}
|
||||||
|
baseUrl={this.props.baseUrl}
|
||||||
|
/>
|
||||||
|
<Text style={styles.title}>{this.props.user.username}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRight() {
|
||||||
|
return (
|
||||||
|
<View style={styles.right}>
|
||||||
|
{Platform.OS === 'android' ?
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerButton}
|
||||||
|
onPress={() => this.onPressSearchButton()}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name='md-search'
|
||||||
|
color='#292E35'
|
||||||
|
size={24}
|
||||||
|
backgroundColor='transparent'
|
||||||
|
/>
|
||||||
|
</TouchableOpacity> : null}
|
||||||
|
{Platform.OS === 'ios' ?
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.headerButton}
|
||||||
|
onPress={() => this.createChannel()}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name='ios-add'
|
||||||
|
color='#292E35'
|
||||||
|
size={24}
|
||||||
|
backgroundColor='transparent'
|
||||||
|
/>
|
||||||
|
</TouchableOpacity> : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderModalButton = (status, text) => {
|
||||||
|
const statusStyle = [styles.status, { backgroundColor: STATUS_COLORS[status] }];
|
||||||
|
const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' };
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalButton}
|
||||||
|
onPress={() => this.onPressModalButton(status)}
|
||||||
|
>
|
||||||
|
<View style={statusStyle} />
|
||||||
|
<Text style={textStyle}>
|
||||||
|
{text || status.charAt(0).toUpperCase() + status.slice(1)}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHeader() {
|
||||||
|
if (this.state.searching) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.header}>
|
||||||
|
{this.renderLeft()}
|
||||||
|
{this.renderTitle()}
|
||||||
|
{this.renderRight()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSearch() {
|
||||||
|
if (!this.state.searching) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={styles.left}>
|
||||||
|
<HeaderBackButton onPress={() => this.onPressCancelSearchButton()} />
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
ref={inputSearch => this.inputSearch = inputSearch}
|
||||||
|
underlineColorAndroid='transparent'
|
||||||
|
style={styles.inputSearch}
|
||||||
|
onChangeText={text => this.onSearchChangeText(text)}
|
||||||
|
returnKeyType='search'
|
||||||
|
placeholder='Search'
|
||||||
|
clearButtonMode='while-editing'
|
||||||
|
blurOnSubmit
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.header}>
|
||||||
|
{this.renderHeader()}
|
||||||
|
{this.renderSearch()}
|
||||||
|
<Modal
|
||||||
|
isVisible={this.state.isModalVisible}
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
|
style={{ alignItems: 'center' }}
|
||||||
|
onModalHide={() => this.hideModal()}
|
||||||
|
onBackdropPress={() => this.hideModal()}
|
||||||
|
>
|
||||||
|
<View style={styles.modal}>
|
||||||
|
{this.renderModalButton('online')}
|
||||||
|
{this.renderModalButton('busy')}
|
||||||
|
{this.renderModalButton('away')}
|
||||||
|
{this.renderModalButton('offline', 'Invisible')}
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { StyleSheet, Platform, Dimensions } from 'react-native';
|
||||||
|
|
||||||
|
const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
|
||||||
|
const { width } = Dimensions.get('window');
|
||||||
|
export default StyleSheet.create({
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
left: TITLE_OFFSET,
|
||||||
|
right: TITLE_OFFSET,
|
||||||
|
position: 'absolute',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 44
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
borderRadius: 4,
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#292E35'
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
left: 0,
|
||||||
|
position: 'absolute'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
right: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
width: width - 60,
|
||||||
|
height: width - 60,
|
||||||
|
backgroundColor: '#F7F7F7',
|
||||||
|
borderRadius: 4,
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
modalButton: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomColor: 'rgba(0, 0, 0, .3)',
|
||||||
|
paddingHorizontal: 20
|
||||||
|
},
|
||||||
|
headerButton: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
height: 44,
|
||||||
|
width: 44,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
serverImage: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
inputSearch: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 44
|
||||||
|
}
|
||||||
|
});
|
|
@ -3,69 +3,26 @@ import { ListView } from 'realm/react-native';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'react-native-vector-icons/Ionicons';
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
import { Platform, View, StyleSheet, TextInput, SafeAreaView } from 'react-native';
|
import { Platform, View, TextInput, SafeAreaView } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../../actions';
|
||||||
import * as server from '../actions/connect';
|
import * as server from '../../actions/connect';
|
||||||
import realm from '../lib/realm';
|
import realm from '../../lib/realm';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import RoomItem from '../presentation/RoomItem';
|
import RoomItem from '../../presentation/RoomItem';
|
||||||
import Banner from '../containers/Banner';
|
import Banner from '../../containers/Banner';
|
||||||
import { goRoom } from '../containers/routes/NavigationService';
|
import { goRoom } from '../../containers/routes/NavigationService';
|
||||||
|
import Header from '../../containers/Header';
|
||||||
const styles = StyleSheet.create({
|
import RoomsListHeader from './Header';
|
||||||
container: {
|
import styles from './styles';
|
||||||
flex: 1,
|
|
||||||
alignItems: 'stretch',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
height: 1,
|
|
||||||
backgroundColor: '#E7E7E7'
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: '#FFFFFF'
|
|
||||||
},
|
|
||||||
emptyView: {
|
|
||||||
flexGrow: 1,
|
|
||||||
alignItems: 'stretch',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
emptyText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
color: '#ccc'
|
|
||||||
},
|
|
||||||
actionButtonIcon: {
|
|
||||||
fontSize: 20,
|
|
||||||
height: 22,
|
|
||||||
color: 'white'
|
|
||||||
},
|
|
||||||
searchBoxView: {
|
|
||||||
backgroundColor: '#eee'
|
|
||||||
},
|
|
||||||
searchBox: {
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
margin: 5,
|
|
||||||
borderRadius: 5,
|
|
||||||
padding: 5,
|
|
||||||
paddingLeft: 10,
|
|
||||||
color: '#aaa'
|
|
||||||
},
|
|
||||||
safeAreaView: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
login: state.login,
|
login: state.login,
|
||||||
Site_Url: state.settings.Site_Url,
|
Site_Url: state.settings.Site_Url,
|
||||||
canShowList: state.login.token || state.login.user.token
|
canShowList: state.login.token || state.login.user.token,
|
||||||
// Message_DateFormat: state.settings.Message_DateFormat
|
searchText: state.rooms.searchText
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
login: () => dispatch(actions.login()),
|
login: () => dispatch(actions.login()),
|
||||||
connect: () => dispatch(server.connectRequest())
|
connect: () => dispatch(server.connectRequest())
|
||||||
|
@ -75,10 +32,14 @@ export default class RoomsListView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object.isRequired,
|
navigation: PropTypes.object.isRequired,
|
||||||
Site_Url: PropTypes.string,
|
Site_Url: PropTypes.string,
|
||||||
// Message_DateFormat: PropTypes.string,
|
server: PropTypes.string,
|
||||||
server: PropTypes.string
|
searchText: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static navigationOptions = ({ navigation }) => ({
|
||||||
|
header: <Header subview={<RoomsListHeader navigation={navigation} />} />
|
||||||
|
});
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -104,6 +65,8 @@ export default class RoomsListView extends React.Component {
|
||||||
this.data.removeListener(this.updateState);
|
this.data.removeListener(this.updateState);
|
||||||
this.data = realm.objects('subscriptions').filtered('_server.id = $0', props.server).sorted('roomUpdatedAt', true);
|
this.data = realm.objects('subscriptions').filtered('_server.id = $0', props.server).sorted('roomUpdatedAt', true);
|
||||||
this.data.addListener(this.updateState);
|
this.data.addListener(this.updateState);
|
||||||
|
} else if (this.props.searchText !== props.searchText) {
|
||||||
|
this.search(props.searchText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,11 +74,13 @@ export default class RoomsListView extends React.Component {
|
||||||
this.data.removeAllListeners();
|
this.data.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText = (text) => {
|
onSearchChangeText(text) {
|
||||||
|
this.setState({ searchText: text });
|
||||||
|
this.search(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(text) {
|
||||||
const searchText = text.trim();
|
const searchText = text.trim();
|
||||||
this.setState({
|
|
||||||
searchText: text
|
|
||||||
});
|
|
||||||
if (searchText === '') {
|
if (searchText === '') {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
dataSource: ds.cloneWithRows(this.data)
|
dataSource: ds.cloneWithRows(this.data)
|
||||||
|
@ -222,7 +187,7 @@ export default class RoomsListView extends React.Component {
|
||||||
underlineColorAndroid='transparent'
|
underlineColorAndroid='transparent'
|
||||||
style={styles.searchBox}
|
style={styles.searchBox}
|
||||||
value={this.state.searchText}
|
value={this.state.searchText}
|
||||||
onChangeText={this.onSearchChangeText}
|
onChangeText={text => this.onSearchChangeText(text)}
|
||||||
returnKeyType='search'
|
returnKeyType='search'
|
||||||
placeholder='Search'
|
placeholder='Search'
|
||||||
clearButtonMode='while-editing'
|
clearButtonMode='while-editing'
|
||||||
|
@ -251,8 +216,8 @@ export default class RoomsListView extends React.Component {
|
||||||
dataSource={this.state.dataSource}
|
dataSource={this.state.dataSource}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
renderRow={this.renderItem}
|
renderRow={this.renderItem}
|
||||||
renderHeader={this.renderSearchBar}
|
renderHeader={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
||||||
contentOffset={{ x: 0, y: 38 }}
|
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
||||||
enableEmptySections
|
enableEmptySections
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
/>
|
/>
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'stretch',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: '#E7E7E7'
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: '#FFFFFF'
|
||||||
|
},
|
||||||
|
emptyView: {
|
||||||
|
flexGrow: 1,
|
||||||
|
alignItems: 'stretch',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#ccc'
|
||||||
|
},
|
||||||
|
actionButtonIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
height: 22,
|
||||||
|
color: 'white'
|
||||||
|
},
|
||||||
|
searchBoxView: {
|
||||||
|
backgroundColor: '#eee'
|
||||||
|
},
|
||||||
|
searchBox: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
margin: 5,
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 5,
|
||||||
|
paddingLeft: 10,
|
||||||
|
color: '#aaa'
|
||||||
|
},
|
||||||
|
safeAreaView: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff'
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue