Typing Indicators
This commit is contained in:
parent
99252702e1
commit
79a861a870
23 changed files with 906 additions and 121 deletions
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue