diff --git a/plugins/sms/conversationsdbusinterface.cpp b/plugins/sms/conversationsdbusinterface.cpp index f8937d3b..6a653309 100644 --- a/plugins/sms/conversationsdbusinterface.cpp +++ b/plugins/sms/conversationsdbusinterface.cpp @@ -1,127 +1,145 @@ /** * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "conversationsdbusinterface.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/conversationmessage.h" #include #include #include Q_LOGGING_CATEGORY(KDECONNECT_CONVERSATIONS, "kdeconnect.conversations") ConversationsDbusInterface::ConversationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()) , m_plugin(plugin) , m_lastId(0) , m_smsInterface(m_device->id()) { ConversationMessage::registerDbusType(); } ConversationsDbusInterface::~ConversationsDbusInterface() { } -QStringList ConversationsDbusInterface::activeConversations() +QVariantList ConversationsDbusInterface::activeConversations() { - return m_conversations.keys(); + QList toReturn; + toReturn.reserve(m_conversations.size()); + + for (auto it = m_conversations.cbegin(); it != m_conversations.cend(); ++it) { + const auto& conversation = it.value().values(); + if (conversation.isEmpty()) { + // This should really never happen because we create a conversation at the same time + // as adding a message, but better safe than sorry + qCWarning(KDECONNECT_CONVERSATIONS) + << "Conversation with ID" << it.key() << "is unexpectedly empty"; + break; + } + const QVariantMap& message = (*conversation.crbegin()).toVariant(); + toReturn.append(message); + } + + return toReturn; } void ConversationsDbusInterface::requestConversation(const QString& conversationID, int start, int end) { const auto messagesList = m_conversations[conversationID].values(); if (messagesList.isEmpty()) { // Since there are no messages in the conversation, it's likely that it is a junk ID, but go ahead anyway qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!" << conversationID; } + // TODO: Check local cache before requesting new messages + // TODO: Make Android interface capable of requesting small window of messages m_smsInterface.requestConversation(conversationID); // Messages are sorted in ascending order of keys, meaning the front of the list has the oldest // messages (smallest timestamp number) // Therefore, return the end of the list first (most recent messages) int i = start; - for(auto it = messagesList.crbegin(); it != messagesList.crend(); ++it) { + for(auto it = messagesList.crbegin() + start; it != messagesList.crend(); ++it) { Q_EMIT conversationMessageReceived(it->toVariant(), i); i++; if (i >= end) { break; } } } void ConversationsDbusInterface::addMessage(const ConversationMessage &message) { const QString& threadId = QString::number(message.threadID()); if (m_known_messages[threadId].contains(message.uID())) { // This message has already been processed. Don't do anything. return; } // Store the Message in the list corresponding to its thread bool newConversation = !m_conversations.contains(threadId); m_conversations[threadId].insert(message.date(), message); m_known_messages[threadId].insert(message.uID()); // Tell the world about what just happened if (newConversation) { - Q_EMIT conversationCreated(threadId); + Q_EMIT conversationCreated(message.toVariant()); } else { Q_EMIT conversationUpdated(message.toVariant()); } } void ConversationsDbusInterface::removeMessage(const QString& internalId) { // TODO: Delete the specified message from our internal structures } void ConversationsDbusInterface::replyToConversation(const QString& conversationID, const QString& message) { const auto messagesList = m_conversations[conversationID]; if (messagesList.isEmpty()) { // Since there are no messages in the conversation, we can't do anything sensible qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!"; return; } // Caution: // This method assumes that the address of any message (in this case, whichever one pops out // with .first()) will be the same. This works fine for single-target SMS but might break down // for group MMS, etc. const QString& address = messagesList.first().address(); m_smsInterface.sendSms(address, message); } void ConversationsDbusInterface::requestAllConversationThreads() { // Prepare the list of conversations by requesting the first in every thread m_smsInterface.requestAllConversations(); } QString ConversationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/sms/conversationsdbusinterface.h b/plugins/sms/conversationsdbusinterface.h index 65c1bb95..71b2b988 100644 --- a/plugins/sms/conversationsdbusinterface.h +++ b/plugins/sms/conversationsdbusinterface.h @@ -1,104 +1,107 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CONVERSATIONSDBUSINTERFACE_H #define CONVERSATIONSDBUSINTERFACE_H #include #include #include #include #include #include #include #include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" class KdeConnectPlugin; class Device; Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_CONVERSATIONS) class ConversationsDbusInterface : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.conversations") public: explicit ConversationsDbusInterface(KdeConnectPlugin* plugin); ~ConversationsDbusInterface() override; void addMessage(const ConversationMessage &message); void removeMessage(const QString& internalId); public Q_SLOTS: /** - * Return a list of the threadID for all valid conversations + * Return a list of the first message in every conversation + * + * Note that the return value is a list of QVariants, which in turn have a value of + * QVariantMap created from each message */ - QStringList activeConversations(); + QVariantList activeConversations(); void requestConversation(const QString &conversationID, int start, int end); /** * Send a new message to this conversation */ void replyToConversation(const QString& conversationID, const QString& message); /** * Send the request to the Telephony plugin to update the list of conversation threads */ void requestAllConversationThreads(); Q_SIGNALS: - Q_SCRIPTABLE void conversationCreated(const QString& threadID); + Q_SCRIPTABLE void conversationCreated(const QVariantMap& msg); Q_SCRIPTABLE void conversationRemoved(const QString& threadID); Q_SCRIPTABLE void conversationUpdated(const QVariantMap& msg) const; Q_SCRIPTABLE void conversationMessageReceived(const QVariantMap& msg, int pos) const; private /*methods*/: QString newId(); //Generates successive identifitiers to use as public ids private /*attributes*/: const Device* m_device; KdeConnectPlugin* m_plugin; /** * Mapping of threadID to the messages which make up that thread * * The messages are stored as a QMap of the timestamp to the actual message object so that * we can use .values() to get a sorted list of messages from least- to most-recent */ QHash> m_conversations; /** * Mapping of threadID to the set of uIDs known in the corresponding conversation */ QHash> m_known_messages; int m_lastId; SmsDbusInterface m_smsInterface; }; #endif // CONVERSATIONSDBUSINTERFACE_H diff --git a/smsapp/conversationlistmodel.cpp b/smsapp/conversationlistmodel.cpp index cc5806f0..50db39ab 100644 --- a/smsapp/conversationlistmodel.cpp +++ b/smsapp/conversationlistmodel.cpp @@ -1,180 +1,210 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "conversationlistmodel.h" #include #include "interfaces/conversationmessage.h" +#include "interfaces/dbusinterfaces.h" Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL, "kdeconnect.sms.conversations_list") ConversationListModel::ConversationListModel(QObject* parent) : QStandardItemModel(parent) , m_conversationsInterface(nullptr) { - qCCritical(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; + qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; auto roles = roleNames(); roles.insert(FromMeRole, "fromMe"); roles.insert(AddressRole, "address"); roles.insert(PersonUriRole, "personUri"); roles.insert(ConversationIdRole, "conversationId"); roles.insert(DateRole, "date"); setItemRoleNames(roles); ConversationMessage::registerDbusType(); } ConversationListModel::~ConversationListModel() { } void ConversationListModel::setDeviceId(const QString& deviceId) { - qCCritical(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; - if (deviceId == m_deviceId) + if (deviceId == m_deviceId) { return; + } + + qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; if (m_conversationsInterface) { - disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QString)), this, SLOT(handleCreatedConversation(QString))); + disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QVariantMap)), this, SLOT(handleCreatedConversation(QVariantMap))); + disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdated(QVariantMap))); delete m_conversationsInterface; + m_conversationsInterface = nullptr; } m_deviceId = deviceId; + + // This method still gets called *with a valid deviceID* when the device is not connected while the component is setting up + // Detect that case and don't do anything. + DeviceDbusInterface device(deviceId); + if (!(device.isValid() && device.isReachable())) { + return; + } + m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); - connect(m_conversationsInterface, SIGNAL(conversationCreated(QString)), this, SLOT(handleCreatedConversation(QString))); - connect(m_conversationsInterface, SIGNAL(conversationMessageReceived(QVariantMap,int)), this, SLOT(createRowFromMessage(QVariantMap,int))); + connect(m_conversationsInterface, SIGNAL(conversationCreated(QVariantMap)), this, SLOT(handleCreatedConversation(QVariantMap))); + connect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdated(QVariantMap))); prepareConversationsList(); m_conversationsInterface->requestAllConversationThreads(); } void ConversationListModel::prepareConversationsList() { - - QDBusPendingReply validThreadIDsReply = m_conversationsInterface->activeConversations(); - - setWhenAvailable(validThreadIDsReply, [this](const QStringList& convs) { - clear(); - for (const QString& conversationId : convs) { - handleCreatedConversation(conversationId); + if (!m_conversationsInterface->isValid()) { + qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Tried to prepareConversationsList with an invalid interface!"; + return; + } + QDBusPendingReply validThreadIDsReply = m_conversationsInterface->activeConversations(); + + setWhenAvailable(validThreadIDsReply, [this](const QVariantList& convs) { + clear(); // If we clear before we receive the reply, there might be a (several second) visual gap! + for (const QVariant& headMessage : convs) { + QDBusArgument data = headMessage.value(); + QVariantMap message; + data >> message; + handleCreatedConversation(message); } }, this); } -void ConversationListModel::handleCreatedConversation(const QString& conversationId) +void ConversationListModel::handleCreatedConversation(const QVariantMap& msg) +{ + createRowFromMessage(msg); +} + +void ConversationListModel::handleConversationUpdated(const QVariantMap& msg) { - m_conversationsInterface->requestConversation(conversationId, 0, 1); + createRowFromMessage(msg); } void ConversationListModel::printDBusError(const QDBusError& error) { qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << error; } QStandardItem * ConversationListModel::conversationForThreadId(qint32 threadId) { for(int i=0, c=rowCount(); idata(ConversationIdRole) == threadId) return it; } return nullptr; } -void ConversationListModel::createRowFromMessage(const QVariantMap& msg, int row) +void ConversationListModel::createRowFromMessage(const QVariantMap& msg) { - if (row != 0) - return; - const ConversationMessage message(msg); if (message.type() == -1) { // The Android side currently hacks in -1 if something weird comes up // TODO: Remove this hack when MMS support is implemented return; } bool toadd = false; QStandardItem* item = conversationForThreadId(message.threadID()); if (!item) { toadd = true; item = new QStandardItem(); QScopedPointer personData(lookupPersonByAddress(message.address())); if (personData) { item->setText(personData->name()); item->setIcon(QIcon(personData->photo())); item->setData(personData->personUri(), PersonUriRole); } else { item->setData(QString(), PersonUriRole); item->setText(message.address()); } item->setData(message.threadID(), ConversationIdRole); } - item->setData(message.address(), AddressRole); - item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); - item->setData(message.body(), Qt::ToolTipRole); - item->setData(message.date(), DateRole); + + // Update the message if the data is newer + // This will be true if a conversation receives a new message, but false when the user + // does something to trigger past conversation history loading + bool oldDateExists; + qint64 oldDate = item->data(DateRole).toLongLong(&oldDateExists); + if (!oldDateExists || message.date() >= oldDate) { + // If there was no old data or incoming data is newer, update the record + item->setData(message.address(), AddressRole); + item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); + item->setData(message.body(), Qt::ToolTipRole); + item->setData(message.date(), DateRole); + } if (toadd) appendRow(item); } KPeople::PersonData* ConversationListModel::lookupPersonByAddress(const QString& address) { const QString& canonicalAddress = canonicalizePhoneNumber(address); int rowIndex = 0; for (rowIndex = 0; rowIndex < m_people.rowCount(); rowIndex++) { const QString& uri = m_people.get(rowIndex, KPeople::PersonsModel::PersonUriRole).toString(); KPeople::PersonData* person = new KPeople::PersonData(uri); const QString& email = person->email(); const QString& phoneNumber = canonicalizePhoneNumber(person->contactCustomProperty("phoneNumber").toString()); // To decide if a phone number matches: // 1. Are they similar lengths? If two numbers are very different, probably one is junk data and should be ignored // 2. Is one a superset of the other? Phone number digits get more specific the further towards the end of the string, // so if one phone number ends with the other, it is probably just a more-complete version of the same thing const QString& longerNumber = canonicalAddress.length() >= phoneNumber.length() ? canonicalAddress : phoneNumber; const QString& shorterNumber = canonicalAddress.length() < phoneNumber.length() ? canonicalAddress : phoneNumber; bool matchingPhoneNumber = longerNumber.endsWith(shorterNumber) && shorterNumber.length() * 2 >= longerNumber.length(); if (address == email || matchingPhoneNumber) { qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Matched" << address << "to" << person->name(); return person; } delete person; } return nullptr; } QString ConversationListModel::canonicalizePhoneNumber(const QString& phoneNumber) { QString toReturn(phoneNumber); toReturn = toReturn.remove(' '); toReturn = toReturn.remove('-'); toReturn = toReturn.remove('('); toReturn = toReturn.remove(')'); toReturn = toReturn.remove('+'); toReturn = toReturn.remove(QRegularExpression("^0*")); // Strip leading zeroes return toReturn; } diff --git a/smsapp/conversationlistmodel.h b/smsapp/conversationlistmodel.h index 21bceede..49733280 100644 --- a/smsapp/conversationlistmodel.h +++ b/smsapp/conversationlistmodel.h @@ -1,117 +1,118 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef CONVERSATIONLISTMODEL_H #define CONVERSATIONLISTMODEL_H #include #include #include #include #include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) public: Qt::SortOrder sortOrder() const { return m_sortOrder; } void setSortOrder(Qt::SortOrder sortOrder) { if (m_sortOrder != sortOrder) { m_sortOrder = sortOrder; sortNow(); } } void classBegin() override {} void componentComplete() override { m_completed = true; sortNow(); } private: void sortNow() { if (m_completed && dynamicSortFilter()) sort(0, m_sortOrder); } bool m_completed = false; Qt::SortOrder m_sortOrder = Qt::AscendingOrder; }; class ConversationListModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId) public: ConversationListModel(QObject* parent = nullptr); ~ConversationListModel(); enum Roles { FromMeRole = Qt::UserRole, PersonUriRole, AddressRole, ConversationIdRole, DateRole, }; Q_ENUM(Roles) QString deviceId() const { return m_deviceId; } void setDeviceId(const QString &/*deviceId*/); public Q_SLOTS: - void handleCreatedConversation(const QString& conversationId); - void createRowFromMessage(const QVariantMap& message, int row); + void handleCreatedConversation(const QVariantMap& msg); + void handleConversationUpdated(const QVariantMap& msg); + void createRowFromMessage(const QVariantMap& message); void printDBusError(const QDBusError& error); private: /** * Get all conversations currently known by the conversationsInterface, if any */ void prepareConversationsList(); /** * Get the data for a particular person given their contact address */ KPeople::PersonData* lookupPersonByAddress(const QString& address); /** * Simplify a phone number to a known form */ QString canonicalizePhoneNumber(const QString& phoneNumber); QStandardItem* conversationForThreadId(qint32 threadId); DeviceConversationsDbusInterface* m_conversationsInterface; QString m_deviceId; KPeople::PersonsModel m_people; }; #endif // CONVERSATIONLISTMODEL_H diff --git a/smsapp/conversationmodel.cpp b/smsapp/conversationmodel.cpp index 675bf596..266c4532 100644 --- a/smsapp/conversationmodel.cpp +++ b/smsapp/conversationmodel.cpp @@ -1,113 +1,117 @@ /* * This file is part of KDE Telepathy Chat * * Copyright (C) 2018 Aleix Pol Gonzalez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "conversationmodel.h" #include #include "interfaces/conversationmessage.h" Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATION_MODEL, "kdeconnect.sms.conversation") ConversationModel::ConversationModel(QObject* parent) : QStandardItemModel(parent) , m_conversationsInterface(nullptr) { auto roles = roleNames(); roles.insert(FromMeRole, "fromMe"); roles.insert(DateRole, "date"); setItemRoleNames(roles); } ConversationModel::~ConversationModel() { } QString ConversationModel::threadId() const { return m_threadId; } void ConversationModel::setThreadId(const QString &threadId) { if (m_threadId == threadId) return; m_threadId = threadId; clear(); if (!threadId.isEmpty()) { m_conversationsInterface->requestConversation(threadId, 0, 10); } } void ConversationModel::setDeviceId(const QString& deviceId) { if (deviceId == m_deviceId) return; qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "setDeviceId" << "of" << this; - if (m_conversationsInterface) delete m_conversationsInterface; + if (m_conversationsInterface) { + disconnect(m_conversationsInterface, SIGNAL(conversationMessageReceived(QVariantMap, int)), this, SLOT(createRowFromMessage(QVariantMap, int))); + disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdate(QVariantMap))); + delete m_conversationsInterface; + } m_deviceId = deviceId; m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); connect(m_conversationsInterface, SIGNAL(conversationMessageReceived(QVariantMap,int)), this, SLOT(createRowFromMessage(QVariantMap,int))); connect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdate(QVariantMap))); } void ConversationModel::sendReplyToConversation(const QString& message) { qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Trying to send" << message << "to conversation with ID" << m_threadId; m_conversationsInterface->replyToConversation(m_threadId, message); } void ConversationModel::createRowFromMessage(const QVariantMap& msg, int pos) { const ConversationMessage message(msg); if (!(message.threadID() == m_threadId.toInt())) { // 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; } auto item = new QStandardItem; item->setText(message.body()); item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); item->setData(message.date(), DateRole); insertRow(pos, item); } void ConversationModel::handleConversationUpdate(const QVariantMap& msg) { const ConversationMessage message(msg); if (!(message.threadID() == m_threadId.toInt())) { // 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(msg, 0); } diff --git a/smsapp/qml/ContactList.qml b/smsapp/qml/ContactList.qml deleted file mode 100644 index 12623268..00000000 --- a/smsapp/qml/ContactList.qml +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is part of KDE Telepathy Chat - * - * Copyright (C) 2015 Aleix Pol Gonzalez - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.1 -import org.kde.people 1.0 -import org.kde.plasma.core 2.0 as Core -import org.kde.kirigami 2.2 as Kirigami -import org.kde.kdeconnect 1.0 - -Kirigami.ScrollablePage -{ - Component { - id: chatView - ConversationDisplay {} - } - - ListView { - id: view - currentIndex: 0 - - model: PersonsSortFilterProxyModel { - requiredProperties: ["phoneNumber"] - sortRole: Qt.DisplayRole - sortCaseSensitivity: Qt.CaseInsensitive - sourceModel: PersonsModel {} - } - - header: TextField { - id: filter - placeholderText: i18n("Filter...") - width: parent.width - onTextChanged: { - view.model.filterRegExp = new RegExp(filter.text) - view.currentIndex = 0 - } - Keys.onUpPressed: view.currentIndex = Math.max(view.currentIndex-1, 0) - Keys.onDownPressed: view.currentIndex = Math.min(view.currentIndex+1, view.count-1) - onAccepted: { - view.currentItem.startChat() - } - Shortcut { - sequence: "Ctrl+F" - onActivated: filter.forceActiveFocus() - } - } - - delegate: Kirigami.BasicListItem - { - hoverEnabled: true - - readonly property var person: PersonData { - personUri: model.personUri - } - - label: display - icon: decoration - function startChat() { - applicationWindow().pageStack.push(chatView, { person: person.person, device: Qt.binding(function() {return devicesCombo.device })}) - } - onClicked: { startChat(); } - } - - } - footer: ComboBox { - id: devicesCombo - readonly property QtObject device: currentIndex>=0 ? model.data(model.index(currentIndex, 0), DevicesModel.DeviceRole) : null - enabled: count > 0 - displayText: enabled ? undefined : i18n("No devices available") - model: DevicesSortProxyModel { - //TODO: make it possible to sort only if they can do sms - sourceModel: DevicesModel { displayFilter: DevicesModel.Paired | DevicesModel.Reachable } - onRowsInserted: if (devicesCombo.currentIndex < 0) { - devicesCombo.currentIndex = 0 - } - } - textRole: "display" - } -}