diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index eff1ee41..890c1896 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -28,7 +28,7 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [ ]); export const USER = createRequestTypes('USER', ['SET']); 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', 'CLOSE', 'USER_TYPING', 'MESSAGE_RECEIVED', 'SET_LAST_OPEN']); export const APP = createRequestTypes('APP', ['READY', 'INIT']); export const MESSAGES = createRequestTypes('MESSAGES', [ ...defaultTypes, diff --git a/app/actions/room.js b/app/actions/room.js index 14e346fc..ebae2bd6 100644 --- a/app/actions/room.js +++ b/app/actions/room.js @@ -29,9 +29,29 @@ export function openRoom(room) { }; } +export function closeRoom() { + return { + type: types.ROOM.CLOSE + }; +} + export function userTyping(status = true) { return { type: types.ROOM.USER_TYPING, status }; } + +export function roomMessageReceived(message) { + return { + type: types.ROOM.MESSAGE_RECEIVED, + message + }; +} + +export function setLastOpen(date = new Date()) { + return { + type: types.ROOM.SET_LAST_OPEN, + date + }; +} diff --git a/app/lib/ddp.js b/app/lib/ddp.js index 91a05097..5a88923a 100644 --- a/app/lib/ddp.js +++ b/app/lib/ddp.js @@ -114,13 +114,14 @@ export default class Socket extends EventEmitter { subscribe(name, ...params) { return this.send({ msg: 'sub', name, params - }).then((data) => { - this.subscriptions[data.id] = { + }).then((id) => { + const args = { name, params, - unsubscribe: () => this.unsubscribe(data.id) + unsubscribe: () => this.unsubscribe(id) }; - return this.subscriptions[data.id]; + this.subscriptions[id] = args; + return args; }); } } diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index b8d7fe68..de6ed12d 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -8,7 +8,7 @@ import settingsType from '../constants/settings'; import messagesStatus from '../constants/messagesStatus'; import database from './realm'; import * as actions from '../actions'; -import { someoneTyping } from '../actions/room'; +import { someoneTyping, roomMessageReceived } from '../actions/room'; import { setUser } from '../actions/login'; import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect'; import { requestActiveUser } from '../actions/activeUsers'; @@ -98,10 +98,10 @@ const RocketChat = { } }); - this.ddp.on('stream-room-messages', ddpMessage => database.write(() => { + this.ddp.on('stream-room-messages', (ddpMessage) => { const message = this._buildMessage(ddpMessage.fields.args[0]); - database.create('messages', message, true); - })); + return reduxStore.dispatch(roomMessageReceived(message)); + }); this.ddp.on('stream-notify-room', (ddpMessage) => { const [_rid, ev] = ddpMessage.fields.eventName.split('/'); diff --git a/app/reducers/room.js b/app/reducers/room.js index 08b95b97..03b1fa89 100644 --- a/app/reducers/room.js +++ b/app/reducers/room.js @@ -9,7 +9,17 @@ export default function room(state = initialState, action) { case types.ROOM.OPEN: return { ...initialState, - ...action.room + ...action.room, + lastOpen: new Date() + }; + case types.ROOM.CLOSE: + return { + ...initialState + }; + case types.ROOM.SET_LAST_OPEN: + return { + ...state, + lastOpen: action.date }; case types.ROOM.ADD_USER_TYPING: return { diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index fc8e1230..e8944fc4 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -1,11 +1,12 @@ -import { put, call, takeLatest, take, select, race, fork, cancel } from 'redux-saga/effects'; +import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects'; import { delay } from 'redux-saga'; -import { FOREGROUND } from 'redux-enhancer-react-native-appstate'; +import { FOREGROUND, BACKGROUND } from 'redux-enhancer-react-native-appstate'; import * as types from '../actions/actionsTypes'; import { roomsSuccess, roomsFailure } from '../actions/rooms'; -import { addUserTyping, removeUserTyping } from '../actions/room'; +import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room'; import { messagesRequest } from '../actions/messages'; import RocketChat from '../lib/rocketchat'; +import database from '../lib/realm'; const getRooms = function* getRooms() { return yield RocketChat.getRooms(); @@ -43,6 +44,17 @@ const usersTyping = function* usersTyping({ rid }) { } } }; +const handleMessageReceived = function* handleMessageReceived({ message }) { + const room = yield select(state => state.room); + + if (message.rid === room.rid) { + database.write(() => { + database.create('messages', message, true); + }); + + RocketChat.readMessages(room.rid); + } +}; const watchRoomOpen = function* watchRoomOpen({ room }) { const auth = yield select(state => state.login.isAuthenticated); @@ -50,7 +62,7 @@ const watchRoomOpen = function* watchRoomOpen({ room }) { yield take(types.LOGIN.SUCCESS); } - const subscriptions = []; + yield put(messagesRequest({ rid: room.rid })); const { open } = yield race({ @@ -62,12 +74,16 @@ const watchRoomOpen = function* watchRoomOpen({ room }) { return; } 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 subscriptions = yield Promise.all([RocketChat.subscribe('stream-room-messages', room.rid, false), RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false)]); const thread = yield fork(usersTyping, { rid: room.rid }); - yield take(types.ROOM.OPEN); + yield race({ + open: take(types.ROOM.OPEN), + close: take(types.ROOM.CLOSE) + }); cancel(thread); - subscriptions.forEach(sub => sub.unsubscribe()); + subscriptions.forEach((sub) => { + sub.unsubscribe().catch(e => alert(e)); + }); }; const watchuserTyping = function* watchuserTyping({ status }) { @@ -96,11 +112,18 @@ const updateRoom = function* updateRoom() { } yield put(messagesRequest({ rid: room.rid })); }; + +const updateLastOpen = function* updateLastOpen() { + yield put(setLastOpen()); +}; + const root = function* root() { yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping); yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest); yield takeLatest(types.ROOM.OPEN, watchRoomOpen); + yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived); yield takeLatest(FOREGROUND, updateRoom); yield takeLatest(FOREGROUND, watchRoomsRequest); + yield takeLatest(BACKGROUND, updateLastOpen); }; export default root; diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js index ef0ba593..5cf03f12 100644 --- a/app/views/RoomView/Header/index.js +++ b/app/views/RoomView/Header/index.js @@ -9,14 +9,18 @@ import realm from '../../../lib/realm'; import Avatar from '../../../containers/Avatar'; import { STATUS_COLORS } from '../../../constants/colors'; import styles from './styles'; +import { closeRoom } from '../../../actions/room'; @connect(state => ({ user: state.login.user, baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', activeUsers: state.activeUsers +}), dispatch => ({ + close: () => dispatch(closeRoom()) })) export default class extends React.PureComponent { static propTypes = { + close: PropTypes.func.isRequired, navigation: PropTypes.object.isRequired, user: PropTypes.object.isRequired, baseUrl: PropTypes.string, @@ -57,7 +61,15 @@ export default class extends React.PureComponent { isDirect = () => this.state.room && this.state.room.t === 'd'; - renderLeft = () => this.props.navigation.goBack(null)} tintColor='#292E35' title='Back' titleStyle={{ display: 'none' }} />; + renderLeft = () => ( { + this.props.close(); + this.props.navigation.goBack(null); + }} + tintColor='#292E35' + title='Back' + titleStyle={{ display: 'none' }} + />); renderTitle() { if (!this.state.roomName) { diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 92a8a260..72816705 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -32,7 +32,8 @@ const typing = () => ; Site_Url: state.settings.Site_Url || state.server ? state.server.server : '', Message_TimeFormat: state.settings.Message_TimeFormat, loading: state.messages.isFetching, - user: state.login.user + user: state.login.user, + lastOpened: state.room.lastOpen }), dispatch => ({ actions: bindActionCreators(actions, dispatch), @@ -42,6 +43,7 @@ const typing = () => ; ) export default class RoomView extends React.Component { static propTypes = { + // lastOpened: PropTypes.instanceOf(Date), navigation: PropTypes.object.isRequired, openRoom: PropTypes.func.isRequired, user: PropTypes.object.isRequired,