732 lines
30 KiB
QML
732 lines
30 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Services.Notifications
|
|
|
|
import "../../config" as Cfg
|
|
import "../../components"
|
|
|
|
ModuleChip {
|
|
id: root
|
|
|
|
property int unreadCount: 0
|
|
property bool doNotDisturb: false
|
|
property bool historyOpen: historyPopup.popupOpen
|
|
|
|
width: 35
|
|
height: Cfg.Config.moduleHeight
|
|
radius: panelRadius
|
|
|
|
chipColor: t.notifBg
|
|
chipColorHovered: t.notifBgHover
|
|
popupOpen: historyOpen
|
|
|
|
border.color: root.doNotDisturb
|
|
? t.notifDnd
|
|
: ((hovered || historyOpen) ? t.notifBorderActive : t.notifBorder)
|
|
|
|
Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
|
|
|
|
component NotificationCard: Item {
|
|
id: toastRoot
|
|
|
|
property string toastId: ""
|
|
property string appName: ""
|
|
property string summary: ""
|
|
property string body: ""
|
|
property int urgency: NotificationUrgency.Normal
|
|
property string timeStr: ""
|
|
property int timeout: 5000
|
|
|
|
property var t
|
|
property var urgencyColor
|
|
property var urgencyIcon
|
|
|
|
signal dismissed(string toastId)
|
|
|
|
width: parent ? parent.width : 350
|
|
height: toastCard.height
|
|
|
|
property real progressVal: 1.0
|
|
property bool slideFromRight: false
|
|
property real slideX: slideFromRight ? (width + 20) : -(width + 20)
|
|
|
|
transform: [
|
|
Translate { id: slideTranslate; x: toastRoot.slideX },
|
|
Scale { id: bubbleScale; yScale: 0; origin.y: toastLayer.atBottom ? toastRoot.height : 0 }
|
|
]
|
|
|
|
Component.onCompleted: entranceAnim.start()
|
|
|
|
ParallelAnimation {
|
|
id: entranceAnim
|
|
NumberAnimation {
|
|
target: slideTranslate; property: "x"
|
|
from: toastRoot.slideX; to: 0
|
|
duration: 320; easing.type: Easing.OutCubic
|
|
}
|
|
NumberAnimation {
|
|
target: bubbleScale; property: "yScale"
|
|
from: 0; to: 1
|
|
duration: 280; easing.type: Easing.OutBack
|
|
}
|
|
}
|
|
|
|
function triggerDismiss() {
|
|
if (exitAnim.running) return
|
|
autoTimer.stop()
|
|
exitAnim.start()
|
|
}
|
|
|
|
ParallelAnimation {
|
|
id: exitAnim
|
|
NumberAnimation {
|
|
target: slideTranslate; property: "x"
|
|
to: toastRoot.slideX
|
|
duration: 250; easing.type: Easing.InCubic
|
|
}
|
|
NumberAnimation {
|
|
target: bubbleScale; property: "yScale"
|
|
from: 1; to: 0
|
|
duration: 200; easing.type: Easing.InBack
|
|
}
|
|
onStopped: toastRoot.dismissed(toastRoot.toastId)
|
|
}
|
|
|
|
Timer {
|
|
id: autoTimer
|
|
interval: toastRoot.timeout
|
|
running: toastRoot.timeout > 0
|
|
repeat: false
|
|
onTriggered: toastRoot.triggerDismiss()
|
|
}
|
|
|
|
NumberAnimation on progressVal {
|
|
from: 1.0; to: 0.0
|
|
duration: toastRoot.timeout
|
|
running: toastRoot.urgency === NotificationUrgency.Low && toastRoot.timeout > 0
|
|
}
|
|
|
|
Rectangle {
|
|
id: toastCard
|
|
width: parent.width
|
|
height: toastContent.implicitHeight + (toastRoot.urgency === NotificationUrgency.Low ? 30 : 24)
|
|
radius: 16
|
|
|
|
color: cardHover.hovered ? toastRoot.t.notifToastBgHover : toastRoot.t.notifToastBg
|
|
border.color: toastRoot.urgency === NotificationUrgency.Critical
|
|
? toastRoot.t.notifUrgencyCritical
|
|
: (cardHover.hovered ? toastRoot.t.notifBorderActive : toastRoot.t.notifToastBorder)
|
|
border.width: Cfg.Config.popupBorderWidth
|
|
|
|
Behavior on color { ColorAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
|
Behavior on border.color { ColorAnimation { duration: 150 } }
|
|
|
|
HoverHandler { id: cardHover }
|
|
TapHandler { onTapped: toastRoot.triggerDismiss() }
|
|
|
|
Rectangle {
|
|
width: 4
|
|
height: Math.max(16, toastCard.height - 24)
|
|
radius: 2
|
|
anchors {
|
|
left: parent.left
|
|
leftMargin: 10
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
color: toastRoot.urgencyColor(toastRoot.urgency)
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: toastContent
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
top: parent.top
|
|
leftMargin: 24
|
|
rightMargin: 12
|
|
topMargin: 12
|
|
}
|
|
spacing: 4
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
|
|
Text {
|
|
text: toastRoot.urgencyIcon(toastRoot.urgency)
|
|
color: toastRoot.urgencyColor(toastRoot.urgency)
|
|
font.pixelSize: 12
|
|
font.bold: true
|
|
Layout.alignment: Qt.AlignVCenter
|
|
renderType: Text.NativeRendering
|
|
}
|
|
Text {
|
|
text: toastRoot.appName
|
|
color: toastRoot.t.notifToastAppName
|
|
font.pixelSize: 12
|
|
font.bold: true
|
|
font.capitalization: Font.AllUppercase
|
|
font.letterSpacing: 0.4
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
verticalAlignment: Text.AlignVCenter
|
|
renderType: Text.NativeRendering
|
|
}
|
|
Text {
|
|
text: toastRoot.timeStr
|
|
color: toastRoot.t.notifToastDim
|
|
font.pixelSize: 11
|
|
font.bold: true
|
|
verticalAlignment: Text.AlignVCenter
|
|
renderType: Text.NativeRendering
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.alignment: Qt.AlignVCenter
|
|
width: 20; height: 20; radius: 10
|
|
color: closeHover.hovered ? toastRoot.t.notifHistoryHover : "transparent"
|
|
Behavior on color { ColorAnimation { duration: 100 } }
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "✕"
|
|
color: closeHover.hovered ? toastRoot.t.notifToastText : toastRoot.t.notifToastDim
|
|
font.pixelSize: 10
|
|
font.bold: true
|
|
renderType: Text.NativeRendering
|
|
}
|
|
HoverHandler { id: closeHover; cursorShape: Qt.PointingHandCursor }
|
|
TapHandler { onTapped: toastRoot.triggerDismiss() }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: toastRoot.summary
|
|
color: toastRoot.t.notifToastText
|
|
font.pixelSize: 14
|
|
font.bold: true
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
textFormat: Text.PlainText
|
|
visible: toastRoot.summary !== ""
|
|
renderType: Text.NativeRendering
|
|
}
|
|
|
|
Text {
|
|
text: toastRoot.body
|
|
color: toastRoot.t.notifToastDim
|
|
font.pixelSize: 12
|
|
font.bold: true
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 3
|
|
elide: Text.ElideRight
|
|
textFormat: Text.PlainText
|
|
visible: toastRoot.body !== ""
|
|
Layout.bottomMargin: toastRoot.urgency === NotificationUrgency.Low ? 6 : 0
|
|
lineHeight: 1.2
|
|
renderType: Text.NativeRendering
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: toastRoot.urgency === NotificationUrgency.Low && toastRoot.timeout > 0
|
|
anchors {
|
|
left: parent.left; right: parent.right; bottom: parent.bottom
|
|
margins: 10; bottomMargin: 10
|
|
}
|
|
height: 4
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: 2
|
|
color: toastRoot.t.notifProgressBg
|
|
}
|
|
Rectangle {
|
|
width: parent.width * toastRoot.progressVal
|
|
height: parent.height
|
|
radius: 2
|
|
color: toastRoot.t.notifProgressFill
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component HistoryCard: Rectangle {
|
|
id: histRoot
|
|
|
|
property int itemIndex: 0
|
|
property string appName: ""
|
|
property string summary: ""
|
|
property string body: ""
|
|
property int urgency: 0
|
|
property string timeStr: ""
|
|
|
|
property var t
|
|
property var urgencyColor
|
|
property var urgencyIcon
|
|
|
|
signal dismissed(int itemIndex)
|
|
|
|
height: histItemContent.implicitHeight + 16
|
|
radius: 10
|
|
clip: true
|
|
color: histItemHover.hovered ? histRoot.t.notifHistoryHover : "transparent"
|
|
Behavior on color { ColorAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
|
|
|
HoverHandler { id: histItemHover }
|
|
|
|
SequentialAnimation {
|
|
id: dismissAnim
|
|
ParallelAnimation {
|
|
NumberAnimation { target: histRoot; property: "opacity"; to: 0; duration: 150; easing.type: Easing.OutCubic }
|
|
NumberAnimation { target: histRoot; property: "scale"; to: 0.95; duration: 150; easing.type: Easing.OutCubic }
|
|
}
|
|
NumberAnimation { target: histRoot; property: "height"; to: 0; duration: 150; easing.type: Easing.InOutQuad }
|
|
ScriptAction { script: histRoot.dismissed(histRoot.itemIndex) }
|
|
}
|
|
|
|
Rectangle {
|
|
width: 3
|
|
height: Math.max(10, histRoot.height - 16)
|
|
radius: 1.5
|
|
anchors { left: parent.left; leftMargin: 8; verticalCenter: parent.verticalCenter }
|
|
color: histRoot.urgencyColor(histRoot.urgency)
|
|
}
|
|
|
|
Rectangle {
|
|
anchors { right: parent.right; top: parent.top; margins: 6 }
|
|
width: 18; height: 18; radius: 9
|
|
color: histCloseHover.hovered ? histRoot.t.notifHistoryHover : "transparent"
|
|
opacity: histItemHover.hovered ? 1 : 0
|
|
Behavior on opacity { NumberAnimation { duration: 150 } }
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
|
|
Text { anchors.centerIn: parent; text: "✕"; color: histRoot.t.notifToastText; font.pixelSize: 10; font.bold: true; renderType: Text.NativeRendering }
|
|
HoverHandler { id: histCloseHover; cursorShape: Qt.PointingHandCursor }
|
|
TapHandler { onTapped: dismissAnim.start() }
|
|
}
|
|
|
|
Text {
|
|
anchors { right: parent.right; rightMargin: 6; top: parent.top; topMargin: 8 }
|
|
text: histRoot.timeStr
|
|
color: histRoot.t.notifToastDim
|
|
font.pixelSize: 10
|
|
font.bold: true
|
|
verticalAlignment: Text.AlignVCenter
|
|
renderType: Text.NativeRendering
|
|
opacity: histItemHover.hovered ? 0 : 1
|
|
Behavior on opacity { NumberAnimation { duration: 150 } }
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: histItemContent
|
|
anchors { left: parent.left; right: parent.right; top: parent.top; leftMargin: 20; rightMargin: 40; topMargin: 8 }
|
|
spacing: 2
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true; spacing: 5
|
|
Text { text: histRoot.urgencyIcon(histRoot.urgency); color: histRoot.urgencyColor(histRoot.urgency); font.pixelSize: 10; font.bold: true; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering }
|
|
Text {
|
|
text: histRoot.appName; color: histRoot.t.notifToastAppName; font.pixelSize: 11; font.bold: true
|
|
font.capitalization: Font.AllUppercase; font.letterSpacing: 0.4; Layout.fillWidth: true; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
|
|
renderType: Text.NativeRendering
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: histRoot.summary; color: histRoot.t.notifToastText; font.pixelSize: 12; font.bold: true
|
|
Layout.fillWidth: true; elide: Text.ElideRight; textFormat: Text.PlainText; visible: histRoot.summary !== ""
|
|
renderType: Text.NativeRendering
|
|
}
|
|
Text {
|
|
text: histRoot.body; color: histRoot.t.notifToastDim; font.pixelSize: 11; font.bold: true; Layout.fillWidth: true
|
|
wrapMode: Text.WordWrap; maximumLineCount: 2; elide: Text.ElideRight; textFormat: Text.PlainText; visible: histRoot.body !== ""; lineHeight: 1.2
|
|
renderType: Text.NativeRendering
|
|
}
|
|
}
|
|
}
|
|
|
|
HoverHandler { cursorShape: Qt.PointingHandCursor; onHoveredChanged: if (hovered && Cfg.Config.notificationShakeEnabled) bellRingAnim.start() }
|
|
TapHandler {
|
|
onTapped: {
|
|
if (historyPopup.popupOpen) {
|
|
historyPopup.close()
|
|
} else {
|
|
root.unreadCount = 0
|
|
historyPopup.open()
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
anchors.centerIn: parent
|
|
width: Cfg.Config.notifIconSize + 10
|
|
height: Cfg.Config.notifIconSize + 10
|
|
|
|
Text {
|
|
id: bellIcon
|
|
anchors.centerIn: parent
|
|
text: root.doNotDisturb ? "" : ""
|
|
color: root.doNotDisturb
|
|
? t.notifDnd
|
|
: (root.unreadCount > 0 ? t.notifIconActive : t.notifIcon)
|
|
font.pixelSize: Cfg.Config.notifIconSize
|
|
font.bold: true
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
|
|
rotation: 0
|
|
transformOrigin: Item.Top
|
|
|
|
SequentialAnimation {
|
|
id: bellRingAnim
|
|
NumberAnimation { target: bellIcon; property: "rotation"; to: -18; duration: 80; easing.type: Easing.OutQuad }
|
|
NumberAnimation { target: bellIcon; property: "rotation"; to: 20; duration: 100; easing.type: Easing.OutQuad }
|
|
NumberAnimation { target: bellIcon; property: "rotation"; to: -10; duration: 80; easing.type: Easing.OutQuad }
|
|
NumberAnimation { target: bellIcon; property: "rotation"; to: 10; duration: 70; easing.type: Easing.OutQuad }
|
|
NumberAnimation { target: bellIcon; property: "rotation"; to: 0; duration: 80; easing.type: Easing.OutCubic }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.unreadCount > 0
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
width: Math.max(14, badgeLabel.implicitWidth + 6)
|
|
height: 14
|
|
radius: 7
|
|
color: t.notifBadgeBg
|
|
|
|
Behavior on width { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
|
|
|
|
Text {
|
|
id: badgeLabel
|
|
anchors.centerIn: parent
|
|
text: root.unreadCount > 99 ? "99+" : root.unreadCount.toString()
|
|
color: t.notifBadgeText
|
|
font.pixelSize: 8
|
|
font.bold: true
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
renderType: Text.QtRendering
|
|
}
|
|
}
|
|
}
|
|
|
|
ListModel { id: notifHistory }
|
|
ListModel { id: activeToasts }
|
|
|
|
function removeToast(toastId) {
|
|
for (var i = 0; i < activeToasts.count; i++) {
|
|
if (activeToasts.get(i).toastId === toastId) {
|
|
activeToasts.remove(i)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
function urgencyColor(urgency) {
|
|
if (urgency === NotificationUrgency.Critical) return t.notifUrgencyCritical
|
|
if (urgency === NotificationUrgency.Normal) return t.notifUrgencyNormal
|
|
return t.notifUrgencyLow
|
|
}
|
|
|
|
function urgencyIcon(urgency) {
|
|
if (urgency === NotificationUrgency.Critical) return ""
|
|
if (urgency === NotificationUrgency.Normal) return ""
|
|
return ""
|
|
}
|
|
|
|
NotificationServer {
|
|
id: notifServer
|
|
keepOnReload: false
|
|
actionIconsSupported: true
|
|
bodyMarkupSupported: true
|
|
bodySupported: true
|
|
persistenceSupported: true
|
|
imageSupported: true
|
|
|
|
onNotification: function(notif) {
|
|
notif.tracked = true
|
|
if (Cfg.Config.notificationShakeEnabled) bellRingAnim.start()
|
|
|
|
const timeStr = new Date().toLocaleTimeString(Qt.locale(), "HH:mm")
|
|
const toastId = notif.id + "_" + Date.now()
|
|
const appName = notif.appName || "Unknown"
|
|
const summary = notif.summary || ""
|
|
const body = notif.body || ""
|
|
const urgency = notif.urgency
|
|
|
|
notifHistory.insert(0, {
|
|
nid: notif.id,
|
|
appName, summary, body, urgency, timeStr
|
|
})
|
|
while (notifHistory.count > Cfg.Config.notificationHistorySize)
|
|
notifHistory.remove(notifHistory.count - 1)
|
|
|
|
var cfgTimeout = urgency === NotificationUrgency.Critical
|
|
? Cfg.Config.notifTimeoutCritical
|
|
: (urgency === NotificationUrgency.Low
|
|
? Cfg.Config.notifTimeoutLow
|
|
: Cfg.Config.notifTimeoutNormal)
|
|
|
|
var finalTimeout = (notif.expireTimeout > 0) ? notif.expireTimeout : cfgTimeout
|
|
|
|
if (!root.doNotDisturb) {
|
|
var toastEntry = { toastId, appName, summary, body, urgency, timeStr, timeout: finalTimeout }
|
|
if (toastLayer.atBottom) activeToasts.append(toastEntry)
|
|
else activeToasts.insert(0, toastEntry)
|
|
if (!root.historyOpen) root.unreadCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
PanelWindow {
|
|
id: toastLayer
|
|
screen: root.targetScreen
|
|
visible: activeToasts.count > 0
|
|
color: "transparent"
|
|
|
|
readonly property string pos: Cfg.Config.notificationPosition
|
|
readonly property bool atRight: pos === "topright" || pos === "bottomright"
|
|
readonly property bool atBottom: pos === "bottomleft" || pos === "bottomright"
|
|
|
|
anchors { top: true; bottom: true; right: atRight; left: !atRight }
|
|
implicitWidth: 350 + root.barMargin + toastPad
|
|
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
WlrLayershell.namespace: "main-shell-notifications-toasts"
|
|
WlrLayershell.exclusiveZone: -1
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
|
|
mask: Region { item: toastCol }
|
|
|
|
readonly property bool shareEdge: (root.isTop && !atBottom) || (!root.isTop && atBottom)
|
|
readonly property int edgeGap: shareEdge ? root.barMargin + root.barHeight + 10 : root.barMargin + 10
|
|
readonly property int toastPad: root.barMargin + Cfg.Config.frameBorderWidth + Cfg.Config.notifToastEdgePad
|
|
|
|
Column {
|
|
id: toastCol
|
|
x: toastLayer.atRight ? root.barMargin : toastLayer.toastPad
|
|
y: !toastLayer.atBottom ? toastLayer.edgeGap : parent.height - toastLayer.edgeGap - toastCol.height
|
|
spacing: 8
|
|
width: 350
|
|
|
|
Repeater {
|
|
model: activeToasts
|
|
NotificationCard {
|
|
width: toastCol.width
|
|
toastId: model.toastId
|
|
appName: model.appName
|
|
summary: model.summary
|
|
body: model.body
|
|
urgency: model.urgency
|
|
timeStr: model.timeStr
|
|
timeout: model.timeout
|
|
t: root.t
|
|
urgencyColor: root.urgencyColor
|
|
urgencyIcon: root.urgencyIcon
|
|
slideFromRight: toastLayer.atRight
|
|
onDismissed: (id) => root.removeToast(id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PanelWindow {
|
|
id: historyPopup
|
|
screen: root.targetScreen
|
|
visible: false
|
|
color: "transparent"
|
|
|
|
property bool popupOpen: false
|
|
|
|
function open() { hideTimer.stop(); visible = true; popupOpen = true }
|
|
function close() { popupOpen = false; hideTimer.restart() }
|
|
|
|
PopupHideTimer {
|
|
id: hideTimer
|
|
onTriggered: historyPopup.visible = false
|
|
}
|
|
|
|
anchors { top: true; bottom: true; left: true; right: true }
|
|
WlrLayershell.layer: WlrLayer.Top
|
|
WlrLayershell.namespace: "main-shell-notifications-history"
|
|
WlrLayershell.exclusiveZone: -1
|
|
mask: popupOpen ? null : _noInput
|
|
Region { id: _noInput }
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: historyPopup.close()
|
|
}
|
|
|
|
Item {
|
|
id: histClipContainer
|
|
readonly property int cardW: 340
|
|
readonly property int screenPad: root.barMargin + 10
|
|
|
|
x: {
|
|
var cx = root.mapToItem(null, 0, 0).x + root.width / 2
|
|
return Math.max(screenPad, Math.min(cx - cardW / 2, parent.width - cardW - screenPad))
|
|
}
|
|
width: cardW
|
|
|
|
anchors.top: root.isTop ? parent.top : undefined
|
|
anchors.bottom: root.isTop ? undefined : parent.bottom
|
|
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
|
|
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
|
|
height: parent.height - root.barHeight - 10
|
|
|
|
clip: true
|
|
|
|
PopupCard {
|
|
id: historyCard
|
|
popupOpen: historyPopup.popupOpen
|
|
isTop: root.isTop
|
|
animEnabled: historyPopup.visible
|
|
height: Math.min(histHeader.implicitHeight + 56 + (notifHistory.count > 0 ? histCol.implicitHeight : 90), 520)
|
|
|
|
color: t.notifHistoryBg
|
|
radius: Cfg.Config.popupRadius
|
|
border.color: t.notifHistoryBorder
|
|
border.width: Cfg.Config.popupBorderWidth
|
|
|
|
RowLayout {
|
|
id: histHeader
|
|
anchors { left: parent.left; right: parent.right; top: parent.top }
|
|
anchors.topMargin: 20; anchors.leftMargin: 20; anchors.rightMargin: 20
|
|
spacing: 10
|
|
|
|
Text { text: ""; color: t.notifToastAppName; font.pixelSize: 18; font.bold: true; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering }
|
|
Text {
|
|
text: "Notifications"; color: t.notifToastAppName; font.pixelSize: 15; font.bold: true
|
|
Layout.fillWidth: true; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.alignment: Qt.AlignVCenter
|
|
width: dndRow.implicitWidth + 16
|
|
height: 24
|
|
radius: 12
|
|
color: root.doNotDisturb
|
|
? Qt.rgba(t.notifDnd.r, t.notifDnd.g, t.notifDnd.b, 0.18)
|
|
: (dndHover.hovered ? t.notifHistoryHover : "transparent")
|
|
border.color: root.doNotDisturb ? t.notifDnd : "transparent"
|
|
border.width: Cfg.Config.popupBorderWidth
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
Behavior on border.color { ColorAnimation { duration: 150 } }
|
|
|
|
Row {
|
|
id: dndRow
|
|
anchors.centerIn: parent
|
|
spacing: 4
|
|
Text {
|
|
text: ""
|
|
color: root.doNotDisturb ? t.notifDnd : t.notifToastDim
|
|
font.pixelSize: 12
|
|
font.bold: true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
renderType: Text.NativeRendering
|
|
}
|
|
Text {
|
|
text: root.doNotDisturb ? "On" : "Off"
|
|
color: root.doNotDisturb ? t.notifDnd : t.notifToastDim
|
|
font.pixelSize: 11
|
|
font.bold: true
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
renderType: Text.NativeRendering
|
|
}
|
|
}
|
|
|
|
HoverHandler { id: dndHover; cursorShape: Qt.PointingHandCursor }
|
|
TapHandler { onTapped: root.doNotDisturb = !root.doNotDisturb }
|
|
}
|
|
|
|
Rectangle {
|
|
visible: notifHistory.count > 0
|
|
Layout.alignment: Qt.AlignVCenter
|
|
width: clearText.implicitWidth + 16
|
|
height: 24
|
|
radius: 12
|
|
color: clearHover.hovered ? t.notifHistoryHover : "transparent"
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
|
|
Text { id: clearText; anchors.centerIn: parent; text: "Clear all"; color: t.notifToastDim; font.pixelSize: 11; font.bold: true; renderType: Text.NativeRendering }
|
|
HoverHandler { id: clearHover; cursorShape: Qt.PointingHandCursor }
|
|
TapHandler { onTapped: clearAllAnim.start() }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: histDivider
|
|
anchors { left: parent.left; right: parent.right; top: histHeader.bottom }
|
|
anchors.topMargin: 12; height: 1; color: t.notifSeparator
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: clearAllAnim
|
|
ParallelAnimation {
|
|
NumberAnimation {
|
|
target: histCol; property: "opacity"
|
|
to: 0; duration: 220; easing.type: Easing.OutCubic
|
|
}
|
|
NumberAnimation {
|
|
target: clearShift; property: "y"
|
|
to: -16; duration: 220; easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
ScriptAction {
|
|
script: { notifHistory.clear(); root.unreadCount = 0 }
|
|
}
|
|
ParallelAnimation {
|
|
NumberAnimation { target: histCol; property: "opacity"; to: 1; duration: 0 }
|
|
NumberAnimation { target: clearShift; property: "y"; to: 0; duration: 0 }
|
|
}
|
|
}
|
|
|
|
Flickable {
|
|
id: histFlick
|
|
anchors { left: parent.left; right: parent.right; top: histDivider.bottom; bottom: parent.bottom }
|
|
anchors.leftMargin: 2; anchors.rightMargin: 8
|
|
anchors.topMargin: 6; anchors.bottomMargin: 8; clip: true
|
|
contentHeight: histCol.implicitHeight
|
|
flickableDirection: Flickable.VerticalFlick
|
|
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
|
|
|
Column {
|
|
id: histCol
|
|
opacity: 1
|
|
transform: Translate { id: clearShift; y: 0 }
|
|
width: histFlick.width; spacing: 2; topPadding: 4; bottomPadding: 4; leftPadding: 2; rightPadding: 8
|
|
|
|
Item {
|
|
width: histCol.width - 16; height: 90; visible: notifHistory.count === 0
|
|
ColumnLayout {
|
|
anchors.centerIn: parent; spacing: 8
|
|
Text { text: ""; color: t.notifHistoryEmpty; font.pixelSize: 30; font.bold: true; Layout.alignment: Qt.AlignHCenter; renderType: Text.NativeRendering }
|
|
Text { text: "No notifications"; color: t.notifHistoryEmpty; font.pixelSize: 12; font.bold: true; Layout.alignment: Qt.AlignHCenter; renderType: Text.NativeRendering }
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: notifHistory
|
|
HistoryCard {
|
|
width: histCol.width - 10; x: 2; itemIndex: index
|
|
appName: model.appName; summary: model.summary; body: model.body; urgency: model.urgency; timeStr: model.timeStr
|
|
t: root.t; urgencyColor: root.urgencyColor; urgencyIcon: root.urgencyIcon
|
|
onDismissed: (idx) => notifHistory.remove(idx)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|