Implement 10s in-view/30s out-of-view timeout for moving RM.

Uses Timer & changed UserActivity promise based api
pull/21833/head
Bruno Windels 2018-12-11 16:19:22 +01:00
parent 7f6d581377
commit 2b0c2eff1e
1 changed files with 67 additions and 24 deletions

View File

@ -33,9 +33,13 @@ const ObjectUtils = require('../../ObjectUtils');
const Modal = require("../../Modal"); const Modal = require("../../Modal");
const UserActivity = require("../../UserActivity"); const UserActivity = require("../../UserActivity");
import { KeyCode } from '../../Keyboard'; import { KeyCode } from '../../Keyboard';
import Timer from '../../utils/Timer';
const PAGINATE_SIZE = 20; const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20; const INITIAL_SIZE = 20;
const READ_MARKER_INVIEW_THRESHOLD_MS = 10 * 1000;
const READ_MARKER_OUTOFVIEW_THRESHOLD_MS = 30 * 1000;
const READ_RECEIPT_INTERVAL_MS = 500;
const DEBUG = false; const DEBUG = false;
@ -188,6 +192,14 @@ var TimelinePanel = React.createClass({
this.lastRRSentEventId = undefined; this.lastRRSentEventId = undefined;
this.lastRMSentEventId = undefined; this.lastRMSentEventId = undefined;
if (this.props.manageReadReceipts) {
this.updateReadReceiptOnUserActivity();
}
if (this.props.manageReadMarkers) {
this.updateReadMarkerOnUserActivity();
}
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.timelineReset", this.onRoomTimelineReset); MatrixClientPeg.get().on("Room.timelineReset", this.onRoomTimelineReset);
@ -254,6 +266,14 @@ var TimelinePanel = React.createClass({
// //
// (We could use isMounted, but facebook have deprecated that.) // (We could use isMounted, but facebook have deprecated that.)
this.unmounted = true; this.unmounted = true;
if (this._readReceiptActivityTimer) {
this._readReceiptActivityTimer.abort();
this._readReceiptActivityTimer = null;
}
if (this._readMarkerActivityTimer) {
this._readMarkerActivityTimer.abort();
this._readMarkerActivityTimer = null;
}
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
@ -362,30 +382,25 @@ var TimelinePanel = React.createClass({
} }
if (this.props.manageReadMarkers) { if (this.props.manageReadMarkers) {
const rmPosition = this.getReadMarkerPosition();
// we hide the read marker when it first comes onto the screen, but if // we hide the read marker when it first comes onto the screen, but if
// it goes back off the top of the screen (presumably because the user // it goes back off the top of the screen (presumably because the user
// clicks on the 'jump to bottom' button), we need to re-enable it. // clicks on the 'jump to bottom' button), we need to re-enable it.
if (this.getReadMarkerPosition() < 0) { if (rmPosition < 0) {
this.setState({readMarkerVisible: true}); this.setState({readMarkerVisible: true});
} }
// if read marker position goes between 0 and -1/1,
// (and user is active), switch timeout
const timeout = this._readMarkerTimeout(rmPosition);
// NO-OP when timeout already has set to the given value
this._readMarkerActivityTimer.changeTimeout(timeout);
} }
}, },
onAction: function(payload) { onAction: function(payload) {
switch (payload.action) { if (payload.action === 'ignore_state_changed') {
case 'user_activity': this.forceUpdate();
case 'user_activity_end':
// we could treat user_activity_end differently and not
// send receipts for messages that have arrived between
// the actual user activity and the time they stopped
// being active, but let's see if this is actually
// necessary.
this.sendReadReceipt();
this.updateReadMarker();
break;
case 'ignore_state_changed':
this.forceUpdate();
break;
} }
}, },
@ -531,6 +546,38 @@ var TimelinePanel = React.createClass({
this.setState({clientSyncState: state}); this.setState({clientSyncState: state});
}, },
_readMarkerTimeout(readMarkerPosition) {
return readMarkerPosition === 0 ?
READ_MARKER_INVIEW_THRESHOLD_MS :
READ_MARKER_OUTOFVIEW_THRESHOLD_MS;
},
updateReadMarkerOnUserActivity: async function() {
const initialTimeout = this._readMarkerTimeout(this.getReadMarkerPosition());
this._readMarkerActivityTimer = new Timer(initialTimeout);
while (this._readMarkerActivityTimer) { //unset on unmount
UserActivity.timeWhileActive(this._readMarkerActivityTimer);
try {
await this._readMarkerActivityTimer.finished();
} catch(e) { continue; /* aborted */ }
// outside of try/catch to not swallow errors
this.updateReadMarker();
}
},
updateReadReceiptOnUserActivity: async function() {
this._readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS);
while (this._readReceiptActivityTimer) { //unset on unmount
UserActivity.timeWhileActive(this._readReceiptActivityTimer);
try {
await this._readReceiptActivityTimer.finished();
} catch(e) { continue; /* aborted */ }
// outside of try/catch to not swallow errors
this.sendReadReceipt();
}
},
sendReadReceipt: function() { sendReadReceipt: function() {
if (!this.refs.messagePanel) return; if (!this.refs.messagePanel) return;
if (!this.props.manageReadReceipts) return; if (!this.props.manageReadReceipts) return;
@ -634,10 +681,11 @@ var TimelinePanel = React.createClass({
// of the screen, so move the marker down to the bottom of the screen. // of the screen, so move the marker down to the bottom of the screen.
updateReadMarker: function() { updateReadMarker: function() {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
if (this.getReadMarkerPosition() !== 0) { if (this.getReadMarkerPosition() === 1) {
// the read marker is at an event below the viewport,
// we don't want to rewind it.
return; return;
} }
// move the RM to *after* the message at the bottom of the screen. This // move the RM to *after* the message at the bottom of the screen. This
// avoids a problem whereby we never advance the RM if there is a huge // avoids a problem whereby we never advance the RM if there is a huge
// message which doesn't fit on the screen. // message which doesn't fit on the screen.
@ -654,7 +702,6 @@ var TimelinePanel = React.createClass({
if (lastDisplayedIndex === null) { if (lastDisplayedIndex === null) {
return; return;
} }
const lastDisplayedEvent = this.state.events[lastDisplayedIndex]; const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this._setReadMarker(lastDisplayedEvent.getId(), this._setReadMarker(lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs()); lastDisplayedEvent.getTs());
@ -821,15 +868,12 @@ var TimelinePanel = React.createClass({
canJumpToReadMarker: function() { canJumpToReadMarker: function() {
// 1. Do not show jump bar if neither the RM nor the RR are set. // 1. Do not show jump bar if neither the RM nor the RR are set.
// 2. Only show jump bar if RR !== RM. If they are the same, there are only fully
// read messages and unread messages. We already have a badge count and the bottom
// bar to jump to "live" when we have unread messages.
// 3. We want to show the bar if the read-marker is off the top of the screen. // 3. We want to show the bar if the read-marker is off the top of the screen.
// 4. Also, if pos === null, the event might not be paginated - show the unread bar // 4. Also, if pos === null, the event might not be paginated - show the unread bar
const pos = this.getReadMarkerPosition(); const pos = this.getReadMarkerPosition();
return this.state.readMarkerEventId !== null && // 1. const ret = this.state.readMarkerEventId !== null && // 1.
this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
(pos < 0 || pos === null); // 3., 4. (pos < 0 || pos === null); // 3., 4.
return ret;
}, },
/** /**
@ -916,7 +960,6 @@ var TimelinePanel = React.createClass({
} }
this.sendReadReceipt(); this.sendReadReceipt();
this.updateReadMarker();
}); });
}; };