Compare commits

..

2 Commits

Author SHA1 Message Date
Carlos De Maine
bf754711e3 skip windows as one test is failing 2025-07-06 09:34:01 +10:00
Carlos De Maine
32d0f846ee Testing for VM based CI 2025-07-06 09:15:21 +10:00
81 changed files with 11430 additions and 14031 deletions

View File

@@ -3,19 +3,20 @@
include: include:
- project: sysadmin/ci-utilities - project: sysadmin/ci-utilities
ref: work/switch-vm-ci
file: file:
- /gitlab-templates/reuse-lint.yml #- /gitlab-templates/reuse-lint.yml
- /gitlab-templates/json-validation.yml #- /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml #- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml #- /gitlab-templates/yaml-lint.yml
- /gitlab-templates/android-qt6.yml #- /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml - /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/windows-qt6.yml #- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml #- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml - /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml - /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml #- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml #- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml - /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml - /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -130,8 +130,7 @@ void EventHandlerTest::timeString()
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat)); QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true), QCOMPARE(EventHandler::timeString(room, event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat)); format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(u"hh:mm"_s));
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson()); const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1); QCOMPARE(room->pendingEvents().size(), 1);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -106,7 +106,7 @@ KirigamiComponents.ConvergentContextMenu {
} }
QQC2.Action { QQC2.Action {
text: i18n("Logout") text: i18n("Logout")
icon.name: "im-kick-user" icon.name: "im-kick-user"
onTriggered: confirmLogoutDialogComponent.createObject(root).open() onTriggered: confirmLogoutDialogComponent.createObject(root).open()
} }

View File

