/* * SPDX-FileCopyrightText: 2015 Marco Martin * * SPDX-License-Identifier: LGPL-2.0-or-later */ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Templates as T import QtQuick.Layouts import org.kde.kirigami.platform as Platform import org.kde.kirigami.primitives as Primitives import org.kde.kirigami.private.polyfill /*! \brief PassiveNotificatiomodelnManager is meant to display small, passive and inline notifications in the app. It is used to show messages of limited importance that make sense only when the user is using the application and wouldn't be suited as a global system-wide notification. \internal */ Item { id: root readonly property int maximumNotificationWidth: { if (Platform.Settings.isMobile) { return applicationWindow().width - Platform.Units.largeSpacing * 4 } else { return Math.min(Platform.Units.gridUnit * 25, applicationWindow().width / 1.5) } } readonly property int maximumNotificationCount: 4 function showNotification(message, timeout, actionText, callBack) { if (!message) { return; } let interval = 7000; if (timeout === "short") { interval = 4000; } else if (timeout === "long") { interval = 12000; } else if (timeout > 0) { interval = timeout; } // this wrapper is necessary because of Qt casting a function into an object const callBackWrapperObj = callBackWrapper.createObject(listView, { callBack }) // set empty string & function for qml not to complain notificationsModel.append({ text: message, actionButtonText: actionText || "", closeInterval: interval, callBackWrapper: callBackWrapperObj }) // remove the oldest notification if new notification count would exceed 3 if (notificationsModel.count === maximumNotificationCount) { if ((listView.itemAtIndex(0) as T.Control).hovered === true) { hideNotification(1) } else { hideNotification() } } } /*! * \brief Remove a notification at specific index. By default, index is set to 0. */ function hideNotification(index = 0) { if (index >= 0 && notificationsModel.count > index) { const callBackWrapperObj = notificationsModel.get(index).callBackWrapper if (callBackWrapperObj) { callBackWrapperObj.destroy() } notificationsModel.remove(index) } } // we have to set height to show more than one notification height: Math.min(applicationWindow().height, Platform.Units.gridUnit * 10) implicitHeight: listView.implicitHeight implicitWidth: listView.implicitWidth Platform.Theme.inherit: false Platform.Theme.colorSet: Platform.Theme.Complementary ListModel { id: notificationsModel } ListView { id: listView anchors.fill: parent anchors.bottomMargin: Platform.Units.largeSpacing leftMargin: SafeArea.margins.left rightMargin: SafeArea.margins.right topMargin: SafeArea.margins.top bottomMargin: SafeArea.margins.bottom implicitWidth: root.maximumNotificationWidth spacing: Platform.Units.smallSpacing model: notificationsModel verticalLayoutDirection: ListView.BottomToTop keyNavigationEnabled: false reuseItems: false // do not reuse items, otherwise delegates do not hide themselves properly focus: false interactive: false add: Transition { id: addAnimation ParallelAnimation { alwaysRunToEnd: true NumberAnimation { property: "opacity" from: 0 to: 1 duration: Platform.Units.longDuration easing.type: Easing.OutCubic } NumberAnimation { property: "y" from: addAnimation.ViewTransition.destination.y - Platform.Units.gridUnit * 3 duration: Platform.Units.longDuration easing.type: Easing.OutCubic } } } displaced: Transition { ParallelAnimation { alwaysRunToEnd: true NumberAnimation { property: "y" duration: Platform.Units.longDuration easing.type: Easing.InOutCubic } NumberAnimation { property: "opacity" duration: Platform.Units.longDuration to: 1 } } } remove: Transition { ParallelAnimation { alwaysRunToEnd: true NumberAnimation { property: "opacity" from: 1 to: 0 duration: Platform.Units.longDuration easing.type: Easing.InCubic } NumberAnimation { property: "y" to: Platform.Units.gridUnit * 3 duration: Platform.Units.longDuration easing.type: Easing.InCubic } PropertyAction { property: "transformOrigin" value: Item.Bottom } PropertyAnimation { property: "scale" from: 1 to: 0 duration: Platform.Units.longDuration easing.type: Easing.InCubic } } } delegate: QQC2.Control { id: delegate required property int index required property int closeInterval required property string text required property string actionButtonText required property QtObject callBackWrapper hoverEnabled: true // We force the delegate to be not visible when // created, as this could cause some flickering // otherwise; instead, we rely on `add` and // `displaced` animations to change the opacity. opacity: Platform.Units.longDuration > 0 ? 0 : 1 anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined width: Math.min(implicitWidth, root.maximumNotificationWidth) implicitHeight: { // HACK: contentItem.implicitHeight needs to be updated manually for some reason void contentItem.implicitHeight; return Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding); } z: { if (delegate.hovered) { return 2; } else if (delegate.index === 0) { return 1; } else { return 0; } } leftPadding: Platform.Units.largeSpacing rightPadding: Platform.Units.largeSpacing topPadding: Platform.Units.largeSpacing bottomPadding: Platform.Units.largeSpacing contentItem: RowLayout { id: mainLayout Platform.Theme.inherit: false Platform.Theme.colorSet: root.Platform.Theme.colorSet spacing: Platform.Units.mediumSpacing TapHandler { acceptedButtons: Qt.LeftButton onTapped: eventPoint => root.hideNotification(delegate.index) } Timer { id: timer interval: delegate.closeInterval running: !delegate.hovered onTriggered: root.hideNotification(delegate.index) } QQC2.Label { id: label text: delegate.text elide: Text.ElideRight wrapMode: Text.Wrap Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } QQC2.Button { id: actionButton text: delegate.actionButtonText visible: text.length > 0 Layout.alignment: Qt.AlignVCenter onClicked: { const callBack = delegate.callBackWrapper.callBack root.hideNotification(delegate.index) if (callBack && (typeof callBack === "function")) { callBack(); } } } } background: Primitives.ShadowedRectangle { Platform.Theme.inherit: false Platform.Theme.colorSet: root.Platform.Theme.colorSet shadow { size: Platform.Units.gridUnit/2 color: Qt.rgba(0, 0, 0, 0.4) yOffset: 2 } radius: Platform.Units.cornerRadius color: Platform.Theme.backgroundColor opacity: 0.9 } } } Component { id: callBackWrapper QtObject { property var callBack } } }