Virtualize Messages List - only render what's visible
This commit is contained in:
parent
a976cfe6b6
commit
5ebd8bc690
73 changed files with 4717 additions and 2745 deletions
|
@ -27,13 +27,7 @@
|
|||
};
|
||||
|
||||
const { Util } = window.Signal;
|
||||
const {
|
||||
Conversation,
|
||||
Contact,
|
||||
Errors,
|
||||
Message,
|
||||
PhoneNumber,
|
||||
} = window.Signal.Types;
|
||||
const { Conversation, Contact, Message, PhoneNumber } = window.Signal.Types;
|
||||
const {
|
||||
deleteAttachmentData,
|
||||
getAbsoluteAttachmentPath,
|
||||
|
@ -277,6 +271,7 @@
|
|||
|
||||
this.messageCollection.remove(id);
|
||||
existing.trigger('expired');
|
||||
existing.cleanup();
|
||||
};
|
||||
|
||||
// If a fetch is in progress, then we need to wait until that's complete to
|
||||
|
@ -288,18 +283,33 @@
|
|||
},
|
||||
|
||||
async onNewMessage(message) {
|
||||
await this.updateLastMessage();
|
||||
|
||||
// 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);
|
||||
|
||||
await this.updateLastMessage();
|
||||
},
|
||||
|
||||
addSingleMessage(message) {
|
||||
const { id } = message;
|
||||
const existing = this.messageCollection.get(id);
|
||||
|
||||
const model = this.messageCollection.add(message, { merge: true });
|
||||
model.setToExpire();
|
||||
|
||||
if (!existing) {
|
||||
const { messagesAdded } = window.reduxActions.conversations;
|
||||
const isNewMessage = true;
|
||||
messagesAdded(
|
||||
this.id,
|
||||
[model.getReduxData()],
|
||||
isNewMessage,
|
||||
document.hasFocus()
|
||||
);
|
||||
}
|
||||
|
||||
return model;
|
||||
},
|
||||
|
||||
|
@ -310,7 +320,12 @@
|
|||
const { format } = PhoneNumber;
|
||||
const regionCode = storage.get('regionCode');
|
||||
const color = this.getColor();
|
||||
const typingKeys = Object.keys(this.contactTypingTimers || {});
|
||||
|
||||
const typingValues = _.values(this.contactTypingTimers || {});
|
||||
const typingMostRecent = _.first(_.sortBy(typingValues, 'timestamp'));
|
||||
const typingContact = typingMostRecent
|
||||
? ConversationController.getOrCreate(typingMostRecent.sender, 'private')
|
||||
: null;
|
||||
|
||||
const result = {
|
||||
id: this.id,
|
||||
|
@ -321,7 +336,7 @@
|
|||
color,
|
||||
type: this.isPrivate() ? 'direct' : 'group',
|
||||
isMe: this.isMe(),
|
||||
isTyping: typingKeys.length > 0,
|
||||
typingContact: typingContact ? typingContact.format() : null,
|
||||
lastUpdated: this.get('timestamp'),
|
||||
name: this.getName(),
|
||||
profileName: this.getProfileName(),
|
||||
|
@ -894,6 +909,9 @@
|
|||
sendMessage(body, attachments, quote, preview, sticker) {
|
||||
this.clearTypingTimers();
|
||||
|
||||
const { clearUnreadMetrics } = window.reduxActions.conversations;
|
||||
clearUnreadMetrics(this.id);
|
||||
|
||||
const destination = this.id;
|
||||
const expireTimer = this.get('expireTimer');
|
||||
const recipients = this.getRecipients();
|
||||
|
@ -1202,7 +1220,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const messages = await window.Signal.Data.getMessagesByConversation(
|
||||
const messages = await window.Signal.Data.getOlderMessagesByConversation(
|
||||
this.id,
|
||||
{ limit: 1, MessageCollection: Whisper.MessageCollection }
|
||||
);
|
||||
|
@ -1310,7 +1328,7 @@
|
|||
model.set({ id });
|
||||
|
||||
const message = MessageController.register(id, model);
|
||||
this.messageCollection.add(message);
|
||||
this.addSingleMessage(message);
|
||||
|
||||
// if change was made remotely, don't send it to the number/group
|
||||
if (receivedAt) {
|
||||
|
@ -1373,7 +1391,7 @@
|
|||
async endSession() {
|
||||
if (this.isPrivate()) {
|
||||
const now = Date.now();
|
||||
const message = this.messageCollection.add({
|
||||
const model = new Whisper.Message({
|
||||
conversationId: this.id,
|
||||
type: 'outgoing',
|
||||
sent_at: now,
|
||||
|
@ -1383,10 +1401,13 @@
|
|||
flags: textsecure.protobuf.DataMessage.Flags.END_SESSION,
|
||||
});
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||
const id = await window.Signal.Data.saveMessage(model.attributes, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
message.set({ id });
|
||||
model.set({ id });
|
||||
|
||||
const message = MessageController.register(model.id, model);
|
||||
this.addSingleMessage(message);
|
||||
|
||||
const options = this.getSendOptions();
|
||||
message.send(
|
||||
|
@ -1407,7 +1428,7 @@
|
|||
groupUpdate = this.pick(['name', 'avatar', 'members']);
|
||||
}
|
||||
const now = Date.now();
|
||||
const message = this.messageCollection.add({
|
||||
const model = new Whisper.Message({
|
||||
conversationId: this.id,
|
||||
type: 'outgoing',
|
||||
sent_at: now,
|
||||
|
@ -1415,10 +1436,14 @@
|
|||
group_update: groupUpdate,
|
||||
});
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||
const id = await window.Signal.Data.saveMessage(model.attributes, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
message.set({ id });
|
||||
|
||||
model.set({ id });
|
||||
|
||||
const message = MessageController.register(model.id, model);
|
||||
this.addSingleMessage(message);
|
||||
|
||||
const options = this.getSendOptions();
|
||||
message.send(
|
||||
|
@ -1443,7 +1468,7 @@
|
|||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
|
||||
const message = this.messageCollection.add({
|
||||
const model = new Whisper.Message({
|
||||
group_update: { left: 'You' },
|
||||
conversationId: this.id,
|
||||
type: 'outgoing',
|
||||
|
@ -1451,10 +1476,13 @@
|
|||
received_at: now,
|
||||
});
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message.attributes, {
|
||||
const id = await window.Signal.Data.saveMessage(model.attributes, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
message.set({ id });
|
||||
model.set({ id });
|
||||
|
||||
const message = MessageController.register(model.id, model);
|
||||
this.addSingleMessage(message);
|
||||
|
||||
const options = this.getSendOptions();
|
||||
message.send(
|
||||
|
@ -1830,57 +1858,6 @@
|
|||
this.set({ accessKey });
|
||||
},
|
||||
|
||||
async upgradeMessages(messages) {
|
||||
for (let max = messages.length, i = 0; i < max; i += 1) {
|
||||
const message = messages.at(i);
|
||||
const { attributes } = message;
|
||||
const { schemaVersion } = attributes;
|
||||
|
||||
if (schemaVersion < Message.VERSION_NEEDED_FOR_DISPLAY) {
|
||||
// Yep, we really do want to wait for each of these
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const upgradedMessage = await upgradeMessageSchema(attributes);
|
||||
message.set(upgradedMessage);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await window.Signal.Data.saveMessage(upgradedMessage, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async fetchMessages() {
|
||||
if (!this.id) {
|
||||
throw new Error('This conversation has no id!');
|
||||
}
|
||||
if (this.inProgressFetch) {
|
||||
window.log.warn('Attempting to start a parallel fetchMessages() call');
|
||||
return;
|
||||
}
|
||||
|
||||
this.inProgressFetch = this.messageCollection.fetchConversation(
|
||||
this.id,
|
||||
undefined,
|
||||
this.get('unreadCount')
|
||||
);
|
||||
|
||||
await this.inProgressFetch;
|
||||
|
||||
try {
|
||||
// We are now doing the work to upgrade messages before considering the load from
|
||||
// the database complete. Note that we do save messages back, so it is a
|
||||
// one-time hit. We do this so we have guarantees about message structure.
|
||||
await this.upgradeMessages(this.messageCollection);
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'fetchMessages: failed to upgrade messages',
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
}
|
||||
|
||||
this.inProgressFetch = null;
|
||||
},
|
||||
|
||||
hasMember(number) {
|
||||
return _.contains(this.get('members'), number);
|
||||
},
|
||||
|
@ -1908,10 +1885,6 @@
|
|||
},
|
||||
|
||||
async destroyMessages() {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(this.id, {
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
|
||||
this.messageCollection.reset([]);
|
||||
|
||||
this.set({
|
||||
|
@ -1922,6 +1895,10 @@
|
|||
await window.Signal.Data.updateConversation(this.id, this.attributes, {
|
||||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
|
||||
await window.Signal.Data.removeAllMessagesInConversation(this.id, {
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
},
|
||||
|
||||
getName() {
|
||||
|
@ -2102,10 +2079,6 @@
|
|||
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
|
||||
|
@ -2121,14 +2094,12 @@
|
|||
);
|
||||
if (!record) {
|
||||
// User was not previously typing before. State change!
|
||||
this.trigger('typing-update');
|
||||
this.trigger('change', this);
|
||||
}
|
||||
} else {
|
||||
delete this.contactTypingTimers[identifier];
|
||||
if (record) {
|
||||
// User was previously typing, and is no longer. State change!
|
||||
this.trigger('typing-update');
|
||||
this.trigger('change', this);
|
||||
}
|
||||
}
|
||||
|
@ -2143,7 +2114,6 @@
|
|||
delete this.contactTypingTimers[identifier];
|
||||
|
||||
// User was previously typing, but timed out or we received message. State change!
|
||||
this.trigger('typing-update');
|
||||
this.trigger('change', this);
|
||||
}
|
||||
},
|
||||
|
@ -2155,17 +2125,6 @@
|
|||
comparator(m) {
|
||||
return -m.get('timestamp');
|
||||
},
|
||||
|
||||
async destroyAll() {
|
||||
await Promise.all(
|
||||
this.models.map(conversation =>
|
||||
window.Signal.Data.removeConversation(conversation.id, {
|
||||
Conversation: Whisper.Conversation,
|
||||
})
|
||||
)
|
||||
);
|
||||
this.reset([]);
|
||||
},
|
||||
});
|
||||
|
||||
Whisper.Conversation.COLORS = COLORS.concat(['grey', 'default']).join(' ');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue