The log lists one entry per app, ordered by each app's newest notification. An app with several shows a header with its name, a count and a clear mark for that app alone, then its newest entry and a '+ N more' line; clicking either unfolds the rest. Entries under a header drop their own sender line and keep the urgency dot.
279 lines
9.8 KiB
QML
279 lines
9.8 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Services.Notifications
|
|
import "root:/config"
|
|
import "root:/components"
|
|
import "root:/services"
|
|
|
|
// Notification history and the do-not-disturb switch.
|
|
Popout {
|
|
id: root
|
|
|
|
contentWidth: 400
|
|
contentHeight: 380
|
|
|
|
PopoutSurface {
|
|
anchors.fill: parent
|
|
tone: Notifs.dnd ? Theme.muted : Theme.primary
|
|
|
|
PopoutHeader {
|
|
id: header
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
title: "Notifications"
|
|
icon: Notifs.icon
|
|
accent: Notifs.dnd ? Theme.muted : Theme.primary
|
|
subtitle: Notifs.count + " HELD"
|
|
}
|
|
|
|
Row {
|
|
id: controls
|
|
anchors.top: header.bottom
|
|
anchors.topMargin: Theme.padM
|
|
anchors.left: parent.left
|
|
spacing: Theme.padS
|
|
|
|
ActionButton {
|
|
text: Notifs.dnd ? "Silenced" : "Silence"
|
|
icon: Notifs.dnd ? "" : ""
|
|
accent: Notifs.dnd ? Theme.treatmentOffText : Theme.primary
|
|
onClicked: Notifs.toggleDnd()
|
|
}
|
|
|
|
ActionButton {
|
|
text: "Clear"
|
|
icon: ""
|
|
destructive: true
|
|
onClicked: Notifs.clearAll()
|
|
}
|
|
}
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: Notifs.count === 0
|
|
text: "NOTHING TO REPORT"
|
|
color: Theme.muted
|
|
font.family: Theme.fontDisplay
|
|
font.pixelSize: Theme.fsBody
|
|
font.weight: Font.DemiBold
|
|
font.letterSpacing: 3
|
|
renderType: Text.NativeRendering
|
|
}
|
|
|
|
// The log, by sender. A sender with one notification is just that
|
|
// entry. A sender with several gets a header — its name, how many, a
|
|
// clear-all mark — over its newest entry, and opens to show the rest.
|
|
ListView {
|
|
id: log
|
|
anchors.top: controls.bottom
|
|
anchors.topMargin: Theme.padM
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
clip: true
|
|
spacing: 0
|
|
model: Notifs.groups
|
|
|
|
// Which senders are open, by app name. Survives the list being
|
|
// rebuilt when a notification arrives.
|
|
property var expanded: ({})
|
|
|
|
function toggle(app: string) {
|
|
const next = Object.assign({}, log.expanded);
|
|
next[app] = !next[app];
|
|
log.expanded = next;
|
|
}
|
|
|
|
delegate: Column {
|
|
id: group
|
|
required property var modelData
|
|
readonly property string app: group.modelData.app
|
|
readonly property var items: group.modelData.items
|
|
readonly property bool many: group.items.length > 1
|
|
readonly property bool open: log.expanded[group.app] === true
|
|
|
|
width: ListView.view.width
|
|
|
|
Item {
|
|
id: groupHeader
|
|
visible: group.many
|
|
width: parent.width
|
|
height: group.many ? 30 : 0
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.padS / 2
|
|
radius: Theme.radiusL
|
|
color: headerHover.hovered ? Theme.treatmentHoverFill : "transparent"
|
|
|
|
Behavior on color {
|
|
ColorAnimation { duration: Theme.durFast }
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: headerRow
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.padM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.padS
|
|
|
|
Icon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: Notifs.appIconFor(group.items[0])
|
|
color: Theme.subtext
|
|
font.pixelSize: Theme.fsBody
|
|
}
|
|
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: group.app.toUpperCase()
|
|
color: Theme.text
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: Theme.fsMicro
|
|
font.letterSpacing: 2
|
|
renderType: Text.NativeRendering
|
|
}
|
|
|
|
// The count, in a small grey pill.
|
|
Rectangle {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: countText.implicitWidth + 10
|
|
height: countText.implicitHeight + 2
|
|
radius: height / 2
|
|
color: Theme.surfaceAlt
|
|
|
|
Text {
|
|
id: countText
|
|
anchors.centerIn: parent
|
|
text: group.items.length
|
|
color: Theme.subtext
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: Theme.fsMicro
|
|
renderType: Text.NativeRendering
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.left: headerRow.right
|
|
anchors.leftMargin: Theme.padS
|
|
anchors.right: chevron.left
|
|
anchors.rightMargin: Theme.padS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
height: 1
|
|
color: Theme.alpha(Theme.outline, 0.5)
|
|
}
|
|
|
|
Icon {
|
|
id: chevron
|
|
anchors.right: clearGroup.left
|
|
anchors.rightMargin: Theme.padS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: ""
|
|
rotation: group.open ? 180 : 0
|
|
color: headerHover.hovered ? Theme.accent : Theme.muted
|
|
font.pixelSize: Theme.fsBody
|
|
|
|
Behavior on rotation {
|
|
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutCubic }
|
|
}
|
|
}
|
|
|
|
Icon {
|
|
id: clearGroup
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.padM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: ""
|
|
opacity: headerHover.hovered ? 1 : 0
|
|
color: clearHover.hovered ? Theme.alarm : Theme.muted
|
|
font.pixelSize: Theme.fsBody
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation { duration: Theme.durFast }
|
|
}
|
|
|
|
HoverHandler {
|
|
id: clearHover
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
|
|
TapHandler {
|
|
onTapped: Notifs.dismissApp(group.app)
|
|
}
|
|
}
|
|
|
|
HoverHandler {
|
|
id: headerHover
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
|
|
TapHandler {
|
|
onTapped: log.toggle(group.app)
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: group.open ? group.items : group.items.slice(0, 1)
|
|
|
|
NotifCard {
|
|
required property var modelData
|
|
width: group.width
|
|
notif: modelData
|
|
showSender: !group.many
|
|
onDismissed: Notifs.dismiss(modelData)
|
|
}
|
|
}
|
|
|
|
// Folded: say how many more are behind the newest.
|
|
Text {
|
|
visible: group.many && !group.open
|
|
leftPadding: Theme.padM + Theme.marker + Theme.padS
|
|
bottomPadding: Theme.padS
|
|
text: "+ " + (group.items.length - 1) + " more"
|
|
color: moreHover.hovered ? Theme.accent : Theme.muted
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: Theme.fsMicro
|
|
font.letterSpacing: 1
|
|
renderType: Text.NativeRendering
|
|
|
|
HoverHandler {
|
|
id: moreHover
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
|
|
TapHandler {
|
|
onTapped: log.toggle(group.app)
|
|
}
|
|
}
|
|
}
|
|
|
|
add: Transition {
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
from: 0; to: 1
|
|
duration: Theme.durBase
|
|
}
|
|
}
|
|
|
|
remove: Transition {
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
to: 0
|
|
duration: Theme.durFast
|
|
}
|
|
}
|
|
|
|
displaced: Transition {
|
|
NumberAnimation {
|
|
properties: "y"
|
|
duration: Theme.durBase
|
|
easing.type: Easing.OutExpo
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|