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:
James Graham
2025-03-22 16:32:08 +00:00
parent 55d68af499
commit 37d77f579a
18 changed files with 764 additions and 165 deletions

View File

@@ -121,7 +121,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
}
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()));
}
@@ -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) {
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 senderId = event->senderId();
if (eventId.isEmpty()) {

View File

@@ -129,7 +129,7 @@ Q_SIGNALS:
* Any model inheriting from MessageModel needs to emit this signal for every
* new event it adds.
*/
void newEventAdded(const Quotient::RoomEvent *event, bool isPending = false);
void newEventAdded(const Quotient::RoomEvent *event);
protected:
QPointer<NeoChatRoom> m_room;
@@ -154,5 +154,5 @@ private:
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
void createEventObjects(const Quotient::RoomEvent *event);
};

View File

@@ -58,7 +58,7 @@ void PinnedMessageModel::fill()
connect(job, &BaseJob::success, this, [this, job] {
beginInsertRows({}, m_pinnedEvents.size(), m_pinnedEvents.size());
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();
});
}

View 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"},
};
}

View 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;
};

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "timelinemessagemodel.h"
#include "events/pollevent.h"
#include "messagemodel_logging.h"
using namespace Quotient;
@@ -36,7 +37,7 @@ void TimelineMessageModel::connectNewRoom()
});
connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) {
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();
Q_EMIT newEventAdded(event);
}
@@ -58,14 +59,14 @@ void TimelineMessageModel::connectNewRoom()
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
connect(m_room, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
m_initialized = true;
Q_EMIT newEventAdded(event, true);
Q_EMIT newEventAdded(event);
beginInsertRows({}, 0, 0);
endInsertRows();
});
#else
connect(m_room, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
m_initialized = true;
Q_EMIT newEventAdded(event, true);
Q_EMIT newEventAdded(event);
beginInsertRows({}, 0, 0);
});
connect(m_room, &Room::pendingEventAdded, this, &TimelineMessageModel::endInsertRows);
@@ -111,9 +112,6 @@ void TimelineMessageModel::connectNewRoom()
const auto eventIt = m_room->findInTimeline(eventId);
if (eventIt != m_room->historyEdge()) {
Q_EMIT newEventAdded(eventIt->event());
if (eventIt->event()->is<PollStartEvent>()) {
m_room->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
}
}
refreshEventRoles(eventId, {Qt::DisplayRole});
});