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..fbb3171
--- /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 { FadeAnimation {} }
+ 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 { FadeAnimation {}}
+ 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..83d519e 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,17 @@ Page {
}
}
}
+ IconButton {
+ visible: !chatPage.isPrivateChat &&
+ (chatGroupInformation.status["@type"] === "chatMemberStatusCreator"
+ || chatGroupInformation.status["@type"] === "chatMemberStatusAdministrator"
+ || (chatGroupInformation.status["@type"] === "chatMemberStatusMember" && chatInformation.permissions.can_send_polls))
+ 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..3f630ac
--- /dev/null
+++ b/qml/pages/PollCreationPage.qml
@@ -0,0 +1,351 @@
+/*
+ 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 { FadeAnimation {} }
+ Behavior on height { NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
+ 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 { NumberAnimation {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 { FadeAnimation {} }
+ Behavior on width { NumberAnimation {duration: 500; easing.type: Easing.InOutQuad}}
+ 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 { NumberAnimation {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 80580f0..383108a 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(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 9d75584..36f4fc5 100644
--- a/src/tdlibreceiver.h
+++ b/src/tdlibreceiver.h
@@ -80,6 +80,7 @@ signals:
void userProfilePhotos(const QString &extra, const QVariantList &photos, 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, 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 65c40d1..6ecdb62 100644
--- a/src/tdlibwrapper.cpp
+++ b/src/tdlibwrapper.cpp
@@ -104,6 +104,7 @@ TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent)
connect(this->tdLibReceiver, SIGNAL(userProfilePhotos(QString, QVariantList, int)), this, SIGNAL(userProfilePhotosReceived(QString, QVariantList, int)));
connect(this->tdLibReceiver, SIGNAL(chatPermissionsUpdated(QString, QVariantMap)), this, SIGNAL(chatPermissionsUpdated(QString, QVariantMap)));
connect(this->tdLibReceiver, SIGNAL(chatTitleUpdated(QString, QString)), this, SIGNAL(chatTitleUpdated(QString, QString)));
+ connect(this->tdLibReceiver, SIGNAL(usersReceived(QString, QVariantList, int)), this, SIGNAL(usersReceived(QString, QVariantList, int)));
connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList)));
@@ -382,6 +383,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);
@@ -621,6 +652,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);
diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h
index b603f82..677e907 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, 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, 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, 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, int totalUsers);
public slots:
void handleVersionDetected(const QString &version);
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..2843445 100644
--- a/translations/harbour-fernschreiber-es.ts
+++ b/translations/harbour-fernschreiber-es.ts
@@ -91,67 +91,67 @@
ChatInformationPage
- habilitar notificación
+ habilitar notificación
- deshabilitar notificación
+ deshabilitar notificación
-
+ Desconocido
-
+ El enlace de invitación se ha copiado en el portapapeles.
- %1 miembros, %2 en línea
+ %1 miembros, %2 en línea
- %1 suscriptores
+ %1 suscriptores
- %1 miembros
+ %1 miembros
-
+ Salir del grupo
-
+ Saliendo de la charla
group or user infotext header
-
+ Detalles
user phone number header
-
+ Número de teléfono
header
-
+ Enlace de invitación
-
+ Aún no hay texto de información disponible.
group title header
-
+ Título de charla
-
+ Introducir 1-128 caracteres
@@ -159,37 +159,37 @@
chats you have in common with a user
-
+ Cargando charlas comunes…
-
+ Desconocido
Button: groups in common (short)
-
+ Grupos
Button: Group Members
-
+ Miembros
-
+ Cargando miembros del grupo…
-
+ Usted
-
+ No hay ningún grupo en común con este usuario.
-
+ Este grupo está vacío.
@@ -197,38 +197,38 @@
Button: Chat Settings
- Ajustes
+ Ajustes
ChatListViewItem
-
+ Desconocido
-
+ Usted
- habilitar notificación
+ habilitar notificación
- deshabilitar notificación
+ deshabilitar notificación
-
+ detalle de usuario
-
+ Detalle de grupo
-
+ Marcar todos los mensajes como leídos
@@ -239,7 +239,7 @@
- Escribir mensaje
+ Introducir texto
@@ -255,7 +255,7 @@
- Responder mensaje
+ Responder
@@ -275,7 +275,7 @@
- Editar mensaje
+ Editar
@@ -287,7 +287,7 @@
- Borrar mensaje
+ Borrar
@@ -299,7 +299,7 @@
-
+ Esta charla está vacía.
@@ -361,67 +361,67 @@
what can normal group members do
-
+ Permisos de miembros del grupo
member permission
-
+ Enviar mensage
member permission
-
+ Enviar mensajes multimedia
member permission
-
+ Enviar otros mensajes
member permission
-
+ Agregar vistas previas de páginas web
member permission
-
+ Cambiar detalle de la charla
member permission
-
+ Invitar a usuarios
member permission
-
+ Mensajes de PIN
what can new group members do
-
+ Miembros nuevos
member permission
-
+ Los miembros nuevos pueden ver mensajes antiguos
EditSuperGroupSlowModeColumn
-
+ Modo lento
-
+ apagado
-
+ Establecer cuánto tiempo debe esperar cada miembro de la charla entre mensajes
@@ -536,10 +536,28 @@
myself
-
+ el título del charla se cambió
+ el título del charla se cambió
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
@@ -585,7 +603,7 @@
- Introducir código recibido:
+ Introducir el código recibido:
@@ -625,7 +643,7 @@
-
+ Usar el formato internacional, Ejemplo.
@@ -682,7 +700,173 @@
- No hay todavía ninguna charla .
+ 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
+
+
+
@@ -713,11 +897,11 @@
- Mostrar fondo para pegatinas y alinearlas centralmente como imágenes
+ Muestra el fondo para pegatinas y las alinea como imágenes
- Comentarios de notificación
+ Notificaciones
@@ -865,32 +1049,32 @@
myself
- envié una animación
+ envió una animación
myself
- envié un audio
+ envió un audio
myself
- envié una nota de voz
+ envió una nota de voz
myself
- envié un documento
+ envió un documento
myself
- envié una ubicación
+ envió una ubicación
myself
- envié un lugar
+ envió un lugar
@@ -913,55 +1097,91 @@
- nunca estuvo en línea
+ nunca en línea
- sin línea, hace en línea: hace 1 mes
+ sin línea, hace: hace 1 mes
- sin línea, hace en línea: hace 1 semana
+ sin línea, hace: hace 1 semana
- sin línea, hace en línea: %1
+ sin línea, hace: %1
- en línea
+ en línea
- sin línea, estuvo en línea
+ estuvo en línea
channel user role
-
+ Administrador
channel user role
-
+ Prohibido
channel user role
-
+ Creador
channel user role
-
+ Restringido
myself
-
+ se cambió el título de la charla a %1
+ se cambió el título de la charla a %1
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts
index 6645016..c7dfa43 100644
--- a/translations/harbour-fernschreiber-fi.ts
+++ b/translations/harbour-fernschreiber-fi.ts
@@ -37,11 +37,11 @@
- Tämä projekti käyttää Telegram Database Library (TDLib) -kirjastoa. Kiitokset sen jakamisesta Boost Software License 1.0 -lisenssillä!
+ Tämä projekti käyttää Telegram Database Library (TDLib) ‑kirjastoa. Kiitokset sen jakamisesta Boost Software License 1.0 ‑lisenssillä!
- Avaa Telegram Database -kirjasto GitHubissa
+ Avaa Telegram Database ‑kirjasto GitHubissa
@@ -49,7 +49,7 @@
- Tämä sovellus käyttää Telegram API:a, mutta ei ole Telegram:n hyväksymä tai varmentama.
+ Tämä sovellus käyttää Telegram APIa, mutta ei ole Telegramin hyväksymä tai varmentama.
@@ -57,7 +57,7 @@
- Kirjautuneena sisään käyttäjänä %1
+ Kirjautunut sisään käyttäjänä %1
@@ -65,7 +65,7 @@
- Tämä projekti käyttää twemojia. Tekijänoikeus 2018 Twitter, Inc. ja muut tekijät. Kiitokset sen julkaisusta MIT- (lähdekoodi) ja CC-BY 4.0- (grafiikat) lisensseillä!
+ Tämä projekti käyttää twemojia. Tekijänoikeus 2018 Twitter, Inc. ja muut tekijät. Kiitokset sen julkaisusta MIT‑ (lähdekoodi) ja CC-BY 4.0‑ (grafiikat) lisensseillä!
@@ -91,67 +91,67 @@
ChatInformationPage
- Poista keskustelun vaimennus
+ Poista keskustelun vaimennus
- Vaimenna keskustelu
+ Vaimenna keskustelu
- Tuntematon
+ Tuntematon
-
+ Kutsulinkki on kopioitu leikepöydälle.
- %1 jäsentä, %2 paikalla
+ %1 jäsentä, %2 paikalla
- %1 tilaajaa
+ %1 tilaajaa
- %1 jäsentä
+ %1 jäsentä
-
+ Poistu ryhmästä
-
+ Poistutaan keskustelusta
group or user infotext header
-
+ Tietoa
user phone number header
-
+ Puhelinnumero
header
-
+ Kutsulinkki
-
+ Tietoa ei ole vielä saatavilla.
group title header
-
+ Keskustelun otsikko
-
+ Syötä 1-128 merkkiä
@@ -159,37 +159,37 @@
chats you have in common with a user
-
+ Ladataan yhteisiä keskusteluja...
- Tuntematon
+ Tuntematon
Button: groups in common (short)
-
+ Ryhmät
Button: Group Members
-
+ Jäsenet
-
+ Ladataan ryhmän jäseniä...
- Sinä
+ Sinä
-
+ Sinulla ei ole yhteisiä ryhmiä tämän käyttäjän kanssa.
-
+ Tämä ryhmä on tyhjä.
@@ -197,38 +197,38 @@
Button: Chat Settings
- Asetukset
+ Asetukset
ChatListViewItem
- Tuntematon
+ Tuntematon
- Sinä
+ Sinä
- Poista keskustelun vaimennus
+ Poista keskustelun vaimennus
- Vaimenna keskustelu
+ Vaimenna keskustelu
-
+ Käyttäjän tiedot
-
+ Ryhmän tiedot
-
+ Merkitse kaikki viestit luetuiksi
@@ -295,11 +295,11 @@
-
+ Välitetty viesti
-
+ Tämä keskustelu on tyhjä.
@@ -314,7 +314,8 @@
-
+ The preposition 'in' is translated to Finnish using the inessive case (suffix -ssa/-ssä), so this string should be left empty in the translation. Unfortunately Qt will ignore empty translations, so let's use the character U+200B (zero width space) instead.
+
@@ -338,7 +339,7 @@
- keskustelu
+ keskustelussa
@@ -361,67 +362,67 @@
what can normal group members do
-
+ Ryhmän jäsenten käyttöoikeudet
member permission
-
+ Lähettä viestejä
member permission
-
+ Lähettää mediaviestejä
member permission
-
+ Lähettää muita viestejä
member permission
-
+ Lähettää verkkosivuesikatseluita
member permission
-
+ Muuttaa keskustelun tietoja
member permission
-
+ Kutsua jäseniä
member permission
-
+ Kiinnittää viestejä
what can new group members do
-
+ Uudet jäsenet
member permission
-
+ Uudet jäsenet voivat nähdä vanhoja viestejä
EditSuperGroupSlowModeColumn
-
+ Hidas moodi
-
+ Pois
-
+ Aseta kuinka kauan jokaisen keskustelun jäsenen täytyy odottaa viestien välillä
@@ -536,10 +537,28 @@
myself
-
+ muutit keskustelun otsikkoa
+ muutti keskustelun otsikkoa
+
+
+
+ myself
+
+
+
+
+
+
+
+
+ myself
+
+
+
+
@@ -555,7 +574,7 @@
- Lataus epäonnistui
+ Lataus epäonnistui.
@@ -625,14 +644,14 @@
-
+ Käytä kansainvälistä muotoa, esimerkiksi %1
LocationPreview
- Asenna Pure Maps tarkastellaksesi sijaintia
+ Asenna Pure Maps tarkastellaksesi sijaintia.
@@ -682,7 +701,179 @@
- Sinulla ei ole vielä keskusteluja
+ 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
+
+
+
+
@@ -697,54 +888,54 @@
- Lähetä viesti palautusnäppäimellä
+ Lähetä viesti rivinvaihdolla
- Lähetä viestisi painamalla palautusnäppäintä (enter)
+ Lähetä viestisi painamalla rivinvaihtonäppäintä (enter)
-
+ Ulkoasu
-
+ Näytä tarrat kuvina
-
+ Näytä tarroissa tausta ja keskitä ne kuten kuvat
-
+ Ilmoitusten palaute
-
+ Kaikki tapahtumat
-
+ Vain uudet tapahtumat
-
+ Ei mitään
-
+ Käytä ei-graafista palautetta (ääni, värinä) ilmoituksille
StickerPicker
-
+ Viimeksi käytetty
-
+ Ladataan tarroja...
@@ -759,7 +950,7 @@
- Lataus epäonnistui
+ Lataus epäonnistui.
@@ -913,55 +1104,91 @@
- Ei ole ollut koskaan paikalla
+ ei ole ollut koskaan paikalla
- Poissa. Nähty viimeksi: viime kuussa
+ poissa. Nähty viimeksi: viime kuussa
- Poissa. Nähty viimeksi: viime viikolla
+ poissa. Nähty viimeksi: viime viikolla
- Poissa. Nähty viimeksi: %1
+ poissa. Nähty viimeksi: %1
- paikalla
+ paikalla
- poissa, oli hetki sitten paikalla
+ poissa, oli hetki sitten paikalla
channel user role
-
+ Pääkäyttäjä
channel user role
-
+ Estetty
channel user role
-
+ Perustaja
channel user role
-
+ Rajoitettu
myself
-
+ vaihdoit keskustelun otsikoksi %1
+ vaihtoi keskustelun otsikoksi %1
+
+
+
+ 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..7188854 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
@@ -938,7 +1128,7 @@
channel user role
-
+ Amministratore
@@ -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
+
+
+
+
+
+