mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-07 14:05:38 -05:00
355 lines
15 KiB
QML
355 lines
15 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Widgets
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
visible: NotificationService.groupedPopups.length > 0
|
|
WlrLayershell.layer: WlrLayershell.Overlay
|
|
WlrLayershell.exclusiveZone: -1
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
color: "transparent"
|
|
implicitWidth: 400
|
|
implicitHeight: notificationsList.height + 32
|
|
|
|
anchors {
|
|
top: true
|
|
right: true
|
|
}
|
|
|
|
margins {
|
|
top: Theme.barHeight
|
|
right: 12
|
|
}
|
|
|
|
Column {
|
|
id: notificationsList
|
|
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.topMargin: 16
|
|
anchors.rightMargin: 16
|
|
spacing: Theme.spacingM
|
|
width: 380
|
|
|
|
Repeater {
|
|
model: NotificationService.groupedPopups
|
|
|
|
delegate: Rectangle {
|
|
required property var modelData
|
|
|
|
width: parent.width
|
|
height: content.height + Theme.spacingL * 2
|
|
radius: Theme.cornerRadiusLarge
|
|
color: Theme.popupBackground()
|
|
border.color: modelData.latestNotification.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
border.width: modelData.latestNotification.urgency === 2 ? 2 : 1
|
|
clip: true
|
|
|
|
Rectangle {
|
|
width: 4
|
|
height: parent.height - 16
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 2
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
radius: 2
|
|
color: Theme.primary
|
|
visible: modelData.latestNotification.urgency === 2
|
|
}
|
|
|
|
Row {
|
|
id: content
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
height: Math.max(48, textContent.height)
|
|
|
|
Rectangle {
|
|
width: 48
|
|
height: 48
|
|
radius: 24
|
|
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
|
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
|
border.width: 1
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
readonly property bool hasNotificationImage: modelData.latestNotification.image && modelData.latestNotification.image !== ""
|
|
readonly property bool appIconIsImage: modelData.latestNotification.appIcon &&
|
|
(modelData.latestNotification.appIcon.startsWith("file://") ||
|
|
modelData.latestNotification.appIcon.startsWith("http://") ||
|
|
modelData.latestNotification.appIcon.startsWith("https://"))
|
|
|
|
IconImage {
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
source: {
|
|
// Priority 1: Use notification image if available
|
|
if (parent.hasNotificationImage) {
|
|
return modelData.latestNotification.cleanImage;
|
|
}
|
|
|
|
// Priority 2: Use appIcon - handle URLs directly, use iconPath for icon names
|
|
if (modelData.latestNotification.appIcon) {
|
|
const appIcon = modelData.latestNotification.appIcon;
|
|
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://")) {
|
|
return appIcon;
|
|
}
|
|
return Quickshell.iconPath(appIcon, "");
|
|
}
|
|
|
|
return "";
|
|
}
|
|
visible: status === Image.Ready
|
|
}
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: !parent.hasNotificationImage && (!modelData.latestNotification.appIcon || modelData.latestNotification.appIcon === "")
|
|
text: {
|
|
const appName = modelData.appName || "?";
|
|
return appName.charAt(0).toUpperCase();
|
|
}
|
|
font.pixelSize: 20
|
|
font.weight: Font.Bold
|
|
color: Theme.primaryText
|
|
}
|
|
|
|
Rectangle {
|
|
width: 18
|
|
height: 18
|
|
radius: 9
|
|
color: Theme.primary
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.topMargin: -2
|
|
anchors.rightMargin: -2
|
|
visible: modelData.count > 1
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: modelData.count > 99 ? "99+" : modelData.count.toString()
|
|
color: Theme.primaryText
|
|
font.pixelSize: 9
|
|
font.weight: Font.Bold
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: textContent
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 4
|
|
width: parent.width - 48 - Theme.spacingM - controls.width - Theme.spacingS
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: {
|
|
if (modelData.latestNotification.timeStr.length > 0)
|
|
return modelData.appName + " • " + modelData.latestNotification.timeStr;
|
|
else
|
|
return modelData.appName;
|
|
}
|
|
color: Theme.surfaceVariantText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.Medium
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
}
|
|
|
|
Text {
|
|
text: modelData.latestNotification.summary
|
|
color: Theme.surfaceText
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
width: parent.width
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
visible: text.length > 0
|
|
}
|
|
|
|
Text {
|
|
text: modelData.latestNotification.body
|
|
color: Theme.surfaceVariantText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
width: parent.width
|
|
elide: Text.ElideRight
|
|
maximumLineCount: modelData.count > 1 ? 1 : 2
|
|
wrapMode: Text.WordWrap
|
|
visible: text.length > 0
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
visible: modelData.latestNotification.notification.hasInlineReply
|
|
|
|
Rectangle {
|
|
width: parent.width - 60
|
|
height: 36
|
|
radius: 18
|
|
color: Theme.surfaceContainer
|
|
border.color: quickReplyField.activeFocus ? Theme.primary : Theme.outline
|
|
border.width: 1
|
|
|
|
TextField {
|
|
id: quickReplyField
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingS
|
|
placeholderText: modelData.latestNotification.notification.inlineReplyPlaceholder || "Quick reply..."
|
|
color: Theme.surfaceText
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
onAccepted: {
|
|
if (text.length > 0) {
|
|
modelData.latestNotification.notification.sendInlineReply(text);
|
|
text = "";
|
|
}
|
|
}
|
|
background: Item {}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: 52
|
|
height: 36
|
|
radius: 18
|
|
color: quickReplyField.text.length > 0 ? Theme.primary : Theme.surfaceContainer
|
|
border.color: quickReplyField.text.length > 0 ? "transparent" : Theme.outline
|
|
border.width: quickReplyField.text.length > 0 ? 0 : 1
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "send"
|
|
size: 16
|
|
color: quickReplyField.text.length > 0 ? Theme.primaryText : Theme.surfaceVariantText
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: quickReplyField.text.length > 0
|
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
onClicked: {
|
|
modelData.latestNotification.notification.sendInlineReply(quickReplyField.text);
|
|
quickReplyField.text = "";
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.standardEasing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: controls
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 8
|
|
|
|
Rectangle {
|
|
width: 32
|
|
height: 32
|
|
radius: 16
|
|
color: expandArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
visible: modelData.count > 1
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "expand_more"
|
|
size: 18
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
MouseArea {
|
|
id: expandArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: NotificationService.toggleGroupExpansion(modelData.key)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: 32
|
|
height: 32
|
|
radius: 16
|
|
color: dismissArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : "transparent"
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "close"
|
|
size: 16
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
MouseArea {
|
|
id: dismissArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: NotificationService.dismissGroup(modelData.key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.NoButton
|
|
propagateComposedEvents: true
|
|
onEntered: dismissTimer.stop()
|
|
onExited: {
|
|
if (modelData.latestNotification.popup)
|
|
dismissTimer.restart();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: dismissTimer
|
|
running: modelData.latestNotification.popup
|
|
interval: modelData.latestNotification.notification.expireTimeout > 0 ? modelData.latestNotification.notification.expireTimeout * 1000 : 5000
|
|
onTriggered: {
|
|
if (!parent.children[parent.children.length - 2].containsMouse) {
|
|
modelData.latestNotification.popup = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
transform: Translate {
|
|
x: root.visible ? 0 : 400
|
|
Behavior on x {
|
|
NumberAnimation {
|
|
duration: 350
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: 300
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on implicitHeight {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
} |