Files
dms-chat/ChatBarWidget.qml
SalastilandClaude Sonnet 5 20389e7396 Add Matrix verification/backup, per-protocol join pages, Discord friends, and expired-media/auth-failure recovery
- 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]>
2026-08-21 12:30:46 -04:00

109 lines
4.1 KiB
QML

import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
// Composite-plugin "widget" surface: the DankBar pill. Reads connection/
// unread state the daemon surface (ChatDaemon.qml) publishes via
// PluginService global vars - see PLUGINS/README.md's "Plugin Global
// Variables" section for why this indirection exists (daemon and widget
// are separate object instances with no direct reference to each other).
//
// Clicking the pill bumps a "toggleNonce" global var the daemon watches,
// rather than PluginService.togglePlugin(pluginId) - that generic lookup
// didn't reliably find this composite plugin's daemon instance when
// tested against this DMS version. See ChatDaemon.qml for the other end.
PluginComponent {
id: root
pillClickAction: () => {
const n = PluginService.getGlobalVar(root.pluginId, "toggleNonce", 0);
PluginService.setGlobalVar(root.pluginId, "toggleNonce", n + 1);
}
PluginGlobalVar {
id: chatdConnected
varName: "chatdConnected"
defaultValue: false
}
PluginGlobalVar {
id: totalUnread
varName: "totalUnread"
defaultValue: 0
}
// Set by ChatDaemon.qml whenever a pinned buffer (see BufferList.qml's
// Pin menu item) has an unread DM or highlighted mention pending -
// chatd only ever emits the underlying "notification" event for one
// of those two cases, never plain traffic, so this is exactly "unread
// DM or mention from a pinned channel", not just "any unread".
PluginGlobalVar {
id: hasPinnedAlert
varName: "hasPinnedAlert"
defaultValue: false
}
// Deliberately just the icon+count Row, no background/sizing of our
// own - the host BasePill (see PluginComponent.qml, which wraps
// whatever horizontalBarPill provides as this component's `content`)
// already supplies the pill's real background, padding, and radius,
// sized from this Row's own natural implicitWidth/implicitHeight.
// Matches every other composite-plugin bar widget's own convention
// (see PLUGINS/ExampleCompositePlugin/CompositeBarWidget.qml) -
// wrapping this in a second StyledRect of our own, as it used to be,
// just draws a redundant background with its own different padding
// math, which is what made this pill a different size than its
// siblings in the first place.
horizontalBarPill: Component {
Row {
id: content
spacing: Theme.spacingXS
DankIcon {
id: chatIcon
name: "chat"
size: Theme.iconSize - 4
color: hasPinnedAlert.value ? Theme.error : (chatdConnected.value ? Theme.primary : Theme.surfaceVariantText)
anchors.verticalCenter: parent.verticalCenter
// Pulses opacity while a pinned alert is pending - stops
// (and resets to fully opaque, rather than possibly
// freezing mid-fade) the instant hasPinnedAlert clears,
// e.g. once the buffer's actually been read.
SequentialAnimation {
running: hasPinnedAlert.value
loops: Animation.Infinite
onRunningChanged: if (!running) chatIcon.opacity = 1.0
NumberAnimation {
target: chatIcon
property: "opacity"
to: 0.35
duration: 500
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: chatIcon
property: "opacity"
to: 1.0
duration: 500
easing.type: Easing.InOutQuad
}
}
}
StyledText {
visible: totalUnread.value > 0
text: totalUnread.value
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
}
}