Adding support to send attachments to the remote device.
This commit is contained in:
parent
60a1adf229
commit
706fc314fb
14 changed files with 343 additions and 122 deletions
|
@ -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>");
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_conversationsInterface->replyToConversation(m_threadId, textMessage, fileUrls);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConversationModel::startNewConversation(const QString& message, const QList<ConversationAddress>& addressList)
|
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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
208
smsapp/qml/SendingArea.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue