diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro
index 5bc9469..6d639c3 100644
--- a/harbour-fernschreiber.pro
+++ b/harbour-fernschreiber.pro
@@ -39,6 +39,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/ImagePreview.qml \
qml/components/InReplyToRow.qml \
qml/components/LocationPreview.qml \
+ qml/components/PollPreview.qml \
qml/components/StickerPicker.qml \
qml/components/PhotoTextsListItem.qml \
qml/components/WebPagePreview.qml \
@@ -60,6 +61,8 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/pages/InitializationPage.qml \
qml/pages/OverviewPage.qml \
qml/pages/AboutPage.qml \
+ qml/pages/PollCreationPage.qml \
+ qml/pages/PollResultsPage.qml \
qml/pages/SettingsPage.qml \
qml/pages/VideoPage.qml \
rpm/harbour-fernschreiber.changes.in \
diff --git a/qml/components/PollPreview.qml b/qml/components/PollPreview.qml
new file mode 100644
index 0000000..11aa8cc
--- /dev/null
+++ b/qml/components/PollPreview.qml
@@ -0,0 +1,293 @@
+/*
+ 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.0
+import Sailfish.Silica 1.0
+import QtGraphicalEffects 1.0
+
+import "../js/functions.js" as Functions
+import "../js/twemoji.js" as Emoji
+
+Item {
+ id: pollMessageComponent
+
+ property string chatId
+ property var message:({})
+ property bool isOwnMessage
+
+ property string messageId: message.id
+ property bool canEdit: message.can_be_edited
+ property var pollData: message.content.poll
+ property var chosenPollData:({})
+ property var chosenIndexes: []
+ property bool hasAnswered: {
+ return pollData.options.filter(function(option){
+ return option.is_chosen
+ }).length > 0;
+ }
+ property bool canAnswer: !hasAnswered && !pollData.is_closed
+ property bool isQuiz: pollData.type['@type'] === "pollTypeQuiz"
+ property Item messageItem
+ height: pollColumn.height
+ opacity: 0
+ Behavior on opacity { FadeAnimator {} }
+ function handleChoose(index) {
+ if(!pollData.type.allow_multiple_answers) {
+ chosenIndexes = [index];
+ sendResponse();
+ return;
+ }
+ var indexes = chosenIndexes;
+ var found = indexes.indexOf(index);
+ if(found > -1) { // uncheck
+ indexes.splice(found, 1);
+ } else {
+ indexes.push(index)
+ }
+ chosenIndexes = indexes;
+ }
+ function resetChosen() {
+ chosenIndexes = [];
+ sendResponse();
+ }
+ function sendResponse() {
+ tdLibWrapper.setPollAnswer(chatId, messageId, chosenIndexes);
+ }
+
+ Column {
+ id: pollColumn
+ width: parent.width
+ spacing: Theme.paddingSmall
+ Label {
+ font.pixelSize: Theme.fontSizeSmall
+ width: parent.width
+ visible: text !== ""
+ text: Emoji.emojify(pollData.question, Theme.fontSizeSmall)
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ color: pollMessageComponent.isOwnMessage ? Theme.highlightColor : Theme.primaryColor
+ }
+
+ Label {
+ font.pixelSize: Theme.fontSizeTiny
+ width: parent.width
+ visible: text !== ""
+ text: pollData.is_closed ? qsTr("Final Result:") : (pollData.type.allow_multiple_answers ? qsTr("Multiple Answers are allowed.") : "")
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ color: pollMessageComponent.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
+ }
+
+ Item {
+ visible: !pollMessageComponent.canAnswer
+ width: parent.width
+ height: Theme.paddingSmall
+ }
+
+ Component {
+ id: canAnswerDelegate
+ TextSwitch {
+ id: optionDelegate
+ width: pollMessageComponent.width
+ automaticCheck: false
+ // emojify does not work here :/
+ text: modelData.text
+ checked: pollMessageComponent.chosenIndexes.indexOf(index) > -1
+ onClicked: {
+ pollMessageComponent.handleChoose(index);
+ }
+ }
+ }
+ Component {
+ id: resultDelegate
+ Item {
+ id: optionDelegate
+ width: pollMessageComponent.width
+ height: displayOptionLabel.height + displayOptionStatistics.height
+
+ Rectangle {
+ id: displayOptionChosenMarker
+ height: parent.height
+ width: Theme.horizontalPageMargin/2
+ color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
+ visible: modelData.is_chosen
+ x: -width
+ }
+ OpacityRampEffect {
+ sourceItem: displayOptionChosenMarker
+ direction: OpacityRamp.LeftToRight
+ }
+ Column {
+ id: iconsColumn
+ width: pollMessageComponent.isQuiz ?Theme.iconSizeSmall + Theme.paddingMedium : Theme.paddingMedium
+
+ anchors {
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ }
+
+ Icon {
+ highlighted: pollMessageComponent.isOwnMessage
+ property bool isRight: pollMessageComponent.isQuiz && pollData.type.correct_option_id === index
+ source: "image://theme/icon-s-accept"
+ visible: isRight
+ }
+ }
+
+ Label {
+ id: displayOptionLabel
+ text: Emoji.emojify(modelData.text, Theme.fontSizeMedium)
+
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ anchors {
+ left: iconsColumn.right
+ top: parent.top
+ right: parent.right
+ }
+ color: pollMessageComponent.isOwnMessage ? Theme.highlightColor : Theme.primaryColor
+ }
+ Item {
+ id: displayOptionStatistics
+ height: optionVoterPercentage.height + optionVoterPercentageBar.height
+ anchors {
+ top: displayOptionLabel.bottom
+ left: iconsColumn.right
+ right: parent.right
+ }
+
+ Label {
+ id: optionVoterPercentage
+ font.pixelSize: Theme.fontSizeTiny
+ text: qsTr("%L1\%", "% of votes for option").arg(modelData.vote_percentage)
+ horizontalAlignment: Text.AlignRight
+ anchors {
+ right: parent.right
+ left: parent.horizontalCenter
+ leftMargin: Theme.paddingSmall
+ }
+ color: pollMessageComponent.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
+ }
+ Rectangle {
+ id: optionVoterPercentageBar
+ height: Theme.paddingSmall
+ width: parent.width
+
+ color: Theme.rgba(pollMessageComponent.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor, 0.3)
+ radius: height/2
+ anchors {
+ left: parent.left
+ bottom: parent.bottom
+ }
+
+ Rectangle {
+ height: parent.height
+ color: pollMessageComponent.isOwnMessage ? Theme.highlightColor : Theme.primaryColor
+ radius: height/2
+ width: parent.width * modelData.vote_percentage * 0.01
+ }
+ }
+ }
+ }
+ }
+
+ Repeater {
+ model: pollData.options
+ delegate: pollMessageComponent.canAnswer ? canAnswerDelegate : resultDelegate
+ }
+
+ Row {
+ layoutDirection: Qt.RightToLeft
+ width: parent.width
+ spacing: Theme.paddingMedium
+ Behavior on height { NumberAnimation {}}
+
+
+ Label {
+ id: totalVoterCount
+ font.pixelSize: Theme.fontSizeTiny
+ anchors.verticalCenter: parent.verticalCenter
+ text: qsTr("%L1 vote(s) total", "number of total votes", pollData.total_voter_count).arg(pollData.total_voter_count)
+ width: contentWidth
+ height: contentHeight
+ horizontalAlignment: Text.AlignRight
+ color: pollMessageComponent.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
+ }
+
+ Row {
+ spacing: Theme.paddingSmall
+ width: parent.width - totalVoterCount.width - parent.spacing
+ IconButton {
+ visible: !pollData.is_closed && pollMessageComponent.chosenIndexes.length > 0 && pollData.type.allow_multiple_answers && !pollMessageComponent.hasAnswered
+ opacity: visible ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation {}}
+ icon.source: "image://theme/icon-m-send"
+ onClicked: {
+ pollMessageComponent.sendResponse()
+ }
+ }
+
+
+ IconButton {
+ visible: !pollMessageComponent.canAnswer && !pollData.is_anonymous && pollData.total_voter_count > 0
+ icon.source: "image://theme/icon-m-media-artists"
+ onClicked: {
+ pageStack.push(Qt.resolvedUrl("../pages/PollResultsPage.qml"), { chatId:chatId, message:pollMessageComponent.message});
+ }
+ Icon {
+ opacity: 0.8
+ source: "image://theme/icon-s-maybe"
+ anchors {
+ right: parent.right
+ top: parent.top
+ }
+ }
+ }
+ }
+ }
+
+ }
+ Component {
+ id: closePollMenuItemComponent
+ MenuItem {
+ text: qsTr("Close Poll")
+ onClicked: {
+ tdLibWrapper.stopPoll(pollMessageComponent.chatId, pollMessageComponent.messageId);
+ }
+ }
+ }
+ Component {
+ id: resetAnswerMenuItemComponent
+ MenuItem {
+ text: qsTr("Reset Answer")
+ onClicked: {
+ pollMessageComponent.resetChosen()
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ opacity = 1;
+ if(messageItem && messageItem.menu ) { // workaround to add menu entries
+ if(!pollData.is_closed && pollMessageComponent.canEdit) {
+ closePollMenuItemComponent.createObject(messageItem.menu._contentColumn);
+ }
+ if(!pollData.is_closed && !pollMessageComponent.isQuiz && pollMessageComponent.hasAnswered) {
+ resetAnswerMenuItemComponent.createObject(messageItem.menu._contentColumn);
+ }
+ }
+ }
+}
diff --git a/qml/js/functions.js b/qml/js/functions.js
index 771d836..b62ffb9 100644
--- a/qml/js/functions.js
+++ b/qml/js/functions.js
@@ -97,6 +97,18 @@ function getMessageText(message, simple, myself) {
if (message.content['@type'] === 'messageChatChangeTitle') {
return myself ? qsTr("changed the chat title to %1", "myself").arg(message.content.title) : qsTr("changed the chat title to %1").arg(message.content.title);
}
+ if (message.content['@type'] === 'messagePoll') {
+ if(message.content.poll.type['@type'] === "pollTypeQuiz") {
+ if(message.content.poll.is_anonymous) {
+ return myself ? qsTr("sent an anonymous quiz", "myself") : qsTr("sent an anonymous quiz");
+ }
+ return myself ? qsTr("sent a quiz", "myself") : qsTr("sent a quiz");
+ }
+ if(message.content.poll.is_anonymous) {
+ return myself ? qsTr("sent an anonymous poll", "myself") : qsTr("sent an anonymous poll");
+ }
+ return myself ? qsTr("sent a poll", "myself") : qsTr("sent a poll");
+ }
return qsTr("Unsupported message: %1").arg(message.content['@type'].substring(7));
}
diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml
index 6350048..56c39da 100644
--- a/qml/pages/ChatPage.qml
+++ b/qml/pages/ChatPage.qml
@@ -220,7 +220,6 @@ Page {
tdLibWrapper.openChat(chatInformation.id);
break;
case PageStatus.Active:
- console.log("CHAT opendirectly?", chatPage.isInitialized)
if (!chatPage.isInitialized) {
chatModel.initialize(chatInformation);
chatPage.isInitialized = true;
@@ -537,6 +536,7 @@ Page {
property bool containsAudio: (( display.content['@type'] === "messageVoiceNote" ) || ( display.content['@type'] === "messageAudio" ));
property bool containsDocument: ( display.content['@type'] === "messageDocument" )
property bool containsLocation: ( display.content['@type'] === "messageLocation" || ( display.content['@type'] === "messageVenue" ))
+ property bool containsPoll: display.content['@type'] === "messagePoll"
menu: ContextMenu {
MenuItem {
@@ -594,6 +594,7 @@ Page {
audioPreviewLoader.active = messageListItem.containsAudio;
documentPreviewLoader.active = messageListItem.containsDocument;
locationPreviewLoader.active = messageListItem.containsLocation;
+ pollPreviewLoader.active = messageListItem.containsPoll;
forwardedInformationLoader.active = messageListItem.isForwarded;
}
}
@@ -921,6 +922,25 @@ Page {
}
}
+ Loader {
+ id: pollPreviewLoader
+ active: false
+ asynchronous: true
+ width: parent.width
+// height: messageListItem.containsLocation ? (item ? item.height : (parent.width * 2 / 3)) : 0
+ sourceComponent: Component {
+ id: pollPreviewComponent
+ PollPreview {
+ id: messageLocationPreview
+ width: parent.width
+ chatId: chatInformation.id
+ isOwnMessage: messageListItem.isOwnMessage
+ message: display
+ messageItem: messageListItem
+ }
+ }
+ }
+
Timer {
id: messageDateUpdater
interval: 60000
@@ -1160,6 +1180,14 @@ Page {
}
}
}
+ IconButton {
+ visible: !chatPage.isPrivateChat
+ icon.source: "image://theme/icon-m-question"
+ onClicked: {
+ pageStack.push(Qt.resolvedUrl("../pages/PollCreationPage.qml"), { "chatId" : chatInformation.id, groupName: chatInformation.title});
+ attachmentOptionsRow.visible = false;
+ }
+ }
}
Row {
diff --git a/qml/pages/OverviewPage.qml b/qml/pages/OverviewPage.qml
index f3533f5..cda297f 100644
--- a/qml/pages/OverviewPage.qml
+++ b/qml/pages/OverviewPage.qml
@@ -154,7 +154,6 @@ Page {
}
onChatReceived: {
if(chat["@extra"] === "openDirectly") {
- console.log("ON CHAT RECEIVED", JSON.stringify(chat, null, 2));
if (status !== PageStatus.Active) {
pageStack.pop(pageStack.find( function(page){ return(page._depth === 0)} ), PageStackAction.Immediate);
}
diff --git a/qml/pages/PollCreationPage.qml b/qml/pages/PollCreationPage.qml
new file mode 100644
index 0000000..4b00a5e
--- /dev/null
+++ b/qml/pages/PollCreationPage.qml
@@ -0,0 +1,353 @@
+/*
+ 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.0
+import "../components"
+import "../js/functions.js" as Functions
+import "../js/twemoji.js" as Emoji
+
+Dialog {
+ id: pollCreationPage
+ allowedOrientations: Orientation.All
+ property string groupName
+ // poll request data start
+ property string chatId
+ property alias pollQuestion: questionTextArea.text
+ property ListModel options: ListModel {
+ ListElement {
+ text: ""
+ }
+ }
+ property alias anonymous: anonymousSwitch.checked
+ property int correctOption: -1
+ property alias quiz: quizSwitch.checked
+ property alias multiple: multipleSwitch.checked
+ property string replyToMessageId: "0"
+ // poll request data end
+
+ canAccept: validationErrors.length === 0
+ onDone: {
+ }
+ onAcceptPendingChanged: {
+ if(acceptPending) {
+
+ validate();
+
+ if(validationErrors.length > 0) {
+ validationErrorsVisible = true;
+ contentFlickable.scrollToTop();
+ }
+ }
+ }
+
+ property var validationErrorsVisible: false
+ property var validationErrors:[""]
+
+ function validate() {
+ var errors = [];
+ if(pollQuestion.length === 0) {
+ errors.push(qsTr("You have to enter a question."));
+ } else if(pollQuestion.length > 255) {
+ errors.push(qsTr("The question has to be shorter than 256 characters."));
+ }
+
+ if(options.count < 2 || options.count > 10) {
+ errors.push(qsTr("A poll requires 2-10 answers."));
+ } else {
+ for(var i = 0; i < options.count; i += 1) {
+ var len = options.get(i).text.length
+ if(len < 1 || len > 100) {
+ errors.push(qsTr("All answers have to contain 1-100 characters."));
+ break;
+ }
+ }
+ }
+ if(quiz && (correctOption < 0 || correctOption > options.count - 1)) {
+ errors.push(qsTr("To send a quiz, you have to specify the right answer."));
+ }
+ if(errors.length === 0) {
+ validationErrorsVisible = false;
+ }
+
+ validationErrors = errors;
+ }
+ function createNewOption() {
+ if(options.count < 10) {
+ pollCreationPage.options.append({text:""});
+ focusLastOptionTimer.start();
+ }
+ }
+
+ signal focusOption(int focusIndex)
+ DialogHeader {
+ id: header
+ dialog: pollCreationPage
+ title: qsTr("Create a Poll", "Dialog Header")
+ }
+ Label {
+ id: subHeaderLabel
+ anchors {
+ verticalCenter: header.bottom
+ left: parent.left
+ right: parent.right
+ leftMargin: Theme.horizontalPageMargin
+ rightMargin: Theme.horizontalPageMargin
+ }
+
+ color: Theme.secondaryHighlightColor
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ text: qsTr("in %1", "After dialog header… Create a Poll in [group name]").arg(Emoji.emojify(pollCreationPage.groupName, font.pixelSize))
+ font.pixelSize: Theme.fontSizeSmall
+ }
+
+ SilicaFlickable {
+ id: contentFlickable
+ clip: true
+ anchors {
+ top: subHeaderLabel.bottom
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ contentHeight: contentColumn.height
+
+ Column {
+ id: contentColumn
+ width: parent.width
+ topPadding: Theme.paddingLarge
+ bottomPadding: Theme.paddingLarge
+
+ Item {
+ id: errorItem
+ width: parent.width - Theme.horizontalPageMargin * 2
+ x: Theme.horizontalPageMargin
+ property bool shown: pollCreationPage.validationErrorsVisible && pollCreationPage.validationErrors.length > 0
+ property int visibleHeight: errorContentColumn.height
+ height: pollCreationPage.validationErrorsVisible ? visibleHeight : 0
+ clip: true;
+ opacity: pollCreationPage.validationErrorsVisible ? 1.0 : 0.0
+ Behavior on opacity { PropertyAnimation {duration: 500; easing.type: Easing.InOutCubic}}
+ Behavior on height { PropertyAnimation {duration: 200; easing.type: Easing.InOutCubic}}
+ Rectangle {
+ color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
+ anchors.fill: parent
+ radius: Theme.paddingLarge
+ IconButton {
+ icon.source: "image://theme/icon-m-close"
+ anchors {
+ right: parent.right
+ top: parent.top
+ }
+ onClicked: {
+ pollCreationPage.validationErrorsVisible = false
+ }
+ }
+ }
+
+
+ Column {
+ id: errorContentColumn
+ width: parent.width - Theme.paddingLarge * 2 - Theme.itemSizeSmall
+ spacing: Theme.paddingMedium
+ padding: Theme.paddingLarge
+ Repeater {
+ model: pollCreationPage.validationErrors
+ delegate: Label {
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.highlightColor
+ width: errorContentColumn.width
+ text: modelData
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ leftPadding: Theme.iconSizeSmall + Theme.paddingSmall
+ Icon {
+ highlighted: true
+ source: "image://theme/icon-s-high-importance"
+ y: Theme.paddingSmall / 2
+ }
+ }
+ }
+
+ }
+ }
+
+ TextArea {
+ id: questionTextArea
+ width: parent.width
+ placeholderText: qsTr("Enter your question here")
+ property int charactersLeft: 255 - text.length
+ color: charactersLeft < 0 ? Theme.errorColor : Theme.highlightColor
+ label: qsTr("Question (%n1 characters left)", "", charactersLeft).arg(charactersLeft)
+ wrapMode: TextEdit.Wrap
+ onFocusChanged: {
+ validate();
+ }
+ }
+ SectionHeader {
+ topPadding: 0
+ text: qsTr("Answers", "Section header")
+ }
+
+ Column {
+ id: optionsListView
+ width: parent.width - Theme.horizontalPageMargin * 2
+ x: Theme.horizontalPageMargin
+ add: Transition {
+ NumberAnimation { properties: "opacity"; from: 0; to: 1; duration: 200; easing.type: Easing.InOutCubic }
+ NumberAnimation { properties: "height"; from: 0; to: ViewTransition.item.childrenRect.height; duration: 200; easing.type: Easing.InOutCubic }
+ }
+ move: Transition {
+ NumberAnimation { properties: "y"; duration: 200; easing.type: Easing.InOutCubic }
+ }
+ Behavior on height { PropertyAnimation {duration: 200; easing.type: Easing.InOutCubic}}
+ Repeater {
+ model: pollCreationPage.options
+
+ delegate: Row {
+ width: parent.width
+ BackgroundItem {
+ id: answerCorrectBackgroundItem
+ width: enabled ? Theme.itemSizeSmall : 0
+ contentItem.radius: height/2
+ height: Theme.itemSizeSmall
+ property bool checked: pollCreationPage.correctOption === index
+ enabled: pollCreationPage.quiz
+ opacity: enabled ? (checked ? 1.0 : 0.5) : 0.0
+ Behavior on opacity { PropertyAnimation {duration: 500; easing.type: Easing.InOutCubic}}
+ Behavior on width { PropertyAnimation {duration: 500; easing.type: Easing.InOutCubic}}
+ Icon {
+ source: "image://theme/icon-m-accept"
+ anchors.centerIn: parent
+ }
+ onClicked: {
+ pollCreationPage.correctOption = index
+ validate();
+ }
+
+ }
+
+ TextField {
+ id: answerTextArea
+ textMargin: Theme.paddingSmall
+ width: answerCorrectBackgroundItem.enabled ? parent.width - Theme.itemSizeSmall * 2 : parent.width - Theme.itemSizeSmall
+ Behavior on width { PropertyAnimation {duration: 500; easing.type: Easing.InOutCubic}}
+ text: model.text
+ onTextChanged: {
+ pollCreationPage.options.setProperty(index, "text", text)
+ pollCreationPage.validate()
+ }
+ placeholderText: qsTr("Enter an answer here")
+ property int charactersLeft: 100 - text.length
+ color: charactersLeft < 0 ? Theme.errorColor : Theme.highlightColor
+ label: qsTr("Answer (%n1 characters left)", "", charactersLeft).arg(charactersLeft)
+ property bool hasNextOption: index < pollCreationPage.options.count - 1
+ EnterKey.onClicked: {
+ if(hasNextOption) {
+ pollCreationPage.focusOption(index + 1);
+ } else if(pollCreationPage.options.count < 10) {
+ pollCreationPage.createNewOption();
+ } else {
+ focus = false;
+ }
+ }
+ EnterKey.iconSource: hasNextOption ? "image://theme/icon-m-enter-next" : (pollCreationPage.options.count < 10 ? "image://theme/icon-m-add" : "image://theme/icon-m-enter-close")
+
+ onFocusChanged: {
+ validate();
+ }
+ }
+ Connections {
+ target: pollCreationPage
+ onFocusOption: {
+ if(focusIndex === index) answerTextArea.forceActiveFocus()
+ }
+ }
+
+ IconButton {
+ icon.source: "image://theme/icon-m-remove"
+ onClicked: {
+ pollCreationPage.options.remove(index)
+
+ validate();
+ }
+ }
+ }
+ }
+ }
+ ButtonLayout {
+ Button {
+ enabled: pollCreationPage.options.count < 10
+ text: qsTr("Add an answer")
+ onClicked: {
+ pollCreationPage.createNewOption();
+ validate();
+ }
+ }
+ }
+ Timer {
+ id: focusLastOptionTimer
+ interval: 20
+ onTriggered: {
+ pollCreationPage.focusOption(pollCreationPage.options.count - 1);
+ }
+ }
+
+ SectionHeader {
+ text: qsTr("Poll Options", "Section header")
+ }
+ TextSwitch {
+ id: anonymousSwitch
+ text: qsTr("Anonymous answers")
+ }
+ TextSwitch {
+ id: multipleSwitch
+ text: qsTr("Multiple answers allowed")
+ onCheckedChanged: {
+ if(checked) {
+ quizSwitch.checked = false
+ }
+ }
+ }
+ TextSwitch {
+ id: quizSwitch
+ text: qsTr("Quiz Mode")
+ onCheckedChanged: {
+ if(checked) {
+ multipleSwitch.checked = false
+ }
+ validate();
+ }
+ description: qsTr("Quizzes have one correct answer. Participants can't revoke their responses.")
+ }
+ }
+ }
+
+ onAccepted: {
+ var optionsArr = [];
+ for(var i = 0; i < options.count; i += 1) {
+ optionsArr.push(options.get(i).text);
+ }
+
+ tdLibWrapper.sendPollMessage(chatId, pollQuestion, optionsArr, anonymous, quiz ? correctOption : -1, multiple, "0");
+
+ }
+
+
+}
diff --git a/qml/pages/PollResultsPage.qml b/qml/pages/PollResultsPage.qml
new file mode 100644
index 0000000..764754c
--- /dev/null
+++ b/qml/pages/PollResultsPage.qml
@@ -0,0 +1,328 @@
+/*
+ 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.0
+import "../components"
+import "../js/functions.js" as Functions
+import "../js/twemoji.js" as Emoji
+
+Page {
+ id: pollResultsPage
+ allowedOrientations: Orientation.All
+ property string chatId;
+ property var message: ({});
+
+ property string messageId: message.id;
+
+ property var pollData: message.content.poll
+
+ property var userInformation: tdLibWrapper.getUserInformation(message.sender_user_id)
+
+ property bool isQuiz: pollData.type['@type'] === "pollTypeQuiz"
+
+ property bool hasAnswered: {
+ return pollData.options.filter(function(option){
+ return option.is_chosen
+ }).length > 0;
+ }
+
+ property bool canAnswer: !hasAnswered && !pollData.is_closed
+ onCanAnswerChanged: {
+ if(canAnswer) { // vote removed from another client?
+ pageStack.pop()
+ }
+ }
+
+ SilicaFlickable {
+ anchors.fill: parent
+ contentHeight: pageHeader.height + contentColumn.height
+
+ PageHeader {
+ id: pageHeader
+ title: pollResultsPage.isQuiz ? qsTr("Quiz Results") : qsTr("Poll Results")
+ description: qsTr("%L1 vote(s) total", "number of total votes", pollData.total_voter_count).arg(pollData.total_voter_count)
+ leftMargin: headerPictureThumbnail.width + Theme.paddingLarge + Theme.horizontalPageMargin
+ ProfileThumbnail {
+ id: headerPictureThumbnail
+ photoData: (typeof pollResultsPage.userInformation.profile_photo !== "undefined") ? pollResultsPage.userInformation.profile_photo.small : ""
+ replacementStringHint: Emoji.emojify(Functions.getUserName(pollResultsPage.userInformation), font.pixelSize)
+ width: visible ? Theme.itemSizeSmall : 0
+ height: visible ? Theme.itemSizeSmall : 0
+ anchors {
+ verticalCenter: pageHeader.verticalCenter
+ left: parent.left
+ leftMargin: Theme.horizontalPageMargin
+ }
+ }
+ }
+ Column {
+ id: contentColumn
+ anchors {
+ left: parent.left
+ leftMargin: Theme.horizontalPageMargin
+ right: parent.right
+ rightMargin: Theme.horizontalPageMargin
+ top: pageHeader.bottom
+ }
+ SectionHeader {
+ x: 0
+ text: qsTr("Question", "section header")
+ }
+// Label {
+// width: parent.width
+// font.pixelSize: Theme.fontSizeTiny
+// wrapMode: Text.Wrap
+// color: Theme.secondaryHighlightColor
+// text: JSON.stringify(pollData, null, 2)
+// }
+// Label {
+// width: parent.width
+// font.pixelSize: Theme.fontSizeTiny
+// wrapMode: Text.Wrap
+// color: Theme.secondaryHighlightColor
+// text: JSON.stringify(userInformation, null, 2)
+// }
+ Label {
+ width: parent.width
+ wrapMode: Text.Wrap
+ color: Theme.secondaryHighlightColor
+ text: Emoji.emojify(pollData.question, font.pixelSize)
+ }
+
+ Column {
+ id: resultsColumn
+ width: parent.width
+ topPadding: Theme.paddingLarge
+ bottomPadding: Theme.paddingLarge
+
+ SectionHeader {
+ x: 0
+ text: qsTr("Results", "section header")
+ }
+ Repeater {
+ model: pollData.options
+ delegate: Item {
+ id: optionDelegate
+ width: resultsColumn.width
+ height: displayOptionLabel.height + displayOptionStatistics.height + displayOptionUsers.height + Theme.paddingLarge
+ property ListModel users: ListModel {}
+ property string usersResponseIdentifierString: "pollResults."+pollResultsPage.chatId+"."+pollResultsPage.messageId+"."+index
+ function loadUsers() {
+ if(users.count < modelData.voter_count) {
+ tdLibWrapper.getPollVoters(pollResultsPage.chatId, pollResultsPage.messageId, index, 50, users.length, usersResponseIdentifierString)
+ }
+ }
+ Component.onCompleted: {
+// loadUsers()
+ loadUsersTimer.start()
+ }
+ Timer {
+ id: loadUsersTimer
+ interval: index * 80
+ onTriggered: {
+ optionDelegate.loadUsers();
+ }
+ }
+
+ Connections {
+ target: tdLibWrapper
+ onUsersReceived: {
+ if(extra === optionDelegate.usersResponseIdentifierString) {
+ for(var i = 0; i < userIds.length; i += 1) {
+ optionDelegate.users.append({id: userIds[i], user:tdLibWrapper.getUserInformation(userIds[i])});
+ console.log("APPEND USER", JSON.stringify({id: userIds[i], user:tdLibWrapper.getUserInformation(userIds[i])}));
+ }
+ loadUsersTimer.start();
+ }
+ }
+ }
+ Rectangle {
+ id: displayOptionChosenMarker
+ height: parent.height
+ width: Theme.horizontalPageMargin/2
+ color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
+ visible: modelData.is_chosen
+ x: -width
+ }
+ OpacityRampEffect {
+ sourceItem: displayOptionChosenMarker
+ direction: OpacityRamp.LeftToRight
+ }
+ Column {
+ id: iconsColumn
+ width: pollResultsPage.isQuiz ?Theme.iconSizeSmall + Theme.paddingMedium : Theme.paddingMedium
+ height: displayOptionLabel.height + displayOptionStatistics.height
+ anchors {
+ left: parent.left
+// verticalCenter: parent.verticalCenter
+ }
+
+ Icon {
+ highlighted: true
+ property bool isRight: pollResultsPage.isQuiz && pollData.type.correct_option_id === index
+ source: "image://theme/icon-s-accept"
+ visible: isRight
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Label {
+ id: displayOptionLabel
+ text: Emoji.emojify(modelData.text, Theme.fontSizeMedium)
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ anchors {
+ left: iconsColumn.right
+ top: parent.top
+ right: parent.right
+ }
+ color: Theme.highlightColor
+ }
+ Item {
+ id: displayOptionStatistics
+ height: optionVoterPercentage.height + optionVoterPercentageBar.height
+ anchors {
+ top: displayOptionLabel.bottom
+ left: iconsColumn.right
+ right: parent.right
+ }
+
+ Label {
+ id: optionVoterCount
+ font.pixelSize: Theme.fontSizeTiny
+ text: modelData.is_chosen ? qsTr("%L1 vote(s) including yours", "number of votes for option", modelData.voter_count).arg(modelData.voter_count) : qsTr("%L1 vote(s)", "number of votes for option", modelData.voter_count).arg(modelData.voter_count)
+ anchors {
+ left: parent.left
+ right: parent.horizontalCenter
+ rightMargin: Theme.paddingSmall
+ }
+ color: Theme.secondaryHighlightColor
+ }
+ Label {
+ id: optionVoterPercentage
+ font.pixelSize: Theme.fontSizeTiny
+ text: qsTr("%L1\%", "% of votes for option").arg(modelData.vote_percentage)
+ horizontalAlignment: Text.AlignRight
+ anchors {
+ right: parent.right
+ left: parent.horizontalCenter
+ leftMargin: Theme.paddingSmall
+ }
+ color: Theme.secondaryHighlightColor
+ }
+ Rectangle {
+ id: optionVoterPercentageBar
+ height: Theme.paddingSmall
+ width: parent.width
+
+ color: Theme.rgba(Theme.secondaryHighlightColor, 0.3)
+ radius: height/2
+ anchors {
+ left: parent.left
+ bottom: parent.bottom
+ }
+
+ Rectangle {
+ height: parent.height
+ color: Theme.highlightColor
+ radius: height/2
+ width: parent.width * modelData.vote_percentage * 0.01
+ }
+ }
+ }
+
+
+ // users voted for this:
+ Flow {
+ id: displayOptionUsers
+ anchors.top: displayOptionStatistics.bottom
+ width: parent.width
+ visible: optionDelegate.users.count > 0
+ topPadding: Theme.paddingLarge
+ spacing: Theme.paddingMedium
+ leftPadding: iconsColumn.width
+ property int itemHeight: Theme.itemSizeExtraSmall / 2
+ Item {
+ height: displayOptionUsers.itemHeight
+ width: chosenByUserText.width
+ Label {
+ id: chosenByUserText
+ font.pixelSize: Theme.fontSizeTiny
+ text: qsTr("Chosen by:", "This answer has been chosen by the following users")
+ width: contentWidth
+ anchors.centerIn: parent
+ color: Theme.secondaryHighlightColor
+ }
+ }
+ Repeater {
+ model: optionDelegate.users
+ delegate:
+ Item {
+ id: chosenByUserItem
+ width: chosenByUserPictureThumbnail.width + chosenByUserLabel.width + Theme.paddingSmall
+ height: displayOptionUsers.itemHeight
+
+ ProfileThumbnail {
+ id: chosenByUserPictureThumbnail
+ photoData: (typeof model.user.profile_photo !== "undefined") ? model.user.profile_photo.small : ""
+ replacementStringHint: chosenByUserLabel.text
+ width: visible ? parent.height : 0
+ height: width
+ anchors {
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Label {
+ id: chosenByUserLabel
+ font.pixelSize: Theme.fontSizeSmall
+ text: Emoji.emojify(Functions.getUserName(model.user), font.pixelSize)
+ width: contentWidth
+ height: contentHeight
+ color: Theme.highlightColor
+ anchors {
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+
+
+ }
+ }
+
+ }
+ }
+
+ }
+
+ }
+ }
+
+ Connections {
+ target: tdLibWrapper
+ onMessageContentUpdated: {
+ if(chatId === pollResultsPage.chatId && messageId === pollResultsPage.messageId) {
+ pollResultsPage.pollData = newContent.poll;
+ }
+ }
+ }
+
+}
diff --git a/src/fernschreiberutils.cpp b/src/fernschreiberutils.cpp
index b52122e..99c05e6 100644
--- a/src/fernschreiberutils.cpp
+++ b/src/fernschreiberutils.cpp
@@ -54,5 +54,11 @@ QString FernschreiberUtils::getMessageShortText(const QVariantMap &messageConten
if (contentType == "messageChatChangeTitle") {
return myself ? tr("changed the chat title", "myself") : tr("changed the chat title");
}
+ if (contentType == "messagePoll") {
+ if(messageContent.value("poll").toMap().value("type").toMap().value("@type").toString() == "pollTypeQuiz") {
+ return myself ? tr("sent a quiz", "myself") : tr("sent a quiz");
+ }
+ return myself ? tr("sent a poll", "myself") : tr("sent a poll");
+ }
return tr("Unsupported message: %1").arg(contentType.mid(7));
}
diff --git a/src/tdlibreceiver.cpp b/src/tdlibreceiver.cpp
index cb80fe1..134e023 100644
--- a/src/tdlibreceiver.cpp
+++ b/src/tdlibreceiver.cpp
@@ -116,6 +116,7 @@ TDLibReceiver::TDLibReceiver(void *tdLibClient, QObject *parent) : QThread(paren
handlers.insert("userProfilePhotos", &TDLibReceiver::processUserProfilePhotos);
handlers.insert("updateChatPermissions", &TDLibReceiver::processUpdateChatPermissions);
handlers.insert("updateChatTitle", &TDLibReceiver::processUpdateChatTitle);
+ handlers.insert("users", &TDLibReceiver::processUsers);
}
void TDLibReceiver::setActive(const bool &active)
@@ -494,3 +495,9 @@ void TDLibReceiver::processUpdateChatTitle(const QVariantMap &receivedInformatio
LOG("Received UpdateChatTitle");
emit chatTitleUpdated(receivedInformation.value("chat_id").toString(), receivedInformation.value("title").toString());
}
+
+void TDLibReceiver::processUsers(const QVariantMap &receivedInformation)
+{
+ LOG("Received Users");
+ emit usersReceived(receivedInformation.value(EXTRA).toString(), receivedInformation.value("user_ids").toList(), receivedInformation.value("total_count").toInt());
+}
diff --git a/src/tdlibreceiver.h b/src/tdlibreceiver.h
index a8eb27a..663ce87 100644
--- a/src/tdlibreceiver.h
+++ b/src/tdlibreceiver.h
@@ -80,6 +80,7 @@ signals:
void userProfilePhotos(const QString &extra, const QVariantList &photos, const int &totalPhotos);
void chatPermissionsUpdated(const QString &chatId, const QVariantMap &chatPermissions);
void chatTitleUpdated(const QString &chatId, const QString &title);
+ void usersReceived(const QString &extra, const QVariantList &userIds, const int &totalUsers);
private:
typedef void (TDLibReceiver::*Handler)(const QVariantMap &);
@@ -134,6 +135,7 @@ private:
void processUserProfilePhotos(const QVariantMap &receivedInformation);
void processUpdateChatPermissions(const QVariantMap &receivedInformation);
void processUpdateChatTitle(const QVariantMap &receivedInformation);
+ void processUsers(const QVariantMap &receivedInformation);
};
#endif // TDLIBRECEIVER_H
diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp
index c1d6ffc..eb3dec1 100644
--- a/src/tdlibwrapper.cpp
+++ b/src/tdlibwrapper.cpp
@@ -104,8 +104,8 @@ TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent)
connect(this->tdLibReceiver, SIGNAL(userProfilePhotos(QString, QVariantList, int)), this, SLOT(handleUserProfilePhotos(QString, QVariantList, int)));
connect(this->tdLibReceiver, SIGNAL(userProfilePhotos(QString, QVariantList, int)), this, SLOT(handleUserProfilePhotos(QString, QVariantList, int)));
connect(this->tdLibReceiver, SIGNAL(chatPermissionsUpdated(QString, QVariantMap)), this, SLOT(handleChatPermissionsUpdated(QString, QVariantMap)));
-
connect(this->tdLibReceiver, SIGNAL(chatTitleUpdated(QString, QString)), this, SLOT(handleChatTitleUpdated(QString, QString)));
+ connect(this->tdLibReceiver, SIGNAL(usersReceived(QString, QVariantList, int)), this, SLOT(handleUsersReceived(QString, QVariantList, int)));
connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList)));
@@ -384,6 +384,36 @@ void TDLibWrapper::sendStickerMessage(const QString &chatId, const QString &file
this->sendRequest(requestObject);
}
+void TDLibWrapper::sendPollMessage(const QString &chatId, const QString &question, const QVariantList &options, const bool &anonymous, const int &correctOption, const bool &multiple, const QString &replyToMessageId)
+{
+ LOG("Sending poll message" << chatId << question << replyToMessageId);
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "sendMessage");
+ requestObject.insert("chat_id", chatId);
+ if (replyToMessageId != "0") {
+ requestObject.insert("reply_to_message_id", replyToMessageId);
+ }
+ QVariantMap inputMessageContent;
+ inputMessageContent.insert(_TYPE, "inputMessagePoll");
+
+ QVariantMap pollType;
+ if(correctOption > -1) {
+ pollType.insert(_TYPE, "pollTypeQuiz");
+ pollType.insert("correct_option_id", correctOption);
+ } else {
+ pollType.insert(_TYPE, "pollTypeRegular");
+ pollType.insert("allow_multiple_answers", multiple);
+ }
+
+ inputMessageContent.insert("type", pollType);
+ inputMessageContent.insert("question", question);
+ inputMessageContent.insert("options", options);
+ inputMessageContent.insert("is_anonymous", anonymous);
+
+ requestObject.insert("input_message_content", inputMessageContent);
+ this->sendRequest(requestObject);
+}
+
void TDLibWrapper::getMessage(const QString &chatId, const QString &messageId)
{
LOG("Retrieving message" << chatId << messageId);
@@ -623,6 +653,41 @@ void TDLibWrapper::toggleSupergroupIsAllHistoryAvailable(const QString &groupId,
this->sendRequest(requestObject);
}
+void TDLibWrapper::setPollAnswer(const QString &chatId, const qlonglong &messageId, QVariantList optionIds)
+{
+ LOG("Setting Poll Answer");
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "setPollAnswer");
+ requestObject.insert("chat_id", chatId);
+ requestObject.insert("message_id", messageId);
+ requestObject.insert("option_ids", optionIds);
+ this->sendRequest(requestObject);
+}
+
+void TDLibWrapper::stopPoll(const QString &chatId, const qlonglong &messageId)
+{
+ LOG("Stopping Poll");
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "stopPoll");
+ requestObject.insert("chat_id", chatId);
+ requestObject.insert("message_id", messageId);
+ this->sendRequest(requestObject);
+}
+
+void TDLibWrapper::getPollVoters(const QString &chatId, const qlonglong &messageId, const int &optionId, const int &limit, const int &offset, const QString &extra)
+{
+ LOG("Retrieving Poll Voters");
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "getPollVoters");
+ requestObject.insert(_EXTRA, extra);
+ requestObject.insert("chat_id", chatId);
+ requestObject.insert("message_id", messageId);
+ requestObject.insert("option_id", optionId);
+ requestObject.insert("offset", offset);
+ requestObject.insert("limit", limit); //max 50
+ this->sendRequest(requestObject);
+}
+
void TDLibWrapper::searchEmoji(const QString &queryString)
{
LOG("Searching emoji" << queryString);
@@ -1049,16 +1114,21 @@ void TDLibWrapper::handleUserProfilePhotos(const QString &extra, const QVariantL
emit this->userProfilePhotosReceived(extra, photos, totalPhotos);
}
-void TDLibWrapper::handleChatPermissionsUpdated(const QString &chatId, const QVariantMap permissions)
+void TDLibWrapper::handleChatPermissionsUpdated(const QString &chatId, const QVariantMap &permissions)
{
emit this->chatPermissionsUpdated(chatId, permissions);
}
-void TDLibWrapper::handleChatTitleUpdated(const QString &chatId, const QString title)
+void TDLibWrapper::handleChatTitleUpdated(const QString &chatId, const QString &title)
{
emit this->chatTitleUpdated(chatId, title);
}
+void TDLibWrapper::handleUsersReceived(const QString &extra, const QVariantList &userIds, const int &totalUsers)
+{
+ emit this->usersReceived(extra, userIds, totalUsers);
+}
+
void TDLibWrapper::setInitialParameters()
{
LOG("Sending initial parameters to TD Lib");
diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h
index d440b04..5603ba3 100644
--- a/src/tdlibwrapper.h
+++ b/src/tdlibwrapper.h
@@ -121,6 +121,7 @@ public:
Q_INVOKABLE void sendVideoMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendDocumentMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendStickerMessage(const QString &chatId, const QString &fileId, const QString &replyToMessageId = "0");
+ Q_INVOKABLE void sendPollMessage(const QString &chatId, const QString &question, const QVariantList &options, const bool &anonymous, const int &correctOption, const bool &multiple, const QString &replyToMessageId = "0");
Q_INVOKABLE void getMessage(const QString &chatId, const QString &messageId);
Q_INVOKABLE void setOptionInteger(const QString &optionName, const int &optionValue);
Q_INVOKABLE void setChatNotificationSettings(const QString &chatId, const QVariantMap ¬ificationSettings);
@@ -142,6 +143,9 @@ public:
Q_INVOKABLE void setChatTitle(const QString &chatId, const QString &title);
Q_INVOKABLE void setBio(const QString &bio);
Q_INVOKABLE void toggleSupergroupIsAllHistoryAvailable(const QString &groupId, const bool &isAllHistoryAvailable);
+ Q_INVOKABLE void setPollAnswer(const QString &chatId, const qlonglong &messageId, QVariantList optionIds);
+ Q_INVOKABLE void stopPoll(const QString &chatId, const qlonglong &messageId);
+ Q_INVOKABLE void getPollVoters(const QString &chatId, const qlonglong &messageId, const int &optionId, const int &limit, const int &offset, const QString &extra);
// Others (candidates for extraction ;))
Q_INVOKABLE void searchEmoji(const QString &queryString);
@@ -198,6 +202,7 @@ signals:
void userProfilePhotosReceived(const QString &extra, const QVariantList &photos, const int &totalPhotos);
void chatPermissionsUpdated(const QString &chatId, const QVariantMap &permissions);
void chatTitleUpdated(const QString &chatId, const QString &title);
+ void usersReceived(const QString &extra, const QVariantList &userIds, const int &totalUsers);
public slots:
void handleVersionDetected(const QString &version);
@@ -243,8 +248,11 @@ public slots:
void handleSupergroupFullInfo(const QString &groupId, const QVariantMap &groupFullInfo);
void handleSupergroupFullInfoUpdated(const QString &groupId, const QVariantMap &groupFullInfo);
void handleUserProfilePhotos(const QString &extra, const QVariantList &photos, const int &totalPhotos);
- void handleChatPermissionsUpdated(const QString &chatId, const QVariantMap permissions);
- void handleChatTitleUpdated(const QString &chatId, const QString title);
+ void handleChatPermissionsUpdated(const QString &chatId, const QVariantMap &permissions);
+ void handleChatTitleUpdated(const QString &chatId, const QString &title);
+ void handleUsersReceived(const QString &extra, const QVariantList &userIds, const int &totalUsers);
+
+
private:
void setInitialParameters();
diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts
index 93e44cf..eaae40c 100644
--- a/translations/harbour-fernschreiber-de.ts
+++ b/translations/harbour-fernschreiber-de.ts
@@ -542,6 +542,24 @@
hat den Chattitel geändert
+
+
+ myself
+ haben eine Umfrage geschickt
+
+
+
+ hat eine Umfrage geschickt
+
+
+
+ myself
+ haben ein Quiz geschickt
+
+
+
+ hat ein Quiz geschickt
+
ImagePage
@@ -685,6 +703,178 @@
Sie haben noch keine Chats.
+
+ PollCreationPage
+
+
+ Alle Antworten müssen 1-100 Zeichen beinhalten.
+
+
+
+ Um ein Quiz zu senden, müssen Sie die richtige Antwort auswählen.
+
+
+
+ Sie müssen eine Frage eingeben.
+
+
+
+ Die Frage muss kürzer als 256 Zeichen sein.
+
+
+
+ Eine Umfrage benötigt 2-10 Antworten.
+
+
+
+ Dialog Header
+ Erstellen Sie eine Umfrage
+
+
+
+ After dialog header… Create a Poll in [group name]
+ in %1
+
+
+
+ Geben Sie Ihre Frage ein
+
+
+
+
+ Frage (%n1 Zeichen übrig)
+ Frage (%n1 Zeichen übrig)
+
+
+
+
+ Section header
+ Antworten
+
+
+
+ Geben Sie eine Antwort ein
+
+
+
+
+ Antwort (%n1 Zeichen übrig)
+ Antwort (%n1 Zeichen übrig)
+
+
+
+
+ Antwort hinzufügen
+
+
+
+ Section header
+ Umfrageoptionen
+
+
+
+ Anonyme Antworten
+
+
+
+ Mehrere Antworten erlaubt
+
+
+
+ Quizmodus
+
+
+
+ Quizze haben eine korrekte Antwort. Teilnehmer können ihre Antwort nicht zurückziehen.
+
+
+
+ PollPreview
+
+
+ % of votes for option
+ %L1%
+
+
+
+ Endergebnis:
+
+
+
+ Mehrfachauswahl ist erlaubt.
+
+
+
+ number of total votes
+
+ %L1 Stimme insgesamt
+ %L1 Stimmen insgesamt
+
+
+
+
+ Umfrage beenden
+
+
+
+ Antwort zurückziehen
+
+
+
+ PollResultsPage
+
+
+ Quizergebnis
+
+
+
+ Umfrageergebnis
+
+
+
+ number of total votes
+
+ %L1 Stimme insgesamt
+ %L1 Stimmen insgesamt
+
+
+
+
+ section header
+ Frage
+
+
+
+ section header
+ Ergebnis
+
+
+
+ number of votes for option
+
+ %L1 Antwort
+ %L1 Antworten
+
+
+
+
+ % of votes for option
+ %L1%
+
+
+
+ This answer has been chosen by the following users
+ Gewählt von:
+
+
+
+ number of votes for option
+
+ %L1 Antwort inklusive Ihrer
+ %L1 Antworten inklusive Ihrer
+
+
+
SettingsPage
@@ -964,5 +1154,41 @@
hat den Chattitel zu %1 geändert
+
+
+ myself
+ haben eine Umfrage gesendet
+
+
+
+ hat eine Umfrage gesendet
+
+
+
+ myself
+ haben ein anonymes Quiz gesendet
+
+
+
+ hat ein anonymes Quiz gesendet
+
+
+
+ myself
+ haben ein Quiz gesendet
+
+
+
+ hat ein Quiz gesendet
+
+
+
+ myself
+ haben eine anonyme Umfrage gesendet
+
+
+
+ hat eine anonyme Umfrage gesendet
+
diff --git a/translations/harbour-fernschreiber-es.ts b/translations/harbour-fernschreiber-es.ts
index c85a3da..69add33 100644
--- a/translations/harbour-fernschreiber-es.ts
+++ b/translations/harbour-fernschreiber-es.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,172 @@
No hay todavía ninguna charla .
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
SettingsPage
@@ -964,5 +1148,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts
index 6645016..4b0ac72 100644
--- a/translations/harbour-fernschreiber-fi.ts
+++ b/translations/harbour-fernschreiber-fi.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,178 @@
Sinulla ei ole vielä keskusteluja
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
SettingsPage
@@ -964,5 +1154,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-hu.ts b/translations/harbour-fernschreiber-hu.ts
index 5955b06..42c4433 100644
--- a/translations/harbour-fernschreiber-hu.ts
+++ b/translations/harbour-fernschreiber-hu.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,172 @@
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
SettingsPage
@@ -964,5 +1148,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-it.ts b/translations/harbour-fernschreiber-it.ts
index 775556b..839bffb 100644
--- a/translations/harbour-fernschreiber-it.ts
+++ b/translations/harbour-fernschreiber-it.ts
@@ -542,6 +542,24 @@
Ha modificato il titolo della chat
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,178 @@
Non hai nessuna chat.
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
SettingsPage
@@ -964,5 +1154,41 @@
ha modificato il titolo della chat in %1
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-pl.ts b/translations/harbour-fernschreiber-pl.ts
index 83b1565..0ad2981 100644
--- a/translations/harbour-fernschreiber-pl.ts
+++ b/translations/harbour-fernschreiber-pl.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,184 @@
Nie masz jeszcze żadnych czatów.
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
SettingsPage
@@ -964,5 +1160,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-ru.ts b/translations/harbour-fernschreiber-ru.ts
index 263552a..69bf463 100644
--- a/translations/harbour-fernschreiber-ru.ts
+++ b/translations/harbour-fernschreiber-ru.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,184 @@
Тут пока ничего нет
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+
SettingsPage
@@ -964,5 +1160,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber-zh_CN.ts b/translations/harbour-fernschreiber-zh_CN.ts
index a18810d..7ef0098 100644
--- a/translations/harbour-fernschreiber-zh_CN.ts
+++ b/translations/harbour-fernschreiber-zh_CN.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,172 @@
你尚无任何对话。
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
SettingsPage
@@ -964,5 +1148,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts
index da0dd42..48bfa9e 100644
--- a/translations/harbour-fernschreiber.ts
+++ b/translations/harbour-fernschreiber.ts
@@ -542,6 +542,24 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
ImagePage
@@ -685,6 +703,172 @@
+
+ PollCreationPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dialog Header
+
+
+
+
+ After dialog header… Create a Poll in [group name]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Section header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollPreview
+
+
+ % of votes for option
+
+
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PollResultsPage
+
+
+
+
+
+
+
+
+
+
+ number of total votes
+
+
+
+
+
+
+ section header
+
+
+
+
+ section header
+
+
+
+
+ number of votes for option
+
+
+
+
+
+
+ % of votes for option
+
+
+
+
+ This answer has been chosen by the following users
+
+
+
+
+ number of votes for option
+
+
+
+
+
SettingsPage
@@ -964,5 +1148,41 @@
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+