/* * SPDX-FileCopyrightText: 2020-2022 Devin Lin * SPDX-License-Identifier: GPL-2.0-or-later */ import QtQuick 2.12 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.12 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.workspace.keyboardlayout 1.0 import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards import org.kde.kirigami 2.12 as Kirigami Rectangle { id: root implicitHeight: PlasmaCore.Units.gridUnit * 2.5 required property var lockScreenState // toggle between pin and password mode property bool isPinMode: true // for displaying temporary number in pin dot display property int previewCharIndex: -2 property string pinLabel: qsTr("Enter PIN") property bool keypadOpen readonly property color headerTextColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.75*255}) readonly property color headerTextInactiveColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.4*255}) // model for shown dots // we need to use a listmodel to avoid all delegates from reloading ListModel { id: dotDisplayModel } Connections { target: root.lockScreenState function onUnlockSucceeded() { root.pinLabel = qsTr("Logging in..."); } function onUnlockFailed() { root.pinLabel = qsTr("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)}); } } } // keypad functions function backspace() { if (!lockScreenState.waitingForAuth) { root.previewCharIndex = -2; lockScreenState.password = lockScreenState.password.substr(0, lockScreenState.password.length - 1); } } function clear() { if (!lockScreenState.waitingForAuth) { root.previewCharIndex = -2; lockScreenState.resetPassword(); } } function enter() { lockScreenState.tryPassword(); if (keypadOpen && !isPinMode) { // make sure keyboard doesn't close openKeyboardTimer.restart(); } } function keyPress(data) { if (!lockScreenState.waitingForAuth) { if (root.pinLabel !== qsTr("Enter PIN")) { root.pinLabel = qsTr("Enter PIN"); } root.previewCharIndex = lockScreenState.password.length; lockScreenState.password += data // 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 } // trigger turning letter into dot after 500 milliseconds Timer { id: letterTimer interval: 500 running: false repeat: false onTriggered: { root.previewCharIndex = -2; } } // hidden textfield so that the virtual keyboard shows up TextField { id: textField visible: false focus: keypadOpen && !isPinMode z: 1 inputMethodHints: Qt.ImhNoPredictiveText onFocusChanged: { if (focus) { Keyboards.KWinVirtualKeyboard.active = true; } } property bool externalEdit: false property string prevText: "" Connections { target: root.lockScreenState function onPasswordChanged() { if (textField.text != root.lockScreenState.password) { textField.externalEdit = true; textField.text = root.lockScreenState.password; } } } onEditingFinished: { if (textField.focus) { root.enter(); } } 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; } } MouseArea { anchors.fill: parent onClicked: { // clicking on rectangle opens keyboard if not already open if (!isPinMode) { Keyboards.KWinVirtualKeyboard.active = true; } } // toggle between showing keypad and not ToolButton { anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: PlasmaCore.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; } } } // 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 ColumnLayout { anchors.fill: parent ListView { id: dotDisplay property int dotWidth: Math.round(PlasmaCore.Units.gridUnit * 0.35) Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.bottomMargin: Math.round(dotWidth / 2) orientation: ListView.Horizontal implicitWidth: count * dotWidth + spacing * (count - 1) spacing: 8 model: dotDisplayModel Behavior on implicitWidth { NumberAnimation { duration: 50 } } delegate: Item { implicitWidth: dotDisplay.dotWidth implicitHeight: dotDisplay.dotWidth property bool showChar: index === root.previewCharIndex 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 } } 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"; } } } } } } }