Add notification history (Widets/Notification/NotificationHistory.qml)
This commit is contained in:
parent
0ed7b3fa1c
commit
cf26fe52d9
5 changed files with 395 additions and 0 deletions
357
Widgets/Notification/NotificationHistory.qml
Normal file
357
Widgets/Notification/NotificationHistory.qml
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import QtQuick.Layouts
|
||||
|
||||
PanelWindow {
|
||||
id: notificationHistoryWin
|
||||
width: 400
|
||||
height: 500
|
||||
color: "transparent"
|
||||
visible: false
|
||||
screen: Quickshell.primaryScreen
|
||||
focusable: true
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: 4
|
||||
margins.right: 4
|
||||
|
||||
property int maxHistory: 100
|
||||
property string configDir: Quickshell.configDir
|
||||
property string historyFilePath: configDir + "/notification_history.json"
|
||||
|
||||
ListModel {
|
||||
id: historyModel // Holds notification objects
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: historyFileView
|
||||
path: historyFilePath
|
||||
blockLoading: true
|
||||
printErrors: true
|
||||
watchChanges: true
|
||||
|
||||
JsonAdapter {
|
||||
id: historyAdapter
|
||||
property var notifications: [] // Array of notification objects
|
||||
}
|
||||
|
||||
onFileChanged: {
|
||||
reload() // Reload if file changes on disk
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
loadHistory() // Populate model after loading
|
||||
}
|
||||
|
||||
onLoadFailed: function(error) {
|
||||
console.error("Failed to load history file:", error)
|
||||
if (error.includes("No such file")) {
|
||||
historyAdapter.notifications = [] // Create new file if missing
|
||||
writeAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
onSaved: {}
|
||||
onSaveFailed: function(error) {
|
||||
console.error("Failed to save history:", error)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (path) reload()
|
||||
}
|
||||
}
|
||||
|
||||
function loadHistory() {
|
||||
if (historyAdapter.notifications) {
|
||||
historyModel.clear()
|
||||
const notifications = historyAdapter.notifications
|
||||
const count = Math.min(notifications.length, maxHistory)
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (typeof notifications[i] === 'object' && notifications[i] !== null) {
|
||||
historyModel.append(notifications[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveHistory() {
|
||||
const historyArray = []
|
||||
const count = Math.min(historyModel.count, maxHistory)
|
||||
for (let i = 0; i < count; ++i) {
|
||||
let obj = historyModel.get(i)
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
historyArray.push({
|
||||
id: obj.id,
|
||||
appName: obj.appName,
|
||||
summary: obj.summary,
|
||||
body: obj.body,
|
||||
timestamp: obj.timestamp
|
||||
})
|
||||
}
|
||||
}
|
||||
historyAdapter.notifications = historyArray
|
||||
Qt.callLater(function() {
|
||||
historyFileView.writeAdapter()
|
||||
})
|
||||
}
|
||||
|
||||
function addToHistory(notification) {
|
||||
if (!notification.id) notification.id = Date.now()
|
||||
if (!notification.timestamp) notification.timestamp = new Date().toISOString()
|
||||
for (let i = 0; i < historyModel.count; ++i) {
|
||||
if (historyModel.get(i).id === notification.id) {
|
||||
historyModel.remove(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
historyModel.insert(0, notification)
|
||||
if (historyModel.count > maxHistory) historyModel.remove(maxHistory)
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
historyModel.clear()
|
||||
historyAdapter.notifications = []
|
||||
historyFileView.writeAdapter()
|
||||
}
|
||||
|
||||
function formatTimestamp(ts) {
|
||||
if (!ts) return "";
|
||||
var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts));
|
||||
var y = date.getFullYear();
|
||||
var m = (date.getMonth()+1).toString().padStart(2,'0');
|
||||
var d = date.getDate().toString().padStart(2,'0');
|
||||
var h = date.getHours().toString().padStart(2,'0');
|
||||
var min = date.getMinutes().toString().padStart(2,'0');
|
||||
return `${y}-${m}-${d} ${h}:${min}`;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: notificationHistoryWin.width
|
||||
height: notificationHistoryWin.height
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 16
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
Text {
|
||||
text: "Notification History"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
width: 90
|
||||
height: 32
|
||||
radius: 20
|
||||
color: clearAllMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
Text {
|
||||
text: "delete_sweep"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 14
|
||||
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Text {
|
||||
text: "Clear"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: clearAllMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: notificationHistoryWin.clearHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 0
|
||||
color: "transparent"
|
||||
visible: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 56
|
||||
anchors.bottom: parent.bottom
|
||||
color: Theme.surfaceVariant
|
||||
radius: 20
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
border.width: 1
|
||||
border.color: Theme.surfaceVariant
|
||||
z: 0
|
||||
}
|
||||
Rectangle {
|
||||
id: listContainer
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 12
|
||||
anchors.bottomMargin: 12
|
||||
color: "transparent"
|
||||
clip: true
|
||||
ListView {
|
||||
id: historyList
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
model: historyModel
|
||||
delegate: Item {
|
||||
height: notificationCard.implicitHeight + 12
|
||||
Rectangle {
|
||||
id: notificationCard
|
||||
width: parent.width - 24
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 16
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 0
|
||||
implicitHeight: contentColumn.implicitHeight + 20
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 6
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
spacing: 8
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 20
|
||||
color: Theme.accentPrimary
|
||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||
border.width: 1.2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
Column {
|
||||
id: appInfoColumn
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Text {
|
||||
text: model.appName || "Unknown App"
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Text {
|
||||
text: formatTimestamp(model.timestamp)
|
||||
color: Theme.textSecondary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Rectangle {
|
||||
id: deleteButton
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: deleteMouseArea.containsMouse ? Theme.accentPrimary : Theme.surfaceVariant
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
z: 2
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
Text {
|
||||
text: "close"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: deleteMouseArea.containsMouse ? Theme.onAccent : Theme.error
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: deleteMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
historyModel.remove(index)
|
||||
saveHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: model.summary || ""
|
||||
color: Theme.textSecondary
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Text {
|
||||
text: model.body || ""
|
||||
color: Theme.textDisabled
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 1; height: 24; color: "transparent" }
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Widgets/Notification/NotificationManager.qml
Normal file
181
Widgets/Notification/NotificationManager.qml
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Settings
|
||||
|
||||
PanelWindow {
|
||||
id: window
|
||||
width: 350
|
||||
implicitHeight: notificationColumn.implicitHeight + 20
|
||||
color: "transparent"
|
||||
visible: false
|
||||
screen: Quickshell.primaryScreen
|
||||
focusable: false
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: -20 // keep as you want
|
||||
margins.right: 6
|
||||
|
||||
property var notifications: []
|
||||
property int maxVisible: 5
|
||||
property int spacing: 10
|
||||
|
||||
function addNotification(notification) {
|
||||
var notifObj = {
|
||||
id: notification.id,
|
||||
appName: notification.appName || "Notification",
|
||||
summary: notification.summary || "",
|
||||
body: notification.body || "",
|
||||
rawNotification: notification
|
||||
};
|
||||
notifications.unshift(notifObj);
|
||||
|
||||
if (notifications.length > maxVisible) {
|
||||
notifications = notifications.slice(0, maxVisible);
|
||||
}
|
||||
|
||||
visible = true;
|
||||
notificationsChanged();
|
||||
}
|
||||
|
||||
function dismissNotification(id) {
|
||||
notifications = notifications.filter(n => n.id !== id);
|
||||
if (notifications.length === 0) {
|
||||
visible = false;
|
||||
}
|
||||
notificationsChanged();
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
anchors.right: parent.right
|
||||
spacing: window.spacing
|
||||
width: parent.width
|
||||
clip: false // prevent clipping during animation
|
||||
|
||||
Repeater {
|
||||
model: notifications
|
||||
|
||||
delegate: Rectangle {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
height: contentColumn.height + 20
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
opacity: 1
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
width: parent.width - 20
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
text: modelData.appName
|
||||
width: parent.width
|
||||
color: "white"
|
||||
font.family: Theme.fontFamily
|
||||
font.bold: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.summary
|
||||
width: parent.width
|
||||
color: "#eeeeee"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.body
|
||||
width: parent.width
|
||||
color: "#cccccc"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 4000
|
||||
running: true
|
||||
onTriggered: {
|
||||
dismissAnimation.start();
|
||||
if (modelData.rawNotification) {
|
||||
modelData.rawNotification.expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
dismissAnimation.start();
|
||||
if (modelData.rawNotification) {
|
||||
modelData.rawNotification.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: dismissAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 300
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: 0
|
||||
duration: 300
|
||||
}
|
||||
onFinished: window.dismissNotification(modelData.id)
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
opacity = 0;
|
||||
height = 0;
|
||||
appearAnimation.start();
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: appearAnimation
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "opacity"
|
||||
to: 1
|
||||
duration: 300
|
||||
}
|
||||
NumberAnimation {
|
||||
target: notificationDelegate
|
||||
property: "height"
|
||||
to: contentColumn.height + 20
|
||||
duration: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onNotificationsChanged: {
|
||||
height = notificationColumn.implicitHeight + 20
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (window.screen) {
|
||||
x = window.screen.width - width - 20
|
||||
// y stays as it is (margins.top = -20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
270
Widgets/Notification/NotificationPopup.qml
Normal file
270
Widgets/Notification/NotificationPopup.qml
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Settings
|
||||
|
||||
PanelWindow {
|
||||
id: window
|
||||
implicitWidth: 350
|
||||
implicitHeight: notificationColumn.implicitHeight
|
||||
color: "transparent"
|
||||
visible: notificationModel.count > 0
|
||||
screen: Quickshell.primaryScreen !== undefined ? Quickshell.primaryScreen : null
|
||||
focusable: false
|
||||
|
||||
property bool barVisible: true
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: 6
|
||||
margins.right: 6
|
||||
|
||||
ListModel {
|
||||
id: notificationModel
|
||||
}
|
||||
|
||||
property int maxVisible: 5
|
||||
property int spacing: 5
|
||||
|
||||
function addNotification(notification) {
|
||||
notificationModel.insert(0, {
|
||||
id: notification.id,
|
||||
appName: notification.appName || "Notification",
|
||||
summary: notification.summary || "",
|
||||
body: notification.body || "",
|
||||
rawNotification: notification,
|
||||
appeared: false,
|
||||
dismissed: false
|
||||
});
|
||||
|
||||
while (notificationModel.count > maxVisible) {
|
||||
notificationModel.remove(notificationModel.count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissNotificationById(id) {
|
||||
for (var i = 0; i < notificationModel.count; i++) {
|
||||
if (notificationModel.get(i).id === id) {
|
||||
dismissNotificationByIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dismissNotificationByIndex(index) {
|
||||
if (index >= 0 && index < notificationModel.count) {
|
||||
var notif = notificationModel.get(index);
|
||||
if (!notif.dismissed) {
|
||||
notificationModel.set(index, {
|
||||
id: notif.id,
|
||||
appName: notif.appName,
|
||||
summary: notif.summary,
|
||||
body: notif.body,
|
||||
rawNotification: notif.rawNotification,
|
||||
appeared: notif.appeared,
|
||||
dismissed: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
anchors.right: parent.right
|
||||
spacing: window.spacing
|
||||
width: parent.width
|
||||
clip: false
|
||||
|
||||
Repeater {
|
||||
id: notificationRepeater
|
||||
model: notificationModel
|
||||
|
||||
delegate: Rectangle {
|
||||
id: notificationDelegate
|
||||
width: parent.width
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 20
|
||||
|
||||
property bool appeared: model.appeared
|
||||
property bool dismissed: model.dismissed
|
||||
property var rawNotification: model.rawNotification
|
||||
|
||||
x: appeared ? 0 : width
|
||||
opacity: dismissed ? 0 : 1
|
||||
height: dismissed ? 0 : contentRow.height + 20
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
width: parent.width - 20
|
||||
|
||||
// Circular Icon container with border
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
width: 36
|
||||
height: 36
|
||||
radius: width / 2 // Circular
|
||||
color: Theme.accentPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||
border.width: 1.5
|
||||
|
||||
// Get all possible icon sources from notification
|
||||
property var iconSources: [
|
||||
rawNotification?.image || "",
|
||||
rawNotification?.appIcon || "",
|
||||
rawNotification?.icon || ""
|
||||
]
|
||||
|
||||
// Try to load notification icon
|
||||
Image {
|
||||
id: iconImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize.width: 36
|
||||
sourceSize.height: 36
|
||||
source: {
|
||||
for (var i = 0; i < iconBackground.iconSources.length; i++) {
|
||||
var icon = iconBackground.iconSources[i];
|
||||
if (!icon) continue;
|
||||
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
|
||||
if (icon.startsWith('/')) {
|
||||
return "file://" + icon;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
visible: status === Image.Ready && source.toString() !== ""
|
||||
}
|
||||
|
||||
// Fallback to first letter of app name
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImage.visible
|
||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
font.bold: true
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: contentRow.width - iconBackground.width - 10
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
text: model.appName
|
||||
width: parent.width
|
||||
color: Theme.textPrimary
|
||||
font.family: Theme.fontFamily
|
||||
font.bold: true
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
text: model.summary
|
||||
width: parent.width
|
||||
color: "#eeeeee"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
Text {
|
||||
text: model.body
|
||||
width: parent.width
|
||||
color: "#cccccc"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
wrapMode: Text.Wrap
|
||||
visible: text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 4000
|
||||
running: !dismissed
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification) rawNotification.expire();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
dismissAnimation.start();
|
||||
if (rawNotification) rawNotification.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: dismissAnimation
|
||||
NumberAnimation { target: notificationDelegate; property: "opacity"; to: 0; duration: 150 }
|
||||
NumberAnimation { target: notificationDelegate; property: "height"; to: 0; duration: 150 }
|
||||
NumberAnimation { target: notificationDelegate; property: "x"; to: width; duration: 150; easing.type: Easing.InQuad }
|
||||
onFinished: {
|
||||
var idx = notificationRepeater.indexOf(notificationDelegate);
|
||||
if (idx !== -1) {
|
||||
notificationModel.remove(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: appearAnimation
|
||||
NumberAnimation { target: notificationDelegate; property: "opacity"; to: 1; duration: 150 }
|
||||
NumberAnimation { target: notificationDelegate; property: "height"; to: contentRow.height + 20; duration: 150 }
|
||||
NumberAnimation { target: notificationDelegate; property: "x"; to: 0; duration: 150; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!appeared) {
|
||||
opacity = 0;
|
||||
height = 0;
|
||||
x = width;
|
||||
appearAnimation.start();
|
||||
var idx = notificationRepeater.indexOf(notificationDelegate);
|
||||
if (idx !== -1) {
|
||||
var oldItem = notificationModel.get(idx);
|
||||
notificationModel.set(idx, {
|
||||
id: oldItem.id,
|
||||
appName: oldItem.appName,
|
||||
summary: oldItem.summary,
|
||||
body: oldItem.body,
|
||||
rawNotification: oldItem.rawNotification,
|
||||
appeared: true,
|
||||
dismissed: oldItem.dismissed
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (window.screen) {
|
||||
x = window.screen.width - width - 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue