on xenial help sign in
Merge in tutorial-fixups
commit8d5437fa9af33ebe2940a0c4a2b1ef776526cc54 authorMichael Terry <michael.terry@canonical.com> dateFri 10 Jun 2016, 10:36 (10 years ago) parent04e7bbbaa4dd14
14 files changed+262−486
qml/Components/Dialogs.qml+3−1 qml/Notifications/Notifications.qml+1 qml/Shell.qml+12−8 qml/Tutorial/InactivityTimer.qml+26−10 qml/Tutorial/Tutorial.qml+7−6 qml/Tutorial/TutorialBottom.qml−124 qml/Tutorial/TutorialContent.qml+51−134 qml/Tutorial/TutorialLeft.qml+2−2 qml/Tutorial/TutorialLeftLong.qml+3−1 qml/Tutorial/TutorialPage.qml+2−2 qml/Tutorial/TutorialRight.qml+2−2 qml/Tutorial/TutorialTop.qml+2−3 tests/qmltests/Tutorial/tst_Tutorial.qml+151−183 tests/qmltests/tst_Shell.qml−10
qml/Components/Dialogs.qml+3−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@ import "../Greeter"
2727Item {
2828 id: root
2929
30+ readonly property alias hasActiveDialog: dialogLoader.active
31+
3032 // to be set from outside, useful mostly for testing purposes
3133 property var unitySessionService: DBusUnitySessionService
3234 property var closeAllApps: function() {
qml/Notifications/Notifications.qml+1
@@ -26,6 +26,7 @@ ListView {
2626 objectName: "notificationList"
2727 interactive: false
2828
29+ readonly property bool hasNotification: count > 1 // placeholder is index 0
2930 property real margin
3031 property bool hasMouse
3132 property url background: ""
qml/Shell.qml+12−8
@@ -116,10 +116,9 @@ StyledItem {
116116 }
117117 function _onMainAppChanged(appId) {
118118 if (wizard.active && appId != "" && appId != "unity8-dash") {
119− // If this happens on first boot, we may be in edge
120− // tutorial or wizard while receiving a call. But a call
121− // is more important than wizard so just bail out of those.
122− tutorial.finish();
119+ // If this happens on first boot, we may be in the
120+ // wizard while receiving a call. But a call is more
121+ // important than the wizard so just bail out of it.
123122 wizard.hide();
124123 }
125124
@@ -320,7 +319,7 @@ StyledItem {
320319 Binding {
321320 target: applicationsDisplayLoader.item
322321 property: "inverseProgress"
323− value: greeter && greeter.locked ? 0 : launcher.progress
322+ value: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
324323 }
325324 Binding {
326325 target: applicationsDisplayLoader.item
@@ -504,7 +503,8 @@ StyledItem {
504503 launcher.fadeOut();
505504 }
506505
507− if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
506+ if (!greeter.locked && tutorial.launcherLongSwipeEnabled
507+ && ApplicationManager.focusedApplicationId != "unity8-dash") {
508508 ApplicationManager.requestFocusApplication("unity8-dash")
509509 launcher.fadeOut();
510510 }
@@ -623,8 +623,12 @@ StyledItem {
623623 objectName: "tutorial"
624624 anchors.fill: parent
625625
626− paused: callManager.hasCalls || greeter.shown
627− keyboardVisible: inputMethod.state === "shown"
626+ paused: callManager.hasCalls || !greeter || greeter.shown ||
627+ wizard.active
628+ delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
629+ inputMethod.state === "shown" ||
630+ (launcher.shown && !launcher.lockedVisible) ||
631+ panel.indicators.shown || stage.dragProgress > 0
628632 usageScenario: shell.usageScenario
629633 lastInputTimestamp: inputFilter.lastInputTimestamp
630634 launcher: launcher
qml/Tutorial/InactivityTimer.qml+26−10
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -18,36 +18,52 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919
2020Item {
21− readonly property alias running: internalTimer.running
22− property alias interval: internalTimer.interval
21+ id: root
22+
23+ readonly property bool running: delayTimer.running || inactivityTimer.running
24+ property int interval: 3000
2325 property var page
2426 property int lastInputTimestamp
2527
2628 function start() {
27− internalTimer.start();
29+ if (!delayTimer.running && !inactivityTimer.running) {
30+ delayTimer.start();
31+ }
2832 }
2933
3034 ////
3135
3236 onLastInputTimestampChanged: {
33− if (internalTimer.running) {
34− internalTimer.restart();
37+ if (inactivityTimer.running) {
38+ inactivityTimer.restart();
3539 }
3640 }
3741
3842 Connections {
3943 target: page
4044 onIsReadyChanged: {
41− if (page.isReady && internalTimer.running) {
42− internalTimer.restart();
45+ if (page.isReady && inactivityTimer.running) {
46+ inactivityTimer.restart();
47+ }
48+ }
49+ onPausedChanged: {
50+ if (root.paused) {
51+ delayTimer.stop();
52+ inactivityTimer.stop();
4353 }
4454 }
4555 }
4656
4757 Timer {
48− id: internalTimer
58+ id: delayTimer
59+ interval: Math.max(root.interval - 3000, 0)
60+ onTriggered: inactivityTimer.start()
61+ }
62+
63+ Timer {
64+ id: inactivityTimer
4965
50− interval: 3000
66+ interval: Math.min(root.interval, 3000)
5167
5268 onTriggered: {
5369 if (page.isReady) {
qml/Tutorial/Tutorial.qml+7−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -26,17 +26,18 @@ import AccountsService 0.1
2626Item {
2727 id: root
2828
29− property alias active: loader.active
29+ readonly property alias active: loader.active
3030
3131 property Item launcher
3232 property Item panel
3333 property Item stage
3434 property string usageScenario
35− property bool paused
36− property bool keyboardVisible
35+ property bool paused // hide any existing tutorial and don't show new ones
36+ property bool delayed // don't show new tutorials
3737 property int lastInputTimestamp
3838
3939 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
40+ readonly property bool launcherLongSwipeEnabled: loader.item ? loader.item.launcherLongSwipeEnabled : true
4041 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
4142 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
4243 readonly property bool running: loader.item ? loader.item.running : false
@@ -85,8 +86,8 @@ Item {
8586
8687 Binding {
8788 target: loader.item
88− property: "keyboardVisible"
89− value: root.keyboardVisible
89+ property: "delayed"
90+ value: root.delayed
9091 }
9192
9293 Binding {
qml/Tutorial/TutorialBottom.qmldeleted−124
@@ -1,124 +0,0 @@
1−/*
2− * Copyright (C) 2015 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.4
18−import Ubuntu.Components 1.3
19−import Ubuntu.Gestures 0.1
20−import Unity.Application 0.1
21−
22−TutorialPage {
23− id: root
24−
25− property string usageScenario
26− property var stage
27− property var application: null
28−
29− // This page is a bit fragile. It relies on knowing how the app beneath
30− // the shell will react to a drag. What we do is put a monitor-only DDA
31− // at the bottom of the page (so that we know when the drag is finished)
32− // and pass the events on through to the app. Thus, it sees the drag and
33− // brings its bottom edge up.
34− //
35− // Unfortunately, each app is on its own when implementing the bottom edge
36− // drag. Most share copied-and-pasted code right now, but they will
37− // eventually consolidate on a version of DirectionalDragArea that will
38− // land in the SDK (making our guessing job easier). Though, also in the
39− // future, this whole bottom tutorial component will also land in the SDK,
40− // rendering our version here obsolete.
41− //
42− // Anyway, for the moment, we base our guesses on the copied-and-pasted
43− // code used in several of the core apps and only bring this component
44− // up if we are in those core apps.
45−
46− readonly property real mainStageWidth: stage.width - sideStageWidth
47− readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?
48− stage.sideStageWidth : 0
49− readonly property bool isMainStageApp: usageScenario !== "tablet" ||
50− stage.stageFocusedSurface === ApplicationInfoInterface.MainStage
51− readonly property real dragAreaHeight: units.gu(3) // based on PageWithBottomEdge.qml
52− readonly property real targetDistance: height * 0.2 + dragAreaHeight // based on PageWithBottomEdge.qml
53−
54− opacityOverride: dragArea.dragging ? 1 - (dragArea.distance / targetDistance) : 1
55−
56− mouseArea {
57− anchors.bottomMargin: root.dragAreaHeight
58− }
59−
60− background {
61− sourceSize.height: 1916
62− sourceSize.width: 1080
63− source: Qt.resolvedUrl("graphics/background2.png")
64− rotation: 180
65− }
66−
67− arrow {
68− anchors.bottom: root.bottom
69− anchors.bottomMargin: units.gu(3)
70− anchors.horizontalCenter: label.horizontalCenter
71− anchors.horizontalCenterOffset: -(label.width - label.contentWidth) / 2
72− rotation: 90
73− }
74−
75− label {
76− text: !application ? "" :
77− application.appId === "address-book-app" ?
78− i18n.tr("Swipe up to add a contact") :
79− application.appId === "com.ubuntu.calculator_calculator" ?
80− i18n.tr("Swipe up for favorite calculations") :
81− application.appId === "dialer-app" ?
82− i18n.tr("Swipe up for recent calls") :
83− application.appId === "messaging-app" ?
84− i18n.tr("Swipe up to create a message") :
85− i18n.tr("Swipe up to manage the app") // shouldn't be used
86− anchors.bottom: arrow.top
87− anchors.bottomMargin: units.gu(3)
88− anchors.left: root.left
89− anchors.leftMargin: (label.width - label.contentWidth) / 2 + sideMargin +
90− (isMainStageApp ? 0 : mainStageWidth)
91− width: (isMainStageApp ? mainStageWidth : sideStageWidth) - sideMargin * 2
92−
93− readonly property real sideMargin: units.gu(4)
94− }
95−
96− // Watches drag events but does not intercept them, so that the app beneath
97− // will still drag the bottom edge up.
98− SwipeArea {
99− id: dragArea
100− grabGesture: false
101− direction: Direction.Upwards
102− anchors.left: parent.left
103− anchors.right: parent.right
104− anchors.bottom: parent.bottom
105− height: root.dragAreaHeight
106−
107− // Apps currently don't use DDA. DDA will stop a gesture if
108− // horizontal motion is detected. But our apps won't. So turn off
109− // that gesture cleverness on our part, it will only get us out of sync.
110− immediateRecognition: true
111− }
112−
113− MouseArea {
114− // A second mouse area because in tablet mode, we only want to let the
115− // user drag up on one of the stages, not both. So we want to cover
116− // the second bottom edge with an event eater.
117− enabled: root.usageScenario === "tablet"
118− height: root.dragAreaHeight
119− width: isMainStageApp ? sideStageWidth : mainStageWidth
120− anchors.bottom: parent.bottom
121− anchors.left: isMainStageApp ? undefined : parent.left
122− anchors.right: isMainStageApp ? parent.right : undefined
123− }
124−}
qml/Tutorial/TutorialContent.qml+51−134
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -26,19 +26,21 @@ Item {
2626 property Item panel
2727 property Item stage
2828 property string usageScenario
29− property bool paused
30− property bool keyboardVisible
29+ property bool paused: true // default to true so that we won't start until top level is all ready
30+ property bool delayed: true // same
3131 property var lastInputTimestamp
3232
3333 readonly property bool launcherEnabled: !running
3434 || tutorialLeftLoader.shown
3535 || tutorialLeftLongLoader.shown
36+ readonly property bool launcherLongSwipeEnabled: tutorialLeftLongLoader.shown
37+ || tutorialLeftLongLoader.skipped
3638 readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
3739 readonly property bool panelEnabled: !running || tutorialTopLoader.shown
3840 readonly property bool running: tutorialLeftLoader.shown
41+ || tutorialLeftLongLoader.shown
3942 || tutorialTopLoader.shown
4043 || tutorialRightLoader.shown
41− || tutorialBottomLoader.shown
4244
4345 signal finished()
4446
@@ -57,16 +59,11 @@ Item {
5759 root.usageScenario === "phone" ||
5860 root.usageScenario === "tablet"
5961
60− property var focusedApp: ApplicationManager.focusedApplicationId
61− ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
62− : null
63−
6462 function haveShown(tutorialId) {
6563 return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
6664 }
6765
68− property bool endPointsFinished: tutorialRightLoader.skipped &&
69− tutorialBottomLoader.skipped
66+ property bool endPointsFinished: tutorialRightLoader.skipped
7067 onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
7168 }
7269
@@ -88,15 +85,14 @@ Item {
8885 hides: [launcher, panel.indicators]
8986 paused: root.paused
9087
91− isReady: !tutorialLeftLoader.skipped && !paused && !keyboardVisible &&
92− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
88+ isReady: !tutorialLeftLoader.skipped && !paused && !delayed
9389
94− // Use an idle timer here, because when constructed, all our isReady variables will be false.
95− // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
96− Timer {
90+ InactivityTimer {
9791 id: tutorialLeftTimer
98− interval: 0
99− onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
92+ objectName: "tutorialLeftTimer"
93+ interval: 20000
94+ lastInputTimestamp: root.lastInputTimestamp
95+ page: parent
10096 }
10197
10298 onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
@@ -105,86 +101,71 @@ Item {
105101 }
106102
107103 Loader {
108− id: tutorialLeftLongLoader
109− objectName: "tutorialLeftLongLoader"
104+ id: tutorialTopLoader
105+ objectName: "tutorialTopLoader"
110106 anchors.fill: parent
111107
112− readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
108+ readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
113109 readonly property bool shown: item && item.shown
114110 active: !skipped || (item && item.visible)
115111 onSkippedChanged: if (skipped && shown) item.hide()
116112
117− sourceComponent: TutorialLeftLong {
118− id: tutorialLeftLong
119− objectName: "tutorialLeftLong"
113+ sourceComponent: TutorialTop {
114+ id: tutorialTop
115+ objectName: "tutorialTop"
120116 anchors.fill: parent
121− launcher: root.launcher
117+ panel: root.panel
122118 hides: [launcher, panel.indicators]
123119 paused: root.paused
124120
125− skipped: tutorialLeftLongLoader.skipped
126− isReady: tutorialLeftLoader.skipped && !skipped && !paused && !keyboardVisible &&
127− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
121+ skipped: tutorialTopLoader.skipped
122+ isReady: tutorialLeftLoader.skipped && !skipped && !paused && !delayed
128123
129− Timer {
130− id: tutorialLeftLongTimer
131− objectName: "tutorialLeftLongTimer"
132− interval: 5000
133− onTriggered: {
134− if (parent.isReady) {
135− if (!parent.shown) {
136− parent.show();
137− }
138− } else if (!parent.skipped) {
139− restart();
140− }
141− }
124+ InactivityTimer {
125+ id: tutorialTopTimer
126+ objectName: "tutorialTopTimer"
127+ interval: 60000
128+ lastInputTimestamp: root.lastInputTimestamp
129+ page: parent
142130 }
143131
144− onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
145− onFinished: AccountsService.markDemoEdgeCompleted("left-long")
132+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
133+ onFinished: AccountsService.markDemoEdgeCompleted("top")
146134 }
147135 }
148136
149137 Loader {
150− id: tutorialTopLoader
151− objectName: "tutorialTopLoader"
138+ id: tutorialLeftLongLoader
139+ objectName: "tutorialLeftLongLoader"
152140 anchors.fill: parent
153141
154− readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
142+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
155143 readonly property bool shown: item && item.shown
156144 active: !skipped || (item && item.visible)
157145 onSkippedChanged: if (skipped && shown) item.hide()
158146
159− sourceComponent: TutorialTop {
160− id: tutorialTop
161− objectName: "tutorialTop"
147+ sourceComponent: TutorialLeftLong {
148+ id: tutorialLeftLong
149+ objectName: "tutorialLeftLong"
162150 anchors.fill: parent
163− panel: root.panel
151+ launcher: root.launcher
164152 hides: [launcher, panel.indicators]
165153 paused: root.paused
166154
167− skipped: tutorialTopLoader.skipped
168− isReady: tutorialLeftLongLoader.skipped && !skipped && !paused && !keyboardVisible &&
169− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
170−
171− // We fire 30s after left edge tutorial, with at least 3s of inactivity
155+ skipped: tutorialLeftLongLoader.skipped
156+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !delayed &&
157+ ApplicationManager.focusedApplicationId != "unity8-dash"
172158
173159 InactivityTimer {
174− id: tutorialTopInactivityTimer
160+ id: tutorialLeftLongTimer
161+ objectName: "tutorialLeftLongTimer"
162+ interval: 5000
175163 lastInputTimestamp: root.lastInputTimestamp
176164 page: parent
177165 }
178166
179− Timer {
180− id: tutorialTopTimer
181− objectName: "tutorialTopTimer"
182− interval: 27000
183− onTriggered: tutorialTopInactivityTimer.start()
184− }
185−
186− onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
187− onFinished: AccountsService.markDemoEdgeCompleted("top")
167+ onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
168+ onFinished: AccountsService.markDemoEdgeCompleted("left-long")
188169 }
189170 }
190171
@@ -208,83 +189,19 @@ Item {
208189 paused: root.paused
209190
210191 skipped: tutorialRightLoader.skipped
211− isReady: tutorialTopLoader.skipped && !skipped && !paused && !keyboardVisible &&
212− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
213− ApplicationManager.count >= 3
192+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !delayed &&
193+ ApplicationManager.count >= 4
214194
215195 InactivityTimer {
216− id: tutorialRightInactivityTimer
217− objectName: "tutorialRightInactivityTimer"
196+ id: tutorialRightTimer
197+ objectName: "tutorialRightTimer"
198+ interval: 10000
218199 lastInputTimestamp: root.lastInputTimestamp
219200 page: parent
220201 }
221202
222− Connections {
223− target: d
224− onFocusedAppChanged: {
225− if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
226− && d.focusedApp.state === ApplicationInfoInterface.Starting) {
227− tutorialRight.show();
228− }
229− }
230− }
231−
232− onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
203+ onIsReadyChanged: if (isReady && !shown) tutorialRightTimer.start()
233204 onFinished: AccountsService.markDemoEdgeCompleted("right")
234205 }
235206 }
236−
237− Loader {
238− id: tutorialBottomLoader
239− objectName: "tutorialBottomLoader"
240− anchors.fill: parent
241−
242− // See TutorialBottom.qml for an explanation of why we only support
243− // certain apps.
244− readonly property var supportedApps: ["address-book-app",
245− "com.ubuntu.calculator_calculator",
246− "dialer-app",
247− "messaging-app"]
248− readonly property bool skipped: {
249− if (!d.mobileScenario) {
250− return true;
251− }
252− for (var i = 0; i < supportedApps.length; i++) {
253− if (!d.haveShown("bottom-" + supportedApps[i])) {
254− return false;
255− }
256− }
257− return true;
258− }
259− readonly property bool shown: item && item.shown
260− readonly property bool haveShownFocusedApp: d.focusedApp &&
261− d.haveShown("bottom-" + d.focusedApp.appId)
262− readonly property bool mightShow: !skipped && d.focusedApp &&
263− supportedApps.indexOf(d.focusedApp.appId) !== -1 &&
264− !haveShownFocusedApp
265− active: !skipped || (item && item.visible)
266− onHaveShownFocusedAppChanged: if (haveShownFocusedApp && shown) hide()
267− onSkippedChanged: if (skipped && shown) item.hide()
268−
269− sourceComponent: TutorialBottom {
270− id: tutorialBottom
271− objectName: "tutorialBottom"
272− anchors.fill: parent
273− hides: [launcher, panel.indicators]
274− paused: root.paused
275− usageScenario: root.usageScenario
276− stage: root.stage
277− application: d.focusedApp
278−
279− skipped: tutorialBottomLoader.skipped
280− isReady: !tutorialBottomLoader.skipped && !paused && !keyboardVisible &&
281− !tutorialLeftLoader.shown && !tutorialLeftLongLoader.shown &&
282− !tutorialTopLoader.shown && !tutorialRightLoader.shown &&
283− tutorialBottomLoader.mightShow &&
284− d.focusedApp.state === ApplicationInfoInterface.Running
285−
286− onIsReadyChanged: if (isReady && !shown) show()
287− onFinished: AccountsService.markDemoEdgeCompleted("bottom-" + d.focusedApp.appId)
288− }
289− }
290207}
qml/Tutorial/TutorialLeft.qml+2−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -44,7 +44,7 @@ TutorialPage {
4444 }
4545
4646 label {
47− text: i18n.tr("Swipe from the left edge to open the launcher")
47+ text: i18n.tr("Short swipe from the left edge to open the launcher")
4848 anchors.left: arrow.right
4949 anchors.leftMargin: units.gu(3)
5050 anchors.right: root.right
qml/Tutorial/TutorialLeftLong.qml+3−1
@@ -23,7 +23,9 @@ TutorialPage {
2323
2424 property var launcher
2525
26− opacityOverride: 1 - launcher.dragDistance / launcher.minimizeDistance
26+ // Unlike other tutorials, this one can't be skipped before we show it, so
27+ // only set opacityOverride if we're already shown.
28+ opacityOverride: shown ? 1 - launcher.dragDistance / launcher.minimizeDistance : 1
2729
2830 mouseArea {
2931 anchors.leftMargin: launcher.dragAreaWidth
qml/Tutorial/TutorialPage.qml+2−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -46,7 +46,7 @@ Showable {
4646 onOpacityOverrideChanged: {
4747 if (opacityOverride <= 0) {
4848 d.showOnUnpause = false;
49− hide();
49+ hideNow();
5050 }
5151 }
5252 property real _showOpacity: 0
qml/Tutorial/TutorialRight.qml+2−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -53,7 +53,7 @@ TutorialPage {
5353 label {
5454 text: root.usageScenario === "desktop" ?
5555 i18n.tr("Push your mouse against the right edge to view your open apps") :
56− i18n.tr("Short or long swipe from the right edge to view your open apps")
56+ i18n.tr("Swipe from the right edge to view your open apps")
5757 anchors.right: arrow.left
5858 anchors.rightMargin: units.gu(2) - (label.width - label.contentWidth)
5959 anchors.verticalCenter: arrow.verticalCenter
qml/Tutorial/TutorialTop.qml+2−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -50,8 +50,7 @@ TutorialPage {
5050 }
5151
5252 label {
53− text: d.landscape ? i18n.tr("Swipe from the top right edge to open the notification bar")
54− : i18n.tr("Swipe from the top edge to open the notification bar")
53+ text: i18n.tr("Swipe from the top edge to access notifications and quick settings")
5554 anchors.top: arrow.bottom
5655 anchors.topMargin: units.gu(3)
5756 anchors.horizontalCenter: arrow.horizontalCenter
tests/qmltests/Tutorial/tst_Tutorial.qml+151−183
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ import Ubuntu.Components 1.3
2222import Ubuntu.Components.ListItems 1.3
2323import Ubuntu.Telephony 0.1 as Telephony
2424import Unity.Application 0.1
25+import Unity.Notifications 1.0
2526import Unity.Test 0.1 as UT
2627import Utils 0.1
2728
@@ -55,6 +56,33 @@ Rectangle {
5556 phoneNumber: "+447812221111"
5657 }
5758
59+ Component {
60+ id: mockNotification
61+ QtObject {}
62+ }
63+
64+ ListModel {
65+ id: mockNotificationsModel
66+
67+ function getRaw(id) {
68+ return mockNotification.createObject(mockNotificationsModel)
69+ }
70+ }
71+
72+ function addNotification() {
73+ var n = {
74+ type: Notification.Confirmation,
75+ hints: {},
76+ summary: "",
77+ body: "",
78+ icon: "",
79+ secondaryIcon: "",
80+ actions: []
81+ };
82+
83+ mockNotificationsModel.append(n);
84+ }
85+
5886 Component.onCompleted: {
5987 // must set the mock mode before loading the Shell
6088 LightDM.Greeter.mockMode = "single-pin";
@@ -231,6 +259,8 @@ Rectangle {
231259 prepareShell();
232260
233261 var tutorialLeft = findChild(shell, "tutorialLeft");
262+ var tutorialLeftTimer = findInvisibleChild(tutorialLeft, "tutorialLeftTimer");
263+ tutorialLeftTimer.interval = 0;
234264 tryCompare(tutorialLeft, "opacity", 1);
235265 }
236266
@@ -260,6 +290,7 @@ Rectangle {
260290 // reload our test subject to get it in a fresh state once again
261291 shellLoader.active = true;
262292
293+ mockNotificationsModel.clear();
263294 tryCompare(shellLoader, "status", Loader.Ready);
264295 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
265296 }
@@ -300,40 +331,41 @@ Rectangle {
300331 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
301332
302333 tutorialTopTimer.interval = 1;
303− AccountsService.demoEdgesCompleted = ["left", "left-long"];
334+ AccountsService.demoEdgesCompleted = ["left"];
304335
305336 tryCompare(tutorialLeftLoader, "active", false);
306337 tryCompare(tutorialTop, "shown", true);
307338 tryCompare(tutorialTop, "opacity", 1);
308339 }
309340
341+ function openTutorialLeftLong() {
342+ var tutorialLeftLong = findChild(shell, "tutorialLeftLong");
343+ var tutorialLeftLongTimer = findChild(tutorialLeftLong, "tutorialLeftLongTimer");
344+
345+ AccountsService.demoEdgesCompleted = ["left", "top"];
346+ ApplicationManager.startApplication("gallery-app");
347+
348+ tutorialLeftLongTimer.interval = 1;
349+ tryCompare(tutorialLeftLong, "shown", true);
350+ tryCompare(tutorialLeftLong, "opacity", 1);
351+ }
352+
310353 function openTutorialRight() {
311354 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
312355 var tutorialRight = findChild(shell, "tutorialRight");
356+ var tutorialRightTimer = findChild(tutorialRight, "tutorialRightTimer");
313357
314− AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
358+ AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
315359 ApplicationManager.startApplication("gallery-app");
316360 ApplicationManager.startApplication("facebook-webapp");
361+ ApplicationManager.startApplication("camera-app");
317362
318363 tryCompare(tutorialLeftLoader, "active", false);
364+ tutorialRightTimer.interval = 1;
319365 tryCompare(tutorialRight, "shown", true);
320366 tryCompare(tutorialRight, "opacity", 1);
321367 }
322368
323− function openTutorialBottom() {
324− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
325− var tutorialBottom = findChild(shell, "tutorialBottom");
326−
327− AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right"];
328− tryCompare(tutorialLeftLoader, "active", false);
329−
330− ApplicationManager.startApplication("dialer-app");
331− ApplicationManager.requestFocusApplication("dialer-app");
332−
333− tryCompare(tutorialBottom, "shown", true);
334− tryCompare(tutorialBottom, "opacity", 1);
335− }
336−
337369 function test_tutorialLeftEdges() {
338370 var tutorial = findChild(shell, "tutorial");
339371 var tutorialLeft = findChild(tutorial, "tutorialLeft");
@@ -396,19 +428,21 @@ Rectangle {
396428 tryCompare(launcher, "state", "visible");
397429 }
398430
399− function test_tutorialLeftLongDrag() {
400− // Just confirm that a long drag ("dash" drag) doesn't confuse us
431+ function test_tutorialLeftLongFinish() {
432+ AccountsService.demoEdgesCompleted = ["left", "top"];
433+
434+ var tutorialLeftLongTimer = findInvisibleChild(shell, "tutorialLeftLongTimer");
435+ tutorialLeftLongTimer.interval = 1;
401436
402− // So that we actually switch to dash and launcher hides
403437 ApplicationManager.startApplication("gallery-app");
404438
405− var launcher = findChild(shell, "launcher");
406− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
439+ var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
440+ tryCompare(tutorialLeftLongLoader, "shown", true);
407441
408442 touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
409443
410− tryCompare(tutorialLeftLoader, "shown", false);
411− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long"]);
444+ tryCompare(tutorialLeftLongLoader, "shown", false);
445+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "left-long"]);
412446 }
413447
414448 function test_tutorialLeftAutoSkipped() {
@@ -423,6 +457,24 @@ Rectangle {
423457 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
424458 }
425459
460+ function test_tutorialLongLeftSwipeDisabled() {
461+ // Test that a long left swipe is disabled until we get to or pass
462+ // the long left tutorial.
463+ var tutorial = findChild(shell, "tutorial");
464+
465+ verify(!tutorial.launcherLongSwipeEnabled);
466+
467+ openTutorialLeftLong();
468+ verify(tutorial.launcherLongSwipeEnabled);
469+
470+ openTutorialRight();
471+ verify(tutorial.launcherLongSwipeEnabled);
472+
473+ AccountsService.demoEdges = false;
474+ tryCompare(tutorial, "running", false);
475+ verify(tutorial.launcherLongSwipeEnabled);
476+ }
477+
426478 function test_tutorialTopEdges() {
427479 var tutorial = findChild(shell, "tutorial");
428480 var tutorialTop = findChild(tutorial, "tutorialTop");
@@ -451,7 +503,7 @@ Rectangle {
451503 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
452504
453505 tryCompare(tutorialTopLoader, "shown", false);
454− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
506+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
455507 tryCompare(panel.indicators, "fullyOpened", true);
456508 }
457509
@@ -477,12 +529,12 @@ Rectangle {
477529
478530 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
479531 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
480− AccountsService.demoEdgesCompleted = ["left", "left-long"];
532+ AccountsService.demoEdgesCompleted = ["left"];
481533 tryCompare(tutorialLeftLoader, "active", false);
482534 verify(!tutorialTopLoader.shown);
483535
484536 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
485− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
537+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
486538 }
487539
488540 function test_tutorialRightEdges() {
@@ -505,15 +557,11 @@ Rectangle {
505557 }
506558
507559 function test_tutorialRightFinish() {
508− var tutorial = findChild(shell, "tutorial");
509− var tutorialRightLoader = findChild(tutorial, "tutorialRightLoader");
510− var stage = findChild(shell, "stage");
511−
512560 openTutorialRight();
513561 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
514562
515− tryCompare(tutorialRightLoader, "shown", false);
516− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right"]);
563+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "left-long", "right"]);
564+ tryCompare(AccountsService, "demoEdges", false);
517565 }
518566
519567 function test_tutorialRightShortDrag() {
@@ -537,17 +585,18 @@ Rectangle {
537585 // Test that if we exit the top tutorial, we don't immediately
538586 // jump into right tutorial.
539587 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
540− var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightInactivityTimer");
588+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
541589
542− tutorialRightTimer.interval = 1;
543590 openTutorialTop();
544591 ApplicationManager.startApplication("gallery-app");
545592 ApplicationManager.startApplication("facebook-webapp");
546− tryCompare(ApplicationManager, "count", 3);
593+ ApplicationManager.startApplication("camera-app");
594+ tryCompare(ApplicationManager, "count", 4);
547595
548− AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
549− verify(tutorialRightTimer.running, true);
596+ AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
597+ verify(tutorialRightTimer.running);
550598 verify(!tutorialRightLoader.shown);
599+ tutorialRightTimer.interval = 1;
551600 tryCompare(tutorialRightLoader, "shown", true);
552601 }
553602
@@ -555,112 +604,11 @@ Rectangle {
555604 // Test that we skip the tutorial if user uses right edge themselves
556605
557606 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
558− AccountsService.demoEdgesCompleted = ["left", "left-long"];
607+ AccountsService.demoEdgesCompleted = ["left"];
559608 tryCompare(tutorialLeftLoader, "active", false);
560609
561610 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
562− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "right"]);
563− }
564−
565− function test_tutorialBottomEdges() {
566− var tutorial = findChild(shell, "tutorial");
567− var tutorialBottom = findChild(tutorial, "tutorialBottom");
568− var tutorialLabel = findChild(tutorialBottom, "tutorialLabel");
569− var launcher = findChild(shell, "launcher");
570− var stage = findChild(shell, "stage");
571− var panel = findChild(shell, "panel");
572−
573− openTutorialBottom();
574−
575− tryCompare(tutorial, "running", true);
576− verify(!tutorial.launcherEnabled);
577− verify(!tutorial.spreadEnabled);
578− verify(!tutorial.panelEnabled);
579− verify(tutorialBottom.shown);
580− verify(!launcher.available);
581− verify(!stage.spreadEnabled);
582− verify(!panel.indicators.available);
583− compare(tutorialLabel.text, "Swipe up for recent calls");
584− }
585−
586− function test_tutorialBottomFinish() {
587− var tutorial = findChild(shell, "tutorial");
588− var tutorialBottomLoader = findChild(tutorial, "tutorialBottomLoader");
589−
590− openTutorialBottom();
591− touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
592−
593− tryCompare(tutorialBottomLoader, "shown", false);
594− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right", "bottom-dialer-app"]);
595−
596− // OK, we did one, just confirm that when all are done, we mark whole tutorial as done.
597− verify(AccountsService.demoEdges);
598− AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right",
599− "bottom-address-book-app",
600− "bottom-com.ubuntu.calculator_calculator",
601− "bottom-dialer-app",
602− "bottom-messaging-app"];
603− verify(!AccountsService.demoEdges);
604− }
605−
606− function test_tutorialBottomAppearsBeforeRight() {
607− // Confirm that if bottom edge and right edge would appear on the
608− // the same app open, bottom edge appears first. (this is a lightly
609− // edited version of openTutorialRight)
610− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
611− var tutorialRight = findChild(shell, "tutorialRight");
612− var tutorialBottom = findChild(shell, "tutorialBottom");
613−
614− AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
615− ApplicationManager.startApplication("gallery-app");
616− ApplicationManager.startApplication("dialer-app");
617−
618− tryCompare(tutorialLeftLoader, "active", false);
619− tryCompare(tutorialBottom, "shown", true);
620− tryCompare(tutorialBottom, "opacity", 1);
621− tryCompare(tutorialRight, "visible", false);
622− }
623−
624− function test_tutorialBottomOnlyCoversSideStageOnTablet() {
625− loadShell("tablet");
626−
627− var tutorialBottom = findChild(shell, "tutorialBottom");
628− var targetHeight = shell.height - units.gu(4);
629− var mainStageX = units.gu(20);
630− var sideStageX = shell.width - units.gu(20);
631−
632− WindowStateStorage.saveStage("dialer-app", ApplicationInfoInterface.SideStage);
633− openTutorialBottom();
634−
635− touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
636− compare(tutorialBottom.opacity, 1);
637− touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
638−
639− touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
640− verify(tutorialBottom.opacity < 1);
641− touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
642− }
643−
644− function test_tutorialBottomOnlyCoversMainStageOnTablet() {
645− loadShell("tablet");
646−
647− var tutorialBottom = findChild(shell, "tutorialBottom");
648− var targetHeight = shell.height - units.gu(4);
649− var mainStageX = units.gu(20);
650− var sideStageX = shell.width - units.gu(20);
651−
652− WindowStateStorage.saveStage("notes-app", ApplicationInfoInterface.SideStage);
653− ApplicationManager.startApplication("notes-app");
654−
655− openTutorialBottom();
656−
657− touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
658− compare(tutorialBottom.opacity, 1);
659− touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
660−
661− touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
662− verify(tutorialBottom.opacity < 1);
663− touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
611+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "right"]);
664612 }
665613
666614 function test_activeCallInterruptsTutorial() {
@@ -678,31 +626,6 @@ Rectangle {
678626 verify(!tutorialLeft.paused);
679627 }
680628
681− function test_dialerInterruptionWithNoOverlappingTutorials() {
682− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
683− var tutorialBottomLoader = findChild(shell, "tutorialBottomLoader");
684− verify(tutorialLeftLoader.shown);
685−
686− // Start call, hiding left tutorial
687− callManager.foregroundCall = phoneCall;
688− tryCompare(tutorialLeftLoader, "shown", false);
689−
690− // Start dialer app
691− ApplicationManager.startApplication("dialer-app");
692− tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
693− verify(!tutorialBottomLoader.shown);
694−
695− // Dismiss call, bottom tutorial should appear
696− callManager.foregroundCall = null;
697− tryCompare(tutorialBottomLoader, "shown", true);
698− verify(!tutorialLeftLoader.shown);
699−
700− // Get rid of bottom tutorial, left should now be shown
701− touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
702− tryCompare(tutorialBottomLoader, "shown", false);
703− tryCompare(tutorialLeftLoader, "shown", true);
704− }
705−
706629 function test_greeterInterruptsTutorial() {
707630 var tutorialLeft = findChild(shell, "tutorialLeft");
708631 verify(tutorialLeft.shown);
@@ -738,7 +661,7 @@ Rectangle {
738661 verify(!tutorialRight.isReady);
739662 verify(!tutorialRight.shown);
740663 verify(!tutorialRight.paused);
741− compare(AccountsService.demoEdgesCompleted, ["left", "left-long", "top"]);
664+ compare(AccountsService.demoEdgesCompleted, ["left", "top", "left-long"]);
742665 }
743666
744667 function test_desktopOnlyShowsTutorialRight() {
@@ -746,17 +669,22 @@ Rectangle {
746669
747670 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
748671 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
672+ var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
749673 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
750− var tutorialBottomLoader = findChild(shell, "tutorialBottomLoader");
751674 verify(!tutorialLeftLoader.active);
752675 verify(!tutorialTopLoader.active);
676+ verify(!tutorialLeftLongLoader.active);
753677 verify(tutorialRightLoader.active);
754− verify(!tutorialBottomLoader.active);
755678 compare(AccountsService.demoEdgesCompleted, []);
756679
680+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
681+ tutorialRightTimer.interval = 1;
682+
757683 ApplicationManager.startApplication("dialer-app");
758684 ApplicationManager.startApplication("camera-app");
685+ ApplicationManager.startApplication("facebook-webapp");
759686 tryCompare(tutorialRightLoader.item, "isReady", true);
687+
760688 tryCompare(tutorialRightLoader, "shown", true);
761689 }
762690
@@ -764,8 +692,11 @@ Rectangle {
764692 loadShell("desktop");
765693
766694 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
695+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
696+ tutorialRightTimer.interval = 1;
767697 ApplicationManager.startApplication("dialer-app");
768698 ApplicationManager.startApplication("camera-app");
699+ ApplicationManager.startApplication("facebook-webapp");
769700 tryCompare(tutorialRightLoader, "shown", true);
770701
771702 var stage = findChild(shell, "stage");
@@ -789,24 +720,60 @@ Rectangle {
789720 verify(tutorialLeftLoader.shown);
790721 }
791722
792− function test_oskPreventsTutorial() {
793− var surface = SurfaceManager.inputMethodSurface;
794− var inputMethod = findInvisibleChild(shell, "inputMethod");
723+ function test_oskDelaysTutorial() {
724+ var tutorial = findChild(shell, "tutorial");
725+ verify(!tutorial.delayed);
795726
796− AccountsService.demoEdges = false;
797− surface.setState(Mir.RestoredState);
798− tryCompare(inputMethod, "state", "shown");
727+ SurfaceManager.inputMethodSurface.setState(Mir.RestoredState);
799728
729+ tryCompare(tutorial, "delayed", true);
730+ }
731+
732+ function test_notificationDelaysTutorial() {
800733 var tutorial = findChild(shell, "tutorial");
801− tryCompare(tutorial, "keyboardVisible", true);
734+ verify(!tutorial.delayed);
802735
803− AccountsService.demoEdges = true;
804− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
805− verify(!tutorialLeftLoader.shown);
736+ var notifications = findChild(shell, "notificationList");
737+ notifications.model = mockNotificationsModel;
738+ addNotification(); // placeholder
739+ addNotification();
806740
807− surface.setState(Mir.MinimizedState);
808− tryCompare(inputMethod, "state", "hidden");
809− tryCompare(tutorialLeftLoader, "shown", false);
741+ tryCompare(tutorial, "delayed", true);
742+ }
743+
744+ function test_dialogDelaysTutorial() {
745+ var tutorial = findChild(shell, "tutorial");
746+ verify(!tutorial.delayed);
747+
748+ var dialogs = findChild(shell, "dialogs");
749+ dialogs.showPowerDialog();
750+
751+ verify(tutorial.delayed);
752+ }
753+
754+ function test_delayedTutorial() {
755+ var tutorial = findChild(shell, "tutorial");
756+ var tutorialTop = findChild(tutorial, "tutorialTop");
757+ var tutorialTopTimer = findChild(tutorialTop, "tutorialTopTimer");
758+
759+ // Get top tutorial ready
760+ AccountsService.demoEdgesCompleted = ["left"];
761+ verify(tutorialTop.isReady);
762+ verify(tutorialTopTimer.running);
763+
764+ // Confirm that we become unready when delayed, but timer still goes
765+ tutorial.delayed = true;
766+ verify(!tutorialTop.isReady);
767+ verify(tutorialTopTimer.running);
768+
769+ // Confirm that we don't open the tutorial when delayed
770+ tutorialTopTimer.interval = 1;
771+ wait(5);
772+ verify(!tutorialTop.shown);
773+
774+ // Confirm we go back to normal when undelayed
775+ tutorial.delayed = false;
776+ tryCompare(tutorialTop, "shown", true);
810777 }
811778
812779 function test_accountsServiceSettings() {
@@ -818,7 +785,8 @@ Rectangle {
818785
819786 AccountsService.demoEdges = true;
820787 tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
821− tryCompare(tutorialLeftLoader, "shown", true);
788+ var tutorialLeftTimer = findChild(tutorialLeftLoader, "tutorialLeftTimer");
789+ verify(tutorialLeftTimer.running);
822790 }
823791 }
824792}
tests/qmltests/tst_Shell.qml−10
@@ -1233,13 +1233,10 @@ Rectangle {
12331233
12341234 function test_wizardEarlyExit() {
12351235 Wizard.System.wizardEnabled = true;
1236− AccountsService.demoEdges = true;
12371236 loadShell("phone");
12381237
12391238 var wizard = findChild(shell, "wizard");
1240− var tutorial = findChild(shell, "tutorial");
12411239 tryCompare(wizard, "active", true);
1242− tryCompare(tutorial, "running", true);
12431240 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "unity8-dash");
12441241
12451242 // Make sure we stay running when there's no top level window (can happen for
@@ -1252,14 +1249,12 @@ Rectangle {
12521249
12531250 tryCompare(topLevelSurfaceList, "count", 0);
12541251 compare(wizard.shown, true);
1255− compare(tutorial.running, true);
12561252
12571253 // And make sure we stay running when dash comes back again
12581254 var dashSurfaceId = topLevelSurfaceList.nextId;
12591255 ApplicationManager.startApplication(dashApplication.appId);
12601256 waitUntilAppWindowIsFullyLoaded(dashSurfaceId);
12611257 compare(wizard.shown, true);
1262− compare(tutorial.running, true);
12631258
12641259 // And make sure we stop when some other surface shows app
12651260 var gallerySurfaceId = topLevelSurfaceList.nextId;
@@ -1267,12 +1262,7 @@ Rectangle {
12671262 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
12681263 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "gallery-app");
12691264 compare(wizard.shown, false);
1270− compare(tutorial.running, false);
1271− tryCompare(AccountsService, "demoEdges", false);
12721265 tryCompare(Wizard.System, "wizardEnabled", false);
1273−
1274− var tutorialLeft = findChild(tutorial, "tutorialLeft");
1275− compare(tutorialLeft, null); // should be destroyed with tutorial
12761266 }
12771267
12781268 function test_tutorialPausedDuringGreeter() {