user typing
This commit is contained in:
parent
de25bd3bff
commit
a4fbeaefff
|
@ -26,8 +26,8 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
|
|||
...defaultTypes,
|
||||
'INIT'
|
||||
]);
|
||||
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'OPEN']);
|
||||
export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'USER_TYPING']);
|
||||
export const ROOMS = createRequestTypes('ROOMS');
|
||||
export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'USER_TYPING', 'OPEN', 'IM_TYPING']);
|
||||
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES');
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
|
||||
|
|
|
@ -14,9 +14,24 @@ export function typing(data) {
|
|||
...data
|
||||
};
|
||||
}
|
||||
|
||||
export function addUserTyping(username) {
|
||||
return {
|
||||
type: types.ROOM.ADD_USER_TYPING,
|
||||
username
|
||||
};
|
||||
}
|
||||
|
||||
export function openRoom(room) {
|
||||
return {
|
||||
type: types.ROOM.OPEN,
|
||||
room
|
||||
};
|
||||
}
|
||||
|
||||
export function imTyping(status = true) {
|
||||
return {
|
||||
type: types.ROOM.IM_TYPING,
|
||||
status
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,10 +19,3 @@ export function roomsFailure(err) {
|
|||
err
|
||||
};
|
||||
}
|
||||
|
||||
export function openRoom({ rid }) {
|
||||
return {
|
||||
type: types.ROOMS.OPEN,
|
||||
rid
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,11 +3,14 @@ import PropTypes from 'prop-types';
|
|||
import { View, TextInput, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import { connect } from 'react-redux';
|
||||
import { imTyping } from '../actions/room';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textBox: {
|
||||
paddingTop: 1,
|
||||
paddingHorizontal: 15,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#ccc',
|
||||
backgroundColor: '#fff'
|
||||
|
@ -24,14 +27,19 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
fileButton: {
|
||||
color: '#aaa',
|
||||
paddingLeft: 23,
|
||||
paddingRight: 20,
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
fontSize: 20
|
||||
}
|
||||
});
|
||||
|
||||
@connect(
|
||||
null,
|
||||
dispatch => ({
|
||||
typing: status => dispatch(imTyping(status))
|
||||
})
|
||||
)
|
||||
|
||||
export default class MessageBox extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
|
@ -80,7 +88,6 @@ export default class MessageBox extends React.PureComponent {
|
|||
return (
|
||||
<View style={styles.textBox}>
|
||||
<SafeAreaView style={styles.safeAreaView}>
|
||||
<Icon style={styles.fileButton} name='add-circle-outline' onPress={this.addFile} />
|
||||
<TextInput
|
||||
ref={component => this.component = component}
|
||||
style={styles.textBoxInput}
|
||||
|
@ -88,9 +95,11 @@ export default class MessageBox extends React.PureComponent {
|
|||
onSubmitEditing={event => this.submit(event.nativeEvent.text)}
|
||||
blurOnSubmit={false}
|
||||
placeholder='New message'
|
||||
onChangeText={text => this.props.typing(text.length > 0)}
|
||||
underlineColorAndroid='transparent'
|
||||
defaultValue=''
|
||||
/>
|
||||
<Icon style={styles.fileButton} name='add-circle-outline' onPress={this.addFile} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -65,7 +65,7 @@ const RocketChat = {
|
|||
Meteor.ddp.on('changed', (ddpMessage) => {
|
||||
const server = { id: reduxStore.getState().server.server };
|
||||
if (ddpMessage.collection === 'stream-room-messages') {
|
||||
realm.write(() => {
|
||||
return realm.write(() => {
|
||||
const message = ddpMessage.fields.args[0];
|
||||
message.temp = false;
|
||||
message._server = server;
|
||||
|
@ -78,7 +78,7 @@ const RocketChat = {
|
|||
if (ev !== 'typing') {
|
||||
return;
|
||||
}
|
||||
reduxStore.dispatch(typing({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
||||
return reduxStore.dispatch(typing({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
||||
}
|
||||
if (ddpMessage.collection === 'stream-notify-user') {
|
||||
const [type, data] = ddpMessage.fields.args;
|
||||
|
@ -436,8 +436,9 @@ const RocketChat = {
|
|||
subscribe(...args) {
|
||||
return Meteor.subscribe(...args);
|
||||
},
|
||||
unsubscribe(...args) {
|
||||
return Meteor.unsubscribe(...args);
|
||||
emitTyping(room, t = true) {
|
||||
const { login } = reduxStore.getState();
|
||||
return call('stream-notify-room', `${ room }/typing`, login.user.username, t);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,11 @@ const initialState = {
|
|||
|
||||
export default function room(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case types.ROOM.OPEN:
|
||||
return {
|
||||
...initialState,
|
||||
...action.room
|
||||
};
|
||||
case types.ROOM.ADD_USER_TYPING:
|
||||
return {
|
||||
...state,
|
||||
|
@ -16,8 +21,6 @@ export default function room(state = initialState, action) {
|
|||
...state,
|
||||
usersTyping: [...state.usersTyping.filter(user => user !== action.username)]
|
||||
};
|
||||
// case types.LOGOUT:
|
||||
// return initialState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { put, call, takeLatest, takeEvery, take, select, race, fork, cancel } from 'redux-saga/effects';
|
||||
import { put, call, takeLatest, take, select, race, fork, cancel } from 'redux-saga/effects';
|
||||
import { delay } from 'redux-saga';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||
import { addUserTyping, removeUserTyping } from '../actions/room';
|
||||
|
@ -17,43 +18,80 @@ const watchRoomsRequest = function* watchRoomsRequest() {
|
|||
yield put(roomsFailure(err.status));
|
||||
}
|
||||
};
|
||||
const userTyping = function* userTyping({ rid }) {
|
||||
|
||||
const cancelTyping = function* cancelTyping(username) {
|
||||
while (true) {
|
||||
const { _rid, username, typing } = yield take(types.ROOM.USER_TYPING);
|
||||
if (_rid === rid) {
|
||||
const tmp = yield (typing ? put(addUserTyping(username)) : put(removeUserTyping(username)));
|
||||
const { typing, timeout } = yield race({
|
||||
typing: take(types.ROOM.USER_TYPING),
|
||||
timeout: yield call(delay, 5000)
|
||||
});
|
||||
if (timeout || (typing.username === username && !typing.typing)) {
|
||||
return yield put(removeUserTyping(username));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const watchRoomOpen = function* watchRoomOpen({ rid }) {
|
||||
const usersTyping = function* usersTyping({ rid }) {
|
||||
while (true) {
|
||||
const { _rid, username, typing } = yield take(types.ROOM.USER_TYPING);
|
||||
if (_rid === rid) {
|
||||
yield (typing ? put(addUserTyping(username)) : put(removeUserTyping(username)));
|
||||
if (typing) {
|
||||
fork(cancelTyping, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const watchRoomOpen = function* watchRoomOpen({ room }) {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
yield take(types.LOGIN.SUCCESS);
|
||||
}
|
||||
|
||||
const subscriptions = [];
|
||||
yield put(messagesRequest({ rid }));
|
||||
yield put(messagesRequest({ rid: room.rid }));
|
||||
|
||||
const { open } = yield race({
|
||||
messages: take(types.MESSAGES.SUCCESS),
|
||||
open: take(types.ROOMS.OPEN)
|
||||
open: take(types.ROOM.OPEN)
|
||||
});
|
||||
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
RocketChat.readMessages(rid);
|
||||
subscriptions.push(RocketChat.subscribe('stream-room-messages', rid, false));
|
||||
subscriptions.push(RocketChat.subscribe('stream-notify-room', `${ rid }/typing`, false));
|
||||
const thread = yield fork(userTyping, { rid });
|
||||
yield take(types.ROOMS.OPEN);
|
||||
|
||||
RocketChat.readMessages(room.rid);
|
||||
subscriptions.push(RocketChat.subscribe('stream-room-messages', room.rid, false));
|
||||
subscriptions.push(RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false));
|
||||
const thread = yield fork(usersTyping, { rid: room.rid });
|
||||
yield take(types.ROOM.OPEN);
|
||||
cancel(thread);
|
||||
subscriptions.forEach(sub => sub.stop());
|
||||
};
|
||||
|
||||
const watchImTyping = function* watchImTyping({ status }) {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
yield take(types.LOGIN.SUCCESS);
|
||||
}
|
||||
|
||||
const room = yield select(state => state.room);
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
yield RocketChat.emitTyping(room.rid, status);
|
||||
|
||||
if (status) {
|
||||
yield call(delay, 5000);
|
||||
yield RocketChat.emitTyping(room.rid, false);
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.ROOM.IM_TYPING, watchImTyping);
|
||||
yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
|
||||
yield takeEvery(types.ROOMS.OPEN, watchRoomOpen);
|
||||
yield takeLatest(types.ROOM.OPEN, watchRoomOpen);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { connect } from 'react-redux';
|
|||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { openRoom } from '../actions/rooms';
|
||||
import { openRoom } from '../actions/room';
|
||||
import realm from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import Message from '../containers/Message';
|
||||
|
@ -15,6 +15,7 @@ import KeyboardView from '../presentation/KeyboardView';
|
|||
|
||||
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'
|
||||
|
@ -48,6 +49,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
@connect(
|
||||
state => ({
|
||||
username: state.login.user.username,
|
||||
usersTyping: state.room.usersTyping,
|
||||
server: state.server.server,
|
||||
Site_Url: state.settings.Site_Url,
|
||||
|
@ -56,7 +58,7 @@ const styles = StyleSheet.create({
|
|||
}),
|
||||
dispatch => ({
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
openRoom: rid => dispatch(openRoom({ rid }))
|
||||
openRoom: room => dispatch(openRoom(room))
|
||||
})
|
||||
)
|
||||
export default class RoomView extends React.Component {
|
||||
|
@ -101,7 +103,7 @@ export default class RoomView extends React.Component {
|
|||
realm.objectForPrimaryKey('subscriptions', this.sid).name
|
||||
});
|
||||
this.timer = setTimeout(() => this.setState({ slow: true }), 5000);
|
||||
this.props.openRoom(this.rid);
|
||||
this.props.openRoom({ rid: this.rid });
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
componentDidMount() {
|
||||
|
@ -135,7 +137,12 @@ export default class RoomView extends React.Component {
|
|||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get usersTyping() {
|
||||
const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
|
||||
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : null;
|
||||
}
|
||||
|
||||
updateState = () => {
|
||||
this.setState({
|
||||
|
@ -190,8 +197,7 @@ export default class RoomView extends React.Component {
|
|||
if (this.state.end) {
|
||||
return <Text style={styles.loadingMore}>Start of conversation</Text>;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
render() {
|
||||
const { height } = Dimensions.get('window');
|
||||
return (
|
||||
|
@ -208,9 +214,9 @@ export default class RoomView extends React.Component {
|
|||
renderRow={item => this.renderItem({ item })}
|
||||
initialListSize={10}
|
||||
/>
|
||||
{this.props.usersTyping ? <Text>{this.props.usersTyping.join(',')}</Text> : null}
|
||||
</SafeAreaView>
|
||||
{this.renderFooter()}
|
||||
<Text style={styles.typing}>{this.usersTyping}</Text>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue