on xenial help sign in
Add support for custom bottom-edge messages for the five apps we support; put each tutorial object under a loader, since they'll be around much longer now
commitc443ec72f7612fa7da9141618a090c57683ae57b authorMichael Terry <michael.terry@canonical.com> dateThu 19 Nov 2015, 14:02 (11 years ago) parent3e7be55
4 files changed+215−137
qml/Tutorial/TutorialBottom.qml+12−9 qml/Tutorial/TutorialContent.qml+166−103 qml/Tutorial/TutorialPage.qml+1 tests/qmltests/Tutorial/tst_Tutorial.qml+36−25
qml/Tutorial/TutorialBottom.qml+12−9
@@ -43,14 +43,6 @@ TutorialPage {
4343 // code used in several of the core apps and only bring this component
4444 // up if we are in those core apps.
4545
46− function canShowForApp(appId) {
47− return appId === "address-book-app" ||
48− appId === "com.ubuntu.calculator_calculator" ||
49− appId === "com.ubuntu.clock_clock" ||
50− appId === "dialer-app" ||
51− appId === "messaging-app";
52− }
53−
5446 readonly property real mainStageWidth: stage.width - sideStageWidth
5547 readonly property real sideStageWidth: root.usageScenario === "tablet" && stage.sideStageVisible ?
5648 stage.sideStageWidth : 0
@@ -81,7 +73,18 @@ TutorialPage {
8173 }
8274
8375 label {
84− text: i18n.tr("Swipe from the bottom edge to manage the app")
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 === "com.ubuntu.clock_clock" ?
82+ i18n.tr("Swipe up to manage alarms") :
83+ application.appId === "dialer-app" ?
84+ i18n.tr("Swipe up for recent calls") :
85+ application.appId === "messaging-app" ?
86+ i18n.tr("Swipe up to create a message") :
87+ i18n.tr("Swipe up to manage the app") // shouldn't be used
8588 anchors.bottom: arrow.top
8689 anchors.bottomMargin: units.gu(3)
8790 anchors.left: root.left
qml/Tutorial/TutorialContent.qml+166−103
@@ -30,13 +30,13 @@ Item {
3030 property bool keyboardVisible
3131 property var lastInputTimestamp
3232
33− readonly property bool launcherEnabled: !running || tutorialLeft.shown
34− readonly property bool spreadEnabled: !running || tutorialRight.shown
35− readonly property bool panelEnabled: !running || tutorialTop.shown
36− readonly property bool running: tutorialLeft.shown
37− || tutorialTop.shown
38− || tutorialRight.shown
39− || tutorialBottom.shown
33+ readonly property bool launcherEnabled: !running || tutorialLeftLoader.shown
34+ readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
35+ readonly property bool panelEnabled: !running || tutorialTopLoader.shown
36+ readonly property bool running: tutorialLeftLoader.shown
37+ || tutorialTopLoader.shown
38+ || tutorialRightLoader.shown
39+ || tutorialBottomLoader.shown
4040
4141 signal finished()
4242
@@ -59,123 +59,186 @@ Item {
5959 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
6060 : null
6161
62− property bool endPointsFinished: tutorialRight.skipped && tutorialBottom.skipped
62+ function haveShown(tutorialId) {
63+ return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
64+ }
65+
66+ property bool endPointsFinished: tutorialRightLoader.skipped &&
67+ tutorialBottomLoader.skipped
6368 onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
6469 }
6570
66− TutorialLeft {
67− id: tutorialLeft
68− objectName: "tutorialLeft"
71+ Loader {
72+ id: tutorialLeftLoader
73+ objectName: "tutorialLeftLoader"
6974 anchors.fill: parent
70− launcher: root.launcher
71− hides: [launcher, panel.indicators]
72− paused: root.paused
73−
74− readonly property bool skipped: !d.mobileScenario ||
75− AccountsService.demoEdgesCompleted.indexOf("left") != -1
76− isReady: !skipped && !paused && !keyboardVisible
77−
78− // Use an idle timer here, because when constructed, all our isReady variables will be false.
79− // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
80− Timer {
81− id: tutorialLeftTimer
82− interval: 0
83− onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
84− }
8575
86− onSkippedChanged: if (skipped && shown) hide()
87− onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
88− onFinished: AccountsService.markDemoEdgeCompleted("left")
76+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left")
77+ readonly property bool shown: item && item.shown
78+ active: !skipped || (item && item.visible)
79+ onSkippedChanged: if (skipped && shown) item.hide()
80+
81+ sourceComponent: TutorialLeft {
82+ id: tutorialLeft
83+ objectName: "tutorialLeft"
84+ anchors.fill: parent
85+ launcher: root.launcher
86+ hides: [launcher, panel.indicators]
87+ paused: root.paused
88+
89+ isReady: !tutorialLeftLoader.skipped && !paused && !keyboardVisible
90+
91+ // Use an idle timer here, because when constructed, all our isReady variables will be false.
92+ // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
93+ Timer {
94+ id: tutorialLeftTimer
95+ interval: 0
96+ onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
97+ }
98+
99+ onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
100+ onFinished: AccountsService.markDemoEdgeCompleted("left")
101+ }
89102 }
90103
91− TutorialTop {
92− id: tutorialTop
93− objectName: "tutorialTop"
104+ Loader {
105+ id: tutorialTopLoader
106+ objectName: "tutorialTopLoader"
94107 anchors.fill: parent
95− panel: root.panel
96− hides: [launcher, panel.indicators]
97− paused: root.paused
98108
99− readonly property bool skipped: !d.mobileScenario ||
100− AccountsService.demoEdgesCompleted.indexOf("top") != -1
101− isReady: tutorialLeft.skipped && !skipped && !paused && !keyboardVisible &&
102− !tutorialBottom.shown && !tutorialBottom.mightShow
103−
104− // We fire 30s after left edge tutorial, with at least 3s of inactivity
109+ readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
110+ readonly property bool shown: item && item.shown
111+ active: !skipped || (item && item.visible)
112+ onSkippedChanged: if (skipped && shown) item.hide()
113+
114+ sourceComponent: TutorialTop {
115+ id: tutorialTop
116+ objectName: "tutorialTop"
117+ anchors.fill: parent
118+ panel: root.panel
119+ hides: [launcher, panel.indicators]
120+ paused: root.paused
121+
122+ isReady: tutorialLeftLoader.skipped && !tutorialTopLoader.skipped &&
123+ !paused && !keyboardVisible &&
124+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
125+
126+ // We fire 30s after left edge tutorial, with at least 3s of inactivity
127+
128+ InactivityTimer {
129+ id: tutorialTopInactivityTimer
130+ lastInputTimestamp: root.lastInputTimestamp
131+ page: parent
132+ }
105133
106− InactivityTimer {
107− id: tutorialTopInactivityTimer
108− lastInputTimestamp: root.lastInputTimestamp
109− page: parent
110− }
134+ Timer {
135+ id: tutorialTopTimer
136+ objectName: "tutorialTopTimer"
137+ interval: 27000
138+ onTriggered: tutorialTopInactivityTimer.start()
139+ }
111140
112− Timer {
113− id: tutorialTopTimer
114− objectName: "tutorialTopTimer"
115− interval: 27000
116− onTriggered: tutorialTopInactivityTimer.start()
141+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
142+ onFinished: AccountsService.markDemoEdgeCompleted("top")
117143 }
118−
119− onSkippedChanged: if (skipped && shown) hide()
120− onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
121− onFinished: AccountsService.markDemoEdgeCompleted("top")
122144 }
123145
124− TutorialRight {
125− id: tutorialRight
126− objectName: "tutorialRight"
146+ Loader {
147+ id: tutorialRightLoader
148+ objectName: "tutorialRightLoader"
127149 anchors.fill: parent
128− stage: root.stage
129− usageScenario: root.usageScenario
130− hides: [launcher, panel.indicators]
131− paused: root.paused
132−
133− readonly property bool skipped: AccountsService.demoEdgesCompleted.indexOf("right") != -1
134− isReady: tutorialTop.skipped && !skipped && !paused && !keyboardVisible &&
135− !tutorialBottom.shown && !tutorialBottom.mightShow &&
136− ApplicationManager.count >= 3
137−
138− InactivityTimer {
139− id: tutorialRightInactivityTimer
140− objectName: "tutorialRightInactivityTimer"
141− lastInputTimestamp: root.lastInputTimestamp
142− page: parent
143− }
144150
145− Connections {
146− target: d
147− onFocusedAppChanged: {
148− if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
149− && d.focusedApp.state === ApplicationInfoInterface.Starting) {
150− tutorialRight.show();
151+ readonly property bool skipped: d.haveShown("right")
152+ readonly property bool shown: item && item.shown
153+ active: !skipped || (item && item.visible)
154+ onSkippedChanged: if (skipped && shown) item.hide()
155+
156+ sourceComponent: TutorialRight {
157+ id: tutorialRight
158+ objectName: "tutorialRight"
159+ anchors.fill: parent
160+ stage: root.stage
161+ usageScenario: root.usageScenario
162+ hides: [launcher, panel.indicators]
163+ paused: root.paused
164+
165+ isReady: tutorialTopLoader.skipped && !tutorialRightLoader.skipped &&
166+ !paused && !keyboardVisible &&
167+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
168+ ApplicationManager.count >= 3
169+
170+ InactivityTimer {
171+ id: tutorialRightInactivityTimer
172+ objectName: "tutorialRightInactivityTimer"
173+ lastInputTimestamp: root.lastInputTimestamp
174+ page: parent
175+ }
176+
177+ Connections {
178+ target: d
179+ onFocusedAppChanged: {
180+ if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
181+ && d.focusedApp.state === ApplicationInfoInterface.Starting) {
182+ tutorialRight.show();
183+ }
151184 }
152185 }
153− }
154186
155− onSkippedChanged: if (skipped && shown) hide()
156− onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
157− onFinished: AccountsService.markDemoEdgeCompleted("right")
187+ onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
188+ onFinished: AccountsService.markDemoEdgeCompleted("right")
189+ }
158190 }
159191
160− TutorialBottom {
161− id: tutorialBottom
162− objectName: "tutorialBottom"
192+ Loader {
193+ id: tutorialBottomLoader
194+ objectName: "tutorialBottomLoader"
163195 anchors.fill: parent
164− hides: [launcher, panel.indicators]
165− paused: root.paused
166− usageScenario: root.usageScenario
167− stage: root.stage
168− application: d.focusedApp
169−
170− readonly property bool skipped: !d.mobileScenario ||
171− AccountsService.demoEdgesCompleted.indexOf("bottom") != -1
172− readonly property bool mightShow: !skipped && d.focusedApp && canShowForApp(d.focusedApp.appId)
173− isReady: !skipped && !paused && !keyboardVisible &&
174− !tutorialTop.shown && !tutorialRight.shown &&
175− mightShow && d.focusedApp.state === ApplicationInfoInterface.Running
176−
177− onSkippedChanged: if (skipped && shown) hide()
178− onIsReadyChanged: if (isReady && !shown) show()
179− onFinished: AccountsService.markDemoEdgeCompleted("bottom")
196+
197+ // See TutorialBottom.qml for an explanation of why we only support
198+ // certain apps.
199+ readonly property var supportedApps: ["address-book-app",
200+ "com.ubuntu.calculator_calculator",
201+ "com.ubuntu.clock_clock",
202+ "dialer-app",
203+ "messaging-app"]
204+ readonly property bool skipped: {
205+ if (!d.mobileScenario) {
206+ return true;
207+ }
208+ for (var i = 0; i < supportedApps.length; i++) {
209+ if (!d.haveShown("bottom-" + supportedApps[i])) {
210+ return false;
211+ }
212+ }
213+ return true;
214+ }
215+ readonly property bool shown: item && item.shown
216+ readonly property bool haveShownFocusedApp: d.focusedApp &&
217+ d.haveShown("bottom-" + d.focusedApp.appId)
218+ readonly property bool mightShow: !skipped && d.focusedApp &&
219+ supportedApps.indexOf(d.focusedApp.appId) !== -1 &&
220+ !haveShownFocusedApp
221+ active: !skipped || (item && item.visible)
222+ onHaveShownFocusedAppChanged: if (haveShownFocusedApp && shown) hide()
223+ onSkippedChanged: if (skipped && shown) item.hide()
224+
225+ sourceComponent: TutorialBottom {
226+ id: tutorialBottom
227+ objectName: "tutorialBottom"
228+ anchors.fill: parent
229+ hides: [launcher, panel.indicators]
230+ paused: root.paused
231+ usageScenario: root.usageScenario
232+ stage: root.stage
233+ application: d.focusedApp
234+
235+ isReady: !tutorialBottomLoader.skipped && !paused && !keyboardVisible &&
236+ !tutorialTopLoader.shown && !tutorialRightLoader.shown &&
237+ tutorialBottomLoader.mightShow &&
238+ d.focusedApp.state === ApplicationInfoInterface.Running
239+
240+ onIsReadyChanged: if (isReady && !shown) show()
241+ onFinished: AccountsService.markDemoEdgeCompleted("bottom-" + d.focusedApp.appId)
242+ }
180243 }
181244}
qml/Tutorial/TutorialPage.qml+1
@@ -110,6 +110,7 @@ Showable {
110110
111111 Label {
112112 id: label
113+ objectName: "tutorialLabel"
113114 fontSize: "large"
114115 font.weight: Font.Light
115116 color: "#333333"
tests/qmltests/Tutorial/tst_Tutorial.qml+36−25
@@ -304,39 +304,39 @@ Rectangle {
304304 }
305305
306306 function openTutorialTop() {
307− var tutorialLeft = findChild(shell, "tutorialLeft");
307+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
308308 var tutorialTop = findChild(shell, "tutorialTop");
309309 var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
310310
311311 tutorialTopTimer.interval = 1;
312312 AccountsService.demoEdgesCompleted = ["left"];
313313
314− tryCompare(tutorialLeft, "visible", false);
314+ tryCompare(tutorialLeftLoader, "active", false);
315315 tryCompare(tutorialTop, "shown", true);
316316 tryCompare(tutorialTop, "opacity", 1);
317317 }
318318
319319 function openTutorialRight() {
320− var tutorialLeft = findChild(shell, "tutorialLeft");
320+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
321321 var tutorialRight = findChild(shell, "tutorialRight");
322322
323323 AccountsService.demoEdgesCompleted = ["left", "top"];
324324 ApplicationManager.startApplication("gallery-app");
325325 ApplicationManager.startApplication("facebook-webapp");
326326
327− tryCompare(tutorialLeft, "visible", false);
327+ tryCompare(tutorialLeftLoader, "active", false);
328328 tryCompare(tutorialRight, "shown", true);
329329 tryCompare(tutorialRight, "opacity", 1);
330330 }
331331
332332 function openTutorialBottom() {
333− var tutorialLeft = findChild(shell, "tutorialLeft");
333+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
334334 var tutorialBottom = findChild(shell, "tutorialBottom");
335335
336336 AccountsService.demoEdgesCompleted = ["left", "top", "right"];
337337 ApplicationManager.startApplication("dialer-app");
338338
339− tryCompare(tutorialLeft, "visible", false);
339+ tryCompare(tutorialLeftLoader, "active", false);
340340 tryCompare(tutorialBottom, "shown", true);
341341 tryCompare(tutorialBottom, "opacity", 1);
342342 }
@@ -480,10 +480,10 @@ Rectangle {
480480 function test_tutorialTopAutoSkipped() {
481481 // Test that we skip the tutorial if user uses top edge themselves
482482
483− var tutorialLeft = findChild(shell, "tutorialLeft");
483+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
484484 var tutorialTop = findChild(shell, "tutorialTop");
485485 AccountsService.demoEdgesCompleted = ["left"];
486− tryCompare(tutorialLeft, "visible", false);
486+ tryCompare(tutorialLeftLoader, "active", false);
487487 verify(!tutorialTop.shown);
488488
489489 touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
@@ -559,9 +559,9 @@ Rectangle {
559559 function test_tutorialRightAutoSkipped() {
560560 // Test that we skip the tutorial if user uses right edge themselves
561561
562− var tutorialLeft = findChild(shell, "tutorialLeft");
562+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
563563 AccountsService.demoEdgesCompleted = ["left"];
564− tryCompare(tutorialLeft, "visible", false);
564+ tryCompare(tutorialLeftLoader, "active", false);
565565
566566 touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
567567 tryCompare(AccountsService, "demoEdgesCompleted", ["left", "right"]);
@@ -570,6 +570,7 @@ Rectangle {
570570 function test_tutorialBottomEdges() {
571571 var tutorial = findChild(shell, "tutorial");
572572 var tutorialBottom = findChild(tutorial, "tutorialBottom");
573+ var tutorialLabel = findChild(tutorialBottom, "tutorialLabel");
573574 var launcher = findChild(shell, "launcher");
574575 var stage = findChild(shell, "stage");
575576 var panel = findChild(shell, "panel");
@@ -584,6 +585,7 @@ Rectangle {
584585 verify(!launcher.available);
585586 verify(!stage.spreadEnabled);
586587 verify(!panel.indicators.available);
588+ compare(tutorialLabel.text, "Swipe up for recent calls");
587589 }
588590
589591 function test_tutorialBottomFinish() {
@@ -594,15 +596,24 @@ Rectangle {
594596 touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
595597
596598 tryCompare(tutorialBottom, "shown", false);
597− tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "right", "bottom"]);
598− tryCompare(AccountsService, "demoEdges", false);
599+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "top", "right", "bottom-dialer-app"]);
600+
601+ // OK, we did one, just confirm that when all are done, we mark whole tutorial as done.
602+ verify(AccountsService.demoEdges);
603+ AccountsService.demoEdgesCompleted = ["left", "top", "right",
604+ "bottom-address-book-app",
605+ "bottom-com.ubuntu.calculator_calculator",
606+ "bottom-com.ubuntu.clock_clock",
607+ "bottom-dialer-app",
608+ "bottom-messaging-app"];
609+ verify(!AccountsService.demoEdges);
599610 }
600611
601612 function test_tutorialBottomAppearsBeforeRight() {
602613 // Confirm that if bottom edge and right edge would appear on the
603614 // the same app open, bottom edge appears first. (this is a lightly
604615 // edited version of openTutorialRight)
605− var tutorialLeft = findChild(shell, "tutorialLeft");
616+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
606617 var tutorialRight = findChild(shell, "tutorialRight");
607618 var tutorialBottom = findChild(shell, "tutorialBottom");
608619
@@ -610,10 +621,10 @@ Rectangle {
610621 ApplicationManager.startApplication("gallery-app");
611622 ApplicationManager.startApplication("dialer-app");
612623
613− tryCompare(tutorialLeft, "visible", false);
614− tryCompare(tutorialRight, "visible", false);
624+ tryCompare(tutorialLeftLoader, "active", false);
615625 tryCompare(tutorialBottom, "shown", true);
616626 tryCompare(tutorialBottom, "opacity", 1);
627+ tryCompare(tutorialRight, "visible", false);
617628 }
618629
619630 function test_tutorialBottomOnlyCoversSideStageOnTablet() {
@@ -712,20 +723,20 @@ Rectangle {
712723 function test_desktopOnlyShowsTutorialRight() {
713724 loadShell("desktop");
714725
715− var tutorialLeft = findChild(shell, "tutorialLeft");
716− var tutorialTop = findChild(shell, "tutorialTop");
717− var tutorialRight = findChild(shell, "tutorialRight");
718− var tutorialBottom = findChild(shell, "tutorialBottom");
719− verify(tutorialLeft.skipped);
720− verify(tutorialTop.skipped);
721− verify(!tutorialRight.skipped);
722− verify(tutorialBottom.skipped);
726+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
727+ var tutorialTopLoader = findChild(shell, "tutorialTopLoader");
728+ var tutorialRightLoader = findChild(shell, "tutorialRightLoader");
729+ var tutorialBottomLoader = findChild(shell, "tutorialBottomLoader");
730+ verify(!tutorialLeftLoader.active);
731+ verify(!tutorialTopLoader.active);
732+ verify(tutorialRightLoader.active);
733+ verify(!tutorialBottomLoader.active);
723734 compare(AccountsService.demoEdgesCompleted, []);
724735
725736 ApplicationManager.startApplication("dialer-app");
726737 ApplicationManager.startApplication("camera-app");
727− tryCompare(tutorialRight, "isReady", true);
728− tryCompare(tutorialRight, "shown", true);
738+ tryCompare(tutorialRightLoader.item, "isReady", true);
739+ tryCompare(tutorialRightLoader, "shown", true);
729740 }
730741
731742 function test_oskDoesNotHideTutorial() {