Adding support to send attachments to the remote device.

This commit is contained in:
Aniket Kumar 2020-08-31 15:35:25 +05:30
parent 60a1adf229
commit 706fc314fb
14 changed files with 343 additions and 122 deletions

View file

@ -53,6 +53,7 @@ int main(int argc, char** argv)
parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device"))); parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device")));
parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message"))); parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message")));
parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number"))); parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number")));
parser.addOption(QCommandLineOption(QStringLiteral("attachment"), i18n("File urls to send attachments with the message"), i18n("file urls")));
parser.addOption(QCommandLineOption(QStringList(QStringLiteral("device")) << QStringLiteral("d"), i18n("Device ID"), QStringLiteral("dev"))); parser.addOption(QCommandLineOption(QStringList(QStringLiteral("device")) << QStringLiteral("d"), i18n("Device ID"), QStringLiteral("dev")));
parser.addOption(QCommandLineOption(QStringList(QStringLiteral("name")) << QStringLiteral("n"), i18n("Device Name"), QStringLiteral("name"))); parser.addOption(QCommandLineOption(QStringList(QStringLiteral("name")) << QStringLiteral("n"), i18n("Device Name"), QStringLiteral("name")));
parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device"))); parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device")));
@ -258,9 +259,11 @@ int main(int argc, char** argv)
addresses << QVariant::fromValue(address); addresses << QVariant::fromValue(address);
} }
const QStringList urlList = parser.value(QStringLiteral("attachment")).split(QRegularExpression(QStringLiteral("\\s+")));
QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/sms"), QStringLiteral("org.kde.kdeconnect.device.sms"), QStringLiteral("sendSms")); QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/sms"), QStringLiteral("org.kde.kdeconnect.device.sms"), QStringLiteral("sendSms"));
const QString text = parser.value(QStringLiteral("send-sms")); const QString text = parser.value(QStringLiteral("send-sms"));
msg.setArguments(QVariantList() << QVariant::fromValue(addresses) << text); msg.setArguments(QVariantList() << QVariant::fromValue(addresses) << text << QVariant(urlList));
blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); blockOnReply(DBusHelper::sessionBus().asyncCall(msg));
} else { } else {
QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination <phone number>"); QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination <phone number>");

View file

@ -72,6 +72,15 @@ ConversationAddress::ConversationAddress(QString address)
: m_address(address) : m_address(address)
{} {}
bool ConversationMessage::isOutgoing() const
{
return type() == MessageTypeSent
|| type() == MessageTypeOutbox
|| type() == MessageTypeDraft
|| type() == MessageTypeFailed
|| type() == MessageTypeQueued;
}
Attachment::Attachment(qint64 partID, QString mimeType, QString base64EncodedFile, QString uniqueIdentifier) Attachment::Attachment(qint64 partID, QString mimeType, QString base64EncodedFile, QString uniqueIdentifier)
: m_partID(partID) : m_partID(partID)
, m_mimeType(mimeType) , m_mimeType(mimeType)

View file

