/* * SPDX-FileCopyrightText: 2024 Micah Stanley * * SPDX-License-Identifier: LGPL-2.0-or-later */ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.1 import org.kde.plasma.clock import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings import org.kde.kirigami as Kirigami Item { id: root required property var actionDrawer required property var contentContainer required property var swipeArea property alias mediaControlsWidget: notificationWidget.header property alias notificationWidget: notificationWidget property real contentY: notificationWidget.listView.contentY // The sibling toolbar whose height must be subtracted from the available space. property Item toolButtonsItem: null property real topPadding: { if (actionDrawer.mode == MobileShell.ActionDrawer.Portrait) return Kirigami.Units.largeSpacing; if (ShellSettings.Settings.convergenceModeEnabled) return Kirigami.Units.largeSpacing; return date.y + date.height + Kirigami.Units.smallSpacing * 6; } property real topMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? actionDrawer.offsetResistance + 1 : 0 readonly property real minWidthHeight: Math.min(actionDrawer.width, actionDrawer.height) readonly property bool hasNotifications: notificationWidget.hasNotifications readonly property bool listOverflowing: notificationWidget.listView.listOverflowing // External cap for convergence mode; -1 means uncapped. property real maximumHeight: -1 height: { let toolH = toolButtonsItem ? toolButtonsItem.height : 0; let avail = actionDrawer.height - toolH; let content = notificationWidget.listView.contentHeight + Kirigami.Units.largeSpacing + topMargin; // When empty, reserve the external cap so the placeholder has room; // when populated, hug content so the clear-all toolbar sits below the list. if (maximumHeight > 0 && !hasNotifications) { return Math.min(avail, maximumHeight); } let cap = maximumHeight > 0 ? Math.min(avail, maximumHeight) : avail; return Math.min(cap, content); } // time source for the time and date whenin landscape mode Clock { id: clockSource } MobileShell.VelocityCalculator { id: velocityCalculator } // notification list widget // margin adjusted to fit and position into the action drawer MobileShell.NotificationsWidget { id: notificationWidget readonly property bool isConvergence: ShellSettings.Settings.convergenceModeEnabled anchors.fill: parent anchors.topMargin: root.topMargin + (isConvergence ? Kirigami.Units.gridUnit : 0) anchors.rightMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : (isConvergence ? Kirigami.Units.gridUnit : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0)) anchors.leftMargin: actionDrawer.mode == MobileShell.ActionDrawer.Portrait ? 0 : (isConvergence ? Kirigami.Units.gridUnit : -Kirigami.Units.gridUnit) historyModel: actionDrawer.notificationModel historyModelType: actionDrawer.notificationModelType notificationSettings: actionDrawer.notificationSettings actionsRequireUnlock: actionDrawer.restrictedPermissions onUnlockRequested: actionDrawer.permissionsRequested() topPadding: root.topPadding showHeader: actionDrawer.mode != MobileShell.ActionDrawer.Portrait && !ShellSettings.Settings.convergenceModeEnabled emptyText: ShellSettings.Settings.convergenceModeEnabled ? i18n("No notifications") : "" listView.interactive: !actionDrawer.dragging && root.listOverflowing cardColorScheme: Kirigami.Theme.View Connections { target: actionDrawer function onRunPendingNotificationAction() { notificationWidget.runPendingAction(); } } // the first swipe when at the top of the notification list is handled using a MouseArea, not the flickable // this is so one can swipe down from the top of the notification drawer to expand the action drawer DragHandler { id: dragHandler // disable the draghandler when we are not at the top of the notification list as it can interfere with the notification scrolling yAxis.enabled: notificationWidget.listView.atYBeginning || active xAxis.enabled: false property bool startActive: false property real startOffset: 0 property real startMouseY: 0 property real lastMouseY: 0 property bool startedAtYBeginning: false property bool startedAtYEnd: false property bool drawerDrag: true property string currentState onTranslationChanged: { if (startActive) { dragHandler.startedAtYBeginning = notificationWidget.listView.atYBeginning; dragHandler.startedAtYEnd = notificationWidget.listView.atYEnd; startActive = false; if (notificationWidget.listView.atYBeginning) { currentState = actionDrawer.state; actionDrawer.cancelAnimations(); actionDrawer.dragging = true; actionDrawer.opened = true; dragHandler.startOffset = actionDrawer.offset; dragHandler.startMouseY = translation.y; dragHandler.lastMouseY = dragHandler.startMouseY; dragHandler.drawerDrag = true; velocityCalculator.startMeasure(); velocityCalculator.changePosition(notificationWidget.listView.contentY); } } if (!actionDrawer.dragging) { return; } if (!(dragHandler.startedAtYBeginning && dragHandler.startedAtYEnd) && ((dragHandler.startedAtYBeginning && (dragHandler.startMouseY - translation.y) > 0) || (dragHandler.startedAtYEnd && (translation.y - dragHandler.startMouseY) > 0))) { actionDrawer.state = currentState; dragHandler.drawerDrag = false; } if (dragHandler.drawerDrag) { actionDrawer.offset = dragHandler.startOffset - (dragHandler.startMouseY - translation.y); } else { let contentY = notificationWidget.listView.contentY - (translation.y - dragHandler.lastMouseY); notificationWidget.listView.contentY = contentY; velocityCalculator.changePosition(notificationWidget.listView.contentY); dragHandler.lastMouseY = translation.y; } } onActiveChanged: { startActive = active; if (!active) { // release event if (actionDrawer.dragging) { if (dragHandler.drawerDrag) { actionDrawer.updateState(); } else { notificationWidget.listView.flick(0, -velocityCalculator.velocity); } } actionDrawer.dragging = false; dragHandler.drawerDrag = true; } } } } // time and date displayed in landscape mode Item { id: landscapeModeHeader anchors.fill: parent visible: actionDrawer.mode != MobileShell.ActionDrawer.Portrait && !ShellSettings.Settings.convergenceModeEnabled transform: [ Translate { y: -notificationWidget.listView.contentY + notificationWidget.listView.originY } ] PlasmaComponents.Label { id: clock text: Qt.formatTime(clockSource.dateTime, MobileShell.ShellUtil.isSystem24HourFormat ? "h:mm" : "h:mm ap") verticalAlignment: Qt.AlignVCenter anchors { left: parent.left top: parent.top topMargin: minWidthHeight * 0.03 } font.pixelSize: Math.min(40, minWidthHeight * 0.1) font.weight: Font.ExtraLight elide: Text.ElideRight } PlasmaComponents.Label { id: date text: Qt.formatDate(clockSource.dateTime, "ddd MMMM d") verticalAlignment: Qt.AlignTop color: Kirigami.Theme.disabledTextColor anchors { left: parent.left top: clock.bottom topMargin: Kirigami.Units.smallSpacing } font.pixelSize: Math.min(20, minWidthHeight * 0.05) font.weight: Font.Light } } }