on xenial help sign in
Make swipe teases in the greeter more helpful and obvious Fixes: 1267623, 1308475, 1308477
a6f2b2dMichael Terry
po/unity8.pot+7−3
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11+"POT-Creation-Date: 2014-04-10 11:13-0400\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -84,7 +84,7 @@ msgstr ""
84msgid "See less"
84msgid "See less"
85msgstr ""
85msgstr ""
86
86
87−#: qml/Dash/DashApps.qml:38
87+#: qml/Dash/DashApps.qml:37
88msgid "Recent"
88msgid "Recent"
89msgstr ""
89msgstr ""
90
90
@@ -113,6 +113,10 @@ msgstr ""
113msgid "Send"
113msgid "Send"
114msgstr ""
114msgstr ""
115
115
116+#: qml/Greeter/Greeter.qml:151
117+msgid "Swipe to unlock"
118+msgstr ""
119+
116#: qml/Greeter/Lockscreen.qml:149
120#: qml/Greeter/Lockscreen.qml:149
117msgid "Emergency Call"
121msgid "Emergency Call"
118msgstr ""
122msgstr ""
@@ -158,7 +162,7 @@ msgstr ""
158msgid "Search"
162msgid "Search"
159msgstr ""
163msgstr ""
160
164
161−#: qml/Shell.qml:417
165+#: qml/Shell.qml:348
162#, qt-format
166#, qt-format
163msgid "Please enter %1"
167msgid "Please enter %1"
164msgstr ""
168msgstr ""
qml/Greeter/Greeter.qml+115−42
@@ -28,10 +28,10 @@ Showable {
28 property url defaultBackground
28 property url defaultBackground
29
29
30 // 1 when fully shown and 0 when fully hidden
30 // 1 when fully shown and 0 when fully hidden
31− property real showProgress: MathUtils.clamp((width + x) / width, 0, 1)
31+ property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
32
32
33 showAnimation: StandardAnimation { property: "x"; to: 0 }
33 showAnimation: StandardAnimation { property: "x"; to: 0 }
34− hideAnimation: StandardAnimation { property: "x"; to: -width }
34+ hideAnimation: __leftHideAnimation
35
35
36 property alias dragHandleWidth: dragHandle.width
36 property alias dragHandleWidth: dragHandle.width
37 property alias model: greeterContentLoader.model
37 property alias model: greeterContentLoader.model
@@ -40,19 +40,81 @@ Showable {
40 readonly property bool narrowMode: !multiUser && height > width
40 readonly property bool narrowMode: !multiUser && height > width
41 readonly property bool multiUser: LightDM.Users.count > 1
41 readonly property bool multiUser: LightDM.Users.count > 1
42
42
43− readonly property bool leftTeaserPressed: greeterContentLoader.status == Loader.Ready &&
44− greeterContentLoader.item.leftTeaserPressed
45− readonly property bool rightTeaserPressed: greeterContentLoader.status == Loader.Ready &&
46− greeterContentLoader.item.rightTeaserPressed
47−
48 readonly property int currentIndex: greeterContentLoader.currentIndex
43 readonly property int currentIndex: greeterContentLoader.currentIndex
49
44
45+ property var __leftHideAnimation: StandardAnimation { property: "x"; to: -width }
46+ property var __rightHideAnimation: StandardAnimation { property: "x"; to: width }
47+
50 signal selected(int uid)
48 signal selected(int uid)
51 signal unlocked(int uid)
49 signal unlocked(int uid)
50+ signal tease()
51+
52+ function hideRight() {
53+ if (shown) {
54+ hideAnimation = __rightHideAnimation
55+ hide()
56+ }
57+ }
58+
59+ onRequiredChanged: {
60+ // Reset hide animation to default once we're finished with it
61+ if (!required) {
62+ // Put back on left for reliable show direction and so that
63+ // if normal hide() is called, we don't animate from right.
64+ x = -width
65+ hideAnimation = __leftHideAnimation
66+ }
67+ }
68+
69+ // Bi-directional revealer
70+ DraggingArea {
71+ id: dragHandle
72+ anchors.fill: parent
73+ enabled: greeter.narrowMode || !greeter.locked
74+ orientation: Qt.Horizontal
75+ propagateComposedEvents: true
76+
77+ Component.onCompleted: {
78+ // set evaluators to baseline of dragValue == 0
79+ leftEvaluator.reset()
80+ rightEvaluator.reset()
81+ }
82+
83+ function maybeTease() {
84+ if (!greeter.locked || greeter.narrowMode)
85+ greeter.tease();
86+ }
87+
88+ onClicked: maybeTease()
89+ onDragStart: maybeTease()
90+ onPressAndHold: {} // eat event, but no need to tease, as drag will cover it
91+
92+ onDragEnd: {
93+ if (rightEvaluator.shouldAutoComplete())
94+ greeter.hideRight()
95+ else if (leftEvaluator.shouldAutoComplete())
96+ greeter.hide();
97+ else
98+ greeter.show(); // undo drag
99+ }
100+
101+ onDragValueChanged: {
102+ // dragValue is kept as a "step" value since we do this x adjusting on the fly
103+ greeter.x += dragValue
104+ }
105+
106+ EdgeDragEvaluator {
107+ id: rightEvaluator
108+ trackedPosition: dragHandle.dragValue + greeter.x
109+ maxDragDistance: parent.width
110+ direction: Direction.Rightwards
111+ }
52
112
53− onRightTeaserPressedChanged: {
113+ EdgeDragEvaluator {
54− if (rightTeaserPressed && (!locked || narrowMode) && x == 0) {
114+ id: leftEvaluator
55− teasingTimer.start();
115+ trackedPosition: dragHandle.dragValue + greeter.x
116+ maxDragDistance: parent.width
117+ direction: Direction.Leftwards
56 }
118 }
57 }
119 }
58
120
@@ -82,45 +144,56 @@ Showable {
82 }
144 }
83 }
145 }
84
146
85− Timer {
147+ onTease: showLabelAnimation.start()
86− id: teasingTimer
87− interval: 200
88− }
89
148
90− states: [
149+ Label {
91− State {
150+ id: swipeHint
92− name: "teasing"
151+ visible: greeter.shown
152+ property real baseOpacity: 0.5
153+ opacity: 0.0
154+ anchors.horizontalCenter: parent.horizontalCenter
155+ anchors.bottom: parent.bottom
156+ anchors.bottomMargin: units.gu(5)
157+ text: i18n.tr("Swipe to unlock")
158+ color: "white"
159+ font.weight: Font.Light
160+
161+ SequentialAnimation on opacity {
162+ id: showLabelAnimation
93− when: teasingTimer.running
163+ running: false
94− PropertyChanges {
164+ loops: 2
95− target: greeter
165+
96− x: -dragHandle.hintDisplacement
166+ StandardAnimation {
167+ from: 0.0
168+ to: swipeHint.baseOpacity
169+ duration: UbuntuAnimation.SleepyDuration
97 }
170 }
98− }
171+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
99− ]
172+ StandardAnimation {
100− transitions: [
173+ from: swipeHint.baseOpacity
101− Transition {
102− from: "*"
103− to: "*"
174+ to: 0.0
104− NumberAnimation {
175+ duration: UbuntuAnimation.SleepyDuration
105− target: greeter
106− property: "x"
107− duration: 300
108− easing.type: Easing.OutCubic
109 }
176 }
110 }
177 }
111− ]
178+ }
112−
113− DragHandle {
114− id: dragHandle
115
179
180+ // right side shadow
181+ Image {
182+ anchors.left: parent.right
116 anchors.top: parent.top
183 anchors.top: parent.top
117 anchors.bottom: parent.bottom
184 anchors.bottom: parent.bottom
118− anchors.right: parent.right
185+ visible: parent.required
119−
186+ fillMode: Image.Tile
120− hintDisplacement: units.gu(2)
187+ source: "../graphics/dropshadow_right.png"
121−
188+ }
122− enabled: greeter.narrowMode || !greeter.locked
123
189
124− direction: Direction.Leftwards
190+ // left side shadow
191+ Image {
192+ anchors.right: parent.left
193+ anchors.top: parent.top
194+ anchors.bottom: parent.bottom
195+ visible: parent.required
196+ fillMode: Image.Tile
197+ source: "../graphics/dropshadow_left.png"
125 }
198 }
126}
199}
qml/Greeter/GreeterContent.qml+1−10
@@ -20,15 +20,11 @@ import Ubuntu.Components 0.1
20import LightDM 0.1 as LightDM
20import LightDM 0.1 as LightDM
21import "../Components"
21import "../Components"
22
22
23−MouseArea {
23+Item {
24 id: root
24 id: root
25 anchors.fill: parent
25 anchors.fill: parent
26
26
27 property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
27 property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
28− property bool leftTeaserPressed: teasingMouseArea.pressed &&
29− teasingMouseArea.mouseX < teasingMouseArea.width / 2
30− property bool rightTeaserPressed: teasingMouseArea.pressed &&
31− teasingMouseArea.mouseX > teasingMouseArea.width / 2
32
28
33 signal selected(int uid)
29 signal selected(int uid)
34 signal unlocked(int uid)
30 signal unlocked(int uid)
@@ -74,11 +70,6 @@ MouseArea {
74 opacity: 0.4
70 opacity: 0.4
75 }
71 }
76
72
77− MouseArea {
78− id: teasingMouseArea
79− anchors.fill: parent
80− }
81−
82 Loader {
73 Loader {
83 id: loginLoader
74 id: loginLoader
84 objectName: "loginLoader"
75 objectName: "loginLoader"
qml/Greeter/Infographics.qml+3
@@ -457,5 +457,8 @@ Item {
457 !circleGrowAnimTimer.running)
457 !circleGrowAnimTimer.running)
458 infographic.model.nextDataSource()
458 infographic.model.nextDataSource()
459 }
459 }
460+
461+ onClicked: mouse.accepted = false
462+ onPressed: mouse.accepted = false
460 }
463 }
461}
464}
qml/Hud/Hud.qml+1−1
@@ -491,6 +491,6 @@ Item {
491 anchors.top: hudContentClipper.top
491 anchors.top: hudContentClipper.top
492 anchors.bottom: hudContentClipper.bottom
492 anchors.bottom: hudContentClipper.bottom
493 fillMode: Image.Tile
493 fillMode: Image.Tile
494− source: "graphics/dropshadow_right.png"
494+ source: "../graphics/dropshadow_right.png"
495 }
495 }
496}
496}
qml/Shell.qml+59−45
@@ -148,8 +148,8 @@ FocusScope {
148
148
149 available: !greeter.shown && !lockscreen.shown
149 available: !greeter.shown && !lockscreen.shown
150 hides: [stages, launcher, panel.indicators]
150 hides: [stages, launcher, panel.indicators]
151− shown: disappearingAnimationProgress !== 1.0
151+ shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
152− enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
152+ enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
153
153
154 anchors {
154 anchors {
155 fill: parent
155 fill: parent
@@ -159,18 +159,14 @@ FocusScope {
159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160 opacity: 1.0 - disappearingAnimationProgress
160 opacity: 1.0 - disappearingAnimationProgress
161 property real disappearingAnimationProgress: {
161 property real disappearingAnimationProgress: {
162− if (greeter.shown) {
162+ if (stages.overlayMode) {
163− return greeter.showProgress;
163+ return 0;
164 } else {
164 } else {
165− if (stages.overlayMode) {
166− return 0;
167− }
168 return stages.showProgress
165 return stages.showProgress
169 }
166 }
170 }
167 }
171
168
172− // FIXME: only necessary because stages.showProgress and
169+ // FIXME: only necessary because stages.showProgress is not animated
173− // greeterRevealer.animatedProgress are not animated
174 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
170 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
175 }
171 }
176 }
172 }
@@ -362,52 +358,66 @@ FocusScope {
362 }
358 }
363 }
359 }
364
360
365− Greeter {
361+ Rectangle {
366− id: greeter
362+ anchors.fill: parent
367− objectName: "greeter"
363+ color: "black"
368−
364+ opacity: greeterWrapper.showProgress * 0.8
369− available: true
365+ }
370− hides: [launcher, panel.indicators, hud]
371− shown: true
372−
373− defaultBackground: shell.background
374
366
367+ Item {
368+ // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
369+ id: greeterWrapper
370+ x: launcher.progress
375 y: panel.panelHeight
371 y: panel.panelHeight
376 width: parent.width
372 width: parent.width
377 height: parent.height - panel.panelHeight
373 height: parent.height - panel.panelHeight
378
374
379− dragHandleWidth: shell.edgeSize
375+ Behavior on x {SmoothedAnimation{velocity: 600}}
380
376
377+ readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
378+
379+ Greeter {
380+ id: greeter
381+ objectName: "greeter"
382+
383+ available: true
384+ hides: [launcher, panel.indicators, hud]
385+ shown: true
386+
387+ defaultBackground: shell.background
388+
389+ width: parent.width
390+ height: parent.height
391+
392+ dragHandleWidth: shell.edgeSize
393+
381− onShownChanged: {
394+ onShownChanged: {
382− if (shown) {
395+ if (shown) {
383− lockscreen.reset();
396+ lockscreen.reset();
384− // If there is only one user, we start authenticating with that one here.
397+ // If there is only one user, we start authenticating with that one here.
385− // If there are more users, the Greeter will handle that
398+ // If there are more users, the Greeter will handle that
386− if (LightDM.Users.count == 1) {
399+ if (LightDM.Users.count == 1) {
387− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
400+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
401+ }
402+ greeter.forceActiveFocus();
388 }
403 }
389− greeter.forceActiveFocus();
390 }
404 }
391− }
392−
393− onUnlocked: greeter.hide()
394− onSelected: {
395− // Update launcher items for new user
396− var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
397− AccountsService.user = user;
398− LauncherModel.setUser(user);
399− }
400
405
401− onLeftTeaserPressedChanged: {
406+ onUnlocked: greeter.hide()
402− if (leftTeaserPressed) {
407+ onSelected: {
403− launcher.tease();
408+ // Update launcher items for new user
409+ var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
410+ AccountsService.user = user;
411+ LauncherModel.setUser(user);
404 }
412 }
405− }
406
413
414+ onTease: launcher.tease()
415+
407− Binding {
416+ Binding {
408− target: ApplicationManager
417+ target: ApplicationManager
409− property: "suspended"
418+ property: "suspended"
410− value: greeter.shown && greeter.showProgress == 1
419+ value: greeter.shown && greeterWrapper.showProgress == 1
420+ }
411 }
421 }
412 }
422 }
413
423
@@ -538,7 +548,7 @@ FocusScope {
538 anchors.bottom: parent.bottom
548 anchors.bottom: parent.bottom
539 width: parent.width
549 width: parent.width
540 dragAreaWidth: shell.edgeSize
550 dragAreaWidth: shell.edgeSize
541− available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
551+ available: edgeDemo.launcherEnabled
542
552
543 onShowDashHome: {
553 onShowDashHome: {
544 if (edgeDemo.running)
554 if (edgeDemo.running)
@@ -553,6 +563,10 @@ FocusScope {
553 launcher.hide();
563 launcher.hide();
554 }
564 }
555 }
565 }
566+ if (greeter.shown) {
567+ greeter.hideRight();
568+ launcher.hide();
569+ }
556 }
570 }
557 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
571 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
558 onLauncherApplicationSelected: {
572 onLauncherApplicationSelected: {
qml/graphics/dropshadow_left@20.pngnew file

Binary file not shown.

qml/Hud/graphics/dropshadow_right@20.png → qml/graphics/dropshadow_right@20.pngrenamed

Binary file not shown.

tests/qmltests/Greeter/tst_Phone.qml+10−50
@@ -61,6 +61,12 @@ Item {
61 signalName: "unlocked"
61 signalName: "unlocked"
62 }
62 }
63
63
64+ SignalSpy {
65+ id: teaseSpy
66+ target: greeter
67+ signalName: "tease"
68+ }
69+
64 UT.UnityTestCase {
70 UT.UnityTestCase {
65 name: "Greeter"
71 name: "Greeter"
66 when: windowShown
72 when: windowShown
@@ -82,56 +88,10 @@ Item {
82 }
88 }
83
89
84 function test_teasingArea(data) {
90 function test_teasingArea(data) {
85− tryCompare(greeter, "leftTeaserPressed", false)
91+ teaseSpy.clear()
86− tryCompare(greeter, "rightTeaserPressed", false)
87− mousePress(greeter, data.posX, greeter.height - units.gu(1))
88− tryCompare(greeter, "leftTeaserPressed", data.leftPressed)
89− tryCompare(greeter, "rightTeaserPressed", data.rightPressed)
90− mouseRelease(greeter, data.posX, greeter.height - units.gu(1))
91− tryCompare(greeter, "leftTeaserPressed", false)
92− tryCompare(greeter, "rightTeaserPressed", false)
93− }
94−
95− function test_teaseLockedUnlocked_data() {
96− return [
97− {tag: "unlocked", locked: false, moved: false},
98− {tag: "locked", locked: true, moved: false},
99− {tag: "moved", locked: false, moved: true}
100− ];
101− }
102−
103− function test_teaseLockedUnlocked(data) {
104− tryCompare(greeter, "rightTeaserPressed", false);
105− tryCompare(greeter, "x", 0);
106− greeter.locked = data.locked;
107−
108− // simulate greeter being in the middle of a swipe
109− if (data.moved) {
110− greeter.x = -units.gu(4);
111− tryCompare(greeter, "x", -units.gu(4));
112− }
113−
114− mouseClick(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
92+ mouseClick(greeter, data.posX, greeter.height - units.gu(1))
115− greeter.minX = 0; // This is needed because the transition actually makes x jump once before animating
116−
117− if (!data.moved) {
118− // Check if it has been moved over by 2 GUs. Give it a 2 pixel grace area
119− // because animation duration and teaseTimer are the same duration and
120− // might cause slight offsets
121− tryCompareFunction(function() { return greeter.minX <= -units.gu(2) + 2}, true);
122− } else {
123− // waiting 100ms to make sure nothing moves
124− wait(100);
93+ teaseSpy.wait()
125− compare(greeter.minX, 0, "Greeter teasing not disabled even though it's locked or moving.");
94+ tryCompare(teaseSpy, "count", 1)
126− }
127−
128− // Restore position in case we moved it for the test
129− if (data.moved) {
130− greeter.x = 0;
131− }
132−
133− // Wait until we're back to 0
134− tryCompareFunction(function() { return greeter.x;}, 0);
135 }
95 }
136
96
137 function test_statsWelcomeScreen() {
97 function test_statsWelcomeScreen() {
tests/qmltests/Greeter/tst_Tablet.qml+36−8
@@ -54,6 +54,12 @@ Item {
54 signalName: "selected"
54 signalName: "selected"
55 }
55 }
56
56
57+ SignalSpy {
58+ id: teaseSpy
59+ target: greeter
60+ signalName: "tease"
61+ }
62+
57 UT.UnityTestCase {
63 UT.UnityTestCase {
58 name: "Greeter"
64 name: "Greeter"
59 when: windowShown
65 when: windowShown
@@ -325,14 +331,36 @@ Item {
325 }
331 }
326
332
327 function test_teasingArea(data) {
333 function test_teasingArea(data) {
328− tryCompare(greeter, "leftTeaserPressed", false)
334+ teaseSpy.clear()
329− tryCompare(greeter, "rightTeaserPressed", false)
330− mousePress(greeter, data.posX, greeter.height - units.gu(1))
335+ mouseClick(greeter, data.posX, greeter.height - units.gu(1))
331− tryCompare(greeter, "leftTeaserPressed", data.leftPressed)
336+ teaseSpy.wait()
332− tryCompare(greeter, "rightTeaserPressed", data.rightPressed)
337+ tryCompare(teaseSpy, "count", 1)
338+ }
339+
340+ function test_teaseLockedUnlocked_data() {
341+ return [
342+ {tag: "unlocked", locked: false, narrow: false},
343+ {tag: "locked", locked: true, narrow: false},
344+ ];
345+ }
346+
347+ function test_teaseLockedUnlocked(data) {
348+ teaseSpy.clear()
349+ greeter.locked = data.locked;
350+
333− mouseRelease(greeter, data.posX, greeter.height - units.gu(1))
351+ mouseClick(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
334− tryCompare(greeter, "leftTeaserPressed", false)
352+
335− tryCompare(greeter, "rightTeaserPressed", false)
353+ if (!data.locked || data.narrow) {
354+ teaseSpy.wait()
355+ tryCompare(teaseSpy, "count", 1);
356+ } else {
357+ // waiting 100ms to make sure nothing happens
358+ wait(100);
359+ compare(teaseSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
360+ }
361+
362+ // Reset value
363+ greeter.locked = false;
336 }
364 }
337
365
338 function test_dbus_set_active_entry() {
366 function test_dbus_set_active_entry() {