421 lines
14 KiB
QML
421 lines
14 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
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
|
|
property alias groupedData: groupedData
|
|
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
|
|
}
|
|
|
|
ListModel {
|
|
id: groupedData
|
|
property int totalCount: 0
|
|
property var groupMap: ({})
|
|
|
|
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) {
|
|
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();
|
|
}
|
|
}
|
|
updateCount();
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onClicked: {
|
|
if ( root.centerShown ) {
|
|
root.centerShown = false;
|
|
console.log("groups", groupedData.count);
|
|
console.log(root.notifications)
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: backgroundRect
|
|
y: 10
|
|
x: Screen.width
|
|
implicitWidth: 400
|
|
implicitHeight: root.height - 20
|
|
color: "#801a1a1a"
|
|
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
|
|
text: "Do Not Disturb"
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
color: "#333333"
|
|
Layout.preferredHeight: 1
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Column {
|
|
id: notificationColumn
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
// width: mainLayout.width
|
|
spacing: 10
|
|
clip: true
|
|
|
|
move: Transition {
|
|
NumberAnimation {
|
|
properties: "y,x"
|
|
duration: 200
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
|
|
add: Transition {
|
|
NumberAnimation {
|
|
properties: "y,x"
|
|
duration: 200
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: groupListView
|
|
model: groupedData
|
|
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 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();
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 8
|
|
spacing: 10
|
|
|
|
IconImage {
|
|
source: notificationItem.modelData.image
|
|
Layout.preferredWidth: 48
|
|
Layout.preferredHeight: 48
|
|
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 4
|
|
|
|
Text {
|
|
text: notificationItem.modelData.appName
|
|
color: "white"
|
|
font.bold: true
|
|
font.pointSize: 12
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Text {
|
|
text: notificationItem.modelData.summary
|
|
color: "white"
|
|
font.pointSize: 10
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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.notifications[modelData].dismiss();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|