New Idle timer; messages not marked read if user is idle

This commit is contained in:
Scott Nonnenberg 2019-09-19 15:16:46 -07:00
parent b77246a7e0
commit 8ccb89310b
11 changed files with 55 additions and 55 deletions

View file

@ -449,7 +449,6 @@
<script type='text/javascript' src='js/signal_protocol_store.js'></script> <script type='text/javascript' src='js/signal_protocol_store.js'></script>
<script type='text/javascript' src='js/libtextsecure.js'></script> <script type='text/javascript' src='js/libtextsecure.js'></script>
<script type='text/javascript' src='js/focus_listener.js'></script>
<script type='text/javascript' src='js/notifications.js'></script> <script type='text/javascript' src='js/notifications.js'></script>
<script type='text/javascript' src='js/delivery_receipts.js'></script> <script type='text/javascript' src='js/delivery_receipts.js'></script>
<script type='text/javascript' src='js/read_receipts.js'></script> <script type='text/javascript' src='js/read_receipts.js'></script>

View file

@ -40,6 +40,43 @@
false false
); );
// Idle timer - you're active for ACTIVE_TIMEOUT after one of these events
const ACTIVE_TIMEOUT = 15 * 1000;
const ACTIVE_EVENTS = [
'click',
'keypress',
'mousedown',
'mousemove',
// 'scroll', // this is triggered by Timeline re-renders, can't use
'touchstart',
'wheel',
];
const LISTENER_DEBOUNCE = 5 * 1000;
let activeHandlers = [];
let activeTimestamp = Date.now();
window.resetActiveTimer = _.throttle(() => {
const previouslyActive = window.isActive();
activeTimestamp = Date.now();
if (!previouslyActive) {
activeHandlers.forEach(handler => handler());
}
}, LISTENER_DEBOUNCE);
ACTIVE_EVENTS.forEach(name => {
document.addEventListener(name, window.resetActiveTimer, true);
});
window.isActive = () => {
const now = Date.now();
return now <= activeTimestamp + ACTIVE_TIMEOUT;
};
window.registerForActive = handler => activeHandlers.push(handler);
window.unregisterForActive = handler => {
activeHandlers = activeHandlers.filter(item => item !== handler);
};
// Load these images now to ensure that they don't flicker on first use // Load these images now to ensure that they don't flicker on first use
window.Signal.EmojiLib.preloadImages(); window.Signal.EmojiLib.preloadImages();
const images = []; const images = [];
@ -712,7 +749,7 @@
} }
}); });
window.addEventListener('focus', () => Whisper.Notifications.clear()); window.registerForActive(() => Whisper.Notifications.clear());
window.addEventListener('unload', () => Whisper.Notifications.fastClear()); window.addEventListener('unload', () => Whisper.Notifications.fastClear());
Whisper.events.on('showConversation', (id, messageId) => { Whisper.events.on('showConversation', (id, messageId) => {

View file

@ -1,14 +0,0 @@
// eslint-disable-next-line func-names
(function() {
'use strict';
let windowFocused = false;
window.addEventListener('blur', () => {
windowFocused = false;
});
window.addEventListener('focus', () => {
windowFocused = true;
});
window.isFocused = () => windowFocused;
})();

View file

@ -334,7 +334,7 @@
this.id, this.id,
[model.getReduxData()], [model.getReduxData()],
isNewMessage, isNewMessage,
document.hasFocus() window.isActive()
); );
} }

View file

@ -3,7 +3,6 @@
/* global drawAttention: false */ /* global drawAttention: false */
/* global i18n: false */ /* global i18n: false */
/* global isFocused: false */
/* global Signal: false */ /* global Signal: false */
/* global storage: false */ /* global storage: false */
/* global Whisper: false */ /* global Whisper: false */
@ -54,7 +53,7 @@
} }
const { isEnabled } = this; const { isEnabled } = this;
const isAppFocused = isFocused(); const isAppFocused = window.isActive();
const isAudioNotificationEnabled = const isAudioNotificationEnabled =
storage.get('audio-notification') || false; storage.get('audio-notification') || false;
const isAudioNotificationSupported = Settings.isAudioNotificationSupported(); const isAudioNotificationSupported = Settings.isAudioNotificationSupported();

