Skip to content

Commit 4154698

Browse files
authored
Merge pull request #624 from Serph91P/feat/ntfy-notifications
2 parents 0851036 + 3b4f4c2 commit 4154698

9 files changed

Lines changed: 1368 additions & 0 deletions

File tree

ntfy-notifications/BarWidget.qml

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import QtQuick
2+
import QtQuick.Layouts
3+
import Quickshell
4+
import qs.Commons
5+
import qs.Services.UI
6+
import qs.Widgets
7+
8+
Item {
9+
id: root
10+
11+
property var pluginApi: null
12+
property ShellScreen screen
13+
property string widgetId: ""
14+
property string section: ""
15+
property int sectionWidgetIndex: -1
16+
property int sectionWidgetsCount: 0
17+
18+
readonly property string screenName: screen ? screen.name : ""
19+
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
20+
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
21+
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
22+
readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName)
23+
24+
readonly property var main: pluginApi?.mainInstance
25+
readonly property int unreadCount: main ? main.unreadCount : 0
26+
27+
// Track previous count for pulse animation
28+
property int previousUnreadCount: 0
29+
30+
implicitWidth: isVertical ? capsuleHeight : contentRow.implicitWidth + Style.marginM * 2
31+
implicitHeight: isVertical ? capsuleHeight * 1.5 : capsuleHeight
32+
33+
// Context menu
34+
NPopupContextMenu {
35+
id: contextMenu
36+
model: [
37+
{ "label": pluginApi?.tr("menu.refresh"), "action": "refresh", "icon": "refresh" },
38+
{ "label": pluginApi?.tr("menu.markAllRead"), "action": "markAllRead", "icon": "circle-check" },
39+
{ "label": pluginApi?.tr("menu.settings"), "action": "settings", "icon": "settings" }
40+
]
41+
onTriggered: action => {
42+
contextMenu.close();
43+
PanelService.closeContextMenu(screen);
44+
if (action === "settings") {
45+
BarService.openPluginSettings(root.screen, pluginApi.manifest);
46+
} else if (action === "refresh" && main) {
47+
main.pollMessages();
48+
} else if (action === "markAllRead" && main) {
49+
main.markAllAsRead();
50+
}
51+
}
52+
}
53+
54+
Rectangle {
55+
id: capsule
56+
anchors.centerIn: parent
57+
width: root.implicitWidth
58+
height: root.implicitHeight
59+
radius: Style.radiusM
60+
color: mouseArea.containsMouse ? Color.mHover : Style.capsuleColor
61+
border.color: Style.capsuleBorderColor
62+
border.width: Style.capsuleBorderWidth
63+
64+
MouseArea {
65+
id: mouseArea
66+
anchors.fill: parent
67+
hoverEnabled: true
68+
cursorShape: Qt.PointingHandCursor
69+
acceptedButtons: Qt.LeftButton | Qt.RightButton
70+
71+
onClicked: mouse => {
72+
if (mouse.button === Qt.LeftButton) {
73+
if (pluginApi) pluginApi.togglePanel(root.screen, root);
74+
} else if (mouse.button === Qt.RightButton) {
75+
PanelService.showContextMenu(contextMenu, root, screen);
76+
}
77+
}
78+
79+
onEntered: {
80+
var text = unreadCount > 0
81+
? pluginApi?.tr("widget.tooltipWithCount", { count: unreadCount })
82+
: pluginApi?.tr("widget.tooltip");
83+
TooltipService.show(root, text, BarService.getTooltipDirection());
84+
}
85+
86+
onExited: {
87+
TooltipService.hide();
88+
}
89+
}
90+
91+
// Horizontal layout
92+
RowLayout {
93+
id: contentRow
94+
anchors.centerIn: parent
95+
spacing: Style.marginXS
96+
visible: !isVertical
97+
98+
NIcon {
99+
icon: "bell"
100+
width: root.barFontSize * 1.4
101+
height: width
102+
color: unreadCount > 0 ? Color.mPrimary : (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface)
103+
}
104+
105+
// Badge
106+
Rectangle {
107+
visible: unreadCount > 0
108+
width: Math.max(badgeText.implicitWidth + Style.marginS * 2, height)
109+
height: root.barFontSize * 1.3
110+
radius: height / 2
111+
color: Color.mPrimary
112+
113+
NText {
114+
id: badgeText
115+
anchors.centerIn: parent
116+
text: unreadCount > 99 ? "99+" : unreadCount.toString()
117+
pointSize: root.barFontSize * 0.7
118+
applyUiScale: false
119+
color: Color.mOnPrimary
120+
font.weight: Style.fontWeightBold
121+
}
122+
123+
// Pulse animation on new messages
124+
SequentialAnimation {
125+
id: pulseAnimation
126+
loops: 2
127+
PropertyAnimation {
128+
target: capsule
129+
property: "scale"
130+
from: 1.0; to: 1.1
131+
duration: 150
132+
easing.type: Easing.OutQuad
133+
}
134+
PropertyAnimation {
135+
target: capsule
136+
property: "scale"
137+
from: 1.1; to: 1.0
138+
duration: 150
139+
easing.type: Easing.InQuad
140+
}
141+
}
142+
}
143+
}
144+
145+
// Vertical layout
146+
ColumnLayout {
147+
anchors.centerIn: parent
148+
spacing: Style.marginXXS
149+
visible: isVertical
150+
151+
NIcon {
152+
icon: "bell"
153+
width: root.barFontSize * 1.4
154+
height: width
155+
color: unreadCount > 0 ? Color.mPrimary : (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface)
156+
Layout.alignment: Qt.AlignHCenter
157+
}
158+
159+
NText {
160+
visible: unreadCount > 0
161+
text: unreadCount > 99 ? "99+" : unreadCount.toString()
162+
pointSize: root.barFontSize * 0.6
163+
applyUiScale: false
164+
color: Color.mPrimary
165+
font.weight: Style.fontWeightBold
166+
Layout.alignment: Qt.AlignHCenter
167+
}
168+
}
169+
}
170+
171+
// Watch for unread count changes to trigger pulse
172+
onUnreadCountChanged: {
173+
if (unreadCount > previousUnreadCount && previousUnreadCount >= 0) {
174+
pulseAnimation.start();
175+
}
176+
previousUnreadCount = unreadCount;
177+
}
178+
}

0 commit comments

Comments
 (0)