on xenial help sign in
Redesign the first-boot edge tutorial

Approved by: Michael Zanetti

commit7fd52cbeb67e7e3bdd083a85d345b8b8297a9585 authorMichael Zanetti <michael.zanetti@canonical.com> dateWed 23 Mar 2016, 09:49 (11 years ago) parent52892bc4851c7c
61 files changed+1502−1341
plugins/AccountsService/AccountsService.cpp+16 plugins/AccountsService/AccountsService.h+7 plugins/AccountsService/AccountsService.qmltypes+5 plugins/AccountsService/com.canonical.unity.AccountsService.xml+5 plugins/ScreenGrabber/screengrabber.cpp−2 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+40−5 plugins/Ubuntu/Gestures/DirectionalDragArea.h+8 plugins/Ubuntu/Gestures/DirectionalDragArea_p.h+2 plugins/Utils/CMakeLists.txt+1−1 plugins/Utils/Utils.qmltypes+2−2 plugins/Utils/plugin.cpp+2−2 plugins/Utils/windowkeysfilter.cpp → plugins/Utils/windowinputfilter.cpp+14−16 plugins/Utils/windowkeysfilter.h → plugins/Utils/windowinputfilter.h+11−11 qml/Greeter/Greeter.qml+10−6 qml/Launcher/Launcher.qml+2−3 qml/Panel/IndicatorsMenu.qml−2 qml/Shell.qml+27−45 qml/Stages/AbstractStage.qml+1 qml/Stages/ApplicationWindow.qml−2 qml/Stages/PhoneStage.qml+3−6 qml/Stages/TabletStage.qml+9−2 qml/Tutorial/Arrow.qml−56 qml/Tutorial/InactivityTimer.qml+62 qml/Tutorial/Slider.qml−123 qml/Tutorial/Tick.qml−29 qml/Tutorial/Tutorial.qml+39−14 qml/Tutorial/TutorialBottom.qml+86−66 qml/Tutorial/TutorialBottomFinish.qml−41 qml/Tutorial/TutorialContent.qml+225−55 qml/Tutorial/TutorialLeft.qml+21−68 qml/Tutorial/TutorialLeftFinish.qml−41 qml/Tutorial/TutorialLeftLong.qml+54 qml/Tutorial/TutorialPage.qml+62−186 qml/Tutorial/TutorialRight.qml+30−192 qml/Tutorial/TutorialTop.qml+61 qml/Tutorial/graphics/arrow.svg+19 qml/Tutorial/graphics/background1.png qml/Tutorial/graphics/background2.png qml/Tutorial/graphics/camera.png qml/Tutorial/graphics/chevron.png qml/Tutorial/graphics/dialer.png qml/Tutorial/graphics/facebook.png qml/Tutorial/graphics/gallery.png qml/Tutorial/graphics/tick.png tests/autopilot/unity8/shell/emulators.py−2 tests/autopilot/unity8/shell/tests/test_tutorial.py−57 tests/autopilot/unity8/tutorial.py−73 tests/mocks/AccountsService/AccountsService.cpp+20 tests/mocks/AccountsService/AccountsService.h+10 tests/mocks/AccountsService/AccountsService.qmltypes+5 tests/mocks/Unity/Application/ApplicationInfo.h+1−1 tests/mocks/Utils/CMakeLists.txt+1−1 tests/mocks/Utils/Utils.qmltypes+2−2 tests/mocks/Utils/plugin.cpp+2−2 tests/plugins/AccountsService/PropertiesServer.cpp+1 tests/plugins/AccountsService/client.cpp+37 tests/qmltests/Components/tst_PhysicalKeysMapper.qml+3−3 tests/qmltests/Greeter/tst_Greeter.qml+7−1 tests/qmltests/Tutorial/tst_Tutorial.qml+586−193 tests/qmltests/tst_Shell.qml+3−8 tests/qmltests/tst_ShellWithPin.qml−22
plugins/AccountsService/AccountsService.cpp+16
@@ -32,6 +32,7 @@
3232
3333#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
3434#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
35+#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
3536#define PROP_EMAIL QStringLiteral("Email")
3637#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
3738#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
@@ -89,6 +90,7 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
8990 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
9091 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
9192 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
93+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
9294 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
9395
9496 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -152,6 +154,20 @@ void AccountsService::setDemoEdges(bool demoEdges)
152154 setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
153155}
154156
157+QStringList AccountsService::demoEdgesCompleted() const
158+{
159+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
160+ return value.toStringList();
161+}
162+
163+void AccountsService::markDemoEdgeCompleted(const QString &edge)
164+{
165+ auto currentList = demoEdgesCompleted();
166+ if (!currentList.contains(edge)) {
167+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
168+ }
169+}
170+
155171bool AccountsService::enableLauncherWhileLocked() const
156172{
157173 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
plugins/AccountsService/AccountsService.h+7
@@ -20,6 +20,7 @@
2020#include <QHash>
2121#include <QObject>
2222#include <QString>
23+#include <QStringList>
2324#include <QVariant>
2425
2526class AccountsServiceDBusAdaptor;
@@ -37,6 +38,9 @@ class AccountsService: public QObject
3738 READ demoEdges
3839 WRITE setDemoEdges
3940 NOTIFY demoEdgesChanged)
41+ Q_PROPERTY (QStringList demoEdgesCompleted
42+ READ demoEdgesCompleted
43+ NOTIFY demoEdgesCompletedChanged)
4044 Q_PROPERTY (bool enableLauncherWhileLocked
4145 READ enableLauncherWhileLocked
4246 NOTIFY enableLauncherWhileLockedChanged)
@@ -82,6 +86,8 @@ public:
8286 void setUser(const QString &user);
8387 bool demoEdges() const;
8488 void setDemoEdges(bool demoEdges);
89+ QStringList demoEdgesCompleted() const;
90+ Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
8591 bool enableLauncherWhileLocked() const;
8692 bool enableIndicatorsWhileLocked() const;
8793 QString backgroundFile() const;
@@ -101,6 +107,7 @@ public:
101107Q_SIGNALS:
102108 void userChanged();
103109 void demoEdgesChanged();
110+ void demoEdgesCompletedChanged();
104111 void enableLauncherWhileLockedChanged();
105112 void enableIndicatorsWhileLockedChanged();
106113 void backgroundFileChanged();
plugins/AccountsService/AccountsService.qmltypes+5
@@ -23,6 +23,7 @@ Module {
2323 }
2424 Property { name: "user"; type: "string" }
2525 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "demoEdgesCompleted"; type: "QStringList"; isReadonly: true }
2627 Property { name: "enableLauncherWhileLocked"; type: "bool"; isReadonly: true }
2728 Property { name: "enableIndicatorsWhileLocked"; type: "bool"; isReadonly: true }
2829 Property { name: "backgroundFile"; type: "string"; isReadonly: true }
@@ -32,5 +33,9 @@ Module {
3233 Property { name: "hereEnabled"; type: "bool" }
3334 Property { name: "hereLicensePath"; type: "string"; isReadonly: true }
3435 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
36+ Method {
37+ name: "markDemoEdgeCompleted"
38+ Parameter { name: "edge"; type: "string" }
39+ }
3540 }
3641}
plugins/AccountsService/com.canonical.unity.AccountsService.xml+5
@@ -14,6 +14,11 @@
1414 <annotation name="org.freedesktop.Accounts.DefaultValue" value="true"/>
1515 </property>
1616
17+ <!-- List of tutorial pages that have been completed by the user -->
18+ <property name="DemoEdgesCompleted" type="as" access="readwrite">
19+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="[]"/>
20+ </property>
21+
1722 <property name="LauncherItems" type="aa{sv}" access="readwrite">
1823 <annotation name="org.freedesktop.Accounts.DefaultValue" value="[{'defaults': <true>}]"/>
1924 </property>
plugins/ScreenGrabber/screengrabber.cpp−2
@@ -46,12 +46,10 @@ ScreenGrabber::ScreenGrabber(QObject *parent)
4646
4747 QDir screenshotsDir;
4848 if (qEnvironmentVariableIsSet("UNITY_TESTING")) {
49− qDebug() << "Using test environment";
5049 QTemporaryDir tDir;
5150 tDir.setAutoRemove(false);
5251 screenshotsDir = tDir.path();
5352 } else {
54− qDebug() << "Using real environment";
5553 screenshotsDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
5654 }
5755 screenshotsDir.mkpath(QStringLiteral("Screenshots"));
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+40−5
@@ -194,6 +194,26 @@ void DirectionalDragArea::setImmediateRecognition(bool enabled)
194194 }
195195}
196196
197+bool DirectionalDragArea::monitorOnly() const
198+{
199+ return d->monitorOnly;
200+}
201+
202+void DirectionalDragArea::setMonitorOnly(bool monitorOnly)
203+{
204+ if (d->monitorOnly != monitorOnly) {
205+ d->monitorOnly = monitorOnly;
206+
207+ if (monitorOnly && d->status == DirectionalDragAreaPrivate::Undecided) {
208+ TouchRegistry::instance()->removeCandidateOwnerForTouch(d->touchId, this);
209+ // We still wanna know when it ends for keeping the composition time window up-to-date
210+ TouchRegistry::instance()->addTouchWatcher(d->touchId, this);
211+ }
212+
213+ Q_EMIT monitorOnlyChanged(monitorOnly);
214+ }
215+}
216+
197217void DirectionalDragArea::removeTimeConstraints()
198218{
199219 d->setMaxTime(60 * 60 * 1000);
@@ -260,7 +280,10 @@ void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTou
260280 unownedTouchEvent_undecided(unownedTouchEvent);
261281 break;
262282 default: // Recognized:
263− // do nothing
283+ if (monitorOnly) {
284+ // Treat unowned event as if we owned it, but we are really just watching it
285+ touchEvent_recognized(event);
286+ }
264287 break;
265288 }
266289
@@ -311,7 +334,9 @@ void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *
311334 }
312335
313336 if (movedFarEnoughAlongGestureAxis()) {
314− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
337+ if (!monitorOnly) {
338+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
339+ }
315340 setStatus(Recognized);
316341 setPublicPos(touchPoint->pos());
317342 setPublicScenePos(touchScenePos);
@@ -411,12 +436,21 @@ void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
411436 if (recognitionIsDisabled()) {
412437 // Behave like a dumb TouchArea
413438 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
414− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
415439 setStatus(Recognized);
416− event->accept();
440+ if (monitorOnly) {
441+ watchPressedTouchPoints(touchPoints);
442+ event->ignore();
443+ } else {
444+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
445+ event->accept();
446+ }
417447 } else {
418448 // just monitor the touch points for now.
419− TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
449+ if (monitorOnly) {
450+ watchPressedTouchPoints(touchPoints);
451+ } else {
452+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
453+ }
420454
421455 setStatus(Undecided);
422456 // Let the item below have it. We will monitor it and grab it later if a gesture
@@ -893,5 +927,6 @@ DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
893927 , recognitionTimer(nullptr)
894928 , timeSource(new RealTimeSource)
895929 , activeTouches(timeSource)
930+ , monitorOnly(false)
896931{
897932}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+8
@@ -77,6 +77,10 @@ class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
7777 WRITE setImmediateRecognition
7878 NOTIFY immediateRecognitionChanged)
7979
80+ // Whether we are merely monitoring touch events (in which case, we don't
81+ // claim ownership of the touch).
82+ Q_PROPERTY(bool monitorOnly READ monitorOnly WRITE setMonitorOnly NOTIFY monitorOnlyChanged)
83+
8084 Q_ENUMS(Direction)
8185public:
8286 DirectionalDragArea(QQuickItem *parent = 0);
@@ -100,6 +104,9 @@ public:
100104 bool immediateRecognition() const;
101105 void setImmediateRecognition(bool enabled);
102106
107+ bool monitorOnly() const;
108+ void setMonitorOnly(bool monitorOnly);
109+
103110 bool event(QEvent *e) override;
104111
105112 /*
@@ -123,6 +130,7 @@ Q_SIGNALS:
123130 void touchSceneXChanged(qreal value);
124131 void touchSceneYChanged(qreal value);
125132 void immediateRecognitionChanged(bool value);
133+ void monitorOnlyChanged(bool value);
126134
127135protected:
128136 void touchEvent(QTouchEvent *event) override;
plugins/Ubuntu/Gestures/DirectionalDragArea_p.h+2
@@ -160,6 +160,8 @@ public:
160160
161161 ActiveTouchesInfo activeTouches;
162162
163+ bool monitorOnly;
164+
163165Q_SIGNALS:
164166 void statusChanged(Status value);
165167};
plugins/Utils/CMakeLists.txt+1−1
@@ -17,7 +17,7 @@ set(QMLPLUGIN_SRC
1717 unitysortfilterproxymodelqml.cpp
1818 Timer.cpp
1919 unitymenumodelpaths.cpp
20− windowkeysfilter.cpp
20+ windowinputfilter.cpp
2121 windowscreenshotprovider.cpp
2222 easingcurve.cpp
2323 windowstatestorage.cpp
plugins/Utils/Utils.qmltypes+2−2
@@ -147,10 +147,10 @@ Module {
147147 }
148148 }
149149 Component {
150− name: "WindowKeysFilter"
150+ name: "WindowInputFilter"
151151 defaultProperty: "data"
152152 prototype: "QQuickItem"
153− exports: ["Utils/WindowKeysFilter 0.1"]
153+ exports: ["Utils/WindowInputFilter 0.1"]
154154 exportMetaObjectRevisions: [0]
155155 }
156156 Component {
plugins/Utils/plugin.cpp+2−2
@@ -30,7 +30,7 @@
3030#include "qlimitproxymodelqml.h"
3131#include "unitysortfilterproxymodelqml.h"
3232#include "unitymenumodelpaths.h"
33−#include "windowkeysfilter.h"
33+#include "windowinputfilter.h"
3434#include "windowscreenshotprovider.h"
3535#include "windowstatestorage.h"
3636#include "constants.h"
@@ -69,7 +69,7 @@ void UtilsPlugin::registerTypes(const char *uri)
6969 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
7070 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
7171 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
72− qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
72+ qmlRegisterType<WindowInputFilter>(uri, 0, 1, "WindowInputFilter");
7373 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
7474 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
7575 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
plugins/Utils/windowkeysfilter.cpp → plugins/Utils/windowinputfilter.cpprenamed+14−16
@@ -16,36 +16,34 @@
1616 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
1717 */
1818
19−#include "windowkeysfilter.h"
19+#include "windowinputfilter.h"
2020
2121#include <QQuickWindow>
2222
23−WindowKeysFilter::WindowKeysFilter(QQuickItem *parent)
23+WindowInputFilter::WindowInputFilter(QQuickItem *parent)
2424 : QQuickItem(parent),
25− m_currentEventTimestamp(0)
25+ m_lastInputTimestamp(0)
2626{
2727 connect(this, &QQuickItem::windowChanged,
28− this, &WindowKeysFilter::setupFilterOnWindow);
28+ this, &WindowInputFilter::setupFilterOnWindow);
2929}
3030
31−bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
31+bool WindowInputFilter::eventFilter(QObject *watched, QEvent *event)
3232{
3333 Q_ASSERT(!m_filteredWindow.isNull());
3434 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
3535 Q_UNUSED(watched);
3636
37+ QInputEvent *inputEvent = dynamic_cast<QInputEvent*>(event);
38+ if (inputEvent) {
39+ m_lastInputTimestamp = inputEvent->timestamp();
40+ Q_EMIT lastInputTimestampChanged();
41+ }
42+
3743 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
3844 // Let QML see this event and decide if it does not want it
3945 event->accept();
40−
41− m_currentEventTimestamp = static_cast<QInputEvent*>(event)->timestamp();
42− Q_EMIT currentEventTimestampChanged();
43−
4446 QCoreApplication::sendEvent(this, event);
45−
46− m_currentEventTimestamp = 0;
47− Q_EMIT currentEventTimestampChanged();
48−
4947 return event->isAccepted();
5048 } else {
5149 // Not interested
@@ -53,7 +51,7 @@ bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
5351 }
5452}
5553
56−void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)
54+void WindowInputFilter::setupFilterOnWindow(QQuickWindow *window)
5755{
5856 if (!m_filteredWindow.isNull()) {
5957 m_filteredWindow->removeEventFilter(this);
@@ -66,7 +64,7 @@ void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)
6664 }
6765}
6866
69−ulong WindowKeysFilter::currentEventTimestamp() const
67+ulong WindowInputFilter::lastInputTimestamp() const
7068{
71− return m_currentEventTimestamp;
69+ return m_lastInputTimestamp;
7270}
plugins/Utils/windowkeysfilter.h → plugins/Utils/windowinputfilter.hrenamed+11−11
@@ -16,8 +16,8 @@
1616 * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
1717 */
1818
19−#ifndef UNITY_WINDOWKEYSFILTER_H
20−#define UNITY_WINDOWKEYSFILTER_H
19+#ifndef UNITY_WINDOWINPUTFILTER_H
20+#define UNITY_WINDOWINPUTFILTER_H
2121
2222#include <QQuickItem>
2323#include <QPointer>
@@ -29,30 +29,30 @@
2929 accepted ones will be filtered out. Events are accepted by default, so make sure you reject
3030 the keys you're not interested in.
3131
32− If more than one WindowKeysFilter exist in the same QML scene (and thus in the same QQuickWindow)
32+ If more than one WindowInputFilter exist in the same QML scene (and thus in the same QQuickWindow)
3333 they will be called in the order of creation, which can be tricky to assess. So the best practice
34− is to have at most one WindowKeysFilter per QML scene.
34+ is to have at most one WindowInputFilter per QML scene.
3535 */
36−class WindowKeysFilter : public QQuickItem
36+class WindowInputFilter : public QQuickItem
3737{
3838 Q_OBJECT
39− Q_PROPERTY(ulong currentEventTimestamp READ currentEventTimestamp NOTIFY currentEventTimestampChanged)
39+ Q_PROPERTY(ulong lastInputTimestamp READ lastInputTimestamp NOTIFY lastInputTimestampChanged)
4040public:
41− WindowKeysFilter(QQuickItem *parent = 0);
41+ WindowInputFilter(QQuickItem *parent = 0);
4242
4343 bool eventFilter(QObject *watched, QEvent *event) override;
4444
45− ulong currentEventTimestamp() const;
45+ ulong lastInputTimestamp() const;
4646
4747Q_SIGNALS:
48− void currentEventTimestampChanged();
48+ void lastInputTimestampChanged();
4949
5050private Q_SLOTS:
5151 void setupFilterOnWindow(QQuickWindow *window);
5252
5353private:
5454 QPointer<QQuickWindow> m_filteredWindow;
55− ulong m_currentEventTimestamp;
55+ ulong m_lastInputTimestamp;
5656};
5757
58−#endif // UNITY_WINDOWKEYSFILTER_H
58+#endif // UNITY_WINDOWINPUTFILTER_H
qml/Greeter/Greeter.qml+10−6
@@ -160,6 +160,14 @@ Showable {
160160 return false;
161161 }
162162 }
163+
164+ function checkForcedUnlock() {
165+ if (forcedUnlock && shown && loader.item) {
166+ // pretend we were just authenticated
167+ loader.item.notifyAuthenticationSucceeded();
168+ loader.item.hide();
169+ }
170+ }
163171 }
164172
165173 onLauncherOffsetChanged: {
@@ -168,12 +176,8 @@ Showable {
168176 }
169177 }
170178
171− onForcedUnlockChanged: {
172− if (forcedUnlock && shown) {
173− // pretend we were just authenticated
174− loader.item.notifyAuthenticationSucceeded();
175− }
176− }
179+ onForcedUnlockChanged: d.checkForcedUnlock()
180+ Component.onCompleted: d.checkForcedUnlock()
177181
178182 onRequiredChanged: {
179183 if (required) {
qml/Launcher/Launcher.qml+2−3
@@ -28,7 +28,6 @@ FocusScope {
2828 property bool lockedVisible: false
2929 property bool available: true // can be used to disable all interactions
3030 property alias inverted: panel.inverted
31− property bool shadeBackground: true // can be used to disable background shade when launcher is visible
3231
3332 property int panelWidth: units.gu(10)
3433 property int dragAreaWidth: units.gu(1)
@@ -298,7 +297,7 @@ FocusScope {
298297 InverseMouseArea {
299298 id: closeMouseArea
300299 anchors.fill: panel
301− enabled: root.shadeBackground && root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
300+ enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
302301 visible: enabled
303302 onPressed: {
304303 panel.highlightIndex = -2
@@ -310,7 +309,7 @@ FocusScope {
310309 id: backgroundShade
311310 anchors.fill: parent
312311 color: "black"
313− opacity: root.shadeBackground && root.state == "visible" && !root.lockedVisible ? 0.6 : 0
312+ opacity: root.state == "visible" && !root.lockedVisible ? 0.6 : 0
314313
315314 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
316315 }
qml/Panel/IndicatorsMenu.qml−2
@@ -36,7 +36,6 @@ Showable {
3636 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
3737 readonly property bool fullyClosed: unitProgress == 0
3838 property bool enableHint: true
39− property bool contentEnabled: true
4039 property bool showOnClick: true
4140 property color panelColor: theme.palette.normal.background
4241
@@ -85,7 +84,6 @@ Showable {
8584 height: openedHeight - bar.height - handle.height
8685 indicatorsModel: root.indicatorsModel
8786 visible: root.unitProgress > 0
88− enabled: contentEnabled
8987 currentMenuIndex: bar.currentItemIndex
9088 }
9189
qml/Shell.qml+27−45
@@ -24,7 +24,7 @@ import Ubuntu.Gestures 0.1
2424import Ubuntu.Telephony 0.1 as Telephony
2525import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
27−import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
27+import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
2828import GSettings 1.0
2929import Utils 0.1
3030import Powerd 0.1
@@ -167,12 +167,13 @@ Item {
167167 }
168168
169169 GlobalShortcut {
170− // dummy shortcut to force creation of GlobalShortcutRegistry before WindowKeyFilter
170+ // dummy shortcut to force creation of GlobalShortcutRegistry before WindowInputFilter
171171 }
172172
173− WindowKeysFilter {
174− Keys.onPressed: physicalKeysMapper.onKeyPressed(event, currentEventTimestamp);
175− Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
173+ WindowInputFilter {
174+ id: inputFilter
175+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
176+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
176177 }
177178
178179 WindowInputMonitor {
@@ -205,7 +206,7 @@ Item {
205206 onFocusedApplicationIdChanged: {
206207 var appId = ApplicationManager.focusedApplicationId;
207208
208− if (tutorial.running && appId != "" && appId != "unity8-dash") {
209+ if (wizard.active && appId != "" && appId != "unity8-dash") {
209210 // If this happens on first boot, we may be in edge
210211 // tutorial or wizard while receiving a call. But a call
211212 // is more important than wizard so just bail out of those.
@@ -262,8 +263,7 @@ Item {
262263 source = qmlComponent;
263264 }
264265
265− property bool interactive: tutorial.spreadEnabled
266− && (!greeter || !greeter.shown)
266+ property bool interactive: (!greeter || !greeter.shown)
267267 && panel.indicators.fullyClosed
268268 && launcher.progress == 0
269269 && !notifications.useModal
@@ -357,30 +357,6 @@ Item {
357357 value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
358358 }
359359 }
360−
361− Tutorial {
362− id: tutorial
363− objectName: "tutorial"
364− anchors.fill: parent
365−
366− // EdgeDragAreas don't work with mice. So to avoid trapping the user,
367− // we skip the tutorial on the Desktop to avoid using them. The
368− // Desktop doesn't use the same spread design anyway. The tutorial is
369− // all a bit of a placeholder on non-phone form factors right now.
370− // When the design team gives us more guidance, we can do something
371− // more clever here.
372− active: usageScenario != "desktop" && AccountsService.demoEdges
373−
374− paused: lightDM.greeter.active
375− launcher: launcher
376− panel: panel
377− edgeSize: shell.edgeSize
378−
379− onFinished: {
380− AccountsService.demoEdges = false;
381− active = false; // for immediate response / if AS is having problems
382− }
383− }
384360 }
385361
386362 InputMethod {
@@ -391,7 +367,7 @@ Item {
391367 topMargin: panel.panelHeight
392368 leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
393369 }
394− z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
370+ z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
395371 }
396372
397373 Connections {
@@ -422,7 +398,7 @@ Item {
422398 hides: [launcher, panel.indicators]
423399 tabletMode: shell.usageScenario != "phone"
424400 launcherOffset: launcher.progress
425− forcedUnlock: tutorial.running
401+ forcedUnlock: wizard.active
426402 background: wallpaperResolver.background
427403
428404 // avoid overlapping with Launcher's edge drag area
@@ -479,7 +455,7 @@ Item {
479455
480456 onStatusChanged: {
481457 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
482− !callManager.hasCalls && !tutorial.running) {
458+ !callManager.hasCalls && !wizard.active) {
483459 // We don't want to simply call greeter.showNow() here, because
484460 // that will take too long. Qt will delay button event
485461 // handling until the greeter is done loading and may think the
@@ -495,10 +471,6 @@ Item {
495471 }
496472
497473 function showHome() {
498− if (tutorial.running) {
499− return
500− }
501−
502474 greeter.notifyAboutToFocusApp("unity8-dash");
503475
504476 var animate = !lightDM.greeter.active && !stages.shown
@@ -532,7 +504,6 @@ Item {
532504 available: tutorial.panelEnabled
533505 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
534506 && (!greeter || !greeter.hasLockedApp)
535− contentEnabled: tutorial.panelContentEnabled
536507 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537508
538509 minimizedPanelHeight: units.gu(3)
@@ -571,7 +542,6 @@ Item {
571542 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
572543 && !greeter.hasLockedApp
573544 inverted: shell.usageScenario !== "desktop"
574− shadeBackground: !tutorial.running
575545 superPressed: physicalKeysMapper.superPressed
576546 superTabPressed: physicalKeysMapper.superTabPressed
577547 panelWidth: units.gu(settings.launcherWidth)
@@ -585,10 +555,8 @@ Item {
585555 }
586556 }
587557 onLauncherApplicationSelected: {
588− if (!tutorial.running) {
589− greeter.notifyAboutToFocusApp(appId);
590− shell.activateApplication(appId)
591− }
558+ greeter.notifyAboutToFocusApp(appId);
559+ shell.activateApplication(appId);
592560 }
593561 onShownChanged: {
594562 if (shown) {
@@ -628,6 +596,20 @@ Item {
628596 }
629597 }
630598
599+ Tutorial {
600+ id: tutorial
601+ objectName: "tutorial"
602+ anchors.fill: parent
603+
604+ paused: callManager.hasCalls || greeter.shown
605+ keyboardVisible: inputMethod.state === "shown"
606+ usageScenario: shell.usageScenario
607+ lastInputTimestamp: inputFilter.lastInputTimestamp
608+ launcher: launcher
609+ panel: panel
610+ stage: applicationsDisplayLoader.item
611+ }
612+
631613 Wizard {
632614 id: wizard
633615 objectName: "wizard"
qml/Stages/AbstractStage.qml+1
@@ -28,6 +28,7 @@ Rectangle {
2828 property url background
2929 property bool beingResized
3030 property int dragAreaWidth
31+ property real dragProgress // How far left the stage has been dragged, used externally by tutorial code
3132 property bool interactive
3233 property real inverseProgress // This is the progress for left edge drags, in pixels.
3334 property bool keepDashRunning: true
qml/Stages/ApplicationWindow.qml−2
@@ -60,7 +60,6 @@ FocusScope {
6060 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
6161 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
6262 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
63− readonly property url defaultScreenshot: (root.application && root.application.defaultScreenshot !== undefined) ? root.application.defaultScreenshot : ""
6463
6564 // Whether the Application had a surface before but lost it.
6665 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
@@ -112,7 +111,6 @@ FocusScope {
112111 Image {
113112 id: screenshotImage
114113 objectName: "screenshotImage"
115− source: d.defaultScreenshot
116114 anchors.fill: parent
117115 antialiasing: !root.interactive
118116
qml/Stages/PhoneStage.qml+3−6
@@ -120,14 +120,11 @@ AbstractStage {
120120 : (Qt.PortraitOrientation | Qt.LandscapeOrientation
121121 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
122122
123− // How far left the stage has been dragged
124− readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
123+ // How far left the stage has been dragged, used externally by tutorial code
124+ dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
125125
126126 readonly property alias dragging: spreadDragArea.dragging
127127
128− // Only used by the tutorial right now, when it is teasing the right edge
129− property real dragAreaOverlap
130−
131128 signal opened()
132129
133130 function select(appId) {
@@ -657,7 +654,7 @@ AbstractStage {
657654 direction: Direction.Leftwards
658655 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
659656
660− anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
657+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; }
661658 width: root.dragAreaWidth
662659
663660 property var gesturePoints: new Array()
qml/Stages/TabletStage.qml+9−2
@@ -27,6 +27,9 @@ AbstractStage {
2727 objectName: "stages"
2828 anchors.fill: parent
2929
30+ property alias sideStageVisible: spreadView.sideStageVisible
31+ property alias sideStageWidth: spreadView.sideStageWidth
32+
3033 // Functions to be called from outside
3134 function updateFocusedAppOrientation() {
3235 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
@@ -97,6 +100,9 @@ AbstractStage {
97100 }
98101 }
99102
103+ // How far left the stage has been dragged, used externally by tutorial code
104+ dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
105+
100106 onWidthChanged: {
101107 spreadView.selectedIndex = -1;
102108 spreadView.phase = 0;
@@ -318,8 +324,6 @@ AbstractStage {
318324 contentX: -shift
319325
320326 property int tileDistance: units.gu(20)
321− property int sideStageWidth: units.gu(40)
322− property bool sideStageVisible: priv.sideStageAppId
323327
324328 // This indicates when the spreadView is active. That means, all the animations
325329 // are activated and tiles need to line up for the spread.
@@ -382,6 +386,9 @@ AbstractStage {
382386 }
383387
384388 property real sideStageDragProgress: sideStage.progress
389+ property bool sideStageVisible: priv.sideStageAppId
390+ property real sideStageWidth: units.gu(40)
391+
385392 property bool surfaceDragging: triGestureArea.recognisedDrag
386393
387394 // In case the ApplicationManager already holds an app when starting up we're missing animations
qml/Tutorial/Arrow.qmldeleted−56
@@ -1,56 +0,0 @@
1−/*
2− * Copyright (C) 2014 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−
20−Item {
21− id: root
22−
23− property alias color: circle.color
24−
25− // Will make whole arrow darker
26− property real darkenBy: 0
27−
28− property alias chevronOpacity: chevron.opacity
29−
30− ////
31−
32− Rectangle {
33− id: circle
34− anchors.fill: parent
35− radius: width / 2
36− }
37−
38− Image {
39− id: chevron
40− anchors.centerIn: parent
41− source: Qt.resolvedUrl("graphics/chevron.png")
42− fillMode: Image.PreserveAspectFit
43− sourceSize.width: 152
44− sourceSize.height: 152
45− width: parent.width / 2
46− height: parent.height / 2
47− }
48−
49− Rectangle {
50− id: darkCircle
51− anchors.fill: parent
52− radius: width / 2
53− color: "black"
54− opacity: root.darkenBy
55− }
56−}
qml/Tutorial/InactivityTimer.qmlnew file+62
@@ -0,0 +1,62 @@
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+
20+Item {
21+ readonly property alias running: internalTimer.running
22+ property alias interval: internalTimer.interval
23+ property var page
24+ property int lastInputTimestamp
25+
26+ function start() {
27+ internalTimer.start();
28+ }
29+
30+ ////
31+
32+ onLastInputTimestampChanged: {
33+ if (internalTimer.running) {
34+ internalTimer.restart();
35+ }
36+ }
37+
38+ Connections {
39+ target: page
40+ onIsReadyChanged: {
41+ if (page.isReady && internalTimer.running) {
42+ internalTimer.restart();
43+ }
44+ }
45+ }
46+
47+ Timer {
48+ id: internalTimer
49+
50+ interval: 3000
51+
52+ onTriggered: {
53+ if (page.isReady) {
54+ if (!page.shown) {
55+ page.show();
56+ }
57+ } else if (!page.skipped) {
58+ restart();
59+ }
60+ }
61+ }
62+}
qml/Tutorial/Slider.qmldeleted−123
@@ -1,123 +0,0 @@
1−/*
2− * Copyright (C) 2014 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−
20−Item {
21− id: root
22−
23− // Whether this slider is short or long
24− property bool shortSwipe
25−
26− // How far the user has slid
27− property real offset
28−
29− // Set to true when slider is being used
30− property bool active
31−
32− // How far in percentage terms
33− readonly property real percent: d.slideOffset / target.x
34−
35− QtObject {
36− id: d
37− readonly property color trayColor: "#424141"
38− readonly property real margin: units.gu(0.5)
39− readonly property real arrowSize: root.height - margin * 2
40− readonly property real dotSize: units.dp(1)
41− readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
42− readonly property real offscreenOffset: units.gu(2)
43− }
44−
45− implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5)
46− implicitHeight: units.gu(6.5)
47−
48− Rectangle {
49− color: d.trayColor
50− anchors.fill: parent
51− anchors.rightMargin: clipBox.width - 1
52− }
53−
54− // We want to have a circular border around the target. But we can't just
55− // do a radius on two of a rectangle's corners. So we clip a full circle.
56− Item {
57− id: clipBox
58−
59− clip: true
60− anchors.top: parent.top
61− anchors.bottom: parent.bottom
62− anchors.right: parent.right
63− width: parent.height / 2
64−
65− Rectangle {
66− color: d.trayColor
67− anchors.top: parent.top
68− anchors.bottom: parent.bottom
69− anchors.right: parent.right
70− width: parent.width * 2
71− radius: parent.width
72− }
73− }
74−
75− Arrow {
76− id: target
77− width: d.arrowSize
78− height: d.arrowSize
79− color: "#73000000"
80− chevronOpacity: 0.52
81− anchors.right: parent.right
82− anchors.rightMargin: d.margin
83− anchors.verticalCenter: parent.verticalCenter
84− }
85−
86− Row {
87− anchors.left: handle.horizontalCenter
88− anchors.right: target.horizontalCenter
89− anchors.verticalCenter: parent.verticalCenter
90−
91− layoutDirection: Qt.RightToLeft
92− spacing: d.dotSize * 2
93−
94− Repeater {
95− model: parent.width / (parent.spacing + d.dotSize)
96− Rectangle {
97− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
98− height: d.dotSize
99− width: height
100− radius: width
101− color: "white"
102− opacity: 0.2
103− }
104− }
105− }
106−
107− Arrow {
108− id: handle
109− width: d.arrowSize
110− height: d.arrowSize
111− color: UbuntuColors.orange
112− darkenBy: root.active ? 0.5 : 0
113− anchors.left: parent.left
114− // We use a Translate transform rather than anchors.leftMargin because
115− // the latter has weird performance problems on the TutorialRight page.
116− transform: [
117− Translate {
118− x: d.slideOffset
119− }
120− ]
121− anchors.verticalCenter: parent.verticalCenter
122− }
123−}
qml/Tutorial/Tick.qmldeleted−29
@@ -1,29 +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−
20−MouseArea {
21− implicitHeight: tick.height
22− implicitWidth: tick.width
23− Image {
24− id: tick
25− source: Qt.resolvedUrl("graphics/tick.png")
26− height: units.gu(6.5)
27− width: units.gu(6.5)
28− }
29−}
qml/Tutorial/Tutorial.qml+39−14
@@ -16,21 +16,29 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import AccountsService 0.1
20+
21+/**
22+ * This object is always present, so it should be lean and mean. It will
23+ * use a Loader to create the heavier tutorial pages if needed.
24+ */
1925
2026Item {
2127 id: root
2228
2329 property alias active: loader.active
24− property bool paused
25− property real edgeSize
2630
2731 property Item launcher
2832 property Item panel
33+ property Item stage
34+ property string usageScenario
35+ property bool paused
36+ property bool keyboardVisible
37+ property int lastInputTimestamp
2938
3039 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
3140 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
3241 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
33− readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
3442 readonly property bool running: loader.item ? loader.item.running : false
3543
3644 function finish() {
@@ -39,40 +47,57 @@ Item {
3947 }
4048 }
4149
42− signal finished()
43−
4450 Loader {
4551 id: loader
4652 anchors.fill: parent
4753 source: "TutorialContent.qml"
54+ active: AccountsService.demoEdges
4855
4956 Binding {
5057 target: loader.item
51− property: "paused"
52− value: root.paused
58+ property: "launcher"
59+ value: root.launcher
5360 }
5461
5562 Binding {
5663 target: loader.item
57− property: "edgeSize"
58− value: root.edgeSize
64+ property: "panel"
65+ value: root.panel
5966 }
6067
6168 Binding {
6269 target: loader.item
63− property: "launcher"
64− value: root.launcher
70+ property: "stage"
71+ value: root.stage
6572 }
6673
6774 Binding {
6875 target: loader.item
69− property: "panel"
70− value: root.panel
76+ property: "usageScenario"
77+ value: root.usageScenario
78+ }
79+
80+ Binding {
81+ target: loader.item
82+ property: "paused"
83+ value: root.paused
84+ }
85+
86+ Binding {
87+ target: loader.item
88+ property: "keyboardVisible"
89+ value: root.keyboardVisible
90+ }
91+
92+ Binding {
93+ target: loader.item
94+ property: "lastInputTimestamp"
95+ value: root.lastInputTimestamp
7196 }
7297
7398 Connections {
7499 target: loader.item
75− onFinished: root.finished()
100+ onFinished: AccountsService.demoEdges = false
76101 }
77102 }
78103}
qml/Tutorial/TutorialBottom.qml+86−66
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015 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
@@ -17,88 +17,108 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Gestures 0.1
20−import "../Components"
21−import "." as LocalComponents
20+import Unity.Application 0.1
2221
2322TutorialPage {
2423 id: root
2524
26− property alias edgeSize: dragArea.height
25+ property string usageScenario
26+ property var stage
27+ property var application: null
2728
28− title: i18n.tr("Open special menus")
29− text: i18n.tr("Swipe up from the bottom edge.")
30− fullTextWidth: true
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.
3145
32− SequentialAnimation {
33− id: teaseAnimation
34− paused: running && root.paused
35− running: !dragArea.useTouchY && slider.dragOffset === 0
36− loops: Animation.Infinite
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+ application.stage === 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
3753
38− UbuntuNumberAnimation {
39− target: slider
40− property: "teaseOffset"
41− to: units.gu(1)
42− duration: UbuntuAnimation.SleepyDuration
43− }
44− UbuntuNumberAnimation {
45− target: slider
46− property: "teaseOffset"
47− to: 0
48− duration: UbuntuAnimation.SleepyDuration
49− }
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
5065 }
5166
52− foreground {
53− children: [
54− LocalComponents.Slider {
55− id: slider
56− anchors {
57− bottom: parent.bottom
58− bottomMargin: width / 2 - height / 2
59− horizontalCenter: parent.horizontalCenter
60− }
61− rotation: -90
62− offset: teaseOffset + dragOffset
63− active: dragArea.dragging
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+ }
6474
65− property real teaseOffset
66− property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0
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
6792
68− Behavior on dragOffset {
69− id: offsetAnimation
70− UbuntuNumberAnimation {}
71− }
72− }
73− ]
93+ readonly property real sideMargin: units.gu(4)
7494 }
7595
96+ // Watches drag events but does not intercept them, so that the app beneath
97+ // will still drag the bottom edge up.
7698 DirectionalDragArea {
7799 id: dragArea
100+ monitorOnly: true
78101 direction: Direction.Upwards
79− anchors {
80− bottom: parent.bottom
81− left: parent.left
82− right: parent.right
83− }
84−
85− property bool useTouchY
102+ anchors.left: parent.left
103+ anchors.right: parent.right
104+ anchors.bottom: parent.bottom
105+ height: root.dragAreaHeight
86106
87− onDraggingChanged: {
88− if (!dragging) {
89− if (slider.percent >= 0.85) {
90− root.hide();
91− } else if (slider.percent >= 0.15) {
92− root.showError();
93− }
94− }
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+ }
95112
96− // We use a separate vars here rather than just directly looking at
97− // 'dragging' because we want to preserve our 'slider.offset'
98− // value during the above percent check. Now that we made it,
99− // we can have 'slider.offset' go back to zero.
100− offsetAnimation.enabled = !dragging;
101− useTouchY = dragging;
102− }
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
103123 }
104124}
qml/Tutorial/TutorialBottomFinish.qmldeleted−41
@@ -1,41 +0,0 @@
1−/*
2− * Copyright (C) 2014 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 "." as LocalComponents
20−
21−TutorialPage {
22− id: root
23−
24− title: i18n.tr("This action does different things for different apps")
25− text: i18n.tr("Tap here to finish.")
26− fullTextWidth: true
27−
28− foreground {
29− children: [
30− LocalComponents.Tick {
31− objectName: "tick"
32− anchors {
33− horizontalCenter: parent.horizontalCenter
34− top: parent.top
35− topMargin: root.textBottom + units.gu(3)
36− }
37− onClicked: root.hide()
38− }
39− ]
40− }
41−}
qml/Tutorial/TutorialContent.qml+225−55
@@ -16,103 +16,273 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import AccountsService 0.1
20+import Unity.Application 0.1
1921
2022Item {
2123 id: root
2224
2325 property Item launcher
2426 property Item panel
27+ property Item stage
28+ property string usageScenario
29+ property bool paused
30+ property bool keyboardVisible
31+ property var lastInputTimestamp
2532
26− readonly property bool launcherEnabled: !running ||
27− (!paused && tutorialLeft.shown)
28− readonly property bool spreadEnabled: !running
29− readonly property bool panelEnabled: !running
30− readonly property bool panelContentEnabled: !running
31− readonly property alias running: d.running
32−
33− property bool paused: false
34− property real edgeSize
33+ readonly property bool launcherEnabled: !running
34+ || tutorialLeftLoader.shown
35+ || tutorialLeftLongLoader.shown
36+ readonly property bool spreadEnabled: !running || tutorialRightLoader.shown
37+ readonly property bool panelEnabled: !running || tutorialTopLoader.shown
38+ readonly property bool running: tutorialLeftLoader.shown
39+ || tutorialTopLoader.shown
40+ || tutorialRightLoader.shown
41+ || tutorialBottomLoader.shown
3542
3643 signal finished()
3744
3845 function finish() {
39− d.stop();
4046 finished();
4147 }
4248
4349 ////
4450
45− Component.onCompleted: {
46− d.start();
47− }
48−
4951 QtObject {
5052 id: d
5153
52− property bool running
54+ // We allow "" because it is briefly empty on startup, and we don't
55+ // want to improperly skip any mobile tutorials.
56+ property bool mobileScenario: root.usageScenario === "" ||
57+ root.usageScenario === "phone" ||
58+ root.usageScenario === "tablet"
5359
54− function stop() {
55− running = false;
56− }
60+ property var focusedApp: ApplicationManager.focusedApplicationId
61+ ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
62+ : null
5763
58− function start() {
59− running = true;
60− tutorialLeft.show();
64+ function haveShown(tutorialId) {
65+ return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
6166 }
67+
68+ property bool endPointsFinished: tutorialRightLoader.skipped &&
69+ tutorialBottomLoader.skipped
70+ onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
6271 }
6372
64− TutorialLeft {
65− id: tutorialLeft
66− objectName: "tutorialLeft"
73+ Loader {
74+ id: tutorialLeftLoader
75+ objectName: "tutorialLeftLoader"
6776 anchors.fill: parent
68− launcher: root.launcher
69− paused: !shown || root.paused
7077
71− onFinished: tutorialLeftFinish.show()
78+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left")
79+ readonly property bool shown: item && item.shown
80+ active: !skipped || (item && item.visible)
81+ onSkippedChanged: if (skipped && shown) item.hide()
82+
83+ sourceComponent: TutorialLeft {
84+ id: tutorialLeft
85+ objectName: "tutorialLeft"
86+ anchors.fill: parent
87+ launcher: root.launcher
88+ hides: [launcher, panel.indicators]
89+ paused: root.paused
90+
91+ isReady: !tutorialLeftLoader.skipped && !paused && !keyboardVisible
92+
93+ // Use an idle timer here, because when constructed, all our isReady variables will be false.
94+ // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
95+ Timer {
96+ id: tutorialLeftTimer
97+ interval: 0
98+ onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
99+ }
100+
101+ onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
102+ onFinished: AccountsService.markDemoEdgeCompleted("left")
103+ }
72104 }
73105
74− TutorialLeftFinish {
75− id: tutorialLeftFinish
76− objectName: "tutorialLeftFinish"
106+ Loader {
107+ id: tutorialLeftLongLoader
108+ objectName: "tutorialLeftLongLoader"
77109 anchors.fill: parent
78− textXOffset: root.launcher.panelWidth
79− paused: !shown || root.paused
80− text: i18n.tr("Tap here to continue.")
81110
82− onFinished: {
83− root.launcher.hide();
84− tutorialRight.show();
111+ readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
112+ readonly property bool shown: item && item.shown
113+ active: !skipped || (item && item.visible)
114+ onSkippedChanged: if (skipped && shown) item.hide()
115+
116+ sourceComponent: TutorialLeftLong {
117+ id: tutorialLeftLong
118+ objectName: "tutorialLeftLong"
119+ anchors.fill: parent
120+ launcher: root.launcher
121+ hides: [launcher, panel.indicators]
122+ paused: root.paused
123+
124+ skipped: tutorialLeftLongLoader.skipped
125+ isReady: tutorialLeftLoader.skipped && !skipped && !paused && !keyboardVisible &&
126+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
127+
128+ Timer {
129+ id: tutorialLeftLongTimer
130+ objectName: "tutorialLeftLongTimer"
131+ interval: 5000
132+ onTriggered: {
133+ if (parent.isReady) {
134+ if (!parent.shown) {
135+ parent.show();
136+ }
137+ } else if (!parent.skipped) {
138+ restart();
139+ }
140+ }
141+ }
142+
143+ onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
144+ onFinished: AccountsService.markDemoEdgeCompleted("left-long")
85145 }
86146 }
87147
88− TutorialRight {
89− id: tutorialRight
90− objectName: "tutorialRight"
148+ Loader {
149+ id: tutorialTopLoader
150+ objectName: "tutorialTopLoader"
91151 anchors.fill: parent
92− edgeSize: root.edgeSize
93− panel: root.panel
94− paused: !shown || root.paused
95152
96− onFinished: tutorialBottom.show()
153+ readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
154+ readonly property bool shown: item && item.shown
155+ active: !skipped || (item && item.visible)
156+ onSkippedChanged: if (skipped && shown) item.hide()
157+
158+ sourceComponent: TutorialTop {
159+ id: tutorialTop
160+ objectName: "tutorialTop"
161+ anchors.fill: parent
162+ panel: root.panel
163+ hides: [launcher, panel.indicators]
164+ paused: root.paused
165+
166+ skipped: tutorialTopLoader.skipped
167+ isReady: tutorialLeftLongLoader.skipped && !skipped && !paused && !keyboardVisible &&
168+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
169+
170+ // We fire 30s after left edge tutorial, with at least 3s of inactivity
171+
172+ InactivityTimer {
173+ id: tutorialTopInactivityTimer
174+ lastInputTimestamp: root.lastInputTimestamp
175+ page: parent
176+ }
177+
178+ Timer {
179+ id: tutorialTopTimer
180+ objectName: "tutorialTopTimer"
181+ interval: 27000
182+ onTriggered: tutorialTopInactivityTimer.start()
183+ }
184+
185+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
186+ onFinished: AccountsService.markDemoEdgeCompleted("top")
187+ }
97188 }
98189
99− TutorialBottom {
100− id: tutorialBottom
101− objectName: "tutorialBottom"
190+ Loader {
191+ id: tutorialRightLoader
192+ objectName: "tutorialRightLoader"
102193 anchors.fill: parent
103− edgeSize: root.edgeSize
104− paused: !shown || root.paused
105194
106− onFinished: tutorialBottomFinish.show()
195+ readonly property bool skipped: d.haveShown("right")
196+ readonly property bool shown: item && item.shown
197+ active: !skipped || (item && item.visible)
198+ onSkippedChanged: if (skipped && shown) item.hide()
199+
200+ sourceComponent: TutorialRight {
201+ id: tutorialRight
202+ objectName: "tutorialRight"
203+ anchors.fill: parent
204+ stage: root.stage
205+ usageScenario: root.usageScenario
206+ hides: [launcher, panel.indicators]
207+ paused: root.paused
208+
209+ skipped: tutorialRightLoader.skipped
210+ isReady: tutorialTopLoader.skipped && !skipped && !paused && !keyboardVisible &&
211+ !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
212+ ApplicationManager.count >= 3
213+
214+ InactivityTimer {
215+ id: tutorialRightInactivityTimer
216+ objectName: "tutorialRightInactivityTimer"
217+ lastInputTimestamp: root.lastInputTimestamp
218+ page: parent
219+ }
220+
221+ Connections {
222+ target: d
223+ onFocusedAppChanged: {
224+ if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
225+ && d.focusedApp.state === ApplicationInfoInterface.Starting) {
226+ tutorialRight.show();
227+ }
228+ }
229+ }
230+
231+ onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
232+ onFinished: AccountsService.markDemoEdgeCompleted("right")
233+ }
107234 }
108235
109− TutorialBottomFinish {
110− id: tutorialBottomFinish
111− objectName: "tutorialBottomFinish"
236+ Loader {
237+ id: tutorialBottomLoader
238+ objectName: "tutorialBottomLoader"
112239 anchors.fill: parent
113− backgroundFadesOut: true
114− paused: !shown || root.paused
115240
116− onFinished: root.finish()
241+ // See TutorialBottom.qml for an explanation of why we only support
242+ // certain apps.
243+ readonly property var supportedApps: ["address-book-app",
244+ "com.ubuntu.calculator_calculator",
245+ "dialer-app",
246+ "messaging-app"]
247+ readonly property bool skipped: {
248+ if (!d.mobileScenario) {
249+ return true;
250+ }
251+ for (var i = 0; i < supportedApps.length; i++) {
252+ if (!d.haveShown("bottom-" + supportedApps[i])) {
253+ return false;
254+ }
255+ }
256+ return true;
257+ }
258+ readonly property bool shown: item && item.shown
259+ readonly property bool haveShownFocusedApp: d.focusedApp &&
260+ d.haveShown("bottom-" + d.focusedApp.appId)
261+ readonly property bool mightShow: !skipped && d.focusedApp &&
262+ supportedApps.indexOf(d.focusedApp.appId) !== -1 &&
263+ !haveShownFocusedApp
264+ active: !skipped || (item && item.visible)
265+ onHaveShownFocusedAppChanged: if (haveShownFocusedApp && shown) hide()
266+ onSkippedChanged: if (skipped && shown) item.hide()
267+
268+ sourceComponent: TutorialBottom {
269+ id: tutorialBottom
270+ objectName: "tutorialBottom"
271+ anchors.fill: parent
272+ hides: [launcher, panel.indicators]
273+ paused: root.paused
274+ usageScenario: root.usageScenario
275+ stage: root.stage
276+ application: d.focusedApp
277+
278+ skipped: tutorialBottomLoader.skipped
279+ isReady: !tutorialBottomLoader.skipped && !paused && !keyboardVisible &&
280+ !tutorialTopLoader.shown && !tutorialRightLoader.shown &&
281+ tutorialBottomLoader.mightShow &&
282+ d.focusedApp.state === ApplicationInfoInterface.Running
283+
284+ onIsReadyChanged: if (isReady && !shown) show()
285+ onFinished: AccountsService.markDemoEdgeCompleted("bottom-" + d.focusedApp.appId)
286+ }
117287 }
118288}
qml/Tutorial/TutorialLeft.qml+21−68
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2015 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
@@ -23,79 +23,32 @@ TutorialPage {
2323
2424 property var launcher
2525
26− title: i18n.tr("Open the launcher")
27− text: i18n.tr("Short swipe from the left edge.")
26+ opacityOverride: 1 - launcher.visibleWidth / launcher.panelWidth
2827
29− textXOffset: root.launcher.x + root.launcher.visibleWidth
30−
31− Connections {
32− target: root.launcher
33−
34− onStateChanged: {
35− if (root.launcher.state === "visible") {
36− finishTimer.start();
37− }
38− }
39−
40− onDash: {
41− finishTimer.stop();
42− root.showError();
43− root.launcher.hide();
44− }
45− }
46−
47− SequentialAnimation {
48− id: teaseAnimation
49− objectName: "teaseAnimation"
50− paused: running && root.paused
51− running: !slider.active && root.launcher.visibleWidth === 0 && root.shown
52− loops: Animation.Infinite
53− property real bounce: 0
54− readonly property real maxBounce: units.gu(2)
55−
56− UbuntuNumberAnimation {
57− target: teaseAnimation
58− property: "bounce"
59− to: teaseAnimation.maxBounce
60− duration: UbuntuAnimation.SleepyDuration
61− }
62− UbuntuNumberAnimation {
63− target: teaseAnimation
64− property: "bounce"
65− to: 0
66− duration: UbuntuAnimation.SleepyDuration
67− }
28+ mouseArea {
29+ anchors.leftMargin: launcher.dragAreaWidth
6830 }
6931
70− Binding {
71− target: root.launcher
72− when: root.shown
73− property: "x"
74− value: Math.min(root.launcher.panelWidth - root.launcher.visibleWidth, teaseAnimation.bounce)
32+ background {
33+ sourceSize.height: 1916
34+ sourceSize.width: 1080
35+ source: Qt.resolvedUrl("graphics/background1.png")
36+ mirror: true
7537 }
7638
77− Timer {
78− id: finishTimer
79− interval: 1
80− onTriggered: {
81− root.hide();
82− root.launcher.x = 0; // make sure to reset launcher before we go
83− }
39+ arrow {
40+ anchors.left: root.left
41+ anchors.leftMargin: units.gu(2)
42+ anchors.verticalCenter: root.verticalCenter
43+ rotation: 180
8444 }
8545
86− foreground {
87− children: [
88− LocalComponents.Slider {
89− id: slider
90− anchors {
91− left: parent.left
92− top: parent.top
93− topMargin: root.textBottom + units.gu(3)
94− }
95− offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress
96− active: root.launcher.dragging
97− shortSwipe: true
98− }
99− ]
46+ label {
47+ text: i18n.tr("Swipe from the left edge to open the launcher")
48+ anchors.left: arrow.right
49+ anchors.leftMargin: units.gu(3)
50+ anchors.right: root.right
51+ anchors.rightMargin: units.gu(4)
52+ anchors.verticalCenter: arrow.verticalCenter
10053 }
10154}
qml/Tutorial/TutorialLeftFinish.qmldeleted−41
@@ -1,41 +0,0 @@
1−/*
2− * Copyright (C) 2014 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 "." as LocalComponents
20−
21−TutorialPage {
22− id: root
23−
24− title: i18n.tr("These are the shortcuts to favorite apps")
25− text: i18n.tr("Tap here to continue.")
26− fullTextWidth: true
27−
28− foreground {
29− children: [
30− LocalComponents.Tick {
31− objectName: "tick"
32− anchors {
33− horizontalCenter: parent.horizontalCenter
34− top: parent.top
35− topMargin: root.textBottom + units.gu(3)
36− }
37− onClicked: root.hide()
38− }
39− ]
40− }
41−}
qml/Tutorial/TutorialLeftLong.qmlnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 2016 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 "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var launcher
25+
26+ opacityOverride: 1 - launcher.dragDistance / launcher.minimizeDistance
27+
28+ mouseArea {
29+ anchors.leftMargin: launcher.dragAreaWidth
30+ }
31+
32+ background {
33+ sourceSize.height: 1916
34+ sourceSize.width: 1080
35+ source: Qt.resolvedUrl("graphics/background1.png")
36+ mirror: true
37+ }
38+
39+ arrow {
40+ anchors.left: root.left
41+ anchors.leftMargin: units.gu(2)
42+ anchors.verticalCenter: root.verticalCenter
43+ rotation: 180
44+ }
45+
46+ label {
47+ text: i18n.tr("Long swipe from the left edge to open the Today scope")
48+ anchors.left: arrow.right
49+ anchors.leftMargin: units.gu(3)
50+ anchors.right: root.right
51+ anchors.rightMargin: units.gu(4)
52+ anchors.verticalCenter: arrow.verticalCenter
53+ }
54+}
qml/Tutorial/TutorialPage.qml+62−186
@@ -21,225 +21,101 @@ import "../Components"
2121Showable {
2222 id: root
2323
24− // This is the header displayed, like "Right edge"
25− property alias title: titleLabel.text
26−
27− // This is the block of text displayed below the header
28− property alias text: textLabel.text
29−
30− // Whether animations are paused
31− property bool paused
32−
33− // Whether to give the text the full width that the title has
34− property bool fullTextWidth
35−
36− // Whether whole page (background + foreground) or just the foreground fades in
37− property bool backgroundFadesIn: false
38−
39− // Whether whole page (background + foreground) or just the foreground fades out
40− property bool backgroundFadesOut: false
41−
42− // The foreground Item, add children to it that you want to fade in
43− property alias foreground: foregroundExtra
44−
45− // The text label bottom, so you can position elements relative to it
46− readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height)
47−
48− // The MouseArea that eats events (so you can adjust size as you will)
24+ property alias arrow: arrow
25+ property alias label: label
26+ property alias background: background
4927 property alias mouseArea: mouseArea
50−
51− // X/Y offsets for text
52− property real textXOffset: 0
53− property real textYOffset: 0
54−
55− // Foreground text opacity
56− property real textOpacity: 1
28+ property real opacityOverride: 1
29+ property bool paused
30+ property bool skipped
31+ property bool isReady
5732
5833 signal finished()
5934
60− function showError() {
61− errorTimer.start();
62− }
63−
6435 ////
6536
37+ QtObject {
38+ id: d
39+ property bool showOnUnpause
40+ }
41+
6642 visible: false
6743 shown: false
6844
69− property real _foregroundHideOpacity
45+ opacity: Math.max(Math.min(_showOpacity, opacityOverride), 0)
46+ onOpacityOverrideChanged: {
47+ if (opacityOverride <= 0) {
48+ d.showOnUnpause = false;
49+ hide();
50+ }
51+ }
52+ property real _showOpacity: 0
53+
54+ onPausedChanged: {
55+ if (paused && shown) {
56+ d.showOnUnpause = true;
57+ hide();
58+ } else if (!paused && d.showOnUnpause) {
59+ if (isReady) {
60+ show();
61+ } else if (hideAnimation.running) {
62+ hideAnimation.stop();
63+ }
64+ d.showOnUnpause = false;
65+ }
66+ }
7067
7168 showAnimation: StandardAnimation {
72− property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity"
69+ property: "_showOpacity"
7370 from: 0
7471 to: 1
75− duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
72+ duration: UbuntuAnimation.SleepyDuration
7673 onStarted: root.visible = true
7774 }
7875
7976 hideAnimation: StandardAnimation {
80− property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
77+ property: "_showOpacity"
8178 to: 0
8279 duration: UbuntuAnimation.BriskDuration
8380 onStopped: {
8481 root.visible = false;
85− root.finished();
86− }
87− }
88−
89− QtObject {
90− id: d
91−
92− readonly property real sideMargin: units.gu(5.5)
93− readonly property real verticalOffset: -units.gu(9)
94− readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2))
95−
96− property real fadeInOffset: {
97− if (showAnimation.running) {
98− var opacity = root[root.showAnimation.property]
99− return (1 - opacity) * units.gu(3);
100− } else {
101− return 0;
82+ if (!d.showOnUnpause) {
83+ root.finished();
10284 }
10385 }
10486 }
10587
106− Timer {
107− id: errorTimer
108− interval: 3500
109− }
110−
11188 MouseArea { // eat any errant presses
11289 id: mouseArea
11390 anchors.fill: parent
11491 }
11592
116− Rectangle {
117− anchors.fill: parent
118− color: "black"
119− opacity: 0.82
93+ Image {
94+ id: background
95+ // Use x/y/height/width instead of anchors so that we don't adjust
96+ // the image if the OSK appears.
97+ x: 0
98+ y: 0
99+ height: root.height
100+ width: root.width
101+ fillMode: Image.PreserveAspectCrop
120102 }
121103
122− Item {
123− id: foreground
124− anchors.fill: parent
125− opacity: root._foregroundHideOpacity
126−
127− Item {
128− anchors.fill: parent
129− opacity: root.textOpacity
130−
131− Label {
132− id: titleLabel
133− anchors {
134− top: parent.verticalCenter
135− topMargin: d.verticalOffset + root.textYOffset
136− left: parent.left
137− leftMargin: d.sideMargin + d.textXOffset
138− }
139− width: parent.width - d.sideMargin * 2
140− horizontalAlignment: Text.AlignLeft
141− wrapMode: Text.Wrap
142− font.weight: Font.Light
143− font.pixelSize: units.gu(3.5)
144− }
145−
146− Label {
147− id: textLabel
148− anchors {
149− top: titleLabel.bottom
150− topMargin: units.gu(2)
151− left: parent.left
152− leftMargin: d.sideMargin + d.textXOffset
153− }
154− width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
155− horizontalAlignment: Text.AlignLeft
156− wrapMode: Text.Wrap
157− font.weight: Font.Light
158− font.pixelSize: units.gu(2.5)
159− }
160−
161− // We use two separate labels like this rather than just changing
162− // the text of the above labels because we want to know where to place
163− // sliders (via root.textBottom) without having that place change
164− // as the text changes length.
165− Label {
166− id: errorTitleLabel
167− objectName: "errorTitleLabel"
168− anchors {
169− top: titleLabel.top
170− left: titleLabel.left
171− }
172− width: titleLabel.width
173− horizontalAlignment: titleLabel.horizontalAlignment
174− wrapMode: titleLabel.wrapMode
175− font.weight: titleLabel.font.weight
176− font.pixelSize: titleLabel.font.pixelSize
177− opacity: 0
178− text: i18n.tr("You almost got it!")
179− }
180−
181− Label {
182− id: errorTextLabel
183− objectName: "errorTextLabel"
184− anchors {
185− top: errorTitleLabel.bottom
186− topMargin: textLabel.anchors.topMargin
187− left: textLabel.left
188− }
189− width: textLabel.width
190− horizontalAlignment: textLabel.horizontalAlignment
191− wrapMode: textLabel.wrapMode
192− font.weight: textLabel.font.weight
193− font.pixelSize: textLabel.font.pixelSize
194− opacity: 0
195− text: i18n.tr("Try again.")
196− }
197− }
198−
199− // A place for subclasses to add extra widgets
200− Item {
201− id: foregroundExtra
202− anchors.fill: parent
203− }
204− }
205−
206− states: State {
207− name: "errorState"
208− when: errorTimer.running
209− PropertyChanges { target: titleLabel; opacity: 0 }
210− PropertyChanges { target: textLabel; opacity: 0 }
211− PropertyChanges { target: errorTitleLabel; opacity: 1 }
212− PropertyChanges { target: errorTextLabel; opacity: 1 }
104+ Image {
105+ id: arrow
106+ width: units.gu(1.5)
107+ source: Qt.resolvedUrl("graphics/arrow.svg")
108+ fillMode: Image.PreserveAspectFit
109+ mipmap: true
213110 }
214111
215− transitions: Transition {
216− to: "errorState"
217− reversible: true
218− SequentialAnimation {
219− ParallelAnimation {
220− StandardAnimation {
221− target: titleLabel
222− property: "opacity"
223− duration: UbuntuAnimation.BriskDuration
224− }
225− StandardAnimation {
226− target: textLabel
227− property: "opacity"
228− duration: UbuntuAnimation.BriskDuration
229− }
230− }
231− ParallelAnimation {
232− StandardAnimation {
233− target: errorTitleLabel
234− property: "opacity"
235− duration: UbuntuAnimation.BriskDuration
236− }
237− StandardAnimation {
238− target: errorTextLabel
239− property: "opacity"
240− duration: UbuntuAnimation.BriskDuration
241− }
242− }
243− }
112+ Label {
113+ id: label
114+ objectName: "tutorialLabel"
115+ fontSize: "large"
116+ font.weight: Font.Light
117+ color: "#333333"
118+ wrapMode: Text.Wrap
119+ lineHeight: 1.2
244120 }
245121}
qml/Tutorial/TutorialRight.qml+30−192
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2015 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
@@ -16,208 +16,46 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import Ubuntu.Gestures 0.1
20−import Unity.Application 0.1
21−import "../Components"
22−import "../Stages"
23−import "." as LocalComponents
2419
2520TutorialPage {
2621 id: root
2722
28− property var panel
29− property alias edgeSize: stage.dragAreaWidth
23+ property var stage
24+ property string usageScenario
3025
31− title: i18n.tr("To view open apps")
32− text: i18n.tr("Long swipe from the right edge.")
26+ // When on phone or tablet, fade out as the drag progresses
27+ opacityOverride: usageScenario === "desktop" ? 1 : 1 - stage.dragProgress * 2
3328
34− textOpacity: 1 - slider.percent
35−
36− SequentialAnimation {
37− id: teaseAnimation
38− paused: running && root.paused
39− running: !stage.dragging && stage.dragProgress === 0
40− loops: Animation.Infinite
41−
42− UbuntuNumberAnimation {
43− target: stage
44− property: "x"
45− to: -units.gu(2)
46− duration: UbuntuAnimation.SleepyDuration
47− }
48− UbuntuNumberAnimation {
49− target: stage
50− property: "x"
51− to: 0
52− duration: UbuntuAnimation.SleepyDuration
53− }
29+ // Else on desktop, fade out when the spread is shown
30+ Connections {
31+ target: usageScenario === "desktop" ? stage : null
32+ ignoreUnknownSignals: true
33+ onSpreadShownChanged: if (stage.spreadShown && root.shown) root.hide()
5434 }
5535
56− foreground {
57− children: [
58− LocalComponents.Slider {
59− id: slider
60− anchors {
61− right: parent.right
62− top: parent.top
63− topMargin: root.textBottom + units.gu(3)
64− }
65− rotation: 180
66− offset: stage.dragProgress - stage.x
67− active: stage.dragging
68− },
69−
70− // Just assume PhoneStage for now. The tablet version of the right-edge
71− // tutorial is still being spec'd by the design team.
72− PhoneStage {
73− id: stage
74− objectName: "stage"
75− anchors.top: parent.top
76− width: parent.width
77− height: parent.height
78− applicationManager: fakeAppManager
79− color: "transparent"
80− interactive: false
81− altTabEnabled: false
82− focusFirstApp: false
83− startScale: 0.8
84− endScale: 0.6
85− dragAreaOverlap: -x
86−
87− onOpened: {
88− overlay.show();
89− root.textOpacity = 0;
90− slider.visible = false;
91− }
92−
93− onDraggingChanged: {
94− if (!dragging) {
95− if (!overlay.shown) {
96− root.showError();
97− }
98− teaseAnimation.complete();
99− }
100− }
101− },
102−
103− Showable {
104− id: overlay
105− objectName: "overlay"
106− anchors.fill: parent
107−
108− opacity: 0
109− shown: false
110− showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 }
111−
112− Label {
113− anchors.top: parent.top
114− anchors.topMargin: root.panel.panelHeight + units.gu(2)
115− anchors.left: parent.left
116− anchors.leftMargin: units.gu(2)
117− anchors.right: parent.right
118− anchors.rightMargin: units.gu(2)
119− wrapMode: Text.Wrap
120− horizontalAlignment: Text.AlignHCenter
121− fontSize: "large"
122− text: i18n.tr("View all your running tasks.")
123− }
124−
125− LocalComponents.Tick {
126− objectName: "tick"
127− anchors.bottom: bottomOverlayText.top
128− anchors.bottomMargin: units.gu(1)
129− anchors.horizontalCenter: bottomOverlayText.horizontalCenter
130− onClicked: root.hide()
131− }
132−
133− Label {
134− id: bottomOverlayText
135− anchors.bottom: parent.bottom
136− anchors.bottomMargin: units.gu(2)
137− anchors.left: parent.left
138− anchors.leftMargin: units.gu(2)
139− anchors.right: parent.right
140− anchors.rightMargin: units.gu(2)
141− wrapMode: Text.Wrap
142− horizontalAlignment: Text.AlignHCenter
143− fontSize: "small"
144− text: i18n.tr("Tap here to continue.")
145− }
146− }
147− ]
36+ mouseArea {
37+ anchors.rightMargin: stage.dragAreaWidth
14838 }
14939
150− ListModel {
151− id: fakeAppManager
152−
153− readonly property string focusedApplicationId: "facebook"
154−
155− function focusApplication(appId) {}
156− function requestFocusApplication(appId) {}
157− function findApplication(appId) {return null;}
158−
159− signal applicationAdded(string appId)
160− signal applicationRemoved(string appId)
161− signal focusRequested(string appId)
162−
163− ListElement {
164− appId: "facebook"
165− fullscreen: false
166− name: ""
167− icon: ""
168− state: ApplicationInfoInterface.Stopped
169− splashTitle: ""
170− splashImage: ""
171− splashShowHeader: false
172− splashColor: "transparent"
173− splashColorHeader: "transparent"
174− splashColorFooter: "transparent"
175− defaultScreenshot: "../Tutorial/graphics/facebook.png"
176− }
177−
178− ListElement {
179− appId: "camera"
180− fullscreen: false
181− name: ""
182− icon: ""
183− state: ApplicationInfoInterface.Stopped
184− splashTitle: ""
185− splashImage: ""
186− splashShowHeader: false
187− splashColor: "transparent"
188− splashColorHeader: "transparent"
189− splashColorFooter: "transparent"
190− defaultScreenshot: "../Tutorial/graphics/camera.png"
191− }
40+ background {
41+ sourceSize.height: 1916
42+ sourceSize.width: 1080
43+ source: Qt.resolvedUrl("graphics/background1.png")
44+ }
19245
193− ListElement {
194− appId: "gallery"
195− fullscreen: false
196− name: ""
197− icon: ""
198− state: ApplicationInfoInterface.Stopped
199− splashTitle: ""
200− splashImage: ""
201− splashShowHeader: false
202− splashColor: "transparent"
203− splashColorHeader: "transparent"
204− splashColorFooter: "transparent"
205− defaultScreenshot: "../Tutorial/graphics/gallery.png"
206− }
46+ arrow {
47+ anchors.right: root.right
48+ anchors.rightMargin: units.gu(2)
49+ anchors.verticalCenter: root.verticalCenter
50+ }
20751
208− ListElement {
209− appId: "dialer"
210− fullscreen: false
211− name: ""
212− icon: ""
213− state: ApplicationInfoInterface.Stopped
214− splashTitle: ""
215− splashImage: ""
216− splashShowHeader: false
217− splashColor: "transparent"
218− splashColorHeader: "transparent"
219− splashColorFooter: "transparent"
220− defaultScreenshot: "../Tutorial/graphics/dialer.png"
221− }
52+ label {
53+ text: root.usageScenario === "desktop" ?
54+ i18n.tr("Hover your mouse on the right edge to view your open apps") :
55+ i18n.tr("Short or long swipe from the right edge to view your open apps")
56+ anchors.right: arrow.left
57+ anchors.rightMargin: units.gu(2) - (label.width - label.contentWidth)
58+ anchors.verticalCenter: arrow.verticalCenter
59+ width: Math.min(units.gu(40), arrow.x - units.gu(4))
22260 }
22361}
qml/Tutorial/TutorialTop.qmlnew file+61
@@ -0,0 +1,61 @@
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 "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var panel
25+
26+ opacityOverride: 1 - panel.indicators.unitProgress
27+
28+ QtObject {
29+ id: d
30+ readonly property bool landscape: root.width > units.gu(50)
31+ readonly property real columnWidth: landscape ? panel.indicators.width : root.width
32+ }
33+
34+ mouseArea {
35+ anchors.topMargin: panel.indicators.minimizedPanelHeight
36+ }
37+
38+ background {
39+ sourceSize.height: 1916
40+ sourceSize.width: 1080
41+ source: Qt.resolvedUrl("graphics/background2.png")
42+ }
43+
44+ arrow {
45+ anchors.top: root.top
46+ anchors.topMargin: units.gu(4)
47+ anchors.horizontalCenter: root.right
48+ anchors.horizontalCenterOffset: - d.columnWidth / 2
49+ rotation: -90
50+ }
51+
52+ 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")
55+ anchors.top: arrow.bottom
56+ anchors.topMargin: units.gu(3)
57+ anchors.horizontalCenter: arrow.horizontalCenter
58+ anchors.horizontalCenterOffset: (label.width - label.contentWidth) / 2
59+ width: d.columnWidth - units.gu(8)
60+ }
61+}
qml/Tutorial/graphics/arrow.svgnew file+19
@@ -0,0 +1,19 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<svg width="35px" height="106px" viewBox="0 0 35 106" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3+ <!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
4+ <title>left swipe arrow</title>
5+ <desc>Created with Sketch.</desc>
6+ <defs></defs>
7+ <g id="Final-design" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
8+ <g id="1.Left-swipe" sketch:type="MSArtboardGroup" transform="translate(-54.000000, -906.000000)">
9+ <g id="left-swipe-arrow" sketch:type="MSLayerGroup" transform="translate(15.000000, 903.000000)">
10+ <g id="layer1" transform="translate(0.857146, 0.494959)" sketch:type="MSShapeGroup">
11+ <g id="g4216" transform="translate(0.952384, 0.831650)">
12+ <path d="M38.0127338,5.94292186 C40.8696258,9.70328846 43.6961579,13.5492607 46.4521869,17.5776877 C50.0756129,22.8738077 53.4767719,28.2917937 56.6611719,33.8315937 C60.6031619,40.9214757 64.3797399,47.5658927 67.7217189,54.6714377 C64.3797399,61.7769828 60.6031619,68.4213998 56.6611719,75.5112818 C53.4767719,81.0510808 50.0756129,86.4690678 46.4521869,91.7651878 C43.6961579,95.7936138 40.8696258,99.6395938 38.0127338,103.399951 L38.0127338,107.669481 C42.0030779,102.508161 45.9748129,97.3405748 49.5537499,92.0679218 C54.5609019,84.6937758 59.0395839,77.5443828 62.9990629,70.6226098 C66.5019159,64.4983168 69.0866949,59.3452977 71.2158589,54.6733907 L69.4678129,54.6713907 L71.2158589,54.6693907 C69.0866949,49.9975777 66.5019159,44.8445587 62.9990629,38.7202657 C59.0395839,31.7984927 54.5609019,24.6490997 49.5537499,17.2749527 C45.9748129,12.0022997 42.0030779,6.83471186 38.0127338,1.67339065 L38.0127338,5.94292186 Z" id="left-swipe-arrow" fill="#6C0D52" transform="translate(54.614296, 54.671436) scale(-1, 1) translate(-54.614296, -54.671436) "></path>
13+ <rect id="rect4782-01" transform="translate(54.190469, 54.673389) rotate(90.000000) translate(-54.190469, -54.673389) " x="0.190470662" y="0.67339065" width="107.999997" height="107.999997"></rect>
14+ </g>
15+ </g>
16+ </g>
17+ </g>
18+ </g>
19+</svg>
qml/Tutorial/graphics/background1.pngnew file

Binary file not shown.

qml/Tutorial/graphics/background2.pngnew file

Binary file not shown.

qml/Tutorial/graphics/camera.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/chevron.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/dialer.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/facebook.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/gallery.pngdeleted

Binary file not shown.

qml/Tutorial/graphics/tick.pngdeleted

Binary file not shown.

tests/autopilot/unity8/shell/emulators.py−2
@@ -36,7 +36,6 @@ __all__ = [
3636 'greeter',
3737 'launcher',
3838 'main_window',
39− 'tutorial',
4039 'UnityEmulatorException',
4140]
4241
@@ -46,7 +45,6 @@ from unity8 import (
4645 dash,
4746 launcher,
4847 shell as main_window,
49− tutorial,
5048 UnityException as UnityEmulatorException
5149)
5250from unity8.shell import create_interactive_notification
tests/autopilot/unity8/shell/tests/test_tutorial.pydeleted−57
@@ -1,57 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014, 2015 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−from autopilot.matchers import Eventually
21−from testtools.matchers import Equals
22−
23−from unity8.shell import (
24− fixture_setup,
25− tests
26−)
27−# unused import to load the tutorial helpers custom proxy objects.
28−from unity8 import tutorial as tutorial_helpers # NOQA
29−
30−
31−class TutorialTestCase(tests.UnityTestCase):
32−
33− def setUp(self):
34− super().setUp()
35− self._qml_mock_enabled = False
36− self._data_dirs_mock_enabled = False
37−
38− self.useFixture(fixture_setup.Tutorial(True))
39− self.unity = self.launch_unity()
40−
41− def test_complete_tutorial(self):
42− greeter = self.main_window.get_greeter()
43− tutorial = self.unity.select_single('Tutorial')
44− self.assertThat(tutorial.running, Eventually(Equals(True)))
45− greeter.swipe()
46− page = self.unity.wait_select_single(objectName='tutorialLeft')
47− page.short_swipe_right()
48− page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
49− page.tap()
50− page = self.unity.wait_select_single(objectName='tutorialRight')
51− page.swipe_left()
52− page.tap()
53− page = self.unity.wait_select_single(objectName='tutorialBottom')
54− page.swipe_up()
55− page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
56− page.tap()
57− self.assertThat(tutorial.running, Eventually(Equals(False)))
tests/autopilot/unity8/tutorial.pydeleted−73
@@ -1,73 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−import logging
21−
22−import ubuntuuitoolkit
23−
24−import autopilot
25−from autopilot import introspection
26−
27−
28−logger = logging.getLogger(__name__)
29−
30−
31−class TutorialPage(
32− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
33−
34− @classmethod
35− def validate_dbus_object(cls, path, state):
36− name = introspection.get_classname_from_path(path)
37− return name in (b'TutorialPage', b'TutorialLeft',
38− b'TutorialLeftFinish', b'TutorialRight',
39− b'TutorialBottom', b'TutorialBottomFinish')
40−
41− @autopilot.logging.log_action(logger.info)
42− def short_swipe_right(self):
43− self.shown.wait_for(True)
44− x, y, width, height = self.globalRect
45− start_x = x
46− stop_x = x + width // 3
47− start_y = stop_y = y + height // 2
48− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
49−
50− @autopilot.logging.log_action(logger.info)
51− def swipe_left(self):
52− self.shown.wait_for(True)
53− x, y, width, height = self.globalRect
54− start_x = width
55− stop_x = x
56− start_y = stop_y = y + height // 2
57− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
58−
59− @autopilot.logging.log_action(logger.info)
60− def swipe_up(self):
61− self.shown.wait_for(True)
62− x, y, width, height = self.globalRect
63− start_y = height
64− stop_y = y
65− start_x = stop_x = x + width // 2
66− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
67−
68− @autopilot.logging.log_action(logger.info)
69− def tap(self):
70− """Tap the tick button to complete this step."""
71− self.shown.wait_for(True)
72− button = self.wait_select_single(objectName="tick")
73− self.pointing_device.click_object(button)
tests/mocks/AccountsService/AccountsService.cpp+20
@@ -26,6 +26,7 @@ AccountsService::AccountsService(QObject* parent)
2626 m_statsWelcomeScreen(true),
2727 m_failedLogins(0),
2828 m_demoEdges(false),
29+ m_demoEdgesCompleted(),
2930 m_hereEnabled(false),
3031 m_hereLicensePath("")
3132{
@@ -54,6 +55,25 @@ void AccountsService::setDemoEdges(bool demoEdges)
5455 Q_EMIT demoEdgesChanged();
5556}
5657
58+QStringList AccountsService::demoEdgesCompleted() const
59+{
60+ return m_demoEdgesCompleted;
61+}
62+
63+void AccountsService::markDemoEdgeCompleted(const QString &edge)
64+{
65+ if (!m_demoEdgesCompleted.contains(edge)) {
66+ m_demoEdgesCompleted << edge;
67+ Q_EMIT demoEdgesCompletedChanged();
68+ }
69+}
70+
71+void AccountsService::setDemoEdgesCompleted(const QStringList &demoEdgesCompleted)
72+{
73+ m_demoEdgesCompleted = demoEdgesCompleted;
74+ Q_EMIT demoEdgesCompletedChanged();
75+}
76+
5777bool AccountsService::enableLauncherWhileLocked() const
5878{
5979 return m_enableLauncherWhileLocked;
tests/mocks/AccountsService/AccountsService.h+10
@@ -19,6 +19,7 @@
1919
2020#include <QObject>
2121#include <QString>
22+#include <QStringList>
2223#include <QVariant>
2324
2425class AccountsService: public QObject
@@ -33,6 +34,10 @@ class AccountsService: public QObject
3334 READ demoEdges
3435 WRITE setDemoEdges
3536 NOTIFY demoEdgesChanged)
37+ Q_PROPERTY (QStringList demoEdgesCompleted
38+ READ demoEdgesCompleted
39+ WRITE setDemoEdgesCompleted // only available in mock
40+ NOTIFY demoEdgesCompletedChanged)
3641 Q_PROPERTY (bool enableLauncherWhileLocked
3742 READ enableLauncherWhileLocked
3843 WRITE setEnableLauncherWhileLocked // only available in mock
@@ -82,6 +87,9 @@ public:
8287 void setUser(const QString &user);
8388 bool demoEdges() const;
8489 void setDemoEdges(bool demoEdges);
90+ QStringList demoEdgesCompleted() const;
91+ void setDemoEdgesCompleted(const QStringList &demoEdges);
92+ Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
8593 bool enableLauncherWhileLocked() const;
8694 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
8795 bool enableIndicatorsWhileLocked() const;
@@ -106,6 +114,7 @@ public:
106114Q_SIGNALS:
107115 void userChanged();
108116 void demoEdgesChanged();
117+ void demoEdgesCompletedChanged();
109118 void enableLauncherWhileLockedChanged();
110119 void enableIndicatorsWhileLockedChanged();
111120 void backgroundFileChanged();
@@ -125,6 +134,7 @@ private:
125134 bool m_statsWelcomeScreen;
126135 uint m_failedLogins;
127136 bool m_demoEdges;
137+ QStringList m_demoEdgesCompleted;
128138 bool m_hereEnabled;
129139 QString m_hereLicensePath;
130140 QString m_realName;
tests/mocks/AccountsService/AccountsService.qmltypes+5
@@ -23,6 +23,7 @@ Module {
2323 }
2424 Property { name: "user"; type: "string" }
2525 Property { name: "demoEdges"; type: "bool" }
26+ Property { name: "demoEdgesCompleted"; type: "QStringList" }
2627 Property { name: "enableLauncherWhileLocked"; type: "bool" }
2728 Property { name: "enableIndicatorsWhileLocked"; type: "bool" }
2829 Property { name: "backgroundFile"; type: "string" }
@@ -32,5 +33,9 @@ Module {
3233 Property { name: "hereEnabled"; type: "bool" }
3334 Property { name: "hereLicensePath"; type: "string" }
3435 Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
36+ Method {
37+ name: "markDemoEdgeCompleted"
38+ Parameter { name: "edge"; type: "string" }
39+ }
3540 }
3641}
tests/mocks/Unity/Application/ApplicationInfo.h+1−1
@@ -60,7 +60,7 @@ public:
6060
6161 QUrl icon() const override { return m_icon; }
6262
63− void setStage(Stage value);
63+ Q_INVOKABLE void setStage(Stage value); // invokable only for mock
6464 Stage stage() const override { return m_stage; }
6565
6666 Q_INVOKABLE void setState(State value);
tests/mocks/Utils/CMakeLists.txt+1−1
@@ -12,7 +12,7 @@ set(QMLPLUGIN_SRC
1212 ${CMAKE_SOURCE_DIR}/plugins/Utils/qlimitproxymodelqml.cpp
1313 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
1414 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
15− ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
15+ ${CMAKE_SOURCE_DIR}/plugins/Utils/windowinputfilter.cpp
1616 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
1717 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
1818 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
tests/mocks/Utils/Utils.qmltypes+2−2
@@ -145,10 +145,10 @@ Module {
145145 }
146146 }
147147 Component {
148− name: "WindowKeysFilter"
148+ name: "WindowInputFilter"
149149 defaultProperty: "data"
150150 prototype: "QQuickItem"
151− exports: ["Utils/WindowKeysFilter 0.1"]
151+ exports: ["Utils/WindowInputFilter 0.1"]
152152 exportMetaObjectRevisions: [0]
153153 }
154154 Component {
tests/mocks/Utils/plugin.cpp+2−2
@@ -32,7 +32,7 @@
3232#include <qlimitproxymodelqml.h>
3333#include <unitysortfilterproxymodelqml.h>
3434#include <unitymenumodelpaths.h>
35−#include <windowkeysfilter.h>
35+#include <windowinputfilter.h>
3636#include <windowscreenshotprovider.h>
3737#include <easingcurve.h>
3838#include <timezoneFormatter.h>
@@ -69,7 +69,7 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
6969 qmlRegisterType<QLimitProxyModelQML>(uri, 0, 1, "LimitProxyModel");
7070 qmlRegisterType<UnitySortFilterProxyModelQML>(uri, 0, 1, "UnitySortFilterProxyModel");
7171 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
72− qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
72+ qmlRegisterType<WindowInputFilter>(uri, 0, 1, "WindowInputFilter");
7373 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
7474 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
7575 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
tests/plugins/AccountsService/PropertiesServer.cpp+1
@@ -89,6 +89,7 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
8989void PropertiesServer::Reset()
9090{
9191 m_properties["com.canonical.unity.AccountsService"]["demo-edges"] = false;
92+ m_properties["com.canonical.unity.AccountsService"]["DemoEdgesCompleted"] = QStringList();
9293 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
9394 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
9495 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
tests/plugins/AccountsService/client.cpp+37
@@ -117,6 +117,43 @@ private Q_SLOTS:
117117 QCOMPARE(session.hereEnabled(), true);
118118 }
119119
120+ void testMarkDemoEdgeCompleted()
121+ {
122+ AccountsService session(this, QTest::currentTestFunction());
123+ QSignalSpy changedSpy(&session, &AccountsService::demoEdgesCompletedChanged);
124+
125+ QCOMPARE(changedSpy.count(), 0);
126+ QCOMPARE(session.demoEdgesCompleted(), QStringList());
127+
128+ session.markDemoEdgeCompleted("testedge");
129+ QCOMPARE(changedSpy.count(), 1);
130+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
131+
132+ session.markDemoEdgeCompleted("testedge");
133+ QCOMPARE(changedSpy.count(), 1);
134+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
135+
136+ session.markDemoEdgeCompleted("testedge2");
137+ QCOMPARE(changedSpy.count(), 2);
138+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge" << "testedge2");
139+ }
140+
141+ void testAsynchronousChangeForDemoEdgesCompleted()
142+ {
143+ AccountsService session(this, QTest::currentTestFunction());
144+ QSignalSpy changedSpy(&session, &AccountsService::demoEdgesCompletedChanged);
145+
146+ QCOMPARE(changedSpy.count(), 0);
147+ QCOMPARE(session.demoEdgesCompleted(), QStringList());
148+
149+ ASSERT_DBUS_CALL(m_userInterface->call("Set",
150+ "com.canonical.unity.AccountsService",
151+ "DemoEdgesCompleted",
152+ dbusVariant(QStringList() << "testedge")));
153+ QTRY_COMPARE(changedSpy.count(), 1);
154+ QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
155+ }
156+
120157 void testAsynchronousChangeForDemoEdges()
121158 {
122159 AccountsService session(this, QTest::currentTestFunction());
tests/qmltests/Components/tst_PhysicalKeysMapper.qml+3−3
@@ -26,9 +26,9 @@ Item {
2626
2727 property var physicalKeysMapper: loader.item
2828
29− WindowKeysFilter {
30− Keys.onPressed: physicalKeysMapper.onKeyPressed(event, currentEventTimestamp);
31− Keys.onReleased: physicalKeysMapper.onKeyReleased(event, currentEventTimestamp);
29+ WindowInputFilter {
30+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event, lastInputTimestamp);
31+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event, lastInputTimestamp);
3232 }
3333
3434 Loader {
tests/qmltests/Greeter/tst_Greeter.qml+7−1
@@ -328,9 +328,15 @@ Item {
328328 LightDM.Greeter.active = true;
329329
330330 greeter.forcedUnlock = true;
331− compare(view.locked, false);
331+ compare(greeter.required, false);
332332 greeter.forcedUnlock = false;
333333
334+ // Now recover from tearing down the view above
335+ LightDM.Greeter.showGreeter();
336+ tryCompare(greeter, "required", true);
337+ tryCompare(greeter, "waiting", false);
338+ view = findChild(greeter, "testView");
339+
334340 selectUser("no-password");
335341 tryCompare(view, "locked", false);
336342 selectUser("has-password");
tests/qmltests/Tutorial/tst_Tutorial.qml+586−193
@@ -19,15 +19,20 @@ import QtTest 1.0
1919import AccountsService 0.1
2020import IntegratedLightDM 0.1 as LightDM
2121import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3
23+import Ubuntu.Telephony 0.1 as Telephony
2224import Unity.Application 0.1
2325import Unity.Test 0.1 as UT
26+import Utils 0.1
2427
2528import "../../../qml"
29+import "../../../qml/Components"
2630
27−Item {
31+Rectangle {
2832 id: root
29− width: shellLoader.width + buttons.width
30− height: shellLoader.height
33+ color: UbuntuColors.lightGrey
34+ width: units.gu(100) + buttons.width
35+ height: units.gu(71)
3136
3237 QtObject {
3338 id: applicationArguments
@@ -45,6 +50,11 @@ Item {
4550 }
4651 }
4752
53+ Telephony.CallEntry {
54+ id: phoneCall
55+ phoneNumber: "+447812221111"
56+ }
57+
4858 Component.onCompleted: {
4959 // must set the mock mode before loading the Shell
5060 LightDM.Greeter.mockMode = "single-pin";
@@ -52,21 +62,63 @@ Item {
5262 shellLoader.active = true;
5363 }
5464
55− Row {
56− spacing: 0
57− anchors.fill: parent
65+ Item {
66+ id: shellContainer
67+ anchors.left: root.left
68+ anchors.right: buttons.left
69+ anchors.top: root.top
70+ anchors.bottom: root.bottom
5871
5972 Loader {
6073 id: shellLoader
6174
6275 active: false
63− width: units.gu(40)
64− height: units.gu(71)
76+ anchors.horizontalCenter: parent.horizontalCenter
77+ anchors.top: parent.top
78+ anchors.bottom: parent.bottom
79+
80+ property int shellOrientation: Qt.PortraitOrientation
81+ property int nativeOrientation: Qt.PortraitOrientation
82+ property int primaryOrientation: Qt.PortraitOrientation
83+
84+ state: modeSelector.model[modeSelector.selectedIndex]
85+ states: [
86+ State {
87+ name: "phone"
88+ PropertyChanges {
89+ target: shellLoader
90+ width: units.gu(40)
91+ }
92+ },
93+ State {
94+ name: "tablet"
95+ PropertyChanges {
96+ target: shellLoader
97+ width: units.gu(100)
98+ shellOrientation: Qt.LandscapeOrientation
99+ nativeOrientation: Qt.LandscapeOrientation
100+ primaryOrientation: Qt.LandscapeOrientation
101+ }
102+ },
103+ State {
104+ name: "desktop"
105+ PropertyChanges {
106+ target: shellLoader
107+ width: units.gu(100)
108+ }
109+ }
110+ ]
65111
66112 property bool itemDestroyed: false
67113 sourceComponent: Component {
68114 Shell {
115+ usageScenario: shellLoader.state
69116 property string indicatorProfile: "phone"
117+ orientation: shellLoader.shellOrientation
118+ orientations: Orientations {
119+ native_: shellLoader.nativeOrientation
120+ primary: shellLoader.primaryOrientation
121+ }
70122
71123 Component.onDestruction: {
72124 shellLoader.itemDestroyed = true;
@@ -74,30 +126,93 @@ Item {
74126 }
75127 }
76128 }
129+ }
77130
78− Rectangle {
79− id: buttons
80− color: "white"
81− width: units.gu(30)
82− height: shellLoader.height
83−
84− Column {
85− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86− spacing: units.gu(1)
87− Row {
88− anchors { left: parent.left; right: parent.right }
89− Button {
90− text: "Restart Tutorial"
91− onClicked: {
92− if (shellLoader.status !== Loader.Ready)
93− return;
94−
95− AccountsService.demoEdges = false;
96− AccountsService.demoEdges = true;
131+ Rectangle {
132+ id: buttons
133+ color: UbuntuColors.darkGrey
134+ width: units.gu(30)
135+ anchors.top: root.top
136+ anchors.bottom: root.bottom
137+ anchors.right: root.right
138+
139+ Column {
140+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
141+ spacing: units.gu(1)
142+ Row {
143+ anchors { left: parent.left; right: parent.right }
144+ Button {
145+ text: "Hide Greeter"
146+ onClicked: {
147+ if (shellLoader.status !== Loader.Ready)
148+ return;
149+
150+ var greeter = testCase.findChild(shellLoader.item, "greeter");
151+ if (greeter.shown) {
152+ greeter.hide();
97153 }
98154 }
99155 }
100156 }
157+
158+ Row {
159+ anchors { left: parent.left; right: parent.right }
160+ Button {
161+ text: "Restart Tutorial"
162+ onClicked: {
163+ if (shellLoader.status !== Loader.Ready)
164+ return;
165+
166+ AccountsService.demoEdges = false;
167+ AccountsService.demoEdgesCompleted = [];
168+ AccountsService.demoEdges = true;
169+ }
170+ }
171+ }
172+
173+ Row {
174+ anchors { left: parent.left; right: parent.right }
175+ CheckBox {
176+ onCheckedChanged: {
177+ if (checked) {
178+ callManager.foregroundCall = phoneCall;
179+ } else {
180+ callManager.foregroundCall = null;
181+ }
182+ }
183+ }
184+ Label {
185+ text: "Active Call"
186+ anchors.verticalCenter: parent.verticalCenter
187+ }
188+ }
189+
190+ Row {
191+ anchors { left: parent.left; right: parent.right }
192+ CheckBox {
193+ activeFocusOnPress: false
194+ onCheckedChanged: {
195+ var surface = SurfaceManager.inputMethodSurface;
196+ if (checked) {
197+ surface.setState(Mir.RestoredState);
198+ } else {
199+ surface.setState(Mir.MinimizedState);
200+ }
201+ }
202+ }
203+ Label {
204+ text: "Input Method"
205+ anchors.verticalCenter: parent.verticalCenter
206+ }
207+ }
208+
209+ ItemSelector {
210+ id: modeSelector
211+ anchors { left: parent.left; right: parent.right }
212+ activeFocusOnPress: false
213+ text: "Mode"
214+ model: ["phone", "tablet", "desktop"]
215+ }
101216 }
102217 }
103218
@@ -111,17 +226,21 @@ Item {
111226 property real halfHeight: shell ? shell.height / 2 : 0
112227
113228 function init() {
114− tryCompare(shell, "enabled", true); // enabled by greeter when ready
229+ prepareShell();
115230
116− AccountsService.demoEdges = false;
117− AccountsService.demoEdges = true;
118− swipeAwayGreeter();
231+ var tutorialLeft = findChild(shell, "tutorialLeft");
232+ tryCompare(tutorialLeft, "opacity", 1);
119233 }
120234
121235 function cleanup() {
236+ resetLoader("phone");
237+ }
238+
239+ function resetLoader(state) {
122240 shellLoader.itemDestroyed = false;
123241
124242 shellLoader.active = false;
243+ shellLoader.state = state;
125244
126245 tryCompare(shellLoader, "status", Loader.Null);
127246 tryCompare(shellLoader, "item", null);
@@ -143,6 +262,24 @@ Item {
143262 removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
144263 }
145264
265+ function prepareShell() {
266+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
267+
268+ WindowStateStorage.clear();
269+ SurfaceManager.inputMethodSurface.setState(Mir.MinimizedState);
270+ callManager.foregroundCall = null;
271+ AccountsService.demoEdges = false;
272+ AccountsService.demoEdgesCompleted = [];
273+ AccountsService.demoEdges = true;
274+
275+ LightDM.Greeter.hideGreeter();
276+ }
277+
278+ function loadShell(state) {
279+ resetLoader(state);
280+ prepareShell();
281+ }
282+
146283 function killApps() {
147284 while (ApplicationManager.count > 1) {
148285 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
@@ -163,234 +300,490 @@ Item {
163300 waitForRendering(greeter);
164301 }
165302
166− function waitForPage(name) {
167− waitForRendering(findChild(shell, name));
168− var page = findChild(shell, name);
169− tryCompare(page, "shown", true);
170− tryCompare(page.showAnimation, "running", false);
171− return page;
303+ function openTutorialTop() {
304+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
305+ var tutorialTop = findChild(shell, "tutorialTop");
306+ var tutorialTopTimer = findInvisibleChild(tutorialTop, "tutorialTopTimer");
307+
308+ tutorialTopTimer.interval = 1;
309+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
310+
311+ tryCompare(tutorialLeftLoader, "active", false);
312+ tryCompare(tutorialTop, "shown", true);
313+ tryCompare(tutorialTop, "opacity", 1);
172314 }
173315
174− function checkTopEdge() {
175− touchFlick(shell, halfWidth, 0, halfWidth, halfHeight);
316+ function openTutorialRight() {
317+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
318+ var tutorialRight = findChild(shell, "tutorialRight");
176319
177− var panel = findChild(shell, "panel");
178− tryCompare(panel.indicators, "fullyClosed", true);
320+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
321+ ApplicationManager.startApplication("gallery-app");
322+ ApplicationManager.startApplication("facebook-webapp");
323+
324+ tryCompare(tutorialLeftLoader, "active", false);
325+ tryCompare(tutorialRight, "shown", true);
326+ tryCompare(tutorialRight, "opacity", 1);
179327 }
180328
181− function checkLeftEdge() {
182− touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
329+ function openTutorialBottom() {
330+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
331+ var tutorialBottom = findChild(shell, "tutorialBottom");
332+
333+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right"];
334+ tryCompare(tutorialLeftLoader, "active", false);
183335
336+ ApplicationManager.startApplication("dialer-app");
337+ ApplicationManager.requestFocusApplication("dialer-app");
338+
339+ tryCompare(tutorialBottom, "shown", true);
340+ tryCompare(tutorialBottom, "opacity", 1);
341+ }
342+
343+ function test_tutorialLeftEdges() {
344+ var tutorial = findChild(shell, "tutorial");
345+ var tutorialLeft = findChild(tutorial, "tutorialLeft");
184346 var launcher = findChild(shell, "launcher");
185− tryCompare(launcher, "state", "");
347+ var stage = findChild(shell, "stage");
348+ var panel = findChild(shell, "panel");
349+
350+ verify(tutorial.running);
351+ verify(tutorial.launcherEnabled);
352+ verify(!tutorial.spreadEnabled);
353+ verify(!tutorial.panelEnabled);
354+ verify(tutorialLeft.shown);
355+ verify(launcher.available);
356+ verify(!stage.spreadEnabled);
357+ verify(!panel.indicators.available);
186358 }
187359
188− function checkRightEdge() {
189− if (shell.usageScenario === "phone") {
190− touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
360+ function test_tutorialLeftFinish() {
361+ var tutorial = findChild(shell, "tutorial");
362+ var tutorialLeft = findChild(tutorial, "tutorialLeft");
363+ var launcher = findChild(shell, "launcher");
191364
192− var stage = findChild(shell, "stage");
193− var spreadView = findChild(stage, "spreadView");
194− tryCompare(spreadView, "phase", 0);
195− }
365+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
366+
367+ tryCompare(tutorialLeft, "shown", false);
368+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
369+ tryCompare(launcher, "state", "visible");
196370 }
197371
198− function checkBottomEdge() {
199− // Can't actually check effect of swipe, since dash isn't really loaded
200− var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
201− tryCompare(applicationsDisplayLoader, "interactive", false);
372+ function test_tutorialLeftShortDrag() {
373+ // Here we want to test just barely pulling the launcher out and
374+ // letting go (i.e. not triggering the "progress" property of
375+ // Launcher).
376+ var tutorialLeft = findChild(shell, "tutorialLeft");
377+ var launcher = findChild(shell, "launcher");
378+
379+ // Confirm fade during drag
380+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, true, false);
381+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
382+ // always match the drag perfectly (takes a moment for drag to kick in)
383+ tryCompareFunction(function() {
384+ return tutorialLeft.opacity >= 0.6 && tutorialLeft.opacity < 0.8;
385+ }, true);
386+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight, false, true);
387+
388+
389+ // Make sure we don't do anything if we don't pull the launcher
390+ // out much.
391+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
392+ tryCompare(launcher, "state", ""); // should remain hidden
393+ tryCompare(tutorialLeft, "shown", true); // and we should still be on left
394+
395+
396+ // Now drag out but not past launcher itself
397+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
398+
399+ tryCompare(tutorialLeft, "shown", false);
400+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
401+ tryCompare(launcher, "state", "visible");
202402 }
203403
204− function checkFinished() {
205− tryCompare(AccountsService, "demoEdges", false);
404+ function test_tutorialLeftLongDrag() {
405+ // Just confirm that a long drag ("dash" drag) doesn't confuse us
206406
207− var tutorial = findChild(shell, "tutorial");
208− tryCompare(tutorial, "running", false);
407+ // So that we actually switch to dash and launcher hides
408+ ApplicationManager.startApplication("gallery-app");
209409
210410 var launcher = findChild(shell, "launcher");
211− tryCompare(launcher, "shown", false);
411+ var tutorialLeft = findChild(shell, "tutorialLeft");
412+ touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
413+
414+ tryCompare(tutorialLeft, "shown", false);
415+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long"]);
212416 }
213417
214− function goToPage(name) {
215− var page = waitForPage("tutorialLeft");
216− checkTopEdge();
217− checkRightEdge();
218− checkBottomEdge();
219− if (name === "tutorialLeft") return page;
418+ function test_tutorialLeftAutoSkipped() {
419+ // Test that we skip the tutorial if user uses left edge themselves
420+
421+ var tutorialLeft = findChild(shell, "tutorialLeft");
422+ LightDM.Greeter.showGreeter();
423+ tryCompare(tutorialLeft, "visible", false);
424+ compare(AccountsService.demoEdgesCompleted, []);
425+
220426 touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
427+ tryCompare(AccountsService, "demoEdgesCompleted", ["left"]);
428+ }
221429
222− page = waitForPage("tutorialLeftFinish");
223− if (name === "tutorialLeftFinish") return page;
224− var tick = findChild(page, "tick");
225− tap(tick);
226−
227− page = waitForPage("tutorialRight");
228− checkTopEdge();
229− checkLeftEdge();
230− checkBottomEdge();
231− if (name === "tutorialRight") return page;
232− touchFlick(shell,
233− shell.width, halfHeight,
234− halfWidth, halfHeight,
235− true /* beginTouch */, true /* endTouch */,
236− 20 /* speed */, 50 /* iterations */);
237− var overlay = findChild(page, "overlay");
238− tryCompare(overlay, "shown", true);
239− var tick = findChild(page, "tick");
240− tap(tick);
241−
242− var page = waitForPage("tutorialBottom");
243− checkTopEdge();
244− checkLeftEdge();
245− checkRightEdge();
246− if (name === "tutorialBottom") return page;
247− touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
430+ function test_tutorialTopEdges() {
431+ var tutorial = findChild(shell, "tutorial");
432+ var tutorialTop = findChild(tutorial, "tutorialTop");
433+ var launcher = findChild(shell, "launcher");
434+ var stage = findChild(shell, "stage");
435+ var panel = findChild(shell, "panel");
248436
249− var page = waitForPage("tutorialBottomFinish");
250− checkTopEdge();
251− checkLeftEdge();
252− checkRightEdge();
253− checkBottomEdge();
254− if (name === "tutorialBottomFinish") return page;
255− var tick = findChild(page, "tick");
256− tap(tick);
437+ openTutorialTop();
257438
258− checkFinished();
259− return null;
439+ tryCompare(tutorial, "running", true);
440+ verify(!tutorial.launcherEnabled);
441+ verify(!tutorial.spreadEnabled);
442+ verify(tutorial.panelEnabled);
443+ verify(tutorialTop.shown);
444+ verify(!launcher.available);
445+ verify(!stage.spreadEnabled);
446+ verify(panel.indicators.available);
260447 }
261448
262− function test_walkthrough() {
263− goToPage(null);
449+ function test_tutorialTopFinish() {
450+ var tutorial = findChild(shell, "tutorial");
451+ var tutorialTop = findChild(tutorial, "tutorialTop");
452+ var panel = findChild(shell, "panel");
453+
454+ openTutorialTop();
455+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
456+
457+ tryCompare(tutorialTop, "shown", false);
458+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
459+ tryCompare(panel.indicators, "fullyOpened", true);
264460 }
265461
266− function test_skipOnDesktop() {
462+ function test_tutorialTopShortDrag() {
267463 var tutorial = findChild(shell, "tutorial");
268− tryCompare(tutorial, "active", true);
269− tryCompare(tutorial, "running", true);
464+ var tutorialTop = findChild(tutorial, "tutorialTop");
465+ var panel = findChild(shell, "panel");
466+
467+ openTutorialTop();
468+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, true, false);
469+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
470+ // always match the drag perfectly (takes a moment for drag to kick in)
471+ tryCompareFunction(function() {
472+ return tutorialTop.opacity >= 0.6 && tutorialTop.opacity < 0.7;
473+ }, true);
474+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height * 0.4, false, true);
270475
271− shell.usageScenario = "desktop";
272− tryCompare(tutorial, "active", false);
273− tryCompare(tutorial, "running", false);
476+ compare(tutorialTop.shown, true);
274477 }
275478
276− function test_launcherShortDrag() {
277− // goToPage does a normal launcher pull. But here we want to test
278− // just barely pulling the launcher out and letting go (i.e. not
279− // triggering the "progress" property of Launcher).
479+ function test_tutorialTopAutoSkipped() {
480+ // Test that we skip the tutorial if user uses top edge themselves
280481
281− var left = goToPage("tutorialLeft");
482+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
483+ var tutorialTop = findChild(shell, "tutorialTop");
484+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
485+ tryCompare(tutorialLeftLoader, "active", false);
486+ verify(!tutorialTop.shown);
282487
283− // Make sure we don't do anything if we don't pull the launcher
284− // out much.
488+ touchFlick(shell, halfWidth, 0, halfWidth, shell.height);
489+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top"]);
490+ }
491+
492+ function test_tutorialRightEdges() {
493+ var tutorial = findChild(shell, "tutorial");
494+ var tutorialRight = findChild(tutorial, "tutorialRight");
285495 var launcher = findChild(shell, "launcher");
286− touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
287− tryCompare(launcher, "state", ""); // should remain hidden
288− tryCompare(left, "shown", true); // and we should still be on left
496+ var stage = findChild(shell, "stage");
497+ var panel = findChild(shell, "panel");
289498
290− // Now drag out but not past launcher itself
291− touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
499+ openTutorialRight();
500+
501+ tryCompare(tutorial, "running", true);
502+ verify(!tutorial.launcherEnabled);
503+ verify(tutorial.spreadEnabled);
504+ verify(!tutorial.panelEnabled);
505+ verify(tutorialRight.shown);
506+ verify(!launcher.available);
507+ verify(stage.spreadEnabled);
508+ verify(!panel.indicators.available);
509+ }
510+
511+ function test_tutorialRightFinish() {
512+ var tutorial = findChild(shell, "tutorial");
513+ var tutorialRight = findChild(tutorial, "tutorialRight");
514+ var stage = findChild(shell, "stage");
515+
516+ openTutorialRight();
517+ touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
518+
519+ tryCompare(tutorialRight, "shown", false);
520+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right"]);
521+ }
522+
523+ function test_tutorialRightShortDrag() {
524+ var tutorial = findChild(shell, "tutorial");
525+ var tutorialRight = findChild(tutorial, "tutorialRight");
526+ var stage = findChild(shell, "stage");
527+
528+ openTutorialRight();
529+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
530+ // compare opacity with a bound rather than hard 0.6 because progress doesn't
531+ // always match the drag perfectly (takes a moment for drag to kick in)
532+ tryCompareFunction(function() {
533+ return tutorialRight.opacity >= 0.6 && tutorialRight.opacity < 0.8;
534+ }, true);
535+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, false, true);
536+
537+ compare(tutorialRight.shown, true);
538+ }
292539
293− waitForPage("tutorialLeftFinish");
540+ function test_tutorialRightDelay() {
541+ // Test that if we exit the top tutorial, we don't immediately
542+ // jump into right tutorial.
543+ var tutorialRight = findChild(shell, "tutorialRight");
544+ var tutorialRightTimer = findInvisibleChild(tutorialRight, "tutorialRightInactivityTimer");
545+
546+ tutorialRightTimer.interval = 1;
547+ openTutorialTop();
548+ ApplicationManager.startApplication("gallery-app");
549+ ApplicationManager.startApplication("facebook-webapp");
550+ tryCompare(ApplicationManager, "count", 3);
551+
552+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
553+ verify(tutorialRightTimer.running, true);
554+ verify(!tutorialRight.shown);
555+ tryCompare(tutorialRight, "shown", true);
294556 }
295557
296− function test_launcherLongDrag() {
297− // goToPage does a normal launcher pull. But here we want to test
298− // a full pull across the page.
558+ function test_tutorialRightAutoSkipped() {
559+ // Test that we skip the tutorial if user uses right edge themselves
560+
561+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
562+ AccountsService.demoEdgesCompleted = ["left", "left-long"];
563+ tryCompare(tutorialLeftLoader, "active", false);
299564
300− var left = goToPage("tutorialLeft");
565+ touchFlick(shell, shell.width, halfHeight, 0, halfHeight);
566+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "right"]);
567+ }
301568
569+ function test_tutorialBottomEdges() {
570+ var tutorial = findChild(shell, "tutorial");
571+ var tutorialBottom = findChild(tutorial, "tutorialBottom");
572+ var tutorialLabel = findChild(tutorialBottom, "tutorialLabel");
302573 var launcher = findChild(shell, "launcher");
303− touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
574+ var stage = findChild(shell, "stage");
575+ var panel = findChild(shell, "panel");
576+
577+ openTutorialBottom();
578+
579+ tryCompare(tutorial, "running", true);
580+ verify(!tutorial.launcherEnabled);
581+ verify(!tutorial.spreadEnabled);
582+ verify(!tutorial.panelEnabled);
583+ verify(tutorialBottom.shown);
584+ verify(!launcher.available);
585+ verify(!stage.spreadEnabled);
586+ verify(!panel.indicators.available);
587+ compare(tutorialLabel.text, "Swipe up for recent calls");
588+ }
304589
305− var errorTextLabel = findChild(left, "errorTextLabel");
306− var errorTitleLabel = findChild(left, "errorTitleLabel");
307− tryCompare(launcher, "state", ""); // launcher goes away
308− tryCompare(left, "shown", true); // still on left page
309− tryCompare(errorTextLabel, "opacity", 1); // show error
310− tryCompare(errorTitleLabel, "opacity", 1); // show error
590+ function test_tutorialBottomFinish() {
591+ var tutorial = findChild(shell, "tutorial");
592+ var tutorialBottom = findChild(tutorial, "tutorialBottom");
593+
594+ openTutorialBottom();
595+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
596+
597+ tryCompare(tutorialBottom, "shown", false);
598+ tryCompare(AccountsService, "demoEdgesCompleted", ["left", "left-long", "top", "right", "bottom-dialer-app"]);
599+
600+ // OK, we did one, just confirm that when all are done, we mark whole tutorial as done.
601+ verify(AccountsService.demoEdges);
602+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top", "right",
603+ "bottom-address-book-app",
604+ "bottom-com.ubuntu.calculator_calculator",
605+ "bottom-dialer-app",
606+ "bottom-messaging-app"];
607+ verify(!AccountsService.demoEdges);
311608 }
312609
313− function test_launcherDragBack() {
314− // goToPage does a full launcher pull. But here we test pulling
315− // all the way out, then dragging back into place.
610+ function test_tutorialBottomAppearsBeforeRight() {
611+ // Confirm that if bottom edge and right edge would appear on the
612+ // the same app open, bottom edge appears first. (this is a lightly
613+ // edited version of openTutorialRight)
614+ var tutorialLeftLoader = findChild(shell, "tutorialLeftLoader");
615+ var tutorialRight = findChild(shell, "tutorialRight");
616+ var tutorialBottom = findChild(shell, "tutorialBottom");
316617
317− var left = goToPage("tutorialLeft");
318− touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false);
319− touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true);
618+ AccountsService.demoEdgesCompleted = ["left", "left-long", "top"];
619+ ApplicationManager.startApplication("gallery-app");
620+ ApplicationManager.startApplication("dialer-app");
320621
321− tryCompare(left, "shown", true); // and we should still be on left
622+ tryCompare(tutorialLeftLoader, "active", false);
623+ tryCompare(tutorialBottom, "shown", true);
624+ tryCompare(tutorialBottom, "opacity", 1);
625+ tryCompare(tutorialRight, "visible", false);
322626 }
323627
324− function test_launcherNoDragGap() {
325− // See bug 1454882, where if you dragged the launcher while it was
326− // visible, you could pull it further than the edge of the screen.
628+ function test_tutorialBottomOnlyCoversSideStageOnTablet() {
629+ loadShell("tablet");
327630
328− var left = goToPage("tutorialLeft");
329− var launcher = findChild(shell, "launcher");
330− var teaseAnimation = findInvisibleChild(left, "teaseAnimation");
631+ var tutorialBottom = findChild(shell, "tutorialBottom");
632+ var targetHeight = shell.height - units.gu(4);
633+ var mainStageX = units.gu(20);
634+ var sideStageX = shell.width - units.gu(20);
635+
636+ WindowStateStorage.saveStage("dialer-app", ApplicationInfoInterface.SideStage);
637+ openTutorialBottom();
638+
639+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
640+ compare(tutorialBottom.opacity, 1);
641+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
642+
643+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
644+ verify(tutorialBottom.opacity < 1);
645+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
646+ }
331647
332− // Wait for launcher to be really out there
333− tryCompareFunction(function() {return launcher.x > teaseAnimation.maxBounce/2}, true);
334− verify(teaseAnimation.running);
648+ function test_tutorialBottomOnlyCoversMainStageOnTablet() {
649+ loadShell("tablet");
335650
336− // Start a drag, make sure animation stops
337− touchFlick(shell, 0, halfHeight, units.gu(4), halfHeight, true, false);
338− verify(!teaseAnimation.running);
339− verify(launcher.visibleWidth > 0);
340− verify(launcher.x > 0);
341− compare(launcher.x, teaseAnimation.bounce);
651+ var tutorialBottom = findChild(shell, "tutorialBottom");
652+ var targetHeight = shell.height - units.gu(4);
653+ var mainStageX = units.gu(20);
654+ var sideStageX = shell.width - units.gu(20);
342655
343− // Continue drag, make sure we don't create a gap on the left hand side
344− touchFlick(shell, units.gu(4), halfHeight, shell.width, halfHeight, false, false);
345− verify(!teaseAnimation.running);
346− compare(launcher.visibleWidth, launcher.panelWidth);
347− compare(launcher.x, 0);
656+ WindowStateStorage.saveStage("notes-app", ApplicationInfoInterface.SideStage);
657+ ApplicationManager.startApplication("notes-app");
348658
349− // Finish and make sure we continue animation
350− touchFlick(shell, shell.width, halfHeight, shell.width, halfHeight, false, true);
351− tryCompare(teaseAnimation, "running", true);
659+ openTutorialBottom();
660+
661+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, true, false);
662+ compare(tutorialBottom.opacity, 1);
663+ touchFlick(shell, sideStageX, shell.height, sideStageX, targetHeight, false, true);
664+
665+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, true, false);
666+ verify(tutorialBottom.opacity < 1);
667+ touchFlick(shell, mainStageX, shell.height, mainStageX, targetHeight, false, true);
352668 }
353669
354− function test_spread() {
355− // Unfortunately, most of what we want to test of the spread is
356− // "did it render correctly?" but that's hard to test. So instead,
357− // just poke and prod it a little bit to see if some of the values
358− // we'd expect to be correct, are so.
670+ function test_activeCallInterruptsTutorial() {
671+ var tutorialLeft = findChild(shell, "tutorialLeft");
672+ verify(tutorialLeft.shown);
673+ verify(!tutorialLeft.paused);
359674
360− var right = goToPage("tutorialRight");
361− var stage = findChild(right, "stage");
362− var delegate0 = findChild(right, "appDelegate0");
675+ callManager.foregroundCall = phoneCall;
676+ verify(!tutorialLeft.shown);
677+ verify(tutorialLeft.paused);
678+ tryCompare(tutorialLeft, "visible", false);
363679
364− tryCompare(stage, "dragProgress", 0);
365− touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
366− verify(stage.dragProgress > 0);
367− compare(stage.dragProgress, -delegate0.xTranslate);
368− touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
369− tryCompare(stage, "dragProgress", 0);
680+ callManager.foregroundCall = null;
681+ tryCompare(tutorialLeft, "shown", true);
682+ verify(!tutorialLeft.paused);
683+ }
684+
685+ function test_greeterInterruptsTutorial() {
686+ var tutorialLeft = findChild(shell, "tutorialLeft");
687+ verify(tutorialLeft.shown);
688+ verify(!tutorialLeft.paused);
370689
371− tryCompare(delegate0, "x", shell.width);
690+ LightDM.Greeter.showGreeter();
691+ verify(!tutorialLeft.shown);
692+ verify(tutorialLeft.paused);
693+ tryCompare(tutorialLeft, "visible", false);
372694
373− var screenshotImage = findChild(right, "screenshotImage");
374− tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
375− tryCompare(screenshotImage, "visible", true);
695+ LightDM.Greeter.hideGreeter();
696+ tryCompare(tutorialLeft, "shown", true);
697+ verify(!tutorialLeft.paused);
376698 }
377699
378− function test_bottomShortDrag() {
379− var bottom = goToPage("tutorialBottom");
700+ function test_interruptionChecksReadyStateWhenDone() {
701+ // If we're done with an interruption (like active call), make sure
702+ // that we don't blindly resume the tutorial -- our trigger
703+ // conditions still need to be met. For example, there need to be
704+ // enough apps open for the right edge tutorial.
380705
381− touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
706+ openTutorialRight();
382707
383− var errorTextLabel = findChild(bottom, "errorTextLabel");
384− var errorTitleLabel = findChild(bottom, "errorTitleLabel");
385− tryCompare(bottom, "shown", true); // still on bottom page
386− tryCompare(errorTextLabel, "opacity", 1); // show error
387− tryCompare(errorTitleLabel, "opacity", 1); // show error
708+ var tutorialRight = findChild(shell, "tutorialRight");
709+ verify(tutorialRight.isReady);
710+ verify(tutorialRight.shown);
711+ verify(!tutorialRight.paused);
712+
713+ callManager.foregroundCall = phoneCall;
714+ killApps();
715+ callManager.foregroundCall = null;
716+
717+ verify(!tutorialRight.isReady);
718+ verify(!tutorialRight.shown);
719+ verify(!tutorialRight.paused);
720+ compare(AccountsService.demoEdgesCompleted, ["left", "left-long", "top"]);
388721 }
389722
390− function test_interrupted() {
391− goToPage("tutorialLeft");
723+ function test_desktopOnlyShowsTutorialRight() {
724+ loadShell("desktop");
725+
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);
734+ compare(AccountsService.demoEdgesCompleted, []);
735+
392736 ApplicationManager.startApplication("dialer-app");
393− checkFinished();
737+ ApplicationManager.startApplication("camera-app");
738+ tryCompare(tutorialRightLoader.item, "isReady", true);
739+ tryCompare(tutorialRightLoader, "shown", true);
740+ }
741+
742+ function test_oskDoesNotHideTutorial() {
743+ var tutorialLeft = findChild(shell, "tutorialLeft");
744+ verify(tutorialLeft.shown);
745+
746+ var surface = SurfaceManager.inputMethodSurface;
747+ surface.setState(Mir.RestoredState);
748+
749+ var inputMethod = findInvisibleChild(shell, "inputMethod");
750+ tryCompare(inputMethod, "state", "shown");
751+
752+ verify(tutorialLeft.shown);
753+ }
754+
755+ function test_oskPreventsTutorial() {
756+ var surface = SurfaceManager.inputMethodSurface;
757+ var inputMethod = findInvisibleChild(shell, "inputMethod");
758+
759+ AccountsService.demoEdges = false;
760+ surface.setState(Mir.RestoredState);
761+ tryCompare(inputMethod, "state", "shown");
762+
763+ var tutorial = findChild(shell, "tutorial");
764+ tryCompare(tutorial, "keyboardVisible", true);
765+
766+ AccountsService.demoEdges = true;
767+ var tutorialLeft = findChild(shell, "tutorialLeft");
768+ verify(!tutorialLeft.shown);
769+
770+ surface.setState(Mir.MinimizedState);
771+ tryCompare(inputMethod, "state", "hidden");
772+ tryCompare(tutorialLeft, "shown", false);
773+ }
774+
775+ function test_accountsServiceSettings() {
776+ var tutorialLeft = findChild(shell, "tutorialLeft");
777+ verify(tutorialLeft != null);
778+ verify(tutorialLeft.shown);
779+
780+ AccountsService.demoEdges = false;
781+ verify(findChild(shell, "tutorialLeft") == null);
782+
783+ AccountsService.demoEdges = true;
784+ tutorialLeft = findChild(shell, "tutorialLeft");
785+ verify(tutorialLeft != null);
786+ tryCompare(tutorialLeft, "shown", true);
394787 }
395788 }
396789}
tests/qmltests/tst_Shell.qml+3−8
@@ -499,6 +499,7 @@ Rectangle {
499499 setLightDMMockMode("single"); // back to the default value
500500
501501 AccountsService.demoEdges = false;
502+ AccountsService.demoEdgesCompleted = [];
502503 Wizard.System.wizardEnabled = false;
503504
504505 // kill all (fake) running apps
@@ -650,9 +651,8 @@ Rectangle {
650651
651652 function test_tabletLeftEdgeDrag_data() {
652653 return [
653− {tag: "without password", user: "no-password", loggedIn: true, demo: false},
654− {tag: "with password", user: "has-password", loggedIn: false, demo: false},
655− {tag: "with demo", user: "has-password", loggedIn: true, demo: true},
654+ {tag: "without password", user: "no-password", loggedIn: true},
655+ {tag: "with password", user: "has-password", loggedIn: false},
656656 ]
657657 }
658658
@@ -662,10 +662,6 @@ Rectangle {
662662
663663 selectUser(data.user)
664664
665− AccountsService.demoEdges = data.demo
666− var tutorial = findChild(shell, "tutorial");
667− tryCompare(tutorial, "running", data.demo);
668−
669665 swipeFromLeftEdge(shell.width * 0.75)
670666 wait(500) // to give time to handle dash() signal from Launcher
671667 confirmLoggedIn(data.loggedIn)
@@ -1262,7 +1258,6 @@ Rectangle {
12621258 var tutorial = findChild(shell, "tutorial");
12631259
12641260 AccountsService.demoEdges = true;
1265− tryCompare(tutorial, "running", true);
12661261 tryCompare(tutorial, "paused", true);
12671262
12681263 swipeAwayGreeter();
tests/qmltests/tst_ShellWithPin.qml−22
@@ -270,28 +270,6 @@ Item {
270270 tryCompare(sessionSpy, "count", 1)
271271 }
272272
273− function test_edgeDemoHidesLockscreen() {
274− LightDM.Greeter.showGreeter()
275− sessionSpy.clear()
276− var lockscreen = findChild(shell, "lockscreen")
277−
278− tryCompare(lockscreen, "shown", true)
279− AccountsService.demoEdges = true
280− tryCompare(lockscreen, "shown", false)
281−
282− var greeter = findChild(shell, "greeter");
283− swipeAwayGreeter(false);
284− tryCompare(greeter, "shown", false);
285− tryCompare(sessionSpy, "count", 1)
286−
287− // Lockscreen is only hidden by the edge demo, so if we turn that
288− // off and show greeter again, lockscreen should appear
289− AccountsService.demoEdges = false
290− LightDM.Greeter.showGreeter()
291− lockscreen = findChild(shell, "lockscreen");
292− tryCompare(lockscreen, "shown", true)
293− }
294−
295273 function test_disabledEdges() {
296274 var launcher = findChild(shell, "launcher")
297275 tryCompare(launcher, "available", true)