Merge pull request #107 from matrix-org/unread_sync

Use read receipts to calculate unread room status
pull/21833/head
David Baker 2016-01-21 13:29:45 +00:00
commit 97d42b3ad7
3 changed files with 64 additions and 69 deletions

62
src/Unread.js Normal file
View File

@ -0,0 +1,62 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require('./MatrixClientPeg');
module.exports = {
/**
* Returns true iff this event arriving in a room should affect the room's
* count of unread messages
*/
eventTriggersUnreadCount: function(ev) {
if (ev.getType() == "m.room.member") {
return false;
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;
}
return true;
},
doesRoomHaveUnreadMessages: function(room) {
var readUpToId = room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
// this just looks at whatever history we have, which if we've only just started
// up probably won't be very much, so if the last couple of events are ones that
// don't count, we don't know if there are any events that do count between where
// we have and the read receipt. We could fetch more history to try & find out,
// but currently we just guess.
// Loop through messages, starting with the most recent...
for (var i = room.timeline.length - 1; i >= 0; --i) {
var ev = room.timeline[i];
if (ev.getId() == readUpToId) {
// If we've read up to this event, there's nothing more recents
// that counts and we can stop looking because the user's read
// this and everything before.
return false;
} else if (this.eventTriggersUnreadCount(ev)) {
// We've found a message that counts before we hit
// the read marker, so this room is definitely unread.
return true;
}
}
// If we got here, we didn't find a message that counted but didn't
// find the read marker either, so we guess and say that the room
// is unread on the theory that false positives are better than
// false negatives here.
return true;
}
};

View File

@ -1,30 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
module.exports = {
/**
* Returns true iff this event arriving in a room should affect the room's
* count of unread messages
*/
eventTriggersUnreadCount: function(ev) {
if (ev.getType() == "m.room.member") {
return false;
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;
}
return true;
}
};

View File

@ -21,7 +21,7 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var CallHandler = require('../../../CallHandler'); var CallHandler = require('../../../CallHandler');
var RoomListSorter = require("../../../RoomListSorter"); var RoomListSorter = require("../../../RoomListSorter");
var UnreadStatus = require('../../../UnreadStatus'); var Unread = require('../../../Unread');
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var sdk = require('../../../index'); var sdk = require('../../../index');
@ -38,7 +38,6 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
activityMap: null,
isLoadingLeftRooms: false, isLoadingLeftRooms: false,
lists: {}, lists: {},
incomingCall: null, incomingCall: null,
@ -57,7 +56,6 @@ module.exports = React.createClass({
cli.on("RoomMember.name", this.onRoomMemberName); cli.on("RoomMember.name", this.onRoomMemberName);
var s = this.getRoomLists(); var s = this.getRoomLists();
s.activityMap = {};
this.setState(s); this.setState(s);
}, },
@ -100,13 +98,6 @@ module.exports = React.createClass({
} }
}, },
componentWillReceiveProps: function(newProps) {
this.state.activityMap[newProps.selectedRoom] = undefined;
this.setState({
activityMap: this.state.activityMap
});
},
onRoom: function(room) { onRoom: function(room) {
this._delayedRefreshRoomList(); this._delayedRefreshRoomList();
}, },
@ -132,29 +123,7 @@ module.exports = React.createClass({
onRoomTimeline: function(ev, room, toStartOfTimeline) { onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (toStartOfTimeline) return; if (toStartOfTimeline) return;
this.refreshRoomList();
var hl = 0;
if (
room.roomId != this.props.selectedRoom &&
ev.getSender() != MatrixClientPeg.get().credentials.userId)
{
if (UnreadStatus.eventTriggersUnreadCount(ev)) {
hl = 1;
}
}
var newState = this.getRoomLists();
if (hl > 0) {
// obviously this won't deep copy but this shouldn't be necessary
var amap = this.state.activityMap;
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
newState.activityMap = amap;
}
// still want to update the list even if the highlight status
// hasn't changed because the ordering may have
this.setState(newState);
}, },
onRoomReceipt: function(receiptEvent, room) { onRoomReceipt: function(receiptEvent, room) {
@ -373,7 +342,6 @@ module.exports = React.createClass({
label="Invites" label="Invites"
editable={ false } editable={ false }
order="recent" order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
@ -384,7 +352,6 @@ module.exports = React.createClass({
verb="favourite" verb="favourite"
editable={ true } editable={ true }
order="manual" order="manual"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
@ -394,7 +361,6 @@ module.exports = React.createClass({
editable={ true } editable={ true }
verb="restore" verb="restore"
order="recent" order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
@ -408,7 +374,6 @@ module.exports = React.createClass({
verb={ "tag as " + tagName } verb={ "tag as " + tagName }
editable={ true } editable={ true }
order="manual" order="manual"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
@ -422,7 +387,6 @@ module.exports = React.createClass({
verb="demote" verb="demote"
editable={ true } editable={ true }
order="recent" order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
incomingCall={ self.state.incomingCall } incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed } /> collapsed={ self.props.collapsed } />
@ -431,7 +395,6 @@ module.exports = React.createClass({
label="Historical" label="Historical"
editable={ false } editable={ false }
order="recent" order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom } selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } collapsed={ self.props.collapsed }
alwaysShowHeader={ true } alwaysShowHeader={ true }