2e67f95017
The rationale is explained in https://planet.kde.org/friedrich-kossebau-2023-06-28-include-also-moc-files-of-headers/ In case of KDEConnect, it impressively speeds up compilation. Before it took 390 seconds on a clean build and with this change it took 330 seconds. This is due to the mocs_compilation having to include the header files and thus all their headers. Due to the lots of small plugins we have, this means that the same headers must be compiled plenty of times. When we include the moc files directly in the C++ file, they are already available.
241 lines
8.7 KiB
C++
241 lines
8.7 KiB
C++
/**
|
|
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
|
|
* SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
*/
|
|
|
|
#include "conversationmodel.h"
|
|
|
|
#include <KLocalizedString>
|
|
#include <QQmlApplicationEngine>
|
|
#include <QQmlContext>
|
|
|
|
#include "attachmentinfo.h"
|
|
#include "interfaces/conversationmessage.h"
|
|
#include "smshelper.h"
|
|
|
|
#include "sms_conversation_debug.h"
|
|
|
|
ConversationModel::ConversationModel(QObject *parent)
|
|
: QStandardItemModel(parent)
|
|
, m_conversationsInterface(nullptr)
|
|
{
|
|
auto roles = roleNames();
|
|
roles.insert(FromMeRole, "fromMe");
|
|
roles.insert(DateRole, "date");
|
|
roles.insert(SenderRole, "sender");
|
|
roles.insert(AvatarRole, "avatar");
|
|
roles.insert(AttachmentsRole, "attachments");
|
|
setItemRoleNames(roles);
|
|
}
|
|
|
|
ConversationModel::~ConversationModel()
|
|
{
|
|
}
|
|
|
|
qint64 ConversationModel::threadId() const
|
|
{
|
|
return m_threadId;
|
|
}
|
|
|
|
void ConversationModel::setThreadId(const qint64 &threadId)
|
|
{
|
|
if (m_threadId == threadId)
|
|
return;
|
|
|
|
m_threadId = threadId;
|
|
clear();
|
|
knownMessageIDs.clear();
|
|
if (m_threadId != INVALID_THREAD_ID && !m_deviceId.isEmpty()) {
|
|
requestMoreMessages();
|
|
m_thumbnailsProvider->clear();
|
|
}
|
|
}
|
|
|
|
void ConversationModel::setDeviceId(const QString &deviceId)
|
|
{
|
|
if (deviceId == m_deviceId)
|
|
return;
|
|
|
|
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "setDeviceId"
|
|
<< "of" << this;
|
|
if (m_conversationsInterface) {
|
|
disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant)));
|
|
disconnect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64)));
|
|
disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant)));
|
|
delete m_conversationsInterface;
|
|
}
|
|
|
|
m_deviceId = deviceId;
|
|
|
|
m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this);
|
|
connect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant)));
|
|
connect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64)));
|
|
connect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant)));
|
|
|
|
connect(m_conversationsInterface, SIGNAL(attachmentReceived(QString, QString)), this, SIGNAL(filePathReceived(QString, QString)));
|
|
|
|
QQmlApplicationEngine *engine = qobject_cast<QQmlApplicationEngine *>(QQmlEngine::contextForObject(this)->engine());
|
|
m_thumbnailsProvider = dynamic_cast<ThumbnailsProvider *>(engine->imageProvider(QStringLiteral("thumbnailsProvider")));
|
|
|
|
// Clear any previous data on device change
|
|
m_thumbnailsProvider->clear();
|
|
|
|
Q_EMIT deviceIdChanged(deviceId);
|
|
}
|
|
|
|
void ConversationModel::setAddressList(const QList<ConversationAddress> &addressList)
|
|
{
|
|
m_addressList = addressList;
|
|
}
|
|
|
|
bool ConversationModel::sendReplyToConversation(const QString &textMessage, QList<QUrl> attachmentUrls)
|
|
{
|
|
QVariantList fileUrls;
|
|
for (const auto &url : attachmentUrls) {
|
|
fileUrls << QVariant::fromValue(url.toLocalFile());
|
|
}
|
|
|
|
m_conversationsInterface->replyToConversation(m_threadId, textMessage, fileUrls);
|
|
return true;
|
|
}
|
|
|
|
bool ConversationModel::startNewConversation(const QString &textMessage, const QList<ConversationAddress> &addressList, QList<QUrl> attachmentUrls)
|
|
{
|
|
QVariantList addresses;
|
|
|
|
for (const auto &address : addressList) {
|
|
addresses << QVariant::fromValue(address);
|
|
}
|
|
|
|
QVariantList fileUrls;
|
|
for (const auto &url : attachmentUrls) {
|
|
fileUrls << QVariant::fromValue(url.toLocalFile());
|
|
}
|
|
|
|
m_conversationsInterface->sendWithoutConversation(addresses, textMessage, fileUrls);
|
|
return true;
|
|
}
|
|
|
|
void ConversationModel::requestMoreMessages(const quint32 &howMany)
|
|
{
|
|
if (m_threadId == INVALID_THREAD_ID) {
|
|
return;
|
|
}
|
|
const auto &numMessages = knownMessageIDs.size();
|
|
m_conversationsInterface->requestConversation(m_threadId, numMessages, numMessages + howMany);
|
|
}
|
|
|
|
void ConversationModel::createRowFromMessage(const ConversationMessage &message, int pos)
|
|
{
|
|
if (message.threadID() != m_threadId) {
|
|
// Because of the asynchronous nature of the current implementation of this model, if the
|
|
// user clicks quickly between threads or for some other reason a message comes when we're
|
|
// not expecting it, we should not display it in the wrong place
|
|
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Got a message for a thread" << message.threadID() << "but we are currently viewing" << m_threadId
|
|
<< "Discarding.";
|
|
return;
|
|
}
|
|
|
|
if (knownMessageIDs.contains(message.uID())) {
|
|
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Ignoring duplicate message with ID" << message.uID();
|
|
return;
|
|
}
|
|
|
|
ConversationAddress sender;
|
|
if (!message.addresses().isEmpty()) {
|
|
sender = message.addresses().first();
|
|
} else {
|
|
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Conversation with ID " << message.threadID() << " did not have any addresses";
|
|
}
|
|
|
|
QString senderName = message.isIncoming() ? SmsHelper::getTitleForAddresses({sender}) : QString();
|
|
QString displayBody = message.body();
|
|
|
|
auto item = new QStandardItem;
|
|
item->setText(displayBody);
|
|
item->setData(message.isOutgoing(), FromMeRole);
|
|
item->setData(message.date(), DateRole);
|
|
item->setData(senderName, SenderRole);
|
|
|
|
QList<QVariant> attachmentInfoList;
|
|
const QList<Attachment> attachmentList = message.attachments();
|
|
|
|
for (const Attachment &attachment : attachmentList) {
|
|
AttachmentInfo attachmentInfo(attachment);
|
|
attachmentInfoList.append(QVariant::fromValue(attachmentInfo));
|
|
|
|
if (attachment.mimeType().startsWith(QLatin1String("image")) || attachment.mimeType().startsWith(QLatin1String("video"))) {
|
|
// The message contains thumbnail as Base64 String, convert it back into image thumbnail
|
|
const QByteArray byteArray = attachment.base64EncodedFile().toUtf8();
|
|
QPixmap thumbnail;
|
|
thumbnail.loadFromData(QByteArray::fromBase64(byteArray));
|
|
|
|
m_thumbnailsProvider->addImage(attachment.uniqueIdentifier(), thumbnail.toImage());
|
|
}
|
|
}
|
|
|
|
item->setData(attachmentInfoList, AttachmentsRole);
|
|
|
|
insertRow(pos, item);
|
|
knownMessageIDs.insert(message.uID());
|
|
}
|
|
|
|
void ConversationModel::handleConversationUpdate(const QDBusVariant &msg)
|
|
{
|
|
ConversationMessage message = ConversationMessage::fromDBus(msg);
|
|
|
|
if (message.threadID() != m_threadId) {
|
|
// If a conversation which we are not currently viewing was updated, discard the information
|
|
qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Saw update for thread" << message.threadID() << "but we are currently viewing" << m_threadId;
|
|
return;
|
|
}
|
|
createRowFromMessage(message, 0);
|
|
}
|
|
|
|
void ConversationModel::handleConversationCreated(const QDBusVariant &msg)
|
|
{
|
|
ConversationMessage message = ConversationMessage::fromDBus(msg);
|
|
|
|
if (m_threadId == INVALID_THREAD_ID && SmsHelper::isPhoneNumberMatch(m_addressList[0].address(), message.addresses().first().address())
|
|
&& !message.isMultitarget()) {
|
|
m_threadId = message.threadID();
|
|
createRowFromMessage(message, 0);
|
|
}
|
|
}
|
|
|
|
void ConversationModel::handleConversationLoaded(qint64 threadID, quint64 numMessages)
|
|
{
|
|
Q_UNUSED(numMessages)
|
|
if (threadID != m_threadId) {
|
|
return;
|
|
}
|
|
// If we get this flag, it means that the phone will not be responding with any more messages
|
|
// so we should not be showing a loading indicator
|
|
Q_EMIT loadingFinished();
|
|
}
|
|
|
|
QString ConversationModel::getCharCountInfo(const QString &message) const
|
|
{
|
|
SmsCharCount count = SmsHelper::getCharCount(message);
|
|
|
|
if (count.messages > 1) {
|
|
// Show remaining char count and message count
|
|
return QString::number(count.remaining) + QLatin1Char('/') + QString::number(count.messages);
|
|
}
|
|
if (count.messages == 1 && count.remaining < 10) {
|
|
// Show only remaining char count
|
|
return QString::number(count.remaining);
|
|
} else {
|
|
// Do not show anything
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
void ConversationModel::requestAttachmentPath(const qint64 &partID, const QString &uniqueIdentifier)
|
|
{
|
|
m_conversationsInterface->requestAttachmentFile(partID, uniqueIdentifier);
|
|
}
|
|
|
|
#include "moc_conversationmodel.cpp"
|