diff --git a/plugins/sms/smsplugin.cpp b/plugins/sms/smsplugin.cpp index fa3cc605..d5b5956d 100644 --- a/plugins/sms/smsplugin.cpp +++ b/plugins/sms/smsplugin.cpp @@ -1,127 +1,125 @@ /** * 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 . */ #include "smsplugin.h" #include #include #include #include #include #include #include #include "sendreplydialog.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_sms.json", registerPlugin< SmsPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS, "kdeconnect.plugin.sms") SmsPlugin::SmsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) , m_conversationInterface(new ConversationsDbusInterface(this)) { } SmsPlugin::~SmsPlugin() { } bool SmsPlugin::receivePacket(const NetworkPacket& np) { if (np.type() == PACKET_TYPE_SMS_MESSAGES) { return handleBatchMessages(np); } return true; } void SmsPlugin::sendSms(const QString& phoneNumber, const QString& messageBody) { NetworkPacket np(PACKET_TYPE_SMS_REQUEST, { {"sendSms", true}, {"phoneNumber", phoneNumber}, {"messageBody", messageBody} }); qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote"; sendPacket(np); } void SmsPlugin::requestAllConversations() { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATIONS); sendPacket(np); } void SmsPlugin::requestConversation (const qint64& conversationID) const { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATION); np.set("threadID", conversationID); sendPacket(np); - - return; } void SmsPlugin::forwardToTelepathy(const ConversationMessage& message) { // If we don't have a valid Telepathy interface, bail out if (!(m_telepathyInterface.isValid())) return; qCDebug(KDECONNECT_PLUGIN_SMS) << "Passing a text message to the telepathy interface"; connect(&m_telepathyInterface, SIGNAL(messageReceived(QString,QString)), SLOT(sendSms(QString,QString)), Qt::UniqueConnection); const QString messageBody = message.body(); const QString contactName; // TODO: When telepathy support is improved, look up the contact with KPeople const QString phoneNumber = message.address(); m_telepathyInterface.call(QDBus::NoBlock, QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody); } bool SmsPlugin::handleBatchMessages(const NetworkPacket& np) { const auto messages = np.get("messages"); QList messagesList; messagesList.reserve(messages.count()); for (const QVariant& body : messages) { ConversationMessage message(body.toMap()); if (message.containsTextBody()) { forwardToTelepathy(message); messagesList.append(message); } } m_conversationInterface->addMessages(messagesList); return true; } QString SmsPlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/sms"; } #include "smsplugin.moc" diff --git a/smsapp/qml/ConversationDisplay.qml b/smsapp/qml/ConversationDisplay.qml index 9bf834dd..30e67dbd 100644 --- a/smsapp/qml/ConversationDisplay.qml +++ b/smsapp/qml/ConversationDisplay.qml @@ -1,100 +1,148 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * * 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 . */ import QtQuick 2.1 import QtQuick.Controls 2.4 import QtQuick.Layouts 1.1 import org.kde.people 1.0 import org.kde.kirigami 2.4 as Kirigami import org.kde.kdeconnect.sms 1.0 Kirigami.ScrollablePage { id: page property alias personUri: person.personUri readonly property QtObject person: PersonData { id: person } property QtObject device property int conversationId property string phoneNumber title: person.person && person.person.name ? person.person.name : phoneNumber + /** + * Build a chat message which is representative of all chat messages + * + * In other words, one which I can use to get a reasonable height guess + */ + ChatMessage { + id: genericMessage + messageBody: "Generic Message Body" + dateTime: new Date('2000-0-0') + visible: false + enabled: false + } + function sendMessage() { console.log("sending sms", page.phoneNumber) model.sourceModel.sendReplyToConversation(message.text) message.text = "" } ListView { + id: viewport model: QSortFilterProxyModel { id: model sortOrder: Qt.AscendingOrder sortRole: ConversationModel.DateRole sourceModel: ConversationModel { deviceId: device.id() threadId: page.conversationId } } spacing: Kirigami.Units.largeSpacing delegate: ChatMessage { messageBody: model.display sentByMe: model.fromMe dateTime: new Date(model.date) + + ListView.onAdd: { + if (index == viewport.count - 1) + // This message is being inserted at the newest position + // We want to scroll to show it if the user is "almost" looking at it + + // Define some fudge area. If the message is being drawn offscreen but within + // this distance, we move to show it anyway. + // Selected to be genericMessage.height because that value scales for different + // font sizes / DPI / etc. -- Better ideas are welcome! + // Double the value works nicely + var offscreenFudge = 2 * genericMessage.height + + var viewportYBottom = viewport.contentY + viewport.height + + if (y < viewportYBottom + genericMessage.height) { + viewport.currentIndex = index + } + } } - // Set the view to start at the bottom of the page and track new elements if it was not manually scrolled up - currentIndex: atYEnd ? - count - 1 : - currentIndex + onMovementEnded: { + // Unset the highlightRangeMode if it was set previously + highlightRangeMode = ListView.ApplyRange + highlightMoveDuration: -1 // "Re-enable" the highlight animation + + if (atYBeginning) { + // "Lock" the view to the message currently at the beginning of the view + // This prevents the view from snapping to the top of the messages we are about to request + currentIndex = 0 // Index 0 is the beginning of the view + preferredHighlightBegin = visibleArea.yPosition + preferredHighlightEnd = preferredHighlightBegin + currentItem.height + highlightRangeMode = ListView.StrictlyEnforceRange + + highlightMoveDuration = 1 // This is not ideal: I would like to disable the highlight animation altogether + + // Get more messages + model.sourceModel.requestMoreMessages() + } + } } footer: RowLayout { enabled: page.device ScrollView { Layout.maximumHeight: page.height / 3 Layout.fillWidth: true Layout.fillHeight: true clip: true TextArea { id: message Layout.fillWidth: true wrapMode: TextEdit.Wrap placeholderText: i18n("Say hi...") Keys.onPressed: { if ((event.key == Qt.Key_Return) && !(event.modifiers & Qt.ShiftModifier)) { sendMessage() event.accepted = true } } } } Button { text: "Send" onClicked: { sendMessage() } } } }