Compare commits
1 Commits
work/redst
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de6e588981 |
@@ -1,2 +1,2 @@
|
||||
[General]
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp"
|
||||
|
||||
@@ -44,22 +44,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "opencv",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTS=OFF",
|
||||
"-DWITH_GTK=OFF",
|
||||
"-DBUILD_LIST=core,imgproc"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/opencv/opencv"
|
||||
}
|
||||
],
|
||||
"builddir": true
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"config-opts": [
|
||||
|
||||
@@ -15,7 +15,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.17")
|
||||
set(QT_MIN_VERSION "6.8")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ void MessageContentModelTest::missingEvent()
|
||||
|
||||
QCOMPARE(model1.rowCount(), 1);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading…"_s);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
|
||||
|
||||
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
|
||||
QCOMPARE(model2.rowCount(), 1);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply…"_s);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
|
||||
|
||||
room->syncNewEvents(u"test-min-sync.json"_s);
|
||||
QCOMPARE(model1.rowCount(), 2);
|
||||
|
||||
@@ -109,20 +109,113 @@ void Server::start()
|
||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
||||
QHttpServerRequest::Method::Post,
|
||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
|
||||
QMap<QString, QJsonArray> stateEvents;
|
||||
QMap<QString, QJsonArray> roomAccountData;
|
||||
|
||||
for (const auto &roomData : m_roomsToCreate) {
|
||||
stateEvents[roomData.id] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomData.id},
|
||||
{u"sender"_s, roomData.members[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : roomData.members) {
|
||||
stateEvents[roomData.id] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomData.id},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : roomData.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
roomAccountData[roomData.id] += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
m_roomsToCreate.clear();
|
||||
for (const auto &roomId : m_invitedUsers.keys()) {
|
||||
const auto &values = m_invitedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_invitedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_bannedUsers.keys()) {
|
||||
const auto &values = m_bannedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_bannedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_joinedUsers.keys()) {
|
||||
const auto &values = m_joinedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_joinedUsers.clear();
|
||||
|
||||
QJsonObject rooms;
|
||||
auto keys = stateEvents.keys() + m_events.keys();
|
||||
for (const auto &roomId : QSet(keys.begin(), keys.end())) {
|
||||
rooms[roomId] = QJsonObject{
|
||||
{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}},
|
||||
{u"account_data"_s, QJsonObject{{u"events"_s, roomAccountData[roomId]}}},
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, m_events[roomId]}}},
|
||||
};
|
||||
}
|
||||
m_events.clear();
|
||||
|
||||
auto json = QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}};
|
||||
responder.write(QJsonDocument(json), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
QSslConfiguration config;
|
||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
||||
void(key.open(QFile::ReadOnly));
|
||||
key.open(QFile::ReadOnly);
|
||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
||||
m_sslServer.setSslConfiguration(config);
|
||||
@@ -136,239 +229,46 @@ void Server::start()
|
||||
|
||||
QString Server::createRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
auto roomId = generateRoomId();
|
||||
m_roomsToCreate += RoomData{
|
||||
.members = {matrixId},
|
||||
.id = roomId,
|
||||
.tags = {},
|
||||
};
|
||||
m_state += changes;
|
||||
return roomId;
|
||||
}
|
||||
|
||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.bans += Changes::BanUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_bannedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.joins += Changes::JoinUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_joinedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
QString Server::createServerNoticesRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {u"m.server_notice"_s},
|
||||
};
|
||||
m_state += changes;
|
||||
auto roomId = createRoom(matrixId);
|
||||
m_roomsToCreate.last().tags = {u"m.server_notice"_s};
|
||||
return roomId;
|
||||
}
|
||||
|
||||
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId}},
|
||||
m_events[roomId] += QJsonObject{
|
||||
{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
||||
{
|
||||
QJsonObject joinRooms;
|
||||
auto token = request.query().queryItemValue(u"since"_s).toInt();
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &newRoom : change.newRooms) {
|
||||
QJsonArray stateEvents;
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, newRoom.initialMembers[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : newRoom.initialMembers) {
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
|
||||
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
|
||||
|
||||
QJsonArray roomAccountData;
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : newRoom.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
if (!tags.empty()) {
|
||||
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
|
||||
if (roomAccountData.size() > 0) {
|
||||
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
|
||||
}
|
||||
|
||||
joinRooms[newRoom.roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &invitation : change.invitations) {
|
||||
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, invitation.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, invitation.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(invitation.roomId)) {
|
||||
auto room = joinRooms[invitation.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[invitation.roomId] = room;
|
||||
} else {
|
||||
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &ban : change.bans) {
|
||||
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, ban.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, ban.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(ban.roomId)) {
|
||||
auto room = joinRooms[ban.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[ban.roomId] = room;
|
||||
} else {
|
||||
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &join : change.joins) {
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, join.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, join.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(join.roomId)) {
|
||||
auto room = joinRooms[join.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[join.roomId] = room;
|
||||
} else {
|
||||
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &event : change.events) {
|
||||
// TODO the room might be in a different join state.
|
||||
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
|
||||
timeline += event.fullJson;
|
||||
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
|
||||
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
|
||||
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
|
||||
} else {
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject syncData = {
|
||||
// {u"account_data"_s, QJsonObject {}},
|
||||
// {u"presence"_s, QJsonObject {}},
|
||||
{u"next_batch"_s, QString::number(m_state.size())},
|
||||
};
|
||||
|
||||
QJsonObject rooms;
|
||||
if (!joinRooms.isEmpty()) {
|
||||
rooms[u"join"_s] = joinRooms;
|
||||
}
|
||||
|
||||
if (!rooms.empty()) {
|
||||
syncData[u"rooms"_s] = rooms;
|
||||
}
|
||||
|
||||
qWarning() << syncData;
|
||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
||||
}
|
||||
|
||||
@@ -2,51 +2,16 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include <QHttpServer>
|
||||
#include <QJsonObject>
|
||||
#include <QSslServer>
|
||||
|
||||
struct Changes {
|
||||
struct NewRoom {
|
||||
QStringList initialMembers;
|
||||
QString roomId;
|
||||
QStringList tags;
|
||||
};
|
||||
QList<NewRoom> newRooms;
|
||||
|
||||
struct InviteUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<InviteUser> invitations;
|
||||
|
||||
struct BanUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<BanUser> bans;
|
||||
|
||||
struct JoinUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<JoinUser> joins;
|
||||
|
||||
struct Event {
|
||||
QJsonObject fullJson;
|
||||
};
|
||||
QList<Event> events;
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
QStringList members;
|
||||
QString id;
|
||||
QStringList tags;
|
||||
};
|
||||
|
||||
class Server : public QObject
|
||||
class Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server();
|
||||
|
||||
@@ -72,7 +37,10 @@ private:
|
||||
QHttpServer m_server;
|
||||
QSslServer m_sslServer;
|
||||
|
||||
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
|
||||
QHash<QString, QList<QString>> m_invitedUsers;
|
||||
QHash<QString, QList<QString>> m_bannedUsers;
|
||||
QHash<QString, QList<QString>> m_joinedUsers;
|
||||
|
||||
QList<Changes> m_state;
|
||||
QList<RoomData> m_roomsToCreate;
|
||||
QMap<QString, QJsonArray> m_events;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2023 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 <QTest>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||
update(std::move(roomData));
|
||||
@@ -47,7 +46,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
|
||||
if (!eventFileName.isEmpty()) {
|
||||
QFile testEventFile;
|
||||
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
||||
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
|
||||
testEventFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
// different every time.
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
||||
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
auto root = testSyncJson.object();
|
||||
auto timeline = root["timeline"_L1].toObject();
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
qt_add_executable(timeline_memtest
|
||||
qt_add_executable(timeline-memtest
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline_memtest PUBLIC
|
||||
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline-memtest PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
Qt::Qml
|
||||
@@ -16,13 +16,14 @@ target_link_libraries(timeline_memtest PUBLIC
|
||||
Qt::QuickControls2
|
||||
Qt::Widgets
|
||||
KF6::I18nQml
|
||||
KF6::Kirigami
|
||||
QuotientQt6
|
||||
LibNeoChat
|
||||
Timeline
|
||||
)
|
||||
|
||||
ecm_add_qml_module(timeline_memtest URI org.kde.neochat.timeline_memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline_memtest
|
||||
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
|
||||
QML_FILES
|
||||
Main.qml
|
||||
SOURCES
|
||||
|
||||
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
|
||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
auto ok = testSyncFile.open(QIODevice::ReadOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
|
||||
}
|
||||
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||
|
||||
@@ -488,7 +488,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="25.08.1" date="2025-09-11"/>
|
||||
<release version="25.08.0" date="2025-08-14"/>
|
||||
<release version="25.04.3" date="2025-07-03"/>
|
||||
<release version="25.04.2" date="2025-06-05"/>
|
||||
|
||||
@@ -20,7 +20,7 @@ index 10fe66daa..cd063113d 100644
|
||||
|
||||
-set(KF_MIN_VERSION "6.17")
|
||||
+set(KF_MIN_VERSION "6.12")
|
||||
set(QT_MIN_VERSION "6.8")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
--
|
||||
|
||||
905
po/ar/neochat.po
905
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
639
po/az/neochat.po
639
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
719
po/ca/neochat.po
719
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1422
po/cs/neochat.po
1422
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
785
po/da/neochat.po
785
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
836
po/de/neochat.po
836
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
817
po/el/neochat.po
817
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
826
po/eo/neochat.po
826
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
853
po/es/neochat.po
853
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
725
po/eu/neochat.po
725
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
920
po/fi/neochat.po
920
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1116
po/fr/neochat.po
1116
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
792
po/gl/neochat.po
792
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
846
po/he/neochat.po
846
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
833
po/hi/neochat.po
833
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
792
po/hu/neochat.po
792
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1108
po/ia/neochat.po
1108
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
815
po/id/neochat.po
815
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/ie/neochat.po
772
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
797
po/it/neochat.po
797
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
695
po/ja/neochat.po
695
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
719
po/ka/neochat.po
719
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
795
po/ko/neochat.po
795
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/lt/neochat.po
696
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1198
po/lv/neochat.po
1198
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
715
po/nl/neochat.po
715
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
816
po/nn/neochat.po
816
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
638
po/pa/neochat.po
638
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
832
po/pl/neochat.po
832
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
815
po/pt/neochat.po
815
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
792
po/ru/neochat.po
792
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
833
po/sa/neochat.po
833
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
659
po/sk/neochat.po
659
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
729
po/sl/neochat.po
729
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
748
po/sv/neochat.po
748
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
832
po/ta/neochat.po
832
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
910
po/tr/neochat.po
910
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
719
po/uk/neochat.po
719
po/uk/neochat.po
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
@@ -342,6 +342,9 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(neochat PUBLIC KUnifiedPush)
|
||||
|
||||
if (NOT ANDROID)
|
||||
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "general_logging.h"
|
||||
#include "mediasizehelper.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/messagemodel.h"
|
||||
@@ -38,6 +37,14 @@
|
||||
#include "trayicon_sni.h"
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
#include <kunifiedpush/connector.h>
|
||||
#endif
|
||||
@@ -126,10 +133,8 @@ Controller::Controller(QObject *parent)
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
#endif
|
||||
|
||||
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
#ifndef Q_OS_ANDROID
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
delete m_trayIcon;
|
||||
#endif
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -195,15 +200,13 @@ void Controller::setAccountManager(AccountManager *manager)
|
||||
|
||||
m_accountManager = manager;
|
||||
|
||||
if (!m_accountManager) {
|
||||
return;
|
||||
if (m_accountManager) {
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
void Controller::initConnection(NeoChatConnection *connection)
|
||||
@@ -259,8 +262,8 @@ bool Controller::supportSystemTray() const
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
|
||||
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
|
||||
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||
return de != u"GNOME"_s && de != u"Pantheon"_s;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -270,8 +273,11 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
} else if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -328,7 +334,30 @@ void Controller::clearInvitationNotification(const QString &roomId)
|
||||
|
||||
void Controller::updateBadgeNotificationCount(int count)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
// copied from Telegram desktop
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
|
||||
// Gnome requires that count is a 64bit integer
|
||||
const qint64 counterSlice = std::min(count, 9999);
|
||||
QVariantMap dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
dbusUnityProperties["count"_L1] = counterSlice;
|
||||
dbusUnityProperties["count-visible"_L1] = true;
|
||||
} else {
|
||||
dbusUnityProperties["count-visible"_L1] = false;
|
||||
}
|
||||
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
|
||||
|
||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
#endif // Q_OS_ANDROID
|
||||
#else
|
||||
qGuiApp->setBadgeNumber(count);
|
||||
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
@@ -349,10 +378,7 @@ QString Controller::loadFileContent(const QString &path) const
|
||||
{
|
||||
QUrl url(path);
|
||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCWarning(GENERAL) << "Failed to open file" << path;
|
||||
return {};
|
||||
}
|
||||
file.open(QFile::ReadOnly);
|
||||
return QString::fromLatin1(file.readAll());
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,8 @@ private:
|
||||
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QPointer<TrayIcon> m_trayIcon;
|
||||
#endif
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
||||
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{TextRole, "notificationText"},
|
||||
{TextRole, "text"},
|
||||
{RoomIdRole, "roomId"},
|
||||
{AuthorName, "authorName"},
|
||||
{AuthorAvatar, "authorAvatar"},
|
||||
|
||||
@@ -60,8 +60,9 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
|
||||
}
|
||||
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
m_connActiveJob.append(connection->user()->id());
|
||||
connection->callApi<GetNotificationsJob>().onResult([this, connection](const auto &job) {
|
||||
connect(job, &BaseJob::success, this, [this, job, connection]() {
|
||||
m_connActiveJob.removeAll(connection->user()->id());
|
||||
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Verify This Device")
|
||||
icon.name: "security-low"
|
||||
visible: !root.connection.isVerifiedSession
|
||||
visible: !root.connection.isVerifiedSession()
|
||||
onTriggered: {
|
||||
root.connection.startSelfVerification();
|
||||
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
|
||||
|
||||
@@ -48,7 +48,7 @@ Components.AbstractMaximizeComponent {
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: root.author.displayName
|
||||
name: root.author.name ?? root.author.displayName
|
||||
source: root.author.avatarUrl
|
||||
color: root.author.color
|
||||
}
|
||||
@@ -57,7 +57,7 @@ Components.AbstractMaximizeComponent {
|
||||
QQC2.Label {
|
||||
id: userLabel
|
||||
|
||||
text: root.author.displayName
|
||||
text: root.author.name ?? root.author.displayName
|
||||
color: root.author.color
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
|
||||
@@ -6,15 +6,16 @@ import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.neochat
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property string emoji
|
||||
required property string description
|
||||
property alias emoji: emojiLabel.text
|
||||
property alias description: descriptionLabel.text
|
||||
|
||||
QQC2.Label {
|
||||
text: root.emoji
|
||||
id: emojiLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
@@ -24,7 +25,7 @@ ColumnLayout {
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
|
||||
}
|
||||
QQC2.Label {
|
||||
text: root.description
|
||||
id: descriptionLabel
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
@@ -17,6 +17,9 @@ RowLayout {
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
delegate: EmojiItem {}
|
||||
delegate: EmojiItem {
|
||||
emoji: modelData.emoji
|
||||
description: modelData.description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
z: 99
|
||||
Kirigami.OverlayZStacking.layer: Kirigami.OverlayZStacking.ToolTip
|
||||
z: Kirigami.OverlayZStacking.z
|
||||
spacing: 0
|
||||
|
||||
opacity: (!root.text.startsWith("https://matrix.to/") && root.text.length > 0) ? 1 : 0
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import QtQml
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
@@ -171,7 +170,6 @@ Kirigami.Page {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
onDone: root.QQC2.Window.window.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ Kirigami.ApplicationWindow {
|
||||
RoomListPage {
|
||||
id: roomList
|
||||
|
||||
onSearch: root.quickSwitcher.open()
|
||||
onSearch: quickSwitcher.open()
|
||||
|
||||
connection: root.connection
|
||||
|
||||
@@ -267,17 +267,6 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.connection
|
||||
function onLoggedOut(): void {
|
||||
root.pageStack.clear();
|
||||
let page = root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {
|
||||
showExisting: true,
|
||||
}) as WelcomePage;
|
||||
page.connectionChosen.connect(() => root.load())
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AccountRegistry
|
||||
function onRowsRemoved() {
|
||||
|
||||
@@ -34,8 +34,8 @@ Kirigami.Page {
|
||||
enabled: root.model
|
||||
target: root.room
|
||||
function onChanged(): void {
|
||||
root.contentJson = root.model.stateEventContentJson(root.type, root.stateKey);
|
||||
root.sourceText = root.model.stateEventJson(root.type, root.stateKey);
|
||||
root.contentJson = model.stateEventContentJson(root.type, root.stateKey);
|
||||
root.sourceText = model.stateEventJson(root.type, root.stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ Kirigami.Page {
|
||||
text: i18nc("@action As in 'edit the state of this room'", "Edit state")
|
||||
icon.name: "document-edit"
|
||||
visible: root.allowEdit
|
||||
enabled: root.room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
||||
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
|
||||
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"), {
|
||||
room: root.room,
|
||||
type: root.type,
|
||||
stateKey: root.stateKey,
|
||||
|
||||
@@ -120,7 +120,7 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
onOpened: forceActiveFocus()
|
||||
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom, root.currentAuthor)
|
||||
|
||||
onSaveItem: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigami.delegates as KD
|
||||
import org.kde.kirigamiaddons.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
@@ -42,23 +41,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
id: notificationDelegate
|
||||
|
||||
required property string uri
|
||||
required property string authorAvatar
|
||||
required property string authorName
|
||||
required property string roomDisplayName
|
||||
required property string notificationText
|
||||
|
||||
width: parent?.width ?? 0
|
||||
|
||||
onClicked: RoomManager.resolveResource(uri)
|
||||
onClicked: RoomManager.resolveResource(model.uri)
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Components.Avatar {
|
||||
source: notificationDelegate.authorAvatar
|
||||
name: notificationDelegate.authorName
|
||||
source: model.authorAvatar
|
||||
name: model.authorName
|
||||
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
implicitWidth: implicitHeight
|
||||
|
||||
@@ -75,7 +66,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.Label {
|
||||
id: label
|
||||
|
||||
text: notificationDelegate.roomDisplayName
|
||||
text: model.roomDisplayName
|
||||
elide: Text.ElideRight
|
||||
font.weight: Font.Normal
|
||||
textFormat: Text.PlainText
|
||||
@@ -87,9 +78,10 @@ Kirigami.ScrollablePage {
|
||||
QQC2.Label {
|
||||
id: subtitle
|
||||
|
||||
text: notificationDelegate.notificationText
|
||||
text: model.text
|
||||
elide: Text.ElideRight
|
||||
font: Kirigami.Theme.smallFont
|
||||
opacity: root.hasNotifications ? 0.9 : 0.7
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Window
|
||||
@@ -79,9 +77,9 @@ Kirigami.Page {
|
||||
|
||||
if (root.currentRoom.tagNames.includes("m.server_notice")) {
|
||||
banner.text = i18nc("@info", "This room contains official messages from your homeserver.")
|
||||
banner.show("message");
|
||||
banner.visible = true;
|
||||
} else {
|
||||
banner.hideIf("message");
|
||||
banner.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +88,10 @@ Kirigami.Page {
|
||||
function onIsOnlineChanged() {
|
||||
if (!root.currentRoom.connection.isOnline) {
|
||||
banner.text = i18nc("@info:status", "NeoChat is offline. Please check your network connection.");
|
||||
banner.visible = true;
|
||||
banner.type = Kirigami.MessageType.Error;
|
||||
banner.show("offline");
|
||||
} else {
|
||||
banner.hideIf("offline");
|
||||
banner.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,30 +99,17 @@ Kirigami.Page {
|
||||
header: Kirigami.InlineMessage {
|
||||
id: banner
|
||||
|
||||
// Used to keep track of messages so we can hide the right one at the right time
|
||||
property string messageId
|
||||
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
position: Kirigami.InlineMessage.Position.Header
|
||||
|
||||
function show(msgid: string): void {
|
||||
messageId = msgid;
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function hideIf(msgid: string): void {
|
||||
if (messageId == msgid) {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: timelineViewLoader
|
||||
anchors.fill: parent
|
||||
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
|
||||
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
|
||||
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
|
||||
visible: !root.loading
|
||||
sourceComponent: TimelineView {
|
||||
id: timelineView
|
||||
messageFilterModel: root.messageFilterModel
|
||||
@@ -170,13 +155,13 @@ Kirigami.Page {
|
||||
|
||||
footer: Loader {
|
||||
id: chatBarLoader
|
||||
height: active ? (item as ChatBar).implicitHeight : 0
|
||||
height: active ? item.implicitHeight : 0
|
||||
active: timelineViewLoader.active && !root.currentRoom.readOnly
|
||||
sourceComponent: ChatBar {
|
||||
id: chatBar
|
||||
width: parent.width
|
||||
currentRoom: root.currentRoom
|
||||
connection: root.currentRoom.connection as NeoChatConnection
|
||||
connection: root.currentRoom.connection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +203,7 @@ Kirigami.Page {
|
||||
function onShowMessage(messageType, message) {
|
||||
banner.text = message;
|
||||
banner.type = messageType;
|
||||
banner.show("generic");
|
||||
banner.visible = true;
|
||||
}
|
||||
|
||||
function onShowEventSource(eventId) {
|
||||
@@ -230,15 +215,29 @@ Kirigami.Page {
|
||||
});
|
||||
}
|
||||
|
||||
function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
|
||||
(delegateContextMenu.createObject(root, {
|
||||
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(root, {
|
||||
selectedText: selectedText,
|
||||
hoveredLink: hoveredLink,
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
messageComponentType: messageComponentType,
|
||||
plainText: plainText,
|
||||
htmlText: htmlText,
|
||||
});
|
||||
contextMenu.popup();
|
||||
}
|
||||
|
||||
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
messageComponentType: messageComponentType,
|
||||
}) as DelegateContextMenu).popup();
|
||||
});
|
||||
contextMenu.popup();
|
||||
}
|
||||
|
||||
function onShowMaximizedMedia(index) {
|
||||
@@ -253,20 +252,28 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
function onShowMaximizedCode(author, time, codeText, language) {
|
||||
(Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
let popup = Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
author: author,
|
||||
time: time,
|
||||
codeText: codeText,
|
||||
language: language
|
||||
}) as CodeMaximizeComponent).open();
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: delegateContextMenu
|
||||
DelegateContextMenu {
|
||||
id: messageDelegateContextMenu
|
||||
MessageDelegateContextMenu {
|
||||
room: root.currentRoom
|
||||
connection: root.currentRoom.connection as NeoChatConnection
|
||||
connection: root.currentRoom.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenu
|
||||
FileDelegateContextMenu {
|
||||
room: root.currentRoom
|
||||
connection: root.currentRoom.connection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 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 ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
@@ -44,7 +44,7 @@ QQC2.ComboBox {
|
||||
required property bool isHomeServer
|
||||
required property bool isDeletable
|
||||
|
||||
text: isAddServerDelegate ? i18nc("@action:button", "Add New Server") : url
|
||||
text: isAddServerDelegate ? i18n("Add New Server") : url
|
||||
highlighted: index === root.highlightedIndex
|
||||
|
||||
topInset: index === 0 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
@@ -60,7 +60,7 @@ QQC2.ComboBox {
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: serverItem
|
||||
subtitle: serverItem.isHomeServer ? i18nc("@info", "Home Server") : ""
|
||||
subtitle: serverItem.isHomeServer ? i18n("Home Server") : ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -138,11 +138,11 @@ QQC2.ComboBox {
|
||||
text: {
|
||||
if (serverUrlField.length > 0) {
|
||||
if (!serverUrlField.acceptableInput) {
|
||||
return i18nc("@info", "The entered text is not a valid url");
|
||||
return i18n("The entered text is not a valid url");
|
||||
}
|
||||
|
||||
if (!serverUrlField.isValidServer) {
|
||||
return i18nc("@info", "This server cannot be resolved or has already been added");
|
||||
return i18n("This server cannot be resolved or has already been added");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ QQC2.ComboBox {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: i18nc("@label", "Server URL:")
|
||||
text: i18n("Server URL:")
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
|
||||
@@ -45,8 +45,6 @@ Kirigami.Action {
|
||||
model: Purpose.PurposeAlternativesModel {
|
||||
pluginType: "Export"
|
||||
inputData: root.inputData
|
||||
// We already have many better ways to copy events to the clipboard
|
||||
disabledPlugins: ["clipboardplugin"]
|
||||
}
|
||||
|
||||
delegate: Kirigami.Action {
|
||||
|
||||
@@ -130,6 +130,6 @@ FormCard.FormCardPage {
|
||||
|
||||
property OpenFileDialog openFileDialog: OpenFileDialog {
|
||||
id: openFileDialog
|
||||
onChosen: path => securityKeyField.text = Controller.loadFileContent(path)
|
||||
onChosen: securityKeyField.text = Controller.loadFileContent(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@ Kirigami.Dialog {
|
||||
level: 1
|
||||
Layout.fillWidth: true
|
||||
font.bold: true
|
||||
clip: true // Intentional to limit insane Unicode in display names
|
||||
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
@@ -110,15 +109,15 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
let map = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
text: barcode.content,
|
||||
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
|
||||
subtitle: root.user.id,
|
||||
avatarColor: root.room?.member(root.user.id).color,
|
||||
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
|
||||
}) as QrCodeMaximizeComponent;
|
||||
});
|
||||
root.close();
|
||||
qrCode.open();
|
||||
map.open();
|
||||
}
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
@@ -152,7 +151,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("kick") && room.containsUser(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
|
||||
|
||||
text: i18nc("@action:button", "Kick this user")
|
||||
icon.name: "im-kick-user"
|
||||
@@ -174,7 +173,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("invite") && !root.room.containsUser(root.user.id)
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("invite") && !room.containsUser(root.user.id)
|
||||
|
||||
enabled: root.room && !root.room.isUserBanned(root.user.id)
|
||||
text: i18nc("@action:button", "Invite this user")
|
||||
@@ -186,7 +185,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
|
||||
|
||||
text: i18nc("@action:button", "Ban this user")
|
||||
icon.name: "im-ban-user"
|
||||
@@ -209,7 +208,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
|
||||
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && room.isUserBanned(root.user.id)
|
||||
|
||||
text: i18nc("@action:button", "Unban this user")
|
||||
icon.name: "im-irc"
|
||||
@@ -225,11 +224,12 @@ Kirigami.Dialog {
|
||||
text: i18nc("@action:button", "Set user power level")
|
||||
icon.name: "visibility"
|
||||
onClicked: {
|
||||
(powerLevelDialog.createObject(this, {
|
||||
let dialog = powerLevelDialog.createObject(this, {
|
||||
room: root.room,
|
||||
userId: root.user.id,
|
||||
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
|
||||
}) as PowerLevelDialog).open();
|
||||
});
|
||||
dialog.open();
|
||||
root.close();
|
||||
}
|
||||
|
||||
@@ -242,13 +242,13 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: root.room && (root.user.id === root.connection.localUserId || root.room.canSendState("redact"))
|
||||
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
|
||||
|
||||
text: i18nc("@action:button", "Remove recent messages by this user")
|
||||
icon.name: "delete"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onClicked: {
|
||||
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Messages"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
||||
@@ -274,20 +274,6 @@ Kirigami.Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
|
||||
icon.name: "search-symbolic"
|
||||
onClicked: {
|
||||
((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
||||
room: root.room,
|
||||
senderId: root.user.id
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Copy link")
|
||||
icon.name: "username-copy"
|
||||
|
||||
@@ -71,15 +71,15 @@ SearchPage {
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: userDelegate.directChatExists
|
||||
text: i18nc("@info", "Friends")
|
||||
text: i18n("Friends")
|
||||
textFormat: Text.PlainText
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchFieldPlaceholder: i18nc("@info:placeholder", "Find your friends…")
|
||||
noSearchPlaceholderMessage: i18nc("@info:placeholder", "Enter text to start searching for your friends")
|
||||
searchFieldPlaceholder: i18n("Find your friends…")
|
||||
noSearchPlaceholderMessage: i18n("Enter text to start searching for your friends")
|
||||
noResultPlaceholderMessage: i18nc("@info:label", "No matches found")
|
||||
|
||||
noSearchHelpfulAction: noResultHelpfulAction
|
||||
@@ -101,8 +101,8 @@ SearchPage {
|
||||
function openManualUserDialog() {
|
||||
let dialog = manualUserDialog.createObject(this, {
|
||||
connection: root.connection
|
||||
}) as ManualUserDialog;
|
||||
dialog.parent = root.QQC2.Overlay.overlay;
|
||||
});
|
||||
dialog.parent = root.Window.window.overlay;
|
||||
dialog.accepted.connect(() => {
|
||||
root.closeDialog();
|
||||
});
|
||||
|
||||
@@ -266,7 +266,7 @@ void RoomManager::viewEventSource(const QString &eventId)
|
||||
Q_EMIT showEventSource(eventId);
|
||||
}
|
||||
|
||||
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, 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";
|
||||
@@ -279,15 +279,23 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, const
|
||||
return;
|
||||
}
|
||||
const auto &event = **it;
|
||||
Q_EMIT showDelegateMenu(eventId,
|
||||
room->qmlSafeMember(event.senderId()),
|
||||
if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) {
|
||||
Q_EMIT showFileMenu(eventId,
|
||||
sender,
|
||||
MessageComponentType::typeForEvent(event),
|
||||
EventHandler::plainBody(room, &event),
|
||||
EventHandler::richBody(room, &event),
|
||||
EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(),
|
||||
room->fileTransferInfo(eventId),
|
||||
selectedText,
|
||||
hoveredLink);
|
||||
room->fileTransferInfo(eventId));
|
||||
return;
|
||||
}
|
||||
|
||||
Q_EMIT showMessageMenu(eventId,
|
||||
sender,
|
||||
MessageComponentType::typeForEvent(event),
|
||||
EventHandler::plainBody(room, &event),
|
||||
EventHandler::richBody(room, &event),
|
||||
selectedText,
|
||||
hoveredLink);
|
||||
}
|
||||
|
||||
bool RoomManager::hasOpenRoom() const
|
||||
@@ -400,7 +408,7 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
|
||||
job.get(),
|
||||
&Quotient::BaseJob::finished,
|
||||
this,
|
||||
[this, account, roomAliasOrId](Quotient::BaseJob *finish) {
|
||||
[this, account](Quotient::BaseJob *finish) {
|
||||
if (finish->status() == Quotient::BaseJob::Success) {
|
||||
connect(
|
||||
account,
|
||||
@@ -411,7 +419,7 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
} else {
|
||||
Q_EMIT showMessage(MessageType::Warning, i18n("Failed to join %1:<br />%2", roomAliasOrId, finish->errorString()));
|
||||
Q_EMIT showMessage(MessageType::Warning, i18n("Failed to join room<br />%1", finish->errorString()));
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
@@ -478,8 +486,9 @@ void RoomManager::setConnection(NeoChatConnection *connection)
|
||||
|
||||
m_connection = connection;
|
||||
|
||||
m_lastRoomConfig = m_config->group(m_connection->userId()).group(u"LastOpenRoom"_s);
|
||||
|
||||
if (m_connection != nullptr) {
|
||||
m_lastRoomConfig = m_config->group(m_connection->userId()).group(u"LastOpenRoom"_s);
|
||||
connect(m_connection, &NeoChatConnection::showMessage, this, &RoomManager::showMessage);
|
||||
connect(m_connection, &NeoChatConnection::createdRoom, this, [this](Quotient::Room *room) {
|
||||
resolveResource(room->id());
|
||||
|
||||
@@ -222,7 +222,8 @@ public:
|
||||
/**
|
||||
* @brief Show a context menu for the given event.
|
||||
*/
|
||||
Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText = {}, const QString &hoveredLink = {});
|
||||
Q_INVOKABLE void
|
||||
viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {});
|
||||
|
||||
/**
|
||||
* @brief Set a URL to be loaded as the initial room.
|
||||
@@ -295,15 +296,23 @@ Q_SIGNALS:
|
||||
/**
|
||||
* @brief Request to show a menu for the given event.
|
||||
*/
|
||||
void showDelegateMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &richtText,
|
||||
const QString &mimeType,
|
||||
const FileTransferInfo &progressInfo,
|
||||
const QString &selectedText,
|
||||
const QString &hoveredLink);
|
||||
void showMessageMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &htmlText,
|
||||
const QString &selectedText,
|
||||
const QString &hoveredLink);
|
||||
|
||||
/**
|
||||
* @brief Request to show a menu for the given media event.
|
||||
*/
|
||||
void showFileMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &mimeType,
|
||||
const FileTransferInfo &progressInfo);
|
||||
|
||||
/**
|
||||
* @brief Show the direct chat confirmation dialog.
|
||||
|
||||
@@ -253,8 +253,7 @@ QQC2.Control {
|
||||
placeholderText: root.currentRoom.usesEncryption ? i18nc("@placeholder", "Send an encrypted message…") : root.currentRoom.mainCache.attachmentPath.length > 0 ? i18nc("@placeholder", "Set an attachment caption…") : i18nc("@placeholder", "Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
// This has to stay PlainText or else formatting starts breaking in strange ways
|
||||
textFormat: TextEdit.PlainText
|
||||
textFormat: TextEdit.MarkdownText
|
||||
|
||||
Accessible.description: placeholderText
|
||||
|
||||
|
||||
@@ -92,7 +92,6 @@ QQC2.Popup {
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: completionDelegate
|
||||
labelItem.textFormat: Text.PlainText
|
||||
labelItem.clip: true // Intentional to limit insane Unicode in display names
|
||||
subtitle: completionDelegate.subtitle ?? ""
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ QQC2.ScrollView {
|
||||
readonly property int emojisPerRow: emojis.width / targetIconSize
|
||||
required property bool withCustom
|
||||
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
|
||||
required property Item header
|
||||
required property QtObject header
|
||||
property bool stickers: false
|
||||
|
||||
signal chosen(string unicode)
|
||||
@@ -75,7 +75,7 @@ QQC2.ScrollView {
|
||||
shortName: modelData.shortName,
|
||||
unicode: modelData.unicode,
|
||||
categoryIconSize: root.targetIconSize
|
||||
}) as EmojiTonesPicker;
|
||||
});
|
||||
tones.open();
|
||||
tones.forceActiveFocus();
|
||||
}
|
||||
@@ -85,14 +85,14 @@ QQC2.ScrollView {
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
icon.name: root.stickers ? "stickers" : "preferences-desktop-emoticons"
|
||||
text: root.stickers ? i18nc("@info", "No stickers") : i18nc("@info", "No emojis")
|
||||
text: root.stickers ? i18n("No stickers") : i18n("No emojis")
|
||||
visible: emojis.count === 0
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: tonesPopupComponent
|
||||
EmojiTonesPicker {
|
||||
onChosen: emoji => root.chosen(emoji)
|
||||
onChosen: root.chosen(emoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -53,14 +51,14 @@ ColumnLayout {
|
||||
Kirigami.Action {
|
||||
id: emojis
|
||||
icon.name: "smiley"
|
||||
text: i18nc("@action:button", "Emojis")
|
||||
text: i18n("Emojis")
|
||||
checked: true
|
||||
onTriggered: root.selectedType = 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: stickers
|
||||
icon.name: "stickers"
|
||||
text: i18nc("@action:button", "Stickers")
|
||||
text: i18n("Stickers")
|
||||
onTriggered: root.selectedType = 1
|
||||
}
|
||||
]
|
||||
@@ -109,7 +107,7 @@ ColumnLayout {
|
||||
id: searchField
|
||||
Layout.margins: Kirigami.Units.smallSpacing
|
||||
Layout.fillWidth: true
|
||||
visible: root.selectedType === 0
|
||||
visible: selectedType === 0
|
||||
|
||||
/**
|
||||
* The focus is manged by the parent and we don't want to use the standard
|
||||
@@ -129,17 +127,17 @@ ColumnLayout {
|
||||
header: categories
|
||||
Keys.forwardTo: searchField
|
||||
stickers: root.selectedType === 1
|
||||
onStickerChosen: index => stickerModel.postSticker(emoticonFilterModel.mapToSource(emoticonFilterModel.index(index, 0)).row)
|
||||
onStickerChosen: stickerModel.postSticker(emoticonFilterModel.mapToSource(emoticonFilterModel.index(index, 0)).row)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
visible: root.showQuickReaction
|
||||
visible: showQuickReaction
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
visible: root.showQuickReaction
|
||||
visible: showQuickReaction
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||
@@ -151,7 +149,6 @@ ColumnLayout {
|
||||
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
|
||||
|
||||
delegate: EmojiDelegate {
|
||||
required property string modelData
|
||||
emoji: modelData
|
||||
|
||||
height: root.categoryIconSize
|
||||
@@ -187,14 +184,11 @@ ColumnLayout {
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
Kirigami.NavigationTabButton {
|
||||
required property string emoji
|
||||
required property int index
|
||||
required property string name
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: categories.currentIndex === index
|
||||
text: emoji
|
||||
QQC2.ToolTip.text: name
|
||||
checked: categories.currentIndex === model.index
|
||||
text: modelData ? modelData.emoji : ""
|
||||
QQC2.ToolTip.text: modelData ? modelData.name : ""
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
onClicked: {
|
||||
@@ -207,25 +201,21 @@ ColumnLayout {
|
||||
Component {
|
||||
id: stickerDelegate
|
||||
Kirigami.NavigationTabButton {
|
||||
id: sticker
|
||||
required property string name
|
||||
required property int index
|
||||
required property string emoji
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: stickerModel.packIndex === index
|
||||
checked: stickerModel.packIndex === model.index
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
contentItem: Image {
|
||||
source: sticker.emoji
|
||||
source: model.url
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
QQC2.ToolTip.text: name
|
||||
QQC2.ToolTip.text: model.name
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered && !!name
|
||||
onClicked: stickerModel.packIndex = index
|
||||
QQC2.ToolTip.visible: hovered && !!model.name
|
||||
onClicked: stickerModel.packIndex = model.index
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ void StateModel::loadState()
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
if (!m_room) {
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
const auto keys = m_room->currentState().events().keys();
|
||||
|
||||
@@ -66,13 +66,6 @@ ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE
|
||||
io.github.quotient_im.libquotient
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(LibNeoChat
|
||||
HEADER "general_logging.h"
|
||||
IDENTIFIER "GENERAL"
|
||||
CATEGORY_NAME "org.kde.neochat"
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(LibNeoChat
|
||||
HEADER "eventhandler_logging.h"
|
||||
IDENTIFIER "EventHandling"
|
||||
@@ -112,8 +105,3 @@ if(NOT ANDROID)
|
||||
)
|
||||
target_compile_definitions(LibNeoChat PUBLIC -DHAVE_ICU)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(LibNeoChat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(LibNeoChat PUBLIC KUnifiedPush)
|
||||
endif()
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "general_logging.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
AccountManager::AccountManager(bool testMode, QObject *parent)
|
||||
@@ -25,10 +23,10 @@ AccountManager::AccountManager(bool testMode, QObject *parent)
|
||||
loadAccountsFromCache();
|
||||
});
|
||||
} else {
|
||||
auto connection = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
||||
connection->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
||||
m_accountRegistry->add(connection);
|
||||
connection->syncLoop();
|
||||
auto c = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
||||
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
||||
m_accountRegistry->add(c);
|
||||
c->syncLoop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,45 +37,48 @@ Quotient::AccountRegistry *AccountManager::accounts()
|
||||
|
||||
void AccountManager::loadAccountsFromCache()
|
||||
{
|
||||
for (const auto &accountId : Quotient::SettingsGroup("Accounts"_L1).childGroups()) {
|
||||
const auto accounts = Quotient::SettingsGroup("Accounts"_L1).childGroups();
|
||||
for (const auto &accountId : accounts) {
|
||||
Quotient::AccountSettings account{accountId};
|
||||
m_accountsLoading += accountId;
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (account.homeserver().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||
if (accessTokenLoadingJob->error() != QKeychain::Error::NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
Quotient::AccountSettings account{accountId};
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<Quotient::RoomCreateEvent>()) {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (!account.homeserver().isEmpty()) {
|
||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||
Quotient::AccountSettings account{accountId};
|
||||
QString accessToken;
|
||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
||||
accessToken = QString::fromLatin1(accessTokenLoadingJob->binaryData());
|
||||
} else {
|
||||
connect(
|
||||
connection->allRooms()[0],
|
||||
&NeoChatRoom::baseStateLoaded,
|
||||
this,
|
||||
[this, connection, accountId]() {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<Quotient::RoomCreateEvent>()) {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
} else {
|
||||
connect(
|
||||
connection->allRooms()[0],
|
||||
&NeoChatRoom::baseStateLoaded,
|
||||
this,
|
||||
[this, connection, accountId]() {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), QString::fromLatin1(accessTokenLoadingJob->binaryData()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ void AccountManager::saveAccessTokenToKeyChain(NeoChatConnection *connection)
|
||||
}
|
||||
const auto userId = connection->userId();
|
||||
|
||||
qCDebug(GENERAL) << "Save the access token to the keychain for " << userId;
|
||||
qDebug() << "Save the access token to the keychain for " << userId;
|
||||
auto job = new QKeychain::WritePasswordJob(qAppName());
|
||||
job->setAutoDelete(true);
|
||||
job->setKey(userId);
|
||||
@@ -108,7 +109,7 @@ void AccountManager::saveAccessTokenToKeyChain(NeoChatConnection *connection)
|
||||
|
||||
QKeychain::ReadPasswordJob *AccountManager::loadAccessTokenFromKeyChain(const QString &userId)
|
||||
{
|
||||
qCDebug(GENERAL) << "Reading access token from the keychain for" << userId;
|
||||
qDebug() << "Reading access token from the keychain for" << userId;
|
||||
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
||||
job->setKey(userId);
|
||||
|
||||
@@ -204,7 +205,8 @@ void AccountManager::dropConnection(const QString &userId)
|
||||
if (dropConnectionLoading(m_connectionsLoading.value(userId, nullptr))) {
|
||||
return;
|
||||
}
|
||||
if (const auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry->get(userId))) {
|
||||
const auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry->get(userId));
|
||||
if (connection) {
|
||||
dropRegistry(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ QString ChatDocumentHandler::getText() const
|
||||
qCWarning(ChatDocumentHandling) << "getText called with no QQuickTextDocument available.";
|
||||
return {};
|
||||
}
|
||||
return document()->toPlainText();
|
||||
return document()->toRawText();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
|
||||
@@ -116,14 +116,12 @@ Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
void roomChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateCompletion() const;
|
||||
|
||||
private:
|
||||
ChatBarType::Type m_type = ChatBarType::None;
|
||||
QPointer<QQuickItem> m_textItem;
|
||||
QTextDocument *document() const;
|
||||
|
||||
void updateCompletion() const;
|
||||
int completionStartIndex() const;
|
||||
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
|
||||
@@ -186,10 +186,6 @@ int DelegateSizeHelper::availablePercentageWidth() const
|
||||
qreal DelegateSizeHelper::availableWidth() const
|
||||
{
|
||||
qreal absoluteWidth = maxAvailableWidth() * availablePercentageWidth() * 0.01;
|
||||
// We want to use all available space for a horizontal line.
|
||||
if (m_startPercentWidth == m_endPercentWidth) {
|
||||
return std::round(absoluteWidth);
|
||||
}
|
||||
return std::round(std::min(absoluteWidth, maxWidth()));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,14 @@ void LinkPreviewer::loadUrlPreview()
|
||||
return;
|
||||
}
|
||||
|
||||
auto onSuccess = [this, conn](const auto &job) {
|
||||
BaseJob *job = nullptr;
|
||||
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
|
||||
job = conn->callApi<GetUrlPreviewAuthedJob>(m_url);
|
||||
} else {
|
||||
QT_IGNORE_DEPRECATIONS(job = conn->callApi<GetUrlPreviewJob>(m_url);)
|
||||
}
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
const auto json = job->jsonData();
|
||||
m_title = json["og:title"_L1].toString().trimmed();
|
||||
m_description = json["og:description"_L1].toString().trimmed().replace("\n"_L1, " "_L1);
|
||||
@@ -78,13 +85,7 @@ void LinkPreviewer::loadUrlPreview()
|
||||
Q_EMIT descriptionChanged();
|
||||
Q_EMIT imageSourceChanged();
|
||||
Q_EMIT loadedChanged();
|
||||
};
|
||||
|
||||
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
|
||||
conn->callApi<GetUrlPreviewAuthedJob>(m_url);
|
||||
} else {
|
||||
QT_IGNORE_DEPRECATIONS(conn->callApi<GetUrlPreviewJob>(m_url).onResult(onSuccess);)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
m_connection->uploadFile(location.toLocalFile()).onResult([name, location, this](const auto &job) {
|
||||
auto job = m_connection->uploadFile(location.toLocalFile());
|
||||
|
||||
connect(job, &BaseJob::success, this, [name, location, job, this] {
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_L1);
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"_L1].toObject();
|
||||
|
||||
@@ -44,8 +44,8 @@ QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
|
||||
QHash<int, QByteArray> ImagePacksModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{DisplayNameRole, "name"},
|
||||
{AvatarUrlRole, "emoji"},
|
||||
{DisplayNameRole, "displayName"},
|
||||
{AvatarUrlRole, "avatarUrl"},
|
||||
{AttributionRole, "attribution"},
|
||||
{IdRole, "id"},
|
||||
};
|
||||
|
||||
@@ -12,27 +12,33 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
{
|
||||
connect(this, &LocationsModel::roomChanged, this, [this]() {
|
||||
for (const auto &event : m_room->messageEvents()) {
|
||||
if (const auto &roomMessageEvent = event.viewAs<RoomMessageEvent>()) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
}
|
||||
connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [this](const auto &events) {
|
||||
for (const auto &event : events) {
|
||||
if (const auto &roomMessageEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [this](const auto &events) {
|
||||
for (const auto &event : events) {
|
||||
if (const auto &roomMessageEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,7 +133,8 @@ void NeoChatConnection::connectSignals()
|
||||
&Connection::connected,
|
||||
this,
|
||||
[this] {
|
||||
callApi<GetVersionsJob>(BackgroundRequest).onResult([this](const auto &job) {
|
||||
auto job = callApi<GetVersionsJob>(BackgroundRequest);
|
||||
connect(job, &GetVersionsJob::success, this, [this, job] {
|
||||
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
|
||||
Q_EMIT canCheckMutualRoomsChanged();
|
||||
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
|
||||
@@ -141,12 +142,6 @@ void NeoChatConnection::connectSignals()
|
||||
});
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
connect(this, &Connection::sessionVerified, this, [this](const QString &userId, const QString &deviceId) {
|
||||
if (userId == this->userId() && deviceId == this->deviceId()) {
|
||||
Q_EMIT ownSessionVerified();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int NeoChatConnection::badgeNotificationCount() const
|
||||
@@ -242,22 +237,24 @@ bool NeoChatConnection::canCheckMutualRooms() const
|
||||
|
||||
void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
callApi<ChangePasswordJob>(newPassword, false).onFailure([this, currentPassword, newPassword](const auto &job) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["password"_L1] = currentPassword;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData)
|
||||
.then(
|
||||
[this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT passwordStatus(job->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other);
|
||||
});
|
||||
auto job = callApi<ChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["password"_L1] = currentPassword;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
|
||||
Q_EMIT passwordStatus(innerJob->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,18 +274,22 @@ QString NeoChatConnection::label() const
|
||||
|
||||
void NeoChatConnection::deactivateAccount(const QString &password, const bool erase)
|
||||
{
|
||||
callApi<DeactivateAccountJob>().onFailure([password, erase, this](const auto &job) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.authInfo["password"_L1] = password;
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
authData.authInfo["identifier"_L1] = identifier;
|
||||
callApi<DeactivateAccountJob>(authData, QString{}, erase).onResult([this]() {
|
||||
logout(false);
|
||||
});
|
||||
auto job = callApi<DeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password, erase] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.authInfo["password"_L1] = password;
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
authData.authInfo["identifier"_L1] = identifier;
|
||||
auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
logout(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,19 +342,19 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
|
||||
});
|
||||
}
|
||||
|
||||
Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents)
|
||||
.then(
|
||||
[parent, setChildParent, this](const auto &job) {
|
||||
if (parent.isEmpty() || !setChildParent) {
|
||||
return;
|
||||
}
|
||||
const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents);
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
|
||||
}
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||
@@ -370,19 +371,20 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
});
|
||||
}
|
||||
|
||||
Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1, "m.space"_L1}})
|
||||
.then(
|
||||
[parent, setChildParent, this](const auto &job) {
|
||||
if (parent.isEmpty() || !setChildParent) {
|
||||
return;
|
||||
}
|
||||
const auto job =
|
||||
Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1, "m.space"_L1}});
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
|
||||
}
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
|
||||
Quotient::ForgetRoomJob *NeoChatConnection::forgetRoom(const QString &id)
|
||||
@@ -531,11 +533,7 @@ KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphra
|
||||
}
|
||||
QUrl url(path);
|
||||
QFile file(url.toLocalFile());
|
||||
auto ok = file.open(QFile::WriteOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << file.fileName() << file.errorString();
|
||||
return KeyImport::OtherError;
|
||||
}
|
||||
file.open(QFile::WriteOnly);
|
||||
file.write(result.value());
|
||||
file.close();
|
||||
return KeyImport::Success;
|
||||
|
||||
@@ -90,11 +90,6 @@ class NeoChatConnection : public Quotient::Connection
|
||||
*/
|
||||
Q_PROPERTY(bool enablePushNotifications READ enablePushNotifications NOTIFY enablePushNotificationsChanged)
|
||||
|
||||
/**
|
||||
* @brief True if this connection is a verified session.
|
||||
*/
|
||||
Q_PROPERTY(bool isVerifiedSession READ isVerifiedSession NOTIFY ownSessionVerified)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
@@ -214,7 +209,7 @@ public:
|
||||
/**
|
||||
* @return True if this connection is a verified session.
|
||||
*/
|
||||
bool isVerifiedSession() const;
|
||||
Q_INVOKABLE bool isVerifiedSession() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void globalUrlPreviewEnabledChanged();
|
||||
@@ -247,11 +242,6 @@ Q_SIGNALS:
|
||||
*/
|
||||
void roomAboutToBeLeft(const QString &id);
|
||||
|
||||
/**
|
||||
* @brief When the connection's own verification state changes.
|
||||
*/
|
||||
void ownSessionVerified();
|
||||
|
||||
private:
|
||||
static bool m_globalUrlPreviewDefault;
|
||||
static PushRuleAction::Action m_defaultAction;
|
||||
|
||||
@@ -261,10 +261,7 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body, std::optiona
|
||||
|
||||
QTemporaryFile file;
|
||||
file.setFileTemplate(QStringLiteral("XXXXXX.jpg"));
|
||||
auto ok = file.open();
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << file.fileName() << file.errorString();
|
||||
}
|
||||
file.open();
|
||||
|
||||
const auto thumbnailImage = sink.videoFrame().toImage();
|
||||
Q_UNUSED(thumbnailImage.save(file.fileName()))
|
||||
@@ -513,9 +510,12 @@ QUrl NeoChatRoom::avatarMediaUrl() const
|
||||
|
||||
void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
||||
{
|
||||
connection()->uploadFile(localFile.toLocalFile()).onResult([this](const auto &job) {
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar"_L1, QString(), QJsonObject{{"url"_L1, job->contentUri().toString()}});
|
||||
});
|
||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
||||
if (isJobPending(job)) {
|
||||
connect(job, &BaseJob::success, this, [this, job] {
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar"_L1, QString(), QJsonObject{{"url"_L1, job->contentUri().toString()}});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
||||
@@ -625,8 +625,8 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
if (const auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
|
||||
return stateEvent->contentPart<QString>("history_visibility"_L1);
|
||||
if (auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
|
||||
return stateEvent->contentJson()["history_visibility"_L1].toString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -883,11 +883,10 @@ void NeoChatRoom::addParent(const QString &parentId, bool canonical, bool setPar
|
||||
|
||||
setState("m.space.parent"_L1, parentId, QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
|
||||
if (!setParentChild) {
|
||||
return;
|
||||
}
|
||||
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parent->setState("m.space.child"_L1, id(), QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
if (setParentChild) {
|
||||
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parent->setState("m.space.child"_L1, id(), QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,27 +937,23 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
|
||||
}
|
||||
setState("m.space.child"_L1, childId, QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}, {"suggested"_L1, suggested}, {"order"_L1, order}});
|
||||
|
||||
if (!setChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (!child->canSendState("m.space.parent"_L1)) {
|
||||
return;
|
||||
}
|
||||
child->setState("m.space.parent"_L1, id(), QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
if (setChildParent) {
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (child->canSendState("m.space.parent"_L1)) {
|
||||
child->setState("m.space.parent"_L1, id(), QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
|
||||
if (!canonical) {
|
||||
return;
|
||||
}
|
||||
// Only one canonical parent can exist so make sure others are set to false.
|
||||
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_L1);
|
||||
for (const auto &parentEvent : parentEvents) {
|
||||
if (!parentEvent->contentPart<bool>("canonical"_L1)) {
|
||||
continue;
|
||||
if (canonical) {
|
||||
// Only one canonical parent can exist so make sure others are set to false.
|
||||
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_L1);
|
||||
for (const auto &parentEvent : parentEvents) {
|
||||
if (parentEvent->contentPart<bool>("canonical"_L1)) {
|
||||
auto content = parentEvent->contentJson();
|
||||
content.insert("canonical"_L1, false);
|
||||
setState("m.space.parent"_L1, parentEvent->stateKey(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto content = parentEvent->contentJson();
|
||||
content.insert("canonical"_L1, false);
|
||||
setState("m.space.parent"_L1, parentEvent->stateKey(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,14 +968,12 @@ void NeoChatRoom::removeChild(const QString &childId, bool unsetChildParent)
|
||||
}
|
||||
setState("m.space.child"_L1, childId, {});
|
||||
|
||||
if (!unsetChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (!child->canSendState("m.space.parent"_L1) || !child->currentState().contains("m.space.parent"_L1, id())) {
|
||||
return;
|
||||
if (unsetChildParent) {
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (child->canSendState("m.space.parent"_L1) && child->currentState().contains("m.space.parent"_L1, id())) {
|
||||
child->setState("m.space.parent"_L1, id(), {});
|
||||
}
|
||||
}
|
||||
child->setState("m.space.parent"_L1, id(), {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,8 +1105,10 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
connection()->callApi<SetPushRuleJob>("override"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("override"_L1, id(), true).onResult([this]() {
|
||||
auto job = connection()->callApi<SetPushRuleJob>("override"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("override"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1136,8 +1131,10 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
// No conditions for a room rule
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true).onResult([this]() {
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1165,8 +1162,10 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true).onResult([this]() {
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1233,15 +1232,20 @@ void NeoChatRoom::updatePushNotificationState(QString type)
|
||||
|
||||
void NeoChatRoom::reportEvent(const QString &eventId, const QString &reason)
|
||||
{
|
||||
auto job = connection()->callApi<ReportContentJob>(id(), eventId, -50, reason).onResult([this]() {
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
auto job = connection()->callApi<ReportContentJob>(id(), eventId, -50, reason);
|
||||
connect(job, &BaseJob::finished, this, [this, job]() {
|
||||
if (job->error() == BaseJob::Success) {
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId)
|
||||
{
|
||||
if (const auto evtIt = findInTimeline(eventId); evtIt != messageEvents().rend() && is<RoomEvent>(**evtIt)) {
|
||||
return QJsonDocument(evtIt->viewAs<RoomEvent>()->fullJson()).toJson();
|
||||
auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomEvent>();
|
||||
return QJsonDocument(event->fullJson()).toJson();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -1529,11 +1533,13 @@ void NeoChatRoom::download(const QString &eventId, const QUrl &localFilename)
|
||||
|
||||
void NeoChatRoom::mapAlias(const QString &alias)
|
||||
{
|
||||
connection()->callApi<GetLocalAliasesJob>(id()).onResult([this, alias](const auto &job) {
|
||||
if (job->aliases().contains(alias)) {
|
||||
auto getLocalAliasesJob = connection()->callApi<GetLocalAliasesJob>(id());
|
||||
connect(getLocalAliasesJob, &BaseJob::success, this, [this, getLocalAliasesJob, alias] {
|
||||
if (getLocalAliasesJob->aliases().contains(alias)) {
|
||||
return;
|
||||
} else {
|
||||
connection()->callApi<SetRoomAliasJob>(alias, id()).onResult([this, alias] {
|
||||
auto setRoomAliasJob = connection()->callApi<SetRoomAliasJob>(alias, id());
|
||||
connect(setRoomAliasJob, &BaseJob::success, this, [this, alias] {
|
||||
auto newAltAliases = altAliases();
|
||||
newAltAliases.append(alias);
|
||||
setLocalAliases(newAltAliases);
|
||||
@@ -1625,25 +1631,23 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
connection()
|
||||
->callApi<GetOneRoomEventJob>(id(), eventId)
|
||||
.then(
|
||||
[this, eventId](const auto &job) {
|
||||
// The event may have arrived in the meantime so check it's not in the timeline.
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
auto job = connection()->callApi<GetOneRoomEventJob>(id(), eventId);
|
||||
connect(job, &BaseJob::success, this, [this, job, eventId] {
|
||||
// The event may have arrived in the meantime so check it's not in the timeline.
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||
m_extraEvents.push_back(std::move(event));
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
},
|
||||
[this, eventId](const auto &job) {
|
||||
if (job->error() == BaseJob::NotFound) {
|
||||
Q_EMIT extraEventNotFound(eventId);
|
||||
}
|
||||
});
|
||||
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||
m_extraEvents.push_back(std::move(event));
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
});
|
||||
connect(job, &BaseJob::failure, this, [this, job, eventId] {
|
||||
if (job->error() == BaseJob::NotFound) {
|
||||
Q_EMIT extraEventNotFound(eventId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString &eventId) const
|
||||
|
||||
@@ -112,7 +112,7 @@ Kirigami.ScrollablePage {
|
||||
* @brief Force the search to be updated if the model has a valid search function.
|
||||
*/
|
||||
function updateSearch() {
|
||||
root.model.search();
|
||||
searchTimer.restart();
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
@@ -142,13 +142,10 @@ Kirigami.ScrollablePage {
|
||||
Layout.fillWidth: true
|
||||
Keys.onEnterPressed: searchButton.clicked()
|
||||
Keys.onReturnPressed: searchButton.clicked()
|
||||
onTextChanged: root.model.searchText = text
|
||||
onAccepted: {
|
||||
// If the text is empty, call the search model immediately because it will early-return.
|
||||
if (root.model.searchText.length === 0) {
|
||||
root.model.search();
|
||||
} else {
|
||||
searchTimer.restart();
|
||||
onTextChanged: {
|
||||
searchTimer.restart();
|
||||
if (root.model) {
|
||||
root.model.searchText = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +158,6 @@ Kirigami.ScrollablePage {
|
||||
|
||||
onClicked: {
|
||||
if (typeof root.model.search === 'function') {
|
||||
searchTimer.stop();
|
||||
root.model.search();
|
||||
}
|
||||
}
|
||||
@@ -191,7 +187,6 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noSearchMessage
|
||||
icon.name: "search"
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
|
||||
helpfulAction: root.noSearchHelpfulAction
|
||||
@@ -199,9 +194,8 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noResultMessage
|
||||
icon.name: "search"
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && (!root.model.searching && !searchTimer.running) && customPlaceholder.text.length === 0
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
|
||||
helpfulAction: root.noResultHelpfulAction
|
||||
}
|
||||
|
||||
@@ -214,7 +208,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && (root.model.searching || searchTimer.running) && customPlaceholder.text.length === 0
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
|
||||
}
|
||||
|
||||
Keys.onUpPressed: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user