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 }
248 objectName: "dotRepeater"
257 property int number: index
258 property alias dot: point
259 property alias animation: anim
261 property int bigR: root.state === "ENTRY_MODE" ? main.height / 3 : 0
262 property int offsetRadius: radius
263 x: (main.width / 2) + bigR * Math.sin(2 * Math.PI * index / root.maxnum) - offsetRadius
264 y: (main.height / 2) - bigR * Math.cos(2 * Math.PI * index / root.maxnum) - offsetRadius
268 font.pixelSize: main.height / 10
269 anchors.centerIn: parent
272 opacity: root.state === "ENTRY_MODE" ? 1 : 0
273 property bool selected: false
275 Behavior on opacity {
276 LomiriNumberAnimation{ duration: 500 }
283 root.addNumber(index)
284 mouse.accepted = false
289 LomiriNumberAnimation { duration: 500 }
292 SequentialAnimation {
302 target: selectionRect
304 to: Qt.rgba(d.selected.r, d.selected.g, d.selected.b, 0.3)
316 target: selectionRect
331 script: root.reset();
335 name: "WRONG_PASSWORD"
336 when: root.loginError
339 locker: "image://theme/dialog-warning-symbolic"
344 transitions: Transition {
345 from: "WRONG_PASSWORD"; to: "ENTRY_MODE";
346 PropertyAction { target: center; property: "locker"; value: "image://theme/dialog-warning-symbolic" }
347 PauseAnimation { duration: 1000 }
350 onActiveFocusChanged: {
351 if (!activeFocus && !pinHint.activeFocus) {
354 root.state = "ENTRY_MODE"
355 pinHint.forceActiveFocus()