mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-07-31 16:54:46 +00:00
lockscreen: Rework and simplify keypad appearance, and fix lockscreen state
The current lockscreen mockups were originally from this mockup: https://phabricator.kde.org/T12717 There are some issues with it however: - The 0 key is on the right, which is a bit strange and differs from the layouts of all other platforms - One-handed usability is worse due to 4 columns instead of 3 - Most mobile lockscreen have the keypad toward the center for one-handed usability, our keypad is stuck to the bottom - It makes use of a lot of shadows which makes it slower to load - It's supposed to emulate the design of the keyboard, but it doesn't look like it so it's out of place The new design is much simpler, with a centered 3 column approach making keys much easier to reach with one hand. It also avoids the use of shadows and layers.    TODO: - Investigate some improvements for keyboard input
This commit is contained in:
parent
b2bb4b4d6f
commit
5c41092c0f
11 changed files with 456 additions and 618 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Devin Lin <espidev@gmail.com>
|
// SPDX-FileCopyrightText: 2023-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
@ -27,7 +27,6 @@ Loader {
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
implicitWidth: Kirigami.Units.iconSizes.small
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||||||
implicitHeight: Kirigami.Units.iconSizes.small
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||||||
opacity: 1 - flickable.openFactor
|
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||||
source: "arrow-up"
|
source: "arrow-up"
|
||||||
|
|
@ -39,7 +38,6 @@ Loader {
|
||||||
|
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
source: 'fingerprint-symbolic'
|
source: 'fingerprint-symbolic'
|
||||||
opacity: 1 - flickable.openFactor
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
* SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
* SPDX-FileCopyrightText: 2020-2022 Devin Lin <devin@kde.org>
|
* SPDX-FileCopyrightText: 2020-2024 Devin Lin <devin@kde.org>
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <devin@kde.org>
|
// SPDX-FileCopyrightText: 2021-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
|
|
@ -9,14 +9,17 @@ import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
|
|
||||||
MobileShell.SwipeArea {
|
MobileShell.SwipeArea {
|
||||||
id: root
|
id: root
|
||||||
mode: MobileShell.SwipeArea.VerticalOnly
|
|
||||||
|
|
||||||
property int position: 0
|
|
||||||
|
|
||||||
required property real keypadHeight
|
required property real keypadHeight
|
||||||
|
|
||||||
|
readonly property real openFactor: position / keypadHeight
|
||||||
|
property real position: 0
|
||||||
|
property bool movingUp: false
|
||||||
|
property real __oldPosition: position
|
||||||
|
|
||||||
signal opened()
|
signal opened()
|
||||||
|
|
||||||
|
mode: MobileShell.SwipeArea.VerticalOnly
|
||||||
|
|
||||||
function cancelAnimations() {
|
function cancelAnimations() {
|
||||||
positionAnim.stop();
|
positionAnim.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +47,7 @@ MobileShell.SwipeArea {
|
||||||
|
|
||||||
NumberAnimation on position {
|
NumberAnimation on position {
|
||||||
id: positionAnim
|
id: positionAnim
|
||||||
duration: Kirigami.Units.veryLongDuration
|
duration: 800
|
||||||
easing.type: Easing.OutExpo
|
easing.type: Easing.OutExpo
|
||||||
|
|
||||||
onFinished: {
|
onFinished: {
|
||||||
|
|
@ -54,12 +57,16 @@ MobileShell.SwipeArea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property int oldPosition: position
|
|
||||||
property bool movingUp: false
|
|
||||||
|
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
movingUp = oldPosition <= position;
|
movingUp = __oldPosition <= position;
|
||||||
oldPosition = position;
|
__oldPosition = position;
|
||||||
|
|
||||||
|
// Limit position to between 0 and keypadHeight
|
||||||
|
if (position > keypadHeight) {
|
||||||
|
position = keypadHeight;
|
||||||
|
} else if (position < 0) {
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwipeStarted: cancelAnimations();
|
onSwipeStarted: cancelAnimations();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2020-2023 Devin Lin <espidev@gmail.com>
|
// SPDX-FileCopyrightText: 2020-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
@ -13,182 +13,153 @@ import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import org.kde.kirigami 2.12 as Kirigami
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: keypadRoot
|
id: root
|
||||||
|
required property real openProgress
|
||||||
required property var lockScreenState
|
required property var lockScreenState
|
||||||
|
|
||||||
property alias passwordBar: passwordBar
|
property alias passwordBar: passwordBar
|
||||||
|
|
||||||
// 0 - keypad is not shown, 1 - keypad is shown
|
|
||||||
property double swipeProgress
|
|
||||||
|
|
||||||
// slightly translucent background, for key contrast
|
|
||||||
color: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.backgroundColor, {"alpha": 0.9*255})
|
|
||||||
|
|
||||||
// colour calculations
|
|
||||||
readonly property color buttonColor: Qt.lighter(Kirigami.Theme.backgroundColor, 1.3)
|
|
||||||
readonly property color buttonPressedColor: Qt.darker(Kirigami.Theme.backgroundColor, 1.08)
|
|
||||||
readonly property color buttonTextColor: Kirigami.Theme.textColor
|
|
||||||
readonly property color dropShadowColor: Qt.darker(Kirigami.Theme.backgroundColor, 1.2)
|
|
||||||
readonly property color headerBackgroundColor: Qt.lighter(Kirigami.Theme.backgroundColor, 1.3)
|
|
||||||
|
|
||||||
opacity: Math.sin((Math.PI / 2) * swipeProgress + 1.5 * Math.PI) + 1
|
|
||||||
|
|
||||||
implicitHeight: {
|
|
||||||
if (passwordBar.isPinMode && !Qt.inputMethod.visible) {
|
|
||||||
return Kirigami.Units.gridUnit * 17;
|
|
||||||
} else {
|
|
||||||
return Math.min(root.height - passwordBar.implicitHeight, // don't make the password bar go off the screen
|
|
||||||
Kirigami.Units.smallSpacing * 2 + Qt.inputMethod.keyboardRectangle.height + passwordBar.implicitHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Kirigami.Units.longDuration
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileShell.HapticsEffect {
|
MobileShell.HapticsEffect {
|
||||||
id: haptics
|
id: haptics
|
||||||
}
|
}
|
||||||
|
|
||||||
// pin display and bar
|
// Column layout - most cases
|
||||||
PasswordBar {
|
ColumnLayout {
|
||||||
id: passwordBar
|
id: keypadVerticalContainer
|
||||||
|
visible: root.height > Kirigami.Units.gridUnit * 25
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.centerIn: parent
|
||||||
anchors.left: parent.left
|
spacing: Kirigami.Units.gridUnit
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
color: keypadRoot.headerBackgroundColor
|
LayoutItemProxy { target: header }
|
||||||
opacity: (Math.sin(2*((Math.PI / 2) * keypadRoot.swipeProgress + 1.5 * Math.PI)) + 1)
|
LayoutItemProxy { target: keypadGrid }
|
||||||
|
|
||||||
lockScreenState: keypadRoot.lockScreenState
|
|
||||||
|
|
||||||
keypadOpen: swipeProgress === 1
|
|
||||||
previewCharIndex: -2
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
blurMax: 16
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowVerticalOffset: 1
|
|
||||||
shadowOpacity: 0.3
|
|
||||||
shadowColor: keypadRoot.dropShadowColor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual number keys
|
// Row layout - used when there is restricted height
|
||||||
ColumnLayout {
|
RowLayout {
|
||||||
visible: opacity > 0
|
id: keypadHorizontalContainer
|
||||||
opacity: passwordBar.isPinMode ? 1 : 0
|
visible: !keypadVerticalContainer.visible
|
||||||
|
|
||||||
Behavior on opacity {
|
anchors.centerIn: parent
|
||||||
NumberAnimation {
|
spacing: Kirigami.Units.gridUnit * 2
|
||||||
duration: Kirigami.Units.longDuration
|
|
||||||
easing.type: Easing.InOutQuad
|
LayoutItemProxy { target: header }
|
||||||
|
LayoutItemProxy { target: keypadGrid }
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: header
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
// label ("wrong pin", "enter pin")
|
||||||
|
Label {
|
||||||
|
id: descriptionLabel
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
opacity: root.lockScreenState.password.length === 0 ? 1 : 0
|
||||||
|
text: root.lockScreenState.pinLabel
|
||||||
|
font.pointSize: 12
|
||||||
|
font.bold: true
|
||||||
|
color: 'white'
|
||||||
|
|
||||||
|
// Enforce extra margin at top of vertical container
|
||||||
|
Layout.topMargin: keypadVerticalContainer.visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 200 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors {
|
// pin display and bar
|
||||||
left: parent.left
|
PasswordBar {
|
||||||
right: parent.right
|
id: passwordBar
|
||||||
top: passwordBar.bottom
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 14
|
||||||
bottom: parent.bottom
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||||
topMargin: Kirigami.Units.gridUnit
|
|
||||||
bottomMargin: Kirigami.Units.gridUnit
|
lockScreenState: root.lockScreenState
|
||||||
|
isKeypadOpen: root.openProgress >= 0.9
|
||||||
}
|
}
|
||||||
spacing: Kirigami.Units.gridUnit
|
}
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: grid
|
id: keypadGrid
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
columnSpacing: Kirigami.Units.gridUnit
|
||||||
Layout.leftMargin: Kirigami.Units.gridUnit * 0.5
|
rowSpacing: Kirigami.Units.gridUnit
|
||||||
Layout.rightMargin: Kirigami.Units.gridUnit * 0.5
|
uniformCellHeights: true
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 22
|
uniformCellWidths: true
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 12.5
|
|
||||||
opacity: (Math.sin(2*((Math.PI / 2) * keypadRoot.swipeProgress + 1.5 * Math.PI)) + 1)
|
|
||||||
|
|
||||||
columns: 4
|
readonly property real intendedWidth: Kirigami.Units.gridUnit * 14
|
||||||
|
|
||||||
readonly property real keyRadius: 5
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 14
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 22
|
||||||
|
|
||||||
// numpad keys
|
readonly property real cellLength: (intendedWidth - columnSpacing * 2) / 3
|
||||||
Repeater {
|
|
||||||
model: ["1", "2", "3", "R", "4", "5", "6", "0", "7", "8", "9", "E"]
|
|
||||||
|
|
||||||
delegate: AbstractButton {
|
columns: 3
|
||||||
id: button
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
visible: modelData.length > 0
|
|
||||||
opacity: (Math.sin(2*((Math.PI / 2) * keypadRoot.swipeProgress + 1.5 * Math.PI)) + 1)
|
|
||||||
|
|
||||||
background: Rectangle {
|
// numpad keys
|
||||||
id: keyRect
|
Repeater {
|
||||||
radius: grid.keyRadius
|
model: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "R", "0", "E"]
|
||||||
color: button.pressed ? keypadRoot.buttonPressedColor : keypadRoot.buttonColor
|
|
||||||
|
delegate: AbstractButton {
|
||||||
|
id: button
|
||||||
|
implicitWidth: keypadGrid.cellLength
|
||||||
|
implicitHeight: keypadGrid.cellLength
|
||||||
|
visible: modelData.length > 0
|
||||||
|
enabled: root.openProgress >= 0.8 // Only enable after a certain point in animation
|
||||||
|
|
||||||
|
opacity: enabled
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 100 * index }
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
readonly property real restingOpacity: (modelData !== "R" && modelData !== "E") ? 0.2 : 0.0
|
||||||
|
radius: width
|
||||||
|
color: Qt.rgba(255, 255, 255,
|
||||||
|
button.pressed ? 0.5 : restingOpacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressedChanged: {
|
||||||
|
if (pressed) {
|
||||||
|
haptics.buttonVibrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (modelData === "R") {
|
||||||
|
passwordBar.backspace();
|
||||||
|
} else if (modelData === "E") {
|
||||||
|
passwordBar.enter();
|
||||||
|
} else {
|
||||||
|
passwordBar.keyPress(modelData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressAndHold: {
|
||||||
|
if (modelData === "R") {
|
||||||
|
haptics.buttonVibrate();
|
||||||
|
passwordBar.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
PlasmaComponents.Label {
|
||||||
|
visible: modelData !== "R" && modelData !== "E"
|
||||||
|
text: modelData
|
||||||
|
anchors.centerIn: parent
|
||||||
|
font.pointSize: 18
|
||||||
|
color: 'white'
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.enabled: true
|
Kirigami.Icon {
|
||||||
layer.effect: MultiEffect {
|
visible: modelData === "R" || modelData === "E"
|
||||||
blurMax: 16
|
anchors.centerIn: parent
|
||||||
shadowEnabled: true
|
width: Kirigami.Units.iconSizes.small
|
||||||
shadowVerticalOffset: 1
|
height: Kirigami.Units.iconSizes.small
|
||||||
shadowOpacity: 0.3
|
source: modelData === "R" ? "edit-clear" : "go-next"
|
||||||
shadowColor: keypadRoot.dropShadowColor
|
Kirigami.Theme.inherit: false
|
||||||
}
|
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||||
|
|
||||||
onPressedChanged: {
|
|
||||||
if (pressed) {
|
|
||||||
haptics.buttonVibrate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (modelData === "R") {
|
|
||||||
passwordBar.backspace();
|
|
||||||
} else if (modelData === "E") {
|
|
||||||
passwordBar.enter();
|
|
||||||
} else {
|
|
||||||
passwordBar.keyPress(modelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPressAndHold: {
|
|
||||||
if (modelData === "R") {
|
|
||||||
haptics.buttonVibrate();
|
|
||||||
passwordBar.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Item {
|
|
||||||
PlasmaComponents.Label {
|
|
||||||
visible: modelData !== "R" && modelData !== "E"
|
|
||||||
text: modelData
|
|
||||||
anchors.centerIn: parent
|
|
||||||
font.pointSize: 18
|
|
||||||
font.weight: Font.Light
|
|
||||||
color: keypadRoot.buttonTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
visible: modelData === "R"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Kirigami.Units.iconSizes.small
|
|
||||||
height: Kirigami.Units.iconSizes.small
|
|
||||||
source: "edit-clear"
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
visible: modelData === "E"
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: Kirigami.Units.iconSizes.small
|
|
||||||
height: Kirigami.Units.iconSizes.small
|
|
||||||
source: "go-next"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
// SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
|
// SPDX-FileCopyrightText: 2021-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
@ -19,47 +19,56 @@ import org.kde.kirigami 2.12 as Kirigami
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var lockScreenState: LockScreenState {}
|
readonly property var lockScreenState: LockScreenState {}
|
||||||
property var notifModel: Notifications.WatchedNotificationsModel {}
|
readonly property var notifModel: Notifications.WatchedNotificationsModel {}
|
||||||
|
|
||||||
// only show widescreen mode for short height devices (ex. phone landscape)
|
// Only show widescreen mode for short height devices (ex. phone landscape)
|
||||||
property bool isWidescreen: root.height < 720 && (root.height < root.width * 0.75)
|
readonly property bool isWidescreen: root.height < 720 && (root.height < root.width * 0.75)
|
||||||
property bool notificationsShown: false
|
property bool notificationsShown: false
|
||||||
|
|
||||||
readonly property bool drawerOpen: flickable.openFactor >= 1
|
property var passwordBar: keypad.passwordBar
|
||||||
property var passwordBar: keypadLoader.item.passwordBar
|
|
||||||
|
|
||||||
// listen for keyboard events, and focus on input area
|
Component.onCompleted: {
|
||||||
Component.onCompleted: forceActiveFocus();
|
forceActiveFocus();
|
||||||
|
|
||||||
|
// Go to closed position when loaded
|
||||||
|
flickable.position = 0;
|
||||||
|
flickable.goToClosePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for keyboard events, and focus on input area
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
passwordBar.isPinMode = false;
|
root.lockScreenState.isKeyboardMode = true;
|
||||||
flickable.goToOpenPosition();
|
flickable.goToOpenPosition();
|
||||||
passwordBar.textField.forceActiveFocus();
|
passwordBar.textField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// wallpaper blur
|
// Wallpaper blur
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
sourceComponent: WallpaperBlur {
|
sourceComponent: WallpaperBlur {
|
||||||
source: wallpaper
|
source: wallpaper
|
||||||
shouldBlur: root.notificationsShown || root.drawerOpen // only blur once animation finished for performance
|
opacity: flickable.openFactor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.lockScreenState
|
target: root.lockScreenState
|
||||||
|
|
||||||
// ensure keypad is opened when password is updated (ex. keyboard)
|
// Ensure keypad is opened when password is updated (ex. keyboard)
|
||||||
function onPasswordChanged() {
|
function onPasswordChanged() {
|
||||||
flickable.goToOpenPosition()
|
if (root.lockScreenState.password !== "") {
|
||||||
|
flickable.goToOpenPosition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: lockscreenContainer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
// header bar and action drawer
|
// Header bar and action drawer
|
||||||
Loader {
|
Loader {
|
||||||
id: headerBarLoader
|
id: headerBarLoader
|
||||||
z: 1 // on top of flick area
|
z: 1 // on top of flick area
|
||||||
|
|
@ -80,67 +89,35 @@ Item {
|
||||||
id: flickable
|
id: flickable
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
property real openFactor: position / keypadHeight
|
// Distance to swipe to fully open keypad
|
||||||
|
|
||||||
onOpened: {
|
|
||||||
if (root.lockScreenState.passwordless) {
|
|
||||||
// try unlocking if flicked to the top, and we have passwordless login
|
|
||||||
root.lockScreenState.tryPassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keypadHeight: Kirigami.Units.gridUnit * 20
|
keypadHeight: Kirigami.Units.gridUnit * 20
|
||||||
|
|
||||||
// go to closed position when loaded
|
// Clear entered password after closing keypad
|
||||||
Component.onCompleted: {
|
onOpenFactorChanged: {
|
||||||
flickable.position = 0;
|
if (flickable.openFactor < 0.1) {
|
||||||
flickable.goToClosePosition();
|
root.passwordBar.clear();
|
||||||
}
|
|
||||||
|
|
||||||
// update position, and cap it at the keypad height
|
|
||||||
onPositionChanged: {
|
|
||||||
if (position > keypadHeight) {
|
|
||||||
position = keypadHeight;
|
|
||||||
} else if (position < 0) {
|
|
||||||
position = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LockScreenNarrowContent {
|
LockScreenContent {
|
||||||
id: phoneComponent
|
id: lockScreenContent
|
||||||
|
|
||||||
visible: !isWidescreen
|
isVertical: !root.isWidescreen
|
||||||
active: visible
|
opacity: Math.max(0, 1 - flickable.openFactor * 2)
|
||||||
opacity: 1 - flickable.openFactor
|
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
|
fullHeight: root.height
|
||||||
|
|
||||||
lockScreenState: root.lockScreenState
|
lockScreenState: root.lockScreenState
|
||||||
notificationsModel: root.notifModel
|
notificationsModel: root.notifModel
|
||||||
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
||||||
|
|
||||||
onPasswordRequested: flickable.goToOpenPosition()
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
// move while swiping up
|
|
||||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
LockScreenWideScreenContent {
|
|
||||||
id: tabletComponent
|
|
||||||
|
|
||||||
visible: isWidescreen
|
|
||||||
active: visible
|
|
||||||
opacity: 1 - flickable.openFactor
|
|
||||||
|
|
||||||
lockScreenState: root.lockScreenState
|
|
||||||
notificationsModel: root.notifModel
|
|
||||||
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
|
||||||
|
|
||||||
onPasswordRequested: flickable.goToOpenPosition()
|
onPasswordRequested: flickable.goToOpenPosition()
|
||||||
|
|
||||||
anchors.topMargin: headerBarLoader.statusBarHeight
|
anchors.topMargin: headerBarLoader.statusBarHeight
|
||||||
|
|
@ -148,75 +125,36 @@ Item {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
// move while swiping up
|
|
||||||
transform: Translate { y: Math.round((1 - phoneComponent.opacity) * (-root.height / 6)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// scroll up icon
|
// scroll up icon
|
||||||
BottomIconIndicator {
|
BottomIconIndicator {
|
||||||
id: scrollUpIconLoader
|
id: scrollUpIconLoader
|
||||||
lockScreenState: root.lockScreenState
|
lockScreenState: root.lockScreenState
|
||||||
|
opacity: Math.max(0, 1 - flickable.openFactor * 2)
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.5
|
anchors.bottomMargin: Kirigami.Units.gridUnit + flickable.position * 0.1
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
// password keypad
|
Rectangle {
|
||||||
Loader {
|
id: keypadScrim
|
||||||
id: keypadLoader
|
anchors.fill: parent
|
||||||
width: parent.width
|
visible: opacity > 0
|
||||||
asynchronous: true
|
opacity: flickable.openFactor
|
||||||
active: !root.lockScreenState.passwordless // only load keypad if not passwordless
|
color: Qt.rgba(0, 0, 0, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
Keypad {
|
||||||
|
id: keypad
|
||||||
|
anchors.fill: parent
|
||||||
|
openProgress: flickable.openFactor
|
||||||
|
lockScreenState: root.lockScreenState
|
||||||
|
|
||||||
sourceComponent: ColumnLayout {
|
// only show in last 50% of anim
|
||||||
property alias passwordBar: keypad.passwordBar
|
opacity: (flickable.openFactor - 0.5) * 2
|
||||||
|
transform: Translate { y: (flickable.keypadHeight - flickable.position) * 0.1 }
|
||||||
transform: Translate { y: flickable.keypadHeight - flickable.position }
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// info notification text
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2
|
|
||||||
font.pointSize: 9
|
|
||||||
|
|
||||||
elide: Text.ElideRight
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: root.lockScreenState.info
|
|
||||||
opacity: (root.lockScreenState.info.length === 0 || flickable.openFactor < 1) ? 0 : 1
|
|
||||||
color: 'white'
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scroll down icon
|
|
||||||
Kirigami.Icon {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: Kirigami.Units.gridUnit
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.small
|
|
||||||
implicitHeight: Kirigami.Units.iconSizes.small
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
|
||||||
source: "arrow-down"
|
|
||||||
opacity: Math.sin((Math.PI / 2) * flickable.openFactor + 1.5 * Math.PI) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Keypad {
|
|
||||||
id: keypad
|
|
||||||
Layout.fillWidth: true
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
lockScreenState: root.lockScreenState
|
|
||||||
swipeProgress: flickable.openFactor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
132
shell/contents/lockscreen/LockScreenContent.qml
Normal file
132
shell/contents/lockscreen/LockScreenContent.qml
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021-2024 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.plasma.workspace.keyboardlayout 1.0
|
||||||
|
import org.kde.notificationmanager as Notifications
|
||||||
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var lockScreenState
|
||||||
|
required property bool isVertical
|
||||||
|
|
||||||
|
property var notificationsModel: []
|
||||||
|
property bool notificationsShown: false
|
||||||
|
|
||||||
|
property real fullHeight
|
||||||
|
|
||||||
|
signal passwordRequested()
|
||||||
|
|
||||||
|
// Vertical layout
|
||||||
|
ColumnLayout {
|
||||||
|
id: verticalLayout
|
||||||
|
visible: root.isVertical
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// center clock when no notifications are shown, otherwise move the clock upward
|
||||||
|
anchors.topMargin: !root.notificationsShown ? Math.round(root.fullHeight / 2 - (verticalLayout.implicitHeight / 2)) : Kirigami.Units.gridUnit * 5
|
||||||
|
anchors.bottomMargin: Kirigami.Units.gridUnit
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// animate
|
||||||
|
Behavior on anchors.topMargin {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Kirigami.Units.veryLongDuration
|
||||||
|
easing.type: Easing.InOutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItemProxy { target: clockAndMediaWidget }
|
||||||
|
LayoutItemProxy { target: notificationComponent }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal layout (landscape on smaller devices)
|
||||||
|
Item {
|
||||||
|
id: horizontalLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: !root.isVertical
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: leftLayout
|
||||||
|
width: parent.width / 2
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Kirigami.Units.gridUnit * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItemProxy { target: clockAndMediaWidget }
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: rightLayout
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
bottom: parent.bottom
|
||||||
|
left: leftLayout.right
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Kirigami.Units.gridUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItemProxy { target: notificationComponent }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock and media widget column
|
||||||
|
ColumnLayout {
|
||||||
|
id: clockAndMediaWidget
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: root.isVertical
|
||||||
|
spacing: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
Clock {
|
||||||
|
layoutAlignment: root.isVertical ? Qt.AlignHCenter : Qt.AlignLeft
|
||||||
|
Layout.alignment: root.isVertical ? Qt.AlignHCenter : Qt.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: root.isVertical ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileShell.MediaControlsWidget {
|
||||||
|
Layout.alignment: root.isVertical ? Qt.AlignHCenter : Qt.AlignLeft
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
|
||||||
|
Layout.leftMargin: root.isVertical ? Kirigami.Units.gridUnit : 0
|
||||||
|
Layout.rightMargin: root.isVertical ? Kirigami.Units.gridUnit : 0
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
blurMax: 16
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowVerticalOffset: 1
|
||||||
|
shadowOpacity: 0.5
|
||||||
|
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationsComponent {
|
||||||
|
id: notificationComponent
|
||||||
|
lockScreenState: root.lockScreenState
|
||||||
|
notificationsModel: root.notificationsModel
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * (25 + 2) // clip margins
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
onPasswordRequested: root.passwordRequested()
|
||||||
|
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <espidev@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Effects
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.20 as Kirigami
|
|
||||||
import org.kde.plasma.workspace.keyboardlayout 1.0
|
|
||||||
import org.kde.notificationmanager as Notifications
|
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var lockScreenState
|
|
||||||
|
|
||||||
property var notificationsModel: []
|
|
||||||
property bool notificationsShown: false
|
|
||||||
|
|
||||||
property real fullHeight
|
|
||||||
|
|
||||||
signal passwordRequested()
|
|
||||||
|
|
||||||
// avoid topMargin animation when item is being loaded
|
|
||||||
onLoaded: loadTimer.restart();
|
|
||||||
Timer {
|
|
||||||
id: loadTimer
|
|
||||||
interval: 200
|
|
||||||
}
|
|
||||||
|
|
||||||
// move while swiping up
|
|
||||||
transform: Translate { y: Math.round((1 - root.opacity) * (-root.height / 6)) }
|
|
||||||
|
|
||||||
asynchronous: true
|
|
||||||
sourceComponent: Item {
|
|
||||||
ColumnLayout {
|
|
||||||
id: column
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// center clock when no notifications are shown, otherwise move the clock upward
|
|
||||||
anchors.topMargin: !root.notificationsShown ? Math.round(root.fullHeight / 2 - (column.implicitHeight / 2)) : Kirigami.Units.gridUnit * 5
|
|
||||||
anchors.bottomMargin: Kirigami.Units.gridUnit
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
// animate
|
|
||||||
Behavior on anchors.topMargin {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: loadTimer.running ? 0 : Kirigami.Units.veryLongDuration
|
|
||||||
easing.type: Easing.InOutExpo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Clock {
|
|
||||||
layoutAlignment: Qt.AlignHCenter
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: Kirigami.Units.gridUnit * 2 // keep spacing even if media controls are gone
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileShell.MediaControlsWidget {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
|
|
||||||
Layout.leftMargin: Kirigami.Units.gridUnit
|
|
||||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
blurMax: 16
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowVerticalOffset: 1
|
|
||||||
shadowOpacity: 0.5
|
|
||||||
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationsComponent {
|
|
||||||
id: notificationComponent
|
|
||||||
lockScreenState: root.lockScreenState
|
|
||||||
notificationsModel: root.notificationsModel
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * (25 + 2) // clip margins
|
|
||||||
topMargin: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
onPasswordRequested: root.passwordRequested()
|
|
||||||
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
|
// SPDX-FileCopyrightText: 2022-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQml
|
import QtQml
|
||||||
|
|
@ -24,15 +24,24 @@ QtObject {
|
||||||
// whether the device can log in with fingerprint
|
// whether the device can log in with fingerprint
|
||||||
readonly property bool isFingerprintSupported: authenticator.authenticatorTypes & ScreenLocker.Authenticator.Fingerprint
|
readonly property bool isFingerprintSupported: authenticator.authenticatorTypes & ScreenLocker.Authenticator.Fingerprint
|
||||||
|
|
||||||
|
// whether we are in keyboard mode (hiding the numpad)
|
||||||
|
property bool isKeyboardMode: false
|
||||||
|
|
||||||
|
property string pinLabel: enterPinLabel
|
||||||
|
readonly property string enterPinLabel: i18n("Enter PIN")
|
||||||
|
readonly property string wrongPinLabel: i18n("Wrong PIN")
|
||||||
|
|
||||||
signal reset()
|
signal reset()
|
||||||
signal unlockSucceeded()
|
signal unlockSucceeded()
|
||||||
signal unlockFailed()
|
signal unlockFailed()
|
||||||
|
|
||||||
|
Component.onCompleted: authenticator.startAuthenticating();
|
||||||
|
|
||||||
function tryPassword() {
|
function tryPassword() {
|
||||||
if (root.password !== '') { // prevent typing lock when password is empty
|
if (root.password !== '') { // prevent typing lock when password is empty
|
||||||
waitingForAuth = true;
|
root.waitingForAuth = true;
|
||||||
}
|
}
|
||||||
authenticator.startAuthenticating();
|
authenticator.respond(root.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetPassword() {
|
function resetPassword() {
|
||||||
|
|
@ -40,6 +49,19 @@ QtObject {
|
||||||
root.reset();
|
root.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetPinLabel(): void {
|
||||||
|
pinLabel = enterPinLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
property var graceLockTimer: Timer {
|
||||||
|
interval: 1000
|
||||||
|
onTriggered: {
|
||||||
|
root.waitingForAuth = false;
|
||||||
|
root.password = "";
|
||||||
|
authenticator.startAuthenticating();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property var connections: Connections {
|
property var connections: Connections {
|
||||||
target: authenticator
|
target: authenticator
|
||||||
|
|
||||||
|
|
@ -50,10 +72,13 @@ QtObject {
|
||||||
Qt.quit();
|
Qt.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFailed() {
|
function onFailed(kind: int): void {
|
||||||
|
if (kind != 0) { // if this is coming from the noninteractive authenticators
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log('login failed');
|
console.log('login failed');
|
||||||
root.waitingForAuth = false;
|
graceLockTimer.restart();
|
||||||
root.password = "";
|
root.pinLabel = root.wrongPinLabel;
|
||||||
root.unlockFailed();
|
root.unlockFailed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,11 +99,6 @@ QtObject {
|
||||||
|
|
||||||
function onPromptForSecretChanged() {
|
function onPromptForSecretChanged() {
|
||||||
console.log('prompt secret: ' + authenticator.promptForSecret);
|
console.log('prompt secret: ' + authenticator.promptForSecret);
|
||||||
if (root.password !== "") {
|
|
||||||
authenticator.respond(root.password);
|
|
||||||
authenticator.startAuthenticating();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Effects
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.20 as Kirigami
|
|
||||||
import org.kde.plasma.workspace.keyboardlayout 1.0
|
|
||||||
import org.kde.notificationmanager as Notifications
|
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property var lockScreenState
|
|
||||||
|
|
||||||
property var notificationsModel: []
|
|
||||||
property bool notificationsShown: false
|
|
||||||
|
|
||||||
signal passwordRequested()
|
|
||||||
|
|
||||||
asynchronous: true
|
|
||||||
sourceComponent: Item {
|
|
||||||
Item {
|
|
||||||
id: clock
|
|
||||||
width: parent.width / 2
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Kirigami.Units.gridUnit * 3
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: tabletLayout
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
Clock {
|
|
||||||
layoutAlignment: Qt.AlignLeft
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileShell.MediaControlsWidget {
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
blurMax: 16
|
|
||||||
shadowEnabled: true
|
|
||||||
shadowVerticalOffset: 1
|
|
||||||
shadowOpacity: 0.5
|
|
||||||
shadowColor: Qt.lighter(Kirigami.Theme.backgroundColor, 0.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tablet notifications list
|
|
||||||
ColumnLayout {
|
|
||||||
id: tabletNotificationsList
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
|
||||||
left: clock.right
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: Kirigami.Units.gridUnit
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationsComponent {
|
|
||||||
id: notificationComponent
|
|
||||||
lockScreenState: root.lockScreenState
|
|
||||||
notificationsModel: root.notificationsModel
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.topMargin: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.bottomMargin: Kirigami.Units.gridUnit
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 15
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 25
|
|
||||||
|
|
||||||
leftMargin: Kirigami.Units.gridUnit
|
|
||||||
rightMargin: Kirigami.Units.gridUnit
|
|
||||||
bottomMargin: Kirigami.Units.gridUnit
|
|
||||||
topMargin: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
onPasswordRequested: root.passwordRequested()
|
|
||||||
onNotificationsShownChanged: root.notificationsShown = notificationsShown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com>
|
// SPDX-FileCopyrightText: 2020-2024 Devin Lin <devin@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
@ -12,24 +12,20 @@ import org.kde.kirigami 2.12 as Kirigami
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 2.5
|
|
||||||
|
|
||||||
required property var lockScreenState
|
required property var lockScreenState
|
||||||
|
|
||||||
property alias textField: textField
|
property alias textField: textField
|
||||||
|
|
||||||
// toggle between pin and password mode
|
required property bool isKeypadOpen
|
||||||
property bool isPinMode: true
|
|
||||||
|
|
||||||
// for displaying temporary number in pin dot display
|
// for displaying temporary number in pin dot display
|
||||||
property int previewCharIndex: -2
|
property int previewCharIndex: -2
|
||||||
|
|
||||||
property string pinLabel: i18n("Enter PIN")
|
readonly property color headerTextColor: Qt.rgba(255, 255, 255, 1)
|
||||||
|
readonly property color headerTextInactiveColor: Qt.rgba(255, 255, 255, 0.4)
|
||||||
|
|
||||||
property bool keypadOpen
|
radius: Kirigami.Units.largeSpacing
|
||||||
|
color: Qt.rgba(255, 255, 255, 0.3)
|
||||||
readonly property color headerTextColor: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.textColor, {"alpha": 0.75*255})
|
|
||||||
readonly property color headerTextInactiveColor: Kirigami.ColorUtils.adjustColor(Kirigami.Theme.textColor, {"alpha": 0.4*255})
|
|
||||||
|
|
||||||
// model for shown dots
|
// model for shown dots
|
||||||
// we need to use a listmodel to avoid all delegates from reloading
|
// we need to use a listmodel to avoid all delegates from reloading
|
||||||
|
|
@ -37,17 +33,10 @@ Rectangle {
|
||||||
id: dotDisplayModel
|
id: dotDisplayModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen to lockscreen state changes
|
||||||
Connections {
|
Connections {
|
||||||
target: root.lockScreenState
|
target: root.lockScreenState
|
||||||
|
|
||||||
function onUnlockSucceeded() {
|
|
||||||
root.pinLabel = i18n("Logging in...");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUnlockFailed() {
|
|
||||||
root.pinLabel = i18n("Wrong PIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPasswordChanged() {
|
function onPasswordChanged() {
|
||||||
while (root.lockScreenState.password.length < dotDisplayModel.count) {
|
while (root.lockScreenState.password.length < dotDisplayModel.count) {
|
||||||
dotDisplayModel.remove(dotDisplayModel.count - 1);
|
dotDisplayModel.remove(dotDisplayModel.count - 1);
|
||||||
|
|
@ -58,7 +47,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// keypad functions
|
// Keypad functions
|
||||||
function backspace() {
|
function backspace() {
|
||||||
if (!lockScreenState.waitingForAuth) {
|
if (!lockScreenState.waitingForAuth) {
|
||||||
root.previewCharIndex = -2;
|
root.previewCharIndex = -2;
|
||||||
|
|
@ -76,7 +65,7 @@ Rectangle {
|
||||||
function enter() {
|
function enter() {
|
||||||
lockScreenState.tryPassword();
|
lockScreenState.tryPassword();
|
||||||
|
|
||||||
if (keypadOpen && !isPinMode) {
|
if (root.isKeypadOpen && root.lockScreenState.isKeyboardMode) {
|
||||||
// make sure keyboard doesn't close
|
// make sure keyboard doesn't close
|
||||||
openKeyboardTimer.restart();
|
openKeyboardTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
@ -84,10 +73,7 @@ Rectangle {
|
||||||
|
|
||||||
function keyPress(data) {
|
function keyPress(data) {
|
||||||
if (!lockScreenState.waitingForAuth) {
|
if (!lockScreenState.waitingForAuth) {
|
||||||
|
root.lockScreenState.resetPinLabel();
|
||||||
if (root.pinLabel !== i18n("Enter PIN")) {
|
|
||||||
root.pinLabel = i18n("Enter PIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
root.previewCharIndex = lockScreenState.password.length;
|
root.previewCharIndex = lockScreenState.password.length;
|
||||||
lockScreenState.password += data
|
lockScreenState.password += data
|
||||||
|
|
@ -121,7 +107,7 @@ Rectangle {
|
||||||
TextField {
|
TextField {
|
||||||
id: textField
|
id: textField
|
||||||
visible: false
|
visible: false
|
||||||
focus: keypadOpen && !isPinMode
|
focus: root.isKeypadOpen && root.lockScreenState.isKeyboardMode
|
||||||
z: 1
|
z: 1
|
||||||
inputMethodHints: Qt.ImhNoPredictiveText
|
inputMethodHints: Qt.ImhNoPredictiveText
|
||||||
|
|
||||||
|
|
@ -170,7 +156,7 @@ Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// clicking on rectangle opens keyboard if not already open
|
// clicking on rectangle opens keyboard if not already open
|
||||||
if (!isPinMode) {
|
if (root.lockScreenState.isKeyboardMode) {
|
||||||
Keyboards.KWinVirtualKeyboard.active = true;
|
Keyboards.KWinVirtualKeyboard.active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,28 +168,15 @@ Rectangle {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
implicitWidth: height
|
implicitWidth: height
|
||||||
icon.name: root.isPinMode ? "input-keyboard-virtual-symbolic" : "input-dialpad-symbolic"
|
icon.name: root.lockScreenState.isKeyboardMode ? "input-dialpad-symbolic" : "input-keyboard-virtual-symbolic"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.isPinMode = !root.isPinMode;
|
root.lockScreenState.isKeyboardMode = !root.lockScreenState.isKeyboardMode;
|
||||||
if (!root.isPinMode) {
|
if (root.lockScreenState.isKeyboardMode) {
|
||||||
Keyboards.KWinVirtualKeyboard.active = true;
|
Keyboards.KWinVirtualKeyboard.active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// label ("wrong pin", "enter pin")
|
|
||||||
Label {
|
|
||||||
opacity: root.lockScreenState.password.length === 0 ? 1 : 0
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: root.pinLabel
|
|
||||||
font.pointSize: 12
|
|
||||||
color: root.headerTextColor
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation { duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pin dot display
|
// pin dot display
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,4 @@ MultiEffect {
|
||||||
blurEnabled: true
|
blurEnabled: true
|
||||||
blurMax: 50
|
blurMax: 50
|
||||||
blur: 1.0
|
blur: 1.0
|
||||||
|
|
||||||
property bool shouldBlur
|
|
||||||
opacity: shouldBlur ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 1000
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue