diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index a99ffc0..7dfd861 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -45,12 +45,15 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/BackgroundImage.qml \ qml/components/ChatListViewItem.qml \ qml/components/DocumentPreview.qml \ + qml/components/GamePreview.qml \ qml/components/ImagePreview.qml \ qml/components/InReplyToRow.qml \ + qml/components/InlineQuery.qml \ qml/components/LocationPreview.qml \ qml/components/MessageListViewItem.qml \ qml/components/MessageListViewItemSimple.qml \ qml/components/MessageOverlayFlickable.qml \ + qml/components/MessageViaLabel.qml \ qml/components/MultilineEmojiLabel.qml \ qml/components/PinnedMessageItem.qml \ qml/components/PollPreview.qml \ @@ -72,6 +75,20 @@ DISTFILES += qml/harbour-fernschreiber.qml \ qml/components/chatInformationPage/ChatInformationTextItem.qml \ qml/components/chatInformationPage/EditGroupChatPermissionsColumn.qml \ qml/components/chatInformationPage/EditSuperGroupSlowModeColumn.qml \ + qml/components/inlineQueryResults/InlineQueryResult.qml \ + qml/components/inlineQueryResults/InlineQueryResultAnimation.qml \ + qml/components/inlineQueryResults/InlineQueryResultArticle.qml \ + qml/components/inlineQueryResults/InlineQueryResultAudio.qml \ + qml/components/inlineQueryResults/InlineQueryResultContact.qml \ + qml/components/inlineQueryResults/InlineQueryResultDefaultBase.qml \ + qml/components/inlineQueryResults/InlineQueryResultDocument.qml \ + qml/components/inlineQueryResults/InlineQueryResultGame.qml \ + qml/components/inlineQueryResults/InlineQueryResultLocation.qml \ + qml/components/inlineQueryResults/InlineQueryResultPhoto.qml \ + qml/components/inlineQueryResults/InlineQueryResultSticker.qml \ + qml/components/inlineQueryResults/InlineQueryResultVenue.qml \ + qml/components/inlineQueryResults/InlineQueryResultVideo.qml \ + qml/components/inlineQueryResults/InlineQueryResultVoiceNote.qml \ qml/js/debug.js \ qml/js/functions.js \ qml/pages/ChatInformationPage.qml \ diff --git a/qml/components/GamePreview.qml b/qml/components/GamePreview.qml new file mode 100644 index 0000000..d9a722d --- /dev/null +++ b/qml/components/GamePreview.qml @@ -0,0 +1,126 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../js/functions.js" as Functions +import "../js/twemoji.js" as Emoji + +Column { + id: gamePreviewItem + + property ListItem messageListItem + property MessageOverlayFlickable overlayFlickable + property var rawMessage: messageListItem ? messageListItem.myMessage : overlayFlickable.overlayMessage + property bool highlighted + + width: parent.width + height: childrenRect.height + + + Label { + width: parent.width + font.bold: true + font.pixelSize: Theme.fontSizeSmall + text: Emoji.emojify(rawMessage.content.game.title || "", font.pixelSize) + truncationMode: TruncationMode.Fade + textFormat: Text.StyledText + wrapMode: Text.Wrap + } + Label { + width: parent.width + font.pixelSize: Theme.fontSizeExtraSmall + text: Emoji.emojify(rawMessage.content.game.description || "", font.pixelSize) + truncationMode: TruncationMode.Fade + textFormat: Text.StyledText + wrapMode: Text.Wrap + } + Label { + width: parent.width + font.pixelSize: Theme.fontSizeExtraSmall + text: Emoji.emojify(Functions.enhanceMessageText(rawMessage.content.game.text) || "", font.pixelSize) + truncationMode: TruncationMode.Fade + wrapMode: Text.Wrap + textFormat: Text.StyledText + onLinkActivated: { + var chatCommand = Functions.handleLink(link); + if(chatCommand) { + tdLibWrapper.sendTextMessage(chatInformation.id, chatCommand); + } + } + } + Item { + width: parent.width + height: Theme.paddingLarge + } + + Image { + id: thumbnail + source: thumbnailFile.isDownloadingCompleted ? thumbnailFile.path : "" + fillMode: Image.PreserveAspectCrop + asynchronous: true + width: visible ? parent.width : 0 +// height: width + opacity: status === Image.Ready ? 1.0 : 0.0 + + Behavior on opacity { FadeAnimation {} } + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: thumbnail } + + TDLibFile { + id: thumbnailFile + tdlib: tdLibWrapper + autoLoad: true + } + Rectangle { + width: Theme.iconSizeMedium + height: width + anchors { + top: parent.top + topMargin: Theme.paddingSmall + left: parent.left + leftMargin: Theme.paddingSmall + } + + color: Theme.rgba(Theme.overlayBackgroundColor, 0.2) + radius: Theme.paddingSmall + Icon { + id: icon + source: "image://theme/icon-m-game-controller" + asynchronous: true + } + } + } + + Component.onCompleted: { + if (rawMessage.content.game.photo) { + // Check first which size fits best... + var photo + for (var i = 0; i < rawMessage.content.game.photo.sizes.length; i++) { + photo = rawMessage.content.game.photo.sizes[i].photo + if (rawMessage.content.game.photo.sizes[i].width >= gamePreviewItem.width) { + break + } + } + if (photo) { + thumbnailFile.fileInformation = photo + } + } + } +} diff --git a/qml/components/InlineQuery.qml b/qml/components/InlineQuery.qml new file mode 100644 index 0000000..061e78e --- /dev/null +++ b/qml/components/InlineQuery.qml @@ -0,0 +1,431 @@ +/* + 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/debug.js" as Debug +import "../js/twemoji.js" as Emoji +import "../js/functions.js" as Functions + +Loader { + id: inlineQueryLoader + active: userName.length > 1 + asynchronous: true + anchors.fill: parent + property bool hasOverlay: active && status === Loader.Ready && item.overlay && item.overlay.status === Loader.Ready + property bool hasButton: active && status === Loader.Ready && item.button && item.button.status === Loader.Ready + + property int buttonPadding: hasButton ? item.button.height + Theme.paddingSmall : 0 + Behavior on buttonPadding { NumberAnimation { duration: 200} } + + property int chatId + property string userName + property string query + property int currentOffset: 0 + property string responseExtra: chatId+"|"+userName+"|"+query+"|"+currentOffset + + property bool queued: false + property TextArea textField + property bool isLoading + onIsLoadingChanged: { + requestTimeout.start(); + } + + onStatusChanged: { + inlineBotInformation = null; + if(status === Loader.Ready && userName !== "") { + isLoading = true; + tdLibWrapper.searchPublicChat(userName, false); + } + } + + onUserNameChanged: { + inlineBotInformation = null; + + if(status === Loader.Ready && userName !== "") { + isLoading = true; + tdLibWrapper.searchPublicChat(userName, false); + } + } + + onQueryChanged: { + if(userName.length > 0) { + isLoading = true; + requestTimer.start(); + } + } + property var inlineBotInformation: null + + + function handleQuery(name, query, offset) { + if(!name) { + inlineQueryLoader.userName = ""; + inlineQueryLoader.query = ""; + return; + } + if(inlineQueryLoader.userName !== name) { + inlineQueryLoader.userName = name + } + if(inlineQueryLoader.query !== query) { + inlineQueryLoader.query = query + } + inlineQueryLoader.currentOffset = offset || 0 + } + function request() { + if(userName.length === 0) { + return; + } + + if(!inlineBotInformation) { + queued = true; + } else { + queued = false; + var location = null; + if(inlineBotInformation.type.need_location && fernschreiberUtils.supportsGeoLocation()) { + // TODO add location + fernschreiberUtils.startGeoLocationUpdates(); + if(!attachmentPreviewRow.locationData.latitude) { + queued = true; + return; + } + + } + tdLibWrapper.getInlineQueryResults(inlineBotInformation.id, chatId, location, query, inlineQueryLoader.currentOffset, inlineQueryLoader.responseExtra); + isLoading = true; + } + } + Timer { + id: requestTimeout + interval: 5000 + onTriggered: { + inlineQueryLoader.isLoading = false; + } + } + + Timer { + id: requestTimer + interval: 1000 + onTriggered: { + request(); + } + } + + Connections { + target: fernschreiberUtils + onNewPositionInformation: { + attachmentPreviewRow.locationData = positionInformation; + if (inlineQueryLoader.queued) { + inlineQueryLoader.queued = false; + inlineQueryLoader.request() + } + } + } + + Connections { + target: textField + onTextChanged: { + if(textField.text.charAt(0) === '@') { + var queryMatch = textField.text.match(/^@([a-zA-Z0-9_]+)\s(.*)/); + if(queryMatch) { + inlineQueryLoader.handleQuery(queryMatch[1], queryMatch[2]); + } else { + inlineQueryLoader.handleQuery(); + } + } else { + inlineQueryLoader.handleQuery(); + } + } + } + + sourceComponent: Component { + Item { + id: inlineQueryComponent + anchors.fill: parent + property alias overlay: resultsOverlay + property alias button: switchToPmLoader + property string nextOffset + property string inlineQueryId + property string switchPmText + property string switchPmParameter + property ListModel resultModel: ListModel { + dynamicRoles: true + } + + property string inlineQueryPlaceholder: inlineBotInformation ? inlineBotInformation.type.inline_query_placeholder : "" + property bool showInlineQueryPlaceholder: !!inlineQueryPlaceholder && query === "" + property string useDelegateSize: "default" + property var dimensions: ({ + "default": [[Screen.width, Screen.height / 2], [Theme.itemSizeLarge, Theme.itemSizeLarge]], // whole line (portrait half) + "inlineQueryResultAnimation": [[Screen.width / 3, Screen.height / 6], [Screen.width / 3, Screen.height / 6]], + "inlineQueryResultVideo": [[Screen.width / 2, Screen.height / 4], [Theme.itemSizeLarge, Theme.itemSizeLarge]], + "inlineQueryResultSticker": [[Screen.width / 3, Screen.height / 6], [Screen.width / 3, Screen.height / 6]], + "inlineQueryResultPhoto": [[Screen.width/2, Screen.height / 3], [Theme.itemSizeExtraLarge, Theme.itemSizeExtraLarge]], + }) + property int delegateWidth: chatPage.isPortrait ? dimensions[useDelegateSize][0][0] : dimensions[useDelegateSize][0][1] + property int delegateHeight: chatPage.isPortrait ? dimensions[useDelegateSize][1][0] : dimensions[useDelegateSize][1][1] + + function setDelegateSizes() { + var sizeKey = "default"; + var modelCount = resultModel.count; + if(modelCount > 0) { + var firstType = resultModel.get(0)["@type"]; + if(firstType && dimensions[firstType]) { + var startIndex = inlineQueryLoader.currentOffset === 0 ? 1 : inlineQueryLoader.currentOffset; + var same = true; + for(var i = startIndex; i < modelCount; i += 1) { + if(resultModel.get(i)["@type"] !== firstType) { + same = false; + continue; + } + } + if(same) { + sizeKey = firstType; + } + } + } + useDelegateSize = sizeKey; + } + function loadMore() { + if(nextOffset) { + inlineQueryLoader.currentOffset = nextOffset; + inlineQueryLoader.request(); + } + } + + Connections { + target: tdLibWrapper + + onChatReceived: { + if(chat["@extra"] === "searchPublicChat:"+inlineQueryLoader.userName) { + requestTimeout.stop(); + inlineQueryLoader.isLoading = false; + var inlineBotInformation = tdLibWrapper.getUserInformation(chat.type.user_id); + if(inlineBotInformation && inlineBotInformation.type["@type"] === "userTypeBot" && inlineBotInformation.type.is_inline) { + inlineQueryLoader.inlineBotInformation = inlineBotInformation; + requestTimer.start(); + } + } + } + onInlineQueryResults: { + if(extra === inlineQueryLoader.responseExtra) { + requestTimeout.stop(); + inlineQueryLoader.isLoading = false; + inlineQueryComponent.inlineQueryId = inlineQueryId + inlineQueryComponent.nextOffset = nextOffset + inlineQueryComponent.switchPmText = switchPmText + inlineQueryComponent.switchPmParameter = switchPmParameter + + if(inlineQueryLoader.currentOffset === 0) { + inlineQueryComponent.resultModel.clear() + } + for(var i = 0; i < results.length; i++) { + inlineQueryComponent.resultModel.append(results[i]); + } + + if(inlineQueryLoader.currentOffset === 0 || inlineQueryLoader.useDelegateSize !== "default") { + inlineQueryComponent.setDelegateSizes() + } + } + } + } + // switch to pm Button + Loader { + id: switchToPmLoader + asynchronous: true + active: inlineQueryComponent.switchPmText.length > 0 + opacity: status === Loader.Ready ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } +// onActiveChanged: { +// inlineQueryLoader.buttonPadding = active ? height + Theme.paddingSmall : 0 +// if(active) { +// newMessageColumn.topPadding = Theme.itemSizeExtraSmall + Theme.paddingSmall +// } else { + +// newMessageColumn.topPadding = heme.paddingSmall +// } +// } + + height: Theme.itemSizeSmall + anchors { + top: parent.bottom + topMargin: Theme.paddingSmall + left: parent.left + leftMargin: Theme.horizontalPageMargin + right: parent.right + rightMargin: Theme.horizontalPageMargin + } + + sourceComponent: Component { + + MouseArea { + id: customButton + onClicked: { + Debug.log("now we should switch to pm somehow ;) ooooor: ") + tdLibWrapper.createPrivateChat(inlineQueryLoader.inlineBotInformation.id, "openAndSendStartToBot:"+(inlineQueryComponent.switchPmParameter.length > 0 ? " "+inlineQueryComponent.switchPmParameter:"")); + } + Rectangle { + anchors.fill: parent + radius: Theme.paddingSmall + color: parent.pressed ? Theme.highlightBackgroundColor : Theme.rgba(Theme.DarkOnLight ? Qt.lighter(Theme.primaryColor) : Qt.darker(Theme.primaryColor), Theme.opacityFaint) + Label { + anchors { + fill: parent + leftMargin: Theme.paddingLarge + rightMargin: Theme.paddingLarge + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + fontSizeMode: Text.Fit; + minimumPixelSize: Theme.fontSizeTiny; + font.pixelSize: Theme.fontSizeSmall + + color: customButton.pressed ? Theme.highlightColor : Theme.primaryColor + text: Emoji.emojify(inlineQueryComponent.switchPmText, font.pixelSize)// + "we are gonna make this a bit longer" + } + } + } + + + } + } + + // results grid overlay + Loader { + id: resultsOverlay + asynchronous: true + active: inlineQueryComponent.resultModel.count > 0 + anchors.fill: parent + opacity: !!item ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + property var supportedResultTypes: [ + "inlineQueryResultAnimation", + "inlineQueryResultArticle", + "inlineQueryResultAudio", + "inlineQueryResultContact", + "inlineQueryResultDocument", + "inlineQueryResultGame", + "inlineQueryResultLocation", + "inlineQueryResultPhoto", + "inlineQueryResultSticker", + "inlineQueryResultVenue", + "inlineQueryResultVideo", + "inlineQueryResultVoiceNote", + ] + sourceComponent: Component { + Item { + Rectangle { + id: messageContentBackground + color: Theme.overlayBackgroundColor + opacity: 0.7 + anchors.fill: parent +// MouseArea { +// anchors.fill: parent +// onClicked: { +// // handleQuery(); +// } +// } + } + Timer { + id: autoLoadMoreTimer + interval: 400 + onTriggered: { + if (inlineQueryComponent.nextOffset && resultView.height > resultView.contentHeight - Theme.itemSizeHuge) { + inlineQueryComponent.loadMore(); + } + } + } + + SilicaGridView { + id: resultView + anchors.fill: parent + cellWidth: inlineQueryComponent.delegateWidth + cellHeight: inlineQueryComponent.delegateHeight + + signal requestPlayback(url playbackSource) + clip: true + model: inlineQueryComponent.resultModel + delegate: Loader { + id: queryResultDelegate +// asynchronous: true + height: resultView.cellHeight + width: resultView.cellWidth + // TODO: if all are the same, use global? + source: "inlineQueryResults/" + (resultsOverlay.supportedResultTypes.indexOf(model["@type"]) > -1 ? (model["@type"].charAt(0).toUpperCase() + model["@type"].substring(1)) : "InlineQueryResultDefaultBase") +".qml" +// Component.onCompleted: { +// Debug.log("delegate", source) +// } + } + footer: Component { + Item { + width: resultView.width + visible: height > 0 + height: inlineQueryComponent.nextOffset ? Theme.itemSizeLarge : 0 + Behavior on height { NumberAnimation { duration: 500 } } + } + } + + onContentYChanged: { + if(!inlineQueryLoader.isLoading && inlineQueryComponent.nextOffset && contentHeight - contentY - height < Theme.itemSizeHuge) { + + inlineQueryComponent.loadMore(); + + } + } + ScrollDecorator { flickable: resultView } + + } + } + } + } + + + // textarea placeholder + Loader { + asynchronous: true + active: inlineQueryComponent.showInlineQueryPlaceholder +// onActiveChanged: { +// Debug.log("inline: placeholder active changed", active) +// } + + sourceComponent: Component { + Label { + text: Emoji.emojify(inlineQueryComponent.inlineQueryPlaceholder, font.pixelSize); + parent: textField + anchors.fill: parent + anchors.leftMargin: textMetrics.boundingRect.width + Theme.paddingSmall + font: textField.font + color: Theme.secondaryColor + + truncationMode: TruncationMode.Fade + TextMetrics { + id: textMetrics + font: textField.font + text: textField.text + } + } + } + } + + + } + } + + + +} diff --git a/qml/components/LocationPreview.qml b/qml/components/LocationPreview.qml index e50120e..fb4ec49 100644 --- a/qml/components/LocationPreview.qml +++ b/qml/components/LocationPreview.qml @@ -35,6 +35,7 @@ Item { property var pictureFileInformation; width: parent.width height: width / 2 + property string fileExtra Component.onCompleted: { updatePicture(); @@ -47,14 +48,18 @@ Item { function updatePicture() { imagePreviewItem.pictureFileInformation = null; if (locationData) { - tdLibWrapper.getMapThumbnailFile(chatId, locationData.latitude, locationData.longitude, Math.round(imagePreviewItem.width), Math.round(imagePreviewItem.height)); + fileExtra = "location:" + locationData.latitude + ":" + locationData.longitude + ":" + Math.round(imagePreviewItem.width) + ":" + Math.round(imagePreviewItem.height); + tdLibWrapper.getMapThumbnailFile(chatId, locationData.latitude, locationData.longitude, Math.round(imagePreviewItem.width), Math.round(imagePreviewItem.height), fileExtra); } } Connections { target: tdLibWrapper onFileUpdated: { - // we do not have a way of knowing if this is the correct file, so we have to guess the first new one should be right. + if(fileInformation["@extra"] !== imagePreviewItem.fileExtra) { + return; + } + if(!imagePreviewItem.pictureFileInformation) { imagePreviewItem.pictureFileInformation = fileInformation; tdLibWrapper.downloadFile(imagePreviewItem.pictureFileInformation.id); diff --git a/qml/components/MessageListViewItem.qml b/qml/components/MessageListViewItem.qml index bc306e7..259caa2 100644 --- a/qml/components/MessageListViewItem.qml +++ b/qml/components/MessageListViewItem.qml @@ -241,7 +241,7 @@ ListItem { anchors.fill: parent enabled: !(messageListItem.precalculatedValues.pageIsSelecting || messageListItem.isAnonymous) onClicked: { - tdLibWrapper.createPrivateChat(messageListItem.userInformation.id); + tdLibWrapper.createPrivateChat(messageListItem.userInformation.id, "openDirectly"); } } } @@ -299,11 +299,15 @@ ListItem { anchors.fill: parent enabled: !(messageListItem.precalculatedValues.pageIsSelecting || messageListItem.isAnonymous) onClicked: { - tdLibWrapper.createPrivateChat(messageListItem.userInformation.id); + tdLibWrapper.createPrivateChat(messageListItem.userInformation.id, "openDirectly"); } } } + MessageViaLabel { + message: myMessage + } + Loader { id: messageInReplyToLoader active: myMessage.reply_to_message_id !== 0 diff --git a/qml/components/MessageListViewItemSimple.qml b/qml/components/MessageListViewItemSimple.qml index 12747bb..73f10dd 100644 --- a/qml/components/MessageListViewItemSimple.qml +++ b/qml/components/MessageListViewItemSimple.qml @@ -20,12 +20,14 @@ 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 Item { id: messageListItem property var myMessage: display property var userInformation: tdLibWrapper.getUserInformation(myMessage.sender.user_id) property bool isOwnMessage: chatPage.myUserId === myMessage.sender.user_id + property var linkedMessage height: backgroundRectangle.height + Theme.paddingMedium Rectangle { @@ -43,11 +45,40 @@ Item { color: Theme.highlightColor horizontalAlignment: Text.AlignHCenter font.pixelSize: Theme.fontSizeExtraSmall - text: "" + (!messageListItem.isOwnMessage ? Emoji.emojify(Functions.getUserName(messageListItem.userInformation), font.pixelSize) : qsTr("You")) + " " + Emoji.emojify(Functions.getMessageText(messageListItem.myMessage, false, chatPage.myUserId, false), font.pixelSize) + property string messageContentText: Functions.getMessageText(messageListItem.myMessage, false, chatPage.myUserId, false) + text: "" + (!messageListItem.isOwnMessage ? Emoji.emojify(Functions.getUserName(messageListItem.userInformation), font.pixelSize) : qsTr("You")) + " " + Emoji.emojify(messageContentText, font.pixelSize) textFormat: Text.RichText wrapMode: Text.WrapAtWordBoundaryOrAnywhere onLinkActivated: { - Functions.handleLink(link); + if(link === "linkedmessage" && linkedMessage) { + messageOverlayLoader.overlayMessage = linkedMessage; + messageOverlayLoader.active = true; + } else { + Functions.handleLink(link); + } + + } + } + Loader { + id: gameScoreInfoLoader + active: myMessage.content["@type"] === "messageGameScore" + asynchronous: true + sourceComponent: Component { + Connections { + target: tdLibWrapper + onReceivedMessage: { + if(chatId === chatPage.chatInformation.id && messageId === myMessage.content.game_message_id) { + messageListItem.linkedMessage = message; + messageText.messageContentText = messageListItem.isOwnMessage ? + qsTr("scored %Ln points in %2", "myself", myMessage.content.score).arg(""+message.content.game.title+"") : + + qsTr("scored %Ln points in %2", "", myMessage.content.score).arg(""+message.content.game.title+""); + } + } + Component.onCompleted: { + tdLibWrapper.getMessage(chatPage.chatInformation.id, myMessage.content.game_message_id); + } + } } } } diff --git a/qml/components/MessageOverlayFlickable.qml b/qml/components/MessageOverlayFlickable.qml index b3db83d..f26ba00 100644 --- a/qml/components/MessageOverlayFlickable.qml +++ b/qml/components/MessageOverlayFlickable.qml @@ -18,9 +18,9 @@ */ import QtQuick 2.6 import Sailfish.Silica 1.0 -import "../components" import "../js/functions.js" as Functions import "../js/twemoji.js" as Emoji +import "../js/debug.js" as Debug Flickable { id: messageOverlayFlickable @@ -124,6 +124,10 @@ Flickable { } } + MessageViaLabel { + message: overlayMessage + } + Text { id: overlayForwardedInfoText width: parent.width @@ -179,6 +183,16 @@ Flickable { asynchronous: true } + Loader { + id: replyMarkupLoader + property var myMessage: overlayMessage + width: parent.width + height: active ? (overlayMessage.reply_markup.rows.length * (Theme.itemSizeSmall + Theme.paddingSmall) - Theme.paddingSmall) : 0 + asynchronous: true + active: !!overlayMessage.reply_markup && myMessage.reply_markup.rows + source: Qt.resolvedUrl("ReplyMarkupButtons.qml") + } + Timer { id: messageDateUpdater interval: 60000 diff --git a/qml/components/MessageViaLabel.qml b/qml/components/MessageViaLabel.qml new file mode 100644 index 0000000..b76619e --- /dev/null +++ b/qml/components/MessageViaLabel.qml @@ -0,0 +1,49 @@ +/* + 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/functions.js" as Functions +import "../js/twemoji.js" as Emoji + + +Loader { + id: botUserLoader + active: !!message.via_bot_user_id + width: parent.width + asynchronous: true + sourceComponent: Label { + property var botUserInformation: tdLibWrapper.getUserInformation(message.via_bot_user_id) + color: Theme.secondaryColor + font.pixelSize: Theme.fontSizeExtraSmall + text: qsTr("via %1", "message posted via bot user").arg("@" + Emoji.emojify(botUserInformation.username, font.pixelSize)+"") + textFormat: Text.RichText + truncationMode: TruncationMode.Fade + onLinkActivated: { + if(link === "userId://" + message.via_bot_user_id && botUserInformation.type.is_inline) { + newMessageTextField.text = "@"+botUserInformation.username+" " + newMessageTextField.cursorPosition = newMessageTextField.text.length + lostFocusTimer.start(); + } + else { + Functions.handleLink(link); + } + } + } + property var message +} diff --git a/qml/components/ReplyMarkupButtons.qml b/qml/components/ReplyMarkupButtons.qml index 02d9c7f..de2cd2c 100644 --- a/qml/components/ReplyMarkupButtons.qml +++ b/qml/components/ReplyMarkupButtons.qml @@ -23,6 +23,7 @@ import "../js/functions.js" as Functions import "../js/debug.js" as Debug Column { + id: replyMarkupButtons width: parent.width height: childrenRect.height spacing: Theme.paddingSmall @@ -36,7 +37,7 @@ Column { Repeater { id: buttonsRepeater model: modelData - property int itemWidth:precalculatedValues.textColumnWidth / count + property int itemWidth: replyMarkupButtons.width / count delegate: MouseArea { /* Unimplemented callback types: @@ -48,15 +49,35 @@ Column { */ property var callbacks: ({ inlineKeyboardButtonTypeCallback: function(){ - tdLibWrapper.getCallbackQueryAnswer(messageListItem.chatId, messageListItem.messageId, {data: modelData.type.data, "@type": "callbackQueryPayloadData"}) + tdLibWrapper.getCallbackQueryAnswer(myMessage.chat_id, myMessage.id, {data: modelData.type.data, "@type": "callbackQueryPayloadData"}) }, + + inlineKeyboardButtonTypeCallbackGame: function(){ + tdLibWrapper.getCallbackQueryAnswer(myMessage.chat_id, myMessage.id, {game_short_name: myMessage.content.game.short_name, "@type": "callbackQueryPayloadGame"}) + }, inlineKeyboardButtonTypeUrl: function() { Functions.handleLink(modelData.type.url); + }, + inlineKeyboardButtonTypeSwitchInline: function() { + if(modelData.type.in_current_chat) { + chatPage.setMessageText("@" + userInformation.username + " "+(modelData.type.query || "")) + } else { + + pageStack.push(Qt.resolvedUrl("../pages/ChatSelectionPage.qml"), { + myUserId: chatPage.myUserId, + payload: { neededPermissions: ["can_send_other_messages"], text:"@" + userInformation.username + " "+(modelData.type.query || "")}, + state: "fillTextArea" + }) + } + }, + + keyboardButtonTypeText: function() { + chatPage.setMessageText(modelData.text, true); } }) enabled: !!callbacks[modelData.type["@type"]] height: Theme.itemSizeSmall - width: (precalculatedValues.textColumnWidth + Theme.paddingSmall) / buttonsRepeater.count - (Theme.paddingSmall) + width: (replyMarkupButtons.width + Theme.paddingSmall) / buttonsRepeater.count - (Theme.paddingSmall) onClicked: { callbacks[modelData.type["@type"]](); } @@ -71,17 +92,18 @@ Column { 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 + color: parent.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", + inlineKeyboardButtonTypeSwitchInline: !modelData.type.in_current_chat ? "image://theme/icon-s-repost" : "image://theme/icon-s-edit", inlineKeyboardButtonTypeCallbackWithPassword: "image://theme/icon-s-asterisk" }) visible: !!sources[modelData.type["@type"]] + opacity: 0.6 source: sources[modelData.type["@type"]] || "" sourceSize: Qt.size(Theme.iconSizeSmall, Theme.iconSizeSmall) highlighted: parent.pressed diff --git a/qml/components/chatInformationPage/ChatInformationPageContent.qml b/qml/components/chatInformationPage/ChatInformationPageContent.qml index a1f29a6..021377f 100644 --- a/qml/components/chatInformationPage/ChatInformationPageContent.qml +++ b/qml/components/chatInformationPage/ChatInformationPageContent.qml @@ -232,7 +232,7 @@ SilicaFlickable { MenuItem { visible: chatInformationPage.isPrivateChat onClicked: { - tdLibWrapper.createNewSecretChat(chatInformationPage.chatPartnerGroupId); + tdLibWrapper.createNewSecretChat(chatInformationPage.chatPartnerGroupId, "openDirectly"); } text: qsTr("New Secret Chat") } diff --git a/qml/components/chatInformationPage/ChatInformationTabItemMembersGroups.qml b/qml/components/chatInformationPage/ChatInformationTabItemMembersGroups.qml index 79a72db..3ebc78b 100644 --- a/qml/components/chatInformationPage/ChatInformationTabItemMembersGroups.qml +++ b/qml/components/chatInformationPage/ChatInformationTabItemMembersGroups.qml @@ -92,7 +92,7 @@ ChatInformationTabItemBase { } onClicked: { - tdLibWrapper.createPrivateChat(user_id); + tdLibWrapper.createPrivateChat(user_id, "openDirectly"); } } footer: Component { diff --git a/qml/components/inlineQueryResults/InlineQueryResult.qml b/qml/components/inlineQueryResults/InlineQueryResult.qml new file mode 100644 index 0000000..784811d --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResult.qml @@ -0,0 +1,33 @@ +/* + 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 + +BackgroundItem { + id: queryResultItem + + function sendInlineQueryResultMessage() { + tdLibWrapper.sendInlineQueryResultMessage(inlineQueryLoader.chatId, 0, 0, inlineQueryComponent.inlineQueryId, model.id); + inlineQueryLoader.textField.text = ""; + } + onClicked: { + sendInlineQueryResultMessage() + } + +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultAnimation.qml b/qml/components/inlineQueryResults/InlineQueryResultAnimation.qml new file mode 100644 index 0000000..85bbeb1 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultAnimation.qml @@ -0,0 +1,291 @@ +/* + 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 QtMultimedia 5.6 +import WerkWolf.Fernschreiber 1.0 +import QtGraphicalEffects 1.0 +import Nemo.Thumbnailer 1.0 +import "../" +import "../../js/twemoji.js" as Emoji +import "../../js/debug.js" as Debug + +InlineQueryResult { + id: queryResultItem + property bool isAnimation: true + property bool loopPreview: isAnimation + property bool mutePreview: isAnimation + enabled: false // don't send on click + layer.enabled: mouseArea.pressed + layer.effect: PressEffect { source: queryResultItem } + + property string animationKey: "animation" + property bool hasThumbnail: !!model[queryResultItem.animationKey].thumbnail + + property string videoMimeType: "video/mp4" + + TDLibFile { + id: file + tdlib: tdLibWrapper + autoLoad: true + fileInformation: hasThumbnail ? model[queryResultItem.animationKey].thumbnail.file : (queryResultItem.isAnimation ? model[queryResultItem.animationKey].animation : model[queryResultItem.animationKey].video) + } + + Image { + id: miniThumbnail + asynchronous: true + source: model[queryResultItem.animationKey].minithumbnail ? "data:image/jpg;base64,"+model[queryResultItem.animationKey].minithumbnail.data : "" + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: miniThumbnail } + } + Component { + id: videoThumbnail + Thumbnail { + id: thumbnail + source: file.path + sourceSize.width: width + sourceSize.height: height + mimeType: queryResultItem.videoMimeType + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: thumbnail } + opacity: status === Thumbnail.Ready ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + } + } + Component { + id: imageThumbnail + Image { + id: thumbnail + source: file.path + sourceSize.width: width + sourceSize.height: height + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: thumbnail } + fillMode: Image.PreserveAspectCrop + opacity: status === Image.Ready ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + onStatusChanged: { + // we don't get many hints what may be wrong, so we guess it may be a webp image ;) + if(status === Image.Error) { + Debug.log("Inline Query Video: Thumbnail invalid. Blindly trying webp, which might work.") + queryResultItem.videoMimeType = "image/webp"; + thumbnailLoader.sourceComponent = videoThumbnail; + } + } + } + } + Loader { + id: thumbnailLoader + asynchronous: true + active: file.isDownloadingCompleted + anchors.fill: parent + sourceComponent: queryResultItem.hasThumbnail ? (model[queryResultItem.animationKey].thumbnail.format["@type"] === "thumbnailFormatMpeg4" ? videoThumbnail : imageThumbnail) : model[queryResultItem.animationKey].mime_type === "video/mp4" ? videoThumbnail : imageThumbnail + } + Column { + id: texts + anchors { + left: parent.left + margins: Theme.paddingSmall + right: parent.right + bottom: parent.bottom + } + + Label { + id: titleLabel + width: parent.width + font.pixelSize: Theme.fontSizeTiny + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + visible: text.length > 0 + text: Emoji.emojify(model.title || "", font.pixelSize); + } + Label { + id: descriptionLabel + width: parent.width + font.pixelSize: Theme.fontSizeTiny + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + visible: text.length > 0 + text: Emoji.emojify(model.description || "", font.pixelSize); + } + } + + Loader { + anchors.fill: texts + asynchronous: true + active: titleLabel.visible || descriptionLabel.visible + sourceComponent: Component { + DropShadow { + horizontalOffset: 0 + verticalOffset: 0 + radius: Theme.paddingSmall + spread: 0.5 + samples: 17 + color: Theme.overlayBackgroundColor + source: texts + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + // dialog + + var dialog = pageStack.push(dialogComponent,{}) + dialog.accepted.connect(function() { + queryResultItem.sendInlineQueryResultMessage(); + }) + } + } + Component { + id: dialogComponent + Dialog { + + TDLibFile { + id: previewFile + tdlib: tdLibWrapper + autoLoad: model[queryResultItem.animationKey].mime_type !== "text/html" + fileInformation: queryResultItem.isAnimation ? model[queryResultItem.animationKey].animation : model[queryResultItem.animationKey].video + } + + DialogHeader { id: dialogHeader } + + ProgressCircle { + value: previewFile.downloadedSize / previewFile.expectedSize + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + anchors.centerIn: parent + opacity: previewFile.isDownloadingActive ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + } + Column { + visible: !previewFile.autoLoad + spacing: Theme.paddingLarge + anchors { + left: parent.left + leftMargin: Theme.horizontalPageMargin + right: parent.right + rightMargin: Theme.horizontalPageMargin + verticalCenter: parent.verticalCenter + } + + Label { + width: parent.width + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.secondaryHighlightColor + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: Emoji.emojify(model.title || "", font.pixelSize); + visible: text.length > 1 + linkColor: Theme.primaryColor + } + + Label { + width: parent.width + font.pixelSize: Theme.fontSizeLarge + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.highlightColor + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: ''+Emoji.emojify(previewFile.fileInformation.remote.id, font.pixelSize)+' ' + linkColor: Theme.primaryColor + } + + Label { + width: parent.width + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.secondaryHighlightColor + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: Emoji.emojify(model.description || "", font.pixelSize) + visible: text.length > 1 + linkColor: Theme.secondaryColor + } + } + + + Loader { + id: videoLoader + anchors { + top: dialogHeader.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + active: previewFile.isDownloadingCompleted + asynchronous: true + sourceComponent: Component { + Item { + Connections { + target: resultView + onRequestPlayback: { + if(previewVideo.playbackState === MediaPlayer.PlayingState && previewVideo.source !== playbackSource) { + previewVideo.pause() + } + } + } + Timer { + id: loopTimer + interval: 0 + onTriggered: previewVideo.play() + } + + Video { + id: previewVideo + source: previewFile.path + autoPlay: true + muted: queryResultItem.mutePreview + anchors.fill: parent + + onStatusChanged: { + if (status == MediaPlayer.EndOfMedia) { + if(queryResultItem.loopPreview) { + loopTimer.start() + } + } + } + onPlaybackStateChanged: { + if(playbackState === MediaPlayer.PlayingState) { + resultView.requestPlayback(source); + } + } + layer.enabled: playPauseMouseArea.pressed + layer.effect: PressEffect { source: previewVideo } + } + MouseArea { + id: playPauseMouseArea + anchors.fill: parent + onClicked: { + if(previewVideo.playbackState === MediaPlayer.PlayingState) { + previewVideo.pause(); + } else { + previewVideo.play(); + } + } + } + } + } + } + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultArticle.qml b/qml/components/inlineQueryResults/InlineQueryResultArticle.qml new file mode 100644 index 0000000..d002362 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultArticle.qml @@ -0,0 +1,43 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../../js/twemoji.js" as Emoji + +InlineQueryResultDefaultBase { + id: queryResultItem + + title: Emoji.emojify(model.title || "", titleLable.font.pixelSize) + description: Emoji.emojify(model.description || "", descriptionLabel.font.pixelSize) + descriptionLabel { + maximumLineCount: 3 + wrapMode: extraText.length === 0 ? Text.Wrap : Text.NoWrap + } + + extraText: model.url || "" + extraTextLabel.visible: !model.hide_url && extraText.length > 0 + + thumbnailFileInformation: model.thumbnail ? model.thumbnail.file : {} + + icon.source: "image://theme/icon-m-link" + icon.visible: thumbnail.visible && thumbnail.opacity === 0 + + thumbnail.visible: model.thumbnail || !!model.url +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultAudio.qml b/qml/components/inlineQueryResults/InlineQueryResultAudio.qml new file mode 100644 index 0000000..aced1a7 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultAudio.qml @@ -0,0 +1,183 @@ +/* + 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 QtMultimedia 5.6 +import WerkWolf.Fernschreiber 1.0 +import "../" +import "../../js/twemoji.js" as Emoji + +InlineQueryResult { + id: queryResultItem + property var resultData: model.audio || model.voice_note + property var audioData: resultData.audio || resultData.voice + + enabled: false // don't send on click + + Connections { + target: resultView + onRequestPlayback: { + if(audioPlayer.playbackState === Audio.PlayingState && audioPlayer.source !== playbackSource) { + audioPlayer.pause() + } + } + } + + TDLibFile { + id: file + tdlib: tdLibWrapper + autoLoad: false + fileInformation: queryResultItem.audioData + } + + TDLibFile { + id: thumbnail + tdlib: tdLibWrapper + autoLoad: true + fileInformation: queryResultItem.resultData.album_cover_thumbnail ? queryResultItem.resultData.album_cover_thumbnail.file : {} + } + + Loader { + id: thumbnailLoader + asynchronous: true + active: thumbnail.isDownloadingCompleted + height: parent.height + width: height + opacity: item && item.status === Image.Ready ? 0.5 : 0.0 + Behavior on opacity { FadeAnimation {} } + sourceComponent: Component { + Image { + id: thumbnailImage + source: thumbnail.path + sourceSize.width: width + sourceSize.height: height + + layer.enabled: playPauseButton.pressed + layer.effect: PressEffect { source: thumbnailImage } + } + } + } + + IconButton { + id: playPauseButton + anchors.centerIn: thumbnailLoader + icon { + asynchronous: true + source: audioPlayer.playbackState === Audio.PlayingState || (file.isDownloadingActive && audioPlayer.autoPlay) ? "image://theme/icon-m-pause": "image://theme/icon-m-play" + } + onClicked: { + if(!file.isDownloadingCompleted && !file.isDownloadingActive) { + file.load(); + audioPlayer.autoPlay = true + } else if(file.isDownloadingActive) { + // cancel playback intent? + audioPlayer.autoPlay = false + } else if(file.isDownloadingCompleted) { + //playPause + if(audioPlayer.playbackState === Audio.PlayingState) { + audioPlayer.pause(); + } else { + audioPlayer.play(); + } + } + } + } + ProgressCircle { + value: file.downloadedSize / file.expectedSize + width: Theme.iconSizeMedium + height: Theme.iconSizeMedium + anchors.centerIn: playPauseButton + opacity: file.isDownloadingActive ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + } + + Audio { + id: audioPlayer + source: file.isDownloadingCompleted ? file.path : "" + autoPlay: false + onPlaybackStateChanged: { + if(playbackState === Audio.PlayingState) { + resultView.requestPlayback(source); + } + } + } + + Column { + anchors { + left: thumbnailLoader.right + leftMargin: Theme.paddingSmall + right: sendButton.left + verticalCenter: parent.verticalCenter + } + + Label { + width: parent.width + font.pixelSize: Theme.fontSizeSmall + color: Theme.highlightColor + text: Emoji.emojify(queryResultItem.resultData.performer || "", font.pixelSize) + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + Label { + width: parent.width + font.pixelSize: Theme.fontSizeTiny + color: Theme.secondaryHighlightColor + text: Emoji.emojify(queryResultItem.resultData.title || model.title || "", font.pixelSize) + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + Item { + height: sizeLabel.height + width: parent.width + Label { + id: durationLabel + font.pixelSize: Theme.fontSizeTiny + color: Theme.secondaryColor + text: (audioPlayer.position > 0 || audioPlayer.playbackState === Audio.PlayingState ? (Format.formatDuration(audioPlayer.position/1000, Formatter.DurationShort)+" / ") : "") + Format.formatDuration(queryResultItem.audioData.duration || (audioPlayer.duration/1000), Formatter.DurationShort) + visible: (queryResultItem.audioData.duration || (audioPlayer.duration/1000)) > 0 + truncationMode: TruncationMode.Fade + } + Label { + id: sizeLabel + anchors.right: parent.right + font.pixelSize: Theme.fontSizeTiny + color: Theme.secondaryColor + text: Format.formatFileSize(file.expectedSize) + visible: file.expectedSize > 0 + truncationMode: TruncationMode.Fade + } + } + } + + IconButton { + id: sendButton + anchors { + right: parent.right + rightMargin: Theme.horizontalPageMargin + verticalCenter: parent.verticalCenter + } + icon { + asynchronous: true + source: "image://theme/icon-m-send" + } + onClicked: { + queryResultItem.sendInlineQueryResultMessage(); + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultContact.qml b/qml/components/inlineQueryResults/InlineQueryResultContact.qml new file mode 100644 index 0000000..dc05dfa --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultContact.qml @@ -0,0 +1,39 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../../js/twemoji.js" as Emoji + +InlineQueryResultDefaultBase { + id: queryResultItem + property string namesSeparator: model.contact.first_name && model.contact.last_name ? " " : "" + + title: Emoji.emojify(model.contact.first_name + namesSeparator + model.contact.last_name || "", titleLable.font.pixelSize) + description: Emoji.emojify(model.contact.phone_number || "", descriptionLabel.font.pixelSize) + + extraText: model.url || "" + extraTextLabel.visible: !model.hide_url && extraText.length > 0 + + thumbnailFileInformation: model.thumbnail ? model.thumbnail.file : {} + + icon.source: "image://theme/icon-m-contact" + icon.visible: thumbnail.visible && thumbnail.opacity === 0 + +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultDefaultBase.qml b/qml/components/inlineQueryResults/InlineQueryResultDefaultBase.qml new file mode 100644 index 0000000..4c389bc --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultDefaultBase.qml @@ -0,0 +1,103 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../" + +InlineQueryResult { + id: queryResultItem + + property alias title: titleLabel.text + property alias titleLable: titleLabel + + property alias description: descriptionLabel.text + property alias descriptionLabel: descriptionLabel + + property alias extraText: extraTextLabel.text + property alias extraTextLabel: extraTextLabel + + property alias thumbnailFileInformation: thumbnailFile.fileInformation + property alias thumbnail: thumbnail + + property alias icon: icon + + + Image { + id: thumbnail + source: thumbnailFile.isDownloadingCompleted ? thumbnailFile.path : "" + fillMode: Image.PreserveAspectCrop + asynchronous: true + width: visible ? Theme.itemSizeLarge : 0 + height: width + opacity: status === Image.Ready ? 1.0 : 0.0 + + Behavior on opacity { FadeAnimation {} } + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: thumbnail } + + TDLibFile { + id: thumbnailFile + tdlib: tdLibWrapper + autoLoad: true + } + } + Icon { + id: icon + asynchronous: true + anchors.centerIn: thumbnail + Behavior on opacity { FadeAnimation {} } + } + + Column { + anchors { + left: thumbnail.right + leftMargin: thumbnail.visible ? Theme.paddingLarge : Theme.horizontalPageMargin + right: parent.right + rightMargin: Theme.horizontalPageMargin + verticalCenter: parent.verticalCenter + } + + Label { + id: titleLabel + width: parent.width + font.pixelSize: Theme.fontSizeSmall + color: highlighted || !queryResultItem.enabled ? Theme.highlightColor : Theme.primaryColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + Label { + id: descriptionLabel + width: parent.width + font.pixelSize: Theme.fontSizeTiny + color: highlighted || !queryResultItem.enabled ? Theme.secondaryColor : Theme.secondaryHighlightColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + + Label { + id: extraTextLabel + width: parent.width + font.pixelSize: Theme.fontSizeTiny + color: highlighted || !queryResultItem.enabled ? Theme.secondaryHighlightColor : Theme.secondaryColor + visible: text.length > 0 + truncationMode: TruncationMode.Fade + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultDocument.qml b/qml/components/inlineQueryResults/InlineQueryResultDocument.qml new file mode 100644 index 0000000..13e1238 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultDocument.qml @@ -0,0 +1,49 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../" +import "../../js/twemoji.js" as Emoji + +Loader { + Component { + id: documentComponent + InlineQueryResultDefaultBase { + id: queryResultItem + + title: Emoji.emojify(model.title || model.document.file_name || "", titleLable.font.pixelSize) + description: Emoji.emojify(model.description || model.document.file_name || "", descriptionLabel.font.pixelSize) + extraText: Format.formatFileSize(model.document.document.expected_size) + + thumbnailFileInformation: model.thumbnail ? model.thumbnail.file : {} + + icon.source: Theme.iconForMimeType(model.document.mime_type) + icon.visible: thumbnail.visible && thumbnail.opacity === 0 + } + } + Component { + id: voiceNoteDocumentComponent + InlineQueryResultVoiceNote { + resultData: model.document + audioData: model.document.document + } + } + sourceComponent: model.document.mime_type === "audio/ogg" ? voiceNoteDocumentComponent : documentComponent +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultGame.qml b/qml/components/inlineQueryResults/InlineQueryResultGame.qml new file mode 100644 index 0000000..75de557 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultGame.qml @@ -0,0 +1,53 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../../js/twemoji.js" as Emoji + +InlineQueryResultDefaultBase { + id: queryResultItem + + title: Emoji.emojify(model.game.title || "", titleLable.font.pixelSize) + description: Emoji.emojify(model.game.description || "", descriptionLabel.font.pixelSize) + descriptionLabel { + maximumLineCount: 3 + wrapMode: Text.Wrap + } + + icon.source: "image://theme/icon-m-game-controller" + icon.visible: thumbnail.opacity === 0 + + + Component.onCompleted: { + if (model.game.photo) { + // Check first which size fits best... + var photo + for (var i = 0; i < model.game.photo.sizes.length; i++) { + photo = model.game.photo.sizes[i].photo + if (model.game.photo.sizes[i].width >= queryResultItem.width) { + break + } + } + if (photo) { + thumbnailFileInformation = photo + } + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultLocation.qml b/qml/components/inlineQueryResults/InlineQueryResultLocation.qml new file mode 100644 index 0000000..07219c4 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultLocation.qml @@ -0,0 +1,23 @@ +/* + 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 + +InlineQueryResultVenue { + resultData: model +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultPhoto.qml b/qml/components/inlineQueryResults/InlineQueryResultPhoto.qml new file mode 100644 index 0000000..881b9c8 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultPhoto.qml @@ -0,0 +1,67 @@ +/* + 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 QtMultimedia 5.6 +import WerkWolf.Fernschreiber 1.0 + +InlineQueryResult { + id: queryResultItem + + + TDLibFile { + id: file + tdlib: tdLibWrapper + autoLoad: true + } + + Loader { + asynchronous: true + active: file.isDownloadingCompleted + anchors.fill: parent + opacity: item && item.status === Image.Ready ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + sourceComponent: Component { + Image { + id: image + source: file.path + asynchronous: true + clip: true + fillMode: Image.PreserveAspectCrop + layer.enabled: queryResultItem.pressed + layer.effect: PressEffect { source: image } + } + } + } + Component.onCompleted: { + if (model.photo) { + // Check first which size fits best... + var photo + for (var i = 0; i < model.photo.sizes.length; i++) { + photo = model.photo.sizes[i].photo + if (model.photo.sizes[i].width >= queryResultItem.width) { + break + } + } + if (photo) { + file.fileInformation = photo + } + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultSticker.qml b/qml/components/inlineQueryResults/InlineQueryResultSticker.qml new file mode 100644 index 0000000..4d2ab03 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultSticker.qml @@ -0,0 +1,106 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import Nemo.Thumbnailer 1.0 +import "../" + +InlineQueryResult { + id: queryResultItem + + property bool animate + property bool animating: animate && model.sticker.is_animated + property url stickerId: "http://sticker/" + model.sticker.sticker.remote.id + onAnimatingChanged: { + if(animating) { + resultView.requestPlayback(stickerId); + } + } + + Connections { + target: resultView + onRequestPlayback: { + if(queryResultItem.animating && queryResultItem.stickerId !== playbackSource) { + animate = false + } + } + } + + onPressAndHold: { + animate = !animate + } + + TDLibFile { + id: file + tdlib: tdLibWrapper + fileInformation: model.sticker.sticker + autoLoad: true + } + + Loader { + id: animatedStickerLoader + anchors { + fill: parent + margins: Theme.paddingLarge + } + active: queryResultItem.animating + sourceComponent: Component { + AnimatedImage { + id: animatedSticker + anchors.fill: parent + source: file.path + asynchronous: true + paused: !Qt.application.active + cache: false + layer.enabled: highlighted + layer.effect: PressEffect { source: animatedSticker } + } + } + } + + Image { + id: staticSticker + anchors { + fill: parent + margins: Theme.paddingLarge + } + source: file.path + fillMode: Image.PreserveAspectFit + autoTransform: true + asynchronous: true + visible: !queryResultItem.animating && opacity > 0 + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { FadeAnimation {} } + layer.enabled: queryResultItem.highlighted + layer.effect: PressEffect { source: staticSticker } + } + + Icon { + source: "image://theme/icon-m-video" + width: Theme.iconSizeExtraSmall + height: width + visible: model.sticker.is_animated + highlighted: queryResultItem.highlighted || queryResultItem.animating + anchors { + right: parent.right + bottom: parent.bottom + } + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultVenue.qml b/qml/components/inlineQueryResults/InlineQueryResultVenue.qml new file mode 100644 index 0000000..297f9c4 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultVenue.qml @@ -0,0 +1,51 @@ +/* + 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 WerkWolf.Fernschreiber 1.0 +import "../../js/twemoji.js" as Emoji + +InlineQueryResultDefaultBase { + id: queryResultItem + property string locationId + property var resultData: model.venue + + title: Emoji.emojify(queryResultItem.resultData.title || (queryResultItem.resultData.location.latitude + ":" + queryResultItem.resultData.location.longitude), titleLable.font.pixelSize) + description: Emoji.emojify(queryResultItem.resultData.address || "", descriptionLabel.font.pixelSize) + extraText: Emoji.emojify(queryResultItem.resultData.type || "", extraTextLabel.font.pixelSize) + + + Connections { + target: tdLibWrapper + onFileUpdated: { + if(fileInformation["@extra"] === queryResultItem.locationId) { + thumbnailFileInformation = fileInformation + } + + } + } + + Component.onCompleted: { + var dimensions = [ Math.round(thumbnail.width), Math.round(thumbnail.height)]; + + locationId = "location:" + resultData.location.latitude + ":" + resultData.location.longitude + ":" + dimensions[0] + ":" + dimensions[1]; + + tdLibWrapper.getMapThumbnailFile(chatId, resultData.location.latitude, resultData.location.longitude, dimensions[0], dimensions[1], locationId); + } +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultVideo.qml b/qml/components/inlineQueryResults/InlineQueryResultVideo.qml new file mode 100644 index 0000000..7b71f2b --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultVideo.qml @@ -0,0 +1,25 @@ +/* + 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 + +InlineQueryResultAnimation { + isAnimation: false + animationKey: "video" +} diff --git a/qml/components/inlineQueryResults/InlineQueryResultVoiceNote.qml b/qml/components/inlineQueryResults/InlineQueryResultVoiceNote.qml new file mode 100644 index 0000000..b78fc29 --- /dev/null +++ b/qml/components/inlineQueryResults/InlineQueryResultVoiceNote.qml @@ -0,0 +1,24 @@ +/* + 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 + +InlineQueryResultAudio { + +} diff --git a/qml/js/functions.js b/qml/js/functions.js index 923f068..4bbc8c0 100644 --- a/qml/js/functions.js +++ b/qml/js/functions.js @@ -145,6 +145,10 @@ function getMessageText(message, simple, currentUserId, ignoreEntities) { return myself ? qsTr("sent a self-destructing video that is expired", "myself") : qsTr("sent a self-destructing video that is expired"); case 'messageScreenshotTaken': return myself ? qsTr("created a screenshot in this chat", "myself") : qsTr("created a screenshot in this chat"); + case 'messageGame': + return simple ? (myself ? qsTr("sent a game", "myself") : qsTr("sent a game")) : ""; + case 'messageGameScore': + return myself ? qsTr("scored %Ln points", "myself", message.content.score) : qsTr("scored %Ln points", "myself", message.content.score); case 'messageUnsupported': return myself ? qsTr("sent an unsupported message", "myself") : qsTr("sent an unsupported message"); default: @@ -361,16 +365,16 @@ function handleLink(link) { if (typeof userInformation.id === "undefined") { appNotification.show(qsTr("Unable to find user %1").arg(userName)); } else { - tdLibWrapper.createPrivateChat(userInformation.id); + tdLibWrapper.createPrivateChat(userInformation.id, "openDirectly"); } } else if (link.indexOf("userId://") === 0) { - tdLibWrapper.createPrivateChat(link.substring(9)); + tdLibWrapper.createPrivateChat(link.substring(9), "openDirectly"); } else if (link.indexOf("tg://") === 0) { Debug.log("Special TG link: ", link); if (link.indexOf("tg://join?invite=") === 0) { tdLibWrapper.joinChatByInviteLink(tMePrefix + "joinchat/" + link.substring(17)); } else if (link.indexOf("tg://resolve?domain=") === 0) { - tdLibWrapper.searchPublicChat(link.substring(20)); + tdLibWrapper.searchPublicChat(link.substring(20), true); } } else if (link.indexOf("botCommand://") === 0) { // this gets returned to send on ChatPage return link.substring(13); @@ -383,7 +387,7 @@ function handleLink(link) { // Fail with nice error message if it doesn't work } else { Debug.log("Search public chat: ", link.substring(tMePrefix.length)); - tdLibWrapper.searchPublicChat(link.substring(tMePrefix.length)); + tdLibWrapper.searchPublicChat(link.substring(tMePrefix.length), true); // Check responses for updateBasicGroup or updateSupergroup // Fire createBasicGroupChat or createSupergroupChat // Do the necessary stuff to open the chat diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml index 4db6a78..bd10354 100644 --- a/qml/pages/ChatPage.qml +++ b/qml/pages/ChatPage.qml @@ -45,6 +45,7 @@ Page { property bool isSuperGroup: false; property bool isChannel: false; property var chatPartnerInformation; + property var botInformation; property var chatGroupInformation; property int chatOnlineMemberCount: 0; property var emojiProposals; @@ -59,6 +60,8 @@ Page { property var selectedMessages: [] readonly property bool isSelecting: selectedMessages.length > 0 readonly property bool canSendMessages: hasSendPrivilege("can_send_messages") + property bool doSendBotStartMessage + property string sendBotStartMessageParameter states: [ State { @@ -154,6 +157,9 @@ Page { if (isSecretChat) { tdLibWrapper.getSecretChat(chatInformation.type.secret_chat_id); } + if(chatPartnerInformation.type["@type"] === "userTypeBot") { + tdLibWrapper.getUserFullInfo(chatPartnerInformation.id) + } } else if (isBasicGroup) { chatGroupInformation = tdLibWrapper.getBasicGroup(chatInformation.type.basic_group_id); @@ -172,6 +178,7 @@ Page { } tdLibWrapper.getChatPinnedMessage(chatInformation.id); tdLibWrapper.toggleChatIsMarkedAsUnread(chatInformation.id, false); + } function getMessageStatusText(message, listItemIndex, lastReadSentIndex, useElapsed) { @@ -207,14 +214,13 @@ Page { } function clearAttachmentPreviewRow() { - attachmentPreviewRow.visible = false; attachmentPreviewRow.isPicture = false; attachmentPreviewRow.isVideo = false; attachmentPreviewRow.isDocument = false; attachmentPreviewRow.isVoiceNote = false; attachmentPreviewRow.isLocation = false; - attachmentPreviewRow.fileProperties = {}; - attachmentPreviewRow.locationData = {}; + attachmentPreviewRow.fileProperties = null; + attachmentPreviewRow.locationData = null; attachmentPreviewRow.attachmentDescription = ""; fernschreiberUtils.stopGeoLocationUpdates(); } @@ -286,6 +292,10 @@ Page { } function handleMessageTextReplacement(text, cursorPosition) { + if(!newMessageTextField.focus) { + return; + } + var wordBoundaries = getWordBoundaries(text, cursorPosition); var currentWord = text.substring(wordBoundaries.beginIndex, wordBoundaries.endIndex); @@ -300,6 +310,7 @@ Page { } else { knownUsersRepeater.model = undefined; } + } function replaceMessageText(text, cursorPosition, newText) { @@ -310,6 +321,19 @@ Page { newMessageTextField.cursorPosition = newIndex; lostFocusTimer.start(); } + + function setMessageText(text, doSend) { + if(doSend) { + tdLibWrapper.sendTextMessage(chatInformation.id, text, "0"); + } + else { + newMessageTextField.text = text + newMessageTextField.cursorPosition = text.length + lostFocusTimer.start(); + } + + } + function forwardMessages(fromChatId, messageIds) { forwardMessagesTimer.fromChatId = fromChatId; forwardMessagesTimer.messageIds = messageIds; @@ -402,6 +426,17 @@ Page { switch(status) { case PageStatus.Activating: tdLibWrapper.openChat(chatInformation.id); + if(!chatPage.isInitialized) { + if(chatInformation.draft_message) { + if(chatInformation.draft_message && chatInformation.draft_message.input_message_text) { + newMessageTextField.text = chatInformation.draft_message.input_message_text.text.text; + if(chatInformation.draft_message.reply_to_message_id) { + tdLibWrapper.getMessage(chatInformation.id, chatInformation.draft_message.reply_to_message_id); + } + } + } + } + break; case PageStatus.Active: if (!chatPage.isInitialized) { @@ -410,13 +445,8 @@ Page { pageStack.pushAttached(Qt.resolvedUrl("ChatInformationPage.qml"), { "chatInformation" : chatInformation, "privateChatUserInformation": chatPartnerInformation, "groupInformation": chatGroupInformation, "chatOnlineMemberCount": chatOnlineMemberCount}); chatPage.isInitialized = true; - if(chatInformation.draft_message) { - if(chatInformation.draft_message && chatInformation.draft_message.input_message_text) { - newMessageTextField.text = chatInformation.draft_message.input_message_text.text.text; - if(chatInformation.draft_message.reply_to_message_id) { - tdLibWrapper.getMessage(chatInformation.id, chatInformation.draft_message.reply_to_message_id); - } - } + if(doSendBotStartMessage) { + tdLibWrapper.sendBotStartMessage(chatInformation.id, chatInformation.id, sendBotStartMessageParameter, "") } } break; @@ -494,6 +524,25 @@ Page { chatPage.isSecretChatReady = chatPage.secretChatDetails.state["@type"] === "secretChatStateReady"; } } + onCallbackQueryAnswer: { + if(text.length > 0) { // ignore bool "alert", just show as notification: + appNotification.show(Emoji.emojify(text, Theme.fontSizeSmall)); + } + if(url.length > 0) { + Functions.handleLink(url); + } + } + onUserFullInfoReceived: { + if(userFullInfo["@extra"] === chatPartnerInformation.id.toString()) { + chatPage.botInformation = userFullInfo; + } + } + onUserFullInfoUpdated: { + + if(userId === chatPartnerInformation.id) { + chatPage.botInformation = userFullInfo; + } + } } Connections { @@ -895,7 +944,7 @@ Page { id: chatView visible: !blurred - property bool blurred: messageOverlayLoader.item || stickerPickerLoader.item || voiceNoteOverlayLoader.item + property bool blurred: messageOverlayLoader.item || stickerPickerLoader.item || voiceNoteOverlayLoader.item || inlineQuery.hasOverlay anchors.fill: parent opacity: chatPage.loading ? 0 : 1 @@ -975,6 +1024,38 @@ Page { } model: chatModel + header: Component { + Loader { + active: !!chatPage.botInformation + && !!chatPage.botInformation.bot_info && chatPage.botInformation.bot_info.description.length > 0 + asynchronous: true + width: chatView.width + sourceComponent: Component { + Label { + id: botInfoLabel + topPadding: Theme.paddingLarge + bottomPadding: Theme.paddingLarge + leftPadding: Theme.horizontalPageMargin + rightPadding: Theme.horizontalPageMargin + text: Emoji.emojify(chatPage.botInformation.bot_info.description, font.pixelSize) + font.pixelSize: Theme.fontSizeSmall + color: Theme.highlightColor + wrapMode: Text.Wrap + textFormat: Text.StyledText + horizontalAlignment: Text.AlignHCenter + onLinkActivated: { + var chatCommand = Functions.handleLink(link); + if(chatCommand) { + tdLibWrapper.sendTextMessage(chatInformation.id, chatCommand); + } + } + linkColor: Theme.primaryColor + visible: (text !== "") + } + } + } + } + readonly property var contentComponentNames: ({ messageSticker: "StickerPreview", messagePhoto: "ImagePreview", @@ -986,7 +1067,8 @@ Page { messageDocument: "DocumentPreview", messageLocation: "LocationPreview", messageVenue: "LocationPreview", - messagePoll: "PollPreview" + messagePoll: "PollPreview", + messageGame: "GamePreview" }) function getContentComponentHeight(componentName, content, parentWidth) { switch(componentName) { @@ -1002,6 +1084,8 @@ Page { return Theme.itemSizeSmall; case "PollPreview": return Theme.itemSizeSmall * (4 + content.poll.options); + case "GamePreview": + return parentWidth * 0.66666666 + Theme.itemSizeLarge; // 2 / 3; } } @@ -1014,6 +1098,7 @@ Page { "messageChatJoinByLink", "messageChatSetTtl", "messageChatUpgradeFrom", + "messageGameScore", "messageChatUpgradeTo", "messageCustomServiceAction", "messagePinMessage", @@ -1168,12 +1253,17 @@ Page { } } + InlineQuery { + id: inlineQuery + textField: newMessageTextField + chatId: chatInformation.id + } } Column { id: newMessageColumn spacing: Theme.paddingSmall - topPadding: Theme.paddingSmall + topPadding: Theme.paddingSmall + inlineQuery.buttonPadding anchors.horizontalCenter: parent.horizontalCenter visible: height > 0 width: parent.width - ( 2 * Theme.horizontalPageMargin ) @@ -1212,8 +1302,8 @@ Page { property bool isNeeded: false width: chatPage.width x: -Theme.horizontalPageMargin - height: isNeeded ? attachmentOptionsRow.height : 0 - Behavior on height { SmoothedAnimation { duration: 200 } } + height: isNeeded && !inlineQuery.active ? attachmentOptionsRow.height : 0 + Behavior on height { SmoothedAnimation { duration: 200 } } visible: height > 0 contentHeight: attachmentOptionsRow.height contentWidth: Math.max(width, attachmentOptionsRow.width) @@ -1252,7 +1342,6 @@ Page { Debug.log("Selected document: ", picker.selectedContentProperties.filePath ); attachmentPreviewRow.fileProperties = picker.selectedContentProperties; attachmentPreviewRow.isPicture = true; - attachmentPreviewRow.visible = true; controlSendButton(); }) } @@ -1269,7 +1358,6 @@ Page { Debug.log("Selected video: ", picker.selectedContentProperties.filePath ); attachmentPreviewRow.fileProperties = picker.selectedContentProperties; attachmentPreviewRow.isVideo = true; - attachmentPreviewRow.visible = true; controlSendButton(); }) } @@ -1299,7 +1387,6 @@ Page { Debug.log("Selected document: ", picker.selectedContentProperties.filePath ); attachmentPreviewRow.fileProperties = picker.selectedContentProperties; attachmentPreviewRow.isDocument = true; - attachmentPreviewRow.visible = true; controlSendButton(); }) } @@ -1337,7 +1424,6 @@ Page { attachmentOptionsFlickable.isNeeded = false; attachmentPreviewRow.isLocation = true; attachmentPreviewRow.attachmentDescription = qsTr("Location: Obtaining position..."); - attachmentPreviewRow.visible = true; controlSendButton(); } } @@ -1348,7 +1434,7 @@ Page { Row { id: attachmentPreviewRow - visible: false + visible: (!!locationData || !!fileProperties) && !inlineQuery.active spacing: Theme.paddingMedium width: parent.width layoutDirection: Qt.RightToLeft @@ -1359,8 +1445,8 @@ Page { property bool isDocument: false; property bool isVoiceNote: false; property bool isLocation: false; - property var locationData: ({}); - property var fileProperties:({}); + property var locationData: null; + property var fileProperties: null; property string attachmentDescription: ""; Connections { @@ -1390,15 +1476,15 @@ Page { sourceSize.height: height fillMode: Thumbnail.PreserveAspectCrop - mimeType: typeof attachmentPreviewRow.fileProperties !== "undefined" ? attachmentPreviewRow.fileProperties.mimeType || "" : "" - source: typeof attachmentPreviewRow.fileProperties !== "undefined" ? attachmentPreviewRow.fileProperties.url || "" : "" + mimeType: !!attachmentPreviewRow.fileProperties ? attachmentPreviewRow.fileProperties.mimeType || "" : "" + source: !!attachmentPreviewRow.fileProperties ? attachmentPreviewRow.fileProperties.url || "" : "" visible: attachmentPreviewRow.isPicture || attachmentPreviewRow.isVideo } Label { id: attachmentPreviewText font.pixelSize: Theme.fontSizeSmall - text: ( attachmentPreviewRow.isVoiceNote || attachmentPreviewRow.isLocation ) ? attachmentPreviewRow.attachmentDescription : ( typeof attachmentPreviewRow.fileProperties !== "undefined" ? attachmentPreviewRow.fileProperties.fileName || "" : "" ); + text: ( attachmentPreviewRow.isVoiceNote || attachmentPreviewRow.isLocation ) ? attachmentPreviewRow.attachmentDescription : ( !!attachmentPreviewRow.fileProperties ? attachmentPreviewRow.fileProperties.fileName || "" : "" ); anchors.verticalCenter: parent.verticalCenter maximumLineCount: 1 @@ -1491,9 +1577,11 @@ Page { id: atMentionColumn width: parent.width anchors.horizontalCenter: parent.horizontalCenter - visible: knownUsersRepeater.count > 0 ? true : false + visible: opacity > 0 opacity: knownUsersRepeater.count > 0 ? 1 : 0 Behavior on opacity { NumberAnimation {} } + height: knownUsersRepeater.count > 0 ? childrenRect.height : 0 + Behavior on height { SmoothedAnimation { duration: 200 } } spacing: Theme.paddingMedium Flickable { @@ -1598,7 +1686,7 @@ Page { TextArea { id: newMessageTextField - width: parent.width - attachmentIconButton.width - (newMessageSendButton.visible ? newMessageSendButton.width : 0) + width: parent.width - (attachmentIconButton.visible ? attachmentIconButton.width : 0) - (newMessageSendButton.visible ? newMessageSendButton.width : 0) - (cancelInlineQueryButton.visible ? cancelInlineQueryButton.width : 0) height: Math.min(chatContainer.height / 3, implicitHeight) anchors.verticalCenter: parent.verticalCenter font.pixelSize: Theme.fontSizeSmall @@ -1617,7 +1705,7 @@ Page { } } - EnterKey.enabled: !appSettings.sendByEnter || text.length + EnterKey.enabled: !inlineQuery.active && (!appSettings.sendByEnter || text.length) EnterKey.iconSource: appSettings.sendByEnter ? "image://theme/icon-m-chat" : "image://theme/icon-m-enter" onTextChanged: { @@ -1632,6 +1720,7 @@ Page { anchors.bottom: parent.bottom anchors.bottomMargin: Theme.paddingSmall enabled: !attachmentPreviewRow.visible + visible: !inlineQuery.active onClicked: { if (attachmentOptionsFlickable.isNeeded) { attachmentOptionsFlickable.isNeeded = false; @@ -1648,7 +1737,7 @@ Page { icon.source: "image://theme/icon-m-chat" anchors.bottom: parent.bottom anchors.bottomMargin: Theme.paddingSmall - visible: !appSettings.sendByEnter || attachmentPreviewRow.visible + visible: !inlineQuery.active && (!appSettings.sendByEnter || attachmentPreviewRow.visible) enabled: false onClicked: { sendMessage(); @@ -1658,6 +1747,42 @@ Page { } } } + + Item { + width: cancelInlineQueryButton.width + height: cancelInlineQueryButton.height + visible: inlineQuery.active + anchors.bottom: parent.bottom + anchors.bottomMargin: Theme.paddingSmall + + IconButton { + id: cancelInlineQueryButton + icon.source: "image://theme/icon-m-cancel" + visible: parent.visible + opacity: inlineQuery.isLoading ? 0.2 : 1 + Behavior on opacity { FadeAnimation {} } + onClicked: { + if(inlineQuery.query !== "") { + newMessageTextField.text = "@" + inlineQuery.userName + " " + newMessageTextField.cursorPosition = newMessageTextField.text.length + lostFocusTimer.start(); + } else { + newMessageTextField.text = "" + } + } + onPressAndHold: { + newMessageTextField.text = "" + } + } + + BusyIndicator { + size: BusyIndicatorSize.Small + anchors.centerIn: parent + running: inlineQuery.isLoading + } + } + + } } } diff --git a/qml/pages/ChatSelectionPage.qml b/qml/pages/ChatSelectionPage.qml index f75ab64..1e6262a 100644 --- a/qml/pages/ChatSelectionPage.qml +++ b/qml/pages/ChatSelectionPage.qml @@ -43,6 +43,9 @@ Dialog { case "forwardMessages": acceptDestinationInstance.forwardMessages(payload.fromChatId, payload.messageIds) break; + case "fillTextArea": // ReplyMarkupButtons: inlineKeyboardButtonTypeSwitchInline + acceptDestinationInstance.setMessageText(payload.text) + break; // future uses of chat selection can be processed here } } @@ -75,7 +78,7 @@ Dialog { QtObject { property bool visible: false Component.onCompleted: { - if(chatSelectionPage.state === "forwardMessages") { + if(chatSelectionPage.state === "forwardMessages" || chatSelectionPage.state === "fillTextArea" ) { var chatType = display.type['@type'] var chatGroupInformation if(chatType === "chatTypePrivate" || chatType === "chatTypeSecret") { @@ -126,6 +129,7 @@ Dialog { var chat = tdLibWrapper.getChat(display.id); switch(chatSelectionPage.state) { case "forwardMessages": + case "fillTextArea": chatSelectionPage.acceptDestinationProperties = { "chatInformation" : chat}; chatSelectionPage.acceptDestination = Qt.resolvedUrl("../pages/ChatPage.qml"); break; diff --git a/qml/pages/NewChatPage.qml b/qml/pages/NewChatPage.qml index b98f5fd..917d7ea 100644 --- a/qml/pages/NewChatPage.qml +++ b/qml/pages/NewChatPage.qml @@ -220,7 +220,7 @@ Page { icon.source: "image://theme/icon-m-chat" anchors.verticalCenter: parent.verticalCenter onClicked: { - tdLibWrapper.createPrivateChat(display.id); + tdLibWrapper.createPrivateChat(display.id, "openDirectly"); } } @@ -257,7 +257,7 @@ Page { MouseArea { anchors.fill: parent onClicked: { - tdLibWrapper.createPrivateChat(display.id); + tdLibWrapper.createPrivateChat(display.id, "openDirectly"); } onPressed: { privateChatHighlightBackground.visible = true; @@ -295,7 +295,7 @@ Page { icon.source: "image://theme/icon-m-device-lock" anchors.verticalCenter: parent.verticalCenter onClicked: { - tdLibWrapper.createNewSecretChat(display.id); + tdLibWrapper.createNewSecretChat(display.id, "openDirectly"); } } @@ -331,7 +331,7 @@ Page { MouseArea { anchors.fill: parent onClicked: { - tdLibWrapper.createNewSecretChat(display.id); + tdLibWrapper.createNewSecretChat(display.id, "openDirectly"); } onPressed: { secretChatHighlightBackground.visible = true; diff --git a/qml/pages/OverviewPage.qml b/qml/pages/OverviewPage.qml index f9abc67..b58dbc0 100644 --- a/qml/pages/OverviewPage.qml +++ b/qml/pages/OverviewPage.qml @@ -210,10 +210,17 @@ Page { } } onChatReceived: { - if(chat["@extra"] === "openDirectly") { + var openAndSendStartToBot = chat["@extra"].indexOf("openAndSendStartToBot:") === 0 + if(chat["@extra"] === "openDirectly" || openAndSendStartToBot && chat.type["@type"] === "chatTypePrivate") { pageStack.pop(overviewPage, PageStackAction.Immediate) // if we get a new chat (no messages?), we can not use the provided data - pageStack.push(Qt.resolvedUrl("../pages/ChatPage.qml"), { "chatInformation" : tdLibWrapper.getChat(chat.id) }); + var chatinfo = tdLibWrapper.getChat(chat.id); + var options = { "chatInformation" : chatinfo } + if(openAndSendStartToBot) { + options.doSendBotStartMessage = true; + options.sendBotStartMessageParameter = chat["@extra"].substring(22); + } + pageStack.push(Qt.resolvedUrl("../pages/ChatPage.qml"), options); } } onErrorReceived: { diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml index b91691e..bc8d0c8 100644 --- a/qml/pages/SettingsPage.qml +++ b/qml/pages/SettingsPage.qml @@ -159,6 +159,20 @@ Page { } } + SectionHeader { + text: qsTr("Privacy") + } + + TextSwitch { + checked: appSettings.allowInlineBotLocationAccess + text: qsTr("Allow sending Location to inline bots") + description: qsTr("Some inline bots request location data when using them") + automaticCheck: false + onClicked: { + appSettings.allowInlineBotLocationAccess = !checked + } + } + SectionHeader { text: qsTr("Storage") } diff --git a/src/appsettings.cpp b/src/appsettings.cpp index d9239a9..525f078 100644 --- a/src/appsettings.cpp +++ b/src/appsettings.cpp @@ -29,6 +29,7 @@ namespace { const QString KEY_NOTIFICATION_TURNS_DISPLAY_ON("notificationTurnsDisplayOn"); const QString KEY_NOTIFICATION_FEEDBACK("notificationFeedback"); const QString KEY_STORAGE_OPTIMIZER("storageOptimizer"); + const QString KEY_INLINEBOT_LOCATION_ACCESS("allowInlineBotLocationAccess"); const QString KEY_REMAINING_INTERACTION_HINTS("remainingInteractionHints"); const QString KEY_ONLINE_ONLY_MODE("onlineOnlyMode"); } @@ -149,6 +150,21 @@ void AppSettings::setStorageOptimizer(bool enable) } } +bool AppSettings::allowInlineBotLocationAccess() const +{ + return settings.value(KEY_INLINEBOT_LOCATION_ACCESS, false).toBool(); +} + +void AppSettings::setAllowInlineBotLocationAccess(bool enable) +{ + + if (allowInlineBotLocationAccess() != enable) { + LOG(KEY_INLINEBOT_LOCATION_ACCESS << enable); + settings.setValue(KEY_INLINEBOT_LOCATION_ACCESS, enable); + emit allowInlineBotLocationAccessChanged(); + } +} + int AppSettings::remainingInteractionHints() const { return settings.value(KEY_REMAINING_INTERACTION_HINTS, 3).toInt(); diff --git a/src/appsettings.h b/src/appsettings.h index 373d3df..d6135d2 100644 --- a/src/appsettings.h +++ b/src/appsettings.h @@ -31,6 +31,7 @@ class AppSettings : public QObject { Q_PROPERTY(bool notificationTurnsDisplayOn READ notificationTurnsDisplayOn WRITE setNotificationTurnsDisplayOn NOTIFY notificationTurnsDisplayOnChanged) Q_PROPERTY(NotificationFeedback notificationFeedback READ notificationFeedback WRITE setNotificationFeedback NOTIFY notificationFeedbackChanged) Q_PROPERTY(bool storageOptimizer READ storageOptimizer WRITE setStorageOptimizer NOTIFY storageOptimizerChanged) + Q_PROPERTY(bool allowInlineBotLocationAccess READ allowInlineBotLocationAccess WRITE setAllowInlineBotLocationAccess NOTIFY allowInlineBotLocationAccessChanged) Q_PROPERTY(int remainingInteractionHints READ remainingInteractionHints WRITE setRemainingInteractionHints NOTIFY remainingInteractionHintsChanged) Q_PROPERTY(bool onlineOnlyMode READ onlineOnlyMode WRITE setOnlineOnlyMode NOTIFY onlineOnlyModeChanged) @@ -69,6 +70,9 @@ public: bool storageOptimizer() const; void setStorageOptimizer(bool enable); + bool allowInlineBotLocationAccess() const; + void setAllowInlineBotLocationAccess(bool enable); + int remainingInteractionHints() const; void setRemainingInteractionHints(int remainingHints); @@ -84,6 +88,7 @@ signals: void notificationTurnsDisplayOnChanged(); void notificationFeedbackChanged(); void storageOptimizerChanged(); + void allowInlineBotLocationAccessChanged(); void remainingInteractionHintsChanged(); void onlineOnlyModeChanged(); diff --git a/src/fernschreiberutils.cpp b/src/fernschreiberutils.cpp index 3237982..57a07e7 100644 --- a/src/fernschreiberutils.cpp +++ b/src/fernschreiberutils.cpp @@ -178,6 +178,13 @@ QString FernschreiberUtils::getMessageShortText(TDLibWrapper *tdLibWrapper, cons if (contentType == "messageScreenshotTaken") { return myself ? tr("created a screenshot in this chat", "myself") : tr("created a screenshot in this chat"); } + if (contentType == "messageGameScore") { + qint32 score = messageContent.value("score").toInt(); + return myself ? tr("scored %Ln points", "myself", score) : tr("scored %Ln points", "", score); + } + if (contentType == "messageGame") { + return myself ? tr("sent a game", "myself") : tr("sent a game"); + } if (contentType == "messageUnsupported") { return myself ? tr("sent an unsupported message", "myself") : tr("sent an unsupported message"); } diff --git a/src/tdlibreceiver.cpp b/src/tdlibreceiver.cpp index 045e27e..6a62d28 100644 --- a/src/tdlibreceiver.cpp +++ b/src/tdlibreceiver.cpp @@ -137,6 +137,8 @@ TDLibReceiver::TDLibReceiver(void *tdLibClient, QObject *parent) : QThread(paren handlers.insert("updateMessageEdited", &TDLibReceiver::processUpdateMessageEdited); handlers.insert("updateChatIsMarkedAsUnread", &TDLibReceiver::processUpdateChatIsMarkedAsUnread); handlers.insert("updateChatDraftMessage", &TDLibReceiver::processUpdateChatDraftMessage); + handlers.insert("inlineQueryResults", &TDLibReceiver::processInlineQueryResults); + handlers.insert("callbackQueryAnswer", &TDLibReceiver::processCallbackQueryAnswer); } void TDLibReceiver::setActive(bool active) @@ -596,3 +598,16 @@ void TDLibReceiver::processUpdateChatDraftMessage(const QVariantMap &receivedInf LOG("Draft message was updated"); emit chatDraftMessageUpdated(receivedInformation.value(CHAT_ID).toLongLong(), receivedInformation.value("draft_message").toMap(), findChatPositionOrder(receivedInformation.value(POSITIONS).toList())); } + +void TDLibReceiver::processInlineQueryResults(const QVariantMap &receivedInformation) +{ + LOG("Inline Query results"); + emit inlineQueryResults(receivedInformation.value("inline_query_id").toString(), receivedInformation.value("next_offset").toString(), receivedInformation.value("results").toList(), receivedInformation.value("switch_pm_text").toString(), receivedInformation.value("switch_pm_parameter").toString(), receivedInformation.value(EXTRA).toString()); +} + +void TDLibReceiver::processCallbackQueryAnswer(const QVariantMap &receivedInformation) +{ + + LOG("Callback Query answer"); + emit callbackQueryAnswer(receivedInformation.value(TEXT).toString(), receivedInformation.value("alert").toBool(), receivedInformation.value("url").toString()); +} diff --git a/src/tdlibreceiver.h b/src/tdlibreceiver.h index 7eb8b99..4ffbe58 100644 --- a/src/tdlibreceiver.h +++ b/src/tdlibreceiver.h @@ -93,6 +93,8 @@ signals: void contactsImported(const QVariantList &importerCount, const QVariantList &userIds); void chatIsMarkedAsUnreadUpdated(qlonglong chatId, bool chatIsMarkedAsUnread); void chatDraftMessageUpdated(qlonglong chatId, const QVariantMap &draftMessage, const QString &order); + void inlineQueryResults(const QString &inlineQueryId, const QString &nextOffset, const QVariantList &results, const QString &switchPmText, const QString &switchPmParameter, const QString &extra); + void callbackQueryAnswer(const QString &text, bool alert, const QString &url); private: typedef void (TDLibReceiver::*Handler)(const QVariantMap &); @@ -160,6 +162,8 @@ private: void processImportedContacts(const QVariantMap &receivedInformation); void processUpdateChatIsMarkedAsUnread(const QVariantMap &receivedInformation); void processUpdateChatDraftMessage(const QVariantMap &receivedInformation); + void processInlineQueryResults(const QVariantMap &receivedInformation); + void processCallbackQueryAnswer(const QVariantMap &receivedInformation); }; #endif // TDLIBRECEIVER_H diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp index 80d8520..6731805 100644 --- a/src/tdlibwrapper.cpp +++ b/src/tdlibwrapper.cpp @@ -131,6 +131,8 @@ TDLibWrapper::TDLibWrapper(AppSettings *appSettings, MceInterface *mceInterface, connect(this->tdLibReceiver, SIGNAL(messageEditedUpdated(qlonglong, qlonglong, QVariantMap)), this, SIGNAL(messageEditedUpdated(qlonglong, qlonglong, QVariantMap))); connect(this->tdLibReceiver, SIGNAL(chatIsMarkedAsUnreadUpdated(qlonglong, bool)), this, SIGNAL(chatIsMarkedAsUnreadUpdated(qlonglong, bool))); connect(this->tdLibReceiver, SIGNAL(chatDraftMessageUpdated(qlonglong, QVariantMap, QString)), this, SIGNAL(chatDraftMessageUpdated(qlonglong, QVariantMap, QString))); + connect(this->tdLibReceiver, SIGNAL(inlineQueryResults(QString, QString, QVariantList, QString, QString, QString)), this, SIGNAL(inlineQueryResults(QString, QString, QVariantList, QString, QString, QString))); + connect(this->tdLibReceiver, SIGNAL(callbackQueryAnswer(QString, bool, QString)), this, SIGNAL(callbackQueryAnswer(QString, bool, QString))); connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList))); @@ -685,13 +687,12 @@ void TDLibWrapper::deleteMessages(const QString &chatId, const QVariantList mess this->sendRequest(requestObject); } -void TDLibWrapper::getMapThumbnailFile(const QString &chatId, double latitude, double longitude, int width, int height) +void TDLibWrapper::getMapThumbnailFile(const QString &chatId, double latitude, double longitude, int width, int height, const QString &extra) { LOG("Getting Map Thumbnail File" << chatId); QVariantMap location; location.insert("latitude", latitude); location.insert("longitude", longitude); - // ensure dimensions are in bounds (16 - 1024) int boundsWidth = std::min(std::max(width, 16), 1024); int boundsHeight = std::min(std::max(height, 16), 1024); @@ -704,6 +705,7 @@ void TDLibWrapper::getMapThumbnailFile(const QString &chatId, double latitude, d requestObject.insert("height", boundsHeight); requestObject.insert("scale", 1); // 1-3 requestObject.insert(CHAT_ID, chatId); + requestObject.insert(_EXTRA, extra); this->sendRequest(requestObject); } @@ -769,43 +771,43 @@ void TDLibWrapper::getUserFullInfo(const QString &userId) this->sendRequest(requestObject); } -void TDLibWrapper::createPrivateChat(const QString &userId) +void TDLibWrapper::createPrivateChat(const QString &userId, const QString &extra) { LOG("Creating Private Chat"); QVariantMap requestObject; requestObject.insert(_TYPE, "createPrivateChat"); requestObject.insert("user_id", userId); - requestObject.insert(_EXTRA, "openDirectly"); //gets matched in qml + requestObject.insert(_EXTRA, extra); //"openDirectly"/"openAndSendStartToBot:[optional parameter]" gets matched in qml this->sendRequest(requestObject); } -void TDLibWrapper::createNewSecretChat(const QString &userId) +void TDLibWrapper::createNewSecretChat(const QString &userId, const QString &extra) { LOG("Creating new secret chat"); QVariantMap requestObject; requestObject.insert(_TYPE, "createNewSecretChat"); requestObject.insert("user_id", userId); - requestObject.insert(_EXTRA, "openDirectly"); //gets matched in qml + requestObject.insert(_EXTRA, extra); //"openDirectly" gets matched in qml this->sendRequest(requestObject); } -void TDLibWrapper::createSupergroupChat(const QString &supergroupId) +void TDLibWrapper::createSupergroupChat(const QString &supergroupId, const QString &extra) { LOG("Creating Supergroup Chat"); QVariantMap requestObject; requestObject.insert(_TYPE, "createSupergroupChat"); requestObject.insert("supergroup_id", supergroupId); - requestObject.insert(_EXTRA, "openDirectly"); //gets matched in qml + requestObject.insert(_EXTRA, extra); //"openDirectly" gets matched in qml this->sendRequest(requestObject); } -void TDLibWrapper::createBasicGroupChat(const QString &basicGroupId) +void TDLibWrapper::createBasicGroupChat(const QString &basicGroupId, const QString &extra) { LOG("Creating Basic Group Chat"); QVariantMap requestObject; requestObject.insert(_TYPE, "createBasicGroupChat"); requestObject.insert("basic_group_id", basicGroupId); - requestObject.insert(_EXTRA, "openDirectly"); //gets matched in qml + requestObject.insert(_EXTRA, extra); //"openDirectly"/"openAndSend:*" gets matched in qml this->sendRequest(requestObject); } @@ -929,12 +931,15 @@ void TDLibWrapper::getPollVoters(const QString &chatId, qlonglong messageId, int this->sendRequest(requestObject); } -void TDLibWrapper::searchPublicChat(const QString &userName) +void TDLibWrapper::searchPublicChat(const QString &userName, bool doOpenOnFound) { LOG("Search public chat" << userName); - this->activeChatSearchName = userName; + if(doOpenOnFound) { + this->activeChatSearchName = userName; + } QVariantMap requestObject; requestObject.insert(_TYPE, "searchPublicChat"); + requestObject.insert(_EXTRA, "searchPublicChat:"+userName); requestObject.insert(USERNAME, userName); this->sendRequest(requestObject); } @@ -1075,6 +1080,53 @@ void TDLibWrapper::setChatDraftMessage(qlonglong chatId, qlonglong threadId, qlo this->sendRequest(requestObject); } +void TDLibWrapper::getInlineQueryResults(qlonglong botUserId, qlonglong chatId, const QVariantMap &userLocation, const QString &query, const QString &offset, const QString &extra) +{ + + LOG("Get Inline Query Results" << chatId << query); + QVariantMap requestObject; + requestObject.insert(_TYPE, "getInlineQueryResults"); + requestObject.insert(CHAT_ID, chatId); + requestObject.insert("bot_user_id", botUserId); + if(!userLocation.isEmpty()) { + requestObject.insert("user_location", userLocation); + } + requestObject.insert("query", query); + requestObject.insert("offset", offset); + requestObject.insert(_EXTRA, extra); + + this->sendRequest(requestObject); +} + +void TDLibWrapper::sendInlineQueryResultMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &queryId, const QString &resultId) +{ + + LOG("Send Inline Query Result Message" << chatId); + QVariantMap requestObject; + requestObject.insert(_TYPE, "sendInlineQueryResultMessage"); + requestObject.insert(CHAT_ID, chatId); + requestObject.insert("message_thread_id", threadId); + requestObject.insert("reply_to_message_id", replyToMessageId); + requestObject.insert("query_id", queryId); + requestObject.insert("result_id", resultId); + + this->sendRequest(requestObject); +} + +void TDLibWrapper::sendBotStartMessage(qlonglong botUserId, qlonglong chatId, const QString ¶meter, const QString &extra) +{ + + LOG("Send Bot Start Message" << botUserId << chatId << parameter << extra); + QVariantMap requestObject; + requestObject.insert(_TYPE, "sendBotStartMessage"); + requestObject.insert("bot_user_id", botUserId); + requestObject.insert(CHAT_ID, chatId); + requestObject.insert("parameter", parameter); + requestObject.insert(_EXTRA, extra); + + this->sendRequest(requestObject); +} + void TDLibWrapper::searchEmoji(const QString &queryString) { LOG("Searching emoji" << queryString); @@ -1349,12 +1401,12 @@ void TDLibWrapper::handleChatReceived(const QVariantMap &chatInformation) if (receivedChatType == ChatTypeBasicGroup) { LOG("Found basic group for active search" << this->activeChatSearchName); this->activeChatSearchName.clear(); - this->createBasicGroupChat(chatType.value("basic_group_id").toString()); + this->createBasicGroupChat(chatType.value("basic_group_id").toString(), "openDirectly"); } if (receivedChatType == ChatTypeSupergroup) { LOG("Found supergroup for active search" << this->activeChatSearchName); this->activeChatSearchName.clear(); - this->createSupergroupChat(chatType.value("supergroup_id").toString()); + this->createSupergroupChat(chatType.value("supergroup_id").toString(), "openDirectly"); } } } @@ -1381,7 +1433,7 @@ void TDLibWrapper::handleBasicGroupUpdated(qlonglong groupId, const QVariantMap if (!this->activeChatSearchName.isEmpty() && this->activeChatSearchName == groupInformation.value(USERNAME).toString()) { LOG("Found basic group for active search" << this->activeChatSearchName); this->activeChatSearchName.clear(); - this->createBasicGroupChat(groupInformation.value(ID).toString()); + this->createBasicGroupChat(groupInformation.value(ID).toString(), "openDirectly"); } } @@ -1391,7 +1443,7 @@ void TDLibWrapper::handleSuperGroupUpdated(qlonglong groupId, const QVariantMap if (!this->activeChatSearchName.isEmpty() && this->activeChatSearchName == groupInformation.value(USERNAME).toString()) { LOG("Found supergroup for active search" << this->activeChatSearchName); this->activeChatSearchName.clear(); - this->createSupergroupChat(groupInformation.value(ID).toString()); + this->createSupergroupChat(groupInformation.value(ID).toString(), "openDirectly"); } } diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h index c07c761..a6c25f5 100644 --- a/src/tdlibwrapper.h +++ b/src/tdlibwrapper.h @@ -152,17 +152,17 @@ public: Q_INVOKABLE void setChatNotificationSettings(const QString &chatId, const QVariantMap ¬ificationSettings); Q_INVOKABLE void editMessageText(const QString &chatId, const QString &messageId, const QString &message); Q_INVOKABLE void deleteMessages(const QString &chatId, const QVariantList messageIds); - Q_INVOKABLE void getMapThumbnailFile(const QString &chatId, double latitude, double longitude, int width, int height); + Q_INVOKABLE void getMapThumbnailFile(const QString &chatId, double latitude, double longitude, int width, int height, const QString &extra); Q_INVOKABLE void getRecentStickers(); Q_INVOKABLE void getInstalledStickerSets(); Q_INVOKABLE void getStickerSet(const QString &setId); Q_INVOKABLE void getSupergroupMembers(const QString &groupId, int limit, int offset); Q_INVOKABLE void getGroupFullInfo(const QString &groupId, bool isSuperGroup); Q_INVOKABLE void getUserFullInfo(const QString &userId); - Q_INVOKABLE void createPrivateChat(const QString &userId); - Q_INVOKABLE void createNewSecretChat(const QString &userId); - Q_INVOKABLE void createSupergroupChat(const QString &supergroupId); - Q_INVOKABLE void createBasicGroupChat(const QString &basicGroupId); + Q_INVOKABLE void createPrivateChat(const QString &userId, const QString &extra); + Q_INVOKABLE void createNewSecretChat(const QString &userId, const QString &extra); + Q_INVOKABLE void createSupergroupChat(const QString &supergroupId, const QString &extra); + Q_INVOKABLE void createBasicGroupChat(const QString &basicGroupId, const QString &extra); Q_INVOKABLE void getGroupsInCommon(const QString &userId, int limit, int offset); Q_INVOKABLE void getUserProfilePhotos(const QString &userId, int limit, int offset); Q_INVOKABLE void setChatPermissions(const QString &chatId, const QVariantMap &chatPermissions); @@ -174,7 +174,7 @@ public: Q_INVOKABLE void setPollAnswer(const QString &chatId, qlonglong messageId, QVariantList optionIds); Q_INVOKABLE void stopPoll(const QString &chatId, qlonglong messageId); Q_INVOKABLE void getPollVoters(const QString &chatId, qlonglong messageId, int optionId, int limit, int offset, const QString &extra); - Q_INVOKABLE void searchPublicChat(const QString &userName); + Q_INVOKABLE void searchPublicChat(const QString &userName, bool doOpenOnFound); Q_INVOKABLE void joinChatByInviteLink(const QString &inviteLink); Q_INVOKABLE void getDeepLinkInfo(const QString &link); Q_INVOKABLE void getContacts(); @@ -187,6 +187,9 @@ public: Q_INVOKABLE void toggleChatIsMarkedAsUnread(qlonglong chatId, bool isMarkedAsUnread); Q_INVOKABLE void toggleChatIsPinned(qlonglong chatId, bool isPinned); Q_INVOKABLE void setChatDraftMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &draft); + Q_INVOKABLE void getInlineQueryResults(qlonglong botUserId, qlonglong chatId, const QVariantMap &userLocation, const QString &query, const QString &offset, const QString &extra); + Q_INVOKABLE void sendInlineQueryResultMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &queryId, const QString &resultId); + Q_INVOKABLE void sendBotStartMessage(qlonglong botUserId, qlonglong chatId, const QString ¶meter, const QString &extra); // Others (candidates for extraction ;)) Q_INVOKABLE void searchEmoji(const QString &queryString); @@ -259,6 +262,8 @@ signals: void messageNotFound(qlonglong chatId, qlonglong messageId); void chatIsMarkedAsUnreadUpdated(qlonglong chatId, bool chatIsMarkedAsUnread); void chatDraftMessageUpdated(qlonglong chatId, const QVariantMap &draftMessage, const QString &order); + void inlineQueryResults(const QString &inlineQueryId, const QString &nextOffset, const QVariantList &results, const QString &switchPmText, const QString &switchPmParameter, const QString &extra); + void callbackQueryAnswer(const QString &text, bool alert, const QString &url); public slots: void handleVersionDetected(const QString &version); diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts index ced219c..1e481d7 100644 --- a/translations/harbour-fernschreiber-de.ts +++ b/translations/harbour-fernschreiber-de.ts @@ -823,6 +823,30 @@ myself haben %1 vom Chat entfernt + + scored %Ln points + myself + + haben %Ln Punkt erziehlt + haben %Ln Punkte erziehlt + + + + scored %Ln points + + hat %Ln Punkt erziehlt + hat %Ln Punkte erziehlt + + + + sent a game + myself + haben ein Spiel gesendet + + + sent a game + hat ein Spiel gesendet + ImagePage @@ -973,6 +997,21 @@ You Sie + + scored %Ln points in %2 + myself + + haben %Ln Punkt bei %2 erziehlt + haben %Ln Punkte bei %2 erziehlt + + + + scored %Ln points in %2 + + hat %Ln Punkt bei %2 erziehlt + hat %Ln Punkte bei %2 erziehlt + + MessageOverlayFlickable @@ -985,6 +1024,14 @@ Diese Nachricht wurde weitergeleitet. Ursprünglicher Autor: %1 + + MessageViaLabel + + via %1 + message posted via bot user + via %1 + + NewChatPage @@ -1445,6 +1492,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. Schaltet das Offline-Caching aus. Bestimmte Features können in diesem Modus eingeschränkt sein oder fehlen. Änderungen erfordern einen Neustart von Fernschreiber, um in Kraft zu treten. + + Privacy + Privatsphäre + + + Allow sending Location to inline bots + Erlaubt Standortsendung an Inline-Bots + + + Some inline bots request location data when using them + Einige Inline-Bots fragen bei Nutzung Standortdaten an + StickerPicker @@ -1912,5 +1971,22 @@ myself haben %1 vom Chat entfernt + + scored %Ln points + myself + + haben %Ln Punkt erziehlt + haben %Ln Punkte erziehlt + + + + sent a game + myself + haben ein Spiel gesendet + + + sent a game + hat ein Spiel gesendet + diff --git a/translations/harbour-fernschreiber-en.ts b/translations/harbour-fernschreiber-en.ts index a4bdfce..93be06c 100644 --- a/translations/harbour-fernschreiber-en.ts +++ b/translations/harbour-fernschreiber-en.ts @@ -823,6 +823,30 @@ myself have removed %1 from the chat + + scored %Ln points + myself + + scored %Ln point + scored %Ln points + + + + scored %Ln points + + scored %Ln point + scored %Ln points + + + + sent a game + myself + sent a game + + + sent a game + sent a game + ImagePage @@ -973,6 +997,21 @@ You You + + scored %Ln points in %2 + myself + + scored %Ln point in %2 + scored %Ln points in %2 + + + + scored %Ln points in %2 + + scored %Ln point in %2 + scored %Ln points in %2 + + MessageOverlayFlickable @@ -985,6 +1024,14 @@ This message was forwarded. Original author: %1 + + MessageViaLabel + + via %1 + message posted via bot user + via %1 + + NewChatPage @@ -1445,6 +1492,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + Privacy + Privacy + + + Allow sending Location to inline bots + Allow sending Location to inline bots + + + Some inline bots request location data when using them + Some inline bots request location data when using them + StickerPicker @@ -1912,5 +1971,22 @@ myself have removed %1 from the chat + + scored %Ln points + myself + + scored %Ln point + scored %Ln points + + + + sent a game + myself + sent a game + + + sent a game + sent a game + diff --git a/translations/harbour-fernschreiber-es.ts b/translations/harbour-fernschreiber-es.ts index 7391c64..ab34bb6 100644 --- a/translations/harbour-fernschreiber-es.ts +++ b/translations/harbour-fernschreiber-es.ts @@ -813,6 +813,28 @@ myself ha quitado %1 de charla + + scored %Ln points + myself + + + + + + scored %Ln points + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -963,6 +985,19 @@ You Usted + + scored %Ln points in %2 + myself + + + + + + scored %Ln points in %2 + + + + MessageOverlayFlickable @@ -975,6 +1010,14 @@ Este mensaje fue reenviado. Autor original: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1424,6 +1467,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them + + StickerPicker @@ -1891,5 +1946,21 @@ myself ha añadido %1 de la charla + + scored %Ln points + myself + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts index 1be7379..afc69ed 100644 --- a/translations/harbour-fernschreiber-fi.ts +++ b/translations/harbour-fernschreiber-fi.ts @@ -824,6 +824,30 @@ myself poistit käyttäjän %1 keskustelusta + + scored %Ln points + myself + + + + + + + scored %Ln points + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -974,6 +998,21 @@ You Sinä + + scored %Ln points in %2 + myself + + + + + + + scored %Ln points in %2 + + + + + MessageOverlayFlickable @@ -986,6 +1025,14 @@ Välitetty viesti. Alkuperäinen lähettäjä: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1444,6 +1491,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1913,5 +1972,22 @@ myself poistit käyttäjän %1 keskustelusta + + scored %Ln points + myself + + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-hu.ts b/translations/harbour-fernschreiber-hu.ts index ea3bdcf..16941a4 100644 --- a/translations/harbour-fernschreiber-hu.ts +++ b/translations/harbour-fernschreiber-hu.ts @@ -813,6 +813,28 @@ myself + + scored %Ln points + myself + + + + + + scored %Ln points + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -963,6 +985,19 @@ You Te + + scored %Ln points in %2 + myself + + + + + + scored %Ln points in %2 + + + + MessageOverlayFlickable @@ -975,6 +1010,14 @@ + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1422,6 +1465,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1891,5 +1946,21 @@ myself + + scored %Ln points + myself + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-it.ts b/translations/harbour-fernschreiber-it.ts index 4585d13..404fd0b 100644 --- a/translations/harbour-fernschreiber-it.ts +++ b/translations/harbour-fernschreiber-it.ts @@ -823,6 +823,30 @@ myself hai rimosso %1 dalla chat + + scored %Ln points + myself + + + + + + + scored %Ln points + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -973,6 +997,21 @@ You Tu + + scored %Ln points in %2 + myself + + + + + + + scored %Ln points in %2 + + + + + MessageOverlayFlickable @@ -985,6 +1024,14 @@ Questo è un messaggio inoltrato. Autore originale: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1445,6 +1492,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them + + StickerPicker @@ -1912,5 +1971,22 @@ myself hai rimosso %1 dalla chat + + scored %Ln points + myself + + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-pl.ts b/translations/harbour-fernschreiber-pl.ts index 17d3b8e..7b7fda5 100644 --- a/translations/harbour-fernschreiber-pl.ts +++ b/translations/harbour-fernschreiber-pl.ts @@ -833,6 +833,32 @@ myself usunąłem %1 z czatu + + scored %Ln points + myself + + + + + + + + scored %Ln points + + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -983,6 +1009,23 @@ You Ty + + scored %Ln points in %2 + myself + + + + + + + + scored %Ln points in %2 + + + + + + MessageOverlayFlickable @@ -995,6 +1038,14 @@ Ta wiadomość została przekazana. Oryginalny autor: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1466,6 +1517,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them + + StickerPicker @@ -1933,5 +1996,23 @@ myself usunąłem %1 z czatu + + scored %Ln points + myself + + + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-ru.ts b/translations/harbour-fernschreiber-ru.ts index 2dd92ea..8a903e4 100644 --- a/translations/harbour-fernschreiber-ru.ts +++ b/translations/harbour-fernschreiber-ru.ts @@ -833,6 +833,32 @@ myself %1 удалены из чата + + scored %Ln points + myself + + + + + + + + scored %Ln points + + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -983,6 +1009,23 @@ You Вы + + scored %Ln points in %2 + myself + + + + + + + + scored %Ln points in %2 + + + + + + MessageOverlayFlickable @@ -995,6 +1038,14 @@ Это сообщение было переадресовано. Первоначальный автор: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1464,6 +1515,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1933,5 +1996,23 @@ myself %1 удалены из чата + + scored %Ln points + myself + + + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-sv.ts b/translations/harbour-fernschreiber-sv.ts index ad35d9e..6d7cd90 100644 --- a/translations/harbour-fernschreiber-sv.ts +++ b/translations/harbour-fernschreiber-sv.ts @@ -823,6 +823,30 @@ myself har tagit bort %1 från chatten + + scored %Ln points + myself + + + + + + + scored %Ln points + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -973,6 +997,21 @@ You Du + + scored %Ln points in %2 + myself + + + + + + + scored %Ln points in %2 + + + + + MessageOverlayFlickable @@ -985,6 +1024,14 @@ Detta meddelande är vidarebefordrat. Ursprunglig avsändare: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1443,6 +1490,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1912,5 +1971,22 @@ myself har tagit bort %1 från chatten + + scored %Ln points + myself + + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber-zh_CN.ts b/translations/harbour-fernschreiber-zh_CN.ts index c74a8a5..b1bf035 100644 --- a/translations/harbour-fernschreiber-zh_CN.ts +++ b/translations/harbour-fernschreiber-zh_CN.ts @@ -814,6 +814,28 @@ myself 已从此对话移除 %1 + + scored %Ln points + myself + + + + + + scored %Ln points + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -964,6 +986,19 @@ You + + scored %Ln points in %2 + myself + + + + + + scored %Ln points in %2 + + + + MessageOverlayFlickable @@ -976,6 +1011,14 @@ 此消息为转发消息,原作者: %1 + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1423,6 +1466,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1892,5 +1947,21 @@ myself 已从此对话移除 %1 + + scored %Ln points + myself + + + + + + sent a game + myself + + + + sent a game + + diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts index acf72ba..b723da0 100644 --- a/translations/harbour-fernschreiber.ts +++ b/translations/harbour-fernschreiber.ts @@ -823,6 +823,30 @@ myself + + scored %Ln points + myself + + + + + + + scored %Ln points + + + + + + + sent a game + myself + + + + sent a game + + ImagePage @@ -973,6 +997,21 @@ You You + + scored %Ln points in %2 + myself + + + + + + + scored %Ln points in %2 + + + + + MessageOverlayFlickable @@ -985,6 +1024,14 @@ + + MessageViaLabel + + via %1 + message posted via bot user + + + NewChatPage @@ -1443,6 +1490,18 @@ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect. + + + + Privacy + + + + Allow sending Location to inline bots + + + + Some inline bots request location data when using them @@ -1912,5 +1971,22 @@ myself + + scored %Ln points + myself + + + + + + + sent a game + myself + + + + sent a game + +