on xenial help sign in
Update passphrase lockscreen UI and make some other design tweaks
commit7c97457d582452ca15da3ac4428fc4160dfa1fb6 authorMichael Terry <michael.terry@canonical.com> dateMon 15 Sep 2014, 12:41 (12 years ago) parent6798389
7 files changed+247−117
qml/Components/DelayedLockscreen.qml+90 qml/Components/Lockscreen.qml+52−49 qml/Components/PassphraseLockscreen.qml+63−43 qml/Components/PinLockscreen.qml+6−13 qml/Greeter/Greeter.qml+1−1 qml/Shell.qml+32−10 tests/qmltests/tst_ShellWithPin.qml+3−1
qml/Components/DelayedLockscreen.qmlnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
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.
7+ *
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.
12+ *
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/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+Item {
22+ id: root
23+ anchors.fill: parent
24+
25+ property int delayMinutes
26+ property bool alphaNumeric
27+
28+ function clear(playAnimation) {}
29+
30+ Column {
31+ anchors.left: parent.left
32+ anchors.right: parent.right
33+ anchors.leftMargin: units.gu(4)
34+ anchors.rightMargin: units.gu(4)
35+ anchors.verticalCenter: parent.verticalCenter
36+ spacing: units.gu(2)
37+
38+ Label {
39+ anchors.left: parent.left
40+ anchors.right: parent.right
41+ horizontalAlignment: Text.AlignHCenter
42+ wrapMode: Text.Wrap
43+ fontSize: "x-large"
44+ color: "#f3f3e7"
45+ text: i18n.tr("Device Locked")
46+ }
47+
48+ Item { // spacer
49+ width: units.gu(1)
50+ height: units.gu(1)
51+ }
52+
53+ Label {
54+ anchors.left: parent.left
55+ anchors.right: parent.right
56+ horizontalAlignment: Text.AlignHCenter
57+ wrapMode: Text.Wrap
58+ color: "#f3f3e7"
59+ text: alphaNumeric ?
60+ i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
61+ i18n.tr("You have been locked out due to too many failed passcode attempts.")
62+ }
63+
64+ Label {
65+ anchors.left: parent.left
66+ anchors.right: parent.right
67+ horizontalAlignment: Text.AlignHCenter
68+ wrapMode: Text.Wrap
69+ color: "#f3f3e7"
70+ text: i18n.tr("Please wait %1 minute and then try again…",
71+ "Please wait %1 minutes and then try again…",
72+ root.delayMinutes).arg(root.delayMinutes)
73+ }
74+
75+ Item { // spacer
76+ width: units.gu(1)
77+ height: units.gu(1)
78+ }
79+
80+ Icon {
81+ // It would be nice to use a less network-specific name,
82+ // but this is the only lock icon we have.
83+ name: "network-secure"
84+ color: "#f3f3e7"
85+ height: units.gu(4)
86+ width: units.gu(4)
87+ anchors.horizontalCenter: parent.horizontalCenter
88+ }
89+ }
90+}
qml/Components/Lockscreen.qml+52−49
@@ -27,14 +27,11 @@ Showable {
2727 // Informational text. (e.g. some text to tell which domain this is pin is entered for)
2828 property string infoText: ""
2929
30− // Retries text (e.g. 3 retries left)
31− property string retryText: ""
32−
3330 // The text to be displayed in case the login failed
3431 property string errorText: ""
3532
36− // In case the Lockscreen can show a greeter message, this is the username
37− property string username: ""
33+ // If > 0, a forced delay is happening
34+ property int delayMinutes: 0
3835
3936 // Set those to a value greater 0 to restrict the pin length.
4037 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
@@ -60,11 +57,6 @@ Showable {
6057 }
6158 }
6259
63− function forceDelay(delay) {
64− forcedDelayTimer.interval = delay
65− forcedDelayTimer.start()
66− }
67−
6860 function reset() {
6961 // This causes the loader below to destry and recreate the source
7062 pinPadLoader.resetting = true;
@@ -83,11 +75,6 @@ Showable {
8375 PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
8476 }
8577
86− Timer {
87− id: forcedDelayTimer
88− onTriggered: pinPadLoader.showWrongText = false
89− }
90−
9178 Rectangle {
9279 // In case background fails to load or is undefined
9380 id: backgroundBackup
@@ -112,26 +99,26 @@ Showable {
11299 Loader {
113100 id: pinPadLoader
114101 objectName: "pinPadLoader"
115− anchors {
116− left: parent.left
117− right: parent.right
118− verticalCenter: parent.verticalCenter
119− verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : 0
120− }
102+ anchors.fill: parent
121103 property bool resetting: false
122104 property bool waiting: false
123105 property bool showWrongText: false
124106
125− source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
107+ source: {
108+ if (resetting || !root.required) {
109+ return ""
110+ } else if (root.delayMinutes > 0) {
111+ return "DelayedLockscreen.qml"
112+ } else if (root.alphaNumeric) {
113+ return "PassphraseLockscreen.qml"
114+ } else {
115+ return "PinLockscreen.qml"
116+ }
117+ }
126118 onSourceChanged: {
127119 waiting = false
128120 showWrongText = false
129121 }
130− onLoaded: {
131− if (forcedDelayTimer.running) {
132− pinPadLoader.item.clear(true)
133− }
134− }
135122
136123 Connections {
137124 target: pinPadLoader.item
@@ -163,30 +150,28 @@ Showable {
163150 }
164151 Binding {
165152 target: pinPadLoader.item
166− property: "retryText"
167− value: forcedDelayTimer.running ? i18n.tr("Please wait") : root.retryText
153+ property: "errorText"
154+ value: pinPadLoader.showWrongText ? root.errorText : ""
168155 }
169156 Binding {
170157 target: pinPadLoader.item
171− property: "errorText"
172− value: forcedDelayTimer.running ? i18n.tr("Too many incorrect attempts") :
173− (pinPadLoader.showWrongText ? root.errorText : "")
158+ property: "entryEnabled"
159+ value: !pinPadLoader.waiting
174160 }
175161 Binding {
176162 target: pinPadLoader.item
177− property: "username"
178− value: root.username
163+ property: "alphaNumeric"
164+ value: root.alphaNumeric
179165 }
180166 Binding {
181167 target: pinPadLoader.item
182− property: "entryEnabled"
183− value: !pinPadLoader.waiting && !forcedDelayTimer.running
168+ property: "delayMinutes"
169+ value: root.delayMinutes
184170 }
185171 }
186172
187− Label {
188− id: emergencyCallLabel
189− objectName: "emergencyCallLabel"
173+ Item {
174+ id: emergencyCallRow
190175
191176 // FIXME: We *should* show emergency dialer if there is a SIM present,
192177 // regardless of whether the side stage is enabled. But right now,
@@ -199,19 +184,37 @@ Showable {
199184
200185 anchors {
201186 bottom: parent.bottom
202− bottomMargin: units.gu(4)
203− horizontalCenter: parent.horizontalCenter
187+ bottomMargin: units.gu(7) + (Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0)
188+ left: parent.left
189+ right: parent.right
204190 }
205191
206− text: i18n.tr("Emergency Call")
207− color: "#f3f3e7"
208− opacity: 0.6
209− }
192+ Label {
193+ id: emergencyCallLabel
194+ objectName: "emergencyCallLabel"
195+ anchors.horizontalCenter: parent.horizontalCenter
210196
211− MouseArea {
212− anchors.fill: emergencyCallLabel
213− onClicked: root.emergencyCall()
214− enabled: emergencyCallLabel.visible
197+ text: i18n.tr("Emergency Call")
198+ color: "#f3f3e7"
199+ }
200+
201+ Icon {
202+ id: emergencyCallIcon
203+ anchors.left: emergencyCallLabel.right
204+ anchors.leftMargin: units.gu(1)
205+ width: emergencyCallLabel.height
206+ height: emergencyCallLabel.height
207+ name: "call-start"
208+ color: "#f3f3e7"
209+ }
210+
211+ MouseArea {
212+ anchors.top: emergencyCallLabel.top
213+ anchors.bottom: emergencyCallLabel.bottom
214+ anchors.left: emergencyCallLabel.left
215+ anchors.right: emergencyCallIcon.right
216+ onClicked: root.emergencyCall()
217+ }
215218 }
216219
217220 Component {
qml/Components/PassphraseLockscreen.qml+63−43
@@ -20,10 +20,12 @@ import "../Components"
2020
2121Item {
2222 id: root
23− height: highlightItem.height
23+ anchors.top: parent.top
24+ anchors.topMargin: units.gu(4)
25+ height: shakeContainer.height
2426
2527 property string infoText
26− property string username: ""
28+ property string errorText
2729 property bool entryEnabled: true
2830
2931 signal entered(string passphrase)
@@ -31,57 +33,75 @@ Item {
3133
3234 function clear(playAnimation) {
3335 pinentryField.text = "";
36+ pinentryField.forceActiveFocus();
3437 if (playAnimation) {
3538 wrongPasswordAnimation.start();
36− pinentryField.forceActiveFocus();
37− } else {
38− pinentryField.focus = false
3939 }
4040 }
4141
42− Rectangle {
43− id: highlightItem
44− width: units.gu(32)
45− height: units.gu(10)
46− anchors.centerIn: parent
47− color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
48− border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
49− border.width: units.dp(1)
50− radius: units.gu(1.5)
51− antialiasing: true
42+ Column {
43+ id: shakeContainer
44+ anchors.horizontalCenter: parent.horizontalCenter
45+ width: parent.width
46+ spacing: units.gu(2)
5247
5348 Label {
54− objectName: "greeterLabel"
55− anchors {
56− left: parent.left
57− top: parent.top
58− right: parent.right
59− margins: units.gu(1.5)
60− }
61− text: root.username.length > 0 ? i18n.tr("Hello %1").arg(root.username) : i18n.tr("Hello")
62− color: "white"
49+ id: infoField
50+ objectName: "infoTextLabel"
51+ fontSize: "x-large"
52+ color: "#f3f3e7"
53+ anchors.horizontalCenter: parent.horizontalCenter
54+ text: root.infoText
6355 }
6456
65− TextField {
66− id: pinentryField
67− objectName: "pinentryField"
68− anchors {
69− horizontalCenter: parent.horizontalCenter
70− bottom: parent.bottom
71− margins: units.gu(1)
72− }
73− height: units.gu(4.5)
74− width: parent.width - units.gu(2)
75− echoMode: TextInput.Password
76− opacity: 0.9
77− hasClearButton: false
78− enabled: root.entryEnabled
79− placeholderText: root.infoText
57+ Item {
58+ id: entryContainer
59+ anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
60+ height: units.gu(4)
8061
81− onAccepted: {
82− if (pinentryField.text) {
83− root.entered(pinentryField.text);
62+ TextInput {
63+ id: pinentryField
64+ objectName: "pinentryField"
65+ anchors {
66+ top: parent.top
67+ left: parent.left
68+ right: parent.right
8469 }
70+ horizontalAlignment: Text.AlignHCenter
71+ font.pixelSize: FontUtils.sizeToPixels("large")
72+ echoMode: TextInput.Password
73+ inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData |
74+ Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
75+ color: "#f3f3e7"
76+ cursorDelegate: Item {} // disable cursor
77+ onCursorPositionChanged: {
78+ // And because we don't show the cursor, always position the
79+ // cursor at the end of the string (so backspace works like
80+ // the user expects, even if they've clicked on us and
81+ // thus accidentally moved the cursor)
82+ if (cursorPosition !== length) {
83+ cursorPosition = length
84+ }
85+ }
86+ enabled: root.entryEnabled
87+ clip: true
88+
89+ onAccepted: {
90+ if (pinentryField.text) {
91+ root.entered(pinentryField.text);
92+ }
93+ }
94+ }
95+
96+ Label {
97+ id: wrongNoticeLabel
98+ objectName: "wrongNoticeLabel"
99+ fontSize: "large"
100+ color: "#f3f3e7"
101+ anchors.horizontalCenter: parent.horizontalCenter
102+ horizontalAlignment: Text.AlignHCenter
103+ text: root.errorText
104+ visible: pinentryField.text.length == 0
85105 }
86106 }
87107 }
@@ -89,6 +109,6 @@ Item {
89109 WrongPasswordAnimation {
90110 id: wrongPasswordAnimation
91111 objectName: "wrongPasswordAnimation"
92− target: pinentryField
112+ target: shakeContainer
93113 }
94114}
qml/Components/PinLockscreen.qml+6−13
@@ -21,11 +21,12 @@ import "../Components"
2121
2222Column {
2323 id: root
24− anchors.centerIn: parent
24+ anchors.top: parent.top
25+ anchors.topMargin: units.gu(4)
26+ anchors.horizontalCenter: parent.horizontalCenter
2527 spacing: units.gu(2)
2628
2729 property string infoText
28− property string retryText
2930 property string errorText
3031 property int padWidth: units.gu(34)
3132 property int padHeight: units.gu(28)
@@ -132,14 +133,6 @@ Column {
132133 onClicked: pinentryField.backspace()
133134 }
134135 }
135−
136− Label {
137− objectName: "retryLabel"
138− fontSize: "x-small"
139− color: "#f3f3e7"
140− anchors.horizontalCenter: parent.horizontalCenter
141− text: root.retryText || " "
142− }
143136 }
144137
145138 ThinDivider {
@@ -193,19 +186,19 @@ Column {
193186 }
194187 PinPadButton {
195188 iconName: "close"
196− height: numbersGrid.buttonHeight
189+ height: units.gu(5) // visual spec has this row a little closer in
197190 width: numbersGrid.buttonWidth
198191
199192 onClicked: root.cancel()
200193 }
201194 Item {
202− height: numbersGrid.buttonHeight
195+ height: units.gu(5)
203196 width: numbersGrid.buttonWidth
204197 }
205198 PinPadButton {
206199 iconName: "tick"
207200 objectName: "confirmButton"
208− height: numbersGrid.buttonHeight
201+ height: units.gu(5)
209202 width: numbersGrid.buttonWidth
210203 enabled: root.enabled && pinentryField.text.length >= root.minPinLength
211204 visible: root.minPinLength == -1 || root.minPinLength !== root.maxPinLength
qml/Greeter/Greeter.qml+1−1
@@ -154,7 +154,7 @@ Showable {
154154 anchors.horizontalCenter: parent.horizontalCenter
155155 anchors.bottom: parent.bottom
156156 anchors.bottomMargin: units.gu(5)
157− text: i18n.tr("Swipe to unlock")
157+ text: "《 " + i18n.tr("Unlock") + " 》"
158158 color: "white"
159159 font.weight: Font.Light
160160
qml/Shell.qml+32−10
@@ -59,7 +59,7 @@ Item {
5959
6060 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
6161 property int failedLoginsDelayAttempts: 7 // number of failed logins
62− property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
62+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
6363
6464 function activateApplication(appId) {
6565 if (ApplicationManager.findApplication(appId)) {
@@ -250,8 +250,6 @@ Item {
250250 id: lockscreen
251251 objectName: "lockscreen"
252252
253− readonly property int backgroundTopMargin: -panel.panelHeight
254−
255253 hides: [launcher, panel.indicators]
256254 shown: false
257255 enabled: true
@@ -272,6 +270,19 @@ Item {
272270
273271 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
274272
273+ Timer {
274+ id: forcedDelayTimer
275+ interval: 1000 * 60
276+ onTriggered: {
277+ if (lockscreen.delayMinutes > 0) {
278+ lockscreen.delayMinutes -= 1
279+ if (lockscreen.delayMinutes > 0) {
280+ start() // go again
281+ }
282+ }
283+ }
284+ }
285+
275286 Component.onCompleted: {
276287 if (greeter.narrowMode) {
277288 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
@@ -289,14 +300,15 @@ Item {
289300 if (greeter.narrowMode) {
290301 if (isDefaultPrompt) {
291302 if (lockscreen.alphaNumeric) {
292− lockscreen.infoText = i18n.tr("Enter your passphrase")
293− lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
303+ lockscreen.infoText = i18n.tr("Enter passphrase")
304+ lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase") + "\n" +
305+ i18n.tr("Please re-enter")
294306 } else {
295− lockscreen.infoText = i18n.tr("Enter your passcode")
307+ lockscreen.infoText = i18n.tr("Enter passcode")
296308 lockscreen.errorText = i18n.tr("Sorry, incorrect passcode")
297309 }
298310 } else {
299− lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
311+ lockscreen.infoText = i18n.tr("Enter %1").arg(text.toLowerCase())
300312 lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
301313 }
302314
@@ -343,8 +355,10 @@ Item {
343355 SystemImage.factoryReset() // Ouch!
344356 }
345357 }
346− if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
347− lockscreen.forceDelay(failedLoginsDelaySeconds * 1000)
358+
359+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins >= failedLoginsDelayAttempts) {
360+ lockscreen.delayMinutes = failedLoginsDelayMinutes
361+ forcedDelayTimer.start()
348362 }
349363
350364 lockscreen.clear(true);
@@ -390,7 +404,15 @@ Item {
390404 }
391405
392406 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
393− onShowProgressChanged: if (LightDM.Greeter.authenticated && showProgress === 0) greeter.login()
407+ onShowProgressChanged: {
408+ if (showProgress === 0) {
409+ if (LightDM.Greeter.authenticated) {
410+ greeter.login()
411+ } else if (greeter.narrowMode) {
412+ lockscreen.clear(false) // to reset focus if necessary
413+ }
414+ }
415+ }
394416
395417 Greeter {
396418 id: greeter
tests/qmltests/tst_ShellWithPin.qml+3−1
@@ -230,8 +230,10 @@ Item {
230230 enterPin("1111")
231231 tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
232232
233+ var lockscreen = findChild(shell, "lockscreen")
234+ tryCompare(lockscreen, "delayMinutes", 0)
233235 enterPin("1111")
234− tryCompare(placeHolder, "text", "Too many incorrect attempts")
236+ tryCompare(lockscreen, "delayMinutes", shell.failedLoginsDelayMinutes)
235237 }
236238
237239 function test_factoryReset() {