- Matrix: interactive device verification (SAS emoji flow), recovery-key
backup/restore, membership announcement moderation, member/room avatars,
power-level-aware moderation menu, and an encryption lock indicator on
the composer - verified live with two independent devices.
- Replace the single combined join panel with a dedicated page per
protocol (IrcJoinPanel/MatrixJoinPanel/DiscordJoinPanel), routed by
whichever account the "+" button was clicked for rather than listing
every account in one expandable page.
- Discord: friends list (Online/All, click-to-DM, add-friend, matches
READY's relationships + live PRESENCE_UPDATE), create-a-server, and
join-by-invite alongside the existing DM-by-ID flow.
- Discord: an expired attachment-link fallback ("Open in Discord") for
scrollback media whose signed cdn.discordapp.com URL has lapsed -
Discord's own /attachments/refresh-urls re-sign endpoint hard-rejects
this backend's user-token requests, confirmed live, so this opens the
real message instead of showing a broken embed.
- Discord: detect gateway close code 4004 (revoked token) as a distinct
ConnState::AuthFailed instead of retrying a dead credential forever;
the Accounts pane shows a clear "needs login" state that routes
straight to a fresh QR login, which upserts the same account id in
place (no lost buffers/scrollback).
- Fix the unread-DM pinned-buffer flash never reliably triggering: an
implicit property binding computed via nested closures over two
separate properties wasn't consistently re-evaluated by QML's
dependency tracker; replaced with an explicit recompute function called
at every actual mutation point.
- Fix ChatBarWidget.qml's pill double-padding against BasePill's own
padding.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
117 lines
3.7 KiB
QML
117 lines
3.7 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
// The emoji-comparison step of interactive session (SAS) verification -
|
|
// styled like EmojiPicker.qml (this project's only other real Popup {}
|
|
// usage - DankModal is shell-level infrastructure this plugin avoids, see
|
|
// that file's own doc comment). Shown once chatd has exchanged keys with
|
|
// the other session and both sides can compute the same 7-emoji sequence
|
|
// (see backend/matrix/verification.rs's tick - the "started" ->
|
|
// matrixVerificationEmoji event path) - read them out loud, or just
|
|
// compare on screen, against what the other device shows.
|
|
Popup {
|
|
id: root
|
|
|
|
property var emoji: [] // [{symbol, description}] x7
|
|
property string otherDevice: ""
|
|
|
|
signal confirmed(bool matches)
|
|
|
|
// No accidental dismiss for a security-sensitive comparison step - the
|
|
// two buttons below are the only way out, so a stray Escape/click-away
|
|
// can't silently leave the other side waiting indefinitely without the
|
|
// user realizing anything happened.
|
|
closePolicy: Popup.NoAutoClose
|
|
width: 340
|
|
padding: Theme.spacingM
|
|
|
|
background: Rectangle {
|
|
color: BlurService.enabled ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
radius: Theme.cornerRadius
|
|
border.width: 1
|
|
border.color: Theme.outlineStrong
|
|
}
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: Theme.spacingM
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: "Confirm the emoji"
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Bold
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: "Compare these against " + (root.otherDevice || "the other session") + " - they should appear in the exact same order on both."
|
|
wrapMode: Text.WordWrap
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
}
|
|
|
|
GridLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Theme.spacingXS
|
|
columns: 4
|
|
rowSpacing: Theme.spacingM
|
|
columnSpacing: Theme.spacingXS
|
|
|
|
Repeater {
|
|
model: root.emoji
|
|
|
|
ColumnLayout {
|
|
required property var modelData
|
|
Layout.preferredWidth: 70
|
|
spacing: 2
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: modelData.symbol || ""
|
|
font.pixelSize: 28
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
|
|
StyledText {
|
|
Layout.fillWidth: true
|
|
text: modelData.description || ""
|
|
font.pixelSize: 10
|
|
color: Theme.surfaceVariantText
|
|
wrapMode: Text.WordWrap
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Theme.spacingS
|
|
spacing: Theme.spacingS
|
|
|
|
DankButton {
|
|
Layout.fillWidth: true
|
|
text: "They don't match"
|
|
onClicked: {
|
|
root.confirmed(false);
|
|
root.close();
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
Layout.fillWidth: true
|
|
text: "They match"
|
|
onClicked: {
|
|
root.confirmed(true);
|
|
root.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|