@@ -52,15 +52,6 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.currentRoom && root.currentRoom.canonicalAlias
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
}
Kirigami.Heading { Kirigami.Heading {
text: root.currentRoom.displayName text: root.currentRoom.displayName
@@ -79,14 +70,7 @@ ColumnLayout {
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
Kirigami.Heading { Kirigami.Heading {
text: root.invitingMember.displayName text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
QQC2.Label {
text: root.invitingMember.id
color: Kirigami.Theme.disabledTextColor
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
@@ -175,7 +159,7 @@ ColumnLayout {
QQC2.Label { QQC2.Label {
color: Kirigami.Theme.disabledTextColor color: Kirigami.Theme.disabledTextColor
text: xi18nc("@info:label Ensure you are referring to the same translation used for that settings page", "You can reject invitations from unknown users under the <interface>Security & Safety</interface> settings.") text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
// + 5 to prevent it from wrapping unnecessarily // + 5 to prevent it from wrapping unnecessarily

View File

@@ -47,7 +47,7 @@ Kirigami.Page {
icon.name: "document-edit" icon.name: "document-edit"
visible: root.allowEdit visible: root.allowEdit
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create" enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), { onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog.qml"), {
room: root.room, room: root.room,
type: root.type, type: root.type,
stateKey: root.stateKey, stateKey: root.stateKey,

View File

@@ -236,18 +236,11 @@ void RoomManager::resolveResource(Uri uri, const QString &action)
} }
} }
void RoomManager::maximizeMedia(const QString &eventId) void RoomManager::maximizeMedia(int index)
{ {
if (eventId.isEmpty()) { if (index < -1 || index > m_mediaMessageFilterModel->rowCount()) {
qWarning() << "Tried to open media for empty event id";
return; return;
} }
const auto index = m_mediaMessageFilterModel->getRowForEventId(eventId);
if (index == -1) {
return;
}
Q_EMIT showMaximizedMedia(index); Q_EMIT showMaximizedMedia(index);
} }
@@ -271,10 +264,6 @@ void RoomManager::viewEventSource(const QString &eventId)
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink) void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink)
{ {
if (eventId.isEmpty()) {
qWarning() << "Tried to open event menu with empty event id";
return;
}
const auto &event = **room->findInTimeline(eventId); const auto &event = **room->findInTimeline(eventId);
if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) { if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) {
@@ -404,9 +393,7 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
// If no one gives us a homeserver suggestion, try the server specified in the alias/id. // If no one gives us a homeserver suggestion, try the server specified in the alias/id.
// Otherwise joining a remote room not on our homeserver will fail. // Otherwise joining a remote room not on our homeserver will fail.
// This is a hack and we're not supposed to do it. With room ids not containing the server going forward, it won't work anymore for new room versions. if (vias.empty()) {
// FIXME: Let's keep it around anyway for now, remove it at some point, though
if (vias.empty() && roomAliasOrId.contains(':'_L1)) {
vias.append(roomAliasOrId.mid(roomAliasOrId.lastIndexOf(':'_L1) + 1)); vias.append(roomAliasOrId.mid(roomAliasOrId.lastIndexOf(':'_L1) + 1));
} }

View File

@@ -212,8 +212,12 @@ public:
/** /**
* @brief Show a media item maximized. * @brief Show a media item maximized.
*
* @param index the index to open the maximize delegate model at. This is the
* index in the MediaMessageFilterModel owned by this RoomManager. A value
* of -1 opens a the default item.
*/ */
Q_INVOKABLE void maximizeMedia(const QString &eventId); Q_INVOKABLE void maximizeMedia(int index);
Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language); Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language);

View File

@@ -70,23 +70,13 @@ public:
* *
* @param event the event to return a type for. * @param event the event to return a type for.
* *
* @param isInReply whether this event is to be treated like a replied-to event (i.e., a basic text fallback should be shown if no other type is used)
*
* @sa Type * @sa Type
*/ */
static Type typeForEvent(const Quotient::RoomEvent &event, bool isInReply = false) static Type typeForEvent(const Quotient::RoomEvent &event)
{ {
using namespace Quotient; using namespace Quotient;
if (event.isRedacted()) {
return MessageComponentType::Text;
}
if (const auto e = eventCast<const RoomMessageEvent>(&event)) { if (const auto e = eventCast<const RoomMessageEvent>(&event)) {
if (e->rawMsgtype() == u"m.key.verification.request"_s) {
return MessageComponentType::Verification;
}
switch (e->msgtype()) { switch (e->msgtype()) {
case MessageEventType::Emote: case MessageEventType::Emote:
return MessageComponentType::Text; return MessageComponentType::Text;
@@ -113,8 +103,7 @@ public:
if (event.matrixType() == u"org.matrix.msc3672.beacon_info"_s) { if (event.matrixType() == u"org.matrix.msc3672.beacon_info"_s) {
return MessageComponentType::LiveLocation; return MessageComponentType::LiveLocation;
} }
// In the (unlikely) case that this is a reply to a state event, we do want to show something return MessageComponentType::Other;
return isInReply ? MessageComponentType::Text : MessageComponentType::Other;
} }
if (is<const EncryptedEvent>(event)) { if (is<const EncryptedEvent>(event)) {
return MessageComponentType::Encrypted; return MessageComponentType::Encrypted;
@@ -127,8 +116,7 @@ public:
return MessageComponentType::Poll; return MessageComponentType::Poll;
} }
// In the (unlikely) case that this is a reply to an unusual event, we do want to show something return MessageComponentType::Other;
return isInReply ? MessageComponentType::Text : MessageComponentType::Other;
} }
/** /**

View File

@@ -448,12 +448,6 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
[](const PollStartEvent &e) { [](const PollStartEvent &e) {
return e.question(); return e.question();
}, },
[](const EncryptedEvent &) {
return i18nc("@info In room list", "Encrypted event");
},
[](const ReactionEvent &e) {
return i18nc("[user] reacted with <emoji>", "reacted with %1", e.key());
},
i18n("Unknown event")); i18n("Unknown event"));
} }

View File

@@ -31,7 +31,13 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room.")); Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
room->forget(); room->forget();
} else { } else {
// FIXME: re-add sanity check for roomId/alias QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto leaving = dynamic_cast<NeoChatRoom *>(room->connection()->room(text)); auto leaving = dynamic_cast<NeoChatRoom *>(room->connection()->room(text));
if (!leaving) { if (!leaving) {
leaving = dynamic_cast<NeoChatRoom *>(room->connection()->roomByAlias(text)); leaving = dynamic_cast<NeoChatRoom *>(room->connection()->roomByAlias(text));
@@ -211,7 +217,13 @@ QList<ActionsModel::Action> actions{
Action{ Action{
u"join"_s, u"join"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) { [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
// FIXME: re-add sanity check for roomId/alias QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text); auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
if (targetRoom) { if (targetRoom) {
ActionsModel::instance().resolveResource(targetRoom->id()); ActionsModel::instance().resolveResource(targetRoom->id());
@@ -230,18 +242,25 @@ QList<ActionsModel::Action> actions{
[](const QString &text, NeoChatRoom *room, ChatBarCache *) { [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto parts = text.split(u" "_s); auto parts = text.split(u" "_s);
QString roomName = parts[0]; QString roomName = parts[0];
// FIXME: re-add sanity check for roomId/alias QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
if (const auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text)) { auto regexMatch = roomRegex.match(roomName);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
if (targetRoom) {
ActionsModel::instance().resolveResource(targetRoom->id()); ActionsModel::instance().resolveResource(targetRoom->id());
return QString(); return QString();
} }
Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text)); Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
auto connection = dynamic_cast<NeoChatConnection *>(room->connection()); auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
const auto knownServer = roomName.contains(":"_L1) ? QStringList{roomName.mid(roomName.indexOf(":"_L1) + 1)} : QStringList(); const auto knownServer = roomName.mid(roomName.indexOf(":"_L1) + 1);
if (parts.length() >= 2) { if (parts.length() >= 2) {
ActionsModel::instance().knockRoom(connection, roomName, parts[1], knownServer); ActionsModel::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
} else { } else {
ActionsModel::instance().knockRoom(connection, roomName, QString(), knownServer); ActionsModel::instance().knockRoom(connection, roomName, QString(), QStringList{knownServer});
} }
return QString(); return QString();
}, },
@@ -252,7 +271,13 @@ QList<ActionsModel::Action> actions{
Action{ Action{
u"j"_s, u"j"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) { [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
// FIXME: re-add sanity check for roomId/alias QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) { if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text)); Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
return QString(); return QString();

View File

@@ -100,10 +100,6 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
return plEvent->powerLevelForUser(memberId); return plEvent->powerLevelForUser(memberId);
} }
if (role == PowerLevelStringRole) { if (role == PowerLevelStringRole) {
if (m_currentRoom->roomCreatorHasUltimatePowerLevel() && m_currentRoom->isCreator(memberId)) {
return i18nc("@info the person that created this room", "Creator");
}
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>(); auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
// User might not in the room yet, in this case pl can be nullptr. // User might not in the room yet, in this case pl can be nullptr.
// e.g. When invited but user not accepted or denied the invitation. // e.g. When invited but user not accepted or denied the invitation.

View File

@@ -359,14 +359,9 @@ const RoomEvent *NeoChatRoom::lastEvent(std::function<bool(const RoomEvent *)> f
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) { if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
return lastEvent; return lastEvent;
} }
if (auto lastEvent = eventCast<const PollStartEvent>(event)) { if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
return lastEvent; return lastEvent;
} }
if (auto lastEvent = eventCast<const EncryptedEvent>(event)) {
return lastEvent;
}
} }
if (m_cachedEvent != nullptr) { if (m_cachedEvent != nullptr) {
@@ -446,19 +441,20 @@ void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*af
} }
} }
QDateTime NeoChatRoom::lastActiveTime() const QDateTime NeoChatRoom::lastActiveTime()
{ {
// Find the last relevant event: if (timelineSize() == 0) {
if (const auto event = lastEvent(m_hiddenFilter)) { if (m_cachedEvent != nullptr) {
return m_cachedEvent->originTimestamp();
}
return QDateTime();
}
if (auto event = lastEvent()) {
return event->originTimestamp(); return event->originTimestamp();
} }
// If nothing is loaded yet, and there is no cached event: // no message found, take last event
if (timelineSize() == 0) {
return {};
}
// No message found, take last event:
return messageEvents().rbegin()->get()->originTimestamp(); return messageEvents().rbegin()->get()->originTimestamp();
} }
@@ -536,9 +532,6 @@ bool NeoChatRoom::containsUser(const QString &userID) const
bool NeoChatRoom::canSendEvent(const QString &eventType) const bool NeoChatRoom::canSendEvent(const QString &eventType) const
{ {
if (roomCreatorHasUltimatePowerLevel() && isCreator(localMember().id())) {
return true;
}
auto plEvent = currentState().get<RoomPowerLevelsEvent>(); auto plEvent = currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) { if (!plEvent) {
return false; return false;
@@ -551,9 +544,6 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
bool NeoChatRoom::canSendState(const QString &eventType) const bool NeoChatRoom::canSendState(const QString &eventType) const
{ {
if (roomCreatorHasUltimatePowerLevel() && isCreator(localMember().id())) {
return true;
}
auto plEvent = currentState().get<RoomPowerLevelsEvent>(); auto plEvent = currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) { if (!plEvent) {
return false; return false;
@@ -1680,14 +1670,8 @@ void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, con
NeochatRoomMember *NeoChatRoom::qmlSafeMember(const QString &memberId) NeochatRoomMember *NeoChatRoom::qmlSafeMember(const QString &memberId)
{ {
if (memberId.isEmpty()) {
return nullptr;
}
if (!m_memberObjects.contains(memberId)) { if (!m_memberObjects.contains(memberId)) {
auto member = m_memberObjects.emplace(memberId, std::make_unique<NeochatRoomMember>(this, memberId)).first->second.get(); return m_memberObjects.emplace(memberId, std::make_unique<NeochatRoomMember>(this, memberId)).first->second.get();
QQmlEngine::setObjectOwnership(member, QQmlEngine::CppOwnership);
return member;
} }
return m_memberObjects[memberId].get(); return m_memberObjects[memberId].get();
@@ -1747,20 +1731,4 @@ void NeoChatRoom::setHiddenFilter(std::function<bool(const Quotient::RoomEvent *
NeoChatRoom::m_hiddenFilter = hiddenFilter; NeoChatRoom::m_hiddenFilter = hiddenFilter;
} }
bool NeoChatRoom::roomCreatorHasUltimatePowerLevel() const
{
bool ok = false;
auto version = this->version().toInt(&ok);
// This is terrible. For non-numeric room versions, I don't think there's a way of knowing whether they're pre- or post hydra.
// We just assume they are. Shouldn't matter for normal users anyway.
return !ok || version > 11;
}
bool NeoChatRoom::isCreator(const QString &userId) const
{
auto createEvent = currentState().get<RoomCreateEvent>();
return roomCreatorHasUltimatePowerLevel() && createEvent
&& (createEvent->senderId() == userId || createEvent->contentPart<QStringList>(u"additional_creators"_s).contains(userId));
}
#include "moc_neochatroom.cpp" #include "moc_neochatroom.cpp"

View File

@@ -208,7 +208,7 @@ public:
bool visible() const; bool visible() const;
void setVisible(bool visible); void setVisible(bool visible);
[[nodiscard]] QDateTime lastActiveTime() const; [[nodiscard]] QDateTime lastActiveTime();
/** /**
* @brief Get the last interesting event. * @brief Get the last interesting event.
@@ -589,18 +589,6 @@ public:
static void setHiddenFilter(std::function<bool(const Quotient::RoomEvent *)> hiddenFilter); static void setHiddenFilter(std::function<bool(const Quotient::RoomEvent *)> hiddenFilter);
/**
* @brief Whether this room has a room version where the creator is treated as having an ultimate power level
*
* For unusual room versions, this information might be wrong.
*/
bool roomCreatorHasUltimatePowerLevel() const;
/**
* @brief Whether this user is considered a creator of this room. Only applies to post-v12 rooms.
*/
bool isCreator(const QString &userId) const;
private: private:
bool m_visible = false; bool m_visible = false;

View File

@@ -570,9 +570,8 @@ QVariantMap TextHandler::getAttributes(const QString &tag, const QString &tagStr
QList<MessageComponent> QList<MessageComponent>
TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isEdited) TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isEdited)
{ {
if (string.trimmed().isEmpty() && event->is<Quotient::RoomMessageEvent>() if (string.isEmpty()) {
&& !eventCast<const Quotient::RoomMessageEvent>(event)->has<Quotient::EventContent::FileContentBase>()) { return {};
return {MessageComponent{MessageComponentType::Text, i18n("<i>This event does not have any content.</i>"), {}}};
} }
// Strip mx-reply if present. // Strip mx-reply if present.
@@ -591,7 +590,7 @@ TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const Ne
string = string.trimmed(); string = string.trimmed();
if (event != nullptr && room != nullptr) { if (event != nullptr && room != nullptr) {
if (auto e = eventCast<const Quotient::RoomMessageEvent>(event); e && e->msgtype() == Quotient::MessageEventType::Emote && components.size() == 1) { if (auto e = eventCast<const Quotient::RoomMessageEvent>(event); e->msgtype() == Quotient::MessageEventType::Emote && components.size() == 1) {
if (components[0].type == MessageComponentType::Text) { if (components[0].type == MessageComponentType::Text) {
components[0].content = emoteString(room, event) + components[0].content; components[0].content = emoteString(room, event) + components[0].content;
} else { } else {

View File

@@ -158,7 +158,12 @@ Item {
} }
root.Message.timeline.interactive = false; root.Message.timeline.interactive = false;
if (!root.mediaInfo.isSticker) { if (!root.mediaInfo.isSticker) {
RoomManager.maximizeMedia(root.eventId); // We need to make sure the index is that of the MediaMessageFilterModel.
if (root.Message.timeline.model instanceof MessageFilterModel) {
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.Message.index));
} else {
RoomManager.maximizeMedia(root.Message.index);
}
} }
} }
} }

View File

@@ -65,7 +65,7 @@ RowLayout {
id: contentRepeater id: contentRepeater
model: root.replyContentModel model: root.replyContentModel
delegate: ReplyMessageComponentChooser { delegate: ReplyMessageComponentChooser {
onReplyClicked: RoomManager.goToEvent(root.replyEventId) onReplyClicked: root.Message.timeline.goToEvent(root.replyEventId)
} }
} }
} }
@@ -74,7 +74,7 @@ RowLayout {
} }
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onTapped: RoomManager.goToEvent(root.replyEventId) onTapped: root.Message.timeline.goToEvent(root.replyEventId)
} }
QtObject { QtObject {
id: _private id: _private

View File

@@ -385,7 +385,12 @@ Video {
onTriggered: { onTriggered: {
root.Message.timeline.interactive = false; root.Message.timeline.interactive = false;
root.pause(); root.pause();
RoomManager.maximizeMedia(root.eventId); // We need to make sure the index is that of the MediaMessageFilterModel.
if (root.Message.timeline.model instanceof MessageFilterModel) {
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.Message.index));
} else {
RoomManager.maximizeMedia(root.Message.index);
}
} }
} }
} }

