Fix chat ordering and optimize chat data storage

This commit is contained in:
Slava Monich 2020-09-27 17:18:14 +03:00
parent 3c0d046b3f
commit f10d8a4045
3 changed files with 183 additions and 162 deletions

View file

@ -63,7 +63,6 @@ Page {
repeat: false repeat: false
onTriggered: { onTriggered: {
overviewPage.chatListCreated = true; overviewPage.chatListCreated = true;
chatListModel.enableDeltaUpdates();
} }
} }

View file

@ -21,22 +21,76 @@
#include <QListIterator> #include <QListIterator>
#include <QDebug> #include <QDebug>
#define LOG(x) qDebug() << "[ChatListModel]" << x
namespace {
const QString ID("id");
const QString ORDER("order");
const QString CHAT_ID("chat_id");
const QString LAST_MESSAGE("last_message");
const QString UNREAD_COUNT("unread_count");
const QString NOTIFICATION_SETTINGS("notification_settings");
const QString LAST_READ_INBOX_MESSAGE_ID("last_read_inbox_message_id");
const QString LAST_READ_OUTBOX_MESSAGE_ID("last_read_outbox_message_id");
}
class ChatListModel::ChatData
{
public:
ChatData(const QVariantMap &data);
int compareTo(const ChatData *chat) const;
bool setOrder(const QString &order);
public:
QVariantMap chatData;
QString chatId;
qlonglong order;
};
ChatListModel::ChatData::ChatData(const QVariantMap &data) :
chatData(data),
chatId(data.value(ID).toString()),
order(data.value(ORDER).toLongLong())
{
}
int ChatListModel::ChatData::compareTo(const ChatData *other) const
{
if (order == other->order) {
return chatId.compare(other->chatId);
} else {
// This puts most recent ones to the top of the list
return (order < other->order) ? 1 : -1;
}
}
bool ChatListModel::ChatData::setOrder(const QString &newOrder)
{
if (!newOrder.isEmpty()) {
chatData.insert(ORDER, newOrder);
order = newOrder.toLongLong();
return true;
}
return false;
}
ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper) ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper)
{ {
this->tdLibWrapper = tdLibWrapper; tdLibWrapper = tdLibWrapper;
this->deltaUpdates = false; connect(tdLibWrapper, SIGNAL(newChatDiscovered(QString,QVariantMap)), this, SLOT(handleChatDiscovered(QString,QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(newChatDiscovered(QString, QVariantMap)), this, SLOT(handleChatDiscovered(QString, QVariantMap))); connect(tdLibWrapper, SIGNAL(chatLastMessageUpdated(QString,QString,QVariantMap)), this, SLOT(handleChatLastMessageUpdated(QString,QString,QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(chatLastMessageUpdated(QString, QString, QVariantMap)), this, SLOT(handleChatLastMessageUpdated(QString, QString, QVariantMap))); connect(tdLibWrapper, SIGNAL(chatOrderUpdated(QString,QString)), this, SLOT(handleChatOrderUpdated(QString,QString)));
connect(this->tdLibWrapper, SIGNAL(chatOrderUpdated(QString, QString)), this, SLOT(handleChatOrderUpdated(QString, QString))); connect(tdLibWrapper, SIGNAL(chatReadInboxUpdated(QString,QString,int)), this, SLOT(handleChatReadInboxUpdated(QString,QString,int)));
connect(this->tdLibWrapper, SIGNAL(chatReadInboxUpdated(QString, QString, int)), this, SLOT(handleChatReadInboxUpdated(QString, QString, int))); connect(tdLibWrapper, SIGNAL(chatReadOutboxUpdated(QString,QString)), this, SLOT(handleChatReadOutboxUpdated(QString,QString)));
connect(this->tdLibWrapper, SIGNAL(chatReadOutboxUpdated(QString, QString)), this, SLOT(handleChatReadOutboxUpdated(QString, QString))); connect(tdLibWrapper, SIGNAL(messageSendSucceeded(QString,QString,QVariantMap)), this, SLOT(handleMessageSendSucceeded(QString,QString,QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(messageSendSucceeded(QString, QString, QVariantMap)), this, SLOT(handleMessageSendSucceeded(QString, QString, QVariantMap))); connect(tdLibWrapper, SIGNAL(chatNotificationSettingsUpdated(QString,QVariantMap)), this, SLOT(handleChatNotificationSettingsUpdated(QString,QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(chatNotificationSettingsUpdated(QString, QVariantMap)), this, SLOT(handleChatNotificationSettingsUpdated(QString, QVariantMap)));
} }
ChatListModel::~ChatListModel() ChatListModel::~ChatListModel()
{ {
qDebug() << "[ChatListModel] Destroying myself..."; LOG("Destroying myself...");
qDeleteAll(chatList);
} }
int ChatListModel::rowCount(const QModelIndex &) const int ChatListModel::rowCount(const QModelIndex &) const
@ -46,185 +100,156 @@ int ChatListModel::rowCount(const QModelIndex &) const
QVariant ChatListModel::data(const QModelIndex &index, int role) const QVariant ChatListModel::data(const QModelIndex &index, int role) const
{ {
if(index.isValid() && role == Qt::DisplayRole) { const int row = index.row();
return QVariant(chatList.value(index.row())); if (row >= 0 && row < chatList.size() && role == Qt::DisplayRole) {
return chatList.at(row)->chatData;
} }
return QVariant(); return QVariant();
} }
bool ChatListModel::insertRows(int row, int count, const QModelIndex &parent)
{
qDebug() << "[ChatListModel] Inserting at " << row << ", row count: " << count;
beginInsertRows(parent, rowCount(QModelIndex()), rowCount(QModelIndex()) + count - 1);
this->chatList.append(this->chatToBeAdded);
this->chatIndexMap.insert(this->chatToBeAdded.value("id").toString(), row);
endInsertRows();
return true;
}
void ChatListModel::enableDeltaUpdates()
{
qDebug() << "[ChatListModel] Enabling delta updates and enforcing UI redraw...";
layoutChanged();
this->deltaUpdates = true;
}
void ChatListModel::redrawModel() void ChatListModel::redrawModel()
{ {
qDebug() << "[ChatListModel] Enforcing UI redraw..."; LOG("Enforcing UI redraw...");
layoutChanged(); layoutChanged();
} }
bool compareChats(const QVariant &chat1, const QVariant &chat2) int ChatListModel::updateChatOrder(int chatIndex)
{ {
QVariantMap chatMap1 = chat1.toMap(); ChatData* chat = chatList.at(chatIndex);
QVariantMap chatMap2 = chat2.toMap();
// Order comes first... const int n = chatList.size();
if (chatMap1.value("order").toLongLong() > chatMap2.value("order").toLongLong()) { int newIndex = chatIndex;
return true; while (newIndex > 0 && chat->compareTo(chatList.at(newIndex-1)) < 0) {
newIndex--;
} }
// ID comes next... if (newIndex == chatIndex) {
if (chatMap1.value("order").toLongLong() == chatMap2.value("order").toLongLong() && chatMap1.value("id").toLongLong() > chatMap2.value("id").toLongLong()) { while (newIndex < n-1 && chat->compareTo(chatList.at(newIndex+1)) > 0) {
return true; newIndex++;
}
}
if (newIndex != chatIndex) {
LOG("Moving chat" << chat->chatId << "from position" << chatIndex << "to" << newIndex);
beginMoveRows(QModelIndex(), chatIndex, chatIndex, QModelIndex(), (newIndex < chatIndex) ? newIndex : (newIndex+1));
chatList.move(chatIndex, newIndex);
chatIndexMap.insert(chat->chatId, newIndex);
// Update damaged part of the map
const int last = qMax(chatIndex, newIndex);
if (newIndex < chatIndex) {
// First index is already correct
for (int i = newIndex + 1; i <= last; i++) {
chatIndexMap.insert(chatList.at(i)->chatId, i);
}
} else {
// Last index is already correct
for (int i = chatIndex; i < last; i++) {
chatIndexMap.insert(chatList.at(i)->chatId, i);
}
}
endMoveRows();
} else { } else {
return false; LOG("Chat" << chat->chatId << "stays at position" << chatIndex);
} }
return newIndex;
} }
void ChatListModel::handleChatDiscovered(const QString &chatId, const QVariantMap &chatInformation) void ChatListModel::handleChatDiscovered(const QString &chatId, const QVariantMap &chatToBeAdded)
{ {
this->chatListMutex.lock(); ChatData* chat = new ChatData(chatToBeAdded);
qDebug() << "[ChatListModel] Adding new chat " << chatId; const int n = chatList.size();
this->chatToBeAdded = chatInformation; int chatIndex;
insertRows(rowCount(QModelIndex()), 1); for (chatIndex = 0; chatIndex < n && chat->compareTo(chatList.at(chatIndex)) >= 0; chatIndex++);
this->chatListMutex.unlock(); LOG("Adding new chat" << chatId << "at" << chatIndex);
beginInsertRows(QModelIndex(), chatIndex, chatIndex);
chatList.insert(chatIndex, chat);
chatIndexMap.insert(chat->chatId, chatIndex);
// Update damaged part of the map
for (int i = chatIndex + 1; i <= n; i++) {
chatIndexMap.insert(chatList.at(i)->chatId, i);
}
endInsertRows();
} }
void ChatListModel::handleChatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage) void ChatListModel::handleChatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage)
{ {
this->chatListMutex.lock(); if (chatIndexMap.contains(chatId)) {
int chatIndex = this->chatIndexMap.value(chatId).toInt(); int chatIndex = chatIndexMap.value(chatId);
qDebug() << "[ChatListModel] Updating last message for chat " << chatId << " at index " << chatIndex; LOG("Updating last message for chat" << chatId <<" at index" << chatIndex << "new order" << order);
QVariantMap currentChat = this->chatList.value(chatIndex).toMap(); ChatData* chat = chatList.at(chatIndex);
currentChat.insert("last_message", lastMessage); chat->chatData.insert(LAST_MESSAGE, lastMessage);
currentChat.insert("order", order); if (chat->setOrder(order)) {
this->chatList.replace(chatIndex, currentChat); chatIndex = updateChatOrder(chatIndex);
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); }
const QModelIndex modelIndex(index(chatIndex));
this->updateChatOrder(chatIndex, currentChat); emit dataChanged(modelIndex, modelIndex);
emit chatChanged(chatId); emit chatChanged(chatId);
}
this->chatListMutex.unlock();
} }
void ChatListModel::handleChatOrderUpdated(const QString &chatId, const QString &order) void ChatListModel::handleChatOrderUpdated(const QString &chatId, const QString &order)
{ {
this->chatListMutex.lock(); if (chatIndexMap.contains(chatId)) {
qDebug() << "[ChatListModel] Updating chat order because of " << chatId << " new order " << order; LOG("Updating chat order of" << chatId << "to" << order);
int chatIndex = this->chatIndexMap.value(chatId).toInt(); int chatIndex = chatIndexMap.value(chatId);
QVariantMap currentChat = this->chatList.at(chatIndex).toMap(); if (chatList.at(chatIndex)->setOrder(order)) {
currentChat.insert("order", order); chatIndex = updateChatOrder(chatIndex);
this->chatList.replace(chatIndex, currentChat); }
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(modelIndex, modelIndex);
this->updateChatOrder(chatIndex, currentChat); emit chatChanged(chatId);
emit chatChanged(chatId); }
this->chatListMutex.unlock();
} }
void ChatListModel::handleChatReadInboxUpdated(const QString &chatId, const QString &lastReadInboxMessageId, const int &unreadCount) void ChatListModel::handleChatReadInboxUpdated(const QString &chatId, const QString &lastReadInboxMessageId, const int &unreadCount)
{ {
this->chatListMutex.lock(); if (chatIndexMap.contains(chatId)) {
qDebug() << "[ChatListModel] Updating chat unread count for " << chatId << " unread messages " << unreadCount << ", last read message ID: " << lastReadInboxMessageId; LOG("Updating chat unread count for" << chatId << "unread messages" << unreadCount << ", last read message ID: " << lastReadInboxMessageId);
int chatIndex = this->chatIndexMap.value(chatId).toInt(); const int chatIndex = chatIndexMap.value(chatId);
QVariantMap currentChat = this->chatList.at(chatIndex).toMap(); ChatData* chat = chatList.at(chatIndex);
currentChat.insert("unread_count", unreadCount); chat->chatData.insert(UNREAD_COUNT, unreadCount);
currentChat.insert("last_read_inbox_message_id", lastReadInboxMessageId); chat->chatData.insert(LAST_READ_INBOX_MESSAGE_ID, lastReadInboxMessageId);
this->chatList.replace(chatIndex, currentChat); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); emit dataChanged(modelIndex, modelIndex);
emit chatChanged(chatId); emit chatChanged(chatId);
this->chatListMutex.unlock(); }
} }
void ChatListModel::handleChatReadOutboxUpdated(const QString &chatId, const QString &lastReadOutboxMessageId) void ChatListModel::handleChatReadOutboxUpdated(const QString &chatId, const QString &lastReadOutboxMessageId)
{ {
this->chatListMutex.lock(); if (chatIndexMap.contains(chatId)) {
qDebug() << "[ChatListModel] Updating last read message for " << chatId << " last ID " << lastReadOutboxMessageId; LOG("Updating last read message for" << chatId << "last ID" << lastReadOutboxMessageId);
int chatIndex = this->chatIndexMap.value(chatId).toInt(); const int chatIndex = chatIndexMap.value(chatId);
QVariantMap currentChat = this->chatList.at(chatIndex).toMap(); ChatData* chat = chatList.at(chatIndex);
currentChat.insert("last_read_outbox_message_id", lastReadOutboxMessageId); chat->chatData.insert(LAST_READ_OUTBOX_MESSAGE_ID, lastReadOutboxMessageId);
this->chatList.replace(chatIndex, currentChat); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); emit dataChanged(modelIndex, modelIndex);
emit chatChanged(chatId); emit chatChanged(chatId);
this->chatListMutex.unlock(); }
} }
void ChatListModel::handleMessageSendSucceeded(const QString &messageId, const QString &oldMessageId, const QVariantMap &message) void ChatListModel::handleMessageSendSucceeded(const QString &messageId, const QString &oldMessageId, const QVariantMap &message)
{ {
this->chatListMutex.lock(); const QString chatId(message.value(CHAT_ID).toString());
QString chatId = message.value("chat_id").toString(); if (chatIndexMap.contains(chatId)) {
int chatIndex = this->chatIndexMap.value(chatId).toInt(); const int chatIndex = chatIndexMap.value(chatId);
qDebug() << "[ChatListModel] Updating last message for chat " << chatId << " at index " << chatIndex << ", as message was sent, old ID: " << oldMessageId << ", new ID: " << messageId; LOG("Updating last message for chat" << chatId << "at index" << chatIndex << ", as message was sent, old ID:" << oldMessageId << ", new ID:" << messageId);
QVariantMap currentChat = this->chatList.value(chatIndex).toMap(); ChatData* chat = chatList.at(chatIndex);
currentChat.insert("last_message", message); chat->chatData.insert(LAST_MESSAGE, message);
this->chatList.replace(chatIndex, currentChat); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); emit dataChanged(modelIndex, modelIndex);
emit chatChanged(chatId); emit chatChanged(chatId);
}
this->chatListMutex.unlock();
} }
void ChatListModel::handleChatNotificationSettingsUpdated(const QString &chatId, const QVariantMap &chatNotificationSettings) void ChatListModel::handleChatNotificationSettingsUpdated(const QString &chatId, const QVariantMap &chatNotificationSettings)
{ {
this->chatListMutex.lock(); if (chatIndexMap.contains(chatId)) {
int chatIndex = this->chatIndexMap.value(chatId).toInt(); const int chatIndex = chatIndexMap.value(chatId);
qDebug() << "[ChatListModel] Updating notification settings for chat " << chatId << " at index " << chatIndex; LOG("Updating notification settings for chat" << chatId << "at index" << chatIndex);
QVariantMap currentChat = this->chatList.value(chatIndex).toMap(); ChatData* chat = chatList.at(chatIndex);
currentChat.insert("notification_settings", chatNotificationSettings); chat->chatData.insert(NOTIFICATION_SETTINGS, chatNotificationSettings);
this->chatList.replace(chatIndex, currentChat); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(this->index(chatIndex), this->index(chatIndex)); emit dataChanged(modelIndex, modelIndex);
emit chatChanged(chatId); emit chatChanged(chatId);
this->chatListMutex.unlock();
}
void ChatListModel::updateChatOrder(const int &currentChatIndex, const QVariantMap &updatedChat)
{
// Finding the new position manually as information is needed by beginMoveRows()
// This seems to be the most convenient way of persisting the list position while changing the items
// Other alternative layoutChanged() after sorting resets the index position - there we would need to calculate the new position as well
// If somebody has a better solution - go for it ;)
int newChatIndex = 0;
QVariantMap previousChat;
for (int i = 0; i < this->chatList.length(); i++) {
QVariantMap otherChat = this->chatList.at(i).toMap();
if (compareChats(updatedChat, otherChat)) {
if (previousChat.value("id") == updatedChat.value("id")) {
newChatIndex = currentChatIndex;
} else {
newChatIndex = i;
}
break;
}
previousChat = otherChat;
}
if (newChatIndex != currentChatIndex) {
// The updated chat now needs to go to the position of the other chat
qDebug() << "[ChatListModel] Chat " << updatedChat.value("id").toString() << " will be moved from position " << currentChatIndex << " to " << newChatIndex;
if (deltaUpdates) {
beginMoveRows(QModelIndex(), currentChatIndex, currentChatIndex, QModelIndex(), (( newChatIndex < currentChatIndex ) ? newChatIndex : ( newChatIndex + 1 )));
}
std::sort(this->chatList.begin(), this->chatList.end(), compareChats);
this->chatIndexMap.clear();
for (int i = 0; i < this->chatList.length(); i++) {
this->chatIndexMap.insert(this->chatList.at(i).toMap().value("id").toString(), i);
}
if (deltaUpdates) {
endMoveRows();
}
} }
} }

View file

@ -34,15 +34,13 @@ public:
virtual int rowCount(const QModelIndex&) const override; virtual int rowCount(const QModelIndex&) const override;
virtual QVariant data(const QModelIndex &index, int role) const override; virtual QVariant data(const QModelIndex &index, int role) const override;
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
Q_INVOKABLE void enableDeltaUpdates();
Q_INVOKABLE void redrawModel(); Q_INVOKABLE void redrawModel();
signals: signals:
void chatChanged(const QString &chatId); void chatChanged(const QString &chatId);
public slots: private slots:
void handleChatDiscovered(const QString &chatId, const QVariantMap &chatInformation); void handleChatDiscovered(const QString &chatId, const QVariantMap &chatInformation);
void handleChatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage); void handleChatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage);
void handleChatOrderUpdated(const QString &chatId, const QString &order); void handleChatOrderUpdated(const QString &chatId, const QString &order);
@ -52,15 +50,14 @@ public slots:
void handleChatNotificationSettingsUpdated(const QString &chatId, const QVariantMap &chatNotificationSettings); void handleChatNotificationSettingsUpdated(const QString &chatId, const QVariantMap &chatNotificationSettings);
private: private:
int updateChatOrder(int chatIndex);
private:
class ChatData;
TDLibWrapper *tdLibWrapper; TDLibWrapper *tdLibWrapper;
QVariantList chatList; QList<ChatData*> chatList;
QVariantMap chatToBeAdded; QHash<QString,int> chatIndexMap;
QVariantMap chatIndexMap;
QMutex chatListMutex;
bool deltaUpdates;
void updateChatOrder(const int &currentChatIndex, const QVariantMap &updatedChat);
}; };
#endif // CHATLISTMODEL_H #endif // CHATLISTMODEL_H