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

View File

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

View File

@@ -11,6 +11,7 @@
#include <Quotient/events/roompowerlevelsevent.h> #include <Quotient/events/roompowerlevelsevent.h>
#include <algorithm> #include <algorithm>
#include <qcontainerfwd.h>
using namespace Quotient; using namespace Quotient;
@@ -133,6 +134,7 @@ void PollHandler::handleResponse(const Quotient::PollResponseEvent *event)
} }
} }
Q_EMIT totalCountChanged();
Q_EMIT selectionsChanged(); Q_EMIT selectionsChanged();
} }
@@ -217,6 +219,44 @@ PollAnswerModel *PollHandler::answerModel()
return m_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) void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
{ {
Q_ASSERT(eventId.length() > 0); Q_ASSERT(eventId.length() > 0);

View File

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

View File

@@ -3,10 +3,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
import Quotient import Quotient
@@ -34,50 +35,100 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: Message.maxContentWidth Layout.maximumWidth: Message.maxContentWidth
Layout.minimumWidth: Message.maxContentWidth
spacing: 0 spacing: 0
Label { RowLayout {
id: questionLabel
text: root.pollHandler.question
wrapMode: Text.Wrap
Layout.fillWidth: true 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 { Repeater {
model: root.pollHandler.answerModel model: root.pollHandler.answerModel
delegate: FormCard.FormCheckDelegate { delegate: Delegates.RoundedItemDelegate {
id: answerDelegate id: answerDelegate
required property string id required property string id
required property string answerText required property string answerText
required property int count required property int count
required property bool localChoice required property bool localChoice
required property bool isWinner
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing Layout.leftMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing
Layout.rightMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing Layout.rightMargin: -Kirigami.Units.largeSpacing - Kirigami.Units.smallSpacing
checked: answerDelegate.localChoice highlighted: false
onClicked: root.pollHandler.sendPollAnswer(root.eventId, answerDelegate.id)
enabled: !root.pollHandler.hasEnded onClicked: {
if (root.pollHandler.hasEnded) {
return;
}
root.pollHandler.sendPollAnswer(root.eventId, answerDelegate.id);
}
text: answerDelegate.answerText text: answerDelegate.answerText
topPadding: Kirigami.Units.smallSpacing topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing bottomPadding: Kirigami.Units.smallSpacing
trailing: Label { contentItem: ColumnLayout {
visible: root.pollHandler.kind == PollKind.Disclosed || pollHandler.hasEnded Layout.fillWidth: true
Layout.preferredWidth: contentWidth
text: answerDelegate.count 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 { QQC2.Label {
visible: root.pollHandler.kind == "org.matrix.msc3381.poll.disclosed" || root.pollHandler.hasEnded 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.answerCount) + (root.pollHandler.hasEnded ? (" " + i18nc("as in 'this vote has ended'", "(Ended)")) : "") 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 font.pointSize: questionLabel.font.pointSize * 0.8
} }
} }