Unity 8
TutorialContent.qml
1 /*
2  * Copyright (C) 2013,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 AccountsService 0.1
20 import Unity.Application 0.1
21 
22 Item {
23  id: root
24 
25  property Item launcher
26  property Item panel
27  property Item stage
28  property string usageScenario
29  property bool paused
30  property bool keyboardVisible
31  property var lastInputTimestamp
32 
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
42 
43  signal finished()
44 
45  function finish() {
46  finished();
47  }
48 
49  ////
50 
51  QtObject {
52  id: d
53 
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"
59 
60  property var focusedApp: ApplicationManager.focusedApplicationId
61  ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
62  : null
63 
64  function haveShown(tutorialId) {
65  return AccountsService.demoEdgesCompleted.indexOf(tutorialId) != -1;
66  }
67 
68  property bool endPointsFinished: tutorialRightLoader.skipped &&
69  tutorialBottomLoader.skipped
70  onEndPointsFinishedChanged: if (endPointsFinished) root.finish()
71  }
72 
73  Loader {
74  id: tutorialLeftLoader
75  objectName: "tutorialLeftLoader"
76  anchors.fill: parent
77 
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  !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
93 
94  // Use an idle timer here, because when constructed, all our isReady variables will be false.
95  // Qml needs a moment to copy their values from Tutorial.qml. So we idle it and recheck.
96  Timer {
97  id: tutorialLeftTimer
98  interval: 0
99  onTriggered: if (tutorialLeft.isReady && !tutorialLeft.shown) tutorialLeft.show()
100  }
101 
102  onIsReadyChanged: if (isReady && !shown) tutorialLeftTimer.start()
103  onFinished: AccountsService.markDemoEdgeCompleted("left")
104  }
105  }
106 
107  Loader {
108  id: tutorialLeftLongLoader
109  objectName: "tutorialLeftLongLoader"
110  anchors.fill: parent
111 
112  readonly property bool skipped: !d.mobileScenario || d.haveShown("left-long")
113  readonly property bool shown: item && item.shown
114  active: !skipped || (item && item.visible)
115  onSkippedChanged: if (skipped && shown) item.hide()
116 
117  sourceComponent: TutorialLeftLong {
118  id: tutorialLeftLong
119  objectName: "tutorialLeftLong"
120  anchors.fill: parent
121  launcher: root.launcher
122  hides: [launcher, panel.indicators]
123  paused: root.paused
124 
125  skipped: tutorialLeftLongLoader.skipped
126  isReady: tutorialLeftLoader.skipped && !skipped && !paused && !keyboardVisible &&
127  !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
128 
129  Timer {
130  id: tutorialLeftLongTimer
131  objectName: "tutorialLeftLongTimer"
132  interval: 5000
133  onTriggered: {
134  if (parent.isReady) {
135  if (!parent.shown) {
136  parent.show();
137  }
138  } else if (!parent.skipped) {
139  restart();
140  }
141  }
142  }
143 
144  onIsReadyChanged: if (isReady && !shown) tutorialLeftLongTimer.start()
145  onFinished: AccountsService.markDemoEdgeCompleted("left-long")
146  }
147  }
148 
149  Loader {
150  id: tutorialTopLoader
151  objectName: "tutorialTopLoader"
152  anchors.fill: parent
153 
154  readonly property bool skipped: !d.mobileScenario || d.haveShown("top")
155  readonly property bool shown: item && item.shown
156  active: !skipped || (item && item.visible)
157  onSkippedChanged: if (skipped && shown) item.hide()
158 
159  sourceComponent: TutorialTop {
160  id: tutorialTop
161  objectName: "tutorialTop"
162  anchors.fill: parent
163  panel: root.panel
164  hides: [launcher, panel.indicators]
165  paused: root.paused
166 
167  skipped: tutorialTopLoader.skipped
168  isReady: tutorialLeftLongLoader.skipped && !skipped && !paused && !keyboardVisible &&
169  !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow
170 
171  // We fire 30s after left edge tutorial, with at least 3s of inactivity
172 
173  InactivityTimer {
174  id: tutorialTopInactivityTimer
175  lastInputTimestamp: root.lastInputTimestamp
176  page: parent
177  }
178 
179  Timer {
180  id: tutorialTopTimer
181  objectName: "tutorialTopTimer"
182  interval: 27000
183  onTriggered: tutorialTopInactivityTimer.start()
184  }
185 
186  onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
187  onFinished: AccountsService.markDemoEdgeCompleted("top")
188  }
189  }
190 
191  Loader {
192  id: tutorialRightLoader
193  objectName: "tutorialRightLoader"
194  anchors.fill: parent
195 
196  readonly property bool skipped: d.haveShown("right")
197  readonly property bool shown: item && item.shown
198  active: !skipped || (item && item.visible)
199  onSkippedChanged: if (skipped && shown) item.hide()
200 
201  sourceComponent: TutorialRight {
202  id: tutorialRight
203  objectName: "tutorialRight"
204  anchors.fill: parent
205  stage: root.stage
206  usageScenario: root.usageScenario
207  hides: [launcher, panel.indicators]
208  paused: root.paused
209 
210  skipped: tutorialRightLoader.skipped
211  isReady: tutorialTopLoader.skipped && !skipped && !paused && !keyboardVisible &&
212  !tutorialBottomLoader.shown && !tutorialBottomLoader.mightShow &&
213  ApplicationManager.count >= 3
214 
215  InactivityTimer {
216  id: tutorialRightInactivityTimer
217  objectName: "tutorialRightInactivityTimer"
218  lastInputTimestamp: root.lastInputTimestamp
219  page: parent
220  }
221 
222  Connections {
223  target: d
224  onFocusedAppChanged: {
225  if (tutorialRight.isReady && !tutorialRight.shown && d.focusedApp
226  && d.focusedApp.state === ApplicationInfoInterface.Starting) {
227  tutorialRight.show();
228  }
229  }
230  }
231 
232  onIsReadyChanged: if (isReady && !shown) tutorialRightInactivityTimer.start()
233  onFinished: AccountsService.markDemoEdgeCompleted("right")
234  }
235  }
236 
237  Loader {
238  id: tutorialBottomLoader
239  objectName: "tutorialBottomLoader"
240  anchors.fill: parent
241 
242  // See TutorialBottom.qml for an explanation of why we only support
243  // certain apps.
244  readonly property var supportedApps: ["address-book-app",
245  "com.ubuntu.calculator_calculator",
246  "dialer-app",
247  "messaging-app"]
248  readonly property bool skipped: {
249  if (!d.mobileScenario) {
250  return true;
251  }
252  for (var i = 0; i < supportedApps.length; i++) {
253  if (!d.haveShown("bottom-" + supportedApps[i])) {
254  return false;
255  }
256  }
257  return true;
258  }
259  readonly property bool shown: item && item.shown
260  readonly property bool haveShownFocusedApp: d.focusedApp &&
261  d.haveShown("bottom-" + d.focusedApp.appId)
262  readonly property bool mightShow: !skipped && d.focusedApp &&
263  supportedApps.indexOf(d.focusedApp.appId) !== -1 &&
264  !haveShownFocusedApp
265  active: !skipped || (item && item.visible)
266  onHaveShownFocusedAppChanged: if (haveShownFocusedApp && shown) hide()
267  onSkippedChanged: if (skipped && shown) item.hide()
268 
269  sourceComponent: TutorialBottom {
270  id: tutorialBottom
271  objectName: "tutorialBottom"
272  anchors.fill: parent
273  hides: [launcher, panel.indicators]
274  paused: root.paused
275  usageScenario: root.usageScenario
276  stage: root.stage
277  application: d.focusedApp
278 
279  skipped: tutorialBottomLoader.skipped
280  isReady: !tutorialBottomLoader.skipped && !paused && !keyboardVisible &&
281  !tutorialLeftLoader.shown && !tutorialLeftLongLoader.shown &&
282  !tutorialTopLoader.shown && !tutorialRightLoader.shown &&
283  tutorialBottomLoader.mightShow &&
284  d.focusedApp.state === ApplicationInfoInterface.Running
285 
286  onIsReadyChanged: if (isReady && !shown) show()
287  onFinished: AccountsService.markDemoEdgeCompleted("bottom-" + d.focusedApp.appId)
288  }
289  }
290 }