on xenial help sign in
Merge in tutorial-fixups
8d5437fMichael Terry
qml/Components/Dialogs.qml+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014-2015 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@ import "../Greeter"
27Item {
27Item {
28 id: root
28 id: root
29
29
30+ readonly property alias hasActiveDialog: dialogLoader.active
31+
30 // to be set from outside, useful mostly for testing purposes
32 // to be set from outside, useful mostly for testing purposes
31 property var unitySessionService: DBusUnitySessionService
33 property var unitySessionService: DBusUnitySessionService
32 property var closeAllApps: function() {
34 property var closeAllApps: function() {
qml/Notifications/Notifications.qml+1
@@ -26,6 +26,7 @@ ListView {
26 objectName: "notificationList"
26 objectName: "notificationList"
27 interactive: false
27 interactive: false
28
28
29+ readonly property bool hasNotification: count > 1 // placeholder is index 0
29 property real margin
30 property real margin
30 property bool hasMouse
31 property bool hasMouse
31 property url background: ""
32 property url background: ""
qml/Shell.qml+12−8
@@ -116,10 +116,9 @@ StyledItem {
116 }
116 }
117 function _onMainAppChanged(appId) {
117 function _onMainAppChanged(appId) {
118 if (wizard.active && appId != "" && appId != "unity8-dash") {
118 if (wizard.active && appId != "" && appId != "unity8-dash") {
119− // If this happens on first boot, we may be in edge
119+ // If this happens on first boot, we may be in the
120− // tutorial or wizard while receiving a call. But a call
120+ // wizard while receiving a call. But a call is more
121− // is more important than wizard so just bail out of those.
121+ // important than the wizard so just bail out of it.
122− tutorial.finish();
123 wizard.hide();
122 wizard.hide();
124 }
123 }
125
124
@@ -320,7 +319,7 @@ StyledItem {
320 Binding {
319 Binding {
321 target: applicationsDisplayLoader.item
320 target: applicationsDisplayLoader.item
322 property: "inverseProgress"
321 property: "inverseProgress"
323− value: greeter && greeter.locked ? 0 : launcher.progress
322+ value: !greeter || greeter.locked || !tutorial.launcherLongSwipeEnabled ? 0 : launcher.progress
324 }
323 }
325 Binding {
324 Binding {
326 target: applicationsDisplayLoader.item
325 target: applicationsDisplayLoader.item
@@ -504,7 +503,8 @@ StyledItem {
504 launcher.fadeOut();
503 launcher.fadeOut();
505 }
504 }
506
505
506+ if (!greeter.locked && tutorial.launcherLongSwipeEnabled
507− if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
507+ && ApplicationManager.focusedApplicationId != "unity8-dash") {
508 ApplicationManager.requestFocusApplication("unity8-dash")
508 ApplicationManager.requestFocusApplication("unity8-dash")
509 launcher.fadeOut();
509 launcher.fadeOut();
510 }
510 }
@@ -623,8 +623,12 @@ StyledItem {
623 objectName: "tutorial"
623 objectName: "tutorial"
624 anchors.fill: parent
624 anchors.fill: parent
625
625
626− paused: callManager.hasCalls || greeter.shown
626+ paused: callManager.hasCalls || !greeter || greeter.shown ||
627+ wizard.active
628+ delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
627− keyboardVisible: inputMethod.state === "shown"
629+ inputMethod.state === "shown" ||
630+ (launcher.shown && !launcher.lockedVisible) ||
631+ panel.indicators.shown || stage.dragProgress > 0
628 usageScenario: shell.usageScenario
632 usageScenario: shell.usageScenario
629 lastInputTimestamp: inputFilter.lastInputTimestamp
633 lastInputTimestamp: inputFilter.lastInputTimestamp
630 launcher: launcher
634 launcher: launcher
qml/Tutorial/InactivityTimer.qml+26−10
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -18,36 +18,52 @@ import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19
19
20Item {
20Item {
21+ id: root
22+
21− readonly property alias running: internalTimer.running
23+ readonly property bool running: delayTimer.running || inactivityTimer.running
22− property alias interval: internalTimer.interval
24+ property int interval: 3000
23 property var page
25 property var page
24 property int lastInputTimestamp
26 property int lastInputTimestamp
25
27
26 function start() {
28 function start() {
27− internalTimer.start();
29+ if (!delayTimer.running && !inactivityTimer.running) {
30+ delayTimer.start();
31+ }
28 }
32 }
29
33
30 ////
34 ////
31
35
32 onLastInputTimestampChanged: {
36 onLastInputTimestampChanged: {
33− if (internalTimer.running) {
37+ if (inactivityTimer.running) {
34− internalTimer.restart();
38+ inactivityTimer.restart();
35 }
39 }
36 }
40 }
37
41
38 Connections {
42 Connections {
39 target: page
43 target: page
40 onIsReadyChanged: {
44 onIsReadyChanged: {
41− if (page.isReady && internalTimer.running) {
45+ if (page.isReady && inactivityTimer.running) {
42− internalTimer.restart();
46+ inactivityTimer.restart();
47+ }
48+ }
49+ onPausedChanged: {
50+ if (root.paused) {
51+ delayTimer.stop();
52+ inactivityTimer.stop();
43 }
53 }
44 }
54 }
45 }
55 }
46
56
47 Timer {
57 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
49
65
50− interval: 3000
66+ interval: Math.min(root.interval, 3000)
51
67
52 onTriggered: {
68 onTriggered: {
53 if (page.isReady) {
69 if (page.isReady) {
qml/Tutorial/Tutorial.qml+7−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -26,17 +26,18 @@ import AccountsService 0.1
26Item {
26Item {
27 id: root
27 id: root
28
28
29− property alias active: loader.active
29+ readonly property alias active: loader.active
30
30
31 property Item launcher
31 property Item launcher
32 property Item panel
32 property Item panel
33 property Item stage
33 property Item stage
34 property string usageScenario
34 property string usageScenario
35− property bool paused
35+ property bool paused // hide any existing tutorial and don't show new ones
36− property bool keyboardVisible
36+ property bool delayed // don't show new tutorials
37 property int lastInputTimestamp
37 property int lastInputTimestamp
38
38
39 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
39 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
40+ readonly property bool launcherLongSwipeEnabled: loader.item ? loader.item.launcherLongSwipeEnabled : true
40 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
41 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
41 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
42 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
42 readonly property bool running: loader.item ? loader.item.running : false
43 readonly property bool running: loader.item ? loader.item.running : false
@@ -85,8 +86,8 @@ Item {
85
86
86 Binding {
87 Binding {
87 target: loader.item
88 target: loader.item
88− property: "keyboardVisible"
89+ property: "delayed"
89− value: root.keyboardVisible
90+ value: root.delayed
90 }
91 }
91
92
92 Binding {
93 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 @@
1/*
1/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -26,19 +26,21 @@ Item {
26 property Item panel
26 property Item panel
27 property Item stage
27 property Item stage
28 property string usageScenario
28 property string usageScenario
29− property bool paused
29+ property bool paused: true // default to true so that we won't start until top level is all ready
30− property bool keyboardVisible
30+ property bool delayed: true // same
31 property var lastInputTimestamp
31 property var lastInputTimestamp
32
32
33 readonly property bool launcherEnabled: !running
33 readonly property bool launcherEnabled: !running
34 || tutorialLeftLoader.shown
34 || tutorialLeftLoader.shown
35 || tutorialLeftLongLoader.shown
35 || tutorialLeftLongLoader.shown
36+ readonly property bool launcherLongSwipeEnabled: tutorialLeftLongLoader.shown
37+ || tutorialLeftLongLoader.skipped
36 readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
38 readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
37 readonly property bool panelEnabled: !running || tutorialTopLoader.shown
39 readonly property bool panelEnabled: !running || tutorialTopLoader.shown
38 readonly property bool running: tutorialLeftLoader.shown
40 readonly property bool running: tutorialLeftLoader.shown
41+ || tutorialLeftLongLoader.shown
39 || tutorialTopLoader.shown
42 || tutorialTopLoader.shown
40 || tutorialRightLoader.shown
43 || tutorialRightLoader.shown
41− || tutorialBottomLoader.shown
42
44
43 signal finished()
45 signal finished()
44
46
@@ -57,16 +59,11 @@ Item {
57 root.usageScenario === "phone" ||
59 root.usageScenario === "phone" ||
58 root.usageScenario === "tablet"
60 root.usageScenario === "tablet"
59
61
60− property var focusedApp: ApplicationManager.focusedApplicationId
61− ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
62− : null
63−
64 function haveShown(tutorialId) {
62 function haveShown(tutorialId) {
65 return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
63 return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
66 }
64 }
67
65
68− property bool endPointsFinished: tutorialRightLoader.skipped &&
66+ property bool endPointsFinished: tutorialRightLoader.skipped
69− tutorialBottomLoader.skipped
70 onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
67 onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
71 }
68 }
72
69
@@ -88,15 +85,14 @@ Item {
88 hides: [launcher, panel.indicators]
85 hides: [launcher, panel.indicators]
89 paused: root.paused
86 paused: root.paused
90
87
91− isReady: !tutorialLeftLoader.skipped && !paused && !keyboardVisible &&
88+ isReady: !tutorialLeftLoader.skipped && !paused && !delayed
92− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
93
89
94− // Use an idle timer here, because when constructed, all our isReady variables will be false.
90+ InactivityTimer {
95− // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
96− Timer {
97 id: tutorialLeftTimer
91 id: tutorialLeftTimer
92+ objectName: "tutorialLeftTimer"
98− interval: 0
93+ interval: 20000
99− onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
94+ lastInputTimestamp: root.lastInputTimestamp
95+ page: parent
100 }
96 }
101
97
102 onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
98 onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
@@ -105,86 +101,71 @@ Item {
105 }
101 }
106
102
107 Loader {
103 Loader {
108− id: tutorialLeftLongLoader
104+ id: tutorialTopLoader
109− objectName: "tutorialLeftLongLoader"
105+ objectName: "tutorialTopLoader"
110 anchors.fill: parent
106 anchors.fill: parent
111
107
112− readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
108+ readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
113 readonly property bool shown: item && item.shown
109 readonly property bool shown: item && item.shown
114 active: !skipped || (item && item.visible)
110 active: !skipped || (item && item.visible)
115 onSkippedChanged: if (skipped && shown) item.hide()
111 onSkippedChanged: if (skipped && shown) item.hide()
116
112
117− sourceComponent: TutorialLeftLong {
113+ sourceComponent: TutorialTop {
118− id: tutorialLeftLong
114+ id: tutorialTop
119− objectName: "tutorialLeftLong"
115+ objectName: "tutorialTop"
120 anchors.fill: parent
116 anchors.fill: parent
121− launcher: root.launcher
117+ panel: root.panel
122 hides: [launcher, panel.indicators]
118 hides: [launcher, panel.indicators]
123 paused: root.paused
119 paused: root.paused
124
120
125− skipped: tutorialLeftLongLoader.skipped
121+ skipped: tutorialTopLoader.skipped
126− isReady: tutorialLeftLoader.skipped && !skipped && !paused && !keyboardVisible &&
122+ isReady: tutorialLeftLoader.skipped && !skipped && !paused && !delayed
127− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
128
123
129− Timer {
124+ InactivityTimer {
130− id: tutorialLeftLongTimer
125+ id: tutorialTopTimer
131− objectName: "tutorialLeftLongTimer"
126+ objectName: "tutorialTopTimer"
132− interval: 5000
127+ interval: 60000
133− onTriggered: {
128+ lastInputTimestamp: root.lastInputTimestamp
134− if (parent.isReady) {
135− if (!parent.shown) {
136− parent.show();
137− }
138− } else if (!parent.skipped) {
129+ page: parent
139− restart();
140− }
141− }
142 }
130 }
143
131
144− onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
132+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
145− onFinished: AccountsService.markDemoEdgeCompleted("left-long")
133+ onFinished: AccountsService.markDemoEdgeCompleted("top")
146 }
134 }
147 }
135 }
148
136
149 Loader {
137 Loader {
150− id: tutorialTopLoader
138+ id: tutorialLeftLongLoader
151− objectName: "tutorialTopLoader"
139+ objectName: "tutorialLeftLongLoader"
152 anchors.fill: parent
140 anchors.fill: parent
153
141
154− readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
142+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
155 readonly property bool shown: item && item.shown
143 readonly property bool shown: item && item.shown
156 active: !skipped || (item && item.visible)
144 active: !skipped || (item && item.visible)
157 onSkippedChanged: if (skipped && shown) item.hide()
145 onSkippedChanged: if (skipped && shown) item.hide()
158
146
159− sourceComponent: TutorialTop {
147+ sourceComponent: TutorialLeftLong {
160− id: tutorialTop
148+ id: tutorialLeftLong
161− objectName: "tutorialTop"
149+ objectName: "tutorialLeftLong"
162 anchors.fill: parent
150 anchors.fill: parent
163− panel: root.panel
151+ launcher: root.launcher
164 hides: [launcher, panel.indicators]
152 hides: [launcher, panel.indicators]
165 paused: root.paused
153 paused: root.paused
166
154
155+ skipped: tutorialLeftLongLoader.skipped
167− skipped: tutorialTopLoader.skipped
156+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !delayed &&
168− isReady: tutorialLeftLongLoader.skipped && !skipped && !paused && !keyboardVisible &&
157+ ApplicationManager.focusedApplicationId != "unity8-dash"
169− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
170−
171− // We fire 30s after left edge tutorial, with at least 3s of inactivity
172
158
173 InactivityTimer {
159 InactivityTimer {
174− id: tutorialTopInactivityTimer
160+ id: tutorialLeftLongTimer
161+ objectName: "tutorialLeftLongTimer"
162+ interval: 5000
175 lastInputTimestamp: root.lastInputTimestamp
163 lastInputTimestamp: root.lastInputTimestamp
176 page: parent
164 page: parent
177 }
165 }
178
166
179− Timer {
180− id: tutorialTopTimer
181− objectName: "tutorialTopTimer"
182− interval: 27000
183− onTriggered: tutorialTopInactivityTimer.start()
184− }
185−
186− onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
167+ onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
187− onFinished: AccountsService.markDemoEdgeCompleted("top")
168+ onFinished: AccountsService.markDemoEdgeCompleted("left-long")
188 }
169 }
189 }
170 }
190
171
@@ -208,83 +189,19 @@ Item {
208 paused: root.paused
189 paused: root.paused
209
190
210 skipped: tutorialRightLoader.skipped
191 skipped: tutorialRightLoader.skipped
211− isReady: tutorialTopLoader.skipped && !skipped && !paused && !keyboardVisible &&
192+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !delayed &&
212− !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
213− ApplicationManager.count >= 3
193+ ApplicationManager.count >= 4
214
194
215 InactivityTimer {
195 InactivityTimer {
216− id: tutorialRightInactivityTimer
196+ id: tutorialRightTimer
217− objectName: "tutorialRightInactivityTimer"
197+ objectName: "tutorialRightTimer"
198+ interval: 10000
218 lastInputTimestamp: root.lastInputTimestamp
199 lastInputTimestamp: root.lastInputTimestamp
219 page: parent
200 page: parent
220 }
201 }
221
202
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()
233 onFinished: AccountsService.markDemoEdgeCompleted("right")
204 onFinished: AccountsService.markDemoEdgeCompleted("right")
234 }
205 }
235 }
206 }
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− }
290}
207}
qml/Tutorial/TutorialLeft.qml+2−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -44,7 +44,7 @@ TutorialPage {
44 }
44 }
45
45
46 label {
46 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")
48 anchors.left: arrow.right
48 anchors.left: arrow.right
49 anchors.leftMargin: units.gu(3)
49 anchors.leftMargin: units.gu(3)
50 anchors.right: root.right
50 anchors.right: root.right
qml/Tutorial/TutorialLeftLong.qml+3−1
@@ -23,7 +23,9 @@ TutorialPage {
23
23
24 property var launcher
24 property var launcher
25
25
26+ // Unlike other tutorials, this one can't be skipped before we show it, so
27+ // only set opacityOverride if we're already shown.
26− opacityOverride: 1 - launcher.dragDistance / launcher.minimizeDistance
28+ opacityOverride: shown ? 1 - launcher.dragDistance / launcher.minimizeDistance : 1
27
29
28 mouseArea {
30 mouseArea {
29 anchors.leftMargin: launcher.dragAreaWidth
31 anchors.leftMargin: launcher.dragAreaWidth
qml/Tutorial/TutorialPage.qml+2−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -46,7 +46,7 @@ Showable {
46 onOpacityOverrideChanged: {
46 onOpacityOverrideChanged: {
47 if (opacityOverride <= 0) {
47 if (opacityOverride <= 0) {
48 d.showOnUnpause = false;
48 d.showOnUnpause = false;
49− hide();
49+ hideNow();
50 }
50 }
51 }
51 }
52 property real _showOpacity: 0
52 property real _showOpacity: 0
qml/Tutorial/TutorialRight.qml+2−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -53,7 +53,7 @@ TutorialPage {
53 label {
53 label {
54 text: root.usageScenario === "desktop" ?
54 text: root.usageScenario === "desktop" ?
55 i18n.tr("Push your mouse against the right edge to view your open apps") :
55 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")
57 anchors.right: arrow.left
57 anchors.right: arrow.left
58 anchors.rightMargin: units.gu(2) - (label.width - label.contentWidth)
58 anchors.rightMargin: units.gu(2) - (label.width - label.contentWidth)
59 anchors.verticalCenter: arrow.verticalCenter
59 anchors.verticalCenter: arrow.verticalCenter
qml/Tutorial/TutorialTop.qml+2−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -50,8 +50,7 @@ TutorialPage {
50 }
50 }
51
51
52 label {
52 label {
53− text: d.landscape ? i18n.tr("Swipe from the top right edge to open the notification bar")
53+ text: i18n.tr("Swipe from the top edge to access notifications and quick settings")
54− : i18n.tr("Swipe from the top edge to open the notification bar")
55 anchors.top: arrow.bottom
54 anchors.top: arrow.bottom
56 anchors.topMargin: units.gu(3)
55 anchors.topMargin: units.gu(3)
57 anchors.horizontalCenter: arrow.horizontalCenter
56 anchors.horizontalCenter: arrow.horizontalCenter
tests/qmltests/Tutorial/tst_Tutorial.qml+151−183
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ import Ubuntu.Components 1.3
22import Ubuntu.Components.ListItems 1.3
22import Ubuntu.Components.ListItems 1.3
23import Ubuntu.Telephony 0.1 as Telephony
23import Ubuntu.Telephony 0.1 as Telephony
24import Unity.Application 0.1
24import Unity.Application 0.1
25+import Unity.Notifications 1.0
25import Unity.Test 0.1 as UT
26import Unity.Test 0.1 as UT
26import Utils 0.1
27import Utils 0.1
27
28
@@ -55,6 +56,33 @@ Rectangle {
55 phoneNumber: "+447812221111"
56 phoneNumber: "+447812221111"
56 }
57 }
57
58
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+
58 Component.onCompleted: {
86 Component.onCompleted: {
59 // must set the mock mode before loading the Shell
87 // must set the mock mode before loading the Shell
60 LightDM.Greeter.mockMode = "single-pin";
88 LightDM.Greeter.mockMode = "single-pin";
@@ -231,6 +259,8 @@ Rectangle {
231 prepareShell();
259 prepareShell();
232
260
233 var tutorialLeft = findChild(shell, "tutorialLeft");
261 var tutorialLeft = findChild(shell, "tutorialLeft");
262+ var tutorialLeftTimer = findInvisibleChild(tutorialLeft, "tutorialLeftTimer");
263+ tutorialLeftTimer.interval = 0;
234 tryCompare(tutorialLeft, "opacity", 1);
264 tryCompare(tutorialLeft, "opacity", 1);
235 }
265 }
236
266
@@ -260,6 +290,7 @@ Rectangle {
260 // reload our test subject to get it in a fresh state once again
290 // reload our test subject to get it in a fresh state once again
261 shellLoader.active = true;
291 shellLoader.active = true;
262
292
293+ mockNotificationsModel.clear();
263 tryCompare(shellLoader, "status", Loader.Ready);
294 tryCompare(shellLoader, "status", Loader.Ready);
264 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
295 removeTimeConstraintsFromSwipeAreas(shellLoader.item);
265 }
296 }
@@ -300,40 +331,41 @@ Rectangle {
300 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
331 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
301
332
302 tutorialTopTimer.interval = 1;
333 tutorialTopTimer.interval = 1;
303− AccountsService.demoEdgesCompleted = ["left", "left-long"];
334+ AccountsService.demoEdgesCompleted = ["left"];
304
335
305 tryCompare(tutorialLeftLoader, "active", false);
336 tryCompare(tutorialLeftLoader, "active", false);
306 tryCompare(tutorialTop, "shown", true);
337 tryCompare(tutorialTop, "shown", true);
307 tryCompare(tutorialTop, "opacity", 1);
338 tryCompare(tutorialTop, "opacity", 1);
308 }
339 }
309
340
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+
310 function openTutorialRight() {
353 function openTutorialRight() {
311 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
354 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
312 var tutorialRight = findChild(shell, "tutorialRight");
355 var tutorialRight = findChild(shell, "tutorialRight");
356+ var tutorialRightTimer = findChild(tutorialRight, "tutorialRightTimer");
313
357
314− AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
358+ AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
315 ApplicationManager.startApplication("gallery-app");
359 ApplicationManager.startApplication("gallery-app");
316 ApplicationManager.startApplication("facebook-webapp");
360 ApplicationManager.startApplication("facebook-webapp");
361+ ApplicationManager.startApplication("camera-app");
317
362
318 tryCompare(tutorialLeftLoader, "active", false);
363 tryCompare(tutorialLeftLoader, "active", false);
364+ tutorialRightTimer.interval = 1;
319 tryCompare(tutorialRight, "shown", true);
365 tryCompare(tutorialRight, "shown", true);
320 tryCompare(tutorialRight, "opacity", 1);
366 tryCompare(tutorialRight, "opacity", 1);
321 }
367 }
322
368
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−
337 function test_tutorialLeftEdges() {
369 function test_tutorialLeftEdges() {
338 var tutorial = findChild(shell, "tutorial");
370 var tutorial = findChild(shell, "tutorial");
339 var tutorialLeft = findChild(tutorial, "tutorialLeft");
371 var tutorialLeft = findChild(tutorial, "tutorialLeft");
@@ -396,19 +428,21 @@ Rectangle {
396 tryCompare(launcher, "state", "visible");
428 tryCompare(launcher, "state", "visible");
397 }
429 }
398
430
399− function test_tutorialLeftLongDrag() {
431+ function test_tutorialLeftLongFinish() {
400− // Just confirm that a long drag ("dash" drag) doesn't confuse us
432+ AccountsService.demoEdgesCompleted = ["left", "top"];
433+
434+ var tutorialLeftLongTimer = findInvisibleChild(shell, "tutorialLeftLongTimer");
435+ tutorialLeftLongTimer.interval = 1;
401
436
402− // So that we actually switch to dash and launcher hides
403 ApplicationManager.startApplication("gallery-app");
437 ApplicationManager.startApplication("gallery-app");
404
438
405− var launcher = findChild(shell, "launcher");
439+ var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
406− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
440+ tryCompare(tutorialLeftLongLoader, "shown", true);
407
441
408 touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
442 touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
409
443
410− tryCompare(tutorialLeftLoader, "shown", false);
444+ tryCompare(tutorialLeftLongLoader, "shown", false);
411− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long"]);
445+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "left-long"]);
412 }
446 }
413
447
414 function test_tutorialLeftAutoSkipped() {
448 function test_tutorialLeftAutoSkipped() {
@@ -423,6 +457,24 @@ Rectangle {
423 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
457 tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
424 }
458 }
425
459
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+
426 function test_tutorialTopEdges() {
478 function test_tutorialTopEdges() {
427 var tutorial = findChild(shell, "tutorial");
479 var tutorial = findChild(shell, "tutorial");
428 var tutorialTop = findChild(tutorial, "tutorialTop");
480 var tutorialTop = findChild(tutorial, "tutorialTop");
@@ -451,7 +503,7 @@ Rectangle {
451 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
503 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
452
504
453 tryCompare(tutorialTopLoader, "shown", false);
505 tryCompare(tutorialTopLoader, "shown", false);
454− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
506+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
455 tryCompare(panel.indicators, "fullyOpened", true);
507 tryCompare(panel.indicators, "fullyOpened", true);
456 }
508 }
457
509
@@ -477,12 +529,12 @@ Rectangle {
477
529
478 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
530 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
479 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
531 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
480− AccountsService.demoEdgesCompleted = ["left", "left-long"];
532+ AccountsService.demoEdgesCompleted = ["left"];
481 tryCompare(tutorialLeftLoader, "active", false);
533 tryCompare(tutorialLeftLoader, "active", false);
482 verify(!tutorialTopLoader.shown);
534 verify(!tutorialTopLoader.shown);
483
535
484 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
536 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
485− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
537+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top"]);
486 }
538 }
487
539
488 function test_tutorialRightEdges() {
540 function test_tutorialRightEdges() {
@@ -505,15 +557,11 @@ Rectangle {
505 }
557 }
506
558
507 function test_tutorialRightFinish() {
559 function test_tutorialRightFinish() {
508− var tutorial = findChild(shell, "tutorial");
509− var tutorialRightLoader = findChild(tutorial, "tutorialRightLoader");
510− var stage = findChild(shell, "stage");
511−
512 openTutorialRight();
560 openTutorialRight();
513 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
561 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
514
562
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);
517 }
565 }
518
566
519 function test_tutorialRightShortDrag() {
567 function test_tutorialRightShortDrag() {
@@ -537,17 +585,18 @@ Rectangle {
537 // Test that if we exit the top tutorial, we don't immediately
585 // Test that if we exit the top tutorial, we don't immediately
538 // jump into right tutorial.
586 // jump into right tutorial.
539 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
587 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
540− var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightInactivityTimer");
588+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
541
589
542− tutorialRightTimer.interval = 1;
543 openTutorialTop();
590 openTutorialTop();
544 ApplicationManager.startApplication("gallery-app");
591 ApplicationManager.startApplication("gallery-app");
545 ApplicationManager.startApplication("facebook-webapp");
592 ApplicationManager.startApplication("facebook-webapp");
593+ ApplicationManager.startApplication("camera-app");
546− tryCompare(ApplicationManager, "count", 3);
594+ tryCompare(ApplicationManager, "count", 4);
547
595
548− AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
596+ AccountsService.demoEdgesCompleted = ["left", "top", "left-long"];
549− verify(tutorialRightTimer.running, true);
597+ verify(tutorialRightTimer.running);
550 verify(!tutorialRightLoader.shown);
598 verify(!tutorialRightLoader.shown);
599+ tutorialRightTimer.interval = 1;
551 tryCompare(tutorialRightLoader, "shown", true);
600 tryCompare(tutorialRightLoader, "shown", true);
552 }
601 }
553
602
@@ -555,112 +604,11 @@ Rectangle {
555 // Test that we skip the tutorial if user uses right edge themselves
604 // Test that we skip the tutorial if user uses right edge themselves
556
605
557 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
606 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
558− AccountsService.demoEdgesCompleted = ["left", "left-long"];
607+ AccountsService.demoEdgesCompleted = ["left"];
559 tryCompare(tutorialLeftLoader, "active", false);
608 tryCompare(tutorialLeftLoader, "active", false);
560
609
561 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
610 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
562− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "right"]);
611+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "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);
664 }
612 }
665
613
666 function test_activeCallInterruptsTutorial() {
614 function test_activeCallInterruptsTutorial() {
@@ -678,31 +626,6 @@ Rectangle {
678 verify(!tutorialLeft.paused);
626 verify(!tutorialLeft.paused);
679 }
627 }
680
628
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−
706 function test_greeterInterruptsTutorial() {
629 function test_greeterInterruptsTutorial() {
707 var tutorialLeft = findChild(shell, "tutorialLeft");
630 var tutorialLeft = findChild(shell, "tutorialLeft");
708 verify(tutorialLeft.shown);
631 verify(tutorialLeft.shown);
@@ -738,7 +661,7 @@ Rectangle {
738 verify(!tutorialRight.isReady);
661 verify(!tutorialRight.isReady);
739 verify(!tutorialRight.shown);
662 verify(!tutorialRight.shown);
740 verify(!tutorialRight.paused);
663 verify(!tutorialRight.paused);
741− compare(AccountsService.demoEdgesCompleted, ["left", "left-long", "top"]);
664+ compare(AccountsService.demoEdgesCompleted, ["left", "top", "left-long"]);
742 }
665 }
743
666
744 function test_desktopOnlyShowsTutorialRight() {
667 function test_desktopOnlyShowsTutorialRight() {
@@ -746,17 +669,22 @@ Rectangle {
746
669
747 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
670 var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
748 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
671 var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
672+ var tutorialLeftLongLoader = findChild(shell, "tutorialLeftLongLoader");
749 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
673 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
750− var tutorialBottomLoader = findChild(shell, "tutorialBottomLoader");
751 verify(!tutorialLeftLoader.active);
674 verify(!tutorialLeftLoader.active);
752 verify(!tutorialTopLoader.active);
675 verify(!tutorialTopLoader.active);
676+ verify(!tutorialLeftLongLoader.active);
753 verify(tutorialRightLoader.active);
677 verify(tutorialRightLoader.active);
754− verify(!tutorialBottomLoader.active);
755 compare(AccountsService.demoEdgesCompleted, []);
678 compare(AccountsService.demoEdgesCompleted, []);
756
679
680+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
681+ tutorialRightTimer.interval = 1;
682+
757 ApplicationManager.startApplication("dialer-app");
683 ApplicationManager.startApplication("dialer-app");
758 ApplicationManager.startApplication("camera-app");
684 ApplicationManager.startApplication("camera-app");
685+ ApplicationManager.startApplication("facebook-webapp");
759 tryCompare(tutorialRightLoader.item, "isReady", true);
686 tryCompare(tutorialRightLoader.item, "isReady", true);
687+
760 tryCompare(tutorialRightLoader, "shown", true);
688 tryCompare(tutorialRightLoader, "shown", true);
761 }
689 }
762
690
@@ -764,8 +692,11 @@ Rectangle {
764 loadShell("desktop");
692 loadShell("desktop");
765
693
766 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
694 var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
695+ var tutorialRightTimer = findInvisibleChild(tutorialRightLoader, "tutorialRightTimer");
696+ tutorialRightTimer.interval = 1;
767 ApplicationManager.startApplication("dialer-app");
697 ApplicationManager.startApplication("dialer-app");
768 ApplicationManager.startApplication("camera-app");
698 ApplicationManager.startApplication("camera-app");
699+ ApplicationManager.startApplication("facebook-webapp");
769 tryCompare(tutorialRightLoader, "shown", true);
700 tryCompare(tutorialRightLoader, "shown", true);
770
701
771 var stage = findChild(shell, "stage");
702 var stage = findChild(shell, "stage");
@@ -789,24 +720,60 @@ Rectangle {
789 verify(tutorialLeftLoader.shown);
720 verify(tutorialLeftLoader.shown);
790 }
721 }
791
722
792− function test_oskPreventsTutorial() {
723+ function test_oskDelaysTutorial() {
793− var surface = SurfaceManager.inputMethodSurface;
724+ var tutorial = findChild(shell, "tutorial");
794− var inputMethod = findInvisibleChild(shell, "inputMethod");
725+ verify(!tutorial.delayed);
795
726
796− AccountsService.demoEdges = false;
797− surface.setState(Mir.RestoredState);
727+ SurfaceManager.inputMethodSurface.setState(Mir.RestoredState);
798− tryCompare(inputMethod, "state", "shown");
799
728
729+ tryCompare(tutorial, "delayed", true);
730+ }
731+
732+ function test_notificationDelaysTutorial() {
800 var tutorial = findChild(shell, "tutorial");
733 var tutorial = findChild(shell, "tutorial");
801− tryCompare(tutorial, "keyboardVisible", true);
734+ verify(!tutorial.delayed);
802
735
803− AccountsService.demoEdges = true;
736+ var notifications = findChild(shell, "notificationList");
804− var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
737+ notifications.model = mockNotificationsModel;
805− verify(!tutorialLeftLoader.shown);
738+ addNotification(); // placeholder
739+ addNotification();
806
740
807− surface.setState(Mir.MinimizedState);
741+ tryCompare(tutorial, "delayed", true);
808− tryCompare(inputMethod, "state", "hidden");
742+ }
809− tryCompare(tutorialLeftLoader, "shown", false);
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);
810 }
777 }
811
778
812 function test_accountsServiceSettings() {
779 function test_accountsServiceSettings() {
@@ -818,7 +785,8 @@ Rectangle {
818
785
819 AccountsService.demoEdges = true;
786 AccountsService.demoEdges = true;
820 tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
787 tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
821− tryCompare(tutorialLeftLoader, "shown", true);
788+ var tutorialLeftTimer = findChild(tutorialLeftLoader, "tutorialLeftTimer");
789+ verify(tutorialLeftTimer.running);
822 }
790 }
823 }
791 }
824}
792}
tests/qmltests/tst_Shell.qml−10
@@ -1233,13 +1233,10 @@ Rectangle {
1233
1233
1234 function test_wizardEarlyExit() {
1234 function test_wizardEarlyExit() {
1235 Wizard.System.wizardEnabled = true;
1235 Wizard.System.wizardEnabled = true;
1236− AccountsService.demoEdges = true;
1237 loadShell("phone");
1236 loadShell("phone");
1238
1237
1239 var wizard = findChild(shell, "wizard");
1238 var wizard = findChild(shell, "wizard");
1240− var tutorial = findChild(shell, "tutorial");
1241 tryCompare(wizard, "active", true);
1239 tryCompare(wizard, "active", true);
1242− tryCompare(tutorial, "running", true);
1243 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "unity8-dash");
1240 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "unity8-dash");
1244
1241
1245 // Make sure we stay running when there's no top level window (can happen for
1242 // Make sure we stay running when there's no top level window (can happen for
@@ -1252,14 +1249,12 @@ Rectangle {
1252
1249
1253 tryCompare(topLevelSurfaceList, "count", 0);
1250 tryCompare(topLevelSurfaceList, "count", 0);
1254 compare(wizard.shown, true);
1251 compare(wizard.shown, true);
1255− compare(tutorial.running, true);
1256
1252
1257 // And make sure we stay running when dash comes back again
1253 // And make sure we stay running when dash comes back again
1258 var dashSurfaceId = topLevelSurfaceList.nextId;
1254 var dashSurfaceId = topLevelSurfaceList.nextId;
1259 ApplicationManager.startApplication(dashApplication.appId);
1255 ApplicationManager.startApplication(dashApplication.appId);
1260 waitUntilAppWindowIsFullyLoaded(dashSurfaceId);
1256 waitUntilAppWindowIsFullyLoaded(dashSurfaceId);
1261 compare(wizard.shown, true);
1257 compare(wizard.shown, true);
1262− compare(tutorial.running, true);
1263
1258
1264 // And make sure we stop when some other surface shows app
1259 // And make sure we stop when some other surface shows app
1265 var gallerySurfaceId = topLevelSurfaceList.nextId;
1260 var gallerySurfaceId = topLevelSurfaceList.nextId;
@@ -1267,12 +1262,7 @@ Rectangle {
1267 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
1262 waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
1268 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "gallery-app");
1263 tryCompareFunction(function() { return topLevelSurfaceList.applicationAt(0).appId; }, "gallery-app");
1269 compare(wizard.shown, false);
1264 compare(wizard.shown, false);
1270− compare(tutorial.running, false);
1271− tryCompare(AccountsService, "demoEdges", false);
1272 tryCompare(Wizard.System, "wizardEnabled", false);
1265 tryCompare(Wizard.System, "wizardEnabled", false);
1273−
1274− var tutorialLeft = findChild(tutorial, "tutorialLeft");
1275− compare(tutorialLeft, null); // should be destroyed with tutorial
1276 }
1266 }
1277
1267
1278 function test_tutorialPausedDuringGreeter() {
1268 function test_tutorialPausedDuringGreeter() {