@ -69,7 +69,7 @@ public:
bool isMultitarget() const { return (eventField() & ConversationMessage::EventMultiTarget); } bool isMultitarget() const { return (eventField() & ConversationMessage::EventMultiTarget); }
bool isIncoming() const { return type() == MessageTypeInbox; } bool isIncoming() const { return type() == MessageTypeInbox; }
bool isOutgoing() const { return type() == MessageTypeSent; } bool isOutgoing() const;
bool containsAttachment() const { return !attachments().isEmpty(); } bool containsAttachment() const { return !attachments().isEmpty(); }
/** /**

View file

@ -177,7 +177,7 @@ void ConversationsDbusInterface::updateConversation(const qint64& conversationID
waitingForMessagesLock.unlock(); waitingForMessagesLock.unlock();
} }
void ConversationsDbusInterface::replyToConversation(const qint64& conversationID, const QString& message) void ConversationsDbusInterface::replyToConversation(const qint64& conversationID, const QString& message, const QVariantList& attachmentUrls)
{ {
const auto messagesList = m_conversations[conversationID]; const auto messagesList = m_conversations[conversationID];
if (messagesList.isEmpty()) { if (messagesList.isEmpty()) {
@ -192,11 +192,11 @@ void ConversationsDbusInterface::replyToConversation(const qint64& conversationI
addresses << QVariant::fromValue(address); addresses << QVariant::fromValue(address);
} }
m_smsInterface.sendSms(addresses, message, messagesList.first().subID()); m_smsInterface.sendSms(addresses, message, attachmentUrls, messagesList.first().subID());
} }
void ConversationsDbusInterface::sendWithoutConversation(const QVariantList& addresses, const QString& message) { void ConversationsDbusInterface::sendWithoutConversation(const QVariantList& addresses, const QString& message, const QVariantList& attachmentUrls) {
m_smsInterface.sendSms(addresses, message); m_smsInterface.sendSms(addresses, message, attachmentUrls);
} }
void ConversationsDbusInterface::requestAllConversationThreads() void ConversationsDbusInterface::requestAllConversationThreads()

View file

@ -77,12 +77,12 @@ public Q_SLOTS:
/** /**
* Send a new message to this conversation * Send a new message to this conversation
*/ */
void replyToConversation(const qint64& conversationID, const QString& message); void replyToConversation(const qint64& conversationID, const QString& message, const QVariantList& attachmentUrls);
/** /**
* Send a new message to the contact having no previous coversation with * Send a new message to the contact having no previous coversation with
*/ */
void sendWithoutConversation(const QVariantList& addressList, const QString& message); void sendWithoutConversation(const QVariantList& addressList, const QString& message, const QVariantList& attachmentUrls);
/** /**
* Send the request to the Telephony plugin to update the list of conversation threads * Send the request to the Telephony plugin to update the list of conversation threads

View file

@ -13,6 +13,10 @@
#include <QDebug> #include <QDebug>
#include <QDBusConnection> #include <QDBusConnection>
#include <QProcess> #include <QProcess>
#include <QFile>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QTextCodec>
#include <core/device.h> #include <core/device.h>
#include <core/daemon.h> #include <core/daemon.h>
@ -27,6 +31,7 @@ SmsPlugin::SmsPlugin(QObject* parent, const QVariantList& args)
, m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect"))
, m_conversationInterface(new ConversationsDbusInterface(this)) , m_conversationInterface(new ConversationsDbusInterface(this))
{ {
m_codec = QTextCodec::codecForName(CODEC_NAME);
} }
SmsPlugin::~SmsPlugin() SmsPlugin::~SmsPlugin()
@ -47,7 +52,7 @@ bool SmsPlugin::receivePacket(const NetworkPacket& np)
return true; return true;
} }
void SmsPlugin::sendSms(const QVariantList& addresses, const QString& messageBody, const qint64 subID) void SmsPlugin::sendSms(const QVariantList& addresses, const QString& textMessage, const QVariantList& attachmentUrls, const qint64 subID)
{ {
QVariantList addressMapList; QVariantList addressMapList;
for (const QVariant& address : addresses) { for (const QVariant& address : addresses) {
@ -56,13 +61,35 @@ void SmsPlugin::sendSms(const QVariantList& addresses, const QString& messageBod
} }
QVariantMap packetMap({ QVariantMap packetMap({
{QStringLiteral("sendSms"), true}, {QStringLiteral("version"), 2},
{QStringLiteral("addresses"), addressMapList}, {QStringLiteral("addresses"), addressMapList}
{QStringLiteral("messageBody"), messageBody}
}); });
// If there is any text message add it to the network packet
if (textMessage != QStringLiteral("")) {
packetMap[QStringLiteral("textMessage")] = textMessage;
}
if (subID != -1) { if (subID != -1) {
packetMap[QStringLiteral("subID")] = subID; packetMap[QStringLiteral("subID")] = subID;
} }
QVariantList attachmentMapList;
for (const QVariant& attachmentUrl : attachmentUrls) {
const Attachment attachment = createAttachmentFromUrl(attachmentUrl.toString());
QVariantMap attachmentMap({
{QStringLiteral("fileName"), attachment.uniqueIdentifier()},
{QStringLiteral("base64EncodedFile"), attachment.base64EncodedFile()},
{QStringLiteral("mimeType"), attachment.mimeType()}
});
attachmentMapList.append(attachmentMap);
}
// If there is any attachment add it to the network packet
if (!attachmentMapList.isEmpty()) {
packetMap[QStringLiteral("attachments")] = attachmentMapList;
}
NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap); NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap);
qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote"; qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote";
sendPacket(np); sendPacket(np);
@ -185,6 +212,30 @@ void SmsPlugin::getAttachment(const qint64& partID, const QString& uniqueIdentif
} }
} }
Attachment SmsPlugin::createAttachmentFromUrl(const QString& url)
{
QFile file(url);
file.open(QIODevice::ReadOnly);
if (!file.exists()) {
return Attachment();
}
QFileInfo fileInfo(file);
QString fileName(fileInfo.fileName());
QByteArray byteArray = file.readAll().toBase64();
file.close();
QString base64EncodedFile = m_codec->toUnicode(byteArray);
QMimeDatabase mimeDatabase;
QString mimeType = mimeDatabase.mimeTypeForFile(url).name();
Attachment attachment(-1, mimeType, base64EncodedFile, fileName);
return attachment;
}
QString SmsPlugin::dbusPath() const QString SmsPlugin::dbusPath() const
{ {

View file

@ -75,15 +75,21 @@
/** /**
* Packet sent to request a message be sent * Packet sent to request a message be sent
* *
* This will almost certainly need to be replaced or augmented to support MMS,
* but be sure the Android side remains compatible with old desktop apps!
*
* The body should look like so: * The body should look like so:
* { "sendSms": true, * { "version": 2,
* "addresses": <List of Addresses> * "addresses": <List of Addresses>
* "messageBody": "Hi mom!", * "textMessage": "Hi mom!",
* "attachments": <List of Attached files>
* "sub_id": "3859358340534" * "sub_id": "3859358340534"
* } * }
*
* An AttachmentContainer object looks like:
* {
* "fileName": <String> // Name of the file
* "base64EncodedFile": <String> // Base64 encoded file
* "mimeType": <String> // File type (eg: image/jpg, video/mp4 etc.)
* }
*
*/ */
#define PACKET_TYPE_SMS_REQUEST QStringLiteral("kdeconnect.sms.request") #define PACKET_TYPE_SMS_REQUEST QStringLiteral("kdeconnect.sms.request")
@ -123,6 +129,10 @@
Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS) Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS)
#define CODEC_NAME "CP1251"
class QTextCodec;
class Q_DECL_EXPORT SmsPlugin class Q_DECL_EXPORT SmsPlugin
: public KdeConnectPlugin : public KdeConnectPlugin
{ {
@ -139,7 +149,7 @@ public:
QString dbusPath() const override; QString dbusPath() const override;
public Q_SLOTS: public Q_SLOTS:
Q_SCRIPTABLE void sendSms(const QVariantList& addresses, const QString& messageBody, const qint64 subID = -1); Q_SCRIPTABLE void sendSms(const QVariantList& addresses, const QString& textMessage, const QVariantList& attachmentUrls, const qint64 subID = -1);
/** /**
* Send a request to the remote for all of its conversations * Send a request to the remote for all of its conversations
@ -183,8 +193,14 @@ private:
*/ */
bool handleSmsAttachmentFile(const NetworkPacket& np); bool handleSmsAttachmentFile(const NetworkPacket& np);
/**
* Encode a local file so it can be sent to the remote device as part of an MMS message.
*/
Attachment createAttachmentFromUrl(const QString& url);
QDBusInterface m_telepathyInterface; QDBusInterface m_telepathyInterface;
ConversationsDbusInterface* m_conversationInterface; ConversationsDbusInterface* m_conversationInterface;
QTextCodec *m_codec;
}; };
#endif #endif

