Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/NotificationGroup.qml

Raw
pragma ComponentBehavior: Bound
import QtQuick
import "Util.js" as Util

Column {
    id: root

    required property Theme theme
    readonly property Flickable scrollView: scroll
    required property Notifications notificationService
    property var panelState: notificationService ? Util.parseJson(notificationService.stateJson(), {
        "groups": [],
        "dnd": false,
        "activeCount": 0
    }) : ({
            "groups": [],
            "dnd": false,
            "activeCount": 0
        })
    readonly property var groups: panelState.groups || []

    // mutators trigger side-effects only; never assign to `state` — that
    // would destroy the binding above and freeze the open popup, so
    // externally-arriving notifications would stop appearing until reopen.
    // stateJson() reads reactive props (notifications/dnd/expandedGroups/…)
    // so the binding re-evaluates on its own after every mutation.
    function runAction(id, actionId) {
        if (notificationService)
            notificationService.runAction(id, actionId);
    }
    function editReply(id, text) {
        if (notificationService)
            notificationService.setReplyDraft(id, text);
    }
    function sendReply(id, text) {
        if (notificationService)
            notificationService.sendReply(id, text);
    }
    function toggleGroup(appName) {
        if (notificationService)
            notificationService.toggleGroup(appName);
    }
    function toggleDnd() {
        if (notificationService)
            notificationService.toggleDnd();
    }
    function clearAll() {
        if (notificationService)
            notificationService.discardAllNotifications();
    }

    spacing: 8
    width: parent ? parent.width : 0

    StableListModel {
        id: groupRows
        rows: root.groups
    }

    Item {
        width: parent.width
        height: 28
        NuguText {
            anchors.left: parent.left
            anchors.verticalCenter: parent.verticalCenter
            theme: root.theme
            variant: "title"
            text: `notifications${root.panelState.activeCount ? " · " + root.panelState.activeCount : ""}`
        }
        Row {
            anchors.right: parent.right
            anchors.verticalCenter: parent.verticalCenter
            spacing: 8
            PopupButton {
                theme: root.theme
                height: 24
                text: root.panelState.dnd ? "dnd on" : "dnd"
                checked: root.panelState.dnd
                onClicked: root.toggleDnd()
            }
            PopupButton {
                visible: (root.panelState.activeCount || 0) > 0
                theme: root.theme
                height: 24
                text: "clear all"
                role: "destructive"
                onClicked: root.clearAll()
            }
        }
    }

    // History can outgrow the popup; the header stays fixed and everything
    // below scrolls. The Loader hosting this Column forces a concrete
    // root.height, so the viewport can bound on it.
    Item {
        id: scrollArea
        width: parent.width
        height: Math.max(0, root.height - 28 - root.spacing)

        Flickable {
            id: scroll
            anchors.fill: parent
            contentHeight: scrollColumn.height
            clip: true
            interactive: contentHeight > height
            boundsBehavior: Flickable.StopAtBounds
            flickableDirection: Flickable.VerticalFlick

            Column {
                id: scrollColumn
                width: scroll.width
                spacing: 8

                Item {
                    width: parent.width
                    height: 220
                    visible: root.groups.length === 0
                    Column {
                        anchors.centerIn: parent
                        spacing: 8
                        NuguText {
                            anchors.horizontalCenter: parent.horizontalCenter
                            theme: root.theme
                            variant: "title"
                            text: "no notifications"
                        }
                        NuguText {
                            anchors.horizontalCenter: parent.horizontalCenter
                            theme: root.theme
                            variant: "meta"
                            text: "you're all caught up"
                        }
                    }
                }

                Repeater {
                    model: groupRows
                    delegate: Column {
                        id: groupDelegate
                        required property var rowData
                        width: root.width
                        spacing: 6

                        PopupButton {
                            id: groupHeader
                            readonly property int hiddenCount: Math.max(1, (groupDelegate.rowData.count || 1) - (groupDelegate.rowData.previewCount || 1))

                            theme: root.theme
                            width: parent.width
                            height: (groupDelegate.rowData.count || 0) > 1 ? 30 : 0
                            visible: (groupDelegate.rowData.count || 0) > 1
                            text: groupDelegate.rowData.expanded ? `${groupDelegate.rowData.appName || "app"} · ${groupDelegate.rowData.count} — show less` : `${groupDelegate.rowData.appName || "app"} · ${groupDelegate.rowData.count} — show ${hiddenCount} more`
                            checked: groupDelegate.rowData.expanded
                            onClicked: root.toggleGroup(groupDelegate.rowData.appName || "")
                        }

                        StableListModel {
                            id: notificationRows
                            rows: groupDelegate.rowData.visibleNotifications || []
                        }

                        Repeater {
                            model: notificationRows
                            delegate: Item {
                                id: cardWrap
                                required property var rowData
                                width: root.width
                                implicitHeight: cardInner.implicitHeight
                                height: implicitHeight
                                NotificationCard {
                                    id: cardInner
                                    width: parent.width
                                    theme: root.theme
                                    modelData: cardWrap.rowData
                                    replyDraft: cardWrap.rowData.replyDraft || ""
                                    replyError: cardWrap.rowData.replyError || ""
                                    onDismiss: id => root.runAction(id, "close")
                                    onInvokeAction: (id, actionId) => root.runAction(id, actionId)
                                    onReplyEdited: (id, text) => root.editReply(id, text)
                                    onSendReply: (id, text) => root.sendReply(id, text)
                                }
                            }
                        }
                    }
                }
            }
        }

        // slim scroll indicator, overlaid (outside the Flickable so it
        // doesn't scroll with content); shown only when content overflows.
        Rectangle {
            anchors.right: parent.right
            width: 3
            radius: 2
            visible: scroll.contentHeight > scroll.height + 1
            color: root.theme ? root.theme.withAlpha(root.theme.ui.minor, 0.5) : "transparent"
            height: Math.max(24, scrollArea.height * scrollArea.height / Math.max(1, scroll.contentHeight))
            y: scroll.contentY * scrollArea.height / Math.max(1, scroll.contentHeight)
        }
    }
}