2 * Copyright 2022 UBports Foundation
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18import Lomiri.Components 1.3
19import AccountsService 0.1
23 objectName: "ClockPinPrompt"
26 property bool isSecret
27 property bool interactive: true
28 property bool loginError: false
29 property bool hasKeyboard: false //unused
30 property string enteredText: ""
32 property int previousNumber: -1
33 property var currentCode: []
34 property int maxnum: 10
35 readonly property int pincodeLength: AccountsService.pincodeLength
36 readonly property bool validCode: enteredText.length >= pincodeLength
37 property bool isLandscape: width > height
41 signal accepted(string response)
43 onCurrentCodeChanged: {
46 const maxDigits = Math.max(root.pincodeLength, currentCode.length)
47 for( let i = 0; i < maxDigits; i++) {
48 if (i < currentCode.length) {
50 tmpCode += currentCode[i]
56 pinHint.text = tmpText
57 root.enteredText = tmpCode
59 if (root.enteredText.length >= pincodeLength) {
60 root.accepted(root.enteredText);
64 function addNumber (number, fromKeyboard) {
65 if (currentCode.length >= root.pincodeLength) return;
66 let tmpCodes = currentCode
68 currentCode = tmpCodes
69 // don't animate digits while with keyboard
71 repeater.itemAt(number).animation.restart()
73 root.previousNumber = number
76 function removeOne() {
77 let tmpCodes = currentCode
80 currentCode = tmpCodes
91 readonly property color normal: theme.palette.normal.raisedText
92 readonly property color selected: theme.palette.normal.raisedSecondaryText
93 readonly property color selectedCircle: Qt.rgba(selected.r, selected.g, selected.b, 0.2)
94 readonly property color disabled:theme.palette.disabled.raisedSecondaryText
100 anchors.horizontalCenter: parent.horizontalCenter
101 width: contentWidth + eraseIcon.width + units.gu(3)
106 pixelSize: units.gu(3)
107 letterSpacing: units.gu(1.75)
109 secondaryItem: Icon {
112 objectName: "EraseBtn"
115 color: enabled ? d.selected : d.disabled
116 enabled: root.currentCode.length > 0
117 anchors.verticalCenter: parent.verticalCenter
120 onClicked: root.removeOne()
121 onPressAndHold: root.reset()
125 inputMethodHints: Qt.ImhDigitsOnly
127 Keys.onEscapePressed: {
129 event.accepted = true;
133 if(event.key >= Qt.Key_0 && event.key <= Qt.Key_9) {
134 root.addNumber(event.text, true)
135 event.accepted = true;
138 Keys.onReturnPressed: root.accepted(root.enteredText);
139 Keys.onEnterPressed: root.accepted(root.enteredText);
141 Keys.onBackPressed: {
149 objectName: "SelectArea"
151 height: Math.min(parent.height, parent.width)
153 anchors.bottom:parent.bottom
154 // in landscape, let the clock being close to the bottom
155 anchors.bottomMargin: root.isLandscape ? -units.gu(4) : undefined
156 anchors.horizontalCenter: parent.horizontalCenter
163 function reEvaluate() {
164 var child = main.childAt(mouseX, mouseY)
166 if (child !== null && child.number !== undefined) {
167 var number = child.number
168 if (number > -1 && ( root.previousNumber === -1 || number !== root.previousNumber)) {
169 root.addNumber(number)
173 root.previousNumber = -1
178 if (state !== "ENTRY_MODE") {
179 root.state = "ENTRY_MODE"
192 objectName: "CenterCircle"
193 height: main.height / 3
196 property int radiusSquared: radius * radius
197 property alias locker: centerImg.source
198 property alias animation: challengeAnim
199 anchors.centerIn: parent
201 property int number: -1
205 source: "image://theme/lock"
206 anchors.centerIn: parent
209 color: root.validCode ? d.selected : d.disabled
210 onSourceChanged: imgAnim.start()
213 SequentialAnimation {
238 SequentialAnimation {
240 NumberAnimation { target: centerImg; property: "opacity"; from: 0; to: 1; duration: 1000 }
247 objectName: "dotRepeater"
252 property int bigR: root.state === "ENTRY_MODE" || root.state === "TEST_MODE" || root.state === "EDIT_MODE" ? main.height / 3 : 0
253 property int radius: height / 2
254 property int offsetRadius: radius
255 property int number: index
256 property alias dot: point
257 property alias animation: anim
261 x: (main.width / 2) + bigR * Math.sin(2 * Math.PI * index / root.maxnum) - offsetRadius
262 y: (main.height / 2) - bigR * Math.cos(2 * Math.PI * index / root.maxnum) - offsetRadius
267 radius: numberComp.radius
274 font.pixelSize: main.height / 10
275 anchors.centerIn: parent
278 opacity: root.state === "ENTRY_MODE" ? 1 : 0
279 property bool selected: false
281 Behavior on opacity {
282 LomiriNumberAnimation{ duration: 500 }
289 root.addNumber(index)
290 mouse.accepted = false
295 LomiriNumberAnimation { duration: 500 }
298 SequentialAnimation {
308 target: selectionRect
322 target: selectionRect
337 script: root.reset();
341 name: "WRONG_PASSWORD"
342 when: root.loginError
345 locker: "image://theme/dialog-warning-symbolic"
350 transitions: Transition {
351 from: "WRONG_PASSWORD"; to: "ENTRY_MODE";
352 PropertyAction { target: center; property: "locker"; value: "image://theme/dialog-warning-symbolic" }
353 PauseAnimation { duration: 1000 }
356 onActiveFocusChanged: {
357 if (!activeFocus && !pinHint.activeFocus) {
360 root.state = "ENTRY_MODE"
361 pinHint.forceActiveFocus()