diff --git a/resources/icons/ui/refresh.png b/resources/icons/ui/refresh.png new file mode 100644 index 00000000..64268203 Binary files /dev/null and b/resources/icons/ui/refresh.png differ diff --git a/resources/icons/ui/refresh.svg b/resources/icons/ui/refresh.svg new file mode 100644 index 00000000..17c41496 --- /dev/null +++ b/resources/icons/ui/refresh.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 9bf548e3..d5442382 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -249,6 +249,14 @@ ApplicationWindow { visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() } + ImageButton { + image: ":/icons/icons/ui/refresh.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Refresh device list.") + onClicked: profile.refreshDevices(); + } + } } @@ -264,6 +272,9 @@ ApplicationWindow { delegate: RowLayout { + required property int verificationStatus + required property string deviceId + required property string deviceName width: devicelist.width spacing: 4 @@ -276,7 +287,7 @@ ApplicationWindow { elide: Text.ElideRight font.bold: true color: Nheko.colors.text - text: model.deviceId + text: deviceId } Text { @@ -284,7 +295,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight elide: Text.ElideRight color: Nheko.colors.text - text: model.deviceName + text: deviceName } } @@ -292,19 +303,30 @@ ApplicationWindow { Image { Layout.preferredHeight: 16 Layout.preferredWidth: 16 - source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red")) + source: { + switch (verificationStatus){ + case VerificationStatus.VERIFIED: + return "image://colorimage/:/icons/icons/ui/lock.png?green"; + case VerificationStatus.UNVERIFIED: + return "image://colorimage/:/icons/icons/ui/unlock.png?yellow"; + case VerificationStatus.SELF: + return "image://colorimage/:/icons/icons/ui/checkmark.png?green"; + default: + return "image://colorimage/:/icons/icons/ui/unlock.png?red"; + } + } } Button { id: verifyButton - visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled)) - text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") + visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) + text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") onClicked: { - if (model.verificationStatus == VerificationStatus.VERIFIED) - profile.unverify(model.deviceId); + if (verificationStatus == VerificationStatus.VERIFIED) + profile.unverify(deviceId); else - profile.verify(model.deviceId); + profile.verify(deviceId); } } diff --git a/resources/res.qrc b/resources/res.qrc index a001a929..3bd301f7 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -72,6 +72,7 @@ icons/ui/screen-share.png icons/ui/toggle-camera-view.png icons/ui/video-call.png + icons/ui/refresh.png icons/emoji-categories/people.png icons/emoji-categories/people@2x.png icons/emoji-categories/nature.png diff --git a/src/Cache.cpp b/src/Cache.cpp index b124fe5e..ee0ca0c2 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -4045,6 +4045,16 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query } } +void +Cache::markUserKeysOutOfDate(const std::vector &user_ids) +{ + auto currentBatchToken = nextBatchToken(); + auto txn = lmdb::txn::begin(env_); + auto db = getUserKeysDb(txn); + markUserKeysOutOfDate(txn, db, user_ids, currentBatchToken); + txn.commit(); +} + void Cache::markUserKeysOutOfDate(lmdb::txn &txn, lmdb::dbi &db, diff --git a/src/Cache_p.h b/src/Cache_p.h index a15010e6..52375d38 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -50,6 +50,7 @@ public: 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 &user_ids); void markUserKeysOutOfDate(lmdb::txn &txn, lmdb::dbi &db, const std::vector &user_ids, diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 58150ed7..31ae9f8b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -34,6 +34,7 @@ UserProfile::UserProfile(QString roomid, this, &UserProfile::setGlobalUsername, Qt::QueuedConnection); + connect(this, &UserProfile::verificationStatiChanged, &UserProfile::updateVerificationStatus); if (isGlobalUserProfile()) { getGlobalProfileData(); @@ -48,22 +49,7 @@ UserProfile::UserProfile(QString roomid, if (user_id != this->userid_.toStdString()) return; - auto status = cache::verificationStatus(user_id); - if (!status) - return; - this->isUserVerified = status->user_verified; - emit userStatusChanged(); - - for (auto &deviceInfo : deviceList_.deviceList_) { - deviceInfo.verification_status = - std::find(status->verified_devices.begin(), - status->verified_devices.end(), - deviceInfo.device_id.toStdString()) == status->verified_devices.end() - ? verification::UNVERIFIED - : verification::VERIFIED; - } - deviceList_.reset(deviceList_.deviceList_); - emit devicesChanged(); + emit verificationStatiChanged(); }); fetchDeviceList(this->userid_); } @@ -151,6 +137,13 @@ UserProfile::isSelf() const return this->userid_ == utils::localUser(); } +void +UserProfile::refreshDevices() +{ + cache::client()->markUserKeysOutOfDate({this->userid_.toStdString()}); + fetchDeviceList(this->userid_); +} + void UserProfile::fetchDeviceList(const QString &userID) { @@ -161,20 +154,18 @@ UserProfile::fetchDeviceList(const QString &userID) cache::client()->query_keys( userID.toStdString(), - [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys, + [other_user_id = userID.toStdString(), this](const UserKeyCache &, mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", mtx::errors::to_string(err->matrix_error.errcode), static_cast(err->status_code)); - return; } // Ensure local key cache is up to date cache::client()->query_keys( utils::localUser().toStdString(), - [other_user_id, other_user_keys, this](const UserKeyCache &, - mtx::http::RequestErr err) { + [this](const UserKeyCache &, mtx::http::RequestErr err) { using namespace mtx; std::string local_user_id = utils::localUser().toStdString(); @@ -182,41 +173,58 @@ UserProfile::fetchDeviceList(const QString &userID) nhlog::net()->warn("failed to query device keys: {},{}", mtx::errors::to_string(err->matrix_error.errcode), static_cast(err->status_code)); - return; } - this->hasMasterKey = !other_user_keys.master_keys.keys.empty(); - - std::vector deviceInfo; - auto devices = other_user_keys.device_keys; - auto verificationStatus = cache::client()->verificationStatus(other_user_id); - - isUserVerified = verificationStatus.user_verified; - emit userStatusChanged(); - - for (const auto &d : devices) { - auto device = d.second; - verification::Status verified = verification::Status::UNVERIFIED; - - if (std::find(verificationStatus.verified_devices.begin(), - verificationStatus.verified_devices.end(), - device.device_id) != verificationStatus.verified_devices.end() && - mtx::crypto::verify_identity_signature( - device, DeviceId(device.device_id), UserId(other_user_id))) - verified = verification::Status::VERIFIED; - - deviceInfo.push_back( - {QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name), - verified}); - } - - this->deviceList_.queueReset(std::move(deviceInfo)); - emit devicesChanged(); + emit verificationStatiChanged(); }); }); } +void +UserProfile::updateVerificationStatus() +{ + if (!cache::client() || !cache::client()->isDatabaseReady()) + return; + + auto user_keys = cache::client()->userKeys(userid_.toStdString()); + if (!user_keys) { + this->hasMasterKey = false; + this->isUserVerified = crypto::Trust::Unverified; + this->deviceList_.reset({}); + emit userStatusChanged(); + return; + } + + this->hasMasterKey = !user_keys->master_keys.keys.empty(); + + std::vector deviceInfo; + auto devices = user_keys->device_keys; + auto verificationStatus = cache::client()->verificationStatus(userid_.toStdString()); + + this->isUserVerified = verificationStatus.user_verified; + emit userStatusChanged(); + + for (const auto &d : devices) { + auto device = d.second; + verification::Status verified = + std::find(verificationStatus.verified_devices.begin(), + verificationStatus.verified_devices.end(), + device.device_id) == verificationStatus.verified_devices.end() + ? verification::UNVERIFIED + : verification::VERIFIED; + + if (isSelf() && device.device_id == ::http::client()->device_id()) + verified = verification::Status::SELF; + + deviceInfo.push_back({QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name), + verified}); + } + + this->deviceList_.queueReset(std::move(deviceInfo)); + emit devicesChanged(); +} + void UserProfile::banUser() { diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index a148c431..68f9c21b 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -18,6 +18,7 @@ Q_NAMESPACE enum Status { + SELF, VERIFIED, UNVERIFIED, BLOCKED @@ -118,6 +119,7 @@ public: Q_INVOKABLE void verify(QString device = ""); Q_INVOKABLE void unverify(QString device = ""); Q_INVOKABLE void fetchDeviceList(const QString &userID); + Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void banUser(); // Q_INVOKABLE void ignoreUser(); Q_INVOKABLE void kickUser(); @@ -135,11 +137,15 @@ signals: void globalUsernameRetrieved(const QString &globalUser); void devicesChanged(); + // internal + void verificationStatiChanged(); + public slots: void updateAvatarUrl(); -protected slots: +private slots: void setGlobalUsername(const QString &globalUser); + void updateVerificationStatus(); private: void updateRoomMemberState(mtx::events::state::Member member);