Implement viewing and responding to polls
This commit is contained in:
@@ -56,6 +56,7 @@ target_link_libraries(neochat-app PRIVATE
|
||||
|
||||
if(Quotient_VERSION_MINOR GREATER 6)
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
|
||||
target_sources(neochat PRIVATE pollevent.cpp pollhandler.cpp)
|
||||
else()
|
||||
target_sources(neochat PRIVATE neochataccountregistry.cpp)
|
||||
endif()
|
||||
|
||||
@@ -149,6 +149,9 @@ void Controller::handleNotifications()
|
||||
{
|
||||
static bool initial = true;
|
||||
static QStringList oldNotifications;
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
auto job = m_connection->callApi<GetNotificationsJob>();
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
@@ -177,7 +180,18 @@ void Controller::handleNotifications()
|
||||
// The room might have been deleted (for example rejected invitation).
|
||||
auto sender = room->user(notification["event"].toObject()["sender"].toString());
|
||||
|
||||
auto body = notification["event"].toObject()["content"].toObject()["body"].toString();
|
||||
QString body;
|
||||
|
||||
if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]
|
||||
.toObject()["content"]
|
||||
.toObject()["org.matrix.msc3381.poll.start"]
|
||||
.toObject()["question"]
|
||||
.toObject()["body"]
|
||||
.toString();
|
||||
} else {
|
||||
body = notification["event"].toObject()["content"].toObject()["body"].toString();
|
||||
}
|
||||
|
||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "notificationsmanager.h"
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "publicroomlistmodel.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "roommanager.h"
|
||||
@@ -210,6 +213,9 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
|
||||
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||
#ifdef QUOTIENT_07
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include <events/simplestateevents.h>
|
||||
#include <user.h>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollevent.h"
|
||||
#endif
|
||||
#include "stickerevent.h"
|
||||
|
||||
#include <QDebug>
|
||||
@@ -496,6 +499,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
if (is<const EncryptedEvent>(evt)) {
|
||||
return DelegateType::Encrypted;
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
if (is<PollStartEvent>(evt)) {
|
||||
if (evt.isRedacted()) {
|
||||
return DelegateType::Message;
|
||||
}
|
||||
return DelegateType::Poll;
|
||||
}
|
||||
#endif
|
||||
|
||||
return DelegateType::Other;
|
||||
}
|
||||
|
||||
@@ -532,6 +544,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||
return QVariant::fromValue(e->image().originalJson);
|
||||
}
|
||||
return evt.contentJson();
|
||||
}
|
||||
|
||||
if (role == HighlightRole) {
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
State,
|
||||
Encrypted,
|
||||
ReadMarker,
|
||||
Poll,
|
||||
Other,
|
||||
};
|
||||
Q_ENUM(DelegateType);
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatuser.h"
|
||||
#include "notificationsmanager.h"
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollevent.h"
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "stickerevent.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -182,7 +186,7 @@ void NeoChatRoom::sendTypingNotification(bool isTyping)
|
||||
connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), id(), isTyping, 10000);
|
||||
}
|
||||
|
||||
const RoomMessageEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
||||
const RoomEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
||||
{
|
||||
for (auto timelineItem = messageEvents().rbegin(); timelineItem < messageEvents().rend(); timelineItem++) {
|
||||
const RoomEvent *event = timelineItem->get();
|
||||
@@ -212,6 +216,11 @@ const RoomMessageEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
||||
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
return lastEvent;
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
|
||||
return lastEvent;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -621,6 +630,11 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
||||
},
|
||||
#ifdef QUOTIENT_07
|
||||
[](const PollStartEvent &e) {
|
||||
return e.question();
|
||||
},
|
||||
#endif
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
@@ -1201,3 +1215,16 @@ bool NeoChatRoom::canEncryptRoom() const
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
||||
{
|
||||
if (!m_polls.contains(eventId)) {
|
||||
auto handler = new PollHandler(this);
|
||||
handler->setRoom(this);
|
||||
handler->setPollStartEventId(eventId);
|
||||
m_polls.insert(eventId, handler);
|
||||
}
|
||||
return m_polls[eventId];
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
#include <room.h>
|
||||
|
||||
#include <QCache>
|
||||
#include <QObject>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include <qcoro/task.h>
|
||||
|
||||
class PollHandler;
|
||||
class NeoChatUser;
|
||||
|
||||
class PushNotificationState : public QObject
|
||||
@@ -80,7 +82,7 @@ public:
|
||||
/// This function respect the showLeaveJoinEvent setting and discard
|
||||
/// other not interesting events. This function can return an empty pointer
|
||||
/// when the room is empty of RoomMessageEvent.
|
||||
[[nodiscard]] const Quotient::RoomMessageEvent *lastEvent(bool ignoreStateEvent = false) const;
|
||||
[[nodiscard]] const Quotient::RoomEvent *lastEvent(bool ignoreStateEvent = false) const;
|
||||
|
||||
/// Convenient way to get the last event but in a string format.
|
||||
///
|
||||
@@ -192,6 +194,10 @@ public:
|
||||
|
||||
bool canEncryptRoom() const;
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
||||
#endif
|
||||
|
||||
#ifndef QUOTIENT_07
|
||||
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
||||
{
|
||||
@@ -223,6 +229,9 @@ private:
|
||||
QString m_chatBoxAttachmentPath;
|
||||
QVector<Mention> m_mentions;
|
||||
QString m_savedText;
|
||||
#ifdef QUOTIENT_07
|
||||
QCache<QString, PollHandler> m_polls;
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
void countChanged();
|
||||
|
||||
38
src/pollevent.cpp
Normal file
38
src/pollevent.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "pollevent.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
PollStartEvent::PollStartEvent(const QJsonObject &obj)
|
||||
: RoomEvent(obj)
|
||||
{
|
||||
}
|
||||
|
||||
int PollStartEvent::maxSelections() const
|
||||
{
|
||||
return contentJson()["org.matrix.msc3381.poll.start"]["max_selections"].toInt();
|
||||
}
|
||||
|
||||
QString PollStartEvent::question() const
|
||||
{
|
||||
return contentJson()["org.matrix.msc3381.poll.start"]["question"]["body"].toString();
|
||||
}
|
||||
|
||||
PollResponseEvent::PollResponseEvent(const QJsonObject &obj)
|
||||
: RoomEvent(obj)
|
||||
{
|
||||
}
|
||||
|
||||
PollEndEvent::PollEndEvent(const QJsonObject &obj)
|
||||
: RoomEvent(obj)
|
||||
{
|
||||
}
|
||||
|
||||
PollResponseEvent::PollResponseEvent(const QString &pollStartEventId, QStringList responses)
|
||||
: RoomEvent(basicJson(TypeId,
|
||||
{{"org.matrix.msc3381.poll.response", QJsonObject{{"answers", QJsonArray::fromStringList(responses)}}},
|
||||
{"m.relates_to", QJsonObject{{"rel_type", "m.reference"}, {"event_id", pollStartEventId}}}}))
|
||||
{
|
||||
}
|
||||
35
src/pollevent.h
Normal file
35
src/pollevent.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/roomevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class PollStartEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollStartEvent, "org.matrix.msc3381.poll.start");
|
||||
explicit PollStartEvent(const QJsonObject &obj);
|
||||
|
||||
int maxSelections() const;
|
||||
QString question() const;
|
||||
};
|
||||
|
||||
class PollResponseEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollResponseEvent, "org.matrix.msc3381.poll.response");
|
||||
explicit PollResponseEvent(const QJsonObject &obj);
|
||||
explicit PollResponseEvent(const QString &pollStartEventId, QStringList responses);
|
||||
};
|
||||
|
||||
class PollEndEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollEndEvent, "org.matrix.msc3381.poll.end");
|
||||
explicit PollEndEvent(const QJsonObject &obj);
|
||||
};
|
||||
}
|
||||
165
src/pollhandler.cpp
Normal file
165
src/pollhandler.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "pollhandler.h"
|
||||
#include "neochatroom.h"
|
||||
#include "pollevent.h"
|
||||
#include <algorithm>
|
||||
#include <csapi/relations.h>
|
||||
#include <events/roompowerlevelsevent.h>
|
||||
#include <user.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
PollHandler::PollHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(this, &PollHandler::roomChanged, this, &PollHandler::checkLoadRelations);
|
||||
connect(this, &PollHandler::pollStartEventIdChanged, this, &PollHandler::checkLoadRelations);
|
||||
}
|
||||
|
||||
NeoChatRoom *PollHandler::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void PollHandler::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
}
|
||||
if (m_room) {
|
||||
disconnect(m_room, nullptr, this, nullptr);
|
||||
}
|
||||
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
||||
for (const auto &event : events) {
|
||||
if (event->is<PollEndEvent>()) {
|
||||
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
|
||||
auto userPl = pl->powerLevelForUser(event->senderId());
|
||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
||||
m_hasEnded = true;
|
||||
m_endedTimestamp = event->originTimestamp();
|
||||
Q_EMIT hasEndedChanged();
|
||||
}
|
||||
}
|
||||
if (event->is<PollResponseEvent>()) {
|
||||
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
||||
}
|
||||
}
|
||||
});
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
QString PollHandler::pollStartEventId() const
|
||||
{
|
||||
return m_pollStartEventId;
|
||||
}
|
||||
|
||||
void PollHandler::setPollStartEventId(const QString &eventId)
|
||||
{
|
||||
if (eventId == m_pollStartEventId) {
|
||||
return;
|
||||
}
|
||||
m_pollStartEventId = eventId;
|
||||
Q_EMIT pollStartEventIdChanged();
|
||||
}
|
||||
|
||||
void PollHandler::checkLoadRelations()
|
||||
{
|
||||
if (!m_room || m_pollStartEventId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
m_maxVotes = eventCast<const PollStartEvent>(&**m_room->findInTimeline(m_pollStartEventId))->maxSelections();
|
||||
auto job = m_room->connection()->callApi<GetRelatingEventsJob>(m_room->id(), m_pollStartEventId);
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
for (const auto &event : job->chunk()) {
|
||||
if (event->is<PollEndEvent>()) {
|
||||
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
|
||||
auto userPl = pl->powerLevelForUser(event->senderId());
|
||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
||||
m_hasEnded = true;
|
||||
m_endedTimestamp = event->originTimestamp();
|
||||
Q_EMIT hasEndedChanged();
|
||||
}
|
||||
}
|
||||
if (event->is<PollResponseEvent>()) {
|
||||
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PollHandler::handleAnswer(const QJsonObject &content, const QString &sender, QDateTime timestamp)
|
||||
{
|
||||
if (timestamp > m_answerTimestamps[sender] && (!m_hasEnded || timestamp < m_endedTimestamp)) {
|
||||
m_answerTimestamps[sender] = timestamp;
|
||||
m_answers[sender] = {};
|
||||
int i = 0;
|
||||
for (const auto &answer : content["org.matrix.msc3381.poll.response"]["answers"].toArray()) {
|
||||
auto array = m_answers[sender].toArray();
|
||||
array.insert(0, answer);
|
||||
m_answers[sender] = array;
|
||||
i++;
|
||||
if (i == m_maxVotes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto &key : m_answers.keys()) {
|
||||
if (m_answers[key].toArray().isEmpty()) {
|
||||
m_answers.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_EMIT answersChanged();
|
||||
}
|
||||
|
||||
QJsonObject PollHandler::answers() const
|
||||
{
|
||||
return m_answers;
|
||||
}
|
||||
|
||||
QJsonObject PollHandler::counts() const
|
||||
{
|
||||
QJsonObject counts;
|
||||
for (const auto &answer : m_answers) {
|
||||
for (const auto &id : answer.toArray()) {
|
||||
counts[id.toString()] = counts[id.toString()].toInt() + 1;
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
|
||||
{
|
||||
Q_ASSERT(eventId.length() > 0);
|
||||
Q_ASSERT(answerId.length() > 0);
|
||||
QStringList ownAnswers;
|
||||
for (const auto &answer : m_answers[m_room->localUser()->id()].toArray()) {
|
||||
ownAnswers += answer.toString();
|
||||
}
|
||||
if (ownAnswers.contains(answerId)) {
|
||||
ownAnswers.erase(std::remove_if(ownAnswers.begin(), ownAnswers.end(), [answerId](const auto &it) {
|
||||
return answerId == it;
|
||||
}));
|
||||
} else {
|
||||
while (ownAnswers.size() >= m_maxVotes) {
|
||||
ownAnswers.pop_front();
|
||||
}
|
||||
ownAnswers.insert(0, answerId);
|
||||
}
|
||||
|
||||
auto response = new PollResponseEvent(eventId, ownAnswers);
|
||||
handleAnswer(response->contentJson(), m_room->localUser()->id(), QDateTime::currentDateTime());
|
||||
m_room->postEvent(response);
|
||||
}
|
||||
|
||||
bool PollHandler::hasEnded() const
|
||||
{
|
||||
return m_hasEnded;
|
||||
}
|
||||
|
||||
int PollHandler::answerCount() const
|
||||
{
|
||||
return m_answers.size();
|
||||
}
|
||||
57
src/pollhandler.h
Normal file
57
src/pollhandler.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
class PollHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
Q_PROPERTY(QString pollStartEventId READ pollStartEventId WRITE setPollStartEventId NOTIFY pollStartEventIdChanged)
|
||||
Q_PROPERTY(QJsonObject answers READ answers NOTIFY answersChanged)
|
||||
Q_PROPERTY(QJsonObject counts READ counts NOTIFY answersChanged)
|
||||
Q_PROPERTY(bool hasEnded READ hasEnded NOTIFY hasEndedChanged)
|
||||
Q_PROPERTY(int answerCount READ answerCount NOTIFY answersChanged)
|
||||
|
||||
public:
|
||||
PollHandler(QObject *parent = nullptr);
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
QString pollStartEventId() const;
|
||||
void setPollStartEventId(const QString &eventId);
|
||||
|
||||
bool hasEnded() const;
|
||||
int answerCount() const;
|
||||
|
||||
void checkLoadRelations();
|
||||
|
||||
QJsonObject answers() const;
|
||||
QJsonObject counts() const;
|
||||
Q_INVOKABLE void sendPollAnswer(const QString &eventId, const QString &answerId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void pollStartEventIdChanged();
|
||||
void answersChanged();
|
||||
void hasEndedChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QString m_pollStartEventId;
|
||||
|
||||
void handleAnswer(const QJsonObject &object, const QString &sender, QDateTime timestamp);
|
||||
QMap<QString, QDateTime> m_answerTimestamps;
|
||||
QJsonObject m_answers;
|
||||
int m_maxVotes = 1;
|
||||
bool m_hasEnded = false;
|
||||
QDateTime m_endedTimestamp;
|
||||
};
|
||||
@@ -70,6 +70,11 @@ DelegateChooser {
|
||||
delegate: ReadMarkerDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageEventModel.Poll
|
||||
delegate: PollDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageEventModel.Other
|
||||
delegate: Item {}
|
||||
|
||||
52
src/qml/Component/Timeline/PollDelegate.qml
Normal file
52
src/qml/Component/Timeline/PollDelegate.qml
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: pollDelegate
|
||||
|
||||
readonly property var data: model
|
||||
property PollHandler pollHandler: currentRoom.poll(model.eventId)
|
||||
|
||||
innerObject: ColumnLayout {
|
||||
Label {
|
||||
id: questionLabel
|
||||
text: pollDelegate.data.content["org.matrix.msc3381.poll.start"]["question"]["body"]
|
||||
}
|
||||
Repeater {
|
||||
model: pollDelegate.data.content["org.matrix.msc3381.poll.start"]["answers"]
|
||||
delegate: RowLayout {
|
||||
width: pollDelegate.innerObject.width
|
||||
CheckBox {
|
||||
checked: pollDelegate.pollHandler.answers[currentRoom.localUser.id] ? pollDelegate.pollHandler.answers[currentRoom.localUser.id].includes(modelData["id"]) : false
|
||||
onClicked: pollDelegate.pollHandler.sendPollAnswer(pollDelegate.data.eventId, modelData["id"])
|
||||
enabled: !pollDelegate.pollHandler.hasEnded
|
||||
}
|
||||
Label {
|
||||
text: modelData["org.matrix.msc1767.text"]
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
visible: pollDelegate.data.content["org.matrix.msc3381.poll.start"]["kind"] == "org.matrix.msc3381.poll.disclosed" || pollHandler.hasEnded
|
||||
Layout.preferredWidth: contentWidth
|
||||
text: pollDelegate.pollHandler.counts[modelData["id"]] ?? "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Label {
|
||||
visible: pollDelegate.data.content["org.matrix.msc3381.poll.start"]["kind"] == "org.matrix.msc3381.poll.disclosed" || pollDelegate.pollHandler.hasEnded
|
||||
text: i18np("Based on votes by %1 user", "Based on votes by %1 users", pollDelegate.pollHandler.answerCount) + (pollDelegate.pollHandler.hasEnded ? (" " + i18nc("as in 'this vote has ended'", "(Ended)")) : "")
|
||||
font.pointSize: questionLabel.font.pointSize * 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@
|
||||
<file alias="EventDelegate.qml">qml/Component/Timeline/EventDelegate.qml</file>
|
||||
<file alias="MessageDelegate.qml">qml/Component/Timeline/MessageDelegate.qml</file>
|
||||
<file alias="ReadMarkerDelegate.qml">qml/Component/Timeline/ReadMarkerDelegate.qml</file>
|
||||
<file alias="PollDelegate.qml">qml/Component/Timeline/PollDelegate.qml</file>
|
||||
<file alias="MimeComponent.qml">qml/Component/Timeline/MimeComponent.qml</file>
|
||||
<file alias="LoginStep.qml">qml/Component/Login/LoginStep.qml</file>
|
||||
<file alias="Login.qml">qml/Component/Login/Login.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user