Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • nheko-reborn/nheko
1 result
Show changes
Showing with 369 additions and 150 deletions
...@@ -75,7 +75,6 @@ ApplicationWindow { ...@@ -75,7 +75,6 @@ ApplicationWindow {
id: memberList id: memberList
clip: true clip: true
spacing: Nheko.paddingMedium
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: members model: members
...@@ -85,67 +84,86 @@ ApplicationWindow { ...@@ -85,67 +84,86 @@ ApplicationWindow {
enabled: !Settings.mobileMode enabled: !Settings.mobileMode
} }
delegate: RowLayout { delegate: ItemDelegate {
id: del id: del
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
width: ListView.view.width width: ListView.view.width
spacing: Nheko.paddingMedium height: memberLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true
background: Rectangle {
color: del.hovered ? Nheko.colors.dark : roomMembersRoot.color
}
Avatar { RowLayout {
id: avatar id: memberLayout
width: Nheko.avatarSize spacing: Nheko.paddingMedium
height: Nheko.avatarSize anchors.centerIn: parent
userid: model.mxid width: parent.width - Nheko.paddingSmall * 2
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
}
ColumnLayout { Avatar {
spacing: Nheko.paddingSmall id: avatar
ElidedLabel { width: Nheko.avatarSize
fullText: model.displayName height: Nheko.avatarSize
color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) userid: model.mxid
font.pixelSize: fontMetrics.font.pixelSize url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width displayName: model.displayName
enabled: false
} }
ElidedLabel { ColumnLayout {
fullText: model.mxid spacing: Nheko.paddingSmall
color: Nheko.colors.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) ElidedLabel {
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width fullText: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
font.pixelSize: fontMetrics.font.pixelSize
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
}
ElidedLabel {
fullText: model.mxid
color: del.hovered ? Nheko.colors.brightText : Nheko.colors.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
}
} }
Item { Item {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
} }
} EncryptionIndicator {
id: encryptInd
EncryptionIndicator {
id: encryptInd Layout.alignment: Qt.AlignRight
visible: room.isEncrypted
Layout.alignment: Qt.AlignRight encrypted: room.isEncrypted
visible: room.isEncrypted trust: encrypted ? model.trustlevel : Crypto.Unverified
encrypted: room.isEncrypted ToolTip.text: {
trust: encrypted ? model.trustlevel : Crypto.Unverified if (!encrypted)
ToolTip.text: { return qsTr("This room is not encrypted!");
if (!encrypted)
return qsTr("This room is not encrypted!"); switch (trust) {
case Crypto.Verified:
switch (trust) { return qsTr("This user is verified.");
case Crypto.Verified: case Crypto.TOFU:
return qsTr("This user is verified."); return qsTr("This user isn't verified, but is still using the same master key from the first time you met.");
case Crypto.TOFU: default:
return qsTr("This user isn't verified, but is still using the same master key from the first time you met."); return qsTr("This user has unverified devices!");
default: }
return qsTr("This user has unverified devices!");
} }
} }
}
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
} }
} }
......
...@@ -161,7 +161,7 @@ Popup { ...@@ -161,7 +161,7 @@ Popup {
Image { Image {
Layout.preferredWidth: 22 Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + Nheko.colors.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + Nheko.colors.windowText
} }
ComboBox { ComboBox {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
<file>icons/ui/microphone-mute.svg</file> <file>icons/ui/microphone-mute.svg</file>
<file>icons/ui/microphone-unmute.svg</file> <file>icons/ui/microphone-unmute.svg</file>
<file>icons/ui/pause-symbol.svg</file> <file>icons/ui/pause-symbol.svg</file>
<file>icons/ui/people.svg</file>
<file>icons/ui/play-sign.svg</file> <file>icons/ui/play-sign.svg</file>
<file>icons/ui/power-off.svg</file> <file>icons/ui/power-off.svg</file>
<file>icons/ui/refresh.svg</file> <file>icons/ui/refresh.svg</file>
......
...@@ -42,8 +42,8 @@ class BlurhashProvider ...@@ -42,8 +42,8 @@ class BlurhashProvider
{ {
Q_OBJECT Q_OBJECT
public slots: public slots:
QQuickImageResponse *requestImageResponse(const QString &id, QQuickImageResponse *
const QSize &requestedSize) override requestImageResponse(const QString &id, const QSize &requestedSize) override
{ {
BlurhashResponse *response = new BlurhashResponse(id, requestedSize); BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response); pool.start(response);
......
...@@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn, ...@@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn,
auto display_name = auto display_name =
msg->content.display_name.empty() ? msg->state_key : msg->content.display_name; msg->content.display_name.empty() ? msg->state_key : msg->content.display_name;
MemberInfo tmp{display_name, msg->content.avatar_url}; MemberInfo tmp{display_name, msg->content.avatar_url, msg->content.is_direct};
membersdb.put(txn, msg->state_key, json(tmp).dump()); membersdb.put(txn, msg->state_key, json(tmp).dump());
} else { } else {
...@@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, ...@@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex,
try { try {
MemberInfo tmp = json::parse(user_data); MemberInfo tmp = json::parse(user_data);
members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
QString::fromStdString(tmp.name)}); QString::fromStdString(tmp.name),
tmp.is_direct});
} catch (const json::exception &e) { } catch (const json::exception &e) {
nhlog::db()->warn("{}", e.what()); nhlog::db()->warn("{}", e.what());
} }
...@@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info) ...@@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info)
{ {
j["name"] = info.name; j["name"] = info.name;
j["avatar_url"] = info.avatar_url; j["avatar_url"] = info.avatar_url;
if (info.is_direct)
j["is_direct"] = info.is_direct;
} }
void void
...@@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info) ...@@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info)
{ {
info.name = j.at("name"); info.name = j.at("name");
info.avatar_url = j.at("avatar_url"); info.avatar_url = j.at("avatar_url");
info.is_direct = j.value("is_direct", false);
} }
void void
......
...@@ -26,6 +26,7 @@ struct RoomMember ...@@ -26,6 +26,7 @@ struct RoomMember
{ {
QString user_id; QString user_id;
QString display_name; QString display_name;
bool is_direct = false;
}; };
//! Used to uniquely identify a list of read receipts. //! Used to uniquely identify a list of read receipts.
...@@ -98,6 +99,7 @@ struct MemberInfo ...@@ -98,6 +99,7 @@ struct MemberInfo
{ {
std::string name; std::string name;
std::string avatar_url; std::string avatar_url;
bool is_direct = false;
}; };
void void
......
...@@ -47,9 +47,8 @@ public: ...@@ -47,9 +47,8 @@ public:
std::string statusMessage(const std::string &user_id); std::string statusMessage(const std::string &user_id);
// user cache stores user keys // user cache stores user keys
std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys( std::map<std::string, std::optional<UserKeyCache>>
const std::string &room_id, getMembersWithKeys(const std::string &room_id, bool verified_only);
bool verified_only);
void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
void markUserKeysOutOfDate(const std::vector<std::string> &user_ids); void markUserKeysOutOfDate(const std::vector<std::string> &user_ids);
void markUserKeysOutOfDate(lmdb::txn &txn, void markUserKeysOutOfDate(lmdb::txn &txn,
...@@ -90,8 +89,8 @@ public: ...@@ -90,8 +89,8 @@ public:
//! Get a specific state event //! Get a specific state event
template<typename T> template<typename T>
std::optional<mtx::events::StateEvent<T>> getStateEvent(const std::string &room_id, std::optional<mtx::events::StateEvent<T>>
std::string_view state_key = "") getStateEvent(const std::string &room_id, std::string_view state_key = "")
{ {
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
return getStateEvent<T>(txn, room_id, state_key); return getStateEvent<T>(txn, room_id, state_key);
...@@ -99,14 +98,12 @@ public: ...@@ -99,14 +98,12 @@ public:
//! retrieve a specific event from account data //! retrieve a specific event from account data
//! pass empty room_id for global account data //! pass empty room_id for global account data
std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData( std::optional<mtx::events::collections::RoomAccountDataEvents>
mtx::events::EventType type, getAccountData(mtx::events::EventType type, const std::string &room_id = "");
const std::string &room_id = "");
//! Retrieve member info from a room. //! Retrieve member info from a room.
std::vector<RoomMember> getMembers(const std::string &room_id, std::vector<RoomMember>
std::size_t startIndex = 0, getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
std::size_t len = 30);
std::vector<RoomMember> getMembersFromInvite(const std::string &room_id, std::vector<RoomMember> getMembersFromInvite(const std::string &room_id,
std::size_t startIndex = 0, std::size_t startIndex = 0,
...@@ -186,8 +183,8 @@ public: ...@@ -186,8 +183,8 @@ public:
uint64_t index = std::numeric_limits<uint64_t>::max(), uint64_t index = std::numeric_limits<uint64_t>::max(),
bool forward = false); bool forward = false);
std::optional<mtx::events::collections::TimelineEvent> getEvent(const std::string &room_id, std::optional<mtx::events::collections::TimelineEvent>
const std::string &event_id); getEvent(const std::string &room_id, const std::string &event_id);
void storeEvent(const std::string &room_id, void storeEvent(const std::string &room_id,
const std::string &event_id, const std::string &event_id,
const mtx::events::collections::TimelineEvent &event); const mtx::events::collections::TimelineEvent &event);
...@@ -203,9 +200,8 @@ public: ...@@ -203,9 +200,8 @@ public:
std::optional<TimelineRange> getTimelineRange(const std::string &room_id); std::optional<TimelineRange> getTimelineRange(const std::string &room_id);
std::optional<uint64_t> getTimelineIndex(const std::string &room_id, std::string_view event_id); std::optional<uint64_t> getTimelineIndex(const std::string &room_id, std::string_view event_id);
std::optional<uint64_t> getEventIndex(const std::string &room_id, std::string_view event_id); std::optional<uint64_t> getEventIndex(const std::string &room_id, std::string_view event_id);
std::optional<std::pair<uint64_t, std::string>> lastInvisibleEventAfter( std::optional<std::pair<uint64_t, std::string>>
const std::string &room_id, lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id);
std::string_view event_id);
std::optional<std::string> getTimelineEventId(const std::string &room_id, uint64_t index); std::optional<std::string> getTimelineEventId(const std::string &room_id, uint64_t index);
std::optional<uint64_t> getArrivalIndex(const std::string &room_id, std::string_view event_id); std::optional<uint64_t> getArrivalIndex(const std::string &room_id, std::string_view event_id);
...@@ -213,8 +209,8 @@ public: ...@@ -213,8 +209,8 @@ public:
uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res); uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res);
void savePendingMessage(const std::string &room_id, void savePendingMessage(const std::string &room_id,
const mtx::events::collections::TimelineEvent &message); const mtx::events::collections::TimelineEvent &message);
std::optional<mtx::events::collections::TimelineEvent> firstPendingMessage( std::optional<mtx::events::collections::TimelineEvent>
const std::string &room_id); firstPendingMessage(const std::string &room_id);
void removePendingStatus(const std::string &room_id, const std::string &txn_id); void removePendingStatus(const std::string &room_id, const std::string &txn_id);
//! clear timeline keeping only the latest batch //! clear timeline keeping only the latest batch
...@@ -228,14 +224,14 @@ public: ...@@ -228,14 +224,14 @@ public:
std::vector<std::string> getParentRoomIds(const std::string &room_id); std::vector<std::string> getParentRoomIds(const std::string &room_id);
std::vector<std::string> getChildRoomIds(const std::string &room_id); std::vector<std::string> getChildRoomIds(const std::string &room_id);
std::vector<ImagePackInfo> getImagePacks(const std::string &room_id, std::vector<ImagePackInfo>
std::optional<bool> stickers); getImagePacks(const std::string &room_id, std::optional<bool> stickers);
//! Mark a room that uses e2e encryption. //! Mark a room that uses e2e encryption.
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool isRoomEncrypted(const std::string &room_id); bool isRoomEncrypted(const std::string &room_id);
std::optional<mtx::events::state::Encryption> roomEncryptionSettings( std::optional<mtx::events::state::Encryption>
const std::string &room_id); roomEncryptionSettings(const std::string &room_id);
//! Check if a user is a member of the room. //! Check if a user is a member of the room.
bool isRoomMember(const std::string &user_id, const std::string &room_id); bool isRoomMember(const std::string &user_id, const std::string &room_id);
...@@ -273,8 +269,8 @@ public: ...@@ -273,8 +269,8 @@ public:
mtx::crypto::OlmSessionPtr session, mtx::crypto::OlmSessionPtr session,
uint64_t timestamp); uint64_t timestamp);
std::vector<std::string> getOlmSessions(const std::string &curve25519); std::vector<std::string> getOlmSessions(const std::string &curve25519);
std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519, std::optional<mtx::crypto::OlmSessionPtr>
const std::string &session_id); getOlmSession(const std::string &curve25519, const std::string &session_id);
std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(const std::string &curve25519); std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(const std::string &curve25519);
void saveOlmAccount(const std::string &pickled); void saveOlmAccount(const std::string &pickled);
...@@ -331,8 +327,8 @@ private: ...@@ -331,8 +327,8 @@ private:
const QList<mtx::responses::Notification> &res); const QList<mtx::responses::Notification> &res);
//! Get timeline items that a user was mentions in for a given room //! Get timeline items that a user was mentions in for a given room
mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn, mtx::responses::Notifications
const std::string &room_id); getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
...@@ -447,44 +443,43 @@ private: ...@@ -447,44 +443,43 @@ private:
} }
template<typename T> template<typename T>
std::optional<mtx::events::StateEvent<T>> getStateEvent(lmdb::txn &txn, std::optional<mtx::events::StateEvent<T>>
const std::string &room_id, getStateEvent(lmdb::txn &txn, const std::string &room_id, std::string_view state_key = "")
std::string_view state_key = "")
{ {
constexpr auto type = mtx::events::state_content_to_type<T>; try {
static_assert(type != mtx::events::EventType::Unsupported, constexpr auto type = mtx::events::state_content_to_type<T>;
"Not a supported type in state events."); static_assert(type != mtx::events::EventType::Unsupported,
"Not a supported type in state events.");
if (room_id.empty())
return std::nullopt;
const auto typeStr = to_string(type);
std::string_view value; if (room_id.empty())
if (state_key.empty()) {
auto db = getStatesDb(txn, room_id);
if (!db.get(txn, typeStr, value)) {
return std::nullopt; return std::nullopt;
} const auto typeStr = to_string(type);
} else {
auto db = getStatesKeyDb(txn, room_id);
std::string d = json::object({{"key", state_key}}).dump();
std::string_view data = d;
std::string_view typeStrV = typeStr;
auto cursor = lmdb::cursor::open(txn, db); std::string_view value;
if (!cursor.get(typeStrV, data, MDB_GET_BOTH)) if (state_key.empty()) {
return std::nullopt; auto db = getStatesDb(txn, room_id);
if (!db.get(txn, typeStr, value)) {
return std::nullopt;
}
} else {
auto db = getStatesKeyDb(txn, room_id);
std::string d = json::object({{"key", state_key}}).dump();
std::string_view data = d;
std::string_view typeStrV = typeStr;
auto cursor = lmdb::cursor::open(txn, db);
if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
return std::nullopt;
try { try {
auto eventsDb = getEventsDb(txn, room_id); auto eventsDb = getEventsDb(txn, room_id);
if (!eventsDb.get(txn, json::parse(data)["id"].get<std::string>(), value)) if (!eventsDb.get(txn, json::parse(data)["id"].get<std::string>(), value))
return std::nullopt;
} catch (std::exception &e) {
return std::nullopt; return std::nullopt;
} catch (std::exception &e) { }
return std::nullopt;
} }
}
try {
return json::parse(value).get<mtx::events::StateEvent<T>>(); return json::parse(value).get<mtx::events::StateEvent<T>>();
} catch (std::exception &e) { } catch (std::exception &e) {
return std::nullopt; return std::nullopt;
...@@ -492,8 +487,8 @@ private: ...@@ -492,8 +487,8 @@ private:
} }
template<typename T> template<typename T>
std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(lmdb::txn &txn, std::vector<mtx::events::StateEvent<T>>
const std::string &room_id) getStateEventsWithType(lmdb::txn &txn, const std::string &room_id)
{ {
constexpr auto type = mtx::events::state_content_to_type<T>; constexpr auto type = mtx::events::state_content_to_type<T>;
...@@ -531,16 +526,16 @@ private: ...@@ -531,16 +526,16 @@ private:
return events; return events;
} }
void saveInvites(lmdb::txn &txn, void
const std::map<std::string, mtx::responses::InvitedRoom> &rooms); saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void savePresence( void savePresence(
lmdb::txn &txn, lmdb::txn &txn,
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates); const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
//! Sends signals for the rooms that are removed. //! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn, void
const std::map<std::string, mtx::responses::LeftRoom> &rooms) removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{ {
for (const auto &room : rooms) { for (const auto &room : rooms) {
removeRoom(txn, room.first); removeRoom(txn, room.first);
......
...@@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) ...@@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
this, this,
&ChatPage::initializeViews, &ChatPage::initializeViews,
view_manager_, view_manager_,
[this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); }, [this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); },
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, connect(this,
&ChatPage::initializeEmptyViews, &ChatPage::initializeEmptyViews,
...@@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) ...@@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
&TimelineViewManager::initializeRoomlist); &TimelineViewManager::initializeRoomlist);
connect( connect(
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
view_manager_->sync(rooms); view_manager_->sync(sync);
static unsigned int prevNotificationCount = 0; static unsigned int prevNotificationCount = 0;
unsigned int notificationCount = 0; unsigned int notificationCount = 0;
for (const auto &room : rooms.join) { for (const auto &room : sync.rooms.join) {
notificationCount += room.second.unread_notifications.notification_count; notificationCount += room.second.unread_notifications.notification_count;
} }
...@@ -583,7 +583,7 @@ ChatPage::startInitialSync() ...@@ -583,7 +583,7 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events); olm::handle_to_device_messages(res.to_device.events);
emit initializeViews(std::move(res.rooms)); emit initializeViews(std::move(res));
emit initializeMentions(cache::getTimelineMentions()); emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus(); cache::calculateRoomReadStatus();
...@@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string ...@@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string
auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res));
emit syncUI(res.rooms); emit syncUI(std::move(res));
// if we process a lot of syncs (1 every 200ms), this means we clean the // if we process a lot of syncs (1 every 200ms), this means we clean the
// db every 100s // db every 100s
...@@ -660,10 +660,8 @@ ChatPage::trySync() ...@@ -660,10 +660,8 @@ ChatPage::trySync()
http::client()->sync( http::client()->sync(
opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) { opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
if (err) { if (err) {
const auto error = QString::fromStdString(err->matrix_error.error); const auto error = QString::fromStdString(err->matrix_error.error);
const auto msg = tr("Please try to login again: %1").arg(error); const auto msg = tr("Please try to login again: %1").arg(error);
const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
const int status_code = static_cast<int>(err->status_code);
if ((http::is_logged_in() && if ((http::is_logged_in() &&
(err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
...@@ -673,11 +671,7 @@ ChatPage::trySync() ...@@ -673,11 +671,7 @@ ChatPage::trySync()
return; return;
} }
nhlog::net()->error("sync error: {} {} {} {}", nhlog::net()->error("sync error: {}", *err);
err->parse_error,
status_code,
err->error_code,
err_code);
emit tryDelayedSyncCb(); emit tryDelayedSyncCb();
return; return;
} }
......
...@@ -126,10 +126,10 @@ signals: ...@@ -126,10 +126,10 @@ signals:
void newRoom(const QString &room_id); void newRoom(const QString &room_id);
void changeToRoom(const QString &room_id); void changeToRoom(const QString &room_id);
void initializeViews(const mtx::responses::Rooms &rooms); void initializeViews(const mtx::responses::Sync &rooms);
void initializeEmptyViews(); void initializeEmptyViews();
void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs); void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
void syncUI(const mtx::responses::Rooms &rooms); void syncUI(const mtx::responses::Sync &sync);
void dropToLoginPageCb(const QString &msg); void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid, void notifyMessage(const QString &roomid,
......
...@@ -163,9 +163,8 @@ public: ...@@ -163,9 +163,8 @@ public:
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QModelIndex index(int row, QModelIndex
int column, index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override; QModelIndex parent(const QModelIndex &) const override;
public slots: public slots:
......
...@@ -61,8 +61,8 @@ const QRegularExpression url_regex( ...@@ -61,8 +61,8 @@ const QRegularExpression url_regex(
// vvvv atomic match url -> fail if there is a " before or after vvv // vvvv atomic match url -> fail if there is a " before or after vvv
R"((?<!["'])(?>((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!["']))"); R"((?<!["'])(?>((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!["']))");
// match any markdown matrix.to link. Capture group 1 is the link name, group 2 is the target. // match any markdown matrix.to link. Capture group 1 is the link name, group 2 is the target.
static const QRegularExpression matrixToMarkdownLink( static const QRegularExpression
R"(\[(.*?)(?<!\\)\]\((https://matrix.to/#/.*?\)))"); matrixToMarkdownLink(R"(\[(.*?)(?<!\\)\]\((https://matrix.to/#/.*?\)))");
static const QRegularExpression matrixToLink(R"(<a href=\"(https://matrix.to/#/.*?)\">(.*?)</a>)"); static const QRegularExpression matrixToLink(R"(<a href=\"(https://matrix.to/#/.*?)\">(.*?)</a>)");
} }
......
...@@ -16,6 +16,10 @@ InviteesModel::InviteesModel(QObject *parent) ...@@ -16,6 +16,10 @@ InviteesModel::InviteesModel(QObject *parent)
void void
InviteesModel::addUser(QString mxid) InviteesModel::addUser(QString mxid)
{ {
for (const auto &invitee : invitees_)
if (invitee->mxid_ == mxid)
return;
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
auto invitee = new Invitee{mxid, this}; auto invitee = new Invitee{mxid, this};
...@@ -30,6 +34,20 @@ InviteesModel::addUser(QString mxid) ...@@ -30,6 +34,20 @@ InviteesModel::addUser(QString mxid)
emit countChanged(); emit countChanged();
} }
void
InviteesModel::removeUser(QString mxid)
{
for (int i = 0; i < invitees_.length(); ++i) {
if (invitees_[i]->mxid_ == mxid) {
beginRemoveRows(QModelIndex(), i, i);
invitees_.removeAt(i);
endRemoveRows();
emit countChanged();
break;
}
}
}
QHash<int, QByteArray> QHash<int, QByteArray>
InviteesModel::roleNames() const InviteesModel::roleNames() const
{ {
......
...@@ -43,6 +43,7 @@ public: ...@@ -43,6 +43,7 @@ public:
InviteesModel(QObject *parent = nullptr); InviteesModel(QObject *parent = nullptr);
Q_INVOKABLE void addUser(QString mxid); Q_INVOKABLE void addUser(QString mxid);
Q_INVOKABLE void removeUser(QString mxid);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & = QModelIndex()) const override int rowCount(const QModelIndex & = QModelIndex()) const override
......
...@@ -50,8 +50,8 @@ public: ...@@ -50,8 +50,8 @@ public:
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; } static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
public slots: public slots:
QQuickImageResponse *requestImageResponse(const QString &id, QQuickImageResponse *
const QSize &requestedSize) override requestImageResponse(const QString &id, const QSize &requestedSize) override
{ {
auto id_ = id; auto id_ = id;
bool crop = true; bool crop = true;
......
...@@ -57,16 +57,16 @@ public: ...@@ -57,16 +57,16 @@ public:
static MainWindow *instance() { return instance_; } static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize(); void saveCurrentWindowSize();
void openCreateRoomDialog( void
std::function<void(const mtx::requests::CreateRoom &request)> callback); openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback); void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog(); void openLogoutDialog();
void hideOverlay(); void hideOverlay();
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter); void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
void showTransparentOverlayModal(QWidget *content, void
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | showTransparentOverlayModal(QWidget *content,
Qt::AlignHCenter); QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter);
protected: protected:
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
......
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
#include <mtxclient/http/client.hpp> #include <mtxclient/http/client.hpp>
#include <curl/curl.h>
#include "Logging.h"
namespace http { namespace http {
mtx::http::Client * mtx::http::Client *
client(); client();
...@@ -17,3 +21,115 @@ is_logged_in(); ...@@ -17,3 +21,115 @@ is_logged_in();
void void
init(); init();
} }
template<>
struct fmt::formatter<mtx::http::ClientError>
{
// Presentation format: 'f' - fixed, 'e' - exponential.
bool print_network_error = false;
bool print_http_error = false;
bool print_parser_error = false;
bool print_matrix_error = false;
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin())
{
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
while (it != end && *it != '}') {
auto tmp = *it++;
switch (tmp) {
case 'n':
print_matrix_error = true;
break;
case 'h':
print_matrix_error = true;
break;
case 'p':
print_matrix_error = true;
break;
case 'm':
print_matrix_error = true;
break;
default:
throw format_error("invalid format specifier for mtx error");
}
}
// Check if reached the end of the range:
if (it != end && *it != '}')
throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template<typename FormatContext>
auto format(const mtx::http::ClientError &e, FormatContext &ctx) -> decltype(ctx.out())
{
// ctx.out() is an output iterator to write to.
bool prepend_comma = false;
format_to(ctx.out(), "(");
if (print_network_error || e.error_code) {
format_to(ctx.out(), "connection: {}", e.error_code_string());
prepend_comma = true;
}
if (print_http_error ||
(e.status_code != 0 && (e.status_code < 200 || e.status_code >= 300))) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(), "http: {}", e.status_code);
prepend_comma = true;
}
if (print_parser_error || !e.parse_error.empty()) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(), "parser: {}", e.parse_error);
prepend_comma = true;
}
if (print_parser_error ||
(e.matrix_error.errcode != mtx::errors::ErrorCode::M_UNRECOGNIZED &&
!e.matrix_error.error.empty())) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(),
"matrix: {}:'{}'",
to_string(e.matrix_error.errcode),
e.matrix_error.error);
}
return format_to(ctx.out(), ")");
}
};
template<>
struct fmt::formatter<std::optional<mtx::http::ClientError>> : formatter<mtx::http::ClientError>
{
// parse is inherited from formatter<string_view>.
template<typename FormatContext>
auto format(std::optional<mtx::http::ClientError> c, FormatContext &ctx)
{
if (!c)
return format_to(ctx.out(), "(no error)");
else
return formatter<mtx::http::ClientError>::format(*c, ctx);
}
};
...@@ -49,8 +49,8 @@ class MxcImageProvider ...@@ -49,8 +49,8 @@ class MxcImageProvider
{ {
Q_OBJECT Q_OBJECT
public slots: public slots:
QQuickImageResponse *requestImageResponse(const QString &id, QQuickImageResponse *
const QSize &requestedSize) override; requestImageResponse(const QString &id, const QSize &requestedSize) override;
static void addEncryptionInfo(mtx::crypto::EncryptedFile info); static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
static void download(const QString &id, static void download(const QString &id,
......
...@@ -27,13 +27,12 @@ ...@@ -27,13 +27,12 @@
#include "Cache.h" #include "Cache.h"
#include "Config.h" #include "Config.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
using TimelineEvent = mtx::events::collections::TimelineEvents; using TimelineEvent = mtx::events::collections::TimelineEvents;
QHash<QString, QString> authorColors_;
template<class T, class Event> template<class T, class Event>
static DescInfo static DescInfo
createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName) createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName)
...@@ -661,7 +660,7 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun ...@@ -661,7 +660,7 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun
// create a hue value based on the hash of the input. // create a hue value based on the hash of the input.
auto userHue = static_cast<int>(hash % 360); auto userHue = static_cast<int>(hash % 360);
// start with moderate saturation and lightness values. // start with moderate saturation and lightness values.
auto sat = 220; auto sat = 230;
auto lightness = 125; auto lightness = 125;
// converting to a QColor makes the luminance calc easier. // converting to a QColor makes the luminance calc easier.
...@@ -677,10 +676,10 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun ...@@ -677,10 +676,10 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun
// try again and again until they do by modifying first // try again and again until they do by modifying first
// the lightness and then the saturation of the color. // the lightness and then the saturation of the color.
int iterationCount = 9; int iterationCount = 9;
while (contrast < 5) { while (contrast < 4.5) {
// if our lightness is at it's bounds, try changing // if our lightness is at it's bounds, try changing
// saturation instead. // saturation instead.
if (lightness == 242 || lightness == 13) { if (lightness >= 242 || lightness <= 13) {
qreal newSat = qBound(26.0, sat * 1.25, 242.0); qreal newSat = qBound(26.0, sat * 1.25, 242.0);
inputColor.setHsl(userHue, qFloor(newSat), lightness); inputColor.setHsl(userHue, qFloor(newSat), lightness);
...@@ -815,3 +814,65 @@ utils::isReply(const mtx::events::collections::TimelineEvents &e) ...@@ -815,3 +814,65 @@ utils::isReply(const mtx::events::collections::TimelineEvents &e)
{ {
return mtx::accessors::relations(e).reply_to().has_value(); return mtx::accessors::relations(e).reply_to().has_value();
} }
void
utils::removeDirectFromRoom(QString roomid)
{
http::client()->get_account_data<mtx::events::account_data::Direct>(
[roomid](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) {
if (e && e->status_code == 404)
ev = {};
else if (e) {
nhlog::net()->error("Failed to retrieve m.direct: {}", *e);
return;
}
auto r = roomid.toStdString();
for (auto it = ev.user_to_rooms.begin(); it != ev.user_to_rooms.end();) {
for (auto rit = it->second.begin(); rit != it->second.end();) {
if (r == *rit)
rit = it->second.erase(rit);
else
++rit;
}
if (it->second.empty())
it = ev.user_to_rooms.erase(it);
else
++it;
}
http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) {
if (e)
nhlog::net()->error("Failed to update m.direct: {}", *e);
});
});
}
void
utils::markRoomAsDirect(QString roomid, std::vector<RoomMember> members)
{
http::client()->get_account_data<mtx::events::account_data::Direct>(
[roomid, members](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) {
if (e && e->status_code == 404)
ev = {};
else if (e) {
nhlog::net()->error("Failed to retrieve m.direct: {}", *e);
return;
}
auto local = utils::localUser();
auto r = roomid.toStdString();
for (const auto &m : members) {
if (m.user_id != local) {
ev.user_to_rooms[m.user_id.toStdString()].push_back(r);
}
}
http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) {
if (e)
nhlog::net()->error("Failed to update m.direct: {}", *e);
});
});
}
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <variant> #include <variant>
#include <CacheStructs.h>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDateTime> #include <QDateTime>
#include <QPixmap> #include <QPixmap>
...@@ -304,4 +305,10 @@ readImage(const QByteArray &data); ...@@ -304,4 +305,10 @@ readImage(const QByteArray &data);
bool bool
isReply(const mtx::events::collections::TimelineEvents &e); isReply(const mtx::events::collections::TimelineEvents &e);
void
removeDirectFromRoom(QString roomid);
void
markRoomAsDirect(QString roomid, std::vector<RoomMember> members);
} }
...@@ -662,9 +662,12 @@ DeviceVerificationFlow::sendVerificationRequest() ...@@ -662,9 +662,12 @@ DeviceVerificationFlow::sendVerificationRequest()
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
req.to = this->toClient.to_string(); req.to = this->toClient.to_string();
req.msgtype = "m.key.verification.request"; req.msgtype = "m.key.verification.request";
// clang-format off
// clang-format < 12 is buggy on this
req.body = "User is requesting to verify keys with you. However, your client does " req.body = "User is requesting to verify keys with you. However, your client does "
"not support this method, so you will need to use the legacy method of " "not support this method, so you will need to use the legacy method of "
"key verification."; "key verification.";
// clang-format on
} }
send(req); send(req);
......