File: PasswordBar.qml

package info (click to toggle)
plasma-mobile 6.3.6-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 18,612 kB
  • sloc: xml: 38,470; cpp: 18,437; javascript: 139; sh: 82; makefile: 5
file content (298 lines) | stat: -rw-r--r-- 9,946 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// SPDX-FileCopyrightText: 2020-2024 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

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
    required property var lockScreenState

    property alias textField: textField

    required property bool isKeypadOpen

    // for displaying temporary number in pin dot display
    property int previewCharIndex: -2

    readonly property color headerTextColor: Qt.rgba(255, 255, 255, 1)
    readonly property color headerTextInactiveColor: Qt.rgba(255, 255, 255, 0.4)

    radius: Kirigami.Units.largeSpacing
    color: Qt.rgba(255, 255, 255, 0.2)

    // model for shown dots
    // we need to use a listmodel to avoid all delegates from reloading
    ListModel {
        id: dotDisplayModel
    }

    // Listen to lockscreen state changes
    Connections {
        target: root.lockScreenState

        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 (root.isKeypadOpen && root.lockScreenState.isKeyboardMode) {
            // make sure keyboard doesn't close
            openKeyboardTimer.restart();
        }
    }

    function keyPress(data) {
        if (!lockScreenState.waitingForAuth) {
            if (data === "\x08") { // Handle backspace
                root.backspace();
            } else if (data === "\r") { // Handle enter
                root.enter();
            } else {
                root.lockScreenState.resetPinLabel();

                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: root.isKeypadOpen && root.lockScreenState.isKeyboardMode
        z: 1
        inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData

        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 (root.lockScreenState.isKeyboardMode) {
                Keyboards.KWinVirtualKeyboard.active = true;
            }
        }

        // toggle between showing keypad and not
        ToolButton {
            id: keyboardToggle
            Kirigami.Theme.inherit: false
            Kirigami.Theme.colorSet: Kirigami.Theme.Complementary

            anchors.right: parent.right
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.margins: Kirigami.Units.smallSpacing

            // Don't show if the PIN display overlaps it
            visible: (dotDisplay.width / 2) < ((root.width / 2) - keyboardToggle.width - Kirigami.Units.smallSpacing)

            implicitWidth: height
            icon.name: root.lockScreenState.isKeyboardMode ? "input-dialpad-symbolic" : "input-keyboard-virtual-symbolic"
            icon.color: 'white'
            onClicked: {
                root.lockScreenState.isKeyboardMode = !root.lockScreenState.isKeyboardMode;
                if (root.lockScreenState.isKeyboardMode) {
                    Keyboards.KWinVirtualKeyboard.active = true;
                }
            }
        }

        // PIN font metrics
        FontMetrics {
            id: pinFontMetrics
            font.family: Kirigami.Theme.defaultFont.family
            font.pointSize: 12
        }

        // pin dot display
        ColumnLayout {
            anchors.fill: parent

            ListView {
                id: dotDisplay

                property int dotWidth: 6

                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
                Layout.bottomMargin: Math.round(dotWidth / 2)
                Layout.maximumWidth: root.width - (Kirigami.Units.largeSpacing * 2)

                readonly property real delegateHeight: Math.max(pinFontMetrics.height, dotWidth)
                readonly property real delegateWidth: dotWidth

                implicitHeight: delegateHeight
                implicitWidth: count * delegateWidth + spacing * (count - 1)

                orientation: ListView.Horizontal
                spacing: 8
                model: dotDisplayModel

                Behavior on implicitWidth {
                    NumberAnimation { duration: 50 }
                }

                onImplicitWidthChanged: {
                    if (contentWidth > Layout.maximumWidth) {
                        // When character is created and ListView is in overflow,
                        // scroll to the end of the ListView to show it
                        dotDisplay.positionViewAtEnd();
                    }
                }

                delegate: Item {
                    width: dotDisplay.delegateWidth
                    height: dotDisplay.delegateHeight
                    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
                        width: dotDisplay.dotWidth
                        height: dotDisplay.dotWidth
                        anchors.centerIn: 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";
                        }
                    }
                }
            }
        }
    }
}