Typing Indicators

This commit is contained in:
Scott Nonnenberg 2018-11-14 11:10:32 -08:00
parent 99252702e1
commit 79a861a870
23 changed files with 906 additions and 121 deletions

View file

@ -131,12 +131,93 @@
this.unset('tokens');
this.unset('lastMessage');
this.unset('lastMessageStatus');
this.typingRefreshTimer = null;
this.typingPauseTimer = null;
},
isMe() {
return this.id === this.ourNumber;
},
bumpTyping() {
// We don't send typing messages if the setting is disabled
if (!storage.get('typingIndicators')) {
return;
}
if (!this.typingRefreshTimer) {
const isTyping = true;
this.setTypingRefreshTimer();
this.sendTypingMessage(isTyping);
}
this.setTypingPauseTimer();
},
setTypingRefreshTimer() {
if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer);
}
this.typingRefreshTimer = setTimeout(
this.onTypingRefreshTimeout.bind(this),
10 * 1000
);
},
onTypingRefreshTimeout() {
const isTyping = true;
this.sendTypingMessage(isTyping);
// This timer will continue to reset itself until the pause timer stops it
this.setTypingRefreshTimer();
},
setTypingPauseTimer() {
if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer);
}
this.typingPauseTimer = setTimeout(
this.onTypingPauseTimeout.bind(this),
3 * 1000
);
},
onTypingPauseTimeout() {
const isTyping = false;
this.sendTypingMessage(isTyping);
this.clearTypingTimers();
},
clearTypingTimers() {
if (this.typingPauseTimer) {
clearTimeout(this.typingPauseTimer);
this.typingPauseTimer = null;
}
if (this.typingRefreshTimer) {
clearTimeout(this.typingRefreshTimer);
this.typingRefreshTimer = null;
}
},
sendTypingMessage(isTyping) {
const groupId = !this.isPrivate() ? this.id : null;
const recipientId = this.isPrivate() ? this.id : null;
const sendOptions = this.getSendOptions();
this.wrapSend(
textsecure.messaging.sendTypingMessage(
{
groupId,
isTyping,
recipientId,
},
sendOptions
)
);
},
async cleanup() {
await window.Signal.Types.Conversation.deleteExternalFiles(
this.attributes,
@ -189,6 +270,12 @@
},
addSingleMessage(message) {
// Clear typing indicator for a given contact if we receive a message from them
const identifier = message.get
? `${message.get('source')}.${message.get('sourceDevice')}`
: `${message.source}.${message.sourceDevice}`;
this.clearContactTypingTimer(identifier);
const model = this.messageCollection.add(message, { merge: true });
model.setToExpire();
return model;
@ -211,6 +298,8 @@
};
},
getPropsForListItem() {
const typingKeys = Object.keys(this.contactTypingTimers || {});
const result = {
...this.format(),
conversationType: this.isPrivate() ? 'direct' : 'group',
@ -219,6 +308,7 @@
unreadCount: this.get('unreadCount') || 0,
isSelected: this.isSelected,
isTyping: typingKeys.length > 0,
lastMessage: {
status: this.lastMessageStatus,
text: this.lastMessage,
@ -698,6 +788,8 @@
},
sendMessage(body, attachments, quote) {
this.clearTypingTimers();
const destination = this.id;
const expireTimer = this.get('expireTimer');
const recipients = this.getRecipients();
@ -1753,6 +1845,69 @@
})
);
},
notifyTyping(options = {}) {
const { isTyping, sender, senderDevice } = options;
// We don't do anything with typing messages from our other devices
if (sender === this.ourNumber) {
return;
}
const identifier = `${sender}.${senderDevice}`;
this.contactTypingTimers = this.contactTypingTimers || {};
const record = this.contactTypingTimers[identifier];
if (record) {
clearTimeout(record.timer);
}
// Note: We trigger two events because:
// 'typing-update' is a surgical update ConversationView does for in-convo bubble
// 'change' causes a re-render of this conversation's list item in the left pane
if (isTyping) {
this.contactTypingTimers[identifier] = this.contactTypingTimers[
identifier
] || {
timestamp: Date.now(),
sender,
senderDevice,
};
this.contactTypingTimers[identifier].timer = setTimeout(
this.clearContactTypingTimer.bind(this, identifier),
15 * 1000
);
if (!record) {
// User was not previously typing before. State change!
this.trigger('typing-update');
this.trigger('change');
}
} else {
delete this.contactTypingTimers[identifier];
if (record) {
// User was previously typing, and is no longer. State change!
this.trigger('typing-update');
this.trigger('change');
}
}
},
clearContactTypingTimer(identifier) {
this.contactTypingTimers = this.contactTypingTimers || {};
const record = this.contactTypingTimers[identifier];
if (record) {
clearTimeout(record.timer);
delete this.contactTypingTimers[identifier];
// User was previously typing, but timed out or we received message. State change!
this.trigger('typing-update');
this.trigger('change');
}
},
});
Whisper.ConversationCollection = Backbone.Collection.extend({