New Idle timer; messages not marked read if user is idle
This commit is contained in:
parent
b77246a7e0
commit
8ccb89310b
11 changed files with 55 additions and 55 deletions
|
@ -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>
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
|
||||||
})();
|
|
|
@ -334,7 +334,7 @@
|
||||||
this.id,
|
this.id,
|
||||||
[model.getReduxData()],
|
[model.getReduxData()],
|
||||||
isNewMessage,
|
isNewMessage,
|
||||||
document.hasFocus()
|
window.isActive()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue