on xenial help sign in
Implement new lockscreen designs

Approved by: Michael Terry

commitce17b658674846e98cb19d64140e1057674be11e authorMichael Zanetti <michael.zanetti@canonical.com> dateFri 22 Aug 2014, 09:28 (12 years ago) parentf35c4e8722f0f6
10 files changed+357−388
po/unity8.pot+31−35 qml/Components/Lockscreen.qml+33−75 qml/Components/PassphraseLockscreen.qml+3−3 qml/Components/PinLockscreen.qml+151−163 qml/Components/PinPadButton.qml+31−45 qml/Notifications/NotificationMenuItemFactory.qml+4−1 qml/Shell.qml+11−7 tests/autopilot/unity8/shell/tests/test_lock_screen.py+2−2 tests/qmltests/Greeter/tst_Lockscreen.qml+65−46 tests/qmltests/tst_ShellWithPin.qml+26−11
po/unity8.pot+31−35
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-08-13 14:09-0400\n"
11+"POT-Creation-Date: 2014-08-21 11:36+0200\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -92,19 +92,19 @@ msgid ""
9292"phone<br><br>Tap on the screen to start"
9393msgstr ""
9494
95−#: qml/Components/Lockscreen.qml:161
96−msgid "Too many incorrect attempts"
95+#: qml/Components/Lockscreen.qml:167
96+msgid "Please wait"
9797msgstr ""
9898
99−#: qml/Components/Lockscreen.qml:163
100−msgid "Please wait"
99+#: qml/Components/Lockscreen.qml:172
100+msgid "Too many incorrect attempts"
101101msgstr ""
102102
103−#: qml/Components/Lockscreen.qml:245
103+#: qml/Components/Lockscreen.qml:206
104104msgid "Emergency Call"
105105msgstr ""
106106
107−#: qml/Components/Lockscreen.qml:268
107+#: qml/Components/Lockscreen.qml:226
108108msgid "OK"
109109msgstr ""
110110
@@ -117,23 +117,15 @@ msgstr ""
117117msgid "Hello"
118118msgstr ""
119119
120−#: qml/Components/PinLockscreen.qml:197
121−msgid "CANCEL"
122−msgstr ""
123−
124−#: qml/Components/PinLockscreen.qml:215
125−msgid "DONE"
126−msgstr ""
127−
128−#: qml/Dash/GenericScopeView.qml:360
120+#: qml/Dash/GenericScopeView.qml:361
129121msgid "See less"
130122msgstr ""
131123
132−#: qml/Dash/GenericScopeView.qml:360
124+#: qml/Dash/GenericScopeView.qml:361
133125msgid "See all"
134126msgstr ""
135127
136−#: qml/Dash/GenericScopeView.qml:423 qml/Panel/SearchIndicator.qml:27
128+#: qml/Dash/GenericScopeView.qml:424 qml/Panel/SearchIndicator.qml:27
137129msgid "Search"
138130msgstr ""
139131
@@ -201,12 +193,16 @@ msgstr ""
201193msgid "Speaking..."
202194msgstr ""
203195
204−#: qml/Notifications/NotificationMenuItemFactory.qml:100
196+#: qml/Notifications/NotificationMenuItemFactory.qml:105
205197msgid "Show password"
206198msgstr ""
207199
208−#: qml/Notifications/NotificationMenuItemFactory.qml:115
209−msgid "Please enter SIM PIN"
200+#: qml/Notifications/NotificationMenuItemFactory.qml:120
201+msgid "Enter SIM PIN"
202+msgstr ""
203+
204+#: qml/Notifications/NotificationMenuItemFactory.qml:121 qml/Shell.qml:270
205+msgid "Sorry, incorrect PIN"
210206msgstr ""
211207
212208#: qml/Panel/ActiveCallHint.qml:77
@@ -249,47 +245,47 @@ msgstr ""
249245msgid "Roaming"
250246msgstr ""
251247
252−#: qml/Shell.qml:267
253−msgid "passphrase"
248+#: qml/Shell.qml:266
249+msgid "Enter your passphrase"
254250msgstr ""
255251
256252#: qml/Shell.qml:267
257−msgid "passcode"
253+msgid "Sorry, incorrect passphrase"
258254msgstr ""
259255
260256#: qml/Shell.qml:269
261−#, qt-format
262−msgid "Enter your %1"
257+msgid "Enter your PIN"
263258msgstr ""
264259
265−#: qml/Shell.qml:270
260+#: qml/Shell.qml:273
266261#, qt-format
267−msgid "Incorrect %1"
262+msgid "Enter your %1"
268263msgstr ""
269264
270−#: qml/Shell.qml:272
271−msgid "Please re-enter"
265+#: qml/Shell.qml:274
266+#, qt-format
267+msgid "Sorry, incorrect %1"
272268msgstr ""
273269
274−#: qml/Shell.qml:306
270+#: qml/Shell.qml:310
275271msgid "Sorry, incorrect passphrase."
276272msgstr ""
277273
278−#: qml/Shell.qml:307
274+#: qml/Shell.qml:311
279275msgid "Sorry, incorrect passcode."
280276msgstr ""
281277
282−#: qml/Shell.qml:308
278+#: qml/Shell.qml:312
283279msgid "This will be your last attempt."
284280msgstr ""
285281
286−#: qml/Shell.qml:310
282+#: qml/Shell.qml:314
287283msgid ""
288284"If passphrase is entered incorrectly, your phone will conduct a factory "
289285"reset and all personal data will be deleted."
290286msgstr ""
291287
292−#: qml/Shell.qml:311
288+#: qml/Shell.qml:315
293289msgid ""
294290"If passcode is entered incorrectly, your phone will conduct a factory reset "
295291"and all personal data will be deleted."
qml/Components/Lockscreen.qml+33−75
@@ -24,15 +24,14 @@ Showable {
2424 // Determine if a numeric or alphanumeric pad is used.
2525 property bool alphaNumeric: false
2626
27− // Placeholder text
28− property string placeholderText: ""
29− // Placeholder text while shaking (e.g. Incorrect passprase)
30− property string wrongPlaceholderText: ""
27+ // Informational text. (e.g. some text to tell which domain this is pin is entered for)
28+ property string infoText: ""
29+
3130 // Retries text (e.g. 3 retries left)
3231 property string retryText: ""
3332
34− // Informational text. (e.g. some text to tell which domain this is pin is entered for.
35− property string infoText: ""
33+ // The text to be displayed in case the login failed
34+ property string errorText: ""
3635
3736 // In case the Lockscreen can show a greeter message, this is the username
3837 property string username: ""
@@ -110,41 +109,6 @@ Showable {
110109 anchors.fill: root
111110 }
112111
113− Column {
114− spacing: units.gu(2)
115− anchors {
116− left: parent.left
117− right: parent.right
118− bottom: pinPadLoader.top
119− bottomMargin: units.gu(2)
120− }
121− Label {
122− objectName: "retryCountLabel"
123− anchors {
124− left: parent.left
125− right: parent.right
126− }
127− text: root.retryText
128− horizontalAlignment: Text.AlignHCenter
129− color: "#f3f3e7"
130− opacity: 0.6
131− visible: root.retryText.length > 0
132− }
133− Label {
134− id: infoTextLabel
135− objectName: "infoTextLabel"
136− anchors {
137− left: parent.left
138− right: parent.right
139− }
140− text: root.infoText
141− horizontalAlignment: Text.AlignHCenter
142− color: "#f3f3e7"
143− opacity: 0.6
144− visible: root.infoText.length > 0
145− }
146− }
147−
148112 Loader {
149113 id: pinPadLoader
150114 objectName: "pinPadLoader"
@@ -152,21 +116,22 @@ Showable {
152116 left: parent.left
153117 right: parent.right
154118 verticalCenter: parent.verticalCenter
155− verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : -units.gu(4)
119+ verticalCenterOffset: root.alphaNumeric ? -units.gu(10) : 0
156120 }
157121 property bool resetting: false
158122 property bool waiting: false
159123 property bool showWrongText: false
160124
161− readonly property string forcedDelayText: i18n.tr("Too many incorrect attempts") +
162− "\n" +
163− i18n.tr("Please wait")
164−
165125 source: (!resetting && root.required) ? (root.alphaNumeric ? "PassphraseLockscreen.qml" : "PinLockscreen.qml") : ""
166126 onSourceChanged: {
167127 waiting = false
168128 showWrongText = false
169129 }
130+ onLoaded: {
131+ if (forcedDelayTimer.running) {
132+ pinPadLoader.item.clear(true)
133+ }
134+ }
170135
171136 Connections {
172137 target: pinPadLoader.item
@@ -193,10 +158,19 @@ Showable {
193158 }
194159 Binding {
195160 target: pinPadLoader.item
196− property: "placeholderText"
197− value: forcedDelayTimer.running ? pinPadLoader.forcedDelayText :
198− (pinPadLoader.showWrongText ? root.wrongPlaceholderText :
199− root.placeholderText)
161+ property: "infoText"
162+ value: root.infoText
163+ }
164+ Binding {
165+ target: pinPadLoader.item
166+ property: "retryText"
167+ value: forcedDelayTimer.running ? i18n.tr("Please wait") : root.retryText
168+ }
169+ Binding {
170+ target: pinPadLoader.item
171+ property: "errorText"
172+ value: forcedDelayTimer.running ? i18n.tr("Too many incorrect attempts") :
173+ (pinPadLoader.showWrongText ? root.errorText : "")
200174 }
201175 Binding {
202176 target: pinPadLoader.item
@@ -210,8 +184,9 @@ Showable {
210184 }
211185 }
212186
213− Column {
214− id: emergencyCallColumn
187+ Label {
188+ id: emergencyCallLabel
189+ objectName: "emergencyCallLabel"
215190
216191 // FIXME: We *should* show emergency dialer if there is a SIM present,
217192 // regardless of whether the side stage is enabled. But right now,
@@ -223,37 +198,20 @@ Showable {
223198 visible: !shell.sideStageEnabled
224199
225200 anchors {
226− left: parent.left
227201 bottom: parent.bottom
228202 bottomMargin: units.gu(4)
229− right: parent.right
230− }
231− height: childrenRect.height
232− spacing: units.gu(1)
233−
234− Icon {
235− objectName: "emergencyCallIcon"
236− height: units.gu(3)
237− width: height
238− anchors.horizontalCenter: parent.horizontalCenter
239− name: "phone-app-call-symbolic"
240− color: "#f3f3e7"
241− opacity: 0.6
203+ horizontalCenter: parent.horizontalCenter
242204 }
243205
244− Label {
245− text: i18n.tr("Emergency Call")
246− color: "#f3f3e7"
247− opacity: 0.6
248− fontSize: "medium"
249− anchors.horizontalCenter: parent.horizontalCenter
250− }
206+ text: i18n.tr("Emergency Call")
207+ color: "#f3f3e7"
208+ opacity: 0.6
251209 }
252210
253211 MouseArea {
254− anchors.fill: emergencyCallColumn
212+ anchors.fill: emergencyCallLabel
255213 onClicked: root.emergencyCall()
256− enabled: emergencyCallColumn.visible
214+ enabled: emergencyCallLabel.visible
257215 }
258216
259217 Component {
qml/Components/PassphraseLockscreen.qml+3−3
@@ -22,7 +22,7 @@ Item {
2222 id: root
2323 height: highlightItem.height
2424
25− property string placeholderText
25+ property string infoText
2626 property string username: ""
2727 property bool entryEnabled: true
2828
@@ -75,8 +75,8 @@ Item {
7575 echoMode: TextInput.Password
7676 opacity: 0.9
7777 hasClearButton: false
78− enabled: entryEnabled
79− placeholderText: root.placeholderText
78+ enabled: root.entryEnabled
79+ placeholderText: root.infoText
8080
8181 onAccepted: {
8282 if (pinentryField.text) {
qml/Components/PinLockscreen.qml+151−163
@@ -22,9 +22,11 @@ import "../Components"
2222Column {
2323 id: root
2424 anchors.centerIn: parent
25− spacing: units.gu(3.5)
25+ spacing: units.gu(2)
2626
27− property alias placeholderText: pinentryField.placeholderText
27+ property string infoText
28+ property string retryText
29+ property string errorText
2830 property int padWidth: units.gu(34)
2931 property int padHeight: units.gu(28)
3032 property int minPinLength: -1
@@ -35,199 +37,185 @@ Column {
3537
3638 property bool entryEnabled: true
3739
38− function clear(playAnimation) {
40+ function clear(showAnimation) {
3941 pinentryField.text = "";
40− if (playAnimation) {
42+ if (showAnimation) {
43+ pinentryField.incorrectOverride = true;
4144 wrongPasswordAnimation.start();
4245 }
4346 }
4447
45− QtObject {
46− id: priv
47− property bool autoConfirm: root.minPinLength == root.maxPinLength && root.minPinLength != -1
48− }
49−
50− UbuntuShape {
51− id: pinentryField
52− objectName: "pinentryField"
48+ Column {
49+ id: shakeContainer
5350 anchors.horizontalCenter: parent.horizontalCenter
54− color: "#55000000"
55− width:root.padWidth
56− height: units.gu(5)
57− radius: "medium"
58− property string text: ""
59− property string placeholderText: ""
60−
61− function appendChar(character) {
62− if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
63− pinentryField.text = pinentryField.text + character;
64− }
65− }
66−
67− onTextChanged: {
68− pinentryFieldLabel.text = "";
69− for (var i = 0; i < text.length; ++i) {
70− pinentryFieldLabel.text += "•";
71− }
72− if (priv.autoConfirm && text.length === root.maxPinLength) {
73− root.entered(text);
74− }
75− }
51+ width: parent.width
52+ spacing: units.gu(2)
7653
7754 Label {
78− id: pinentryFieldLabel
79− anchors.centerIn: parent
80− width: parent.width - (backspaceIcon.width + backspaceIcon.anchors.rightMargin) * 2
81− elide: Text.ElideMiddle
82− horizontalAlignment: Text.AlignHCenter
83− font.pixelSize: units.dp(44)
55+ id: infoField
56+ objectName: "infoTextLabel"
57+ fontSize: "large"
8458 color: "#f3f3e7"
85− opacity: 0.6
86− }
87− Label {
88− id: pinentryFieldPlaceHolder
89− objectName: "pinentryFieldPlaceHolder"
90− anchors.centerIn: parent
91− horizontalAlignment: Text.AlignHCenter
92− color: "#f3f3e7"
93− opacity: 0.6
94− text: parent.placeholderText
95− visible: pinentryFieldLabel.text.length == 0
59+ anchors.horizontalCenter: parent.horizontalCenter
60+ text: root.infoText
9661 }
9762
98− Icon {
99− id: backspaceIcon
100− objectName: "backspaceIcon"
101− anchors {
102− top: parent.top
103− topMargin: units.gu(1)
104− right: parent.right
105− rightMargin: units.gu(2)
106− bottom: parent.bottom
107− bottomMargin: units.gu(1)
108− }
109− visible: entryEnabled && !priv.autoConfirm
110− width: height
111− name: "erase"
112− color: "#f3f3e7"
113− opacity: 0.6
114− MouseArea {
115− anchors.fill: parent
116− onClicked: pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
117− }
118− }
119− }
63+ Item {
64+ id: pinContainer
65+ anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
66+ height: units.gu(4)
67+
68+ Row {
69+ id: pinentryField
70+ objectName: "pinentryField"
71+ anchors.horizontalCenter: parent.horizontalCenter
72+ anchors.verticalCenter: parent.verticalCenter
73+ spacing: Math.max(0, Math.min(units.gu(3), (parent.width / root.maxPinLength) - units.gu(3)))
74+
75+ property string text
76+ property bool incorrectOverride: false
77+
78+ Repeater {
79+ model: pinentryField.text.length
80+ delegate: Rectangle {
81+ color: "#f3f3e7"
82+ width: Math.min(units.gu(2), (pinContainer.width - pinContainer.height*2 ) / (root.maxPinLength >= 0 ? root.maxPinLength : 16))
83+ height: width
84+ radius: width / 2
85+ }
86+ }
12087
121− UbuntuShape {
122− anchors {
123− left: parent.left
124− right: parent.right
125− margins: (parent.width - root.padWidth) / 2
126− }
127− height: root.padHeight
128− color: "#55000000"
129− radius: "medium"
130−
131− ThinDivider {
132− anchors {
133− left: parent.left
134− right: parent.right
135− top: parent.top
136− topMargin: root.padHeight / 4
137− }
138− }
139− ThinDivider {
140− anchors {
141− left: parent.left
142− right: parent.right
143− verticalCenter: parent.verticalCenter
144− }
145− }
146− ThinDivider {
147− anchors {
148− left: parent.left
149− right: parent.right
150− bottom: parent.bottom
151− bottomMargin: root.padHeight / 4
152− }
153− }
88+ function appendNumber(number) {
89+ if (incorrectOverride) {
90+ incorrectOverride = false;
91+ }
15492
155− ThinDivider {
156− anchors.centerIn: parent
157− anchors.horizontalCenterOffset: -root.padWidth / 6
158− width: root.padHeight
159− rotation: -90
160− }
161− ThinDivider {
162− anchors.centerIn: parent
163− anchors.horizontalCenterOffset: root.padWidth / 6
164− width: root.padHeight
165− rotation: -90
166− }
93+ pinentryField.text = pinentryField.text + number
94+
95+ if (root.minPinLength > 0 && root.maxPinLength > 0
96+ && root.minPinLength == root.maxPinLength && pinentryField.text.length == root.minPinLength) {
97+ root.entered(pinentryField.text)
98+ }
99+ }
167100
168− Grid {
169− anchors {
170− left: parent.left
171− right: parent.right
172− margins: (parent.width - root.padWidth) / 2
101+ function backspace() {
102+ pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1)
103+ }
104+ }
105+ Label {
106+ id: wrongNoticeLabel
107+ objectName: "wrongNoticeLabel"
108+ fontSize: "x-large"
109+ color: "#f3f3e7"
110+ anchors.horizontalCenter: parent.horizontalCenter
111+ text: root.errorText
112+ visible: pinentryField.incorrectOverride
173113 }
174114
175− columns: 3
115+ AbstractButton {
116+ objectName: "backspaceIcon"
117+ anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
118+ width: height
176119
177− Repeater {
178− model: 9
120+ Icon {
121+ anchors.fill: parent
122+ name: "erase"
123+ color: "#f3f3e7"
124+ }
179125
180− PinPadButton {
181− objectName: "pinPadButton" + (index + 1)
182− width: root.padWidth / 3
183− height: root.padHeight / 4
184− text: index + 1
185− enabled: entryEnabled
126+ opacity: (pinentryField.text.length && !pinentryField.incorrectOverride) > 0 ? 1 : 0
186127
187− onClicked: {
188− pinentryField.appendChar(text);
189− }
128+ Behavior on opacity {
129+ UbuntuNumberAnimation {}
190130 }
191− }
192131
193− PinPadButton {
194− objectName: "pinPadButtonBack"
195− width: root.padWidth / 3
196− height: root.padHeight / 4
197− subText: i18n.tr("CANCEL")
198− onClicked: root.cancel();
132+ onClicked: pinentryField.backspace()
199133 }
134+ }
200135
201− PinPadButton {
202− objectName: "pinPadButton0"
203− width: root.padWidth / 3
204− height: root.padHeight / 4
205− text: "0"
206− onClicked: pinentryField.appendChar(text);
207− enabled: entryEnabled
208− }
136+ Label {
137+ objectName: "retryLabel"
138+ fontSize: "x-small"
139+ color: "#f3f3e7"
140+ anchors.horizontalCenter: parent.horizontalCenter
141+ text: root.retryText || " "
142+ }
143+ }
144+
145+ ThinDivider {
146+ anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
147+ }
148+
149+ Grid {
150+ id: numbersGrid
151+ anchors { horizontalCenter: parent.horizontalCenter }
152+ columns: 3
153+
154+ property int buttonHeight: units.gu(8)
155+ property int buttonWidth: units.gu(12)
156+
157+ Repeater {
158+ model: 9
209159
210160 PinPadButton {
211− objectName: "pinPadButtonErase"
212− width: root.padWidth / 3
213− height: root.padHeight / 4
214− iconName: priv.autoConfirm ? "erase" : ""
215− subText: priv.autoConfirm ? "" : i18n.tr("DONE")
161+ objectName: "pinPadButton" + text
162+ text: index + 1
163+ height: numbersGrid.buttonHeight
164+ width: numbersGrid.buttonWidth
165+ enabled: root.entryEnabled && (root.maxPinLength == -1 ||
166+ pinentryField.text.length < root.maxPinLength ||
167+ pinentryField.incorrectOverride)
168+
216169 onClicked: {
217− if (priv.autoConfirm) {
218− pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
219− } else {
220− root.entered(pinentryField.text);
221− }
170+ pinentryField.appendNumber(index + 1)
222171 }
223− enabled: priv.autoConfirm ? entryEnabled : pinentryField.text.length >= root.minPinLength
224172 }
225173 }
226− }
174+ Item {
175+ height: numbersGrid.buttonHeight
176+ width: numbersGrid.buttonWidth
177+ }
178+ PinPadButton {
179+ text: "0"
180+ height: numbersGrid.buttonHeight
181+ width: numbersGrid.buttonWidth
182+ enabled: root.entryEnabled && (root.maxPinLength == -1 ||
183+ pinentryField.text.length < root.maxPinLength ||
184+ pinentryField.incorrectOverride)
185+
186+ onClicked: {
187+ pinentryField.appendNumber(0)
188+ }
189+ }
190+ Item {
191+ height: numbersGrid.buttonHeight
192+ width: numbersGrid.buttonWidth
193+ }
194+ PinPadButton {
195+ iconName: "close"
196+ height: numbersGrid.buttonHeight
197+ width: numbersGrid.buttonWidth
227198
199+ onClicked: root.cancel()
200+ }
201+ Item {
202+ height: numbersGrid.buttonHeight
203+ width: numbersGrid.buttonWidth
204+ }
205+ PinPadButton {
206+ iconName: "tick"
207+ objectName: "confirmButton"
208+ height: numbersGrid.buttonHeight
209+ width: numbersGrid.buttonWidth
210+ enabled: root.enabled && pinentryField.text.length >= root.minPinLength
211+ visible: root.minPinLength == -1 || root.minPinLength !== root.maxPinLength
212+
213+ onClicked: root.entered(pinentryField.text)
214+ }
215+ }
228216 WrongPasswordAnimation {
229217 id: wrongPasswordAnimation
230218 objectName: "wrongPasswordAnimation"
231− target: pinentryField
219+ target: shakeContainer
232220 }
233221}
qml/Components/PinPadButton.qml+31−45
@@ -15,62 +15,48 @@
1515 */
1616
1717import QtQuick 2.0
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.1
1919
20−Item {
20+AbstractButton {
2121 id: root
2222 opacity: enabled ? 1 : 0.6
2323
2424 property alias text: label.text
25− property alias subText: subTextLabel.text
2625 property string iconName
2726
28− signal clicked()
27+ UbuntuShape {
28+ anchors.fill: parent
29+ opacity: root.pressed ? 1 : 0
30+ Behavior on opacity {
31+ UbuntuNumberAnimation {}
32+ }
33+ }
2934
30− Column {
35+ Label {
36+ id: label
3137 anchors.centerIn: parent
32− width: parent.width
33− height: childrenRect.height
34−
35− Item {
36− anchors {
37− left: parent.left
38− right: parent.right
39− }
40− height: label.visible || icon.visible ? Math.max(label.height, icon.height) : 0
41−
42− Label {
43− id: label
44− anchors.centerIn: parent
45− width: parent.width
46− horizontalAlignment: Text.AlignHCenter
47− color: "#f3f3e7"
48− fontSize: "large"
49− font.weight: Font.DemiBold
50− visible: text.length > 0
51− }
52−
53− Icon {
54− id: icon
55− height: units.gu(3)
56− width: height
57− anchors.centerIn: parent
58− name: root.iconName
59− color: "#f3f3e7"
60− visible: name.length > 0
61− }
62− }
63− Label {
64− id: subTextLabel
65− fontSize: "small"
66− color: "#f3f3e7"
67− anchors.horizontalCenter: parent.horizontalCenter
68− visible: text.length > 0
38+ horizontalAlignment: Text.AlignHCenter
39+ color: "#f3f3e7"
40+ fontSize: "x-large"
41+ font.weight: Font.DemiBold
42+ visible: text.length > 0
43+ scale: root.pressed ? 0.9 : 1
44+ Behavior on scale {
45+ UbuntuNumberAnimation {}
6946 }
7047 }
7148
72− MouseArea {
73− anchors.fill: parent
74− onClicked: root.clicked()
49+ Icon {
50+ id: icon
51+ height: units.gu(3)
52+ width: height
53+ anchors.centerIn: parent
54+ name: root.iconName
55+ color: "#f3f3e7"
56+ visible: name.length > 0
57+ scale: root.pressed ? 0.9 : 1
58+ Behavior on scale {
59+ UbuntuNumberAnimation { duration: UbuntuAnimation.SlowDuration }
60+ }
7561 }
7662}
qml/Notifications/NotificationMenuItemFactory.qml+4−1
@@ -117,7 +117,10 @@ Loader {
117117 right: parent.right
118118 }
119119 height: menuFactory.maxHeight
120− placeholderText: i18n.tr("Please enter SIM PIN")
120+ infoText: i18n.tr("Enter SIM PIN")
121+ errorText: i18n.tr("Sorry, incorrect PIN")
122+ minPinLength: 4
123+ maxPinLength: 8
121124 background: shell.background
122125
123126 onEntered: {
qml/Shell.qml+11−7
@@ -274,15 +274,19 @@ Item {
274274
275275 onShowPrompt: {
276276 if (greeter.narrowMode) {
277− var promptText = text.toLowerCase()
278277 if (isDefaultPrompt) {
279− promptText = lockscreen.alphaNumeric ?
280− i18n.tr("passphrase") : i18n.tr("passcode")
278+ if (lockscreen.alphaNumeric) {
279+ lockscreen.infoText = i18n.tr("Enter your passphrase")
280+ lockscreen.errorText = i18n.tr("Sorry, incorrect passphrase")
281+ } else {
282+ lockscreen.infoText = i18n.tr("Enter your PIN")
283+ lockscreen.errorText = i18n.tr("Sorry, incorrect PIN")
284+ }
285+ } else {
286+ lockscreen.infoText = i18n.tr("Enter your %1").arg(text.toLowerCase())
287+ lockscreen.errorText = i18n.tr("Sorry, incorrect %1").arg(text.toLowerCase())
281288 }
282− lockscreen.placeholderText = i18n.tr("Enter your %1").arg(promptText)
283− lockscreen.wrongPlaceholderText = i18n.tr("Incorrect %1").arg(promptText) +
284− "\n" +
285− i18n.tr("Please re-enter")
289+
286290 lockscreen.show();
287291 }
288292 }
tests/autopilot/unity8/shell/tests/test_lock_screen.py+2−2
@@ -54,7 +54,7 @@ class TestLockscreen(UnityTestCase):
5454 self.main_window.enter_pin_code("1234")
5555 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
5656 else:
57− self._enter_prompt_passphrase("1234")
57+ self._enter_prompt_passphrase("1234\n")
5858 self.assertThat(greeter.shown, Eventually(Equals(False)))
5959
6060 @with_lightdm_mock("single-passphrase")
@@ -86,7 +86,7 @@ class TestLockscreen(UnityTestCase):
8686 self.assertThat(pinentryField.text, Eventually(Equals("")))
8787 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
8888 else:
89− self._enter_prompt_passphrase("4231")
89+ self._enter_prompt_passphrase("4231\n")
9090 prompt = self.main_window.get_greeter().get_prompt()
9191 self.assertThat(prompt.text, Eventually(Equals("")))
9292 self.assertThat(greeter.shown, Eventually(Equals(True)))
tests/qmltests/Greeter/tst_Lockscreen.qml+65−46
@@ -26,22 +26,21 @@ import Unity.Test 0.1 as UT
2626Rectangle {
2727 id: root
2828 width: units.gu(80)
29− height: units.gu(80)
29+ height: units.gu(70)
3030 color: "orange"
3131
3232 Lockscreen {
3333 id: lockscreen
3434 anchors.fill: parent
35− anchors.rightMargin: units.gu(30)
36− placeholderText: "Please enter your PIN"
37− wrongPlaceholderText: "Incorrect PIN"
35+ anchors.rightMargin: units.gu(40)
36+ infoText: infoTextTextField.text
3837 retryText: retryCountTextField.text
38+ errorText: errorTextTextField.text
3939 alphaNumeric: pinPadCheckBox.checked
4040 minPinLength: minPinLengthTextField.text
4141 maxPinLength: maxPinLengthTextField.text
4242 username: "Lola"
4343 background: "../../../qml/graphics/phone_background.jpg"
44− infoText: infoTextTextField.text
4544 }
4645
4746 Connections {
@@ -68,7 +67,7 @@ Rectangle {
6867 } else {
6968 pinPadCheckBox.checked = true
7069 }
71− lockscreen.placeholderText = text;
70+ lockscreen.infoText = text;
7271 }
7372 }
7473
@@ -157,7 +156,14 @@ Rectangle {
157156 id: infoTextTextField
158157 width: parent.width
159158 placeholderText: "Infotext"
160− text: "+49 179 3253553"
159+ text: "Enter SIM1 PIN"
160+ }
161+
162+ TextField {
163+ id: errorTextTextField
164+ width: parent.width
165+ placeholderText: "Error text"
166+ text: "Sorry, incorrect PIN"
161167 }
162168
163169 TextField {
@@ -223,7 +229,7 @@ Rectangle {
223229 emergencyCheckBox.checked = false
224230 pinPadCheckBox.checked = data.alphanumeric
225231 waitForLockscreenReady();
226− var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
232+ var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
227233 mouseClick(emergencyButton, units.gu(1), units.gu(1))
228234 tryCompare(emergencyCheckBox, "checked", true)
229235
@@ -231,18 +237,20 @@ Rectangle {
231237
232238 function test_labels_data() {
233239 return [
234− {tag: "numeric", alphanumeric: false, placeholderText: "Please enter your PIN", username: "foobar" },
235− {tag: "alphanumeric", alphanumeric: true, placeholderText: "Please enter your password", username: "Lola" }
240+ {tag: "numeric", alphanumeric: false, infoText: "Please enter your PIN", username: "foobar" },
241+ {tag: "alphanumeric", alphanumeric: true, infoText: "Please enter your password", username: "Lola" }
236242 ]
237243 }
238244
239245 function test_labels(data) {
240246 pinPadCheckBox.checked = data.alphanumeric
241− lockscreen.placeholderText = data.placeholderText
247+ lockscreen.infoText = data.infoText
242248 waitForLockscreenReady();
243− compare(findChild(lockscreen, "pinentryField").placeholderText, data.placeholderText, "Placeholdertext is not what it should be")
244249 if (data.alphanumeric) {
250+ compare(findChild(lockscreen, "pinentryField").placeholderText, data.infoText, "Placeholdertext is not what it should be")
245251 compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
252+ } else {
253+ compare(findChild(lockscreen, "infoTextLabel").text, data.infoText, "Placeholdertext is not what it should be")
246254 }
247255 }
248256
@@ -276,10 +284,8 @@ Rectangle {
276284 var button = findChild(lockscreen, "pinPadButton" + character)
277285 mouseClick(button, units.gu(1), units.gu(1))
278286 }
279− if (data.minPinLength !== data.maxPinLength || data.minPinLength == -1) {
280− var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
281− mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
282− }
287+ var confirmButton = findChild(lockscreen, "confirmButton");
288+ mouseClick(confirmButton, units.gu(1), units.gu(1));
283289 }
284290 tryCompare(enteredLabel, "text", data.password)
285291 }
@@ -317,11 +323,9 @@ Rectangle {
317323 wait(0) // Trigger event loop to make sure the animation would start running
318324 compare(animation.running, data.animation)
319325 if (data.animation) {
320− if (data.alphanumeric) {
321− tryCompare(inputField, "placeholderText", lockscreen.wrongPlaceholderText)
322− } else {
323− var label = findChild(lockscreen, "pinentryFieldPlaceHolder");
324− tryCompare(label, "text", lockscreen.wrongPlaceholderText)
326+ if (!data.alphanumeric) {
327+ var label = findChild(lockscreen, "wrongNoticeLabel");
328+ tryCompare(label, "visible", true)
325329 }
326330 }
327331
@@ -343,24 +347,21 @@ Rectangle {
343347 maxPinLengthTextField.text = data.maxPinLength
344348 waitForLockscreenReady();
345349
346− var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
347350 var backspaceIcon = findChild(lockscreen, "backspaceIcon");
348351 var pinEntryField = findChild(lockscreen, "pinentryField");
349352
350− var autoConfirmEnabled = data.minPinLength === data.maxPinLength && data.minPinLength !== -1;
351− compare(pinPadButtonErase.iconName, autoConfirmEnabled ? "erase" : "");
352− compare(backspaceIcon.visible, !autoConfirmEnabled);
353+ compare(backspaceIcon.opacity, 0);
353354
354355 var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
355356 mouseClick(pinPadButton5, units.gu(1), units.gu(1));
356357 compare(pinEntryField.text, "5");
357358
358− if (data.minPinLength !== data.maxPinLength) {
359− mouseClick(backspaceIcon, units.gu(1), units.gu(1));
360− } else {
361− mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
362− }
359+ tryCompare(backspaceIcon, "opacity", 1);
360+
361+ mouseClick(backspaceIcon, units.gu(1), units.gu(1));
363362 compare(pinEntryField.text, "");
363+
364+ tryCompare(backspaceIcon, "opacity", 0);
364365 }
365366
366367 function test_minMaxLength_data() {
@@ -373,37 +374,55 @@ Rectangle {
373374
374375 function test_minMaxLength(data) {
375376 pinPadCheckBox.checked = false
376− minPinLengthTextField.text = data.minPinLength
377− maxPinLengthTextField.text = data.maxPinLength
377+ minPinLengthTextField.text = data.minPinLength;
378+ maxPinLengthTextField.text = data.maxPinLength;
378379 waitForLockscreenReady();
379380
380381 var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
381− var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
382− var inputField = findChild(lockscreen, "pinentryField")
382+ var backspaceButton = findChild(lockscreen, "backspaceIcon");
383+ var confirmButton = findChild(lockscreen, "confirmButton");
384+ var inputField = findChild(lockscreen, "pinentryField");
385+
386+ tryCompare(backspaceButton, "opacity", 0);
387+
388+ tryCompare(confirmButton, "visible", (data.minPinLength != data.maxPinLength) || (data.minPinLength == -1));
383389
384390 for (var i = 0; i < 10; i++) {
385391 mouseClick(pinPadButton5, units.gu(1), units.gu(1));
392+ tryCompare(backspaceButton, "opacity", 1)
386393
387− if (data.minPinLength == data.maxPinLength && data.minPinLength != -1) {
388− // auto confirm mode...
389− compare(inputField.text.length, (i+1) % data.minPinLength);
394+ if (data.maxPinLength == data.minPinLength && data.minPinLength > 0) {
395+ // Autoconfirm mode. This will automatically confirm (and with it reset)
396+ // the textfield every data.minPinLength presses
397+ if (i+1 < data.minPinLength) {
398+ compare(inputField.text.length, i + 1);
399+ } else {
400+ compare(inputField.text.length, (i+1) % data.minPinLength)
401+ }
390402 } else {
391− // manual confirm mode
392− tryCompare(pinPadButtonErase, "enabled", (i+1) >= data.minPinLength)
393403 if (data.maxPinLength == -1) {
404+ // Undefined maxLength... make sure all presses are recorded
394405 compare(inputField.text.length, i+1);
395406 } else {
407+ // We have a max length. Make sure we're only accepting maxLength presses
396408 compare(inputField.text.length, Math.min(data.maxPinLength, i+1));
397409 }
398410
411+ if (data.minPinLength == -1) {
412+ // Undefined minLength. Make sure confirm button is always enabled
413+ compare(confirmButton.enabled, true);
414+ } else {
415+ // We have a min length. Make sure the confirm button is only enabled when met.
416+ compare(confirmButton.enabled, (i+1) >= data.minPinLength);
417+ }
399418 }
400419 }
401420 }
402421
403422 function test_retryDisplay_data() {
404423 return [
405− {tag: "empty", retryText: "", shown: false},
406− {tag: "3 retries left", retryText: "3 retries left", shown: true},
424+ {tag: "empty", retryText: " "},
425+ {tag: "3 retries left", retryText: "3 retries left"},
407426 ]
408427 }
409428
@@ -412,8 +431,8 @@ Rectangle {
412431 waitForLockscreenReady();
413432
414433 retryCountTextField.text = data.retryText;
415− var label = findChild(lockscreen, "retryCountLabel")
416− compare(label.visible, data.shown);
434+ var label = findChild(lockscreen, "retryLabel")
435+ compare(label.text, data.retryText);
417436 }
418437
419438 function test_infoPopup() {
@@ -438,8 +457,8 @@ Rectangle {
438457
439458 function test_infoTextDisplay_data() {
440459 return [
441− {tag: "empty string", text: "", shown: false},
442− {tag: "hello world", text: "hello world", shown: true},
460+ {tag: "empty string", text: ""},
461+ {tag: "hello world", text: "hello world"},
443462 ]
444463 }
445464
@@ -449,7 +468,7 @@ Rectangle {
449468
450469 infoTextTextField.text = data.text;
451470 var label = findChild(lockscreen, "infoTextLabel")
452− compare(label.visible, data.shown);
471+ compare(label.text, data.text);
453472 }
454473 }
455474
tests/qmltests/tst_ShellWithPin.qml+26−11
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import Ubuntu.Components 1.0
1819import QtTest 1.0
1920import AccountsService 0.1
2021import GSettings 1.0
@@ -28,7 +29,7 @@ import "../../qml"
2829
2930Item {
3031 id: root
31− width: shell.width
32+ width: shell.width + units.gu(20)
3233 height: shell.height
3334
3435 QtObject {
@@ -49,6 +50,20 @@ Item {
4950
5051 Shell {
5152 id: shell
53+ maxFailedLogins: maxRetriesTextField.text
54+ }
55+ Column {
56+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; margins:units.gu(1) }
57+ width: units.gu(18)
58+
59+ Label {
60+ text: "Max retries:"
61+ color: "black"
62+ }
63+ TextField {
64+ id: maxRetriesTextField
65+ text: "-1"
66+ }
5267 }
5368
5469 SignalSpy {
@@ -74,7 +89,7 @@ Item {
7489 function init() {
7590 swipeAwayGreeter()
7691 shell.failedLoginsDelayAttempts = -1
77− shell.maxFailedLogins = -1
92+ maxRetriesTextField.text = "-1"
7893 }
7994
8095 function cleanup() {
@@ -139,7 +154,7 @@ Item {
139154 function test_emergencyCall() {
140155 var greeter = findChild(shell, "greeter")
141156 var lockscreen = findChild(shell, "lockscreen")
142− var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
157+ var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
143158 var panel = findChild(shell, "panel")
144159 var indicators = findChild(shell, "indicators")
145160 var launcher = findChild(shell, "launcher")
@@ -170,7 +185,7 @@ Item {
170185
171186 function test_emergencyCallCrash() {
172187 var lockscreen = findChild(shell, "lockscreen")
173− var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
188+ var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
174189 mouseClick(emergencyButton, units.gu(1), units.gu(1))
175190
176191 tryCompare(lockscreen, "shown", false)
@@ -180,7 +195,7 @@ Item {
180195
181196 function test_emergencyCallAppLaunch() {
182197 var lockscreen = findChild(shell, "lockscreen")
183− var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
198+ var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
184199 mouseClick(emergencyButton, units.gu(1), units.gu(1))
185200
186201 tryCompare(lockscreen, "shown", false)
@@ -201,21 +216,21 @@ Item {
201216 function test_wrongEntries() {
202217 shell.failedLoginsDelayAttempts = 3
203218
204− var placeHolder = findChild(shell, "pinentryFieldPlaceHolder")
205− tryCompare(placeHolder, "text", "Enter your passcode")
219+ var placeHolder = findChild(shell, "wrongNoticeLabel")
220+ tryCompare(placeHolder, "text", "")
206221
207222 enterPin("1111")
208− tryCompare(placeHolder, "text", "Incorrect passcode\nPlease re-enter")
223+ tryCompare(placeHolder, "text", "Sorry, incorrect PIN")
209224
210225 enterPin("1111")
211− tryCompare(placeHolder, "text", "Incorrect passcode\nPlease re-enter")
226+ tryCompare(placeHolder, "text", "Sorry, incorrect PIN")
212227
213228 enterPin("1111")
214− tryCompare(placeHolder, "text", "Too many incorrect attempts\nPlease wait")
229+ tryCompare(placeHolder, "text", "Too many incorrect attempts")
215230 }
216231
217232 function test_factoryReset() {
218− shell.maxFailedLogins = 3
233+ maxRetriesTextField.text = "3"
219234 resetSpy.clear()
220235
221236 enterPin("1111")