shift-shell/shell/contents/lockscreen/LockScreen.qml
Micah Stanley e4323f4ef0 notification list: fix notification scrolling within action drawer and lockscreen
This merge request fixes an issue with notification list scrolling and also adds a few general improvements.

To accomplish this, the notification widget was moved outside of the action drawer swipe area and lock screen swipe area, separating them from the parent-child relationship. Instead, the notification widget is now layered separately on top. This change seems to fix the conflict when both areas are accepting swipes from the same direction.

Additionally, changes were made to the notification list widget for the action drawer to make it behave similarly to the folio home screen app library. Specifically, when at the top of the list, one can swipe down over the notification area to expand the action drawer. In landscape mode, the media widget, clock, and date were also added to the notification list to provide more room for viewing notifications when scrolling.

Closes https://invent.kde.org/plasma/plasma-mobile/-/issues/318
2025-03-20 02:06:33 +00:00

281 lines
10 KiB
QML

// SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
// SPDX-FileCopyrightText: 2021-2024 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import org.kde.plasma.core as PlasmaCore
import org.kde.notificationmanager as Notifications
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings
import org.kde.plasma.private.mobileshell.dpmsplugin as DPMS
import org.kde.plasma.components 3.0 as PC3
import org.kde.kirigami 2.12 as Kirigami
/**
* Lockscreen component that is loaded after the device is locked.
*
* Special attention must be paid to ensuring the GUI loads as fast as possible.
*/
Item {
id: root
readonly property var lockScreenState: LockScreenState {}
readonly property var notifModel: Notifications.WatchedNotificationsModel {}
// Only show widescreen mode for short height devices (ex. phone landscape)
readonly property bool isWidescreen: root.height < 720 && (root.height < root.width * 0.75)
property bool notificationsShown: false
property var passwordBar: flickableLoader.item ? flickableLoader.item.flickable.passwordBar : null
Component.onCompleted: {
forceActiveFocus();
}
// Listen for keyboard events, and focus on input area
Keys.onPressed: (event) => {
if (flickableLoader.item) {
root.lockScreenState.isKeyboardMode = true;
flickableLoader.item.flickable.goToOpenPosition();
passwordBar.textField.forceActiveFocus();
passwordBar.keyPress(event.text);
}
}
// Wallpaper blur
Loader {
id: wallpaperLoader
anchors.fill: parent
active: false
asynchronous: true
// This take a while to load, don't pause initial lockscreen loading for it
Timer {
running: true
repeat: false
onTriggered: wallpaperLoader.active = true
}
sourceComponent: WallpaperBlur {
source: wallpaper
opacity: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0
}
}
Connections {
target: root.lockScreenState
// Ensure keypad is opened when password is updated (ex. keyboard)
function onPasswordChanged() {
if (root.lockScreenState.password !== "" && flickableLoader.item) {
flickableLoader.item.flickable.goToOpenPosition();
}
}
}
// when screen turns off, reset state
DPMS.DPMSUtil {
id: dpms
onDpmsTurnedOff: (screen) => {
if (screen.name === Screen.name) {
if (flickableLoader.item) {
flickableLoader.item.flickable.goToClosePosition();
}
lockScreenState.resetPassword();
}
}
}
Item {
id: lockscreenContainer
anchors.fill: parent
// Header bar and action drawer
HeaderComponent {
id: headerBar
z: 1
anchors.fill: parent
statusBarHeight: MobileShell.Constants.topPanelHeight
openFactor: flickableLoader.item ? flickableLoader.item.flickable.openFactor : 0
notificationsModel: root.notifModel
onPasswordRequested: root.askPassword()
}
// Add loading indicator when status bar has not loaded yet
PC3.BusyIndicator {
id: flickableLoadingBusyIndicator
anchors.centerIn: parent
visible: flickableLoader.status != Loader.Ready
implicitHeight: Kirigami.Units.iconSizes.huge
implicitWidth: Kirigami.Units.iconSizes.huge
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
}
// Load flickable async
Loader {
id: flickableLoader
active: false
asynchronous: true
opacity: status == Loader.Ready ? 1 : 0
visible: opacity > 0
anchors.fill: parent
Behavior on opacity {
NumberAnimation {}
}
// This take a while to load, don't pause initial lockscreen and wallpaper loading for it
Timer {
id: loadTimer
running: true
repeat: false
onTriggered: {
flickableLoader.active = true
}
}
// Container for lockscreen contents
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
// 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();
}
}
// 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 }
}
}
LockScreenContent {
id: lockScreenContent
isVertical: !root.isWidescreen
opacity: Math.max(0, 1 - flickable.openFactor * 2)
transform: [
Scale {
origin.x: lockScreenContent.width / 2
origin.y: lockScreenContent.height / 2
yScale: 1 - (flickable.openFactor * 2) * 0.1
xScale: 1 - (flickable.openFactor * 2) * 0.1
}
]
lockScreenState: root.lockScreenState
notificationsModel: root.notifModel
onNotificationsShownChanged: root.notificationsShown = notificationsShown
onPasswordRequested: flickable.goToOpenPosition()
scrollLock: headerBar.actionDrawerVisible || (flickableLoader.item ? flickableLoader.item.flickable.openFactor > 0.2 : false)
z: scrollLock ? -1 : 0
anchors {
//topMargin: headerBar.statusBarHeight
top: item.top
bottom: item.bottom
left: item.left
right: item.right
}
}
}
}
}
}