Warning, /plasma/plasma-mobile/look-and-feel/contents/lockscreen/PasswordBar.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  * SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com>
0003  * SPDX-License-Identifier: GPL-2.0-or-later
0004  */
0005 
0006 import QtQuick 2.12
0007 import QtQuick.Controls 2.1
0008 import QtQuick.Layouts 1.1
0009 import QtGraphicalEffects 1.12
0010 
0011 import org.kde.plasma.core 2.0 as PlasmaCore
0012 import org.kde.plasma.workspace.keyboardlayout 1.0
0013 import org.kde.plasma.workspace.keyboardlayout 1.0 as Keyboards
0014 
0015 import org.kde.kirigami 2.12 as Kirigami
0016 
0017 Rectangle {
0018     id: root
0019     implicitHeight: PlasmaCore.Units.gridUnit * 2.5
0020     
0021     required property var lockScreenState
0022     
0023     property alias textField: textField
0024     
0025     // toggle between pin and password mode
0026     property bool isPinMode: true
0027     
0028     // for displaying temporary number in pin dot display
0029     property int previewCharIndex: -2
0030     
0031     property string pinLabel: qsTr("Enter PIN")
0032     
0033     property bool keypadOpen
0034     
0035     readonly property color headerTextColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.75*255})
0036     readonly property color headerTextInactiveColor: Kirigami.ColorUtils.adjustColor(PlasmaCore.Theme.textColor, {"alpha": 0.4*255})
0037     
0038     // model for shown dots
0039     // we need to use a listmodel to avoid all delegates from reloading
0040     ListModel {
0041         id: dotDisplayModel
0042     }
0043     
0044     Connections {
0045         target: root.lockScreenState
0046         
0047         function onUnlockSucceeded() {
0048             root.pinLabel = qsTr("Logging in...");
0049         }
0050         
0051         function onUnlockFailed() {
0052             root.pinLabel = qsTr("Wrong PIN");
0053         }
0054         
0055         function onPasswordChanged() {
0056             while (root.lockScreenState.password.length < dotDisplayModel.count) {
0057                 dotDisplayModel.remove(dotDisplayModel.count - 1);
0058             }
0059             while (root.lockScreenState.password.length > dotDisplayModel.count) {
0060                 dotDisplayModel.append({"char": root.lockScreenState.password.charAt(dotDisplayModel.count)});
0061             }
0062         }
0063     }
0064     
0065     // keypad functions
0066     function backspace() {
0067         if (!lockScreenState.waitingForAuth) {
0068             root.previewCharIndex = -2;
0069             lockScreenState.password = lockScreenState.password.substr(0, lockScreenState.password.length - 1);
0070         }
0071     }
0072 
0073     function clear() {
0074         if (!lockScreenState.waitingForAuth) {
0075             root.previewCharIndex = -2;
0076             lockScreenState.resetPassword();
0077         }
0078     }
0079     
0080     function enter() {
0081         lockScreenState.tryPassword();
0082         
0083         if (keypadOpen && !isPinMode) {
0084             // make sure keyboard doesn't close
0085             openKeyboardTimer.restart();
0086         }
0087     }
0088     
0089     function keyPress(data) {
0090         if (!lockScreenState.waitingForAuth) {
0091             
0092             if (root.pinLabel !== qsTr("Enter PIN")) {
0093                 root.pinLabel = qsTr("Enter PIN");
0094             }
0095             
0096             root.previewCharIndex = lockScreenState.password.length;
0097             lockScreenState.password += data
0098             
0099             // trigger turning letter into dot later
0100             letterTimer.restart();
0101         }
0102     }
0103     
0104     // HACK: we have to open the virtual keyboard after a certain amount of time or else it will close anyway
0105     Timer {
0106         id: openKeyboardTimer
0107         interval: 10
0108         running: false
0109         repeat: false
0110         onTriggered: Keyboards.KWinVirtualKeyboard.active = true
0111     }
0112     
0113     // trigger turning letter into dot after 500 milliseconds
0114     Timer {
0115         id: letterTimer
0116         interval: 500
0117         running: false
0118         repeat: false
0119         onTriggered: {
0120             root.previewCharIndex = -2;
0121         }
0122     }
0123     
0124     // hidden textfield so that the virtual keyboard shows up
0125     TextField {
0126         id: textField
0127         visible: false
0128         focus: keypadOpen && !isPinMode
0129         z: 1
0130         inputMethodHints: Qt.ImhNoPredictiveText
0131         
0132         onFocusChanged: {
0133             if (focus) {
0134                 Keyboards.KWinVirtualKeyboard.active = true;
0135             }
0136         }
0137         
0138         property bool externalEdit: false
0139         property string prevText: ""
0140         
0141         Connections {
0142             target: root.lockScreenState
0143 
0144             function onPasswordChanged() {
0145                 if (textField.text != root.lockScreenState.password) {
0146                     textField.externalEdit = true;
0147                     textField.text = root.lockScreenState.password;
0148                 }
0149             }
0150         }
0151         
0152         onEditingFinished: {
0153             if (textField.focus) {
0154                 root.enter();
0155             }
0156         }
0157         
0158         onTextChanged: {
0159             if (!externalEdit) {
0160                 if (prevText.length > text.length) { // backspace
0161                     for (let i = 0; i < (prevText.length - text.length); i++) {
0162                         root.backspace();
0163                     }
0164                 } else if (text.length > 0) { // key enter
0165                     root.keyPress(text.charAt(text.length - 1));
0166                 }
0167                 prevText = text;
0168             }
0169             externalEdit = false;
0170         }
0171     }
0172     
0173     MouseArea {
0174         anchors.fill: parent
0175         onClicked: {
0176             // clicking on rectangle opens keyboard if not already open
0177             if (!isPinMode) {
0178                 Keyboards.KWinVirtualKeyboard.active = true;
0179             }
0180         }
0181         
0182         // toggle between showing keypad and not
0183         ToolButton {
0184             anchors.right: parent.right
0185             anchors.top: parent.top
0186             anchors.bottom: parent.bottom
0187             anchors.margins: PlasmaCore.Units.smallSpacing
0188             implicitWidth: height
0189             icon.name: root.isPinMode ? "input-keyboard-virtual-symbolic" : "input-dialpad-symbolic"
0190             onClicked: {
0191                 root.isPinMode = !root.isPinMode;
0192                 if (!root.isPinMode) {
0193                     Keyboards.KWinVirtualKeyboard.active = true;
0194                 }
0195             }
0196         }
0197         
0198         // label ("wrong pin", "enter pin")
0199         Label {
0200             opacity: root.lockScreenState.password.length === 0 ? 1 : 0
0201             anchors.centerIn: parent
0202             text: root.pinLabel
0203             font.pointSize: 12
0204             color: root.headerTextColor 
0205             
0206             Behavior on opacity {
0207                 NumberAnimation { duration: 200 }
0208             }
0209         }
0210         
0211         // pin dot display
0212         ColumnLayout {
0213             anchors.fill: parent
0214             
0215             ListView {
0216                 id: dotDisplay
0217                 property int dotWidth: Math.round(PlasmaCore.Units.gridUnit * 0.35)
0218                 
0219                 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
0220                 Layout.bottomMargin: Math.round(dotWidth / 2)
0221                 orientation: ListView.Horizontal
0222                 implicitWidth: count * dotWidth + spacing * (count - 1)
0223                 spacing: 8
0224                 model: dotDisplayModel
0225                 
0226                 Behavior on implicitWidth {
0227                     NumberAnimation { duration: 50 }
0228                 }
0229                 
0230                 delegate: Item {
0231                     implicitWidth: dotDisplay.dotWidth
0232                     implicitHeight: dotDisplay.dotWidth
0233                     property bool showChar: index === root.previewCharIndex
0234                     
0235                     Component.onCompleted: {
0236                         if (showChar) {
0237                             charAnimation.to = 1;
0238                             charAnimation.duration = 75;
0239                             charAnimation.restart();
0240                         } else {
0241                             dotAnimation.to = 1;
0242                             dotAnimation.restart();
0243                         }
0244                     }
0245                     
0246                     onShowCharChanged: {
0247                         if (!showChar) {
0248                             charAnimation.to = 0;
0249                             charAnimation.duration = 50;
0250                             charAnimation.restart();
0251                             dotAnimation.to = 1;
0252                             dotAnimation.start();
0253                         }
0254                     }
0255                     
0256                     Rectangle { // dot
0257                         id: dot
0258                         scale: 0
0259                         anchors.fill: parent
0260                         radius: width
0261                         color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth
0262                         
0263                         PropertyAnimation {
0264                             id: dotAnimation
0265                             target: dot;
0266                             property: "scale";
0267                             duration: 50
0268                         }
0269                     }
0270                     
0271                     Label { // number/letter
0272                         id: charLabel
0273                         scale: 0
0274                         anchors.centerIn: parent
0275                         color: lockScreenState.waitingForAuth ? root.headerTextInactiveColor : root.headerTextColor // dim when waiting for auth
0276                         text: model.char
0277                         font.pointSize: 12
0278                         
0279                         PropertyAnimation {
0280                             id: charAnimation
0281                             target: charLabel;
0282                             property: "scale";
0283                         }
0284                     }
0285                 }
0286             }
0287         }
0288     }
0289 }