Poll Updates and Send Polls
Fix showing polls and update the events and PollHandler to make them easier to work with. Add a PollAnswerModel to visualise poll answers. Enable sending polls.
This commit is contained in:
@@ -8,14 +8,14 @@
|
|||||||
"answers": [
|
"answers": [
|
||||||
{
|
{
|
||||||
"id": "option1",
|
"id": "option1",
|
||||||
"org.matrix.msc1767.text": "option1"
|
"org.matrix.msc1767.text": "option1text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "option2",
|
"id": "option2",
|
||||||
"org.matrix.msc1767.text": "option2"
|
"org.matrix.msc1767.text": "option2text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"kind": "org.matrix.msc3381.poll.disclosed",
|
"kind": "org.matrix.msc3381.poll.undisclosed",
|
||||||
"max_selections": 1,
|
"max_selections": 1,
|
||||||
"question": {
|
"question": {
|
||||||
"body": "test",
|
"body": "test",
|
||||||
|
|||||||
@@ -41,29 +41,32 @@ void PollHandlerTest::nullObject()
|
|||||||
auto pollHandler = PollHandler();
|
auto pollHandler = PollHandler();
|
||||||
|
|
||||||
QCOMPARE(pollHandler.hasEnded(), false);
|
QCOMPARE(pollHandler.hasEnded(), false);
|
||||||
QCOMPARE(pollHandler.answerCount(), 0);
|
QCOMPARE(pollHandler.numAnswers(), 0);
|
||||||
QCOMPARE(pollHandler.question(), QString());
|
QCOMPARE(pollHandler.question(), QString());
|
||||||
QCOMPARE(pollHandler.options(), QJsonArray());
|
QCOMPARE(pollHandler.kind(), PollKind::Disclosed);
|
||||||
QCOMPARE(pollHandler.answers(), QJsonObject());
|
|
||||||
QCOMPARE(pollHandler.counts(), QJsonObject());
|
|
||||||
QCOMPARE(pollHandler.kind(), QString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollHandlerTest::poll()
|
void PollHandlerTest::poll()
|
||||||
{
|
{
|
||||||
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
|
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
|
||||||
auto pollHandler = PollHandler(room, startEvent);
|
auto pollHandler = PollHandler(room, startEvent->id());
|
||||||
|
|
||||||
auto options = QJsonArray{QJsonObject{{"id"_L1, "option1"_L1}, {"org.matrix.msc1767.text"_L1, "option1"_L1}},
|
QList<Quotient::EventContent::Answer> options = {EventContent::Answer{"option1"_L1, "option1"_L1}, EventContent::Answer{"option2"_L1, "option2"_L1}};
|
||||||
QJsonObject{{"id"_L1, "option2"_L1}, {"org.matrix.msc1767.text"_L1, "option2"_L1}}};
|
|
||||||
|
|
||||||
|
const auto answer0 = pollHandler.answerAtRow(0);
|
||||||
|
const auto answer1 = pollHandler.answerAtRow(1);
|
||||||
QCOMPARE(pollHandler.hasEnded(), false);
|
QCOMPARE(pollHandler.hasEnded(), false);
|
||||||
QCOMPARE(pollHandler.answerCount(), 0);
|
QCOMPARE(pollHandler.numAnswers(), 2);
|
||||||
QCOMPARE(pollHandler.question(), u"test"_s);
|
QCOMPARE(pollHandler.question(), u"test"_s);
|
||||||
QCOMPARE(pollHandler.options(), options);
|
QCOMPARE(answer0.id, "option1"_L1);
|
||||||
QCOMPARE(pollHandler.answers(), QJsonObject());
|
QCOMPARE(answer1.id, "option2"_L1);
|
||||||
QCOMPARE(pollHandler.counts(), QJsonObject());
|
QCOMPARE(answer0.text, "option1text"_L1);
|
||||||
QCOMPARE(pollHandler.kind(), u"org.matrix.msc3381.poll.disclosed"_s);
|
QCOMPARE(answer1.text, "option2text"_L1);
|
||||||
|
QCOMPARE(pollHandler.answerCountAtId(answer0.id), 0);
|
||||||
|
QCOMPARE(pollHandler.answerCountAtId(answer1.id), 0);
|
||||||
|
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer0.id), false);
|
||||||
|
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer1.id), false);
|
||||||
|
QCOMPARE(pollHandler.kind(), PollKind::Undisclosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(PollHandlerTest)
|
QTEST_GUILESS_MAIN(PollHandlerTest)
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ add_library(neochat STATIC
|
|||||||
models/pinnedmessagemodel.h
|
models/pinnedmessagemodel.h
|
||||||
models/commonroomsmodel.cpp
|
models/commonroomsmodel.cpp
|
||||||
models/commonroomsmodel.h
|
models/commonroomsmodel.h
|
||||||
|
models/pollanswermodel.cpp
|
||||||
|
models/pollanswermodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||||
@@ -296,6 +298,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
qml/HoverLinkIndicator.qml
|
qml/HoverLinkIndicator.qml
|
||||||
qml/AvatarNotification.qml
|
qml/AvatarNotification.qml
|
||||||
qml/ReasonDialog.qml
|
qml/ReasonDialog.qml
|
||||||
|
qml/NewPollDialog.qml
|
||||||
SOURCES
|
SOURCES
|
||||||
messageattached.cpp
|
messageattached.cpp
|
||||||
messageattached.h
|
messageattached.h
|
||||||
|
|||||||
@@ -113,6 +113,20 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
tooltip: text
|
tooltip: text
|
||||||
},
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
id: pollButton
|
||||||
|
icon.name: "amarok_playcount"
|
||||||
|
property bool isBusy: false
|
||||||
|
text: i18nc("@action:button", "Create a Poll")
|
||||||
|
displayHint: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
newPollDialog.createObject(QQC2.Overlay.overlay, {
|
||||||
|
room: root.currentRoom
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
tooltip: text
|
||||||
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
id: sendAction
|
id: sendAction
|
||||||
|
|
||||||
@@ -492,6 +506,11 @@ QQC2.Control {
|
|||||||
LocationChooser {}
|
LocationChooser {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: newPollDialog
|
||||||
|
NewPollDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
CompletionMenu {
|
CompletionMenu {
|
||||||
id: completionMenu
|
id: completionMenu
|
||||||
chatDocumentHandler: documentHandler
|
chatDocumentHandler: documentHandler
|
||||||
|
|||||||
@@ -2,22 +2,28 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
#include "pollevent.h"
|
#include "pollevent.h"
|
||||||
|
#include <Quotient/converters.h>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
PollStartEvent::PollStartEvent(const QJsonObject &obj)
|
PollKind::Kind PollStartEvent::kind() const
|
||||||
: RoomEvent(obj)
|
|
||||||
{
|
{
|
||||||
|
return content().kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PollStartEvent::maxSelections() const
|
int PollStartEvent::maxSelections() const
|
||||||
{
|
{
|
||||||
return contentJson()["org.matrix.msc3381.poll.start"_L1]["max_selections"_L1].toInt();
|
return content().maxSelection > 0 ? content().maxSelection : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PollStartEvent::question() const
|
QString PollStartEvent::question() const
|
||||||
{
|
{
|
||||||
return contentJson()["org.matrix.msc3381.poll.start"_L1]["question"_L1]["body"_L1].toString();
|
return content().question;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<EventContent::Answer> PollStartEvent::answers() const
|
||||||
|
{
|
||||||
|
return content().answers;
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResponseEvent::PollResponseEvent(const QJsonObject &obj)
|
PollResponseEvent::PollResponseEvent(const QJsonObject &obj)
|
||||||
@@ -25,14 +31,34 @@ PollResponseEvent::PollResponseEvent(const QJsonObject &obj)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PollEndEvent::PollEndEvent(const QJsonObject &obj)
|
|
||||||
: RoomEvent(obj)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PollResponseEvent::PollResponseEvent(const QString &pollStartEventId, QStringList responses)
|
PollResponseEvent::PollResponseEvent(const QString &pollStartEventId, QStringList responses)
|
||||||
: RoomEvent(basicJson(TypeId,
|
: RoomEvent(basicJson(TypeId,
|
||||||
{{"org.matrix.msc3381.poll.response"_L1, QJsonObject{{"answers"_L1, QJsonArray::fromStringList(responses)}}},
|
{{"org.matrix.msc3381.poll.response"_L1, QJsonObject{{"answers"_L1, QJsonArray::fromStringList(responses)}}},
|
||||||
{"m.relates_to"_L1, QJsonObject{{"rel_type"_L1, "m.reference"_L1}, {"event_id"_L1, pollStartEventId}}}}))
|
{"m.relates_to"_L1, QJsonObject{{"rel_type"_L1, "m.reference"_L1}, {"event_id"_L1, pollStartEventId}}}}))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList PollResponseEvent::selections() const
|
||||||
|
{
|
||||||
|
const auto jsonSelections = contentPart<QJsonObject>("org.matrix.msc3381.poll.response"_L1)["answers"_L1].toArray();
|
||||||
|
QStringList selections;
|
||||||
|
for (const auto &selection : jsonSelections) {
|
||||||
|
selections += selection.toString();
|
||||||
|
}
|
||||||
|
return selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<EventRelation> PollResponseEvent::relatesTo() const
|
||||||
|
{
|
||||||
|
return contentPart<std::optional<EventRelation>>(RelatesToKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
PollEndEvent::PollEndEvent(const QJsonObject &obj)
|
||||||
|
: RoomEvent(obj)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<EventRelation> PollEndEvent::relatesTo() const
|
||||||
|
{
|
||||||
|
return contentPart<std::optional<EventRelation>>(RelatesToKey);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,148 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <Quotient/converters.h>
|
||||||
|
#include <Quotient/events/eventrelation.h>
|
||||||
#include <Quotient/events/roomevent.h>
|
#include <Quotient/events/roomevent.h>
|
||||||
|
#include <Quotient/quotient_common.h>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PollKind
|
||||||
|
*
|
||||||
|
* This class is designed to define the PollKind enumeration.
|
||||||
|
*/
|
||||||
|
class PollKind : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Enum representing the available poll kinds.
|
||||||
|
*/
|
||||||
|
enum Kind {
|
||||||
|
Disclosed, /**< The poll results can been seen after the user votes. */
|
||||||
|
Undisclosed, /**< The poll results can only been seen after the poll ends. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Kind);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the string for the given Kind.
|
||||||
|
*
|
||||||
|
* @sa Kind
|
||||||
|
*/
|
||||||
|
static QString stringForKind(Kind kind)
|
||||||
|
{
|
||||||
|
switch (kind) {
|
||||||
|
case Undisclosed:
|
||||||
|
return "org.matrix.msc3381.poll.undisclosed"_L1;
|
||||||
|
default:
|
||||||
|
return "org.matrix.msc3381.poll.disclosed"_L1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the Kind for the given string.
|
||||||
|
*
|
||||||
|
* @sa Kind
|
||||||
|
*/
|
||||||
|
static Kind kindForString(const QString &kindString)
|
||||||
|
{
|
||||||
|
if (kindString == "org.matrix.msc3381.poll.undisclosed"_L1) {
|
||||||
|
return Undisclosed;
|
||||||
|
}
|
||||||
|
return Disclosed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
|
namespace EventContent
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief An answer to the poll.
|
||||||
|
*/
|
||||||
|
struct Answer {
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString id MEMBER id CONSTANT)
|
||||||
|
Q_PROPERTY(QString text MEMBER text CONSTANT)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString id;
|
||||||
|
QString text;
|
||||||
|
|
||||||
|
int operator==(const Answer &right) const
|
||||||
|
{
|
||||||
|
return id == right.id && text == right.text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Struct representing the content of a poll event.
|
||||||
|
*/
|
||||||
|
struct PollStartContent {
|
||||||
|
PollKind::Kind kind;
|
||||||
|
int maxSelection;
|
||||||
|
QString question;
|
||||||
|
QList<EventContent::Answer> answers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace EventContent
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline EventContent::Answer fromJson(const QJsonObject &jo)
|
||||||
|
{
|
||||||
|
return EventContent::Answer{fromJson<QString>(jo["id"_L1]), fromJson<QString>(jo["org.matrix.msc1767.text"_L1])};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline auto toJson(const EventContent::Answer &c)
|
||||||
|
{
|
||||||
|
QJsonObject jo;
|
||||||
|
addParam<IfNotEmpty>(jo, "id"_L1, c.id);
|
||||||
|
addParam<IfNotEmpty>(jo, "org.matrix.msc1767.text"_L1, c.text);
|
||||||
|
return jo;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline EventContent::PollStartContent fromJson(const QJsonObject &jo)
|
||||||
|
{
|
||||||
|
return EventContent::PollStartContent{
|
||||||
|
PollKind::kindForString(jo["org.matrix.msc3381.poll.start"_L1]["kind"_L1].toString()),
|
||||||
|
fromJson<int>(jo["org.matrix.msc3381.poll.start"_L1]["max_selections"_L1]),
|
||||||
|
fromJson<QString>(jo["org.matrix.msc3381.poll.start"_L1]["question"_L1]["org.matrix.msc1767.text"_L1]),
|
||||||
|
fromJson<QList<EventContent::Answer>>(jo["org.matrix.msc3381.poll.start"_L1]["answers"_L1]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline auto toJson(const EventContent::PollStartContent &c)
|
||||||
|
{
|
||||||
|
QJsonObject innerJo;
|
||||||
|
addParam<IfNotEmpty>(innerJo, "kind"_L1, PollKind::stringForKind(c.kind));
|
||||||
|
addParam(innerJo, "max_selections"_L1, c.maxSelection);
|
||||||
|
if (innerJo["max_selections"_L1].toInt() < 1) {
|
||||||
|
innerJo["max_selections"_L1] = 1;
|
||||||
|
}
|
||||||
|
innerJo.insert("question"_L1, QJsonObject{{"org.matrix.msc1767.text"_L1, c.question}});
|
||||||
|
addParam<IfNotEmpty>(innerJo, "answers"_L1, c.answers);
|
||||||
|
|
||||||
|
QJsonObject jo;
|
||||||
|
auto textString = c.question;
|
||||||
|
for (int i = 0; i < c.answers.length(); ++i) {
|
||||||
|
textString.append("\n%1. %2"_L1.arg(QString::number(i + 1), c.answers.at(i).text));
|
||||||
|
}
|
||||||
|
addParam<IfNotEmpty>(jo, "org.matrix.msc1767.text"_L1, textString);
|
||||||
|
jo.insert("org.matrix.msc3381.poll.start"_L1, innerJo);
|
||||||
|
return jo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class PollStartEvent
|
* @class PollStartEvent
|
||||||
*
|
*
|
||||||
@@ -17,11 +155,16 @@ namespace Quotient
|
|||||||
*
|
*
|
||||||
* @sa Quotient::RoomEvent
|
* @sa Quotient::RoomEvent
|
||||||
*/
|
*/
|
||||||
class PollStartEvent : public RoomEvent
|
class PollStartEvent : public EventTemplate<PollStartEvent, RoomEvent, EventContent::PollStartContent>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QUO_EVENT(PollStartEvent, "org.matrix.msc3381.poll.start");
|
QUO_EVENT(PollStartEvent, "org.matrix.msc3381.poll.start");
|
||||||
explicit PollStartEvent(const QJsonObject &obj);
|
using EventTemplate::EventTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The poll kind.
|
||||||
|
*/
|
||||||
|
PollKind::Kind kind() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The maximum number of options a user can select in a poll.
|
* @brief The maximum number of options a user can select in a poll.
|
||||||
@@ -32,6 +175,11 @@ public:
|
|||||||
* @brief The question being asked in the poll.
|
* @brief The question being asked in the poll.
|
||||||
*/
|
*/
|
||||||
QString question() const;
|
QString question() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The list of answers to the poll.
|
||||||
|
*/
|
||||||
|
QList<EventContent::Answer> answers() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +198,16 @@ public:
|
|||||||
QUO_EVENT(PollResponseEvent, "org.matrix.msc3381.poll.response");
|
QUO_EVENT(PollResponseEvent, "org.matrix.msc3381.poll.response");
|
||||||
explicit PollResponseEvent(const QJsonObject &obj);
|
explicit PollResponseEvent(const QJsonObject &obj);
|
||||||
explicit PollResponseEvent(const QString &pollStartEventId, QStringList responses);
|
explicit PollResponseEvent(const QString &pollStartEventId, QStringList responses);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The selected answers to the poll.
|
||||||
|
*/
|
||||||
|
QStringList selections() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The EventRelation pointing to the PollStartEvent.
|
||||||
|
*/
|
||||||
|
std::optional<EventRelation> relatesTo() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,5 +225,10 @@ class PollEndEvent : public RoomEvent
|
|||||||
public:
|
public:
|
||||||
QUO_EVENT(PollEndEvent, "org.matrix.msc3381.poll.end");
|
QUO_EVENT(PollEndEvent, "org.matrix.msc3381.poll.end");
|
||||||
explicit PollEndEvent(const QJsonObject &obj);
|
explicit PollEndEvent(const QJsonObject &obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The EventRelation pointing to the PollStartEvent.
|
||||||
|
*/
|
||||||
|
std::optional<EventRelation> relatesTo() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == ContentModelRole) {
|
if (role == ContentModelRole) {
|
||||||
if (event->get().is<EncryptedEvent>()) {
|
if (event->get().is<EncryptedEvent>() || event->get().is<PollStartEvent>()) {
|
||||||
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(event->get().id()));
|
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(event->get().id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,18 +401,12 @@ void MessageModel::refreshLastUserEvents(int baseTimelineRow)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isPending)
|
void MessageModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
if (event == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only create the poll handler for event acknowledged by the server as we need
|
|
||||||
// an ID
|
|
||||||
if (!event->id().isEmpty() && event->is<PollStartEvent>()) {
|
|
||||||
m_room->createPollHandler(eventCast<const PollStartEvent>(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto eventId = event->id();
|
auto eventId = event->id();
|
||||||
auto senderId = event->senderId();
|
auto senderId = event->senderId();
|
||||||
if (eventId.isEmpty()) {
|
if (eventId.isEmpty()) {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Q_SIGNALS:
|
|||||||
* Any model inheriting from MessageModel needs to emit this signal for every
|
* Any model inheriting from MessageModel needs to emit this signal for every
|
||||||
* new event it adds.
|
* new event it adds.
|
||||||
*/
|
*/
|
||||||
void newEventAdded(const Quotient::RoomEvent *event, bool isPending = false);
|
void newEventAdded(const Quotient::RoomEvent *event);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
@@ -154,5 +154,5 @@ private:
|
|||||||
|
|
||||||
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
|
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
|
||||||
|
|
||||||
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
|
void createEventObjects(const Quotient::RoomEvent *event);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void PinnedMessageModel::fill()
|
|||||||
connect(job, &BaseJob::success, this, [this, job] {
|
connect(job, &BaseJob::success, this, [this, job] {
|
||||||
beginInsertRows({}, m_pinnedEvents.size(), m_pinnedEvents.size());
|
beginInsertRows({}, m_pinnedEvents.size(), m_pinnedEvents.size());
|
||||||
m_pinnedEvents.push_back(std::move(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData())));
|
m_pinnedEvents.push_back(std::move(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData())));
|
||||||
Q_EMIT newEventAdded(m_pinnedEvents.back().get(), false);
|
Q_EMIT newEventAdded(m_pinnedEvents.back().get());
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/models/pollanswermodel.cpp
Normal file
76
src/models/pollanswermodel.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "pollanswermodel.h"
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
#include "pollhandler.h"
|
||||||
|
|
||||||
|
PollAnswerModel::PollAnswerModel(PollHandler *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
Q_ASSERT(parent != nullptr);
|
||||||
|
|
||||||
|
connect(parent, &PollHandler::selectionsChanged, this, [this]() {
|
||||||
|
dataChanged(index(0), index(rowCount() - 1), {CountRole, LocalChoiceRole});
|
||||||
|
});
|
||||||
|
connect(parent, &PollHandler::answersChanged, this, [this]() {
|
||||||
|
dataChanged(index(0), index(rowCount() - 1), {TextRole});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PollAnswerModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
|
||||||
|
|
||||||
|
const auto row = index.row();
|
||||||
|
if (row < 0 || row >= rowCount()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pollHandler = dynamic_cast<PollHandler *>(this->parent());
|
||||||
|
if (pollHandler == nullptr) {
|
||||||
|
qWarning() << "PollAnswerModel created with nullptr parent.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == IdRole) {
|
||||||
|
return pollHandler->answerAtRow(row).id;
|
||||||
|
}
|
||||||
|
if (role == TextRole) {
|
||||||
|
return pollHandler->answerAtRow(row).text;
|
||||||
|
}
|
||||||
|
if (role == CountRole) {
|
||||||
|
return pollHandler->answerCountAtId(pollHandler->answerAtRow(row).id);
|
||||||
|
}
|
||||||
|
if (role == LocalChoiceRole) {
|
||||||
|
const auto room = pollHandler->room();
|
||||||
|
if (room == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pollHandler->checkMemberSelectedId(room->localMember().id(), pollHandler->answerAtRow(row).id);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int PollAnswerModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
const auto pollHandler = dynamic_cast<PollHandler *>(this->parent());
|
||||||
|
if (pollHandler == nullptr) {
|
||||||
|
qWarning() << "PollAnswerModel created with nullptr parent.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollHandler->numAnswers();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> PollAnswerModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{IdRole, "id"},
|
||||||
|
{TextRole, "answerText"},
|
||||||
|
{CountRole, "count"},
|
||||||
|
{LocalChoiceRole, "localChoice"},
|
||||||
|
};
|
||||||
|
}
|
||||||
56
src/models/pollanswermodel.h
Normal file
56
src/models/pollanswermodel.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
class PollHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PollAnswerModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising a list of answer to a poll.
|
||||||
|
*/
|
||||||
|
class PollAnswerModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
|
enum Roles {
|
||||||
|
IdRole, /**< The ID of the answer. */
|
||||||
|
TextRole, /**< The answer text. */
|
||||||
|
CountRole, /**< The number of people who gave this answer. */
|
||||||
|
LocalChoiceRole, /**< Whether this option was selected by the local user */
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
|
explicit PollAnswerModel(PollHandler *parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
int rowCount(const QModelIndex &parent = {}) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
#include "timelinemessagemodel.h"
|
#include "timelinemessagemodel.h"
|
||||||
|
#include "events/pollevent.h"
|
||||||
#include "messagemodel_logging.h"
|
#include "messagemodel_logging.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -36,7 +37,7 @@ void TimelineMessageModel::connectNewRoom()
|
|||||||
});
|
});
|
||||||
connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) {
|
connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) {
|
||||||
if (m_initialized) {
|
if (m_initialized) {
|
||||||
for (int i = lowest; i == biggest; ++i) {
|
for (int i = lowest; i <= biggest; ++i) {
|
||||||
const auto event = m_room->findInTimeline(i)->event();
|
const auto event = m_room->findInTimeline(i)->event();
|
||||||
Q_EMIT newEventAdded(event);
|
Q_EMIT newEventAdded(event);
|
||||||
}
|
}
|
||||||
@@ -58,14 +59,14 @@ void TimelineMessageModel::connectNewRoom()
|
|||||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
|
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
|
||||||
connect(m_room, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
|
connect(m_room, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
Q_EMIT newEventAdded(event, true);
|
Q_EMIT newEventAdded(event);
|
||||||
beginInsertRows({}, 0, 0);
|
beginInsertRows({}, 0, 0);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
});
|
});
|
||||||
#else
|
#else
|
||||||
connect(m_room, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
connect(m_room, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
Q_EMIT newEventAdded(event, true);
|
Q_EMIT newEventAdded(event);
|
||||||
beginInsertRows({}, 0, 0);
|
beginInsertRows({}, 0, 0);
|
||||||
});
|
});
|
||||||
connect(m_room, &Room::pendingEventAdded, this, &TimelineMessageModel::endInsertRows);
|
connect(m_room, &Room::pendingEventAdded, this, &TimelineMessageModel::endInsertRows);
|
||||||
@@ -111,9 +112,6 @@ void TimelineMessageModel::connectNewRoom()
|
|||||||
const auto eventIt = m_room->findInTimeline(eventId);
|
const auto eventIt = m_room->findInTimeline(eventId);
|
||||||
if (eventIt != m_room->historyEdge()) {
|
if (eventIt != m_room->historyEdge()) {
|
||||||
Q_EMIT newEventAdded(eventIt->event());
|
Q_EMIT newEventAdded(eventIt->event());
|
||||||
if (eventIt->event()->is<PollStartEvent>()) {
|
|
||||||
m_room->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
refreshEventRoles(eventId, {Qt::DisplayRole});
|
refreshEventRoles(eventId, {Qt::DisplayRole});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1438,24 +1438,37 @@ bool NeoChatRoom::canEncryptRoom() const
|
|||||||
|
|
||||||
static PollHandler *emptyPollHandler = new PollHandler;
|
static PollHandler *emptyPollHandler = new PollHandler;
|
||||||
|
|
||||||
PollHandler *NeoChatRoom::poll(const QString &eventId) const
|
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
||||||
{
|
{
|
||||||
if (auto pollHandler = m_polls[eventId]) {
|
const auto event = getEvent(eventId);
|
||||||
return pollHandler;
|
if (event.first == nullptr || event.second) {
|
||||||
|
return emptyPollHandler;
|
||||||
}
|
}
|
||||||
return emptyPollHandler;
|
|
||||||
|
if (m_polls.contains(eventId)) {
|
||||||
|
return m_polls[eventId];
|
||||||
|
}
|
||||||
|
auto handler = new PollHandler(this, eventId);
|
||||||
|
m_polls.insert(eventId, handler);
|
||||||
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::createPollHandler(const Quotient::PollStartEvent *event)
|
void NeoChatRoom::postPoll(PollKind::Kind kind, const QString &question, const QList<QString> &answers)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
QList<EventContent::Answer> answerStructs;
|
||||||
return;
|
for (const auto &answer : answers) {
|
||||||
}
|
answerStructs += EventContent::Answer{
|
||||||
auto eventId = event->id();
|
QUuid::createUuid().toString().remove(QRegularExpression(u"{|}|-"_s)),
|
||||||
if (!m_polls.contains(eventId)) {
|
answer,
|
||||||
auto handler = new PollHandler(this, event);
|
};
|
||||||
m_polls.insert(eventId, handler);
|
|
||||||
}
|
}
|
||||||
|
const auto content = EventContent::PollStartContent{
|
||||||
|
.kind = kind,
|
||||||
|
.maxSelection = 1,
|
||||||
|
.question = question,
|
||||||
|
.answers = answerStructs,
|
||||||
|
};
|
||||||
|
post<PollStartEvent>(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include "enums/messagetype.h"
|
#include "enums/messagetype.h"
|
||||||
#include "enums/pushrule.h"
|
#include "enums/pushrule.h"
|
||||||
|
#include "events/pollevent.h"
|
||||||
#include "models/messagecontentmodel.h"
|
#include "models/messagecontentmodel.h"
|
||||||
#include "models/threadmodel.h"
|
#include "models/threadmodel.h"
|
||||||
#include "neochatroommember.h"
|
#include "neochatroommember.h"
|
||||||
@@ -501,14 +502,9 @@ public:
|
|||||||
*
|
*
|
||||||
* @sa PollHandler
|
* @sa PollHandler
|
||||||
*/
|
*/
|
||||||
PollHandler *poll(const QString &eventId) const;
|
PollHandler *poll(const QString &eventId);
|
||||||
|
|
||||||
/**
|
Q_INVOKABLE void postPoll(PollKind::Kind kind, const QString &question, const QList<QString> &answers);
|
||||||
* @brief Create a PollHandler object for the given event.
|
|
||||||
*
|
|
||||||
* @sa PollHandler
|
|
||||||
*/
|
|
||||||
void createPollHandler(const Quotient::PollStartEvent *event);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the full Json data for a given room account data event.
|
* @brief Get the full Json data for a given room account data event.
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
|
|
||||||
|
#include "events/pollevent.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "pollanswermodel.h"
|
||||||
|
|
||||||
#include <Quotient/csapi/relations.h>
|
#include <Quotient/csapi/relations.h>
|
||||||
#include <Quotient/events/roompowerlevelsevent.h>
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
@@ -12,11 +14,14 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
PollHandler::PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent)
|
PollHandler::PollHandler(NeoChatRoom *room, const QString &pollStartId)
|
||||||
: QObject(room)
|
: QObject(room)
|
||||||
, m_pollStartEvent(pollStartEvent)
|
, m_pollStartId(pollStartId)
|
||||||
{
|
{
|
||||||
if (room != nullptr && m_pollStartEvent != nullptr) {
|
Q_ASSERT(room != nullptr);
|
||||||
|
Q_ASSERT(!pollStartId.isEmpty());
|
||||||
|
|
||||||
|
if (room != nullptr) {
|
||||||
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollHandler::updatePoll);
|
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollHandler::updatePoll);
|
||||||
checkLoadRelations();
|
checkLoadRelations();
|
||||||
}
|
}
|
||||||
@@ -26,28 +31,38 @@ void PollHandler::updatePoll(Quotient::RoomEventsRange events)
|
|||||||
{
|
{
|
||||||
// This function will never be called if the PollHandler was not initialized with
|
// This function will never be called if the PollHandler was not initialized with
|
||||||
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
||||||
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
const auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
|
if (pollStartEvent == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &event : events) {
|
for (const auto &event : events) {
|
||||||
if (event->is<PollEndEvent>()) {
|
if (event->is<PollEndEvent>()) {
|
||||||
|
const auto endEvent = eventCast<const PollEndEvent>(event);
|
||||||
|
if (endEvent->relatesTo()->eventId != m_pollStartId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
if (!plEvent) {
|
if (!plEvent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
||||||
if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
if (event->senderId() == pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
||||||
m_hasEnded = true;
|
m_hasEnded = true;
|
||||||
m_endedTimestamp = event->originTimestamp();
|
m_endedTimestamp = event->originTimestamp();
|
||||||
Q_EMIT hasEndedChanged();
|
Q_EMIT hasEndedChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event->is<PollResponseEvent>()) {
|
if (event->is<PollResponseEvent>()) {
|
||||||
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
handleResponse(eventCast<const PollResponseEvent>(event));
|
||||||
}
|
}
|
||||||
if (event->contentPart<QJsonObject>("m.relates_to"_L1).contains("rel_type"_L1)
|
if (event->contentPart<QJsonObject>("m.relates_to"_L1).contains("rel_type"_L1)
|
||||||
&& event->contentPart<QJsonObject>("m.relates_to"_L1)["rel_type"_L1].toString() == "m.replace"_L1
|
&& event->contentPart<QJsonObject>("m.relates_to"_L1)["rel_type"_L1].toString() == "m.replace"_L1
|
||||||
&& event->contentPart<QJsonObject>("m.relates_to"_L1)["event_id"_L1].toString() == m_pollStartEvent->id()) {
|
&& event->contentPart<QJsonObject>("m.relates_to"_L1)["event_id"_L1].toString() == pollStartEvent->id()) {
|
||||||
Q_EMIT questionChanged();
|
Q_EMIT questionChanged();
|
||||||
Q_EMIT optionsChanged();
|
Q_EMIT answersChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,91 +72,149 @@ void PollHandler::checkLoadRelations()
|
|||||||
// This function will never be called if the PollHandler was not initialized with
|
// This function will never be called if the PollHandler was not initialized with
|
||||||
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
||||||
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
m_maxVotes = m_pollStartEvent->maxSelections();
|
const auto pollStartEvent = room->getEvent(m_pollStartId).first;
|
||||||
auto job = room->connection()->callApi<GetRelatingEventsJob>(room->id(), m_pollStartEvent->id());
|
if (pollStartEvent == nullptr) {
|
||||||
connect(job, &BaseJob::success, this, [this, job, room]() {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto job = room->connection()->callApi<GetRelatingEventsJob>(room->id(), pollStartEvent->id());
|
||||||
|
connect(job, &BaseJob::success, this, [this, job, room, pollStartEvent]() {
|
||||||
for (const auto &event : job->chunk()) {
|
for (const auto &event : job->chunk()) {
|
||||||
if (event->is<PollEndEvent>()) {
|
if (event->is<PollEndEvent>()) {
|
||||||
|
const auto endEvent = eventCast<const PollEndEvent>(event);
|
||||||
|
if (endEvent->relatesTo()->eventId != m_pollStartId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
if (!plEvent) {
|
if (!plEvent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
||||||
if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
if (event->senderId() == pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
||||||
m_hasEnded = true;
|
m_hasEnded = true;
|
||||||
m_endedTimestamp = event->originTimestamp();
|
m_endedTimestamp = event->originTimestamp();
|
||||||
Q_EMIT hasEndedChanged();
|
Q_EMIT hasEndedChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event->is<PollResponseEvent>()) {
|
if (event->is<PollResponseEvent>()) {
|
||||||
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
handleResponse(eventCast<const PollResponseEvent>(event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollHandler::handleAnswer(const QJsonObject &content, const QString &sender, QDateTime timestamp)
|
void PollHandler::handleResponse(const Quotient::PollResponseEvent *event)
|
||||||
{
|
{
|
||||||
if (timestamp > m_answerTimestamps[sender] && (!m_hasEnded || timestamp < m_endedTimestamp)) {
|
if (event == nullptr) {
|
||||||
m_answerTimestamps[sender] = timestamp;
|
return;
|
||||||
m_answers[sender] = {};
|
}
|
||||||
int i = 0;
|
|
||||||
for (const auto &answer : content["org.matrix.msc3381.poll.response"_L1]["answers"_L1].toArray()) {
|
if (event->relatesTo()->eventId != m_pollStartId) {
|
||||||
auto array = m_answers[sender].toArray();
|
return;
|
||||||
array.insert(0, answer);
|
}
|
||||||
m_answers[sender] = array;
|
|
||||||
i++;
|
// If there is no origin timestamp it's pending and therefore must be newer.
|
||||||
if (i == m_maxVotes) {
|
if ((event->originTimestamp() > m_selectionTimestamps[event->senderId()] || event->id().isEmpty())
|
||||||
break;
|
&& (!m_hasEnded || event->originTimestamp() < m_endedTimestamp)) {
|
||||||
}
|
m_selectionTimestamps[event->senderId()] = event->originTimestamp();
|
||||||
|
|
||||||
|
// This function will never be called if the PollHandler was not initialized with
|
||||||
|
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
||||||
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
const auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
|
if (pollStartEvent == nullptr) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for (const auto &key : m_answers.keys()) {
|
|
||||||
if (m_answers[key].toArray().isEmpty()) {
|
m_selections[event->senderId()] = event->selections().size() > 0 ? event->selections().first(pollStartEvent->maxSelections()) : event->selections();
|
||||||
m_answers.remove(key);
|
if (m_selections.contains(event->senderId()) && m_selections[event->senderId()].isEmpty()) {
|
||||||
}
|
m_selections.remove(event->senderId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Q_EMIT answersChanged();
|
|
||||||
|
Q_EMIT selectionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatRoom *PollHandler::room() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<NeoChatRoom *>(parent());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PollHandler::question() const
|
QString PollHandler::question() const
|
||||||
{
|
{
|
||||||
if (m_pollStartEvent == nullptr) {
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
if (room == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["question"_L1].toObject()["body"_L1].toString();
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
}
|
if (pollStartEvent == nullptr) {
|
||||||
|
|
||||||
QJsonArray PollHandler::options() const
|
|
||||||
{
|
|
||||||
if (m_pollStartEvent == nullptr) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["answers"_L1].toArray();
|
return pollStartEvent->question();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject PollHandler::answers() const
|
int PollHandler::numAnswers() const
|
||||||
{
|
{
|
||||||
return m_answers;
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
if (room == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
|
if (pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pollStartEvent->answers().length();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject PollHandler::counts() const
|
Quotient::EventContent::Answer PollHandler::answerAtRow(int row) const
|
||||||
{
|
{
|
||||||
QJsonObject counts;
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
for (const auto &answer : m_answers) {
|
if (room == nullptr) {
|
||||||
for (const auto &id : answer.toArray()) {
|
return {};
|
||||||
counts[id.toString()] = counts[id.toString()].toInt() + 1;
|
}
|
||||||
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
|
if (pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pollStartEvent->answers()[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
int PollHandler::answerCountAtId(const QString &id) const
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (const auto &selection : m_selections) {
|
||||||
|
if (selection.contains(id)) {
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return counts;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PollHandler::kind() const
|
bool PollHandler::checkMemberSelectedId(const QString &memberId, const QString &id) const
|
||||||
{
|
{
|
||||||
if (m_pollStartEvent == nullptr) {
|
return m_selections[memberId].contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
PollKind::Kind PollHandler::kind() const
|
||||||
|
{
|
||||||
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
if (room == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_L1)["kind"_L1].toString();
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
|
if (pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pollStartEvent->kind();
|
||||||
|
}
|
||||||
|
|
||||||
|
PollAnswerModel *PollHandler::answerModel()
|
||||||
|
{
|
||||||
|
if (m_answerModel == nullptr) {
|
||||||
|
m_answerModel = new PollAnswerModel(this);
|
||||||
|
}
|
||||||
|
return m_answerModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
|
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
|
||||||
@@ -153,23 +226,25 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
|
|||||||
qWarning() << "PollHandler is empty, cannot send an answer.";
|
qWarning() << "PollHandler is empty, cannot send an answer.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QStringList ownAnswers;
|
auto pollStartEvent = eventCast<const PollStartEvent>(room->getEvent(m_pollStartId).first);
|
||||||
for (const auto &answer : m_answers[room->localMember().id()].toArray()) {
|
if (pollStartEvent == nullptr) {
|
||||||
ownAnswers += answer.toString();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList ownAnswers = m_selections[room->localMember().id()];
|
||||||
if (ownAnswers.contains(answerId)) {
|
if (ownAnswers.contains(answerId)) {
|
||||||
ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
|
ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
|
||||||
return answerId == it;
|
return answerId == it;
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
while (ownAnswers.size() >= m_maxVotes) {
|
while (ownAnswers.size() >= pollStartEvent->maxSelections() && ownAnswers.size() > 0) {
|
||||||
ownAnswers.pop_front();
|
ownAnswers.pop_front();
|
||||||
}
|
}
|
||||||
ownAnswers.insert(0, answerId);
|
ownAnswers.insert(0, answerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
|
const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
|
||||||
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
|
handleResponse(eventCast<const PollResponseEvent>(response.event()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PollHandler::hasEnded() const
|
bool PollHandler::hasEnded() const
|
||||||
@@ -177,9 +252,4 @@ bool PollHandler::hasEnded() const
|
|||||||
return m_hasEnded;
|
return m_hasEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PollHandler::answerCount() const
|
|
||||||
{
|
|
||||||
return m_answers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_pollhandler.cpp"
|
#include "moc_pollhandler.cpp"
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
#include <Quotient/events/roomevent.h>
|
#include <Quotient/events/roomevent.h>
|
||||||
|
|
||||||
#include "events/pollevent.h"
|
#include "events/pollevent.h"
|
||||||
|
#include "models/pollanswermodel.h"
|
||||||
|
|
||||||
|
namespace Quotient
|
||||||
|
{
|
||||||
|
class PollResponseEvent;
|
||||||
|
}
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
@@ -32,53 +38,56 @@ class PollHandler : public QObject
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("Use NeoChatRoom::poll")
|
QML_UNCREATABLE("Use NeoChatRoom::poll")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The kind of the poll.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(PollKind::Kind kind READ kind CONSTANT)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The question for the poll.
|
* @brief The question for the poll.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString question READ question NOTIFY questionChanged)
|
Q_PROPERTY(QString question READ question NOTIFY questionChanged)
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The list of possible answers to the poll.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(QJsonArray options READ options NOTIFY optionsChanged)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The list of answer responses to the poll from users in the room.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(QJsonObject answers READ answers NOTIFY answersChanged)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The list number of votes for each answer in the poll.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(QJsonObject counts READ counts NOTIFY answersChanged)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the poll has ended.
|
* @brief Whether the poll has ended.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool hasEnded READ hasEnded NOTIFY hasEndedChanged)
|
Q_PROPERTY(bool hasEnded READ hasEnded NOTIFY hasEndedChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The total number of answers to the poll.
|
* @brief The model to visualize the answers to this poll.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(int answerCount READ answerCount NOTIFY answersChanged)
|
Q_PROPERTY(PollAnswerModel *answerModel READ answerModel CONSTANT)
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The kind of the poll.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(QString kind READ kind CONSTANT)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PollHandler() = default;
|
PollHandler() = default;
|
||||||
PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent);
|
PollHandler(NeoChatRoom *room, const QString &pollStartId);
|
||||||
|
|
||||||
bool hasEnded() const;
|
NeoChatRoom *room() const;
|
||||||
int answerCount() const;
|
|
||||||
|
|
||||||
|
PollKind::Kind kind() const;
|
||||||
QString question() const;
|
QString question() const;
|
||||||
QJsonArray options() const;
|
bool hasEnded() const;
|
||||||
QJsonObject answers() const;
|
PollAnswerModel *answerModel();
|
||||||
QJsonObject counts() const;
|
|
||||||
QString kind() const;
|
/**
|
||||||
|
* @brief The total number of answer options.
|
||||||
|
*/
|
||||||
|
int numAnswers() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The answer at the given row.
|
||||||
|
*/
|
||||||
|
Quotient::EventContent::Answer answerAtRow(int row) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of responders who gave the answer ID.
|
||||||
|
*/
|
||||||
|
int answerCountAtId(const QString &id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether the given member has selected the given ID in their response.
|
||||||
|
*/
|
||||||
|
bool checkMemberSelectedId(const QString &memberId, const QString &id) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send an answer to the poll.
|
* @brief Send an answer to the poll.
|
||||||
@@ -87,20 +96,27 @@ public:
|
|||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void questionChanged();
|
void questionChanged();
|
||||||
void optionsChanged();
|
|
||||||
void answersChanged();
|
|
||||||
void hasEndedChanged();
|
void hasEndedChanged();
|
||||||
|
|
||||||
|
void answersChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emitted when the selected answers to the poll change.
|
||||||
|
*/
|
||||||
|
void selectionsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Quotient::PollStartEvent *m_pollStartEvent = nullptr;
|
QString m_pollStartId;
|
||||||
|
|
||||||
void updatePoll(Quotient::RoomEventsRange events);
|
void updatePoll(Quotient::RoomEventsRange events);
|
||||||
|
|
||||||
void checkLoadRelations();
|
void checkLoadRelations();
|
||||||
void handleAnswer(const QJsonObject &object, const QString &sender, QDateTime timestamp);
|
void handleResponse(const Quotient::PollResponseEvent *event);
|
||||||
QMap<QString, QDateTime> m_answerTimestamps;
|
QHash<QString, QDateTime> m_selectionTimestamps;
|
||||||
QJsonObject m_answers;
|
QHash<QString, QList<QString>> m_selections;
|
||||||
int m_maxVotes = 1;
|
|
||||||
bool m_hasEnded = false;
|
bool m_hasEnded = false;
|
||||||
QDateTime m_endedTimestamp;
|
QDateTime m_endedTimestamp;
|
||||||
|
|
||||||
|
QPointer<PollAnswerModel> m_answerModel;
|
||||||
};
|
};
|
||||||
|
|||||||
156
src/qml/NewPollDialog.qml
Normal file
156
src/qml/NewPollDialog.qml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||||
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
|
|
||||||
|
import Quotient
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.Dialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property NeoChatRoom room
|
||||||
|
|
||||||
|
standardButtons: Kirigami.Dialog.Cancel
|
||||||
|
|
||||||
|
customFooterActions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
enabled: optionModel.allValuesSet && questionTextField.text.length > 0
|
||||||
|
text: i18nc("@action:button", "Send")
|
||||||
|
icon.name: "document-send"
|
||||||
|
onTriggered: {
|
||||||
|
root.room.postPoll(pollTypeCombo.currentValue, questionTextField.text, optionModel.values())
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||||
|
title: i18nc("@title: create new poll in the room", "Create Poll")
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
FormCard.FormComboBoxDelegate {
|
||||||
|
id: pollTypeCombo
|
||||||
|
|
||||||
|
text: i18n("Poll type:")
|
||||||
|
currentIndex: 0
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
model: [
|
||||||
|
{ value: PollKind.Disclosed, text: i18n("Open poll") },
|
||||||
|
{ value: PollKind.Undisclosed, text: i18n("Closed poll") }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
verticalPadding: 0
|
||||||
|
text: pollTypeCombo.currentValue == 0 ? i18n("Voters can see the result as soon as they have voted") : i18n("Results are revealed only after the poll has closed")
|
||||||
|
}
|
||||||
|
FormCard.FormTextFieldDelegate {
|
||||||
|
id: questionTextField
|
||||||
|
label: i18n("Question:")
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
id: optionRepeater
|
||||||
|
|
||||||
|
model: ListModel {
|
||||||
|
id: optionModel
|
||||||
|
|
||||||
|
readonly property bool allValuesSet: {
|
||||||
|
for( var i = 0; i < optionModel.rowCount(); i++ ) {
|
||||||
|
if (optionModel.get(i).optionText.length <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListElement {
|
||||||
|
optionText: ""
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
optionText: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function values() {
|
||||||
|
let textValues = []
|
||||||
|
for( var i = 0; i < optionModel.rowCount(); i++ ) {
|
||||||
|
textValues.push(optionModel.get(i).optionText);
|
||||||
|
}
|
||||||
|
return textValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate: FormCard.AbstractFormDelegate {
|
||||||
|
id: optionDelegate
|
||||||
|
|
||||||
|
required property int index
|
||||||
|
required property string optionText
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
QQC2.Label {
|
||||||
|
id: optionLabel
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18nc("As in first answer option to the poll", "Option %1:", optionDelegate.index + 1)
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
Accessible.ignored: true
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: textField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Accessible.name: optionLabel.text
|
||||||
|
onTextChanged: {
|
||||||
|
optionModel.set(optionDelegate.index, {optionText: text})
|
||||||
|
optionModel.allValuesSetChanged()
|
||||||
|
}
|
||||||
|
placeholderText: i18n("Enter option")
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: removeOptionAction
|
||||||
|
text: i18nc("@action:button", "Remove option")
|
||||||
|
icon.name: "edit-delete-remove"
|
||||||
|
onTriggered: optionModel.remove(optionDelegate.index)
|
||||||
|
}
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: removeOptionAction.text
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Delegates.RoundedItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
horizontalPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
leftInset: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
rightInset: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
highlighted: true
|
||||||
|
|
||||||
|
icon.name: "list-add"
|
||||||
|
text: i18nc("@action:button", "Add option")
|
||||||
|
|
||||||
|
onClicked: optionModel.append({optionText: ""})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ import QtQuick.Layouts
|
|||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
|
||||||
|
import Quotient
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,24 +46,31 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.pollHandler.options
|
model: root.pollHandler.answerModel
|
||||||
delegate: FormCard.FormCheckDelegate {
|
delegate: FormCard.FormCheckDelegate {
|
||||||
|
id: answerDelegate
|
||||||
|
|
||||||
|
required property string id
|
||||||
|
required property string answerText
|
||||||
|
required property int count
|
||||||
|
required property bool localChoice
|
||||||
|
|
||||||
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: root.pollHandler.answers[root.Message.room.localMember.id] ? root.pollHandler.answers[root.Message.room.localMember.id].includes(modelData["id"]) : false
|
checked: answerDelegate.localChoice
|
||||||
onClicked: root.pollHandler.sendPollAnswer(root.eventId, modelData["id"])
|
onClicked: root.pollHandler.sendPollAnswer(root.eventId, answerDelegate.id)
|
||||||
enabled: !root.pollHandler.hasEnded
|
enabled: !root.pollHandler.hasEnded
|
||||||
text: modelData["org.matrix.msc1767.text"]
|
text: answerDelegate.answerText
|
||||||
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
trailing: Label {
|
trailing: Label {
|
||||||
visible: root.pollHandler.kind == "org.matrix.msc3381.poll.disclosed" || pollHandler.hasEnded
|
visible: root.pollHandler.kind == PollKind.Disclosed || pollHandler.hasEnded
|
||||||
Layout.preferredWidth: contentWidth
|
Layout.preferredWidth: contentWidth
|
||||||
text: root.pollHandler.counts[modelData["id"]] ?? "0"
|
text: answerDelegate.count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user