Rework the appearance of poll delegate

![image](/uploads/510e995e15d76ce0566b126a6917a963/image.png){width=541 height=269}
This commit is contained in:
James Graham
2025-03-28 09:05:39 +00:00
parent 8180f111d0
commit fadb5725e0
5 changed files with 129 additions and 21 deletions

View File

@@ -12,7 +12,7 @@ PollAnswerModel::PollAnswerModel(PollHandler *parent)
Q_ASSERT(parent != nullptr);
connect(parent, &PollHandler::selectionsChanged, this, [this]() {
dataChanged(index(0), index(rowCount() - 1), {CountRole, LocalChoiceRole});
dataChanged(index(0), index(rowCount() - 1), {CountRole, LocalChoiceRole, IsWinnerRole});
});
connect(parent, &PollHandler::answersChanged, this, [this]() {
dataChanged(index(0), index(rowCount() - 1), {TextRole});
@@ -50,6 +50,9 @@ QVariant PollAnswerModel::data(const QModelIndex &index, int role) const
}
return pollHandler->checkMemberSelectedId(room->localMember().id(), pollHandler->answerAtRow(row).id);
}
if (role == IsWinnerRole) {
return pollHandler->winningAnswerIds().contains(pollHandler->answerAtRow(row).id) && pollHandler->hasEnded();
}
return {};
}
@@ -72,5 +75,6 @@ QHash<int, QByteArray> PollAnswerModel::roleNames() const
{TextRole, "answerText"},
{CountRole, "count"},
{LocalChoiceRole, "localChoice"},
{IsWinnerRole, "isWinner"},
};
}

View File

@@ -28,6 +28,7 @@ public:
TextRole, /**< The answer text. */
CountRole, /**< The number of people who gave this answer. */
LocalChoiceRole, /**< Whether this option was selected by the local user */
IsWinnerRole, /**< Whether this option was selected by the local user */
};
Q_ENUM(Roles)

View File