View File

@@ -29,6 +29,7 @@
#include "chatbarcache.h" #include "chatbarcache.h"
#include "contentprovider.h" #include "contentprovider.h"
#include "filetype.h" #include "filetype.h"
#include "linkpreviewer.h"
#include "models/reactionmodel.h" #include "models/reactionmodel.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
@@ -56,6 +57,7 @@ void MessageContentModel::initializeModel()
connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() { connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() {
if (m_room != nullptr && m_currentState == Unknown) { if (m_room != nullptr && m_currentState == Unknown) {
initializeEvent(); initializeEvent();
updateReplyModel();
resetModel(); resetModel();
} }
}); });
@@ -69,6 +71,7 @@ void MessageContentModel::initializeModel()
connect(m_room, &NeoChatRoom::pendingEventMerged, this, [this]() { connect(m_room, &NeoChatRoom::pendingEventMerged, this, [this]() {
if (m_room != nullptr && m_currentState == Pending) { if (m_room != nullptr && m_currentState == Pending) {
initializeEvent(); initializeEvent();
updateReplyModel();
resetModel(); resetModel();
} }
}); });
@@ -77,6 +80,7 @@ void MessageContentModel::initializeModel()
for (int i = fromIndex; i <= toIndex; i++) { for (int i = fromIndex; i <= toIndex; i++) {
if (m_room->findInTimeline(i)->event()->id() == m_eventId) { if (m_room->findInTimeline(i)->event()->id() == m_eventId) {
initializeEvent(); initializeEvent();
updateReplyModel();
resetModel(); resetModel();
} }
} }
@@ -92,26 +96,22 @@ void MessageContentModel::initializeModel()
}); });
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) { connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
forEachComponentOfType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video}, updateFileInfo();
m_fileInfoFunction);
} }
}); });
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) { connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
forEachComponentOfType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video}, updateFileInfo();
m_fileInfoFunction);
} }
}); });
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) { connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
if (m_room != nullptr && eventId == m_eventId) { if (m_room != nullptr && eventId == m_eventId) {
forEachComponentOfType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video}, updateFileInfo();
m_fileInfoFunction);
} }
}); });
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId, const QString &errorMessage) { connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId, const QString &errorMessage) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
forEachComponentOfType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video}, updateFileInfo();
m_fileInfoFunction);
if (errorMessage.isEmpty()) { if (errorMessage.isEmpty()) {
Q_EMIT m_room->showMessage(MessageType::Error, i18nc("@info", "Failed to download file.")); Q_EMIT m_room->showMessage(MessageType::Error, i18nc("@info", "Failed to download file."));
} else { } else {
@@ -149,6 +149,7 @@ void MessageContentModel::initializeModel()
}); });
connect(this, &MessageContentModel::threadsEnabledChanged, this, [this]() { connect(this, &MessageContentModel::threadsEnabledChanged, this, [this]() {
updateReplyModel();
resetModel(); resetModel();
}); });
connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) { connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) {
@@ -158,9 +159,23 @@ void MessageContentModel::initializeModel()
}); });
initializeEvent(); initializeEvent();
if (m_currentState == Available || m_currentState == Pending) {
updateReplyModel();
}
resetModel(); resetModel();
} }
void MessageContentModel::updateFileInfo()
{
for (auto it = m_components.cbegin(); it != m_components.cend(); it++) {
const auto currentIndex = it - m_components.cbegin();
if (m_components.at(currentIndex).type == MessageComponentType::File || m_components.at(currentIndex).type == MessageComponentType::Audio
|| m_components.at(currentIndex).type == MessageComponentType::Image || m_components.at(currentIndex).type == MessageComponentType::Video) {
Q_EMIT dataChanged(index(currentIndex), index(currentIndex), {FileTransferInfoRole});
}
}
}
void MessageContentModel::initializeEvent() void MessageContentModel::initializeEvent()
{ {
if (m_currentState == UnAvailable) { if (m_currentState == UnAvailable) {
@@ -188,6 +203,7 @@ void MessageContentModel::getEvent()
if (m_room != nullptr) { if (m_room != nullptr) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
initializeEvent(); initializeEvent();
updateReplyModel();
resetModel(); resetModel();
return true; return true;
} }
@@ -411,38 +427,6 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
return roles; return roles;
} }
bool MessageContentModel::hasComponentType(MessageComponentType::Type type)
{
return std::find_if(m_components.cbegin(),
m_components.cend(),
[type](const MessageComponent &component) {
return component.type == type;
})
!= m_components.cend();
}
void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type,
std::function<MessageContentModel::ComponentIt(MessageContentModel::ComponentIt)> function)
{
auto it = m_components.begin();
while ((it = std::find_if(it,
m_components.end(),
[type](const MessageComponent &component) {
return component.type == type;
}))
!= m_components.end()) {
it = function(it);
}
}
void MessageContentModel::forEachComponentOfType(QList<MessageComponentType::Type> types,
std::function<MessageContentModel::ComponentIt(MessageContentModel::ComponentIt)> function)
{
for (const auto &type : types) {
forEachComponentOfType(type, function);
}
}
void MessageContentModel::resetModel() void MessageContentModel::resetModel()
{ {
beginResetModel(); beginResetModel();
@@ -466,11 +450,6 @@ void MessageContentModel::resetModel()
m_components += messageContentComponents(); m_components += messageContentComponents();
endResetModel(); endResetModel();
if (m_room->urlPreviewEnabled()) {
forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction);
}
updateReplyModel();
updateReactionModel(); updateReactionModel();
} }
@@ -489,11 +468,6 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
m_components += newComponents; m_components += newComponents;
endInsertRows(); endInsertRows();
if (m_room->urlPreviewEnabled()) {
forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction);
}
updateReplyModel();
updateReactionModel(); updateReactionModel();
} }
@@ -506,13 +480,31 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
QList<MessageComponent> newComponents; QList<MessageComponent> newComponents;
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == u"m.key.verification.request"_s) {
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
return newComponents;
}
if (event.first->isRedacted()) {
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
return newComponents;
}
if (m_replyModel != nullptr) {
newComponents += MessageComponent{MessageComponentType::Reply, QString(), {}};
}
if (isEditing) { if (isEditing) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
} else { } else {
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first, m_isReply))); newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
}
if (m_room->urlPreviewEnabled()) {
newComponents = addLinkPreviews(newComponents);
} }
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) #if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
if (m_threadsEnabled && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id())) if (m_threadsEnabled && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) { && roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
@@ -548,8 +540,7 @@ void MessageContentModel::updateReplyModel()
} }
if (!roomMessageEvent->isReply(m_threadsEnabled) || (roomMessageEvent->isThreaded() && m_threadsEnabled)) { if (!roomMessageEvent->isReply(m_threadsEnabled) || (roomMessageEvent->isThreaded() && m_threadsEnabled)) {
if (m_replyModel) { if (m_replyModel) {
m_replyModel->disconnect(this); delete m_replyModel;
m_replyModel->deleteLater();
} }
return; return;
} }
@@ -563,24 +554,6 @@ void MessageContentModel::updateReplyModel()
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() { connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole}); Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
}); });
bool hasModel = hasComponentType(MessageComponentType::Reply);
if (m_replyModel && !hasModel) {
int insertRow = 0;
if (m_components.first().type == MessageComponentType::Author) {
insertRow = 1;
}
beginInsertRows({}, insertRow, insertRow);
m_components.insert(insertRow, MessageComponent{MessageComponentType::Reply, QString(), {}});
} else if (!m_replyModel && hasModel) {
int removeRow = 0;
if (m_components.first().type == MessageComponentType::Author) {
removeRow = 1;
}
beginRemoveRows({}, removeRow, removeRow);
m_components.removeAt(removeRow);
endRemoveRows();
}
} }
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type) QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
@@ -591,26 +564,22 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
} }
switch (type) { switch (type) {
case MessageComponentType::Verification: {
return {MessageComponent{MessageComponentType::Verification, QString(), {}}};
}
case MessageComponentType::Text: { case MessageComponentType::Text: {
if (const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent),
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
} else {
return TextHandler().textComponents(EventHandler::plainBody(m_room, event.first), Qt::TextFormat::PlainText, m_room, event.first, false);
}
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first); const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent), auto body = EventHandler::rawMessageBody(*roomMessageEvent);
EventHandler::messageBodyInputFormat(*roomMessageEvent), if (body.trimmed().isEmpty()) {
m_room, return TextHandler().textComponents(i18n("<i>This event does not have any content.</i>"),
roomMessageEvent, Qt::TextFormat::RichText,
roomMessageEvent->isReplaced()); m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
} else {
return TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
}
} }
case MessageComponentType::File: { case MessageComponentType::File: {
QList<MessageComponent> components; QList<MessageComponent> components;
@@ -701,20 +670,42 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
} }
if (linkPreviewer->loaded()) { if (linkPreviewer->loaded()) {
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_L1, link}}}; return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_L1, link}}};
} } else {
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() { connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link); const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
if (linkPreviewer != nullptr && linkPreviewer->loaded()) { if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
forEachComponentOfType(MessageComponentType::LinkPreviewLoad, [this, link](ComponentIt it) { for (auto it = m_components.begin(); it != m_components.end(); it++) {
if (it->attributes["link"_L1].toUrl() == link) { if (it->attributes["link"_L1].toUrl() == link) {
it->type = MessageComponentType::LinkPreview; it->type = MessageComponentType::LinkPreview;
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole}); Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole});
}
} }
return it; }
}); });
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}};
}
}
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)
{
int i = 0;
while (i < inputComponents.size()) {
const auto component = inputComponents.at(i);
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
const auto links = LinkPreviewer::linkPreviews(component.content);
for (qsizetype j = 0; j < links.size(); ++j) {
const auto linkPreview = linkPreviewComponent(links[j]);
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
inputComponents.insert(i + j + 1, linkPreview);
}
};
}
} }
}); i++;
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}}; }
return inputComponents;
} }
void MessageContentModel::closeLinkPreview(int row) void MessageContentModel::closeLinkPreview(int row)

