shift-shell/shell/contents/lockscreen/PasswordBar.qml

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

290 lines
9.2 KiB
QML
Raw Normal View History

// SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
2021-04-11 04:26:30 +00:00
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
2021-04-11 04:26:30 +00:00
import org.kde.plasma.workspace.keyboardlayout 1.0
import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards
2021-04-11 04:26:30 +00:00
import org.kde.kirigami 2.12 as Kirigami
2021-04-11 04:26:30 +00:00
Rectangle {
id: root
implicitHeight: Kirigami.Units.gridUnit * 2.5
2021-04-11 04:26:30 +00:00
required property var lockScreenState
property alias textField: textField
2021-04-11 04:26:30 +00:00
// toggle between pin and password mode
property bool isPinMode: true
2021-04-11 15:46:50 +00:00
// for displaying temporary number in pin dot display
property int previewCharIndex: -2
2021-04-11 15:46:50 +00:00
property string pinLabel: i18n("Enter PIN")
2021-04-11 04:26:30 +00:00
property bool keypadOpen
2021-04-11 15:46:50 +00:00
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})
2021-04-11 04:26:30 +00:00
// model for shown dots
// we need to use a listmodel to avoid all delegates from reloading
ListModel {
id: dotDisplayModel
}
2021-04-11 15:46:50 +00:00
Connections {
target: root.lockScreenState
function onUnlockSucceeded() {
root.pinLabel = i18n("Logging in...");
}
function onUnlockFailed() {
root.pinLabel = i18n("Wrong PIN");
}
function onPasswordChanged() {
while (root.lockScreenState.password.length < dotDisplayModel.count) {
dotDisplayModel.remove(dotDisplayModel.count - 1);
}
while (root.lockScreenState.password.length > dotDisplayModel.count) {
dotDisplayModel.append({"char": root.lockScreenState.password.charAt(dotDisplayModel.count)});
}
}
2021-04-11 15:46:50 +00:00
}
// keypad functions
2021-04-11 15:46:50 +00:00
function backspace() {
if (!lockScreenState.waitingForAuth) {
2021-04-11 15:46:50 +00:00
root.previewCharIndex = -2;
lockScreenState.password = lockScreenState.password.substr(0, lockScreenState.password.length - 1);
2021-04-11 15:46:50 +00:00
}
}
function clear() {
if (!lockScreenState.waitingForAuth) {
2021-04-11 15:46:50 +00:00
root.previewCharIndex = -2;
lockScreenState.resetPassword();
2021-04-11 15:46:50 +00:00
}
}
function enter() {
lockScreenState.tryPassword();
2021-04-11 15:46:50 +00:00
if (keypadOpen && !isPinMode) {
// make sure keyboard doesn't close
openKeyboardTimer.restart();
}
2021-04-11 15:46:50 +00:00
}
function keyPress(data) {
if (!lockScreenState.waitingForAuth) {
if (root.pinLabel !== i18n("Enter PIN")) {
root.pinLabel = i18n("Enter PIN");
2021-04-11 15:46:50 +00:00
}
root.previewCharIndex = lockScreenState.password.length;
lockScreenState.password += data
2021-04-11 15:46:50 +00:00
// trigger turning letter into dot later
letterTimer.restart();
}
}
// HACK: we have to open the virtual keyboard after a certain amount of time or else it will close anyway
Timer {
id: openKeyboardTimer
interval: 10
running: false
repeat: false
onTriggered: Keyboards.KWinVirtualKeyboard.active = true
}
2021-04-11 15:46:50 +00:00
// trigger turning letter into dot after 500 milliseconds
Timer {
id: letterTimer
interval: 500
running: false
repeat: false
onTriggered: {
root.previewCharIndex = -2;
}
}
2021-04-11 04:26:30 +00:00
// hidden textfield so that the virtual keyboard shows up
TextField {
id: textField
2021-04-11 15:46:50 +00:00
visible: false
2021-04-11 04:26:30 +00:00
focus: keypadOpen && !isPinMode
z: 1
inputMethodHints: Qt.ImhNoPredictiveText
2021-04-11 15:46:50 +00:00
onFocusChanged: {
if (focus) {
Keyboards.KWinVirtualKeyboard.active = true;
}
}
2021-04-11 15:46:50 +00:00
property bool externalEdit: false
property string prevText: ""
Connections {
target: root.lockScreenState
function onPasswordChanged() {
if (textField.text != root.lockScreenState.password) {
2021-04-11 15:46:50 +00:00
textField.externalEdit = true;
textField.text = root.lockScreenState.password;
2021-04-11 15:46:50 +00:00
}
}
}
2021-04-11 15:46:50 +00:00
onEditingFinished: {
if (textField.focus) {
root.enter();
}
}
2021-04-11 15:46:50 +00:00
onTextChanged: {
if (!externalEdit) {
if (prevText.length > text.length) { // backspace
for (let i = 0; i < (prevText.length - text.length); i++) {
root.backspace();
}
} else if (text.length > 0) { // key enter
root.keyPress(text.charAt(text.length - 1));
}
prevText = text;
}
externalEdit = false;
}
2021-04-11 04:26:30 +00:00
}
MouseArea {
anchors.fill: parent
onClicked: {
// clicking on rectangle opens keyboard if not already open
if (!isPinMode) {
Keyboards.KWinVirtualKeyboard.active = true;
}
}
2021-04-11 04:26:30 +00:00
// toggle between showing keypad and not
ToolButton {
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: Kirigami.Units.smallSpacing
implicitWidth: height
icon.name: root.isPinMode ? "input-keyboard-virtual-symbolic" : "input-dialpad-symbolic"
onClicked: {
root.isPinMode = !root.isPinMode;
if (!root.isPinMode) {
Keyboards.KWinVirtualKeyboard.active = true;
}
}
2021-04-11 04:26:30 +00:00
}
// 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
2021-04-11 04:26:30 +00:00
Behavior on opacity {
NumberAnimation { duration: 200 }
2021-04-11 04:26:30 +00:00
}
}
// pin dot display
ColumnLayout {
anchors.fill: parent
2021-04-11 04:26:30 +00:00
ListView {
id: dotDisplay
2023-10-23 06:42:38 +00:00
property int dotWidth: 6
2021-04-11 04:26:30 +00:00
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.bottomMargin: Math.round(dotWidth / 2)
2023-10-23 06:42:38 +00:00
implicitHeight: dotWidth
implicitWidth: count * dotWidth + spacing * (count - 1)
2023-10-23 06:42:38 +00:00
orientation: ListView.Horizontal
spacing: 8
model: dotDisplayModel
2021-04-11 04:26:30 +00:00
Behavior on implicitWidth {
NumberAnimation { duration: 50 }
2021-04-11 04:26:30 +00:00
}
2023-10-23 06:42:38 +00:00
delegate: Item {
2023-10-23 06:42:38 +00:00
width: dotDisplay.dotWidth
height: dotDisplay.dotWidth
property bool showChar: index === root.previewCharIndex
2021-04-11 04:26:30 +00:00
Component.onCompleted: {
if (showChar) {
charAnimation.to = 1;
charAnimation.duration = 75;
charAnimation.restart();
} else {
dotAnimation.to = 1;
dotAnimation.restart();
}
}
onShowCharChanged: {
if (!showChar) {
charAnimation.to = 0;
charAnimation.duration = 50;
charAnimation.restart();
dotAnimation.to = 1;
dotAnimation.start();
}
}
Rectangle { // dot
id: dot
scale: 0
anchors.fill: parent
radius: width
color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth
PropertyAnimation {
id: dotAnimation
target: dot;
property: "scale";
duration: 50
}
2021-04-11 04:26:30 +00:00
}
Label { // number/letter
id: charLabel
scale: 0
anchors.centerIn: parent
color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth
text: model.char
font.pointSize: 12
PropertyAnimation {
id: charAnimation
target: charLabel;
property: "scale";
}
2021-04-11 04:26:30 +00:00
}
}
}
}
}
}