@@ -11,6 +11,7 @@
#include <Quotient/events/roompowerlevelsevent.h>
#include <algorithm>
#include <qcontainerfwd.h>
using namespace Quotient;
@@ -133,6 +134,7 @@ void PollHandler::handleResponse(const Quotient::PollResponseEvent *event)
}
}
Q_EMIT totalCountChanged();
Q_EMIT selectionsChanged();
}
@@ -217,6 +219,44 @@ PollAnswerModel *PollHandler::answerModel()
return m_answerModel;
}
int PollHandler::totalCount() const
{
int votes = 0;
for (const auto &selection : m_selections) {
votes += selection.size();
}
return votes;
}
QStringList PollHandler::winningAnswerIds() const
{
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
return {};
}
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
if (pollStartEvent == nullptr) {
return {};
}
QStringList currentWinners;
for (const auto &answer : pollStartEvent->answers()) {
if (currentWinners.isEmpty()) {
currentWinners += answer.id;
continue;
}
if (answerCountAtId(currentWinners.first()) < answerCountAtId(answer.id)) {
currentWinners.clear();
currentWinners += answer.id;
continue;
}
if (answerCountAtId(currentWinners.first()) == answerCountAtId(answer.id)) {
currentWinners += answer.id;
}
}
return currentWinners;
}
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
{
Q_ASSERT(eventId.length() > 0);

View File

@@ -58,6 +58,11 @@ class PollHandler : public QObject
*/
Q_PROPERTY(PollAnswerModel *answerModel READ answerModel CONSTANT)
/**
* @brief The total number of vote responses to the poll.
*/
Q_PROPERTY(int totalCount READ totalCount NOTIFY totalCountChanged)
public:
PollHandler() = default;
PollHandler(NeoChatRoom *room, const QString &pollStartId);
@@ -89,6 +94,13 @@ public:
*/
bool checkMemberSelectedId(const QString &memberId, const QString &id) const;
int totalCount() const;
/**
* @brief The current answer IDs with the most votes.
*/
QStringList winningAnswerIds() const;
/**
* @brief Send an answer to the poll.
*/
@@ -97,8 +109,8 @@ public:
Q_SIGNALS:
void questionChanged();
void hasEndedChanged();
void answersChanged();
void totalCountChanged();
/**
* @brief Emitted when the selected answers to the poll change.
@@ -113,7 +125,7 @@ private:
void checkLoadRelations();
void handleResponse(const Quotient::PollResponseEvent *event);
QHash<QString, QDateTime> m_selectionTimestamps;
QHash<QString, QList<QString>> m_selections;
QHash<QString, QStringList> m_selections;
bool m_hasEnded = false;
QDateTime m_endedTimestamp;

View File

@@ -3,10 +3,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.formcard as FormCard
import Quotient
@@ -34,50 +35,100 @@ ColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth: Message.maxContentWidth
Layout.minimumWidth: Message.maxContentWidth
spacing: 0
Label {
id: questionLabel
text: root.pollHandler.question
wrapMode: Text.Wrap
RowLayout {
Layout.fillWidth: true
visible: text.length > 0
}
Kirigami.Icon {
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: "amarok_playcount"
}
QQC2.Label {
id: questionLabel
Layout.fillWidth: true
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
text: root.pollHandler.question
wrapMode: Text.Wrap
visible: text.length > 0
}
}
Repeater {
model: root.pollHandler.answerModel
delegate: FormCard.FormCheckDelegate {
delegate: Delegates.RoundedItemDelegate {
id: answerDelegate
required property string id
required property string answerText
required property int count
required property bool localChoice
required property bool isWinner
Layout.fillWidth: true
Layout.leftMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing
Layout.rightMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing
checked: answerDelegate.localChoice
onClicked: root.pollHandler.sendPollAnswer(root.eventId, answerDelegate.id)
enabled: !root.pollHandler.hasEnded
highlighted: false
onClicked: {
if (root.pollHandler.hasEnded) {
return;
}
root.pollHandler.sendPollAnswer(root.eventId, answerDelegate.id);
}
text: answerDelegate.answerText
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
trailing: Label {
visible: root.pollHandler.kind == PollKind.Disclosed || pollHandler.hasEnded
Layout.preferredWidth: contentWidth
text: answerDelegate.count
contentItem: ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
QQC2.CheckBox {
enabled: !root.pollHandler.hasEnded
checked: answerDelegate.localChoice
onClicked: answerDelegate.clicked()
}
QQC2.Label {
Layout.fillWidth: true
text: answerDelegate.answerText
}
Kirigami.Icon {
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: implicitWidth
visible: answerDelegate.isWinner
source: "favorite-favorited"
}
QQC2.Label {
visible: root.pollHandler.kind == PollKind.Disclosed || pollHandler.hasEnded
horizontalAlignment: Text.AlignRight
text: i18np("%1 Vote", "%1 Votes", answerDelegate.count)
}
}
QQC2.ProgressBar {
id: voteProgress
Layout.fillWidth: true
to: root.pollHandler.totalCount
value: root.pollHandler.kind == PollKind.Disclosed || pollHandler.hasEnded ? answerDelegate.count : 0
}
}
}
}
Label {
visible: root.pollHandler.kind == "org.matrix.msc3381.poll.disclosed" || root.pollHandler.hasEnded
text: i18np("Based on votes by %1 user", "Based on votes by %1 users", root.pollHandler.answerCount) + (root.pollHandler.hasEnded ? (" " + i18nc("as in 'this vote has ended'", "(Ended)")) : "")
QQC2.Label {
visible: root.pollHandler.kind == PollKind.Disclosed || root.pollHandler.hasEnded
text: i18np("Based on votes by %1 user", "Based on votes by %1 users", root.pollHandler.totalCount) + (root.pollHandler.hasEnded ? (" " + i18nc("as in 'this vote has ended'", "(Ended)")) : "")
font.pointSize: questionLabel.font.pointSize * 0.8
}
}