View File

@@ -9,7 +9,6 @@
#include <Quotient/events/roomevent.h> #include <Quotient/events/roomevent.h>
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "linkpreviewer.h"
#include "messagecomponent.h" #include "messagecomponent.h"
#include "models/itinerarymodel.h" #include "models/itinerarymodel.h"
#include "models/reactionmodel.h" #include "models/reactionmodel.h"
@@ -133,35 +132,9 @@ private:
void initializeModel(); void initializeModel();
void initializeEvent(); void initializeEvent();
void getEvent(); void getEvent();
void updateFileInfo();
using ComponentIt = QList<MessageComponent>::iterator;
QList<MessageComponent> m_components; QList<MessageComponent> m_components;
bool hasComponentType(MessageComponentType::Type type);
void forEachComponentOfType(MessageComponentType::Type type, std::function<ComponentIt(ComponentIt)> function);
void forEachComponentOfType(QList<MessageComponentType::Type> types, std::function<ComponentIt(ComponentIt)> function);
std::function<ComponentIt(const ComponentIt &)> m_fileInfoFunction = [this](ComponentIt it) {
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {MessageContentModel::FileTransferInfoRole});
return ++it;
};
std::function<ComponentIt(const ComponentIt &)> m_linkPreviewFunction = [this](ComponentIt it) {
bool previewAdded = false;
if (LinkPreviewer::hasPreviewableLinks(it->content)) {
const auto links = LinkPreviewer::linkPreviews(it->content);
for (qsizetype j = 0; j < links.size(); ++j) {
const auto linkPreview = linkPreviewComponent(links[j]);
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
beginInsertRows({}, std::distance(m_components.begin(), it) + j + 1, std::distance(m_components.begin(), it) + j + 1);
it = m_components.insert(it + j + 1, linkPreview);
previewAdded = true;
endInsertRows();
}
};
}
return previewAdded ? it : ++it;
};
void resetModel(); void resetModel();
void resetContent(bool isEditing = false, bool isThreading = false); void resetContent(bool isEditing = false, bool isThreading = false);
QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false); QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false);
@@ -174,6 +147,7 @@ private:
QList<MessageComponent> componentsForType(MessageComponentType::Type type); QList<MessageComponent> componentsForType(MessageComponentType::Type type);
MessageComponent linkPreviewComponent(const QUrl &link); MessageComponent linkPreviewComponent(const QUrl &link);
QList<MessageComponent> addLinkPreviews(QList<MessageComponent> inputComponents);
QList<QUrl> m_removedLinkPreviews; QList<QUrl> m_removedLinkPreviews;

