diff --git a/resources/icons/ui/refresh.png b/resources/icons/ui/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..642682032ab6d6ff73b0769bd2c420e89281d8a9 --- /dev/null +++ b/resources/icons/ui/refresh.png @@ -0,0 +1,6 @@ +‰PNG + +��� IHDR���@���@���ªiqÞ��� pHYs������rçn���tEXtSoftware�www.inkscape.org›î<��fIDATxœíÛO¨UUÇñÏõ)ùÊY”¤Yf`Ñrâ ÈI4h`“jPTHƒÄH‰ µ‚Ê(Ò0I¡À¢?Ð$5eÑÀHld†¾²’|¥ñÌ÷ž ö½ê»ïþ9ûœ³¯÷Áý‚÷àìu×oÝskí³vEZúp=nÃÍX€y¸S0#8Ž¿ñ;àG|‡=8–8ÆÒ™ŠåxàtÁ><+$²«Yˆ8ª˜èVö-Ââ\„;qIc¸Û1,ðzÀjôGÄYÁËú8Š¥¹W™Šç0T²¸;„{«âÚ‰ßÒ`ý¾¼â—«ó%¼Þvcnño6Y7+¼‚•8Ù¢ëíOÜSï$lk³.3“±µ„¶²Q¬¾¨f·}®LÃg] 0«mÇ[¯mK?>ïQIlrñSð1îÈ’©Œü+<@«6Œé¸T¨g”øY…ÉòjgCøD(`åq+æá>¼+”ǩ<ZÐñ�žÀ¬6‚[ÑGðC§°H¸Uó8ü«„B©,ú°BñÞ"SúðMNg;qUyºÇq¹ðsJš€Çr:Ú$`¥Ð)–ž€™BOã`¤PÙ‚Šòê’1<¹xTxHu’ +6GÆ™)ÓÄ÷òëR©lBÙâÇ$àñÈ…_èÜož4âÇ$àûˆEƒBÁÒ)R‰?“€["½Në8*x=2¾èlˆXpRó͇¬‰ˆ-wbnÿ·Óimȯ±åJÀg7 +³ØMIåŽg "¶X;< Wj¿©Xc'ö— *†×úÞX¾Fx{ÓŠcÂÃïP€š±wᢒüÀ|T’¿=zôèÑ£GŽZ)œ¢Ô܉KòÃÕxZØàmƈðõS¨Iªnku™Ê2²#cl‡kRöÛ¿%Úˆdoí÷65ç$h$¡ïF<){k¿»öGÊÝ–5Å0WÜèΙö?•ø²eðRDlÎ-…øÍ:+~¾°UŸ5¾µç.žèâûðeD|§Ô½Ó˜È≟ù^½ƒ‰,~EdŒ£¸µÞIâ¿ÒyñŠŸO~¿‘£²î€MÚ@•Á$¼˜#¾!¡LG™Ï€¯…éñTÌ—0bíxw2pZ8ý±–£aàj•0€•'¦½Â¨oCÊN@ÍŽà\V@øÅBCU¤_Ôæ´IªÔìv ÃW‹µª˜ŒñpuÍ?{w·_©^ØI†pPøVÎYU[¨ÜùÂÕx¥ÝE©ï€óeë³f)‹³-ø D•.>K¶9{�a}ˆkeÃBuE+‡o_áÝ/®óê” `Y¬xš—“Ä×X ÿ<q +Û%Ìçb_¤ø}ÂH}ìhm™vD8[Pˆ¥Â‰«ÓB·´A\c3K˜ù«AYmP8N[ä,ÂfÇI¯+àcºp@"åyŸðT5Þ®f ^UÎIƒB§¹L‚ÑÜNôðsp»0^wPíÍGÍÊÕBU8€_ð³ÐÀìþŸŒÿ©žÉ¾æ¯����IEND®B`‚ \ No newline at end of file diff --git a/resources/icons/ui/refresh.svg b/resources/icons/ui/refresh.svg new file mode 100644 index 0000000000000000000000000000000000000000..17c41496b761b090e2df9e1c8d5e4aa5bed042e6 --- /dev/null +++ b/resources/icons/ui/refresh.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + viewBox="-10 0 1792 1792" + id="svg866" + width="1792" + height="1792" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs870" /> + <path + fill="currentColor" + d="m 1629,1056 q 0,5 -1,7 -64,268 -268,434.5 Q 1156,1664 882,1664 736,1664 599.5,1609 463,1554 356,1452 l -129,129 q -19,19 -45,19 -26,0 -45,-19 -19,-19 -19,-45 v -448 q 0,-26 19,-45 19,-19 45,-19 h 448 q 26,0 45,19 19,19 19,45 0,26 -19,45 l -137,137 q 71,66 161,102 90,36 187,36 134,0 250,-65 116,-65 186,-179 11,-17 53,-117 8,-23 30,-23 h 192 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 25,-800 v 448 q 0,26 -19,45 -19,19 -45,19 h -448 q -26,0 -45,-19 -19,-19 -19,-45 0,-26 19,-45 L 1235,521 Q 1087,384 886,384 q -134,0 -250,65 -116,65 -186,179 -11,17 -53,117 -8,23 -30,23 H 168 q -13,0 -22.5,-9.5 Q 136,749 136,736 v -7 Q 201,461 406,294.5 611,128 886,128 q 146,0 284,55.5 138,55.5 245,156.5 l 130,-129 q 19,-19 45,-19 26,0 45,19 19,19 19,45 z" + id="path864" /> +</svg> diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 9bf548e3cfdafa595a66bed275c169c1b1ae2799..d5442382f5b24692bb277e87522ef77db41ffc35 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 a001a92907160afc16b8863d5bc2c76905a7f77b..3bd301f7afc73b49e8508c54c41c6058f096f91d 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -72,6 +72,7 @@ <file>icons/ui/screen-share.png</file> <file>icons/ui/toggle-camera-view.png</file> <file>icons/ui/video-call.png</file> + <file>icons/ui/refresh.png</file> <file>icons/emoji-categories/people.png</file> <file>icons/emoji-categories/people@2x.png</file> <file>icons/emoji-categories/nature.png</file> diff --git a/src/Cache.cpp b/src/Cache.cpp index b124fe5e567f7d2cf62ad017acf88052650a2289..ee0ca0c22f3f9ed7f3da1131bc92fa58c9835cf8 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<std::string> &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 a15010e6db834a267ac86e83cff5799d98055671..52375d38774cf04a5616c08a9c70095a9425da57 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<std::string> &user_ids); void markUserKeysOutOfDate(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids, diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 58150ed799f4840a31425c786bd95f03d60c9aba..31ae9f8b83561be7d67ff4d209eadf248e19f989 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<int>(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,39 +173,56 @@ UserProfile::fetchDeviceList(const QString &userID) nhlog::net()->warn("failed to query device keys: {},{}", mtx::errors::to_string(err->matrix_error.errcode), static_cast<int>(err->status_code)); - return; } - this->hasMasterKey = !other_user_keys.master_keys.keys.empty(); + emit verificationStatiChanged(); + }); + }); +} - std::vector<DeviceInfo> deviceInfo; - auto devices = other_user_keys.device_keys; - auto verificationStatus = cache::client()->verificationStatus(other_user_id); +void +UserProfile::updateVerificationStatus() +{ + if (!cache::client() || !cache::client()->isDatabaseReady()) + return; - isUserVerified = verificationStatus.user_verified; - emit userStatusChanged(); + 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; + } - for (const auto &d : devices) { - auto device = d.second; - verification::Status verified = verification::Status::UNVERIFIED; + this->hasMasterKey = !user_keys->master_keys.keys.empty(); - 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; + std::vector<DeviceInfo> deviceInfo; + auto devices = user_keys->device_keys; + auto verificationStatus = cache::client()->verificationStatus(userid_.toStdString()); - deviceInfo.push_back( - {QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name), - verified}); - } + this->isUserVerified = verificationStatus.user_verified; + emit userStatusChanged(); - this->deviceList_.queueReset(std::move(deviceInfo)); - emit devicesChanged(); - }); - }); + 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 diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index a148c431a389a709147c0b8049ccc3e9f2151cfa..68f9c21b30d43bab02aca75f761db9eb4fa5e775 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);