waffles: notifications: image support

This commit is contained in:
end-4
2025-11-29 12:51:31 +01:00
parent d2c019f8de
commit 677fa06b06
5 changed files with 172 additions and 60 deletions
@@ -84,4 +84,28 @@ Singleton {
// Older dates // Older dates
return Qt.formatDateTime(messageTime, "MMMM dd"); return Qt.formatDateTime(messageTime, "MMMM dd");
} }
function processNotificationBody(body, appName) {
let processedBody = body
// Clean Chromium-based browsers notifications - remove first line
if (appName) {
const lowerApp = appName.toLowerCase()
const chromiumBrowsers = [
"brave", "chrome", "chromium", "vivaldi", "opera", "microsoft edge"
]
if (chromiumBrowsers.some(name => lowerApp.includes(name))) {
const lines = body.split('\n\n')
if (lines.length > 1 && lines[0].startsWith('<a')) {
processedBody = lines.slice(1).join('\n\n')
}
}
}
processedBody = processedBody.replace(/<img/gi, '\n\n<img');
return processedBody
}
} }
@@ -31,28 +31,6 @@ Item { // Notification item area
implicitHeight: background.implicitHeight implicitHeight: background.implicitHeight
function processNotificationBody(body, appName) {
let processedBody = body
// Clean Chromium-based browsers notifications - remove first line
if (appName) {
const lowerApp = appName.toLowerCase()
const chromiumBrowsers = [
"brave", "chrome", "chromium", "vivaldi", "opera", "microsoft edge"
]
if (chromiumBrowsers.some(name => lowerApp.includes(name))) {
const lines = body.split('\n\n')
if (lines.length > 1 && lines[0].startsWith('<a')) {
processedBody = lines.slice(1).join('\n\n')
}
}
}
return processedBody
}
function destroyWithAnimation(left = false) { function destroyWithAnimation(left = false) {
root.qmlParent.resetDrag() root.qmlParent.resetDrag()
background.anchors.leftMargin = background.anchors.leftMargin; // Break binding background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
@@ -196,12 +174,13 @@ Item { // Notification item area
maximumLineCount: 1 maximumLineCount: 1
textFormat: Text.StyledText textFormat: Text.StyledText
text: { text: {
return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>") return NotificationUtils.processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")
} }
} }
} }
ColumnLayout { // Expanded content ColumnLayout { // Expanded content
id: expandedContentColumn
Layout.fillWidth: true Layout.fillWidth: true
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
visible: opacity > 0 visible: opacity > 0
@@ -218,8 +197,8 @@ Item { // Notification item area
elide: Text.ElideRight elide: Text.ElideRight
textFormat: Text.RichText textFormat: Text.RichText
text: { text: {
return `<style>img{max-width:${300 /* binding to notificationBodyText.width would cause a binding loop */}px;}</style>` + return `<style>img{max-width:${expandedContentColumn.width}px;}</style>` +
`${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}` `${NotificationUtils.processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}`
} }
onLinkActivated: (link) => { onLinkActivated: (link) => {
@@ -293,6 +272,8 @@ Item { // Notification item area
id: actionRepeater id: actionRepeater
model: notificationObject.actions model: notificationObject.actions
NotificationActionButton { NotificationActionButton {
id: notifAction
required property var modelData
Layout.fillWidth: true Layout.fillWidth: true
buttonText: modelData.text buttonText: modelData.text
urgency: notificationObject.urgency urgency: notificationObject.urgency
@@ -7,6 +7,7 @@ import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.waffle.looks import qs.modules.waffle.looks
// TODO: Replace the icon with QMLized svg (with /usr/lib/qt6/bin/svgtoqml) for proper micro-animation
AppButton { AppButton {
id: root id: root
@@ -20,6 +20,7 @@ WBarAttachedPanelContent {
property bool collapsed: false property bool collapsed: false
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: contentLayout
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: parent.top top: parent.top
@@ -41,9 +42,24 @@ WBarAttachedPanelContent {
} }
contentItem: NotificationPaneContent { contentItem: NotificationPaneContent {
implicitWidth: calendarColumnLayout.implicitWidth implicitWidth: calendarColumnLayout.implicitWidth
implicitHeight: Notifications.list.length > 0 ? (notificationArea.height - notificationPane.borderWidth * 2) : 230 implicitHeight: {
if (Notifications.list.length > 0) {
return ((contentLayout.height - calendarPane.height - contentLayout.spacing) - notificationPane.borderWidth * 2)
}
return 230;
}
Timer {
id: enableTimer
interval: Config.options.hacks.arbitraryRaceConditionDelay
onTriggered: heightBehavior.enabled = true;
}
Behavior on implicitHeight { Behavior on implicitHeight {
id: heightBehavior
enabled: false
Component.onCompleted: {
enableTimer.restart();
}
animation: Looks.transition.enter.createObject(this) animation: Looks.transition.enter.createObject(this)
} }
} }
@@ -51,6 +67,7 @@ WBarAttachedPanelContent {
} }
WPane { WPane {
id: calendarPane
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: calendarColumnLayout id: calendarColumnLayout
spacing: 0 spacing: 0
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
@@ -41,52 +42,115 @@ MouseArea {
anchors.margins: contentItem.padding anchors.margins: contentItem.padding
spacing: 19 spacing: 19
RowLayout { // Header
SingleNotificationHeader {
Layout.fillWidth: true Layout.fillWidth: true
}
ExpandButton { // Content
Layout.topMargin: -2 Item {
id: actualContent
Layout.fillWidth: true
Layout.fillHeight: true
property real spacing: 16
implicitHeight: Math.max(contentColumn.implicitHeight, imageLoader.height)
implicitWidth: contentColumn.implicitWidth
Loader {
id: imageLoader
active: root.notification.image != ""
sourceComponent: StyledImage {
width: 48
height: 48
sourceSize.width: width
sourceSize.height: height
source: root.notification.image
}
} }
Item { ColumnLayout {
Layout.fillWidth: true id: contentColumn
} anchors {
top: parent.top
left: parent.left
right: parent.right
}
spacing: 3
NotificationHeaderButton { SummaryText {
Layout.rightMargin: 4 id: summaryText
opacity: root.containsMouse ? 1 : 0 Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0
icon.name: "dismiss" }
implicitSize: 12 BodyText {
onClicked: { Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0
Qt.callLater(() => { // onLineLaidOut: (line) => {
Notifications.discardNotification(root.notification?.notificationId); // if (!imageLoader.active) return;
}); // const dodgeDistance = imageLoader.width + actualContent.spacing;
// // print(line.y, dodgeDistance)
// if (summaryText.height + line.y > dodgeDistance) {
// line.x -= dodgeDistance;
// line.width += dodgeDistance;
// }
// }
} }
} }
} }
ColumnLayout { // Actions
ActionsRow {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 3
SummaryText {}
BodyText {}
} }
AcrylicButton { // "+1 notifications" button
id: groupExpandButton GroupExpandButton {
visible: root.groupExpandControlMessage !== ""
Layout.bottomMargin: 2 Layout.bottomMargin: 2
horizontalPadding: 10 }
implicitHeight: 24 }
implicitWidth: expandButtonText.implicitWidth + horizontalPadding * 2 }
onClicked: root.groupExpandToggle()
contentItem: Item { component SingleNotificationHeader: RowLayout {
WText { ExpandButton {
id: expandButtonText Layout.topMargin: -2
anchors.centerIn: parent }
text: root.groupExpandControlMessage
} Item {
Layout.fillWidth: true
}
NotificationHeaderButton {
Layout.rightMargin: 4
opacity: root.containsMouse ? 1 : 0
icon.name: "dismiss"
implicitSize: 12
onClicked: {
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
}
}
}
component ActionsRow: RowLayout {
visible: root.expanded && root.notification.actions.length > 0
uniformCellSizes: true
Repeater {
id: actionRepeater
model: root.notification.actions
delegate: WBorderedButton {
id: actionButton
Layout.fillHeight: true
required property var modelData
Layout.fillWidth: true
verticalPadding: 16
horizontalPadding: 12
text: modelData.text
implicitHeight: actionButtonText.implicitHeight + verticalPadding * 2
contentItem: WText {
id: actionButtonText
text: actionButton.text
font.pixelSize: Looks.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
} }
} }
} }
@@ -106,8 +170,17 @@ MouseArea {
verticalAlignment: Text.AlignTop verticalAlignment: Text.AlignTop
wrapMode: Text.Wrap wrapMode: Text.Wrap
maximumLineCount: root.expanded ? 100 : 1 maximumLineCount: root.expanded ? 100 : 1
text: root.notification?.body text: {
if (root.expanded)
return `<style>img{max-width:${summaryText.width}px; align: right}</style>` + `${NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "<br/>")}`;
return NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "<br/>");
}
color: Looks.colors.subfg color: Looks.colors.subfg
textFormat: root.expanded ? Text.RichText : Text.StyledText
onLinkActivated: link => {
Qt.openUrlExternally(link);
GlobalStates.sidebarRightOpen = false;
}
} }
component ExpandButton: NotificationHeaderButton { component ExpandButton: NotificationHeaderButton {
@@ -140,4 +213,20 @@ MouseArea {
} }
} }
} }
component GroupExpandButton: AcrylicButton {
id: groupExpandButton
visible: root.groupExpandControlMessage !== ""
horizontalPadding: 10
implicitHeight: 24
implicitWidth: expandButtonText.implicitWidth + horizontalPadding * 2
onClicked: root.groupExpandToggle()
contentItem: Item {
WText {
id: expandButtonText
anchors.centerIn: parent
text: root.groupExpandControlMessage
}
}
}
} }