View file

@ -86,13 +86,18 @@ void ConversationModel::setAddressList(const QList<ConversationAddress>& address
m_addressList = addressList; m_addressList = addressList;
} }
void ConversationModel::sendReplyToConversation(const QString& message) bool ConversationModel::sendReplyToConversation(const QString& textMessage, QList<QUrl> attachmentUrls)
{ {
//qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Trying to send" << message << "to conversation with ID" << m_threadId; QVariantList fileUrls;
m_conversationsInterface->replyToConversation(m_threadId, message); for (const auto& url : attachmentUrls) {
fileUrls << QVariant::fromValue(url.toLocalFile());
} }
void ConversationModel::startNewConversation(const QString& message, const QList<ConversationAddress>& addressList) m_conversationsInterface->replyToConversation(m_threadId, textMessage, fileUrls);
return true;
}
bool ConversationModel::startNewConversation(const QString& textMessage, const QList<ConversationAddress>& addressList, QList<QUrl> attachmentUrls)
{ {
QVariantList addresses; QVariantList addresses;
@ -100,7 +105,13 @@ void ConversationModel::startNewConversation(const QString& message, const QList
addresses << QVariant::fromValue(address); addresses << QVariant::fromValue(address);
} }
m_conversationsInterface->sendWithoutConversation(addresses, message); 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) void ConversationModel::requestMoreMessages(const quint32& howMany)

View file

