parent
d991fd99bc
commit
c84c5a3565
|
@ -35,15 +35,16 @@ exports[`render channel 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"borderRadius": 4,
|
||||
"height": 40,
|
||||
"justifyContent": "center",
|
||||
"overflow": "hidden",
|
||||
"width": 40,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#00BCD4",
|
||||
"borderRadius": 4,
|
||||
"height": 40,
|
||||
"width": 40,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
@ -57,10 +58,14 @@ exports[`render channel 1`] = `
|
|||
"color": undefined,
|
||||
"fontSize": 12,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
},
|
||||
Object {
|
||||
"color": "#fff",
|
||||
"fontSize": 20,
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"fontFamily": "Material Design Icons",
|
||||
"fontStyle": "normal",
|
||||
|
@ -151,6 +156,53 @@ exports[`render no icon 1`] = `
|
|||
testID={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
|
||||
style={
|
||||
Object {
|
||||
|
@ -230,6 +282,53 @@ exports[`render private group 1`] = `
|
|||
testID={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
|
||||
style={
|
||||
Object {
|
||||
|
|
|
@ -27,7 +27,7 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
|
|||
'INIT'
|
||||
]);
|
||||
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 APP = createRequestTypes('APP', ['READY', 'INIT']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||
|
@ -75,6 +75,7 @@ export const SERVER = createRequestTypes('SERVER', [
|
|||
]);
|
||||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
|
||||
|
||||
export const INCREMENT = 'INCREMENT';
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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 { StyleSheet, Text, View } from 'react-native';
|
||||
import { CachedImage } from 'react-native-img-cache';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -21,7 +22,7 @@ const styles = StyleSheet.create({
|
|||
class Avatar extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar
|
||||
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
|
||||
} = this.props;
|
||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||
|
||||
|
@ -42,6 +43,7 @@ class Avatar extends React.PureComponent {
|
|||
borderRadius
|
||||
};
|
||||
|
||||
if (type === 'd') {
|
||||
const uri = avatar || `${ baseUrl }/avatar/${ text }`;
|
||||
const image = (avatar || baseUrl) && (
|
||||
<CachedImage
|
||||
|
@ -49,13 +51,25 @@ class Avatar extends React.PureComponent {
|
|||
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 (
|
||||
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
||||
<MaterialCommunityIcons name={icon} style={[styles.avatarInitials, avatarInitialsStyle]} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Avatar.propTypes = {
|
||||
|
@ -64,6 +78,7 @@ Avatar.propTypes = {
|
|||
text: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
borderRadius: PropTypes.number
|
||||
borderRadius: PropTypes.number,
|
||||
type: PropTypes.string
|
||||
};
|
||||
export default Avatar;
|
||||
|
|
|
@ -1,18 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Text, View, StyleSheet, Platform, TouchableOpacity, Dimensions } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import { View, StyleSheet, Platform } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import Modal from 'react-native-modal';
|
||||
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;
|
||||
if (Platform.OS === 'ios') {
|
||||
platformContainerStyles = {
|
||||
|
@ -32,7 +22,6 @@ if (Platform.OS === 'ios') {
|
|||
}
|
||||
|
||||
const appBarHeight = Platform.OS === 'ios' ? 44 : 56;
|
||||
const { width } = Dimensions.get('window');
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
|
||||
|
@ -41,171 +30,20 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
appBar: {
|
||||
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 {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
Site_Url: PropTypes.string
|
||||
subview: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
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() {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.appBar}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.left}>
|
||||
<DrawerMenuButton navigation={this.props.navigation} />
|
||||
{this.props.subview}
|
||||
</View>
|
||||
{this.renderTitle()}
|
||||
{this.renderRight()}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,38 +1,19 @@
|
|||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { StackNavigator, DrawerNavigator } from 'react-navigation';
|
||||
|
||||
import Sidebar from '../../containers/Sidebar';
|
||||
import DrawerMenuButton from '../../presentation/DrawerMenuButton';
|
||||
import Header from '../../containers/Header';
|
||||
|
||||
import RoomsListView from '../../views/RoomsListView';
|
||||
import RoomView from '../../views/RoomView';
|
||||
import CreateChannelView from '../../views/CreateChannelView';
|
||||
import SelectUsersView from '../../views/SelectUsersView';
|
||||
|
||||
const drawerPosition = 'left';
|
||||
const drawerIconPosition = 'headerLeft';
|
||||
|
||||
const AuthRoutes = StackNavigator(
|
||||
{
|
||||
RoomsList: {
|
||||
screen: RoomsListView,
|
||||
navigationOptions({ navigation }) {
|
||||
return {
|
||||
title: 'Rooms',
|
||||
header: <Header navigation={navigation} />
|
||||
};
|
||||
}
|
||||
screen: RoomsListView
|
||||
},
|
||||
Room: {
|
||||
screen: RoomView,
|
||||
navigationOptions({ navigation }) {
|
||||
return {
|
||||
title: navigation.state.params.name || navigation.state.params.room.name || 'Room'
|
||||
// [drawerIconPosition]: (<DrawerMenuButton navigation={navigation} />)÷
|
||||
};
|
||||
}
|
||||
screen: RoomView
|
||||
},
|
||||
CreateChannel: {
|
||||
screen: CreateChannelView,
|
||||
|
@ -53,18 +34,11 @@ const AuthRoutes = StackNavigator(
|
|||
const Routes = DrawerNavigator(
|
||||
{
|
||||
Home: {
|
||||
screen: AuthRoutes,
|
||||
navigationOptions({ navigation }) {
|
||||
return {
|
||||
title: 'Rooms',
|
||||
[drawerIconPosition]: <DrawerMenuButton navigation={navigation} />
|
||||
};
|
||||
}
|
||||
screen: AuthRoutes
|
||||
}
|
||||
},
|
||||
{
|
||||
contentComponent: Sidebar,
|
||||
drawerPosition,
|
||||
navigationOptions: {
|
||||
drawerLockMode: Platform.OS === 'ios' ? 'locked-closed' : 'unlocked'
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import * as actions from '../actions';
|
|||
import { someoneTyping } from '../actions/room';
|
||||
import { setUser } from '../actions/login';
|
||||
import { disconnect, connectSuccess } from '../actions/connect';
|
||||
import { requestActiveUser } from '../actions/activeUsers';
|
||||
|
||||
export { Accounts } from 'react-native-meteor';
|
||||
|
||||
|
@ -47,6 +48,23 @@ const RocketChat = {
|
|||
}
|
||||
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) {
|
||||
return new Promise((resolve) => {
|
||||
const url = `${ _url }/websocket`;
|
||||
|
@ -63,6 +81,16 @@ const RocketChat = {
|
|||
});
|
||||
|
||||
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) => {
|
||||
if (ddpMessage.collection === 'stream-room-messages') {
|
||||
return realm.write(() => {
|
||||
|
@ -96,7 +124,7 @@ const RocketChat = {
|
|||
}
|
||||
}
|
||||
if (ddpMessage.collection === 'users') {
|
||||
return reduxStore.dispatch(setUser({ status: ddpMessage.fields.status || ddpMessage.fields.statusDefault }));
|
||||
return RocketChat._setUser(ddpMessage);
|
||||
}
|
||||
});
|
||||
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 }/rooms-changed`, false);
|
||||
Meteor.subscribe('userData', null, false);
|
||||
Meteor.subscribe('activeUsers', null, false);
|
||||
return data;
|
||||
},
|
||||
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 moment from 'moment';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import Avatar from '../containers/Avatar';
|
||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -49,22 +47,6 @@ const styles = StyleSheet.create({
|
|||
fontSize: 10,
|
||||
height: 10,
|
||||
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() {
|
||||
const { type, name, baseUrl } = this.props;
|
||||
|
||||
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>
|
||||
);
|
||||
return <Avatar text={name} baseUrl={baseUrl} size={40} type={type} />;
|
||||
}
|
||||
|
||||
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 messages from './messages';
|
||||
import room from './room';
|
||||
import rooms from './rooms';
|
||||
import server from './server';
|
||||
import navigator from './navigator';
|
||||
import createChannel from './createChannel';
|
||||
import app from './app';
|
||||
import permissions from './permissions';
|
||||
import activeUsers from './activeUsers';
|
||||
|
||||
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 = {
|
||||
isFetching: false,
|
||||
failure: false
|
||||
failure: false,
|
||||
searchText: ''
|
||||
};
|
||||
|
||||
export default function login(state = initialState, action) {
|
||||
|
@ -24,8 +25,11 @@ export default function login(state = initialState, action) {
|
|||
failure: true,
|
||||
errorMessage: action.err
|
||||
};
|
||||
// case types.LOGOUT:
|
||||
// return initialState;
|
||||
case types.ROOMS.SET_SEARCH:
|
||||
return {
|
||||
...state,
|
||||
searchText: action.searchText
|
||||
};
|
||||
default:
|
||||
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 init from './init';
|
||||
import state from './state';
|
||||
import activeUsers from './activeUsers';
|
||||
|
||||
const root = function* root() {
|
||||
yield all([
|
||||
|
@ -19,7 +20,8 @@ const root = function* root() {
|
|||
connect(),
|
||||
messages(),
|
||||
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 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 { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { openRoom } from '../actions/room';
|
||||
import { editCancel } from '../actions/messages';
|
||||
import realm from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import Message from '../containers/message';
|
||||
import MessageActions from '../containers/MessageActions';
|
||||
import MessageBox from '../containers/MessageBox';
|
||||
import Typing from '../containers/Typing';
|
||||
import KeyboardView from '../presentation/KeyboardView';
|
||||
import * as actions from '../../actions';
|
||||
import { openRoom } from '../../actions/room';
|
||||
import { editCancel } from '../../actions/messages';
|
||||
import realm from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import Message from '../../containers/message';
|
||||
import MessageActions from '../../containers/MessageActions';
|
||||
import MessageBox from '../../containers/MessageBox';
|
||||
import Typing from '../../containers/Typing';
|
||||
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 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 />;
|
||||
@connect(
|
||||
state => ({
|
||||
|
@ -78,6 +50,10 @@ export default class RoomView extends React.Component {
|
|||
loading: PropTypes.bool
|
||||
};
|
||||
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
header: <Header subview={<RoomsHeader navigation={navigation} />} />
|
||||
});
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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 PropTypes from 'prop-types';
|
||||
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 * as actions from '../actions';
|
||||
import * as server from '../actions/connect';
|
||||
import realm from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import RoomItem from '../presentation/RoomItem';
|
||||
import Banner from '../containers/Banner';
|
||||
import { goRoom } from '../containers/routes/NavigationService';
|
||||
|
||||
const styles = 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'
|
||||
}
|
||||
});
|
||||
import * as actions from '../../actions';
|
||||
import * as server from '../../actions/connect';
|
||||
import realm from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import RoomItem from '../../presentation/RoomItem';
|
||||
import Banner from '../../containers/Banner';
|
||||
import { goRoom } from '../../containers/routes/NavigationService';
|
||||
import Header from '../../containers/Header';
|
||||
import RoomsListHeader from './Header';
|
||||
import styles from './styles';
|
||||
|
||||
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
|
||||
@connect(state => ({
|
||||
server: state.server.server,
|
||||
login: state.login,
|
||||
Site_Url: state.settings.Site_Url,
|
||||
canShowList: state.login.token || state.login.user.token
|
||||
// Message_DateFormat: state.settings.Message_DateFormat
|
||||
canShowList: state.login.token || state.login.user.token,
|
||||
searchText: state.rooms.searchText
|
||||
}), dispatch => ({
|
||||
login: () => dispatch(actions.login()),
|
||||
connect: () => dispatch(server.connectRequest())
|
||||
|
@ -75,10 +32,14 @@ export default class RoomsListView extends React.Component {
|
|||
static propTypes = {
|
||||
navigation: PropTypes.object.isRequired,
|
||||
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) {
|
||||
super(props);
|
||||
|
||||
|
@ -104,6 +65,8 @@ export default class RoomsListView extends React.Component {
|
|||
this.data.removeListener(this.updateState);
|
||||
this.data = realm.objects('subscriptions').filtered('_server.id = $0', props.server).sorted('roomUpdatedAt', true);
|
||||
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();
|
||||
}
|
||||
|
||||
onSearchChangeText = (text) => {
|
||||
onSearchChangeText(text) {
|
||||
this.setState({ searchText: text });
|
||||
this.search(text);
|
||||
}
|
||||
|
||||
search(text) {
|
||||
const searchText = text.trim();
|
||||
this.setState({
|
||||
searchText: text
|
||||
});
|
||||
if (searchText === '') {
|
||||
return this.setState({
|
||||
dataSource: ds.cloneWithRows(this.data)
|
||||
|
@ -222,7 +187,7 @@ export default class RoomsListView extends React.Component {
|
|||
underlineColorAndroid='transparent'
|
||||
style={styles.searchBox}
|
||||
value={this.state.searchText}
|
||||
onChangeText={this.onSearchChangeText}
|
||||
onChangeText={text => this.onSearchChangeText(text)}
|
||||
returnKeyType='search'
|
||||
placeholder='Search'
|
||||
clearButtonMode='while-editing'
|
||||
|
@ -251,8 +216,8 @@ export default class RoomsListView extends React.Component {
|
|||
dataSource={this.state.dataSource}
|
||||
style={styles.list}
|
||||
renderRow={this.renderItem}
|
||||
renderHeader={this.renderSearchBar}
|
||||
contentOffset={{ x: 0, y: 38 }}
|
||||
renderHeader={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
||||
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
||||
enableEmptySections
|
||||
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