diff --git a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml index 3427d25f..d099deeb 100644 --- a/components/mobileshell/qml/actiondrawer/ActionDrawer.qml +++ b/components/mobileshell/qml/actiondrawer/ActionDrawer.qml @@ -59,6 +59,19 @@ Item { */ property real offset: 0 + /** + * Same as offset value except this adds resistance when passing the open position of the current drawer state. + */ + readonly property real offsetResistance: { + if (!openToPinnedMode) { + return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset); + } else if (!opened) { + return root.calculateResistance(offset, contentContainer.minimizedQuickSettingsOffset); + } else { + return root.calculateResistance(offset, contentContainer.maximizedQuickSettingsOffset); + } + } + /** * Whether the panel is being dragged. */ @@ -80,11 +93,6 @@ Item { */ property int direction: MobileShell.Direction.None - /** - * The notifications widget being shown. May be null. - */ - property var notificationsWidget: contentContainer.notificationsWidget - /** * The mode of the action drawer (portrait or landscape). */ @@ -135,8 +143,8 @@ Item { } root.direction = (oldOffset === offset) - ? MobileShell.Direction.None - : (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up); + ? MobileShell.Direction.None + : (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up); oldOffset = offset; @@ -150,6 +158,15 @@ Item { } } + // calculates offset resistance for the action drawer overshoots it's open position + function calculateResistance(value : double, threshold : int) : double { + if (value > threshold) { + return threshold + Math.pow(value - threshold + 1, Math.max(0.8 - (value - threshold) / ((root.height - threshold) * 15), 0.35)); + } else { + return value; + } + } + function cancelAnimations() { root.state = ""; } @@ -267,45 +284,12 @@ Item { } } - MobileShell.SwipeArea { - id: swipeArea - mode: MobileShell.SwipeArea.VerticalOnly + // action drawer ui content + ContentContainer { + id: contentContainer anchors.fill: parent - function startSwipe() { - root.cancelAnimations(); - root.dragging = true; - - // Immediately open action drawer if we interact with it and it's already open - // This allows us to have 2 quick flicks from minimized -> expanded - if (root.visible && !root.opened) { - root.opened = true; - } - } - - function endSwipe() { - root.dragging = false; - root.updateState(); - } - - function moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) { - root.offset += deltaY; - } - - onSwipeStarted: startSwipe() - onSwipeEnded: endSwipe() - onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) - - onTouchpadScrollStarted: startSwipe() - onTouchpadScrollEnded: endSwipe() - onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) - - ContentContainer { - id: contentContainer - anchors.fill: parent - - actionDrawer: root - quickSettingsModel: root.quickSettingsModel - } + actionDrawer: root + quickSettingsModel: root.quickSettingsModel } } diff --git a/components/mobileshell/qml/actiondrawer/ContentContainer.qml b/components/mobileshell/qml/actiondrawer/ContentContainer.qml index 206bb608..fabb6d9e 100644 --- a/components/mobileshell/qml/actiondrawer/ContentContainer.qml +++ b/components/mobileshell/qml/actiondrawer/ContentContainer.qml @@ -3,9 +3,10 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 +import QtQuick.Layouts +import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.components 3.0 as PC3 import org.kde.kirigami as Kirigami @@ -23,30 +24,190 @@ Item { readonly property real minimizedQuickSettingsOffset: contentContainerLoader.minimizedQuickSettingsOffset readonly property real maximizedQuickSettingsOffset: contentContainerLoader.maximizedQuickSettingsOffset - function applyMinMax(val) { - return Math.max(0, Math.min(1, val)); - } - Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false readonly property alias brightnessPressedValue: quickSettings.brightnessPressedValue + function applyMinMax(val) { + return Math.max(0, Math.min(1, val)); + } + + function startSwipe() { + actionDrawer.cancelAnimations(); + actionDrawer.dragging = true; + // Immediately open action drawer if we interact with it and it's already open + // This allows us to have 2 quick flicks from minimized -> expanded + if (actionDrawer.visible && !actionDrawer.opened) { + actionDrawer.opened = true; + } + } + + function endSwipe() { + actionDrawer.dragging = false; + actionDrawer.updateState(); + } + + function moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) { + actionDrawer.offset += deltaY; + } + // Background color Rectangle { anchors.fill: parent color: Qt.rgba(Kirigami.Theme.backgroundColor.r, - Kirigami.Theme.backgroundColor.g, - Kirigami.Theme.backgroundColor.b, - (root.actionDrawer.mode == ActionDrawer.Portrait || notificationWidget.hasNotifications) ? 0.95 : 0.9) + Kirigami.Theme.backgroundColor.g, + Kirigami.Theme.backgroundColor.b, + 0.95) Behavior on color { ColorAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutQuad } } opacity: Math.max(0, Math.min(brightnessPressedValue, actionDrawer.offset / root.minimizedQuickSettingsOffset)) } + // The base swipe area. + // Used to cover the full surface of the drawer to allow dismissing or expanding it. + MobileShell.SwipeArea { + id: swipeAreaBase + mode: MobileShell.SwipeArea.VerticalOnly + anchors.fill: parent + + onSwipeStarted: root.startSwipe() + onSwipeEnded: root.endSwipe() + onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) + + onTouchpadScrollStarted: root.startSwipe() + onTouchpadScrollEnded: root.endSwipe() + onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) + + // Proxy in the layout that switches between landscape and portrait mode. + ColumnLayout { + anchors.fill: parent + visible: root.actionDrawer.mode != ActionDrawer.Portrait + LayoutItemProxy { target: contentContainerLoader } + } + + // Mouse area for dismissing action drawer in portrait mode when background is clicked. + MouseArea { + anchors.fill: parent + visible: root.actionDrawer.mode == ActionDrawer.Portrait + + // dismiss drawer when background is clicked + onClicked: root.actionDrawer.close(); + } + + // The clear all notification history button. + Item { + id: toolButtons + height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0 + + visible: actionDrawer.intendedToBeVisible + opacity: Math.max(0, Math.min(root.brightnessPressedValue, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset)) + + anchors { + topMargin: notificationDrawer.height + leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : 10 + rightMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : notificationDrawer.notificationWidget.anchors.rightMargin + Kirigami.Units.gridUnit - notificationDrawer.anchors.leftMargin + 370 + top: parent.top + left: parent.left + right: parent.right + } + + Rectangle { + id: spacer + anchors.left: parent.left + anchors.right: parent.right + + visible: notificationDrawer.listOverflowing + height: 1 + opacity: 0.25 + color: Kirigami.Theme.textColor + } + + RowLayout { + id: toolLayout + + anchors { + top: spacer.bottom + right: parent.right + left: parent.left + leftMargin: Kirigami.Units.largeSpacing + rightMargin: Kirigami.Units.largeSpacing + topMargin: Kirigami.Units.largeSpacing + bottomMargin: Kirigami.Units.largeSpacing + } + + PlasmaComponents.ToolButton { + id: clearButton + + Layout.alignment: Qt.AlignCenter + + visible: notificationDrawer.hasNotifications + + font.bold: true + font.pointSize: Kirigami.Theme.smallFont.pointSize + + icon.name: "edit-clear-history" + text: i18n("Clear All Notifications") + onClicked: notificationDrawer.notificationWidget.clearHistory() + } + } + } + } + + // notification drawer ui + // separated from the main drawer ui swipe area to prevent scrolling conflicts + NotificationDrawer { + id: notificationDrawer + + swipeArea: swipeAreaPortrait + actionDrawer: root.actionDrawer + mediaControlsWidget: root.mediaControlsWidget + contentContainer: root + opacity: Math.max(0, Math.min(root.brightnessPressedValue, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset)) + + anchors { + top: parent.top + left: parent.left + right: parent.right + rightMargin: root.actionDrawer.mode == ActionDrawer.Portrait ? 0 : 360 + leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : notificationDrawer.minWidthHeight * 0.06 + } + } + + // Secondary swipe area for uses in portrait. + // Covers the surface area of the quick settings panel to allow dismissing or expanding the drawer while also having it over top of the notification list. + MobileShell.SwipeArea { + id: swipeAreaPortrait + mode: MobileShell.SwipeArea.VerticalOnly + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: root.actionDrawer.mode === ActionDrawer.Portrait ? actionDrawer.offsetResistance : root.height + interactive: root.actionDrawer.mode === ActionDrawer.Portrait + + onSwipeStarted: root.startSwipe() + onSwipeEnded: root.endSwipe() + onSwipeMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) + + onTouchpadScrollStarted: root.startSwipe() + onTouchpadScrollEnded: root.endSwipe() + onTouchpadScrollMove: (totalDeltaX, totalDeltaY, deltaX, deltaY) => root.moveSwipe(totalDeltaX, totalDeltaY, deltaX, deltaY) + + // Proxy in the layout that switches between landscape and portrait mode. + ColumnLayout { + anchors.fill: parent + visible: root.actionDrawer.mode == ActionDrawer.Portrait + LayoutItemProxy { target: contentContainerLoader } + } + } + // Layout that switches between landscape and portrait mode Loader { id: contentContainerLoader - anchors.fill: parent + + Layout.fillWidth: true + Layout.fillHeight: true readonly property real minimizedQuickSettingsOffset: item ? item.minimizedQuickSettingsOffset : 0 readonly property real maximizedQuickSettingsOffset: item ? item.maximizedQuickSettingsOffset : 0 @@ -59,6 +220,7 @@ Item { sourceComponent: root.actionDrawer.mode == ActionDrawer.Portrait ? portraitContentContainer : landscapeContentContainer } + // The portrait content container. Component { id: portraitContentContainer PortraitContentContainer { @@ -69,10 +231,10 @@ Item { quickSettings: root.quickSettings statusBar: root.statusBar mediaControlsWidget: root.mediaControlsWidget - notificationsWidget: root.notificationsWidget } } + // The landscape content container. Component { id: landscapeContentContainer LandscapeContentContainer { @@ -82,12 +244,9 @@ Item { quickSettings: root.quickSettings statusBar: root.statusBar - mediaControlsWidget: root.mediaControlsWidget - notificationsWidget: root.notificationsWidget } } - // Components shared between the two layouts. // This allows us to avoid having to reload the components every time the screen size changes. @@ -116,29 +275,8 @@ Item { property MobileShell.MediaControlsWidget mediaControlsWidget: MobileShell.MediaControlsWidget { id: mediaWidget - inActionDrawer: true + inActionDrawer: root.actionDrawer.mode == ActionDrawer.Portrait opacity: brightnessPressedValue } - - property MobileShell.NotificationsWidget notificationsWidget: MobileShell.NotificationsWidget { - id: notificationWidget - historyModel: root.actionDrawer.notificationModel - historyModelType: root.actionDrawer.notificationModelType - notificationSettings: root.actionDrawer.notificationSettings - actionsRequireUnlock: root.actionDrawer.restrictedPermissions - onUnlockRequested: root.actionDrawer.permissionsRequested() - - opacity: brightnessPressedValue - - Connections { - target: root.actionDrawer - - function onRunPendingNotificationAction() { - notificationWidget.runPendingAction(); - } - } - - onBackgroundClicked: root.actionDrawer.close(); - } -} \ No newline at end of file +} diff --git a/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml b/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml index 7dbc79e5..cb22d589 100644 --- a/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml +++ b/components/mobileshell/qml/actiondrawer/LandscapeContentContainer.qml @@ -24,14 +24,12 @@ Item { property alias quickSettings: quickSettingsPanel.quickSettings property alias statusBar: quickSettingsPanel.statusBar - property alias mediaControlsWidget: mediaControlsWidgetProxy.contentItem - property alias notificationsWidget: notificationWidgetProxy.contentItem readonly property real minimizedQuickSettingsOffset: height readonly property real maximizedQuickSettingsOffset: height readonly property bool isOnLargeScreen: width > quickSettingsPanel.width * 2.5 readonly property real minWidthHeight: Math.min(root.width, root.height) - readonly property real opacityValue: Math.max(0, Math.min(1, actionDrawer.offset / root.minimizedQuickSettingsOffset)) + readonly property real opacityValue: Math.max(0, Math.min(1, actionDrawer.offsetResistance / root.minimizedQuickSettingsOffset)) readonly property double brightnessPressedValue: quickSettings.brightnessPressedValue Kirigami.Theme.colorSet: Kirigami.Theme.View @@ -50,86 +48,6 @@ Item { // dismiss drawer when background is clicked onClicked: root.actionDrawer.close(); - // left side - ColumnLayout { - id: columnLayout - - opacity: opacityValue - spacing: 0 - - anchors { - top: mediaControlsWidgetProxy.bottom - topMargin: 0 - bottom: parent.bottom - bottomMargin: 0 - right: quickSettingsPanel.left - left: parent.left - } - anchors.margins: minWidthHeight * 0.06 - - MobileShell.BaseItem { - id: notificationWidgetProxy - - // don't allow notifications widget to get too wide - Layout.maximumWidth: Kirigami.Units.gridUnit * 25 - Layout.fillHeight: true - Layout.fillWidth: true - Layout.topMargin: minWidthHeight * 0.02 - } - } - - PlasmaComponents.Label { - id: clock - text: Qt.formatTime(timeSource.data.Local.DateTime, MobileShell.ShellUtil.isSystem24HourFormat ? "h:mm" : "h:mm ap") - verticalAlignment: Qt.AlignVCenter - opacity: Math.min(brightnessPressedValue, columnLayout.opacity) - - anchors { - left: parent.left - top: parent.top - topMargin: columnLayout.anchors.margins / 2 - leftMargin: columnLayout.anchors.margins - } - - font.pixelSize: Math.min(40, minWidthHeight * 0.1) - font.weight: Font.ExtraLight - elide: Text.ElideRight - } - - PlasmaComponents.Label { - id: date - text: Qt.formatDate(timeSource.data.Local.DateTime, "ddd MMMM d") - verticalAlignment: Qt.AlignTop - color: Kirigami.Theme.disabledTextColor - opacity: Math.min(brightnessPressedValue, columnLayout.opacity) - - anchors { - left: parent.left - top: clock.bottom - bottom: isOnLargeScreen ? columnLayout.top : mediaControlsWidgetProxy.top - topMargin: Kirigami.Units.smallSpacing - leftMargin: columnLayout.anchors.margins - } - - font.pixelSize: Math.min(20, minWidthHeight * 0.05) - font.weight: Font.Light - } - - MobileShell.BaseItem { - id: mediaControlsWidgetProxy - property real fullHeight: visible ? height + Kirigami.Units.smallSpacing * 6 : 0 - - y: isOnLargeScreen ? date.y - height + date.implicitHeight : date.y + date.implicitHeight + columnLayout.anchors.margins / 2 - opacity: columnLayout.opacity - - anchors { - right: quickSettingsPanel.left - left: isOnLargeScreen ? date.right : parent.left - leftMargin: columnLayout.anchors.margins - rightMargin: columnLayout.anchors.margins - quickSettingsPanel.leftPadding - } - } - // right sidebar MobileShell.QuickSettingsPanel { id: quickSettingsPanel @@ -139,7 +57,7 @@ Item { readonly property real intendedWidth: 360 property real offsetRatio: quickSettingsPanel.height / root.height - anchors.topMargin: Math.min(root.actionDrawer.offset * offsetRatio - quickSettingsPanel.height, 0) + anchors.topMargin: Math.min(root.actionDrawer.offsetResistance * offsetRatio - quickSettingsPanel.height, 0) anchors.top: parent.top anchors.right: parent.right diff --git a/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml b/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml new file mode 100644 index 00000000..480ba7d3 --- /dev/null +++ b/components/mobileshell/qml/actiondrawer/NotificationDrawer.qml @@ -0,0 +1,200 @@ +/* + * 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.plasma5support 2.0 as P5Support +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.private.mobileshell as MobileShell +import org.kde.kirigami 2.20 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 + + property real topPadding: actionDrawer.mode == ActionDrawer.Portrait ? Kirigami.Units.largeSpacing : date.y + date.height + Kirigami.Units.smallSpacing * 6 + property real topMargin: actionDrawer.mode == 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 + + height: Math.min(actionDrawer.height - toolButtons.height, notificationWidget.listView.contentHeight + 10 + topMargin) + + // time source for the time and date whenin landscape mode + P5Support.DataSource { + id: timeSource + engine: "time" + connectedSources: ["Local"] + interval: 60 * 1000 + } + + Kirigami.Theme.colorSet: Kirigami.Theme.View + Kirigami.Theme.inherit: false + + MobileShell.VelocityCalculator { + id: velocityCalculator + } + + // notification list widget + // margin adjusted to fit and postion into the action drawer + MobileShell.NotificationsWidget { + id: notificationWidget + anchors.fill: parent + anchors.topMargin: root.topMargin + anchors.rightMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : Math.max(root.width - Kirigami.Units.gridUnit * 25, 0) + anchors.leftMargin: actionDrawer.mode == ActionDrawer.Portrait ? 0 : -Kirigami.Units.gridUnit + + historyModel: actionDrawer.notificationModel + historyModelType: actionDrawer.notificationModelType + notificationSettings: actionDrawer.notificationSettings + actionsRequireUnlock: actionDrawer.restrictedPermissions + onUnlockRequested: actionDrawer.permissionsRequested() + topPadding: root.topPadding + showHeader: actionDrawer.mode != ActionDrawer.Portrait + listView.interactive: !actionDrawer.dragging && root.listOverflowing + + Connections { + target: actionDrawer + + function onRunPendingNotificationAction() { + notificationWidget.runPendingAction(); + } + } + + // the first swipe when at the top of the notification list is handeled 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 + yAxis.enabled: true + 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 != ActionDrawer.Portrait + + transform: [ + Translate { + y: -notificationWidget.listView.contentY + notificationWidget.listView.originY + } + ] + + PlasmaComponents.Label { + id: clock + text: Qt.formatTime(timeSource.data.Local.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(timeSource.data.Local.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 + } + } +} diff --git a/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml b/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml index c4f2e26c..ad892d02 100644 --- a/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml +++ b/components/mobileshell/qml/actiondrawer/PortraitContentContainer.qml @@ -28,7 +28,6 @@ Item { property alias quickSettings: quickSettingsDrawer.quickSettings property alias statusBar: quickSettingsDrawer.statusBar property alias mediaControlsWidget: quickSettingsDrawer.mediaControlsWidget - property alias notificationsWidget: notificationWidgetProxy.contentItem Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false @@ -39,19 +38,20 @@ Item { MobileShell.QuickSettingsDrawer { id: quickSettingsDrawer - z: 1 // ensure it's above notifications // physically move the drawer when between closed <-> pinned mode readonly property real offsetHeight: actionDrawer.openToPinnedMode ? minimizedQuickSettingsOffset : maximizedQuickSettingsOffset - anchors.topMargin: Math.min(root.actionDrawer.offset - offsetHeight, 0) - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + anchors { + topMargin: Math.min(root.actionDrawer.offsetResistance - offsetHeight, 0) + top: parent.top + left: parent.left + right: parent.right + } actionDrawer: root.actionDrawer // opacity and move animation (disabled when openToPinnedMode is false) - property real offsetDist: actionDrawer.offset - minimizedQuickSettingsOffset + property real offsetDist: actionDrawer.offsetResistance - minimizedQuickSettingsOffset property real totalOffsetDist: maximizedQuickSettingsOffset - minimizedQuickSettingsOffset minimizedToFullProgress: actionDrawer.openToPinnedMode ? (actionDrawer.opened ? applyMinMax(offsetDist / totalOffsetDist) : 0) : 1 @@ -65,33 +65,12 @@ Item { addedHeight: { if (!actionDrawer.openToPinnedMode) { // if pinned mode disabled, just go to full height - let progress = (root.actionDrawer.offset - maximizedQuickSettingsOffset) / (quickSettingsDrawer.maxAddedHeight * 4); - let effectProgress = Math.atan(Math.max(0, progress)); - return (quickSettingsDrawer.maxAddedHeight * effectProgress) + quickSettingsDrawer.maxAddedHeight; + return Math.max(maximizedQuickSettingsOffset - minimizedQuickSettingsOffset, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset) } else if (!actionDrawer.opened) { - // over-scroll effect for initial opening - let progress = (root.actionDrawer.offset - minimizedQuickSettingsOffset) / quickSettingsDrawer.maxAddedHeight; - let effectProgress = Math.atan(Math.max(0, progress)); - return quickSettingsDrawer.maxAddedHeight * 0.25 * effectProgress; + return Math.max(0, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset) } else { - // over-scroll effect for full drawer - let progress = (root.actionDrawer.offset - maximizedQuickSettingsOffset) / (quickSettingsDrawer.maxAddedHeight * 4); - let effectProgress = Math.atan(Math.max(0, progress)); - // as the drawer opens, add height to the rectangle, revealing content - return (quickSettingsDrawer.maxAddedHeight * effectProgress) + Math.max(0, Math.min(quickSettingsDrawer.maxAddedHeight, root.actionDrawer.offset - minimizedQuickSettingsOffset)); + return Math.max(0, root.actionDrawer.offsetResistance - minimizedQuickSettingsOffset) } } } - - MobileShell.BaseItem { - id: notificationWidgetProxy - - anchors { - top: quickSettingsDrawer.bottom - bottom: parent.bottom - left: parent.left - right: parent.right - } - opacity: applyMinMax(root.actionDrawer.offset / root.minimizedQuickSettingsOffset) - } } diff --git a/containments/homescreens/folio/package/contents/ui/private/FlickableOpacityGradient.qml b/components/mobileshell/qml/components/FlickableOpacityGradient.qml similarity index 100% rename from containments/homescreens/folio/package/contents/ui/private/FlickableOpacityGradient.qml rename to components/mobileshell/qml/components/FlickableOpacityGradient.qml diff --git a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml index 0e32a7cf..ce127eec 100644 --- a/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml +++ b/components/mobileshell/qml/widgets/notifications/NotificationsWidget.qml @@ -53,6 +53,31 @@ Item { */ property bool actionsRequireUnlock: false + /** + * Top padding of the notification list. + */ + property int topPadding: 0 + + /** + * Bottom padding of the notification list. + */ + property int bottomPadding: 0 + + /** + * Header component for notification list. + */ + property var header + + /** + * Whether to show the header component. + */ + property bool showHeader: false + + /** + * Gives access to the notification list view outside of the notification widget. + */ + property alias listView: list + /** * Whether the widget has notifications. */ @@ -73,11 +98,6 @@ Item { */ signal unlockRequested() - /** - * Emitted when the background is clicked (not a notification or other element). - */ - signal backgroundClicked() - /** * Run pending action that was pending for authentication when unlockRequested() was emitted. */ @@ -128,13 +148,6 @@ Item { intervalAlignment: P5Support.Types.AlignToMinute } - // implement background clicking signal - MouseArea { - anchors.fill: parent - onClicked: backgroundClicked() - z: -1 // ensure that this is below notification items so we don't steal button clicks - } - ListView { id: list model: historyModel @@ -148,10 +161,11 @@ Item { readonly property int animationDuration: ShellSettings.Settings.animationsEnabled ? Kirigami.Units.longDuration : 0 // If a screen overflow occurs, fix height in order to maintain tool buttons in place. - readonly property bool listOverflowing: contentItem.childrenRect.height + toolButtons.height + spacing >= root.height + readonly property bool listOverflowing: listHeight + spacing >= root.height + readonly property int listHeight: contentItem.childrenRect.height bottomMargin: spacing - height: count === 0 ? 0 : (listOverflowing ? root.height - toolButtons.height : contentItem.childrenRect.height + bottomMargin) + height: count === 0 ? (root.topPadding + (showHeader ? root.header.height + listHeight : 0)) : (listOverflowing ? root.height : listHeight + bottomMargin) anchors { top: parent.top @@ -159,7 +173,7 @@ Item { right: parent.right } - boundsBehavior: Flickable.StopAtBounds + boundsBehavior: Flickable.DragAndOvershootBounds spacing: Kirigami.Units.largeSpacing // TODO keyboard focus @@ -167,6 +181,39 @@ Item { highlightResizeDuration: 0 highlight: Item {} + // media control widget + // added to the notification list when in landscape mode + Component { + id: headerComponent + Item { + width: parent.width + + MobileShell.BaseItem { + id: headerComponentProxy + + contentItem: showHeader ? root.header : null + y: root.topPadding - Kirigami.Units.largeSpacing + + width: parent.width - Kirigami.Units.gridUnit * 2 + anchors.left: parent.left + anchors.leftMargin: Kirigami.Units.gridUnit + } + } + } + + // set bottom padding for the notification list + Component { + id: footerComponent + Item { + width: parent.width + height: root.bottomPadding + } + } + + header: headerComponent + + footer: footerComponent + section { property: "isGroup" criteria: ViewSection.FullString @@ -250,12 +297,28 @@ Item { PropertyAction { target: delegateLoader; property: "ListView.delayRemove"; value: false } } + // adjust top paddding for media control widget Component { id: groupDelegate - NotificationGroupHeader { - applicationName: model.applicationName - applicationIconSource: model.applicationIconName - originName: model.originName || "" + + Column { + spacing: Kirigami.Units.smallSpacing + + height: headerSpace.height + groupHeader.height + + Item { + id: headerSpace + width: parent.width + height: index == 0 ? root.topPadding + (showHeader ? root.header.height : 0) : 0 + visible: index == 0 + } + + NotificationGroupHeader { + id: groupHeader + applicationName: model.applicationName + applicationIconSource: model.applicationIconName + originName: model.originName || "" + } } } @@ -265,7 +328,14 @@ Item { Column { spacing: Kirigami.Units.smallSpacing - height: notificationItem.height + showMoreLoader.height + height: headerSpace.height + notificationItem.height + showMoreLoader.height + + Item { + id: headerSpace + width: parent.width + height: index == 0 ? root.topPadding + (showHeader ? root.header.height : 0) : 0 + visible: index == 0 + } NotificationItem { id: notificationItem @@ -303,7 +373,7 @@ Item { return false; return (model.groupChildrenCount > model.expandedGroupChildrenCount || model.isGroupExpanded) - && delegateLoader.ListView.nextSection != delegateLoader.ListView.section; + && delegateLoader.ListView.nextSection != delegateLoader.ListView.section; } // state + transition: animates the item when it becomes visible. Fade off is handled by above ListView.onRemove. @@ -323,8 +393,8 @@ Item { sourceComponent: PlasmaComponents3.ToolButton { icon.name: model.isGroupExpanded ? "arrow-up" : "arrow-down" text: model.isGroupExpanded ? i18n("Show Fewer") - : i18nc("Expand to show n more notifications", - "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount)) + : i18nc("Expand to show n more notifications", + "Show %1 More", (model.groupChildrenCount - model.expandedGroupChildrenCount)) onClicked: { list.setGroupExpanded(model.index, !model.isGroupExpanded) } @@ -334,58 +404,4 @@ Item { } } } - - Item { - id: toolButtons - height: visible ? spacer.height + toolLayout.height + toolLayout.anchors.topMargin + toolLayout.anchors.bottomMargin : 0 - - // do not show on lockscreen - visible: !root.actionsRequireUnlock - - anchors { - top: list.bottom - left: parent.left - right: parent.right - } - - Rectangle { - id: spacer - anchors.left: parent.left - anchors.right: parent.right - - visible: list.listOverflowing - height: 1 - opacity: 0.25 - color: Kirigami.Theme.textColor - } - - RowLayout { - id: toolLayout - - anchors { - top: spacer.bottom - right: parent.right - left: parent.left - leftMargin: Kirigami.Units.largeSpacing - rightMargin: Kirigami.Units.largeSpacing - topMargin: list.spacing - bottomMargin: list.spacing - } - - PlasmaComponents3.ToolButton { - id: clearButton - - Layout.alignment: Qt.AlignCenter - - visible: hasNotifications - - font.bold: true - font.pointSize: Kirigami.Theme.smallFont.pointSize - - icon.name: "edit-clear-history" - text: i18n("Clear All Notifications") - onClicked: clearHistory() - } - } - } } diff --git a/containments/homescreens/folio/package/contents/ui/AppDrawer.qml b/containments/homescreens/folio/package/contents/ui/AppDrawer.qml index 10167a61..d479b505 100644 --- a/containments/homescreens/folio/package/contents/ui/AppDrawer.qml +++ b/containments/homescreens/folio/package/contents/ui/AppDrawer.qml @@ -62,12 +62,12 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - opacity: 0 + opacity: 0 // we display with the opacity gradient below headerHeight: root.headerHeight } // opacity gradient at grid edges - FlickableOpacityGradient { + MobileShell.FlickableOpacityGradient { anchors.fill: appDrawerGrid flickable: appDrawerGrid } diff --git a/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml b/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml index b02bb312..518d25bf 100644 --- a/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml +++ b/containments/homescreens/folio/package/contents/ui/settings/AppletListViewer.qml @@ -13,6 +13,7 @@ import org.kde.plasma.private.shell 2.0 import org.kde.private.mobile.homescreen.folio 1.0 as Folio import org.kde.kirigamiaddons.formcard 1.0 as FormCard import org.kde.plasma.components 3.0 as PC3 +import org.kde.plasma.private.mobileshell as MobileShell import '../delegate' import '../private' @@ -199,7 +200,7 @@ MouseArea { } // opacity gradient at grid edges - FlickableOpacityGradient { + MobileShell.FlickableOpacityGradient { anchors.fill: gridView flickable: gridView } diff --git a/shell/contents/lockscreen/HeaderComponent.qml b/shell/contents/lockscreen/HeaderComponent.qml index 00ee89cb..8844e8f3 100644 --- a/shell/contents/lockscreen/HeaderComponent.qml +++ b/shell/contents/lockscreen/HeaderComponent.qml @@ -20,6 +20,8 @@ Item { property var notificationsModel: [] + readonly property bool actionDrawerVisible: swipeArea.actionDrawer.intendedToBeVisible + signal passwordRequested() // The status bar and quicksettings take a while to load, don't pause initial lockscreen loading for it diff --git a/shell/contents/lockscreen/LockScreen.qml b/shell/contents/lockscreen/LockScreen.qml index 94346d5e..314e8c71 100644 --- a/shell/contents/lockscreen/LockScreen.qml +++ b/shell/contents/lockscreen/LockScreen.qml @@ -30,7 +30,7 @@ Item { readonly property bool isWidescreen: root.height < 720 && (root.height < root.width * 0.75) property bool notificationsShown: false - property var passwordBar: flickableLoader.item ? flickableLoader.item.passwordBar : null + property var passwordBar: flickableLoader.item ? flickableLoader.item.flickable.passwordBar : null Component.onCompleted: { forceActiveFocus(); @@ -40,7 +40,7 @@ Item { Keys.onPressed: (event) => { if (flickableLoader.item) { root.lockScreenState.isKeyboardMode = true; - flickableLoader.item.goToOpenPosition(); + flickableLoader.item.flickable.goToOpenPosition(); passwordBar.textField.forceActiveFocus(); passwordBar.keyPress(event.text); @@ -63,7 +63,7 @@ Item { sourceComponent: WallpaperBlur { source: wallpaper - opacity: flickableLoader.item ? flickableLoader.item.openFactor : 0 + opacity: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0 } } @@ -73,7 +73,7 @@ Item { // Ensure keypad is opened when password is updated (ex. keyboard) function onPasswordChanged() { if (root.lockScreenState.password !== "" && flickableLoader.item) { - flickableLoader.item.goToOpenPosition(); + flickableLoader.item.flickable.goToOpenPosition(); } } } @@ -85,7 +85,7 @@ Item { onDpmsTurnedOff: (screen) => { if (screen.name === Screen.name) { if (flickableLoader.item) { - flickableLoader.item.goToClosePosition(); + flickableLoader.item.flickable.goToClosePosition(); } lockScreenState.resetPassword(); } @@ -102,7 +102,7 @@ Item { z: 1 anchors.fill: parent statusBarHeight: MobileShell.Constants.topPanelHeight - openFactor: flickableLoader.item ? flickableLoader.item.openFactor : 0 + openFactor: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0 notificationsModel: root.notifModel onPasswordRequested: root.askPassword() } @@ -145,43 +145,103 @@ Item { } // Container for lockscreen contents - sourceComponent: FlickContainer { - id: flickable - property alias passwordBar: keypad.passwordBar + sourceComponent: Item { + id: item + property alias flickable: flickable + FlickContainer { + id: flickable + anchors.fill: parent + property alias passwordBar: keypad.passwordBar - // Speed up animation when passwordless - animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800 + // Speed up animation when passwordless + animationDuration: root.lockScreenState.canBeUnlocked ? 400 : 800 - // Distance to swipe to fully open keypad - keypadHeight: Kirigami.Units.gridUnit * 20 + // Distance to swipe to fully open keypad + keypadHeight: Kirigami.Units.gridUnit * 20 - Component.onCompleted: { - // Go to closed position when loaded - flickable.position = 0; - flickable.goToClosePosition(); - } - - // Unlock lockscreen if it's already unlocked and keypad is opened - onOpened: { - if (root.lockScreenState.canBeUnlocked) { - Qt.quit(); + Component.onCompleted: { + // Go to closed position when loaded + flickable.position = 0; + flickable.goToClosePosition(); } - } - // Unlock lockscreen if it's already unlocked and keypad is open - Connections { - target: root.lockScreenState - function onCanBeUnlockedChanged() { - if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) { + // Unlock lockscreen if it's already unlocked and keypad is opened + onOpened: { + if (root.lockScreenState.canBeUnlocked) { Qt.quit(); } } - } - // Clear entered password after closing keypad - onOpenFactorChanged: { - if (flickable.openFactor < 0.1 && !flickable.movingUp) { - root.passwordBar.clear(); + // Unlock lockscreen if it's already unlocked and keypad is open + Connections { + target: root.lockScreenState + function onCanBeUnlockedChanged() { + if (root.lockScreenState.canBeUnlocked && flickable.openFactor > 0.8) { + Qt.quit(); + } + } + } + + // Clear entered password after closing keypad + onOpenFactorChanged: { + if (flickable.openFactor < 0.1 && !flickable.movingUp) { + root.passwordBar.clear(); + } + } + + QuickActionButton { + id: leftButton + buttonAction: ShellSettings.Settings.lockscreenLeftButtonAction + opacity: Math.max(0, 1 - flickable.openFactor * 2) + anchors { + bottom: parent.bottom + left: parent.left + bottomMargin: Kirigami.Units.largeSpacing * 3 + leftMargin: Kirigami.Units.largeSpacing * 3 + } + } + + // scroll up icon + BottomIconIndicator { + id: scrollUpIconLoader + lockScreenState: root.lockScreenState + opacity: Math.max(0, 1 - flickable.openFactor * 2) + + anchors.bottom: parent.bottom + anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1 + anchors.horizontalCenter: parent.horizontalCenter + } + + QuickActionButton { + id: rightButton + buttonAction: ShellSettings.Settings.lockscreenRightButtonAction + opacity: Math.max(0, 1 - flickable.openFactor * 2) + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: Kirigami.Units.largeSpacing * 3 + rightMargin: Kirigami.Units.largeSpacing * 3 + } + } + + Rectangle { + id: keypadScrim + anchors.fill: parent + visible: opacity > 0 + opacity: flickable.openFactor + color: Qt.rgba(0, 0, 0, 0.5) + } + + Keypad { + id: keypad + visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login + anchors.fill: parent + openProgress: flickable.openFactor + lockScreenState: root.lockScreenState + + // only show in last 50% of anim + opacity: (flickable.openFactor - 0.5) * 2 + transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 } } } @@ -199,74 +259,22 @@ Item { } ] - fullHeight: root.height - lockScreenState: root.lockScreenState notificationsModel: root.notifModel onNotificationsShownChanged: root.notificationsShown = notificationsShown onPasswordRequested: flickable.goToOpenPosition() - anchors.topMargin: headerBar.statusBarHeight - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - } + scrollLock: headerBar.actionDrawerVisible || (flickableLoader.item ? flickableLoader.item.flickable.openFactor > 0.2 : false) + z: scrollLock ? -1 : 0 - QuickActionButton { - id: leftButton - buttonAction: ShellSettings.Settings.lockscreenLeftButtonAction - opacity: Math.max(0, 1 - flickable.openFactor * 2) anchors { - bottom: parent.bottom - left: parent.left - bottomMargin: Kirigami.Units.largeSpacing * 3 - leftMargin: Kirigami.Units.largeSpacing * 3 + //topMargin: headerBar.statusBarHeight + top: item.top + bottom: item.bottom + left: item.left + right: item.right } } - - // scroll up icon - BottomIconIndicator { - id: scrollUpIconLoader - lockScreenState: root.lockScreenState - opacity: Math.max(0, 1 - flickable.openFactor * 2) - - anchors.bottom: parent.bottom - anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1 - anchors.horizontalCenter: parent.horizontalCenter - } - - QuickActionButton { - id: rightButton - buttonAction: ShellSettings.Settings.lockscreenRightButtonAction - opacity: Math.max(0, 1 - flickable.openFactor * 2) - anchors { - bottom: parent.bottom - right: parent.right - bottomMargin: Kirigami.Units.largeSpacing * 3 - rightMargin: Kirigami.Units.largeSpacing * 3 - } - } - - Rectangle { - id: keypadScrim - anchors.fill: parent - visible: opacity > 0 - opacity: flickable.openFactor - color: Qt.rgba(0, 0, 0, 0.5) - } - - Keypad { - id: keypad - visible: !root.lockScreenState.canBeUnlocked // don't show for passwordless login - anchors.fill: parent - openProgress: flickable.openFactor - lockScreenState: root.lockScreenState - - // only show in last 50% of anim - opacity: (flickable.openFactor - 0.5) * 2 - transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 } - } } } } diff --git a/shell/contents/lockscreen/LockScreenContent.qml b/shell/contents/lockscreen/LockScreenContent.qml index a76f724f..d5acc97e 100644 --- a/shell/contents/lockscreen/LockScreenContent.qml +++ b/shell/contents/lockscreen/LockScreenContent.qml @@ -13,13 +13,15 @@ import org.kde.plasma.private.mobileshell as MobileShell Item { id: root - required property var lockScreenState required property bool isVertical + required property var lockScreenState + + readonly property bool listOverflowing: notificationComponent.listOverflowing property var notificationsModel: [] property bool notificationsShown: false - property real fullHeight + property bool scrollLock: false signal passwordRequested() @@ -29,7 +31,6 @@ Item { visible: root.isVertical spacing: 0 - // Center clock when no notifications are shown, otherwise move the clock upward anchors.topMargin: Kirigami.Units.gridUnit * 3.5 anchors.bottomMargin: Kirigami.Units.gridUnit * 2 anchors.fill: parent @@ -85,6 +86,7 @@ Item { } MobileShell.MediaControlsWidget { + id: mediaControlsWidget Layout.alignment: root.isVertical ? Qt.AlignHCenter : Qt.AlignLeft Layout.fillWidth: true Layout.maximumWidth: Kirigami.Units.gridUnit * 25 @@ -93,20 +95,24 @@ Item { } } + // notification widget column NotificationsComponent { id: notificationComponent lockScreenState: root.lockScreenState notificationsModel: root.notificationsModel - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.fillWidth: true + Layout.fillHeight: true Layout.maximumWidth: Kirigami.Units.gridUnit * (25 + 2) // clip margins + topPadding: root.isVertical ? (mediaControlsWidget.visible ? Kirigami.Units.smallSpacing : Kirigami.Units.gridUnit) : Kirigami.Units.gridUnit + leftMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit rightMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit - bottomMargin: root.isVertical ? 0 : Kirigami.Units.gridUnit - topMargin: Kirigami.Units.gridUnit + topMargin: root.isVertical ? 0 : MobileShell.Constants.topPanelHeight + bottomMargin: Kirigami.Units.gridUnit * 2 + scrollLock: root.scrollLock onPasswordRequested: root.passwordRequested() onNotificationsShownChanged: root.notificationsShown = notificationsShown diff --git a/shell/contents/lockscreen/NotificationsComponent.qml b/shell/contents/lockscreen/NotificationsComponent.qml index e5992220..09fa51ea 100644 --- a/shell/contents/lockscreen/NotificationsComponent.qml +++ b/shell/contents/lockscreen/NotificationsComponent.qml @@ -20,7 +20,13 @@ Loader { property real rightMargin: 0 property real topMargin: 0 property real bottomMargin: 0 + + property real topPadding: 0 + readonly property bool notificationsShown: item && item.notificationsList.hasNotifications + readonly property bool listOverflowing: item && item.notificationsList.listView.listOverflowing + + property bool scrollLock: false property var notificationsList: item ? item.notificationsList : null @@ -56,24 +62,31 @@ Loader { property alias notificationsList: notificationsList Item { - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right anchors.topMargin: root.topMargin - anchors.bottomMargin: root.bottomMargin anchors.leftMargin: root.leftMargin anchors.rightMargin: root.rightMargin Kirigami.Theme.colorSet: Kirigami.Theme.Window Kirigami.Theme.inherit: false + height: Math.min(parent.height - root.topMargin - root.bottomMargin, notificationsList.listView.listHeight + Kirigami.Units.gridUnit) + MobileShell.NotificationsWidget { id: notificationsList anchors.fill: parent + opacity: 0 // we display with the opacity gradient below historyModelType: MobileShell.NotificationsModelType.WatchedNotificationsModel actionsRequireUnlock: true historyModel: root.notificationsModel notificationSettings: root.notificationSettings inLockscreen: true + topPadding: root.topPadding // Kirigami.Units.gridUnit + bottomPadding: Kirigami.Units.gridUnit + listView.interactive: !root.scrollLock && listView.listOverflowing property bool requestNotificationAction: false @@ -82,6 +95,17 @@ Loader { root.passwordRequested(); } } + + // opacity gradient at flickable edges + MobileShell.FlickableOpacityGradient { + anchors { + top: notificationsList.top + left: notificationsList.left + right: notificationsList.right + } + height: notificationsList.listView.height + flickable: notificationsList.listView + } } } }