// SPDX-FileCopyrightText: 2019 Nicolas Fella // SPDX-FileCopyrightText: 2021-2024 Devin Lin // 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.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.passwordBar : null Component.onCompleted: { forceActiveFocus(); } // Listen for keyboard events, and focus on input area Keys.onPressed: { if (flickableLoader.item) { root.lockScreenState.isKeyboardMode = true; flickableLoader.item.goToOpenPosition(); passwordBar.textField.forceActiveFocus(); } } // 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.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.goToOpenPosition(); } } } // when screen turns off, reset state DPMS.DPMSUtil { id: dpms onDpmsTurnedOff: (screen) => { if (screen.name === Screen.name) { if (flickableLoader.item) { flickableLoader.item.goToClosePosition(); } lockScreenState.resetPassword(); } } } Item { id: lockscreenContainer anchors.fill: parent // Header bar and action drawer HeaderComponent { id: headerBar z: 1 anchors.fill: parent statusBarHeight: Kirigami.Units.gridUnit * 1.25 openFactor: flickableLoader.item ? flickableLoader.item.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: FlickContainer { id: flickable 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) { root.passwordBar.clear(); } } 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 } ] 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 } // 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 } 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 } } } } } }