diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro
index 417c776..dc12009 100644
--- a/harbour-fernschreiber.pro
+++ b/harbour-fernschreiber.pro
@@ -55,6 +55,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/PinnedMessageItem.qml \
qml/components/PollPreview.qml \
qml/components/PressEffect.qml \
+ qml/components/ReplyMarkupButtons.qml \
qml/components/StickerPicker.qml \
qml/components/PhotoTextsListItem.qml \
qml/components/WebPagePreview.qml \
diff --git a/images/icon-s-link.svg b/images/icon-s-link.svg
new file mode 100644
index 0000000..c585d3f
--- /dev/null
+++ b/images/icon-s-link.svg
@@ -0,0 +1,28 @@
+
+
\ No newline at end of file
diff --git a/qml/components/AudioPreview.qml b/qml/components/AudioPreview.qml
index ef8881e..3901f6b 100644
--- a/qml/components/AudioPreview.qml
+++ b/qml/components/AudioPreview.qml
@@ -123,13 +123,14 @@ Item {
fillMode: Image.PreserveAspectCrop
visible: status === Image.Ready ? true : false
layer.enabled: audioMessageComponent.highlighted
- layer.effect: PressEffect { source: singleImage }
+ layer.effect: PressEffect { source: placeholderImage }
}
BackgroundImage {
+ id: backgroundImage
visible: placeholderImage.status !== Image.Ready
layer.enabled: audioMessageComponent.highlighted
- layer.effect: PressEffect { source: singleImage }
+ layer.effect: PressEffect { source: backgroundImage }
}
Rectangle {
@@ -140,6 +141,17 @@ Item {
width: parent.width
visible: playButton.visible
}
+ Label {
+ visible: !!(audioData.performer || audioData.title)
+ color: placeholderBackground.visible ? "white" : Theme.secondaryHighlightColor
+ wrapMode: Text.Wrap
+ anchors {
+ fill: placeholderBackground
+ margins: Theme.paddingSmall
+ }
+ text: audioData.performer + (audioData.performer && audioData.title ? " - " : "") + audioData.title
+ font.pixelSize: Theme.fontSizeTiny
+ }
Column {
width: parent.width
@@ -366,7 +378,7 @@ Item {
anchors.centerIn: parent
width: Theme.iconSizeLarge
height: Theme.iconSizeLarge
- highlighted: videoMessageComponent.highlighted || down
+ highlighted: audioMessageComponent.highlighted || down
icon {
asynchronous: true
source: "image://theme/icon-l-play?white"
@@ -390,7 +402,7 @@ Item {
value: messageAudio.position
enabled: messageAudio.seekable
visible: (messageAudio.duration > 0)
- highlighted: videoMessageComponent.highlighted || down
+ highlighted: audioMessageComponent.highlighted || down
onReleased: {
messageAudio.seek(Math.floor(value));
messageAudio.play();
diff --git a/qml/components/MessageListViewItem.qml b/qml/components/MessageListViewItem.qml
index da91f29..a3aafdb 100644
--- a/qml/components/MessageListViewItem.qml
+++ b/qml/components/MessageListViewItem.qml
@@ -398,7 +398,10 @@ ListItem {
wrapMode: Text.Wrap
textFormat: Text.StyledText
onLinkActivated: {
- Functions.handleLink(link);
+ var chatCommand = Functions.handleLink(link);
+ if(chatCommand) {
+ tdLibWrapper.sendTextMessage(chatInformation.id, chatCommand);
+ }
}
horizontalAlignment: messageListItem.textAlign
linkColor: Theme.highlightColor
@@ -435,6 +438,15 @@ ListItem {
value: messageListItem.highlighted
}
+ Loader {
+ id: replyMarkupLoader
+ width: parent.width
+ height: active ? (myMessage.reply_markup.rows.length * (Theme.itemSizeSmall + Theme.paddingSmall) - Theme.paddingSmall) : 0
+ asynchronous: true
+ active: !!myMessage.reply_markup && myMessage.reply_markup.rows
+ source: Qt.resolvedUrl("ReplyMarkupButtons.qml")
+ }
+
Timer {
id: messageDateUpdater
interval: 60000
diff --git a/qml/components/ReplyMarkupButtons.qml b/qml/components/ReplyMarkupButtons.qml
new file mode 100644
index 0000000..02d9c7f
--- /dev/null
+++ b/qml/components/ReplyMarkupButtons.qml
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2020 Sebastian J. Wolf and other contributors
+
+ This file is part of Fernschreiber.
+
+ Fernschreiber 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 3 of the License, or
+ (at your option) any later version.
+
+ Fernschreiber 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 Fernschreiber. If not, see .
+*/
+import QtQuick 2.6
+import Sailfish.Silica 1.0
+import "../js/twemoji.js" as Emoji
+import "../js/functions.js" as Functions
+import "../js/debug.js" as Debug
+
+Column {
+ width: parent.width
+ height: childrenRect.height
+ spacing: Theme.paddingSmall
+
+ Repeater {
+ model: myMessage.reply_markup.rows
+ delegate: Row {
+ width: parent.width
+ height: Theme.itemSizeSmall
+ spacing: Theme.paddingSmall
+ Repeater {
+ id: buttonsRepeater
+ model: modelData
+ property int itemWidth:precalculatedValues.textColumnWidth / count
+ delegate: MouseArea {
+ /*
+ Unimplemented callback types:
+ inlineKeyboardButtonTypeBuy
+ inlineKeyboardButtonTypeCallbackGame
+ inlineKeyboardButtonTypeCallbackWithPassword
+ inlineKeyboardButtonTypeLoginUrl
+ inlineKeyboardButtonTypeSwitchInline
+ */
+ property var callbacks: ({
+ inlineKeyboardButtonTypeCallback: function(){
+ tdLibWrapper.getCallbackQueryAnswer(messageListItem.chatId, messageListItem.messageId, {data: modelData.type.data, "@type": "callbackQueryPayloadData"})
+ },
+ inlineKeyboardButtonTypeUrl: function() {
+ Functions.handleLink(modelData.type.url);
+ }
+ })
+ enabled: !!callbacks[modelData.type["@type"]]
+ height: Theme.itemSizeSmall
+ width: (precalculatedValues.textColumnWidth + Theme.paddingSmall) / buttonsRepeater.count - (Theme.paddingSmall)
+ onClicked: {
+ callbacks[modelData.type["@type"]]();
+ }
+ Rectangle {
+ anchors.fill: parent
+ radius: Theme.paddingSmall
+ color: parent.pressed ? Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
+ : Theme.rgba(Theme.primaryColor, Theme.opacityFaint)
+ opacity: parent.enabled ? 1.0 : Theme.opacityLow
+
+ Label {
+ width: Math.min(parent.width - Theme.paddingSmall*2, contentWidth)
+ truncationMode: TruncationMode.Fade
+ text: Emoji.emojify(modelData.text, Theme.fontSizeSmall)
+ color: parent.pressed ? Theme.highlightColor : Theme.primaryColor
+ anchors.centerIn: parent
+ font.pixelSize: Theme.fontSizeSmall
+ }
+ Icon {
+ property var sources: ({
+ inlineKeyboardButtonTypeUrl: "../../images/icon-s-link.svg",
+ inlineKeyboardButtonTypeSwitchInline: "image://theme/icon-s-repost",
+ inlineKeyboardButtonTypeCallbackWithPassword: "image://theme/icon-s-asterisk"
+ })
+ visible: !!sources[modelData.type["@type"]]
+ source: sources[modelData.type["@type"]] || ""
+ sourceSize: Qt.size(Theme.iconSizeSmall, Theme.iconSizeSmall)
+ highlighted: parent.pressed
+ anchors {
+ right: parent.right
+ top: parent.top
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+}
diff --git a/qml/js/functions.js b/qml/js/functions.js
index cc08cf0..3b4d180 100644
--- a/qml/js/functions.js
+++ b/qml/js/functions.js
@@ -305,6 +305,13 @@ function enhanceMessageText(formattedText, ignoreEntities) {
{ offset: (entity.offset + entity.length), insertionString: "", removeLength: 0 }
);
break;
+ case "textEntityTypeBotCommand":
+ var command = messageText.substring(entity.offset, entity.offset + entity.length);
+ messageInsertions.push(
+ { offset: entity.offset, insertionString: "", removeLength: 0 },
+ { offset: (entity.offset + entity.length), insertionString: "", removeLength: 0 }
+ );
+ break;
}
}
@@ -347,7 +354,9 @@ function handleLink(link) {
} else if (link.indexOf("tg://resolve?domain=") === 0) {
tdLibWrapper.searchPublicChat(link.substring(20));
}
- } else {
+ } else if (link.indexOf("botCommand://") === 0) { // this gets returned to send on ChatPage
+ return link.substring(13);
+ } else {
if (link.indexOf(tMePrefix) === 0) {
if (link.indexOf("joinchat") !== -1) {
Debug.log("Joining Chat: ", link);
diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml
index ef793a3..5f63add 100644
--- a/qml/pages/ChatPage.qml
+++ b/qml/pages/ChatPage.qml
@@ -922,7 +922,7 @@ Page {
chatId: chatModel.chatId
myMessage: model.display
messageId: model.message_id
- extraContentComponentName: chatView.contentComponentNames[model.content_type]
+ extraContentComponentName: chatView.contentComponentNames[model.content_type] || ""
canReplyToMessage: chatPage.canSendMessages
onReplyToMessage: {
newMessageInReplyToRow.inReplyToMessage = myMessage
diff --git a/src/chatmodel.cpp b/src/chatmodel.cpp
index f7e85b3..4bdb0fb 100644
--- a/src/chatmodel.cpp
+++ b/src/chatmodel.cpp
@@ -38,6 +38,7 @@ namespace {
const QString SENDER("sender");
const QString USER_ID("user_id");
const QString PINNED_MESSAGE_ID("pinned_message_id");
+ const QString REPLY_MARKUP("reply_markup");
const QString _TYPE("@type");
}
@@ -54,6 +55,7 @@ public:
static bool lessThan(const MessageData *message1, const MessageData *message2);
void setContent(const QVariantMap &content);
+ void setReplyMarkup(const QVariantMap &replyMarkup);
int senderUserId() const;
qlonglong senderChatId() const;
bool senderIsChat() const;
@@ -90,6 +92,10 @@ void ChatModel::MessageData::setContent(const QVariantMap &content)
{
messageData.insert(CONTENT, content);
}
+void ChatModel::MessageData::setReplyMarkup(const QVariantMap &replyMarkup)
+{
+ messageData.insert(REPLY_MARKUP, replyMarkup);
+}
bool ChatModel::MessageData::lessThan(const MessageData *message1, const MessageData *message2)
{
@@ -112,6 +118,7 @@ ChatModel::ChatModel(TDLibWrapper *tdLibWrapper) :
connect(this->tdLibWrapper, SIGNAL(chatPhotoUpdated(qlonglong, QVariantMap)), this, SLOT(handleChatPhotoUpdated(qlonglong, QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(chatPinnedMessageUpdated(qlonglong, qlonglong)), this, SLOT(handleChatPinnedMessageUpdated(qlonglong, qlonglong)));
connect(this->tdLibWrapper, SIGNAL(messageContentUpdated(qlonglong, qlonglong, QVariantMap)), this, SLOT(handleMessageContentUpdated(qlonglong, qlonglong, QVariantMap)));
+ connect(this->tdLibWrapper, SIGNAL(messageEditedUpdated(qlonglong, qlonglong, QVariantMap)), this, SLOT(handleMessageEditedUpdated(qlonglong, qlonglong, QVariantMap)));
connect(this->tdLibWrapper, SIGNAL(messagesDeleted(qlonglong, QList)), this, SLOT(handleMessagesDeleted(qlonglong, QList)));
}
@@ -420,6 +427,22 @@ void ChatModel::handleMessageContentUpdated(qlonglong chatId, qlonglong messageI
}
}
+void ChatModel::handleMessageEditedUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &replyMarkup)
+{
+ LOG("Message edited updated" << chatId << messageId);
+ if (chatId == this->chatId && messageIndexMap.contains(messageId)) {
+ LOG("We know the message that was updated" << messageId);
+ const int pos = messageIndexMap.value(messageId, -1);
+ if (pos >= 0) {
+ messages.at(pos)->setReplyMarkup(replyMarkup);
+ LOG("Message was edited at index" << pos);
+ const QModelIndex messageIndex(index(pos));
+ emit dataChanged(messageIndex, messageIndex);
+ emit messageUpdated(pos);
+ }
+ }
+}
+
void ChatModel::handleMessagesDeleted(qlonglong chatId, const QList &messageIds)
{
LOG("Messages were deleted in a chat" << chatId);
diff --git a/src/chatmodel.h b/src/chatmodel.h
index 9353037..bd45b55 100644
--- a/src/chatmodel.h
+++ b/src/chatmodel.h
@@ -71,6 +71,7 @@ private slots:
void handleChatPhotoUpdated(qlonglong chatId, const QVariantMap &photo);
void handleChatPinnedMessageUpdated(qlonglong chatId, qlonglong pinnedMessageId);
void handleMessageContentUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &newContent);
+ void handleMessageEditedUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &replyMarkup);
void handleMessagesDeleted(qlonglong chatId, const QList &messageIds);
private:
diff --git a/src/tdlibreceiver.cpp b/src/tdlibreceiver.cpp
index 301e779..8508a32 100644
--- a/src/tdlibreceiver.cpp
+++ b/src/tdlibreceiver.cpp
@@ -132,6 +132,7 @@ TDLibReceiver::TDLibReceiver(void *tdLibClient, QObject *parent) : QThread(paren
handlers.insert("secretChat", &TDLibReceiver::processSecretChat);
handlers.insert("updateSecretChat", &TDLibReceiver::processUpdateSecretChat);
handlers.insert("importedContacts", &TDLibReceiver::processImportedContacts);
+ handlers.insert("updateMessageEdited", &TDLibReceiver::processUpdateMessageEdited);
}
void TDLibReceiver::setActive(bool active)
@@ -555,6 +556,14 @@ void TDLibReceiver::processUpdateSecretChat(const QVariantMap &receivedInformati
emit secretChatUpdated(updatedSecretChat.value(ID).toLongLong(), updatedSecretChat);
}
+void TDLibReceiver::processUpdateMessageEdited(const QVariantMap &receivedInformation)
+{
+ const qlonglong chatId = receivedInformation.value(CHAT_ID).toLongLong();
+ const qlonglong messageId = receivedInformation.value(MESSAGE_ID).toLongLong();
+ LOG("Message was edited" << chatId << messageId);
+ emit messageEditedUpdated(chatId, messageId, receivedInformation.value("reply_markup").toMap());
+}
+
void TDLibReceiver::processImportedContacts(const QVariantMap &receivedInformation)
{
LOG("Contacts were imported");
diff --git a/src/tdlibreceiver.h b/src/tdlibreceiver.h
index 727b7ad..aae67bc 100644
--- a/src/tdlibreceiver.h
+++ b/src/tdlibreceiver.h
@@ -63,6 +63,7 @@ signals:
void notificationUpdated(const QVariantMap updatedNotification);
void chatNotificationSettingsUpdated(const QString &chatId, const QVariantMap updatedChatNotificationSettings);
void messageContentUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &newContent);
+ void messageEditedUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &replyMarkup);
void messagesDeleted(qlonglong chatId, const QList &messageIds);
void chats(const QVariantMap &chats);
void chat(const QVariantMap &chats);
@@ -152,6 +153,7 @@ private:
void nop(const QVariantMap &receivedInformation);
void processSecretChat(const QVariantMap &receivedInformation);
void processUpdateSecretChat(const QVariantMap &receivedInformation);
+ void processUpdateMessageEdited(const QVariantMap &receivedInformation);
void processImportedContacts(const QVariantMap &receivedInformation);
};
diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp
index 42223a1..7f5dbe2 100644
--- a/src/tdlibwrapper.cpp
+++ b/src/tdlibwrapper.cpp
@@ -123,6 +123,7 @@ TDLibWrapper::TDLibWrapper(AppSettings *appSettings, MceInterface *mceInterface,
connect(this->tdLibReceiver, SIGNAL(usersReceived(QString, QVariantList, int)), this, SIGNAL(usersReceived(QString, QVariantList, int)));
connect(this->tdLibReceiver, SIGNAL(errorReceived(int, QString, QString)), this, SLOT(handleErrorReceived(int, QString, QString)));
connect(this->tdLibReceiver, SIGNAL(contactsImported(QVariantList, QVariantList)), this, SIGNAL(contactsImported(QVariantList, QVariantList)));
+ connect(this->tdLibReceiver, SIGNAL(messageEditedUpdated(qlonglong, qlonglong, QVariantMap)), this, SIGNAL(messageEditedUpdated(qlonglong, qlonglong, QVariantMap)));
connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList)));
@@ -545,6 +546,17 @@ void TDLibWrapper::getMessage(const QString &chatId, const QString &messageId)
this->sendRequest(requestObject);
}
+void TDLibWrapper::getCallbackQueryAnswer(const QString &chatId, const QString &messageId, const QVariantMap &payload)
+{
+ LOG("Getting Callback Query Answer" << chatId << messageId);
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "getCallbackQueryAnswer");
+ requestObject.insert("chat_id", chatId);
+ requestObject.insert("message_id", messageId);
+ requestObject.insert("payload", payload);
+ this->sendRequest(requestObject);
+}
+
void TDLibWrapper::getChatPinnedMessage(const qlonglong &chatId)
{
LOG("Retrieving pinned message" << chatId);
diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h
index 50eb2fb..9f25780 100644
--- a/src/tdlibwrapper.h
+++ b/src/tdlibwrapper.h
@@ -143,6 +143,7 @@ public:
Q_INVOKABLE void sendPollMessage(const QString &chatId, const QString &question, const QVariantList &options, const bool &anonymous, const int &correctOption, const bool &multiple, const QString &replyToMessageId = "0");
Q_INVOKABLE void forwardMessages(const QString &chatId, const QString &fromChatId, const QVariantList &messageIds, const bool sendCopy, const bool removeCaption);
Q_INVOKABLE void getMessage(const QString &chatId, const QString &messageId);
+ Q_INVOKABLE void getCallbackQueryAnswer(const QString &chatId, const QString &messageId, const QVariantMap &payload);
Q_INVOKABLE void getChatPinnedMessage(const qlonglong &chatId);
Q_INVOKABLE void setOptionInteger(const QString &optionName, int optionValue);
Q_INVOKABLE void setOptionBoolean(const QString &optionName, bool optionValue);
@@ -219,6 +220,7 @@ signals:
void notificationUpdated(const QVariantMap updatedNotification);
void chatNotificationSettingsUpdated(const QString &chatId, const QVariantMap chatNotificationSettings);
void messageContentUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &newContent);
+ void messageEditedUpdated(qlonglong chatId, qlonglong messageId, const QVariantMap &replyMarkup);
void messagesDeleted(qlonglong chatId, const QList &messageIds);
void chatsReceived(const QVariantMap &chats);
void chatReceived(const QVariantMap &chat);