@ -48,8 +48,8 @@ public:
QList<ConversationAddress> addressList() const { return m_addressList; } QList<ConversationAddress> addressList() const { return m_addressList; }
void setAddressList(const QList<ConversationAddress>& addressList); void setAddressList(const QList<ConversationAddress>& addressList);
Q_INVOKABLE void sendReplyToConversation(const QString& message); Q_INVOKABLE bool sendReplyToConversation(const QString& textMessage, QList<QUrl> attachmentUrls);
Q_INVOKABLE void startNewConversation(const QString& message, const QList<ConversationAddress>& addressList); Q_INVOKABLE bool startNewConversation(const QString& textMessage, const QList<ConversationAddress>& addressList, QList<QUrl> attachmentUrls);
Q_INVOKABLE void requestMoreMessages(const quint32& howMany = 10); Q_INVOKABLE void requestMoreMessages(const quint32& howMany = 10);
Q_INVOKABLE QString getCharCountInfo(const QString& message) const; Q_INVOKABLE QString getCharCountInfo(const QString& message) const;

View file

@ -168,102 +168,8 @@ Kirigami.ScrollablePage
} }
} }
footer: Controls.Pane { footer: SendingArea {
id: sendingArea width: parent.width
enabled: page.deviceConnected addresses: page.addresses
layer.enabled: sendingArea.enabled
layer.effect: DropShadow {
verticalOffset: 1
color: Kirigami.Theme.disabledTextColor
samples: 20
spread: 0.3
}
Layout.fillWidth: true
padding: 0
wheelEnabled: true
background: Rectangle {
color: Kirigami.Theme.viewBackgroundColor
}
RowLayout {
anchors.fill: parent
Controls.ScrollView {
Layout.fillWidth: true
Layout.maximumHeight: page.height > 300 ? page.height / 3 : 2 * page.height / 3
contentWidth: page.width - sendButtonArea.width
clip: true
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
Controls.TextArea {
anchors.fill: parent
id: messageField
placeholderText: i18nd("kdeconnect-sms", "Compose message")
wrapMode: TextEdit.Wrap
topPadding: Kirigami.Units.gridUnit * 0.5
bottomPadding: topPadding
selectByMouse: true
topInset: height * 2 // This removes background (frame) of the TextArea. Setting `background: Item {}` would cause segfault.
Keys.onReturnPressed: {
if (event.key === Qt.Key_Return) {
if (event.modifiers & Qt.ShiftModifier) {
messageField.append("")
} else {
sendButton.onClicked()
event.accepted = true
}
}
}
}
}
ColumnLayout {
id: sendButtonArea
Controls.ToolButton {
id: sendButton
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
padding: 0
Kirigami.Icon {
source: "document-send"
enabled: sendButton.enabled
isMask: true
smooth: true
anchors.centerIn: parent
width: Kirigami.Units.gridUnit * 1.5
height: width
}
onClicked: {
// don't send empty messages
if (!messageField.text.length) {
return
}
// disable the button to prevent sending
// the same message several times
sendButton.enabled = false
// send the message
if (page.conversationId == page.invalidId) {
conversationModel.startNewConversation(messageField.text, addresses)
} else {
conversationModel.sendReplyToConversation(messageField.text)
}
messageField.text = ""
// re-enable the button
sendButton.enabled = true
}
}
Controls.Label {
id: "charCount"
text: conversationModel.getCharCountInfo(messageField.text)
visible: text.length > 0
Layout.minimumWidth: Math.max(Layout.minimumWidth, width) // Make this label only grow, never shrink
}
}
}
} }
} }

208
smsapp/qml/SendingArea.qml Normal file
View file