View File

@@ -153,7 +153,7 @@ QQC2.ScrollView {
Delegates.RoundedItemDelegate { Delegates.RoundedItemDelegate {
id: leaveButton id: leaveButton
icon.name: "arrow-left-symbolic" icon.name: "arrow-left-symbolic"
text: root.room.isSpace ? i18nc("@action:button", "Leave this space") : i18nc("@action:button", "Leave this room") text: root.room.isSpace ? i18nc("@action:button", "Leave this space") : i18nc("@action:button", "Leave this room")
activeFocusOnTab: true activeFocusOnTab: true
Layout.fillWidth: true Layout.fillWidth: true

View File

@@ -58,7 +58,7 @@ KirigamiComponents.ConvergentContextMenu {
icon.name: "notifications" icon.name: "notifications"
Kirigami.Action { Kirigami.Action {
text: i18nc("@action:inmenu Notification 'Default Settings'", "Default Settings") text: i18n("Follow Global Setting")
icon.name: "globe" icon.name: "globe"
checkable: true checkable: true
autoExclusive: true autoExclusive: true
@@ -152,7 +152,7 @@ KirigamiComponents.ConvergentContextMenu {
} }
QQC2.Action { QQC2.Action {
text: i18n("Leave Room") text: i18n("Leave Room")
icon.name: "go-previous" icon.name: "go-previous"
onTriggered: { onTriggered: {
Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, { Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, {

View File

@@ -96,10 +96,7 @@ Kirigami.Page {
function onCurrentSpaceChanged() { function onCurrentSpaceChanged() {
treeView.expandRecursively(); treeView.expandRecursively();
} }
}
Connections {
target: RoomManager.sortFilterRoomTreeModel
function onCurrentRoomChanged() { function onCurrentRoomChanged() {
treeView.positionViewAtIndex(RoomManager.sortFilterRoomTreeModel.currentRoomIndex(), TableView.AlignVCenter) treeView.positionViewAtIndex(RoomManager.sortFilterRoomTreeModel.currentRoomIndex(), TableView.AlignVCenter)
} }

View File

@@ -70,10 +70,8 @@ KirigamiComponents.ConvergentContextMenu {
} }
QQC2.Action { QQC2.Action {
text: i18nc("'Space' is a matrix space", "Leave Space") text: i18nc("'Space' is a matrix space", "Leave Space")
icon.name: "go-previous" icon.name: "go-previous"
onTriggered: Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, { onTriggered: root.room.forget()
room: root.room
}).open();
} }
} }

View File

@@ -134,7 +134,6 @@ void SortFilterRoomTreeModel::setActiveSpaceId(const QString &spaceId)
void SortFilterRoomTreeModel::setCurrentRoom(NeoChatRoom *room) void SortFilterRoomTreeModel::setCurrentRoom(NeoChatRoom *room)
{ {
m_currentRoom = room; m_currentRoom = room;
Q_EMIT currentRoomChanged();
} }
SortFilterRoomTreeModel::Mode SortFilterRoomTreeModel::mode() const SortFilterRoomTreeModel::Mode SortFilterRoomTreeModel::mode() const

View File

@@ -104,7 +104,6 @@ Q_SIGNALS:
void filterTextChanged(); void filterTextChanged();
void activeSpaceIdChanged(); void activeSpaceIdChanged();
void modeChanged(); void modeChanged();
void currentRoomChanged();
private: private:
Mode m_mode = All; Mode m_mode = All;

View File

@@ -252,7 +252,7 @@ FormCard.FormCardPage {
FormCard.FormCard { FormCard.FormCard {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
id: deactivateAccountButton id: deactivateAccountButton
text: i18nc("@action:button", "Deactivate Account") text: i18n("Deactivate Account")
icon.name: "trash-empty-symbolic" icon.name: "trash-empty-symbolic"
onClicked: { onClicked: {
const component = Qt.createComponent('org.kde.neochat', 'ConfirmDeactivateAccountDialog'); const component = Qt.createComponent('org.kde.neochat', 'ConfirmDeactivateAccountDialog');

View File

@@ -85,7 +85,7 @@ FormCard.FormCardPage {
} }
QQC2.ToolButton { QQC2.ToolButton {
text: i18n("Logout") text: i18n("Logout")
icon.name: "im-kick-user" icon.name: "im-kick-user"
onClicked: confirmLogoutDialogComponent.createObject(root.QQC2.Overlay.overlay).open() onClicked: confirmLogoutDialogComponent.createObject(root.QQC2.Overlay.overlay).open()
} }

View File

@@ -45,26 +45,6 @@ FormCard.FormCardPage {
} }
} }
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.AbstractFormDelegate {
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "data-information"
width: Kirigami.Units.iconSizes.sizeForLabels
height: Kirigami.Units.iconSizes.sizeForLabels
}
QQC2.Label {
text: i18nc("@info", "These are the default notification settings for all rooms. You can customize notifications per-room in the room list or room settings.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
}
FormCard.FormHeader { FormCard.FormHeader {
title: i18nc("@title:group", "Room Notifications") title: i18nc("@title:group", "Room Notifications")
} }

View File

@@ -345,7 +345,7 @@ FormCard.FormCardPage {
FormCard.FormCard { FormCard.FormCard {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
icon.name: "kt-restore-defaults-symbolic" icon.name: "kt-restore-defaults-symbolic"
text: i18nc("@action:button", "Reset all configuration values to their default") text: i18nc("@action:button", "Reset all configuration values to their default")
onClicked: resetDialog.open() onClicked: resetDialog.open()
} }
} }

View File

@@ -28,7 +28,7 @@ FormCard.FormCardPage {
FormCard.FormCard { FormCard.FormCard {
FormCard.FormRadioDelegate { FormCard.FormRadioDelegate {
text: i18nc("As in the default notification setting", "Default Settings") text: i18n("Follow global setting")
checked: room.pushNotificationState === PushNotificationState.Default checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState !== PushNotificationState.Unknown enabled: room.pushNotificationState !== PushNotificationState.Unknown
onToggled: { onToggled: {

View File

@@ -25,34 +25,13 @@ FormCard.FormCardPage {
title: i18nc("@option:check", "Encryption") title: i18nc("@option:check", "Encryption")
} }
FormCard.FormCard { FormCard.FormCard {
FormCard.AbstractFormDelegate { FormCard.FormSwitchDelegate {
visible: room.usesEncryption
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "lock"
width: Kirigami.Units.iconSizes.sizeForLabels
height: Kirigami.Units.iconSizes.sizeForLabels
}
QQC2.Label {
text: i18nc("@info", "This room uses encryption.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
FormCard.FormButtonDelegate {
id: enableEncryptionSwitch id: enableEncryptionSwitch
text: i18n("Enable encryption")
icon.name: "lock-symbolic" description: i18nc("option:check", "Once enabled, encryption cannot be disabled.")
text: i18nc("@action:button Enable encryption in this room", "Enable Encryption…")
description: i18nc("@info:description", "Once enabled, encryption cannot be disabled.")
enabled: room.canEncryptRoom enabled: room.canEncryptRoom
visible: !room.usesEncryption checked: room.usesEncryption
onToggled: if (checked) {
onClicked: {
let dialog = confirmEncryptionDialog.createObject(QQC2.Overlay.overlay, { let dialog = confirmEncryptionDialog.createObject(QQC2.Overlay.overlay, {
room: room room: room
}); });

View File

@@ -95,11 +95,9 @@ ColumnLayout {
} }
} }
QQC2.Button { QQC2.Button {
text: i18nc("@action:button", "Leave this space") text: i18nc("@action:button", "Leave this space")
icon.name: "go-previous" icon.name: "go-previous"
onClicked: Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, { onClicked: root.room.forget()
room: root.room
}).open();
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true

View File

@@ -123,7 +123,7 @@ KirigamiComponents.ConvergentContextMenu {
component ReportMessageAction: Kirigami.Action { component ReportMessageAction: Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic" icon.name: "dialog-warning-symbolic"
visible: !author.isLocalMember visible: !author.isLocalMember
onTriggered: { onTriggered: {

View File

@@ -159,7 +159,7 @@ QQC2.ScrollView {
function onReadMarkerAdded() { function onReadMarkerAdded() {
if (root.markReadCondition == LibNeoChat.TimelineMarkReadCondition.EntryVisible && messageListView.allUnreadVisible()) { if (root.markReadCondition == LibNeoChat.TimelineMarkReadCondition.EntryVisible && messageListView.allUnreadVisible()) {
_private.room.markAllMessagesAsRead(); root.room.markAllMessagesAsRead();
} }
} }

View File

@@ -85,14 +85,9 @@ QHash<int, QByteArray> MediaMessageFilterModel::roleNames() const
return roles; return roles;
} }
int MediaMessageFilterModel::getRowForEventId(const QString &eventId) const int MediaMessageFilterModel::getRowForSourceItem(int sourceRow) const
{ {
for (auto i = 0; i < rowCount(); i++) { return mapFromSource(sourceModel()->index(sourceRow, 0)).row();
if (data(index(i, 0), MessageModel::EventIdRole).toString() == eventId) {
return i;
}
}
return -1;
} }
#include "moc_mediamessagefiltermodel.cpp" #include "moc_mediamessagefiltermodel.cpp"

View File

@@ -63,5 +63,5 @@ public:
*/ */
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
int getRowForEventId(const QString &eventId) const; Q_INVOKABLE int getRowForSourceItem(int sourceRow) const;
}; };