feat(chat): make the key-verify lock clickable with a fingerprint dialog
The header key-verify lock was an unexplained hover-only icon. It's now clickable, opening a dialog that shows the contact's identity-key fingerprint and explains comparing it out-of-band, with a Copy-key button and a clearer tooltip. The crypto/verification logic is unchanged (the wording is careful that the lock means "encrypted to this key", since there is no trusted/verified flag). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,8 @@ std::string s_delete_name; // its peer name (for the confirm copy / bl
|
||||
bool s_show_blocked = false; // blocked-conversations manager overlay (unblock)
|
||||
bool s_show_export_confirm = false; // "export as plaintext" confirm overlay
|
||||
std::string s_export_cid; // conversation targeted by the export confirm
|
||||
bool s_show_fingerprint = false; // key-fingerprint verification dialog
|
||||
std::string s_fingerprint_cid; // conversation targeted by the fingerprint dialog
|
||||
|
||||
// Export file path for a conversation (the same scheme the inline export used).
|
||||
static std::string chatExportPathFor(const std::string& peerName) {
|
||||
@@ -1324,13 +1326,22 @@ void RenderChatTab(App* app)
|
||||
// same row. Hidden while renaming (the field takes the whole span).
|
||||
if (hasAddr && !renaming) {
|
||||
float ax = textX + std::min(nameW, nameAvail) + nameSpacing;
|
||||
// Key-verify lock — we hold the peer's identity key; hover shows a comparable fingerprint.
|
||||
// Key-verify lock — we hold the peer's identity key; now clickable to open a dialog with the
|
||||
// fingerprint for out-of-band comparison (the old hover-only lock was unexplained).
|
||||
if (showLock) {
|
||||
const float ly = hp.y + (rowH - icoSz) * 0.5f;
|
||||
ImGui::SetCursorScreenPos(ImVec2(ax, ly));
|
||||
const bool lockClicked = ImGui::InvisibleButton("##hdr_keylock",
|
||||
ImVec2(std::max(1.0f, lockW - 5.0f * hdpi), icoSz));
|
||||
const bool lockHov = ImGui::IsItemHovered();
|
||||
hdl->AddText(icoFont, icoSz, ImVec2(ax, ly),
|
||||
material::WithAlpha(material::Primary(), 220), ICON_MD_LOCK);
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(ax, ly), ImVec2(ax + lockW, ly + icoSz)))
|
||||
material::Tooltip("%s\n%s", TR("chat_verify_key"), keyFingerprint(sel->peerPubKey).c_str());
|
||||
lockHov ? material::Primary() : material::WithAlpha(material::Primary(), 220),
|
||||
ICON_MD_LOCK);
|
||||
if (lockHov) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
material::Tooltip("%s", TR("chat_verify_tip"));
|
||||
}
|
||||
if (lockClicked) { s_fingerprint_cid = sel->cid; s_show_fingerprint = true; }
|
||||
ax += lockW;
|
||||
}
|
||||
// Click-to-copy compact address.
|
||||
@@ -2207,6 +2218,53 @@ void RenderChatTab(App* app)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Key-fingerprint verification dialog ----
|
||||
if (s_show_fingerprint) {
|
||||
const ConvSummary* fp = nullptr;
|
||||
for (const auto& c : convs) if (c.cid == s_fingerprint_cid) { fp = &c; break; }
|
||||
if (!fp || fp->peerPubKey.empty()) {
|
||||
s_show_fingerprint = false;
|
||||
s_fingerprint_cid.clear();
|
||||
} else {
|
||||
material::OverlayDialogSpec ov;
|
||||
ov.title = TR("chat_verify_title");
|
||||
ov.p_open = &s_show_fingerprint;
|
||||
ov.style = material::OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 500.0f;
|
||||
ov.idSuffix = "chatfp";
|
||||
if (material::BeginOverlayDialog(ov)) {
|
||||
const float fdp = Layout::dpiScale();
|
||||
const std::string fpStr = keyFingerprint(fp->peerPubKey);
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(material::OnSurfaceMedium()));
|
||||
ImGui::TextWrapped("%s", TR("chat_verify_body"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", fp->peerName.c_str());
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(material::Primary()));
|
||||
ImGui::TextWrapped("%s", fpStr.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
const float fgap = ImGui::GetStyle().ItemSpacing.x;
|
||||
float wCopy = std::max(110.0f * fdp, ImGui::CalcTextSize(TR("chat_verify_copy")).x + 24.0f * fdp);
|
||||
float wClose = std::max(90.0f * fdp, ImGui::CalcTextSize(TR("chat_cancel")).x + 24.0f * fdp);
|
||||
material::BeginOverlayDialogFooter(wCopy + wClose + fgap, false);
|
||||
if (material::TactileButton(TR("chat_verify_copy"), ImVec2(wCopy, 0))) {
|
||||
ImGui::SetClipboardText(fp->peerPubKey.c_str());
|
||||
Notifications::instance().success(TR("copied"));
|
||||
}
|
||||
ImGui::SameLine(0, fgap);
|
||||
if (material::TactileButton(TR("chat_cancel"), ImVec2(wClose, 0))) {
|
||||
s_show_fingerprint = false;
|
||||
s_fingerprint_cid.clear();
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Delete-conversation confirm (revive-on-new-message vs delete & block) ----
|
||||
if (s_show_delete_confirm) {
|
||||
const float dp = Layout::dpiScale();
|
||||
|
||||
Reference in New Issue
Block a user