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 {
id: memberList
clip: true
spacing: Nheko.paddingMedium
boundsBehavior: Flickable.StopAtBounds
model: members
......@@ -85,67 +84,86 @@ ApplicationWindow {
enabled: !Settings.mobileMode
}
delegate: RowLayout {
delegate: ItemDelegate {
id: del
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
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 {
id: avatar
RowLayout {
id: memberLayout
width: Nheko.avatarSize
height: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
}
spacing: Nheko.paddingMedium
anchors.centerIn: parent
width: parent.width - Nheko.paddingSmall * 2
ColumnLayout {
spacing: Nheko.paddingSmall
Avatar {
id: avatar
ElidedLabel {
fullText: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
font.pixelSize: fontMetrics.font.pixelSize
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
width: Nheko.avatarSize
height: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName
enabled: false
}
ElidedLabel {
fullText: model.mxid
color: Nheko.colors.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
ColumnLayout {
spacing: Nheko.paddingSmall
ElidedLabel {
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 {
Layout.fillHeight: true
Layout.fillWidth: true
}
}
EncryptionIndicator {
id: encryptInd
Layout.alignment: Qt.AlignRight
visible: room.isEncrypted
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
ToolTip.text: {
if (!encrypted)
return qsTr("This room is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("This user is verified.");
case Crypto.TOFU:
return qsTr("This user isn't verified, but is still using the same master key from the first time you met.");
default:
return qsTr("This user has unverified devices!");
EncryptionIndicator {
id: encryptInd
Layout.alignment: Qt.AlignRight
visible: room.isEncrypted
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
ToolTip.text: {
if (!encrypted)
return qsTr("This room is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("This user is verified.");
case Crypto.TOFU:
return qsTr("This user isn't verified, but is still using the same master key from the first time you met.");
default:
return qsTr("This user has unverified devices!");
}
}
}
}
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
......
......@@ -161,7 +161,7 @@ Popup {
Image {
Layout.preferredWidth: 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 {
......
......@@ -17,6 +17,7 @@
<file>icons/ui/microphone-mute.svg</file>
<file>icons/ui/microphone-unmute.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/power-off.svg</file>
<file>icons/ui/refresh.svg</file>
......
......@@ -42,8 +42,8 @@ class BlurhashProvider
{
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
QQuickImageResponse *
requestImageResponse(const QString &id, const QSize &requestedSize) override
{
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response);
......
......@@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn,
auto 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());
} else {
......@@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex,
try {
MemberInfo tmp = json::parse(user_data);
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) {
nhlog::db()->warn("{}", e.what());
}
......@@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info)
{
j["name"] = info.name;
j["avatar_url"] = info.avatar_url;
if (info.is_direct)
j["is_direct"] = info.is_direct;
}
void
......@@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info)
{
info.name = j.at("name");
info.avatar_url = j.at("avatar_url");
info.is_direct = j.value("is_direct", false);
}
void
......
......@@ -26,6 +26,7 @@ struct RoomMember
{
QString user_id;
QString display_name;
bool is_direct = false;
};
//! Used to uniquely identify a list of read receipts.
......@@ -98,6 +99,7 @@ struct MemberInfo
{
std::string name;
std::string avatar_url;
bool is_direct = false;
};
void
......
......@@ -47,9 +47,8 @@ public:
std::string statusMessage(const std::string &user_id);
// user cache stores user keys
std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys(
const std::string &room_id,
bool verified_only);
std::map<std::string, std::optional<UserKeyCache>>
getMembersWithKeys(const std::string &room_id, bool verified_only);
void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
void markUserKeysOutOfDate(const std::vector<std::string> &user_ids);
void markUserKeysOutOfDate(lmdb::txn &txn,
......@@ -90,8 +89,8 @@ public:
//! Get a specific state event
template<typename T>
std::optional<mtx::events::StateEvent<T>> getStateEvent(const std::string &room_id,
std::string_view state_key = "")
std::optional<mtx::events::StateEvent<T>>
getStateEvent(const std::string &room_id, std::string_view state_key = "")
{
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
return getStateEvent<T>(txn, room_id, state_key);
......@@ -99,14 +98,12 @@ public:
//! retrieve a specific event from account data
//! pass empty room_id for global account data
std::optional<mtx::events::collections::RoomAccountDataEvents> getAccountData(
mtx::events::EventType type,
const std::string &room_id = "");
std::optional<mtx::events::collections::RoomAccountDataEvents>
getAccountData(mtx::events::EventType type, const std::string &room_id = "");
//! Retrieve member info from a room.
std::vector<RoomMember> getMembers(const std::string &room_id,
std::size_t startIndex = 0,
std::size_t len = 30);
std::vector<RoomMember>
getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
std::vector<RoomMember> getMembersFromInvite(const std::string &room_id,
std::size_t startIndex = 0,
......@@ -186,8 +183,8 @@ public:
uint64_t index = std::numeric_limits<uint64_t>::max(),
bool forward = false);
std::optional<mtx::events::collections::TimelineEvent> getEvent(const std::string &room_id,
const std::string &event_id);
std::optional<mtx::events::collections::TimelineEvent>
getEvent(const std::string &room_id, const std::string &event_id);
void storeEvent(const std::string &room_id,
const std::string &event_id,
const mtx::events::collections::TimelineEvent &event);
......@@ -203,9 +200,8 @@ public:
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> getEventIndex(const std::string &room_id, std::string_view event_id);
std::optional<std::pair<uint64_t, std::string>> lastInvisibleEventAfter(
const std::string &room_id,
std::string_view event_id);
std::optional<std::pair<uint64_t, std::string>>
lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id);
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);
......@@ -213,8 +209,8 @@ public:
uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res);
void savePendingMessage(const std::string &room_id,
const mtx::events::collections::TimelineEvent &message);
std::optional<mtx::events::collections::TimelineEvent> firstPendingMessage(
const std::string &room_id);
std::optional<mtx::events::collections::TimelineEvent>
firstPendingMessage(const std::string &room_id);
void removePendingStatus(const std::string &room_id, const std::string &txn_id);
//! clear timeline keeping only the latest batch
......@@ -228,14 +224,14 @@ public:
std::vector<std::string> getParentRoomIds(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::optional<bool> stickers);
std::vector<ImagePackInfo>
getImagePacks(const std::string &room_id, std::optional<bool> stickers);
//! Mark a room that uses e2e encryption.
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool isRoomEncrypted(const std::string &room_id);
std::optional<mtx::events::state::Encryption> roomEncryptionSettings(
const std::string &room_id);
std::optional<mtx::events::state::Encryption>
roomEncryptionSettings(const std::string &room_id);
//! Check if a user is a member of the room.
bool isRoomMember(const std::string &user_id, const std::string &room_id);
......@@ -273,8 +269,8 @@ public:
mtx::crypto::OlmSessionPtr session,
uint64_t timestamp);
std::vector<std::string> getOlmSessions(const std::string &curve25519);
std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
const std::string &session_id);
std::optional<mtx::crypto::OlmSessionPtr>
getOlmSession(const std::string &curve25519, const std::string &session_id);
std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(const std::string &curve25519);
void saveOlmAccount(const std::string &pickled);
......@@ -331,8 +327,8 @@ private:
const QList<mtx::responses::Notification> &res);
//! Get timeline items that a user was mentions in for a given room
mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
const std::string &room_id);
mtx::responses::Notifications
getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
......@@ -447,44 +443,43 @@ private:
}
template<typename T>
std::optional<mtx::events::StateEvent<T>> getStateEvent(lmdb::txn &txn,
const std::string &room_id,
std::string_view state_key = "")
std::optional<mtx::events::StateEvent<T>>
getStateEvent(lmdb::txn &txn, const std::string &room_id, std::string_view state_key = "")
{
constexpr auto type = mtx::events::state_content_to_type<T>;
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);
try {
constexpr auto type = mtx::events::state_content_to_type<T>;
static_assert(type != mtx::events::EventType::Unsupported,
"Not a supported type in state events.");
std::string_view value;
if (state_key.empty()) {
auto db = getStatesDb(txn, room_id);
if (!db.get(txn, typeStr, value)) {
if (room_id.empty())
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;
const auto typeStr = to_string(type);
auto cursor = lmdb::cursor::open(txn, db);
if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
return std::nullopt;
std::string_view value;
if (state_key.empty()) {
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 {
auto eventsDb = getEventsDb(txn, room_id);
if (!eventsDb.get(txn, json::parse(data)["id"].get<std::string>(), value))
try {
auto eventsDb = getEventsDb(txn, room_id);
if (!eventsDb.get(txn, json::parse(data)["id"].get<std::string>(), value))
return std::nullopt;
} catch (std::exception &e) {
return std::nullopt;
} catch (std::exception &e) {
return std::nullopt;
}
}
}
try {
return json::parse(value).get<mtx::events::StateEvent<T>>();
} catch (std::exception &e) {
return std::nullopt;
......@@ -492,8 +487,8 @@ private:
}
template<typename T>
std::vector<mtx::events::StateEvent<T>> getStateEventsWithType(lmdb::txn &txn,
const std::string &room_id)
std::vector<mtx::events::StateEvent<T>>
getStateEventsWithType(lmdb::txn &txn, const std::string &room_id)
{
constexpr auto type = mtx::events::state_content_to_type<T>;
......@@ -531,16 +526,16 @@ private:
return events;
}
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void
saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void savePresence(
lmdb::txn &txn,
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
void
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (const auto &room : rooms) {
removeRoom(txn, room.first);
......
......@@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
this,
&ChatPage::initializeViews,
view_manager_,
[this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); },
[this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); },
Qt::QueuedConnection);
connect(this,
&ChatPage::initializeEmptyViews,
......@@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
&TimelineViewManager::initializeRoomlist);
connect(
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
view_manager_->sync(rooms);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
view_manager_->sync(sync);
static unsigned int prevNotificationCount = 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;
}
......@@ -583,7 +583,7 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events);
emit initializeViews(std::move(res.rooms));
emit initializeViews(std::move(res));
emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus();
......@@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string
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
// db every 100s
......@@ -660,10 +660,8 @@ ChatPage::trySync()
http::client()->sync(
opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
if (err) {
const auto error = QString::fromStdString(err->matrix_error.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);
const auto error = QString::fromStdString(err->matrix_error.error);
const auto msg = tr("Please try to login again: %1").arg(error);
if ((http::is_logged_in() &&
(err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
......@@ -673,11 +671,7 @@ ChatPage::trySync()
return;
}
nhlog::net()->error("sync error: {} {} {} {}",
err->parse_error,
status_code,
err->error_code,
err_code);
nhlog::net()->error("sync error: {}", *err);
emit tryDelayedSyncCb();
return;
}
......
......@@ -126,10 +126,10 @@ signals:
void newRoom(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 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 notifyMessage(const QString &roomid,
......
......@@ -163,9 +163,8 @@ public:
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QModelIndex index(int row,
int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex
index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override;
public slots:
......
......@@ -61,8 +61,8 @@ const QRegularExpression url_regex(
// vvvv atomic match url -> fail if there is a " before or after vvv
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.
static const QRegularExpression matrixToMarkdownLink(
R"(\[(.*?)(?<!\\)\]\((https://matrix.to/#/.*?\)))");
static const QRegularExpression
matrixToMarkdownLink(R"(\[(.*?)(?<!\\)\]\((https://matrix.to/#/.*?\)))");
static const QRegularExpression matrixToLink(R"(<a href=\"(https://matrix.to/#/.*?)\">(.*?)</a>)");
}
......
......@@ -16,6 +16,10 @@ InviteesModel::InviteesModel(QObject *parent)
void
InviteesModel::addUser(QString mxid)
{
for (const auto &invitee : invitees_)
if (invitee->mxid_ == mxid)
return;
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
auto invitee = new Invitee{mxid, this};
......@@ -30,6 +34,20 @@ InviteesModel::addUser(QString mxid)
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>
InviteesModel::roleNames() const
{
......
......@@ -43,6 +43,7 @@ public:
InviteesModel(QObject *parent = nullptr);
Q_INVOKABLE void addUser(QString mxid);
Q_INVOKABLE void removeUser(QString mxid);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & = QModelIndex()) const override
......
......@@ -50,8 +50,8 @@ public:
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
QQuickImageResponse *
requestImageResponse(const QString &id, const QSize &requestedSize) override
{
auto id_ = id;
bool crop = true;
......
......@@ -57,16 +57,16 @@ public:
static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize();
void openCreateRoomDialog(
std::function<void(const mtx::requests::CreateRoom &request)> callback);
void
openCreateRoomDialog(std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog();
void hideOverlay();
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
void showTransparentOverlayModal(QWidget *content,
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
Qt::AlignHCenter);
void
showTransparentOverlayModal(QWidget *content,
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop | Qt::AlignHCenter);
protected:
void closeEvent(QCloseEvent *event) override;
......
......@@ -6,6 +6,10 @@
#include <mtxclient/http/client.hpp>
#include <curl/curl.h>
#include "Logging.h"
namespace http {
mtx::http::Client *
client();
......@@ -17,3 +21,115 @@ is_logged_in();
void
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
{
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override;
QQuickImageResponse *
requestImageResponse(const QString &id, const QSize &requestedSize) override;
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
static void download(const QString &id,
......
......@@ -27,13 +27,12 @@
#include "Cache.h"
#include "Config.h"
#include "EventAccessors.h"
#include "Logging.h"
#include "MatrixClient.h"
#include "UserSettingsPage.h"
using TimelineEvent = mtx::events::collections::TimelineEvents;
QHash<QString, QString> authorColors_;
template<class T, class Event>
static DescInfo
createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName)
......@@ -661,7 +660,7 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun
// create a hue value based on the hash of the input.
auto userHue = static_cast<int>(hash % 360);
// start with moderate saturation and lightness values.
auto sat = 220;
auto sat = 230;
auto lightness = 125;
// converting to a QColor makes the luminance calc easier.
......@@ -677,10 +676,10 @@ utils::generateContrastingHexColor(const QString &input, const QColor &backgroun
// try again and again until they do by modifying first
// the lightness and then the saturation of the color.
int iterationCount = 9;
while (contrast < 5) {
while (contrast < 4.5) {
// if our lightness is at it's bounds, try changing
// saturation instead.
if (lightness == 242 || lightness == 13) {
if (lightness >= 242 || lightness <= 13) {
qreal newSat = qBound(26.0, sat * 1.25, 242.0);
inputColor.setHsl(userHue, qFloor(newSat), lightness);
......@@ -815,3 +814,65 @@ utils::isReply(const mtx::events::collections::TimelineEvents &e)
{
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 @@
#include <variant>
#include <CacheStructs.h>
#include <QCoreApplication>
#include <QDateTime>
#include <QPixmap>
......@@ -304,4 +305,10 @@ readImage(const QByteArray &data);
bool
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()
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
req.to = this->toClient.to_string();
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 "
"not support this method, so you will need to use the legacy method of "
"key verification.";
"not support this method, so you will need to use the legacy method of "
"key verification.";
// clang-format on
}
send(req);
......