2021-12-22 23:29:00 +00:00
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2014 Marco Martin <notmart@gmail.com>
|
|
|
|
|
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import QtQuick 2.15
|
|
|
|
|
import QtQuick.Controls 2.15
|
|
|
|
|
import QtQuick.Layouts 1.1
|
|
|
|
|
import QtQuick.Window 2.2
|
|
|
|
|
|
|
|
|
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
|
|
|
|
import org.kde.plasma.components 3.0 as PlasmaComponents
|
|
|
|
|
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
|
2022-04-07 18:11:08 +00:00
|
|
|
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2023-03-18 19:28:17 +00:00
|
|
|
import "../components" as Components
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
Item {
|
|
|
|
|
id: root
|
2022-02-11 22:50:17 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The model for the notification widget.
|
|
|
|
|
*/
|
|
|
|
|
property var notificationModel
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
/**
|
|
|
|
|
* The model type for the notification widget.
|
|
|
|
|
*/
|
|
|
|
|
property var notificationModelType: MobileShell.NotificationsModelType.NotificationsModel
|
|
|
|
|
|
2022-02-11 22:50:17 +00:00
|
|
|
/**
|
|
|
|
|
* The notification settings object to be used in the notification widget.
|
|
|
|
|
*/
|
|
|
|
|
property var notificationSettings
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
/**
|
|
|
|
|
* Whether actions should be subject to restricted permissions (ex. lockscreen).
|
|
|
|
|
*
|
|
|
|
|
* The permissionsRequested() signal emits when authentication is requested.
|
|
|
|
|
*/
|
|
|
|
|
property bool restrictedPermissions: false
|
|
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
/**
|
|
|
|
|
* The amount of pixels moved by touch/mouse in the process of opening/closing the panel.
|
|
|
|
|
*/
|
|
|
|
|
property real offset: 0
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the panel is being dragged.
|
|
|
|
|
*/
|
|
|
|
|
property bool dragging: false
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the panel is open after touch/mouse release from the first opening swipe.
|
|
|
|
|
*/
|
|
|
|
|
property bool opened: false
|
|
|
|
|
|
2022-05-25 01:03:21 +00:00
|
|
|
/**
|
2022-05-25 16:13:29 +00:00
|
|
|
* Whether the panel should open to pinned mode first, with a second stroke needed to full open.
|
|
|
|
|
* Only applies to portrait mode.
|
2022-05-25 01:03:21 +00:00
|
|
|
*/
|
2022-05-25 16:13:29 +00:00
|
|
|
property bool openToPinnedMode: true
|
2022-05-25 01:03:21 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
/**
|
|
|
|
|
* Direction the panel is currently moving in.
|
|
|
|
|
*/
|
2022-04-10 17:28:32 +00:00
|
|
|
property int direction: MobileShell.Direction.None
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2022-09-10 16:41:08 +00:00
|
|
|
/**
|
|
|
|
|
* The notifications widget being shown. May be null.
|
|
|
|
|
*/
|
|
|
|
|
property var notificationsWidget: contentContainerLoader.item.notificationsWidget
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
/**
|
|
|
|
|
* The mode of the action drawer (portrait or landscape).
|
|
|
|
|
*/
|
2021-12-22 23:29:00 +00:00
|
|
|
property int mode: (height > width && width <= largePortraitThreshold) ? ActionDrawer.Portrait : ActionDrawer.Landscape
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* At some point, even if the screen is technically portrait, if we have a ton of width it'd be best to just show the landscape mode.
|
|
|
|
|
*/
|
|
|
|
|
readonly property real largePortraitThreshold: PlasmaCore.Units.gridUnit * 35
|
|
|
|
|
|
|
|
|
|
enum Mode {
|
|
|
|
|
Portrait = 0,
|
|
|
|
|
Landscape
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
/**
|
|
|
|
|
* Emitted when the drawer has closed.
|
|
|
|
|
*/
|
|
|
|
|
signal drawerClosed()
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
/**
|
|
|
|
|
* Emitted when the drawer has opened.
|
|
|
|
|
*/
|
|
|
|
|
signal drawerOpened()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Emitted when permissions are requested (ex. unlocking the phone).
|
|
|
|
|
*
|
|
|
|
|
* Only gets emitted when restrictedPermissions is set to true.
|
|
|
|
|
*/
|
|
|
|
|
signal permissionsRequested()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Runs the held notification action that was pending for authentication.
|
|
|
|
|
*
|
|
|
|
|
* Should be called by users if authentication is successful after permissionsRequested() was emitted.
|
|
|
|
|
*/
|
|
|
|
|
signal runPendingNotificationAction()
|
2021-12-22 23:29:00 +00:00
|
|
|
|
|
|
|
|
onOpenedChanged: {
|
|
|
|
|
if (opened) flickable.focus = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
property real oldOffset
|
|
|
|
|
onOffsetChanged: {
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
offset = 0;
|
|
|
|
|
}
|
2022-05-25 01:03:21 +00:00
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
root.direction = (oldOffset === offset)
|
2022-04-10 17:28:32 +00:00
|
|
|
? MobileShell.Direction.None
|
|
|
|
|
: (offset > oldOffset ? MobileShell.Direction.Down : MobileShell.Direction.Up);
|
2021-12-22 23:29:00 +00:00
|
|
|
|
|
|
|
|
oldOffset = offset;
|
|
|
|
|
|
|
|
|
|
// close panel immediately after panel is not shown, and the flickable is not being dragged
|
2022-04-07 18:11:08 +00:00
|
|
|
if (opened && root.offset <= 0 && !flickable.dragging && !closeAnim.running && !openAnim.running) {
|
|
|
|
|
root.updateState();
|
2021-12-22 23:29:00 +00:00
|
|
|
focus = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancelAnimations() {
|
|
|
|
|
closeAnim.stop();
|
|
|
|
|
openAnim.stop();
|
|
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
function open() {
|
|
|
|
|
cancelAnimations();
|
2022-05-26 22:11:01 +00:00
|
|
|
if (openToPinnedMode) {
|
|
|
|
|
openAnim.restart(); // go to pinned height
|
|
|
|
|
} else {
|
|
|
|
|
expandAnim.restart(); // go to maximized height
|
|
|
|
|
}
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
function closeImmediately() {
|
|
|
|
|
cancelAnimations();
|
|
|
|
|
offset = 0;
|
|
|
|
|
closeAnim.finished();
|
|
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
function close() {
|
|
|
|
|
cancelAnimations();
|
|
|
|
|
closeAnim.restart();
|
|
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
function expand() {
|
|
|
|
|
cancelAnimations();
|
|
|
|
|
expandAnim.restart();
|
|
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2021-12-22 23:29:00 +00:00
|
|
|
function updateState() {
|
|
|
|
|
cancelAnimations();
|
|
|
|
|
let openThreshold = PlasmaCore.Units.gridUnit;
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
if (root.offset <= 0) {
|
2021-12-22 23:29:00 +00:00
|
|
|
// close immediately, so that we don't have to wait PlasmaCore.Units.longDuration
|
2022-04-07 18:11:08 +00:00
|
|
|
root.visible = false;
|
2021-12-22 23:29:00 +00:00
|
|
|
close();
|
2022-04-10 17:28:32 +00:00
|
|
|
} else if (root.direction === MobileShell.Direction.None || !root.opened) {
|
2022-05-26 22:11:01 +00:00
|
|
|
|
|
|
|
|
// if the panel has not been opened yet, run open animation only if drag passed threshold
|
|
|
|
|
(root.offset < openThreshold) ? close() : open();
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
} else if (root.offset > contentContainerLoader.maximizedQuickSettingsOffset) {
|
2022-05-26 22:11:01 +00:00
|
|
|
// if drag has gone past the fully expanded view
|
2021-12-22 23:29:00 +00:00
|
|
|
expand();
|
2022-04-07 18:11:08 +00:00
|
|
|
} else if (root.offset > contentContainerLoader.minimizedQuickSettingsOffset) {
|
2022-05-26 22:11:01 +00:00
|
|
|
// if drag is between pinned view and fully expanded view
|
2022-04-10 17:28:32 +00:00
|
|
|
if (root.direction === MobileShell.Direction.Down) {
|
2021-12-22 23:29:00 +00:00
|
|
|
expand();
|
|
|
|
|
} else {
|
2022-05-26 22:11:01 +00:00
|
|
|
// go back to pinned, or close if pinned mode is disabled
|
|
|
|
|
openToPinnedMode ? open() : close();
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
2022-05-26 22:11:01 +00:00
|
|
|
|
2022-04-10 17:28:32 +00:00
|
|
|
} else if (root.direction === MobileShell.Direction.Down) {
|
2022-05-26 22:11:01 +00:00
|
|
|
// if drag is between pinned view and open view, and dragging down
|
2021-12-22 23:29:00 +00:00
|
|
|
open();
|
|
|
|
|
} else {
|
2022-05-26 22:11:01 +00:00
|
|
|
// if drag is between pinned view and open view, and dragging up
|
2021-12-22 23:29:00 +00:00
|
|
|
close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Timer {
|
|
|
|
|
id: updateStateTimer
|
|
|
|
|
interval: 0
|
|
|
|
|
onTriggered: updateState()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PropertyAnimation on offset {
|
|
|
|
|
id: closeAnim
|
2022-12-04 17:09:00 +00:00
|
|
|
duration: PlasmaCore.Units.veryLongDuration
|
|
|
|
|
easing.type: Easing.OutExpo
|
2021-12-22 23:29:00 +00:00
|
|
|
to: 0
|
|
|
|
|
onFinished: {
|
2022-04-07 18:11:08 +00:00
|
|
|
root.visible = false;
|
|
|
|
|
root.opened = false;
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PropertyAnimation on offset {
|
|
|
|
|
id: openAnim
|
2022-12-04 17:09:00 +00:00
|
|
|
duration: PlasmaCore.Units.veryLongDuration
|
|
|
|
|
easing.type: Easing.OutExpo
|
2021-12-22 23:29:00 +00:00
|
|
|
to: contentContainerLoader.minimizedQuickSettingsOffset
|
2022-04-07 18:11:08 +00:00
|
|
|
onFinished: root.opened = true
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
PropertyAnimation on offset {
|
|
|
|
|
id: expandAnim
|
2022-12-04 17:09:00 +00:00
|
|
|
duration: PlasmaCore.Units.veryLongDuration
|
|
|
|
|
easing.type: Easing.OutExpo
|
2021-12-22 23:29:00 +00:00
|
|
|
to: contentContainerLoader.maximizedQuickSettingsOffset
|
2022-04-07 18:11:08 +00:00
|
|
|
onFinished: root.opened = true;
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-18 19:28:17 +00:00
|
|
|
Components.Flickable {
|
2021-12-22 23:29:00 +00:00
|
|
|
id: flickable
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
contentWidth: root.width
|
2023-03-20 11:34:18 +00:00
|
|
|
contentHeight: root.height * 2
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2022-04-07 18:11:08 +00:00
|
|
|
// if the recent root.offset change was due to this flickable
|
2021-12-22 23:29:00 +00:00
|
|
|
property bool offsetChangedDueToContentY: false
|
|
|
|
|
Connections {
|
2022-04-07 18:11:08 +00:00
|
|
|
target: root
|
2021-12-22 23:29:00 +00:00
|
|
|
function onOffsetChanged() {
|
|
|
|
|
if (!flickable.offsetChangedDueToContentY) {
|
2022-04-07 18:11:08 +00:00
|
|
|
// ensure the flickable's contentY is not moving when other sources change root.offset
|
2021-12-22 23:29:00 +00:00
|
|
|
flickable.cancelFlick();
|
|
|
|
|
}
|
|
|
|
|
flickable.offsetChangedDueToContentY = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
property real oldContentY
|
|
|
|
|
onContentYChanged: {
|
|
|
|
|
offsetChangedDueToContentY = true;
|
2022-04-07 18:11:08 +00:00
|
|
|
root.offset += oldContentY - contentY;
|
2021-12-22 23:29:00 +00:00
|
|
|
oldContentY = contentY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMovementStarted: {
|
2022-04-07 18:11:08 +00:00
|
|
|
root.cancelAnimations();
|
|
|
|
|
root.dragging = true;
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
2022-04-07 18:11:08 +00:00
|
|
|
onFlickStarted: root.dragging = true;
|
2021-12-22 23:29:00 +00:00
|
|
|
onMovementEnded: {
|
2022-04-07 18:11:08 +00:00
|
|
|
root.dragging = false;
|
|
|
|
|
root.updateState();
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
onFlickEnded: {
|
2022-04-07 18:11:08 +00:00
|
|
|
root.dragging = true;
|
|
|
|
|
root.updateState();
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDraggingChanged: {
|
|
|
|
|
if (!dragging) {
|
2022-04-07 18:11:08 +00:00
|
|
|
root.dragging = false;
|
2021-12-22 23:29:00 +00:00
|
|
|
flickable.cancelFlick();
|
2022-04-07 18:11:08 +00:00
|
|
|
root.updateState();
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// the flickable is only used to measure drag changes, we implement our own UI component movements
|
2022-04-07 18:11:08 +00:00
|
|
|
// the root element is not affected by contentY changes (it's effectively anchored to the flickable)
|
2021-12-22 23:29:00 +00:00
|
|
|
Loader {
|
|
|
|
|
id: contentContainerLoader
|
|
|
|
|
|
|
|
|
|
property real minimizedQuickSettingsOffset: item ? item.minimizedQuickSettingsOffset : 0
|
|
|
|
|
property real maximizedQuickSettingsOffset: item ? item.maximizedQuickSettingsOffset : 0
|
|
|
|
|
|
|
|
|
|
y: flickable.contentY
|
2022-04-07 18:11:08 +00:00
|
|
|
width: root.width
|
|
|
|
|
height: root.height
|
2021-12-22 23:29:00 +00:00
|
|
|
|
2022-12-11 02:40:33 +00:00
|
|
|
asynchronous: true
|
2022-04-07 18:11:08 +00:00
|
|
|
sourceComponent: root.mode == ActionDrawer.Portrait ? portraitContentContainer : landscapeContentContainer
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Component {
|
|
|
|
|
id: portraitContentContainer
|
|
|
|
|
PortraitContentContainer {
|
2022-04-07 18:11:08 +00:00
|
|
|
actionDrawer: root
|
|
|
|
|
width: root.width
|
|
|
|
|
height: root.height
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Component {
|
|
|
|
|
id: landscapeContentContainer
|
|
|
|
|
LandscapeContentContainer {
|
2022-04-07 18:11:08 +00:00
|
|
|
actionDrawer: root
|
|
|
|
|
width: root.width
|
|
|
|
|
height: root.height
|
2021-12-22 23:29:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|