View file

@ -166,7 +166,7 @@
if (!$.contains(this.el, this.inboxView.el)) { if (!$.contains(this.el, this.inboxView.el)) {
this.openView(this.inboxView); this.openView(this.inboxView);
} }
window.focus(); // FIXME
return Promise.resolve(); return Promise.resolve();
}, },
onEmpty() { onEmpty() {

View file

@ -442,7 +442,7 @@
id, id,
models.map(model => model.getReduxData()), models.map(model => model.getReduxData()),
isNewMessage, isNewMessage,
document.hasFocus() window.isActive()
); );
} catch (error) { } catch (error) {
setMessagesLoading(conversationId, true); setMessagesLoading(conversationId, true);
@ -493,7 +493,7 @@
id, id,
models.map(model => model.getReduxData()), models.map(model => model.getReduxData()),
isNewMessage, isNewMessage,
document.hasFocus() window.isActive()
); );
} catch (error) { } catch (error) {
setMessagesLoading(conversationId, false); setMessagesLoading(conversationId, false);
@ -502,10 +502,8 @@
finish(); finish();
} }
}; };
const markMessageRead = async (messageId, forceFocus) => { const markMessageRead = async messageId => {
// We need a forceFocus parameter because the BrowserWindow focus event fires if (!window.isActive()) {
// before the document realizes that it has focus.
if (!document.hasFocus() && !forceFocus) {
return; return;
} }

View file

@ -10,14 +10,6 @@ const { remote } = electron;
const { app } = remote; const { app } = remote;
const { systemPreferences } = remote.require('electron'); const { systemPreferences } = remote.require('electron');
const browserWindow = remote.getCurrentWindow();
let focusHandlers = [];
browserWindow.on('focus', () => focusHandlers.forEach(handler => handler()));
window.registerForFocus = handler => focusHandlers.push(handler);
window.unregisterForFocus = handler => {
focusHandlers = focusHandlers.filter(item => item !== handler);
};
// Waiting for clients to implement changes on receive side // Waiting for clients to implement changes on receive side
window.ENABLE_STICKER_SEND = true; window.ENABLE_STICKER_SEND = true;
window.TIMESTAMP_VALIDATION = false; window.TIMESTAMP_VALIDATION = false;

View file

@ -476,7 +476,6 @@
<script type='text/javascript' src='../js/expiring_messages.js' data-cover></script> <script type='text/javascript' src='../js/expiring_messages.js' data-cover></script>
<script type='text/javascript' src='../js/expiring_tap_to_view_messages.js' data-cover></script> <script type='text/javascript' src='../js/expiring_tap_to_view_messages.js' data-cover></script>
<script type='text/javascript' src='../js/notifications.js' data-cover></script> <script type='text/javascript' src='../js/notifications.js' data-cover></script>
<script type='text/javascript' src='../js/focus_listener.js'></script>
<script type="text/javascript" src="../js/chromium.js" data-cover></script> <script type="text/javascript" src="../js/chromium.js" data-cover></script>

View file

@ -61,7 +61,7 @@ type PropsActionsType = {
loadOlderMessages: (messageId: string) => unknown; loadOlderMessages: (messageId: string) => unknown;
loadNewerMessages: (messageId: string) => unknown; loadNewerMessages: (messageId: string) => unknown;
loadNewestMessages: (messageId: string) => unknown; loadNewestMessages: (messageId: string) => unknown;
markMessageRead: (messageId: string, forceFocus?: boolean) => unknown; markMessageRead: (messageId: string) => unknown;
} & MessageActionsType & } & MessageActionsType &
SafetyNumberActionsType; SafetyNumberActionsType;
@ -402,7 +402,7 @@ export class Timeline extends React.PureComponent<Props, State> {
// tslint:disable-next-line member-ordering cyclomatic-complexity // tslint:disable-next-line member-ordering cyclomatic-complexity
public updateWithVisibleRows = debounce( public updateWithVisibleRows = debounce(
(forceFocus?: boolean) => { () => {
const { const {
unreadCount, unreadCount,
haveNewest, haveNewest,
@ -426,7 +426,7 @@ export class Timeline extends React.PureComponent<Props, State> {
return; return;
} }
markMessageRead(newest.id, forceFocus); markMessageRead(newest.id);
const rowCount = this.getRowCount(); const rowCount = this.getRowCount();
@ -710,19 +710,14 @@ export class Timeline extends React.PureComponent<Props, State> {
public componentDidMount() { public componentDidMount() {
this.updateWithVisibleRows(); this.updateWithVisibleRows();
// @ts-ignore // @ts-ignore
window.registerForFocus(this.forceFocusVisibleRowUpdate); window.registerForActive(this.updateWithVisibleRows);
} }
public componentWillUnmount() { public componentWillUnmount() {
// @ts-ignore // @ts-ignore
window.unregisterForFocus(this.forceFocusVisibleRowUpdate); window.unregisterForActive(this.updateWithVisibleRows);
} }
public forceFocusVisibleRowUpdate = () => {
const forceFocus = true;
this.updateWithVisibleRows(forceFocus);
};
// tslint:disable-next-line cyclomatic-complexity max-func-body-length // tslint:disable-next-line cyclomatic-complexity max-func-body-length
public componentDidUpdate(prevProps: Props) { public componentDidUpdate(prevProps: Props) {
const { const {

View file

@ -160,7 +160,7 @@ export type MessagesAddedActionType = {
conversationId: string; conversationId: string;
messages: Array<MessageType>; messages: Array<MessageType>;
isNewMessage: boolean; isNewMessage: boolean;
isFocused: boolean; isActive: boolean;
}; };
}; };
export type MessagesResetActionType = { export type MessagesResetActionType = {
@ -357,7 +357,7 @@ function messagesAdded(
conversationId: string, conversationId: string,
messages: Array<MessageType>, messages: Array<MessageType>,
isNewMessage: boolean, isNewMessage: boolean,
isFocused: boolean isActive: boolean
): MessagesAddedActionType { ): MessagesAddedActionType {
return { return {
type: 'MESSAGES_ADDED', type: 'MESSAGES_ADDED',
@ -365,7 +365,7 @@ function messagesAdded(
conversationId, conversationId,
messages, messages,
isNewMessage, isNewMessage,
isFocused, isActive,
}, },
}; };
} }
@ -870,12 +870,7 @@ export function reducer(
}; };
} }
if (action.type === 'MESSAGES_ADDED') { if (action.type === 'MESSAGES_ADDED') {
const { const { conversationId, isActive, isNewMessage, messages } = action.payload;
conversationId,
isFocused,
isNewMessage,
messages,
} = action.payload;
const { messagesByConversation, messagesLookup } = state; const { messagesByConversation, messagesLookup } = state;
const existingConversation = messagesByConversation[conversationId]; const existingConversation = messagesByConversation[conversationId];
@ -937,7 +932,7 @@ export function reducer(
const newMessageIds = difference(newIds, existingConversation.messageIds); const newMessageIds = difference(newIds, existingConversation.messageIds);
const { isNearBottom } = existingConversation; const { isNearBottom } = existingConversation;
if ((!isNearBottom || !isFocused) && !oldestUnread) { if ((!isNearBottom || !isActive) && !oldestUnread) {
const oldestId = newMessageIds.find(messageId => { const oldestId = newMessageIds.find(messageId => {
const message = lookup[messageId]; const message = lookup[messageId];