notification daemon + center
This commit is contained in:
@@ -19,6 +19,12 @@ Scope {
|
||||
property bool trayMenuVisible: false
|
||||
screen: modelData
|
||||
|
||||
Process {
|
||||
id: ncProcess
|
||||
command: ["sh", "-c", "qs -p /home/zach/GitProjects/z-bar-qt/shell.qml ipc call root showCenter"]
|
||||
running: false
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
@@ -93,8 +99,18 @@ Scope {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
SwayNC {
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: "\ue7f4"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 22
|
||||
color: "white"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
ncProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
import qs.Modules
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
Text {
|
||||
text: "\ue7f4"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pixelSize: 16
|
||||
color: "white"
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
property list<int> notifIds: []
|
||||
NotificationServer {
|
||||
id: notificationServer
|
||||
imageSupported: true
|
||||
actionsSupported: true
|
||||
persistenceSupported: true
|
||||
bodyImagesSupported: true
|
||||
bodySupported: true
|
||||
onNotification: {
|
||||
notification.tracked = true;
|
||||
notification.receivedTime = Date.now();
|
||||
root.notifIds.push(notification.id);
|
||||
notificationComponent.createObject(root, { notif: notification, visible: !notificationCenter.doNotDisturb });
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notificationComponent
|
||||
Notification {
|
||||
centerX: notificationCenter.posX
|
||||
notifIndex: root.notifIds
|
||||
onNotifDestroy: {
|
||||
root.notifIds.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter {
|
||||
id: notificationCenter
|
||||
notifications: notificationServer.trackedNotifications.values
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import QtQuick.Layouts
|
||||
import QtQuick
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
left: true
|
||||
bottom: true
|
||||
}
|
||||
mask: Region { item: backgroundRect }
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
required property Notification notif
|
||||
required property int centerX
|
||||
required property list<int> notifIndex
|
||||
property int index: notifIndex.indexOf(notif.id)
|
||||
signal notifDestroy()
|
||||
|
||||
Component.onCompleted: {
|
||||
openAnim.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeout
|
||||
interval: 5000
|
||||
onTriggered: {
|
||||
closeAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: openAnim
|
||||
target: backgroundRect
|
||||
property: "x"
|
||||
from: root.centerX
|
||||
to: root.centerX - backgroundRect.implicitWidth - 20
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
onStopped: { timeout.start(); }
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: closeAnim
|
||||
target: backgroundRect
|
||||
property: "x"
|
||||
from: root.centerX - backgroundRect.implicitWidth - 20
|
||||
to: root.centerX
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
onStopped: {
|
||||
root.destroy();
|
||||
root.notifDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundRect
|
||||
implicitWidth: 400
|
||||
implicitHeight: 80
|
||||
x: root.centerX - implicitWidth - 20
|
||||
y: 34 + 20 + ( root.index * ( implicitHeight + 10 ))
|
||||
color: "#801a1a1a"
|
||||
border.color: "#555555"
|
||||
radius: 8
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
IconImage {
|
||||
source: root.notif.image
|
||||
Layout.preferredWidth: 64
|
||||
Layout.preferredHeight: 64
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft
|
||||
visible: root.notif.image !== ""
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: root.notif.image !== "" ? 0 : 16
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
|
||||
Text {
|
||||
text: root.notif.appName
|
||||
color: "white"
|
||||
font.bold: true
|
||||
font.pointSize: 12
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.notif.summary
|
||||
color: "white"
|
||||
font.pointSize: 10
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.notif.body
|
||||
color: "#dddddd"
|
||||
font.pointSize: 8
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 6
|
||||
anchors.topMargin: 6
|
||||
width: 18
|
||||
height: 18
|
||||
color: closeArea.containsMouse ? "#FF6077" : "transparent"
|
||||
radius: 9
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: closeArea.containsMouse ? "white" : "#888888"
|
||||
font.pointSize: 12
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
root.notif.dismiss();
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.FluentWinUI3
|
||||
import QtQuick.Effects
|
||||
import QtQuick
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
color: "transparent"
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
left: true
|
||||
bottom: true
|
||||
}
|
||||
required property list<Notification> notifications
|
||||
property bool centerShown: false
|
||||
property alias posX: backgroundRect.x
|
||||
property alias doNotDisturb: dndSwitch.checked
|
||||
visible: false
|
||||
|
||||
IpcHandler {
|
||||
id: ipcHandler
|
||||
target: "root"
|
||||
|
||||
function showCenter(): void { root.centerShown = !root.centerShown; }
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if ( root.visible ) {
|
||||
showAnimation.start();
|
||||
}
|
||||
}
|
||||
|
||||
onCenterShownChanged: {
|
||||
if ( !root.centerShown ) {
|
||||
closeAnimation.start();
|
||||
closeTimer.start();
|
||||
} else {
|
||||
root.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: 300
|
||||
onTriggered: {
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: showAnimation
|
||||
target: backgroundRect
|
||||
property: "x"
|
||||
from: Screen.width
|
||||
to: Screen.width - backgroundRect.implicitWidth - 10
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: closeAnimation
|
||||
target: backgroundRect
|
||||
property: "x"
|
||||
from: Screen.width - backgroundRect.implicitWidth - 10
|
||||
to: Screen.width
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: groupedData
|
||||
property var groups: ({})
|
||||
|
||||
function updateGroups() {
|
||||
var newGroups = {};
|
||||
for ( var i = 0; i < root.notifications.length; i++ ) {
|
||||
var notif = root.notifications[ i ];
|
||||
var appName = notif.appName || "Unknown";
|
||||
if ( !newGroups[ appName ]) {
|
||||
newGroups[ appName ] = [];
|
||||
}
|
||||
newGroups[ appName ].push( notif );
|
||||
}
|
||||
// Sort notifications within each group (latest first)
|
||||
for ( var app in newGroups ) {
|
||||
newGroups[ app ].sort(( a, b ) => b.receivedTime - a.receivedTime );
|
||||
}
|
||||
groups = newGroups;
|
||||
groupsChanged();
|
||||
}
|
||||
|
||||
Component.onCompleted: updateGroups()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onNotificationsChanged() {
|
||||
groupedData.updateGroups();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: {
|
||||
if ( root.centerShown ) {
|
||||
root.centerShown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backgroundRect
|
||||
y: 10
|
||||
x: Screen.width
|
||||
implicitWidth: 400
|
||||
implicitHeight: root.height - 20
|
||||
color: "#801a1a1a"
|
||||
radius: 8
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Switch {
|
||||
id: dndSwitch
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
text: "Do Not Disturb"
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
text: "Clear all"
|
||||
color: "white"
|
||||
}
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.preferredWidth: 30
|
||||
Layout.preferredHeight: 30
|
||||
color: clearArea.containsMouse ? "#15FFFFFF" : "transparent"
|
||||
radius: 4
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\ue0b8"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: 18
|
||||
color: "white"
|
||||
}
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
for ( var app in groupedData.groups ) {
|
||||
groupedData.groups[ app ].forEach( function( n ) { n.dismiss(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "#333333"
|
||||
Layout.preferredHeight: 1
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contentHeight: notificationColumn.implicitHeight
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
add: Transition {
|
||||
NumberAnimation {
|
||||
properties: "x";
|
||||
duration: 300;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x";
|
||||
duration: 200;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Object.keys( groupedData.groups )
|
||||
Column {
|
||||
id: groupColumn
|
||||
required property string modelData
|
||||
property var notifications: groupedData.groups[ modelData ]
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
property bool shouldShow: false
|
||||
property bool isExpanded: false
|
||||
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x";
|
||||
duration: 100;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
height: 30
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.leftMargin: 5
|
||||
text: groupColumn.modelData
|
||||
color: "white"
|
||||
font.pointSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 30
|
||||
color: collapseArea.containsMouse ? "#15FFFFFF" : "transparent"
|
||||
radius: 4
|
||||
visible: groupColumn.isExpanded
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\ue944"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: 18
|
||||
color: "white"
|
||||
}
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
groupColumn.isExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: groupColumn.notifications
|
||||
Rectangle {
|
||||
id: groupHeader
|
||||
required property var modelData
|
||||
required property var index
|
||||
width: parent.width
|
||||
height: groupColumn.isExpanded ? ( modelData.actions.length > 1 ? 130 : 80 ) : 80
|
||||
color: "#801a1a1a"
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
radius: 8
|
||||
visible: groupColumn.notifications[0].id === modelData.id || groupColumn.isExpanded
|
||||
|
||||
// NumberAnimation {
|
||||
// id: expandAnim
|
||||
// target: groupHeader
|
||||
// property: "y"
|
||||
// duration: 300
|
||||
// easing.type: Easing.OutCubic
|
||||
// from: (( groupHeader.height / 2 ) * index )
|
||||
// to: (( groupHeader.height + 60 ) * index )
|
||||
// onStarted: {
|
||||
// groupColumn.shouldShow = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// NumberAnimation {
|
||||
// id: collapseAnim
|
||||
// target: groupHeader
|
||||
// property: "y"
|
||||
// duration: 300
|
||||
// easing.type: Easing.OutCubic
|
||||
// from: (( groupHeader.height + 60 ) * index )
|
||||
// to: (( groupHeader.height / 2 ) * index )
|
||||
// onStopped: {
|
||||
// groupColumn.isExpanded = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
Connections {
|
||||
target: groupColumn
|
||||
function onShouldShowChanged() {
|
||||
if ( !shouldShow ) {
|
||||
// collapseAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if ( visible ) {
|
||||
// expandAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if ( groupColumn.isExpanded ) {
|
||||
if ( groupHeader.modelData.actions.length === 1 ) {
|
||||
groupHeader.modelData.actions[0].invoke();
|
||||
}
|
||||
} else {
|
||||
groupColumn.isExpanded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
RowLayout {
|
||||
height: 80
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
IconImage {
|
||||
source: groupHeader.modelData.image
|
||||
Layout.preferredWidth: 48
|
||||
Layout.preferredHeight: 48
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.topMargin: 5
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: groupHeader.modelData.summary
|
||||
color: "white"
|
||||
font.bold: true
|
||||
font.pointSize: 12
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: groupHeader.modelData.body
|
||||
font.pointSize: 10
|
||||
color: "#dddddd"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: groupColumn.notifications.length > 1 ? ( groupColumn.isExpanded ? "" : "(" + groupColumn.notifications.length + ")" ) : ""
|
||||
font.pointSize: 10
|
||||
color: "#666666"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 2
|
||||
visible: groupColumn.isExpanded && groupHeader.modelData.actions.length > 1
|
||||
height: 15
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: groupHeader.modelData.actions
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
required property var modelData
|
||||
color: buttonArea.containsMouse ? "#15FFFFFF" : "#09FFFFFF"
|
||||
radius: 4
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.text
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
}
|
||||
MouseArea {
|
||||
id: buttonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
modelData.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 6
|
||||
anchors.topMargin: 6
|
||||
width: 18
|
||||
height: 18
|
||||
color: closeArea.containsMouse ? "#FF6077" : "transparent"
|
||||
radius: 9
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: closeArea.containsMouse ? "white" : "#888888"
|
||||
font.pointSize: 12
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
groupColumn.isExpanded ? groupColumn.notifications[0].dismiss() : groupColumn.notifications.forEach( function( n ) { n.dismiss(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,8 @@ MouseArea {
|
||||
if ( mouse.button === Qt.LeftButton ) {
|
||||
root.item.activate();
|
||||
} else if ( mouse.button === Qt.RightButton ) {
|
||||
trayMenu.trayMenu = root.item?.menu;
|
||||
trayMenu.visible = !trayMenu.visible;
|
||||
trayMenu.trayMenu = root.item?.menu;
|
||||
trayMenu.focusGrab = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ PanelWindow {
|
||||
if ( visible ) {
|
||||
scaleValue = 0;
|
||||
scaleAnimation.start();
|
||||
} else {
|
||||
root.menuStack.pop();
|
||||
backEntry.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Widgets
|
||||
@@ -23,7 +21,6 @@ PanelWindow {
|
||||
property bool centerShown: false
|
||||
property alias posX: backgroundRect.x
|
||||
property alias doNotDisturb: dndSwitch.checked
|
||||
property alias groupedData: groupedData
|
||||
visible: false
|
||||
|
||||
IpcHandler {
|
||||
@@ -76,94 +73,35 @@ PanelWindow {
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
ListModel {
|
||||
QtObject {
|
||||
id: groupedData
|
||||
property int totalCount: 0
|
||||
property var groupMap: ({})
|
||||
property var groups: ({})
|
||||
|
||||
function mapGroups() {
|
||||
groupedData.groupMap = {};
|
||||
for ( var i = 0; i < groupedData.count; i++ ) {
|
||||
var name = get(i).name;
|
||||
groupMap[ name ] = i;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
var count = 0;
|
||||
for ( var i = 0; i < groupedData.count; i++ ) {
|
||||
count += get(i).notifications.count;
|
||||
}
|
||||
totalCount = count;
|
||||
}
|
||||
|
||||
function ensureGroup(appName) {
|
||||
for ( var i = 0; i < count; i++ ) {
|
||||
if ( get(i).name === appName ) {
|
||||
return get(i).notifications;
|
||||
}
|
||||
}
|
||||
var model = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', root);
|
||||
append({ name: appName, notifications: model });
|
||||
mapGroups();
|
||||
return model;
|
||||
}
|
||||
|
||||
function addNotification(notif) {
|
||||
var appName = notif.appName
|
||||
var model = ensureGroup(appName);
|
||||
model.insert(0, notif);
|
||||
updateCount();
|
||||
}
|
||||
|
||||
function removeNotification(notif) {
|
||||
function updateGroups() {
|
||||
var newGroups = {};
|
||||
for ( var i = 0; i < root.notifications.length; i++ ) {
|
||||
var notif = root.notifications[ i ];
|
||||
var appName = notif.appName || "Unknown";
|
||||
var group = get( groupMap[ appName ]);
|
||||
if ( group.name === appName ) {
|
||||
root.notifications[ idMap[ notif.id ]].dismiss();
|
||||
if ( group.notifications.count === 0 ) {
|
||||
remove( groupMap[ appName ], 1 );
|
||||
mapGroups();
|
||||
if ( !newGroups[ appName ]) {
|
||||
newGroups[ appName ] = [];
|
||||
}
|
||||
newGroups[ appName ].push( notif );
|
||||
}
|
||||
updateCount();
|
||||
// Sort notifications within each group (latest first)
|
||||
for ( var app in newGroups ) {
|
||||
newGroups[ app ].sort(( a, b ) => b.receivedTime - a.receivedTime );
|
||||
}
|
||||
groups = newGroups;
|
||||
groupsChanged();
|
||||
}
|
||||
|
||||
function removeGroup(notif) {
|
||||
var appName = notif.appName || "Unknown";
|
||||
var group = get(groupMap[ appName ]);
|
||||
if ( group.name === appName ) {
|
||||
for ( var i = 0; i < group.notifications.count; i++ ) {
|
||||
var item = group.notifications.get( i );
|
||||
item.dismiss();
|
||||
}
|
||||
remove( groupMap[ appName ], 1 );
|
||||
updateCount();
|
||||
mapGroups();
|
||||
}
|
||||
}
|
||||
|
||||
function resetGroups(notifications) {
|
||||
groupedData.clear();
|
||||
for (var i = 0; i < root.notifications.length; i++) {
|
||||
addNotification(root.notifications[i]);
|
||||
}
|
||||
updateCount();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
resetGroups(root.notifications)
|
||||
}
|
||||
Component.onCompleted: updateGroups()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onNotificationsChanged() {
|
||||
if ( root.notifications.length > groupedData.totalCount ) {
|
||||
groupedData.addNotification( root.notifications[ root.notifications.length - 1 ] );
|
||||
groupedData.mapGroups();
|
||||
console.log(root.notifications)
|
||||
}
|
||||
groupedData.updateGroups();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +111,6 @@ PanelWindow {
|
||||
onClicked: {
|
||||
if ( root.centerShown ) {
|
||||
root.centerShown = false;
|
||||
console.log("groups", groupedData.count);
|
||||
console.log(root.notifications)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,29 +125,51 @@ PanelWindow {
|
||||
radius: 8
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
Layout.preferredHeight: 30
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
text: "Notifications"
|
||||
color: "white"
|
||||
font.bold: true
|
||||
font.pointSize: 16
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: dndSwitch
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
text: "Do Not Disturb"
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
text: "Clear all"
|
||||
color: "white"
|
||||
}
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.preferredWidth: 30
|
||||
Layout.preferredHeight: 30
|
||||
color: clearArea.containsMouse ? "#15FFFFFF" : "transparent"
|
||||
radius: 4
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\ue0b8"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: 18
|
||||
color: "white"
|
||||
}
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
for ( var app in groupedData.groups ) {
|
||||
groupedData.groups[ app ].forEach( function( n ) { n.dismiss(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -220,133 +178,173 @@ PanelWindow {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
Layout.fillHeight: true
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
// width: mainLayout.width
|
||||
spacing: 10
|
||||
Layout.fillHeight: true
|
||||
contentHeight: notificationColumn.implicitHeight
|
||||
clip: true
|
||||
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x"
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
Column {
|
||||
id: notificationColumn
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
add: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x"
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
properties: "x";
|
||||
duration: 300;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: groupListView
|
||||
model: groupedData
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x";
|
||||
duration: 200;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Object.keys( groupedData.groups )
|
||||
Column {
|
||||
id: groupColumn
|
||||
required property string modelData
|
||||
property var notifications: groupedData.groups[ modelData ]
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
width: 400
|
||||
height: Math.min(contentHeight, notificationColumn.height)
|
||||
// Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
|
||||
delegate: ListView {
|
||||
required property var modelData
|
||||
required property int index
|
||||
property bool shouldShow: false
|
||||
property bool isExpanded: false
|
||||
property bool isExpandedAnim: false
|
||||
id: listView
|
||||
visible: true
|
||||
property ListModel notificationsModel: modelData.notifications
|
||||
model: notificationsModel
|
||||
width: notificationColumn.width
|
||||
implicitHeight: listView.isExpandedAnim ? contentHeight : 80
|
||||
clip: true
|
||||
|
||||
spacing: 10
|
||||
|
||||
onIsExpandedChanged: {
|
||||
if ( !isExpanded ) {
|
||||
collapseAnim.start();
|
||||
} else {
|
||||
expandAnim.start();
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
id: collapseAnim
|
||||
target: listView
|
||||
property: "implicitHeight"
|
||||
to: 80
|
||||
from: listView.contentHeight
|
||||
duration: 80
|
||||
easing.type: Easing.InOutQuad
|
||||
onStopped: {
|
||||
listView.isExpandedAnim = listView.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: expandAnim
|
||||
target: listView
|
||||
property: "implicitHeight"
|
||||
from: 80
|
||||
to: listView.contentHeight
|
||||
duration: 80
|
||||
easing.type: Easing.InOutQuad
|
||||
onStopped: {
|
||||
listView.isExpandedAnim = listView.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y,x"
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: notificationItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
width: listView.width
|
||||
height: 80
|
||||
color: "#801a1a1a"
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
radius: 8
|
||||
clip: true
|
||||
visible: true
|
||||
opacity: 1
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
listView.isExpanded ? ( notificationItem.index === 0 ? listView.isExpanded = false : null ) : listView.isExpanded = true;
|
||||
properties: "y,x";
|
||||
duration: 100;
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
height: 30
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.leftMargin: 5
|
||||
text: groupColumn.modelData
|
||||
color: "white"
|
||||
font.pointSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 30
|
||||
color: collapseArea.containsMouse ? "#15FFFFFF" : "transparent"
|
||||
radius: 4
|
||||
visible: groupColumn.isExpanded
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\ue944"
|
||||
font.family: "Material Symbols Rounded"
|
||||
font.pointSize: 18
|
||||
color: "white"
|
||||
}
|
||||
MouseArea {
|
||||
id: collapseArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
groupColumn.isExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: groupColumn.notifications
|
||||
Rectangle {
|
||||
id: groupHeader
|
||||
required property var modelData
|
||||
required property var index
|
||||
width: parent.width
|
||||
height: groupColumn.isExpanded ? ( modelData.actions.length > 1 ? 130 : 80 ) : 80
|
||||
color: "#801a1a1a"
|
||||
border.color: "#555555"
|
||||
border.width: 1
|
||||
radius: 8
|
||||
visible: groupColumn.notifications[0].id === modelData.id || groupColumn.isExpanded
|
||||
|
||||
// NumberAnimation {
|
||||
// id: expandAnim
|
||||
// target: groupHeader
|
||||
// property: "y"
|
||||
// duration: 300
|
||||
// easing.type: Easing.OutCubic
|
||||
// from: (( groupHeader.height / 2 ) * index )
|
||||
// to: (( groupHeader.height + 60 ) * index )
|
||||
// onStarted: {
|
||||
// groupColumn.shouldShow = true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// NumberAnimation {
|
||||
// id: collapseAnim
|
||||
// target: groupHeader
|
||||
// property: "y"
|
||||
// duration: 300
|
||||
// easing.type: Easing.OutCubic
|
||||
// from: (( groupHeader.height + 60 ) * index )
|
||||
// to: (( groupHeader.height / 2 ) * index )
|
||||
// onStopped: {
|
||||
// groupColumn.isExpanded = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
Connections {
|
||||
target: groupColumn
|
||||
function onShouldShowChanged() {
|
||||
if ( !shouldShow ) {
|
||||
// collapseAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if ( visible ) {
|
||||
// expandAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if ( groupColumn.isExpanded ) {
|
||||
if ( groupHeader.modelData.actions.length === 1 ) {
|
||||
groupHeader.modelData.actions[0].invoke();
|
||||
}
|
||||
} else {
|
||||
groupColumn.isExpanded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
RowLayout {
|
||||
height: 80
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
IconImage {
|
||||
source: notificationItem.modelData.image
|
||||
source: groupHeader.modelData.image
|
||||
Layout.preferredWidth: 48
|
||||
Layout.preferredHeight: 48
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.topMargin: 5
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -355,7 +353,7 @@ PanelWindow {
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: notificationItem.modelData.appName
|
||||
text: groupHeader.modelData.summary
|
||||
color: "white"
|
||||
font.bold: true
|
||||
font.pointSize: 12
|
||||
@@ -364,27 +362,56 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Text {
|
||||
text: notificationItem.modelData.summary
|
||||
color: "white"
|
||||
text: groupHeader.modelData.body
|
||||
font.pointSize: 10
|
||||
color: "#dddddd"
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: notificationItem.modelData.body
|
||||
color: "#dddddd"
|
||||
font.pointSize: 8
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
text: groupColumn.notifications.length > 1 ? ( groupColumn.isExpanded ? "" : "(" + groupColumn.notifications.length + ")" ) : ""
|
||||
font.pointSize: 10
|
||||
color: "#666666"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 2
|
||||
visible: groupColumn.isExpanded && groupHeader.modelData.actions.length > 1
|
||||
height: 15
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: groupHeader.modelData.actions
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
required property var modelData
|
||||
color: buttonArea.containsMouse ? "#15FFFFFF" : "#09FFFFFF"
|
||||
radius: 4
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.text
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
}
|
||||
MouseArea {
|
||||
id: buttonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
modelData.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
@@ -407,7 +434,9 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
root.notifications[modelData].dismiss();
|
||||
groupColumn.isExpanded ? groupColumn.notifications[0].dismiss() : groupColumn.notifications.forEach( function( n ) { n.dismiss(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ Scope {
|
||||
notification.tracked = true;
|
||||
notification.receivedTime = Date.now();
|
||||
root.notifIds.push(notification.id);
|
||||
notificationCenter.groupedData.addNotification(notification);
|
||||
notificationComponent.createObject(root, { notif: notification, visible: !notificationCenter.doNotDisturb });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user