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 @@ changed the chat title hat den Chattitel geändert + + sent a poll + myself + haben eine Umfrage geschickt + + + sent a poll + hat eine Umfrage geschickt + + + sent a quiz + myself + haben ein Quiz geschickt + + + sent a quiz + hat ein Quiz geschickt + ImagePage @@ -685,6 +703,178 @@ Sie haben noch keine Chats. + + PollCreationPage + + All answers have to contain 1-100 characters. + Alle Antworten müssen 1-100 Zeichen beinhalten. + + + To send a quiz, you have to specify the right answer. + Um ein Quiz zu senden, müssen Sie die richtige Antwort auswählen. + + + You have to enter a question. + Sie müssen eine Frage eingeben. + + + The question has to be shorter than 256 characters. + Die Frage muss kürzer als 256 Zeichen sein. + + + A poll requires 2-10 answers. + Eine Umfrage benötigt 2-10 Antworten. + + + Create a Poll + Dialog Header + Erstellen Sie eine Umfrage + + + in %1 + After dialog header… Create a Poll in [group name] + in %1 + + + Enter your question here + Geben Sie Ihre Frage ein + + + Question (%n1 characters left) + + Frage (%n1 Zeichen übrig) + Frage (%n1 Zeichen übrig) + + + + Answers + Section header + Antworten + + + Enter an answer here + Geben Sie eine Antwort ein + + + Answer (%n1 characters left) + + Antwort (%n1 Zeichen übrig) + Antwort (%n1 Zeichen übrig) + + + + Add an answer + Antwort hinzufügen + + + Poll Options + Section header + Umfrageoptionen + + + Anonymous answers + Anonyme Antworten + + + Multiple answers allowed + Mehrere Antworten erlaubt + + + Quiz Mode + Quizmodus + + + Quizzes have one correct answer. Participants can't revoke their responses. + Quizze haben eine korrekte Antwort. Teilnehmer können ihre Antwort nicht zurückziehen. + + + + PollPreview + + %L1% + % of votes for option + %L1% + + + Final Result: + Endergebnis: + + + Multiple Answers are allowed. + Mehrfachauswahl ist erlaubt. + + + %L1 vote(s) total + number of total votes + + %L1 Stimme insgesamt + %L1 Stimmen insgesamt + + + + Close Poll + Umfrage beenden + + + Reset Answer + Antwort zurückziehen + + + + PollResultsPage + + Quiz Results + Quizergebnis + + + Poll Results + Umfrageergebnis + + + %L1 vote(s) total + number of total votes + + %L1 Stimme insgesamt + %L1 Stimmen insgesamt + + + + Question + section header + Frage + + + Results + section header + Ergebnis + + + %L1 vote(s) + number of votes for option + + %L1 Antwort + %L1 Antworten + + + + %L1% + % of votes for option + %L1% + + + Chosen by: + This answer has been chosen by the following users + Gewählt von: + + + %L1 vote(s) including yours + number of votes for option + + %L1 Antwort inklusive Ihrer + %L1 Antworten inklusive Ihrer + + + SettingsPage @@ -964,5 +1154,41 @@ changed the chat title to %1 hat den Chattitel zu %1 geändert + + sent a poll + myself + haben eine Umfrage gesendet + + + sent a poll + hat eine Umfrage gesendet + + + sent an anonymous quiz + myself + haben ein anonymes Quiz gesendet + + + sent an anonymous quiz + hat ein anonymes Quiz gesendet + + + sent a quiz + myself + haben ein Quiz gesendet + + + sent a quiz + hat ein Quiz gesendet + + + sent an anonymous poll + myself + haben eine anonyme Umfrage gesendet + + + sent an anonymous poll + 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 Unmute Chat - habilitar notificación + habilitar notificación Mute Chat - deshabilitar notificación + deshabilitar notificación Unknown - + Desconocido The Invite Link has been copied to the clipboard. - + El enlace de invitación se ha copiado en el portapapeles. %1 members, %2 online - %1 miembros, %2 en línea + %1 miembros, %2 en línea %1 subscribers - %1 suscriptores + %1 suscriptores %1 members - %1 miembros + %1 miembros Leave Group - + Salir del grupo Leaving chat - + Saliendo de la charla Info group or user infotext header - + Detalles Phone Number user phone number header - + Número de teléfono Invite Link header - + Enlace de invitación There is no information text available, yet. - + Aún no hay texto de información disponible. Chat Title group title header - + Título de charla Enter 1-128 characters - + Introducir 1-128 caracteres @@ -159,37 +159,37 @@ Loading common chats… chats you have in common with a user - + Cargando charlas comunes… Unknown - + Desconocido Groups Button: groups in common (short) - + Grupos Members Button: Group Members - + Miembros Loading group members… - + Cargando miembros del grupo… You - + Usted You don't have any groups in common with this user. - + No hay ningún grupo en común con este usuario. This group is empty. - + Este grupo está vacío. @@ -197,38 +197,38 @@ Settings Button: Chat Settings - Ajustes + Ajustes ChatListViewItem Unknown - + Desconocido You - + Usted Unmute Chat - habilitar notificación + habilitar notificación Mute Chat - deshabilitar notificación + deshabilitar notificación User Info - + detalle de usuario Group Info - + Detalle de grupo Mark all messages as read - + Marcar todos los mensajes como leídos @@ -239,7 +239,7 @@ Your message - Escribir mensaje + Introducir texto %1 members, %2 online @@ -255,7 +255,7 @@ Reply to Message - Responder mensaje + Responder You @@ -275,7 +275,7 @@ Edit Message - Editar mensaje + Editar edited @@ -287,7 +287,7 @@ Delete Message - Borrar mensaje + Borrar Uploading... @@ -299,7 +299,7 @@ This chat is empty. - + Esta charla está vacía. @@ -361,67 +361,67 @@ Group Member Permissions what can normal group members do - + Permisos de miembros del grupo Send Messages member permission - + Enviar mensage Send Media Messages member permission - + Enviar mensajes multimedia Send Other Messages member permission - + Enviar otros mensajes Add Web Page Previews member permission - + Agregar vistas previas de páginas web Change Chat Info member permission - + Cambiar detalle de la charla Invite Users member permission - + Invitar a usuarios Pin Messages member permission - + Mensajes de PIN New Members what can new group members do - + Miembros nuevos New members can see older messages member permission - + Los miembros nuevos pueden ver mensajes antiguos EditSuperGroupSlowModeColumn Slow Mode - + Modo lento Off - + apagado Set how long every chat member has to wait between Messages - + Establecer cuánto tiempo debe esperar cada miembro de la charla entre mensajes @@ -536,10 +536,28 @@ changed the chat title myself - + el título del charla se cambió changed the chat title + el título del charla se cambió + + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz @@ -585,7 +603,7 @@ Please enter the code that you received: - Introducir código recibido: + Introducir el código recibido: Loading... @@ -625,7 +643,7 @@ Use the international format, e.g. %1 - + Usar el formato internacional, Ejemplo. @@ -682,7 +700,173 @@ You don't have any chats yet. - No hay todavía ninguna charla . + No hay todavía ninguna charla. + + + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + @@ -713,11 +897,11 @@ Show background for stickers and align them centrally like images - Mostrar fondo para pegatinas y alinearlas centralmente como imágenes + Muestra el fondo para pegatinas y las alinea como imágenes Notification feedback - Comentarios de notificación + Notificaciones All events @@ -865,32 +1049,32 @@ sent an animation myself - envié una animación + envió una animación sent an audio myself - envié un audio + envió un audio sent a voice note myself - envié una nota de voz + envió una nota de voz sent a document myself - envié un documento + envió un documento sent a location myself - envié una ubicación + envió una ubicación sent a venue myself - envié un lugar + envió un lugar have registered with Telegram @@ -913,55 +1097,91 @@ was never online - nunca estuvo en línea + nunca en línea offline, last online: last month - sin línea, hace en línea: hace 1 mes + sin línea, hace: hace 1 mes offline, last online: last week - sin línea, hace en línea: hace 1 semana + sin línea, hace: hace 1 semana offline, last online: %1 - sin línea, hace en línea: %1 + sin línea, hace: %1 online - en línea + en línea offline, was recently online - sin línea, estuvo en línea + estuvo en línea Admin channel user role - + Administrador Banned channel user role - + Prohibido Creator channel user role - + Creador Restricted channel user role - + Restringido changed the chat title to %1 myself - + se cambió el título de la charla a %1 changed the chat title to %1 + se cambió el título de la charla a %1 + + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll 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 @@ This project uses the Telegram Database Library (TDLib). Thanks for making it available under the conditions of the Boost Software License 1.0! - 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ä! Open Telegram Database Library on GitHub - Avaa Telegram Database -kirjasto GitHubissa + Avaa Telegram Database ‑kirjasto GitHubissa About Telegram @@ -49,7 +49,7 @@ This product uses the Telegram API but is not endorsed or certified by Telegram. - 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. TDLib version %1 @@ -57,7 +57,7 @@ Logged in as %1 - Kirjautuneena sisään käyttäjänä %1 + Kirjautunut sisään käyttäjänä %1 Phone number: +%1 @@ -65,7 +65,7 @@ This project uses twemoji. Copyright 2018 Twitter, Inc. and other contributors. Thanks for making it available under the conditions of the MIT License (coding) and CC-BY 4.0 (graphics)! - 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ä! Open twemoji on GitHub @@ -91,67 +91,67 @@ ChatInformationPage Unmute Chat - Poista keskustelun vaimennus + Poista keskustelun vaimennus Mute Chat - Vaimenna keskustelu + Vaimenna keskustelu Unknown - Tuntematon + Tuntematon The Invite Link has been copied to the clipboard. - + Kutsulinkki on kopioitu leikepöydälle. %1 members, %2 online - %1 jäsentä, %2 paikalla + %1 jäsentä, %2 paikalla %1 subscribers - %1 tilaajaa + %1 tilaajaa %1 members - %1 jäsentä + %1 jäsentä Leave Group - + Poistu ryhmästä Leaving chat - + Poistutaan keskustelusta Info group or user infotext header - + Tietoa Phone Number user phone number header - + Puhelinnumero Invite Link header - + Kutsulinkki There is no information text available, yet. - + Tietoa ei ole vielä saatavilla. Chat Title group title header - + Keskustelun otsikko Enter 1-128 characters - + Syötä 1-128 merkkiä @@ -159,37 +159,37 @@ Loading common chats… chats you have in common with a user - + Ladataan yhteisiä keskusteluja... Unknown - Tuntematon + Tuntematon Groups Button: groups in common (short) - + Ryhmät Members Button: Group Members - + Jäsenet Loading group members… - + Ladataan ryhmän jäseniä... You - Sinä + Sinä You don't have any groups in common with this user. - + Sinulla ei ole yhteisiä ryhmiä tämän käyttäjän kanssa. This group is empty. - + Tämä ryhmä on tyhjä. @@ -197,38 +197,38 @@ Settings Button: Chat Settings - Asetukset + Asetukset ChatListViewItem Unknown - Tuntematon + Tuntematon You - Sinä + Sinä Unmute Chat - Poista keskustelun vaimennus + Poista keskustelun vaimennus Mute Chat - Vaimenna keskustelu + Vaimenna keskustelu User Info - + Käyttäjän tiedot Group Info - + Ryhmän tiedot Mark all messages as read - + Merkitse kaikki viestit luetuiksi @@ -295,11 +295,11 @@ Forwarded Message - + Välitetty viesti This chat is empty. - + Tämä keskustelu on tyhjä. @@ -314,7 +314,8 @@ in - + 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. + Waiting for network... @@ -338,7 +339,7 @@ chat - keskustelu + keskustelussa chats @@ -361,67 +362,67 @@ Group Member Permissions what can normal group members do - + Ryhmän jäsenten käyttöoikeudet Send Messages member permission - + Lähettä viestejä Send Media Messages member permission - + Lähettää mediaviestejä Send Other Messages member permission - + Lähettää muita viestejä Add Web Page Previews member permission - + Lähettää verkkosivuesikatseluita Change Chat Info member permission - + Muuttaa keskustelun tietoja Invite Users member permission - + Kutsua jäseniä Pin Messages member permission - + Kiinnittää viestejä New Members what can new group members do - + Uudet jäsenet New members can see older messages member permission - + Uudet jäsenet voivat nähdä vanhoja viestejä EditSuperGroupSlowModeColumn Slow Mode - + Hidas moodi Off - + Pois Set how long every chat member has to wait between Messages - + Aseta kuinka kauan jokaisen keskustelun jäsenen täytyy odottaa viestien välillä @@ -536,10 +537,28 @@ changed the chat title myself - + muutit keskustelun otsikkoa changed the chat title + muutti keskustelun otsikkoa + + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz @@ -555,7 +574,7 @@ Download failed. - Lataus epäonnistui + Lataus epäonnistui. @@ -625,14 +644,14 @@ Use the international format, e.g. %1 - + Käytä kansainvälistä muotoa, esimerkiksi %1 LocationPreview Install Pure Maps to inspect this location. - Asenna Pure Maps tarkastellaksesi sijaintia + Asenna Pure Maps tarkastellaksesi sijaintia. @@ -682,7 +701,179 @@ You don't have any chats yet. - Sinulla ei ole vielä keskusteluja + Sinulla ei ole vielä keskusteluja. + + + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + @@ -697,54 +888,54 @@ Send message by enter - Lähetä viesti palautusnäppäimellä + Lähetä viesti rivinvaihdolla Send your message by pressing the enter key - Lähetä viestisi painamalla palautusnäppäintä (enter) + Lähetä viestisi painamalla rivinvaihtonäppäintä (enter) Appearance - + Ulkoasu Show stickers as images - + Näytä tarrat kuvina Show background for stickers and align them centrally like images - + Näytä tarroissa tausta ja keskitä ne kuten kuvat Notification feedback - + Ilmoitusten palaute All events - + Kaikki tapahtumat Only new events - + Vain uudet tapahtumat None - + Ei mitään Use non-graphical feedback (sound, vibration) for notifications - + Käytä ei-graafista palautetta (ääni, värinä) ilmoituksille StickerPicker Recently used - + Viimeksi käytetty Loading stickers... - + Ladataan tarroja... @@ -759,7 +950,7 @@ Download failed. - Lataus epäonnistui + Lataus epäonnistui. @@ -913,55 +1104,91 @@ was never online - Ei ole ollut koskaan paikalla + ei ole ollut koskaan paikalla offline, last online: last month - Poissa. Nähty viimeksi: viime kuussa + poissa. Nähty viimeksi: viime kuussa offline, last online: last week - Poissa. Nähty viimeksi: viime viikolla + poissa. Nähty viimeksi: viime viikolla offline, last online: %1 - Poissa. Nähty viimeksi: %1 + poissa. Nähty viimeksi: %1 online - paikalla + paikalla offline, was recently online - poissa, oli hetki sitten paikalla + poissa, oli hetki sitten paikalla Admin channel user role - + Pääkäyttäjä Banned channel user role - + Estetty Creator channel user role - + Perustaja Restricted channel user role - + Rajoitettu changed the chat title to %1 myself - + vaihdoit keskustelun otsikoksi %1 changed the chat title to %1 + vaihtoi keskustelun otsikoksi %1 + + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll 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 @@ changed the chat title + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,172 @@ + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + SettingsPage @@ -964,5 +1148,41 @@ changed the chat title to %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + + 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 @@ changed the chat title Ha modificato il titolo della chat + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,178 @@ Non hai nessuna chat. + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + + SettingsPage @@ -938,7 +1128,7 @@ Admin channel user role - + Amministratore Banned @@ -964,5 +1154,41 @@ changed the chat title to %1 ha modificato il titolo della chat in %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + + 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 @@ changed the chat title + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,184 @@ Nie masz jeszcze żadnych czatów. + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + + + SettingsPage @@ -964,5 +1160,41 @@ changed the chat title to %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + + 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 @@ changed the chat title + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,184 @@ Тут пока ничего нет + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + + + SettingsPage @@ -964,5 +1160,41 @@ changed the chat title to %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + + 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 @@ changed the chat title + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,172 @@ 你尚无任何对话。 + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + SettingsPage @@ -964,5 +1148,41 @@ changed the chat title to %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + + 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 @@ changed the chat title + + sent a poll + myself + + + + sent a poll + + + + sent a quiz + myself + + + + sent a quiz + + ImagePage @@ -685,6 +703,172 @@ + + PollCreationPage + + All answers have to contain 1-100 characters. + + + + To send a quiz, you have to specify the right answer. + + + + You have to enter a question. + + + + The question has to be shorter than 256 characters. + + + + A poll requires 2-10 answers. + + + + Create a Poll + Dialog Header + + + + in %1 + After dialog header… Create a Poll in [group name] + + + + Enter your question here + + + + Question (%n1 characters left) + + + + + + Answers + Section header + + + + Enter an answer here + + + + Answer (%n1 characters left) + + + + + + Add an answer + + + + Poll Options + Section header + + + + Anonymous answers + + + + Multiple answers allowed + + + + Quiz Mode + + + + Quizzes have one correct answer. Participants can't revoke their responses. + + + + + PollPreview + + %L1% + % of votes for option + + + + Final Result: + + + + Multiple Answers are allowed. + + + + %L1 vote(s) total + number of total votes + + + + + + Close Poll + + + + Reset Answer + + + + + PollResultsPage + + Quiz Results + + + + Poll Results + + + + %L1 vote(s) total + number of total votes + + + + + + Question + section header + + + + Results + section header + + + + %L1 vote(s) + number of votes for option + + + + + + %L1% + % of votes for option + + + + Chosen by: + This answer has been chosen by the following users + + + + %L1 vote(s) including yours + number of votes for option + + + + + SettingsPage @@ -964,5 +1148,41 @@ changed the chat title to %1 + + sent a poll + myself + + + + sent a poll + + + + sent an anonymous quiz + myself + + + + sent an anonymous quiz + + + + sent a quiz + myself + + + + sent a quiz + + + + sent an anonymous poll + myself + + + + sent an anonymous poll + +