@ -0,0 +1,208 @@
/**
* Copyright (C) 2020 Aniket Kumar <anikketkumar786@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.1
import QtQuick.Controls 2.2 as Controls
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.4 as Kirigami
import QtGraphicalEffects 1.0
import QtQuick.Dialogs 1.1
import org.kde.kdeconnect.sms 1.0
ColumnLayout {
id: root
property var addresses
property var selectedFileUrls: []
readonly property int maxMessageSize: 600000
MessageDialog {
id: messageDialog
title: i18nd("kdeconnect-sms", "Failed to send")
text: i18nd("kdeconnect-sms", "Max message size limit exceeded.")
onAccepted: {
messageDialog.close()
}
}
FileDialog {
id: fileDialog
folder: shortcuts.home
selectMultiple: true
onAccepted: {
root.selectedFileUrls = fileDialog.fileUrls
fileDialog.close()
}
}
Controls.Pane {
id: sendingArea
enabled: page.deviceConnected
layer.enabled: sendingArea.enabled
layer.effect: DropShadow {
verticalOffset: 1
color: Kirigami.Theme.disabledTextColor
samples: 20
spread: 0.3
}
Layout.fillWidth: true
padding: 0
wheelEnabled: true
background: Rectangle {
color: Kirigami.Theme.viewBackgroundColor
}
RowLayout {
anchors.fill: parent
Controls.ScrollView {
Layout.fillWidth: true
Layout.maximumHeight: page.height > 300 ? page.height / 3 : 2 * page.height / 3
contentWidth: page.width - sendButtonArea.width
clip: true
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
Controls.TextArea {
anchors.fill: parent
id: messageField
placeholderText: i18nd("kdeconnect-sms", "Compose message")
wrapMode: TextEdit.Wrap
topPadding: Kirigami.Units.gridUnit * 0.5
bottomPadding: topPadding
selectByMouse: true
topInset: height * 2 // This removes background (frame) of the TextArea. Setting `background: Item {}` would cause segfault.
Keys.onReturnPressed: {
if (event.key === Qt.Key_Return) {
if (event.modifiers & Qt.ShiftModifier) {
messageField.append("")
} else {
sendButton.onClicked()
event.accepted = true
}
}
}
}
}
ColumnLayout {
id: sendButtonArea
RowLayout {
Controls.ToolButton {
id: sendButton
enabled: messageField.text.length || selectedFileUrls.length
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
padding: 0
Kirigami.Icon {
source: "document-send"
enabled: sendButton.enabled
isMask: true
smooth: true
anchors.centerIn: parent
width: Kirigami.Units.gridUnit * 1.5
height: width
}
property bool messageSent: false
onClicked: {
// disable the button to prevent sending
// the same message several times
sendButton.enabled = false
if (SmsHelper.totalMessageSize(selectedFileUrls, messageField.text) > maxMessageSize) {
messageDialog.visible = true
} else if (page.conversationId === page.invalidId) {
messageSent = conversationModel.startNewConversation(messageField.text, addresses, selectedFileUrls)
} else {
messageSent = conversationModel.sendReplyToConversation(messageField.text, selectedFileUrls)
}
if (messageSent) {
messageField.text = ""
selectedFileUrls = []
sendButton.enabled = false
}
}
}
Controls.ToolButton {
id: attachFilesButton
enabled: true
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
padding: 0
Text {
id: attachedFilesCount
text: selectedFileUrls.length
color: "red"
visible: selectedFileUrls.length > 0
}
Kirigami.Icon {
source: "insert-image"
isMask: true
smooth: true
anchors.centerIn: parent
width: Kirigami.Units.gridUnit * 1.5
height: width
}
onClicked: {
fileDialog.open()
}
}
Controls.ToolButton {
id: clearAttachmentButton
visible: selectedFileUrls.length > 0
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
padding: 0
Kirigami.Icon {
id: cancelIcon
source: "edit-clear"
isMask: true
smooth: true
anchors.centerIn: parent
width: Kirigami.Units.gridUnit * 1.5
height: width
}
onClicked: {
selectedFileUrls = []
if (messageField.text == "") {
sendButton.enabled = false
}
}
}
}
Controls.Label {
id: "charCount"
text: conversationModel.getCharCountInfo(messageField.text)
visible: text.length > 0
Layout.minimumWidth: Math.max(Layout.minimumWidth, width) // Make this label only grow, never shrink
}
}
}
}
}

View file

@ -6,5 +6,6 @@
<file>qml/ChatMessage.qml</file> <file>qml/ChatMessage.qml</file>
<file>qml/MessageAttachments.qml</file> <file>qml/MessageAttachments.qml</file>
<file>qml/AttachmentViewer.qml</file> <file>qml/AttachmentViewer.qml</file>
<file>qml/SendingArea.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -15,6 +15,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QHash> #include <QHash>
#include <QtDebug> #include <QtDebug>
#include <QFileInfo>
#include <KPeople/PersonData> #include <KPeople/PersonData>
#include <KPeople/PersonsModel> #include <KPeople/PersonsModel>
@ -442,3 +443,13 @@ bool SmsHelper::isInGsmAlphabetExtension(const QChar& ch)
} }
return false; return false;
} }
quint64 SmsHelper::totalMessageSize(const QList<QUrl>& urls, const QString& text) {
quint64 totalSize = text.size();
for (QUrl url : urls) {
QFileInfo fileInfo(url.toLocalFile());
totalSize += fileInfo.size();
}
return totalSize;
}

View file

@ -115,6 +115,11 @@ public:
*/ */
Q_INVOKABLE static bool isAddressValid(const QString& address); Q_INVOKABLE static bool isAddressValid(const QString& address);
/**
* Return the total size of the message
*/
Q_INVOKABLE static quint64 totalMessageSize(const QList<QUrl>& urls, const QString& text);
private: private:
static bool isInGsmAlphabet(const QChar& ch); static bool isInGsmAlphabet(const QChar& ch);
static bool isInGsmAlphabetExtension(const QChar& ch); static bool isInGsmAlphabetExtension(const QChar& ch);