Merge lp:~tpeeters/ubuntu-ui-toolkit/headlock into lp:ubuntu-ui-toolkit/staging

Proposed by Tim Peeters on 2015-03-16
Status: Superseded
Proposed branch: lp:~tpeeters/ubuntu-ui-toolkit/headlock
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 1986 lines (+1027/-341)
22 files modified
components.api (+16/-1)
modules/Ubuntu/Components/AppHeader.qml (+104/-15)
modules/Ubuntu/Components/MainView.qml (+2/-1)
modules/Ubuntu/Components/MainView12.qml (+18/-8)
modules/Ubuntu/Components/Page10.qml (+3/-31)
modules/Ubuntu/Components/Page11.qml (+6/-3)
modules/Ubuntu/Components/Page13.qml (+85/-0)
modules/Ubuntu/Components/PageHeadConfiguration.qdoc (+202/-0)
modules/Ubuntu/Components/PageHeadConfiguration11.qml (+3/-155)
modules/Ubuntu/Components/PageHeadConfiguration13.qml (+30/-0)
modules/Ubuntu/Components/Tabs.qml (+4/-1)
modules/Ubuntu/Components/pageUtils.js (+47/-0)
modules/Ubuntu/Components/qmldir (+5/-1)
modules/Ubuntu/Test/UbuntuTestCase.qml (+20/-2)
tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py (+4/-1)
tests/resources/header/lockedToolbar.deprecated.qml (+0/-52)
tests/unit_x11/tst_components/tst_header_actions.qml (+11/-19)
tests/unit_x11/tst_components/tst_header_contents_width.qml (+5/-10)
tests/unit_x11/tst_components/tst_header_visible.qml (+288/-0)
tests/unit_x11/tst_components/tst_page11.qml (+1/-1)
tests/unit_x11/tst_components/tst_page13.qml (+141/-0)
tests/unit_x11/tst_components/tst_pagestack.new_header.qml (+32/-40)
To merge this branch: bzr merge lp:~tpeeters/ubuntu-ui-toolkit/headlock
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve on 2015-03-23
Ubuntu SDK team 2015-03-16 Pending
Review via email: mp+253121@code.launchpad.net

Commit Message

Add visible and locked properties to Page.head.

Description of the Change

To post a comment you must log in.
1475. By Tim Peeters on 2015-03-20

update components.api

1476. By Tim Peeters on 2015-03-23

flake8

1477. By Tim Peeters on 2015-03-23

mere wait_for_head_animation branch

1478. By Tim Peeters on 2015-03-23

clean

1479. By Tim Peeters on 2015-03-23

use waitForHeaderAnimation(mainView) in tst_page13.qml

1480. By Tim Peeters on 2015-03-24

merge staging

1481. By Tim Peeters on 2015-03-26

merge staging

1482. By Tim Peeters on 2015-03-27

update components.api

1483. By Tim Peeters on 2015-04-02

merge waitForHeaderAnimation prerequisite branch

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2015-03-10 13:58:52 +0000
3+++ components.api 2015-03-23 15:53:11 +0000
4@@ -246,7 +246,17 @@
5 property Flickable flickable
6 property list<Action> actions
7 Page 1.1
8-Page10
9+Toolkit10.Page
10+ readonly property PageHeadConfiguration head
11+Page 1.3
12+PageTreeNode
13+ property string title
14+ property Flickable flickable
15+ readonly property PageHeadConfiguration head
16+Page 1.3
17+PageTreeNode
18+ property string title
19+ property Flickable flickable
20 readonly property PageHeadConfiguration head
21 PageHeadConfiguration 1.1
22 Object
23@@ -256,6 +266,10 @@
24 property string preset
25 readonly property PageHeadSections sections
26 property color foregroundColor
27+PageHeadConfiguration 1.3
28+Toolkit12.PageHeadConfiguration
29+ property bool locked
30+ property bool visible
31 PageHeadSections 1.1
32 QtObject
33 property bool enabled
34@@ -692,6 +706,7 @@
35 function tryCompareFunction(func, expectedResult, timeout)
36 function typeString(string)
37 function warningFormat(line, column, message)
38+ function waitForHeaderAnimation(mainView)
39 plugins.qmltypes
40 name: "FilterBehavior"
41 prototype: "QObject"
42
43=== modified file 'modules/Ubuntu/Components/AppHeader.qml'
44--- modules/Ubuntu/Components/AppHeader.qml 2015-03-03 13:47:48 +0000
45+++ modules/Ubuntu/Components/AppHeader.qml 2015-03-23 15:53:11 +0000
46@@ -1,5 +1,5 @@
47 /*
48- * Copyright 2013-2014 Canonical Ltd.
49+ * Copyright 2013-2015 Canonical Ltd.
50 *
51 * This program is free software; you can redistribute it and/or modify
52 * it under the terms of the GNU Lesser General Public License as published by
53@@ -60,7 +60,8 @@
54 internal.movementEnded();
55 }
56
57- visible: title || contents || tabsModel
58+ // with PageHeadConfiguration 1.2, always be visible.
59+ visible: title || contents || tabsModel || internal.newConfig
60 onVisibleChanged: {
61 internal.checkFlickableMargins();
62 }
63@@ -69,6 +70,9 @@
64 Show the header
65 */
66 function show() {
67+ if (internal.newConfig) {
68+ header.config.visible = true;
69+ }
70 header.y = 0;
71 }
72
73@@ -76,7 +80,10 @@
74 Hide the header
75 */
76 function hide() {
77- header.y = - header.height;
78+ if (internal.newConfig) {
79+ header.config.visible = false;
80+ }
81+ header.y = -header.height;
82 }
83
84 /*!
85@@ -84,15 +91,22 @@
86 */
87 property string title: ""
88 onTitleChanged: {
89- header.show();
90+ // deprecated for new versions of PageHeadConfiguration
91+ if (!internal.newConfig) {
92+ header.show();
93+ }
94 }
95
96 /*!
97 The contents of the header. If this is set, \l title will be ignored.
98+ DEPRECATED and replaced by Page.head.contents.
99 */
100 property Item contents: null
101 onContentsChanged: {
102- header.show();
103+ // deprecated for new versions of PageHeadConfiguration
104+ if (!internal.newConfig) {
105+ header.show();
106+ }
107 }
108
109 /*!
110@@ -151,9 +165,10 @@
111 */
112 property Flickable flickable: null
113 onFlickableChanged: {
114- internal.checkFlickableMargins();
115 internal.connectFlickable();
116- header.show();
117+ if (!internal.newConfig || !header.config.locked) {
118+ header.show();
119+ }
120 }
121
122 /*!
123@@ -163,12 +178,68 @@
124
125 /*!
126 Configuration of the header.
127+ FIXME: Must be of type PageHeadConfiguration. Setting that as the property type
128+ however will use the latest version (1.3) and a Page that uses an older
129+ version (1.1) will no longer work.
130 */
131- property PageHeadConfiguration config: null
132+ property Object config: null
133+ onConfigChanged: {
134+ // set internal.newConfig because when we rely on the binding,
135+ // the value of newConfig may be updated after executing the code below.
136+ internal.newConfig = config && config.hasOwnProperty("visible") &&
137+ config.hasOwnProperty("locked");
138+ internal.connectFlickable();
139+
140+ if (internal.newConfig && header.config.locked &&!header.config.visible) {
141+ header.hide();
142+ } else {
143+ header.show();
144+ }
145+ }
146+ Connections {
147+ target: header.config
148+ ignoreUnknownSignals: true // PageHeadConfiguration <1.2 lacks the signals below
149+ onVisibleChanged: {
150+ if (header.config.visible) {
151+ header.show();
152+ } else {
153+ header.hide();
154+ }
155+ internal.checkFlickableMargins();
156+ }
157+ onLockedChanged: {
158+ internal.connectFlickable();
159+ if (!header.config.locked) {
160+ internal.movementEnded();
161+ }
162+ }
163+ }
164+
165+ /*!
166+ The header is not fully opened or fully closed.
167+
168+ This property is true if the header is animating towards a fully
169+ opened or fully closed state, or if the header is moving due to user
170+ interaction with the flickable.
171+
172+ The value of moving is always false when using an old version of
173+ PageHeadConfiguration (which does not have the visible property).
174+
175+ Used in tst_header_locked_visible.qml.
176+ */
177+ readonly property bool moving: internal.newConfig &&
178+ ((config.visible && header.y !== 0) ||
179+ (!config.visible && header.y !== -header.height))
180
181 QtObject {
182 id: internal
183
184+ // This property is updated in header.onConfigChanged to ensure it
185+ // is updated before other functions are called in onConfigChanged.
186+ property bool newConfig: header.config &&
187+ header.config.hasOwnProperty("locked") &&
188+ header.config.hasOwnProperty("visible")
189+
190 /*!
191 Track the y-position inside the flickable.
192 */
193@@ -183,20 +254,26 @@
194 Disconnect previous flickable, and connect the new one.
195 */
196 function connectFlickable() {
197+ // Finish the current header movement in case the current
198+ // flickable is disconnected while scrolling:
199+ internal.movementEnded();
200+
201 if (previousFlickable) {
202 previousFlickable.contentYChanged.disconnect(internal.scrollContents);
203 previousFlickable.movementEnded.disconnect(internal.movementEnded);
204 previousFlickable.interactiveChanged.disconnect(internal.interactiveChanged);
205+ previousFlickable = null;
206 }
207- if (flickable) {
208+ if (flickable && !(internal.newConfig && header.config.locked)) {
209 // Connect flicking to movements of the header
210 previousContentY = flickable.contentY;
211 flickable.contentYChanged.connect(internal.scrollContents);
212 flickable.movementEnded.connect(internal.movementEnded);
213 flickable.interactiveChanged.connect(internal.interactiveChanged);
214 flickable.contentHeightChanged.connect(internal.contentHeightChanged);
215+ previousFlickable = flickable;
216 }
217- previousFlickable = flickable;
218+ internal.checkFlickableMargins();
219 }
220
221 /*!
222@@ -216,9 +293,14 @@
223 Fully show or hide the header, depending on its current y.
224 */
225 function movementEnded() {
226- if (flickable && flickable.contentY < 0) header.show();
227- else if (header.y < -header.height/2) header.hide();
228- else header.show();
229+ if (!(internal.newConfig && header.config.locked)) {
230+ if ( (flickable && flickable.contentY < 0) ||
231+ (header.y > -header.height/2)) {
232+ header.show();
233+ } else {
234+ header.hide();
235+ }
236+ }
237 }
238
239 /*
240@@ -241,13 +323,20 @@
241 */
242 function checkFlickableMargins() {
243 if (header.flickable) {
244- var headerHeight = header.visible ? header.height : 0
245+ var headerHeight = 0;
246+ if (header.visible && !(internal.newConfig &&
247+ header.config.locked &&
248+ !header.config.visible)) {
249+ headerHeight = header.height;
250+ }
251+
252 if (flickable.topMargin !== headerHeight) {
253+ var oldContentY = flickable.contentY;
254 var previousHeaderHeight = flickable.topMargin;
255 flickable.topMargin = headerHeight;
256 // push down contents when header grows,
257 // pull up contents when header shrinks.
258- flickable.contentY -= headerHeight - previousHeaderHeight;
259+ flickable.contentY = oldContentY - headerHeight + previousHeaderHeight;
260 }
261 }
262 }
263
264=== modified file 'modules/Ubuntu/Components/MainView.qml'
265--- modules/Ubuntu/Components/MainView.qml 2015-03-03 13:47:48 +0000
266+++ modules/Ubuntu/Components/MainView.qml 2015-03-23 15:53:11 +0000
267@@ -125,6 +125,7 @@
268 AppHeader {
269 // This objectName is used in the MainView autopilot custom proxy object
270 // in order to select the application header.
271+ // Also used in tst_header_locked_visible.qml.
272 objectName: "MainView_Header"
273 id: headerItem
274 property real bottomY: headerItem.y + headerItem.height
275@@ -136,7 +137,7 @@
276 flickable: internal.activePage ? internal.activePage.flickable : null
277 pageStack: internal.activePage ? internal.activePage.pageStack : null
278
279- PageHeadConfiguration {
280+ Toolkit.PageHeadConfiguration {
281 id: headerConfig
282 // for backwards compatibility with deprecated tools property
283 actions: internal.activePage ?
284
285=== modified file 'modules/Ubuntu/Components/MainView12.qml'
286--- modules/Ubuntu/Components/MainView12.qml 2015-03-03 12:53:42 +0000
287+++ modules/Ubuntu/Components/MainView12.qml 2015-03-23 15:53:11 +0000
288@@ -69,6 +69,7 @@
289 AppHeader {
290 // This objectName is used in the MainView autopilot custom proxy object
291 // in order to select the application header.
292+ // Also used in tst_header_locked_visible.qml.
293 objectName: "MainView_Header"
294 id: headerItem
295 property real bottomY: headerItem.y + headerItem.height
296@@ -80,10 +81,11 @@
297 flickable: internal.activePage ? internal.activePage.flickable : null
298 pageStack: internal.activePage ? internal.activePage.pageStack : null
299
300- contents: internal.activePage ?
301+ contents: internal.activePage &&
302+ internal.activePage.hasOwnProperty("__customHeaderContents") ?
303 internal.activePage.__customHeaderContents : null
304
305- PageHeadConfiguration {
306+ Toolkit.PageHeadConfiguration {
307 id: defaultConfig
308 // Used when there is no active Page, or a Page 1.0 is used which
309 // does not have a PageHeadConfiguration.
310@@ -114,9 +116,13 @@
311 target: Qt.application
312 onActiveChanged: {
313 if (Qt.application.active) {
314- headerItem.animate = false;
315- headerItem.show();
316- headerItem.animate = true;
317+ if (!(headerItem.config &&
318+ headerItem.config.hasOwnProperty("locked") &&
319+ headerItem.locked)) {
320+ headerItem.animate = false;
321+ headerItem.show();
322+ headerItem.animate = true;
323+ }
324 }
325 }
326 }
327@@ -127,11 +133,15 @@
328
329 // Even when using MainView 1.1, we still support Page 1.0.
330 // PageBase (=Page 1.0) is the superclass of Page 1.1.
331- property PageBase activePage: isPage(mainView.activeLeafNode) ? mainView.activeLeafNode : null
332+ property Item activePage: isPage(mainView.activeLeafNode) ? mainView.activeLeafNode : null
333
334 function isPage(item) {
335- return item && item.hasOwnProperty("__isPageTreeNode") && item.__isPageTreeNode &&
336- item.hasOwnProperty("title") && item.hasOwnProperty("tools");
337+ return item && item.hasOwnProperty("__isPageTreeNode") &&
338+ item.__isPageTreeNode &&
339+ item.hasOwnProperty("title") &&
340+ item.hasOwnProperty("flickable") &&
341+ item.hasOwnProperty("active") &&
342+ item.hasOwnProperty("pageStack")
343 }
344 }
345
346
347=== modified file 'modules/Ubuntu/Components/Page10.qml'
348--- modules/Ubuntu/Components/Page10.qml 2015-03-03 13:47:48 +0000
349+++ modules/Ubuntu/Components/Page10.qml 2015-03-23 15:53:11 +0000
350@@ -15,7 +15,8 @@
351 */
352
353 import QtQuick 2.4
354-import Ubuntu.Components 1.2 as Toolkit
355+import Ubuntu.Components 1.0 as Toolkit
356+import "pageUtils.js" as Utils
357
358 /*!
359 \internal
360@@ -53,7 +54,7 @@
361 */
362 property Item __customHeaderContents: null
363
364- property Flickable flickable: internal.getFlickableChild(page)
365+ property Flickable flickable: Utils.getFlickableChild(page)
366
367 /*! \internal */
368 onActiveChanged: {
369@@ -95,34 +96,5 @@
370 when: internal.header && !internal.header.useDeprecatedToolbar &&
371 page.tools !== null
372 }
373-
374- function isVerticalFlickable(object) {
375- if (object && object.hasOwnProperty("flickableDirection") && object.hasOwnProperty("contentHeight")) {
376- var direction = object.flickableDirection;
377- if ( ((direction === Flickable.AutoFlickDirection) && (object.contentHeight !== object.height) )
378- || direction === Flickable.VerticalFlick
379- || direction === Flickable.HorizontalAndVerticalFlick) {
380- return true;
381- }
382- }
383- return false;
384- }
385-
386- /*!
387- Return the first flickable child of this page.
388- */
389- function getFlickableChild(item) {
390- if (item && item.hasOwnProperty("children")) {
391- for (var i=0; i < item.children.length; i++) {
392- var child = item.children[i];
393- if (internal.isVerticalFlickable(child)) {
394- if (child.anchors.top === page.top || child.anchors.fill === page) {
395- return item.children[i];
396- }
397- }
398- }
399- }
400- return null;
401- }
402 }
403 }
404
405=== modified file 'modules/Ubuntu/Components/Page11.qml'
406--- modules/Ubuntu/Components/Page11.qml 2015-03-03 12:53:42 +0000
407+++ modules/Ubuntu/Components/Page11.qml 2015-03-23 15:53:11 +0000
408@@ -15,24 +15,27 @@
409 */
410
411 import QtQuick 2.4
412+import Ubuntu.Components 1.0 as Toolkit10
413+import Ubuntu.Components 1.1 as Toolkit11
414
415 /*! \internal */
416 // Documentation in Page.qdoc
417-Page10 {
418+Toolkit10.Page {
419 id: page
420 /*!
421 \qmlproperty PageHeadConfiguration head
422 */
423 readonly property alias head: headerConfig
424- PageHeadConfiguration {
425+ Toolkit11.PageHeadConfiguration {
426 id: headerConfig
427+// visible: false
428 }
429
430 onToolsChanged: {
431 print("Page.tools is a deprecated property. Please use Page.head instead.");
432 }
433
434- Object {
435+ Toolkit11.Object {
436 id: internal
437
438 // Note: The bindings below need to check whether headerConfig.contents
439
440=== added file 'modules/Ubuntu/Components/Page13.qml'
441--- modules/Ubuntu/Components/Page13.qml 1970-01-01 00:00:00 +0000
442+++ modules/Ubuntu/Components/Page13.qml 2015-03-23 15:53:11 +0000
443@@ -0,0 +1,85 @@
444+/*
445+ * Copyright 2015 Canonical Ltd.
446+ *
447+ * This program is free software; you can redistribute it and/or modify
448+ * it under the terms of the GNU Lesser General Public License as published by
449+ * the Free Software Foundation; version 3.
450+ *
451+ * This program is distributed in the hope that it will be useful,
452+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
453+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
454+ * GNU Lesser General Public License for more details.
455+ *
456+ * You should have received a copy of the GNU Lesser General Public License
457+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
458+ */
459+
460+import QtQuick 2.4
461+import Ubuntu.Components 1.3 as Toolkit13
462+import "pageUtils.js" as Utils
463+
464+/*!
465+ \internal
466+ Documentation is in Page.qdoc
467+*/
468+PageTreeNode {
469+ id: page
470+ anchors {
471+ left: parent ? parent.left : undefined
472+ bottom: parent ? parent.bottom : undefined
473+ }
474+ // Set width and height so that a parent Loader can be automatically resized
475+ // to the size of the loaded Page.
476+ width: parentNode ? parentNode.width - page.x : undefined
477+ height: parentNode ? page.flickable ? parentNode.height : parentNode.height - internal.headerHeight : undefined
478+
479+ isLeaf: true
480+ property string title: parentNode && parentNode.hasOwnProperty("title") ? parentNode.title : ""
481+ property Flickable flickable: Utils.getFlickableChild(page)
482+
483+ /*!
484+ \qmlproperty PageHeadConfiguration head
485+ */
486+ readonly property alias head: headerConfig
487+ Toolkit13.PageHeadConfiguration {
488+ id: headerConfig
489+ }
490+
491+ Object {
492+ id: internal
493+
494+ property AppHeader header: page.__propagated && page.__propagated.header ? page.__propagated.header : null
495+ // Used to position the Page when there is no flickable.
496+ // When there is a flickable, the header will automatically position it.
497+ // FIXME TIM: Check this with the new API
498+ property real headerHeight: internal.header && internal.header.visible ? internal.header.height : 0
499+
500+ // Note: The bindings below need to check whether headerConfig.contents
501+ // is valid in the "value", even when that is required in the Binding's "when"
502+ // property, to avoid TypeErrors while/after a page becomes (in)active.
503+ //
504+ // Note 2: contents.parent binding is made by PageHeadStyle.
505+ property bool hasParent: headerConfig.contents &&
506+ headerConfig.contents.parent
507+
508+ Binding {
509+ target: headerConfig.contents
510+ property: "visible"
511+ value: page.active
512+ when: headerConfig.contents
513+ }
514+ Binding {
515+ target: headerConfig.contents
516+ property: "anchors.verticalCenter"
517+ value: internal.hasParent ? headerConfig.contents.parent.verticalCenter :
518+ undefined
519+ when: headerConfig.contents
520+ }
521+ Binding {
522+ target: headerConfig.contents
523+ property: "anchors.left"
524+ value: internal.hasParent ? headerConfig.contents.parent.left : undefined
525+ when: headerConfig.contents
526+ }
527+ }
528+}
529
530=== added file 'modules/Ubuntu/Components/PageHeadConfiguration.qdoc'
531--- modules/Ubuntu/Components/PageHeadConfiguration.qdoc 1970-01-01 00:00:00 +0000
532+++ modules/Ubuntu/Components/PageHeadConfiguration.qdoc 2015-03-23 15:53:11 +0000
533@@ -0,0 +1,202 @@
534+/*
535+ * Copyright 2014-2015 Canonical Ltd.
536+ *
537+ * This program is free software; you can redistribute it and/or modify
538+ * it under the terms of the GNU Lesser General Public License as published by
539+ * the Free Software Foundation; version 3.
540+ *
541+ * This program is distributed in the hope that it will be useful,
542+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
543+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
544+ * GNU Lesser General Public License for more details.
545+ *
546+ * You should have received a copy of the GNU Lesser General Public License
547+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
548+ */
549+
550+/*!
551+ \qmltype PageHeadConfiguration
552+ \inqmlmodule Ubuntu.Components 1.1
553+ \ingroup ubuntu
554+ \since Ubuntu.Components 1.1
555+ \brief Page.head is used to configure the header for a \l Page.
556+
557+ For examples how to use Page.head, see \l Page.
558+ */
559+
560+/*!
561+ List of actions to show in the header.
562+
563+ Example:
564+ \qml
565+ Page {
566+ title: "Custom header actions"
567+ head.actions: [
568+ Action {
569+ iconName: "save"
570+ text: i18n.tr("Save")
571+ },
572+ Action {
573+ iconName: "add"
574+ text: i18n.tr("Add")
575+ }
576+ ]
577+ }
578+ \endqml
579+ \qmlproperty list<Action> PageHeadConfiguration::actions
580+ */
581+
582+/*!
583+ \qmlproperty Action PageHeadConfiguration::backAction
584+ Overrides the default \l PageStack back button and the
585+ \l Tabs drawer button in the header.
586+
587+ Example:
588+ \qml
589+ Page {
590+ title: "Back Action Page"
591+ head.backAction: Action {
592+ iconName: "close"
593+ onTriggered: {
594+ console.log("Run custom back action")
595+ }
596+ }
597+ }
598+ \endqml
599+ */
600+
601+/*!
602+ \qmlproperty Item PageHeadConfiguration::contents
603+ Set this property to show this Item in the header instead of
604+ the title. Use a \l TextField here for implementing search in header.
605+
606+ The parent of this Item will be binded while the \l Page is active.
607+ The header contents will automatically be anchored to the left and
608+ vertically centered inside the header.
609+
610+ Example:
611+ \qml
612+ Page {
613+ title: "Invisible title"
614+ head.contents: Rectangle {
615+ color: UbuntuColors.orange
616+ height: units.gu(5)
617+ width: parent ? parent.width - units.gu(2) : undefined
618+ }
619+ }
620+ \endqml
621+
622+ See \l PageHeadState for an example that shows how search mode can
623+ be implemented.
624+ */
625+
626+// FIXME: The example below can be much simplified using PageHeadState
627+// when bug #1345775 has been fixed.
628+/*!
629+ \qmlproperty string PageHeadConfiguration::preset
630+ Choose a preset for the header visuals and behavior.
631+ The default is an empty string "".
632+ By setting this to "select", title will be hidden and
633+ actions will be represented by icons with a label.
634+
635+ Example:
636+ \qml
637+ import QtQuick 2.4
638+ import Ubuntu.Components 1.2
639+
640+ MainView {
641+ id: mainView
642+ width: units.gu(40)
643+ height: units.gu(50)
644+
645+ Page {
646+ id: page
647+ title: "Demo"
648+
649+ state: "default"
650+ states: [
651+ PageHeadState {
652+ name: "default"
653+ head: page.head
654+ actions: [
655+ Action {
656+ iconName: "contact"
657+ text: "Contact"
658+ }
659+ ]
660+ },
661+ State {
662+ id: selectState
663+ name: "select"
664+
665+ property Action leaveSelect: Action {
666+ iconName: "back"
667+ text: "Back"
668+ onTriggered: page.state = "default"
669+ }
670+ property list<Action> actions: [
671+ Action {
672+ iconName: "select"
673+ text: "Select All"
674+ },
675+ Action {
676+ iconName: "delete"
677+ text: "Delete"
678+ }
679+ ]
680+ PropertyChanges {
681+ target: page.head
682+ backAction: selectState.leaveSelect
683+ actions: selectState.actions
684+ preset: "select"
685+ }
686+ }
687+ ]
688+
689+ Label {
690+ anchors.centerIn: parent
691+ text: "Use back button to leave selection mode."
692+ visible: page.state == "select"
693+ }
694+
695+ Button {
696+ anchors.centerIn: parent
697+ onClicked: page.state = "select"
698+ visible: page.state != "select"
699+ text: "selection mode"
700+ }
701+ }
702+ }
703+ \endqml
704+ */
705+
706+/*!
707+ \qmlproperty PageHeadSections PageHeadConfiguration::sections
708+ Defines the sections in the page header divider.
709+ */
710+
711+/*!
712+ \qmlproperty color PageHeadConfiguration::foregroundColor
713+ The color of the text and icons.
714+ */
715+
716+/*!
717+ \qmlproperty bool PageHeadConfiguration::locked
718+ \since 1.2
719+ When the \l Page is active, the locked property controls the behavior
720+ of the header. A locked header stays visible or invisible, depending
721+ on the value of the \l visible property. An unlocked header automatically
722+ shows and hides if the \l Page has a flickable in which the user
723+ scrolls up or down.
724+ Default value: false
725+ */
726+
727+/*!
728+ \qmlproperty bool PageHeadConfiguration::visible
729+ \since 1.2
730+ Update the value of the visible property to show or hide the header.
731+ This works both when the header is \l locked and unlocked. An unlocked
732+ header can also become visible or hidden when the user scrolls the
733+ active \l Page's flickable. The value of the visible property will be
734+ updated at the end of the showing/hiding animation of the header.
735+ */
736
737=== renamed file 'modules/Ubuntu/Components/PageHeadConfiguration.qml' => 'modules/Ubuntu/Components/PageHeadConfiguration11.qml'
738--- modules/Ubuntu/Components/PageHeadConfiguration.qml 2015-03-03 13:47:48 +0000
739+++ modules/Ubuntu/Components/PageHeadConfiguration11.qml 2015-03-23 15:53:11 +0000
740@@ -15,92 +15,24 @@
741 */
742
743 import QtQuick 2.4
744-import Ubuntu.Components 1.2
745+import Ubuntu.Components 1.1
746
747 /*!
748- \qmltype PageHeadConfiguration
749- \inqmlmodule Ubuntu.Components 1.1
750- \ingroup ubuntu
751- \since Ubuntu.Components 1.1
752- \brief Page.head is used to configure the header for a \l Page.
753-
754- For examples how to use Page.head, see \l Page.
755+ \internal
756+ Documented in PageHeadConfiguration.qdoc
757 */
758 Object {
759 // To be used inside a Page only.
760 id: headerConfig
761
762- /*!
763- List of actions to show in the header.
764-
765- Example:
766- \qml
767- Page {
768- title: "Custom header actions"
769- head.actions: [
770- Action {
771- iconName: "save"
772- text: i18n.tr("Save")
773- },
774- Action {
775- iconName: "add"
776- text: i18n.tr("Add")
777- }
778- ]
779- }
780- \endqml
781- */
782 property list<Action> actions
783-
784- /*!
785- Overrides the default \l PageStack back button and the
786- \l Tabs drawer button in the header.
787-
788- Example:
789- \qml
790- Page {
791- title: "Back Action Page"
792- head.backAction: Action {
793- iconName: "close"
794- onTriggered: {
795- console.log("Run custom back action")
796- }
797- }
798- }
799- \endqml
800- */
801 property Action backAction: null
802-
803- /*!
804- Set this property to show this Item in the header instead of
805- the title. Use a \l TextField here for implementing search in header.
806-
807- The parent of this Item will be binded while the \l Page is active.
808- The header contents will automatically be anchored to the left and
809- vertically centered inside the header.
810-
811- Example:
812- \qml
813- Page {
814- title: "Invisible title"
815- head.contents: Rectangle {
816- color: UbuntuColors.orange
817- height: units.gu(5)
818- width: parent ? parent.width - units.gu(2) : undefined
819- }
820- }
821- \endqml
822-
823- See \l PageHeadState for an example that shows how search mode can
824- be implemented.
825- */
826 property Item contents: null
827
828 QtObject {
829 id: internal
830 property Item oldContents: null
831 }
832-
833 onContentsChanged: {
834 if (internal.oldContents) {
835 // FIX: bug #1341814 and #1400297
836@@ -111,97 +43,13 @@
837 internal.oldContents = contents;
838 }
839
840- // FIXME: The example below can be much simplified using PageHeadState
841- // when bug #1345775 has been fixed.
842- /*!
843- Choose a preset for the header visuals and behavior.
844- The default is an empty string "".
845- By setting this to "select", title will be hidden and
846- actions will be represented by icons with a label.
847-
848- Example:
849- \qml
850- import QtQuick 2.4
851- import Ubuntu.Components 1.2
852-
853- MainView {
854- id: mainView
855- width: units.gu(40)
856- height: units.gu(50)
857-
858- Page {
859- id: page
860- title: "Demo"
861-
862- state: "default"
863- states: [
864- PageHeadState {
865- name: "default"
866- head: page.head
867- actions: [
868- Action {
869- iconName: "contact"
870- text: "Contact"
871- }
872- ]
873- },
874- State {
875- id: selectState
876- name: "select"
877-
878- property Action leaveSelect: Action {
879- iconName: "back"
880- text: "Back"
881- onTriggered: page.state = "default"
882- }
883- property list<Action> actions: [
884- Action {
885- iconName: "select"
886- text: "Select All"
887- },
888- Action {
889- iconName: "delete"
890- text: "Delete"
891- }
892- ]
893- PropertyChanges {
894- target: page.head
895- backAction: selectState.leaveSelect
896- actions: selectState.actions
897- preset: "select"
898- }
899- }
900- ]
901-
902- Label {
903- anchors.centerIn: parent
904- text: "Use back button to leave selection mode."
905- visible: page.state == "select"
906- }
907-
908- Button {
909- anchors.centerIn: parent
910- onClicked: page.state = "select"
911- visible: page.state != "select"
912- text: "selection mode"
913- }
914- }
915- }
916- \endqml
917- */
918 property string preset: ""
919-
920 /*!
921 \qmlproperty PageHeadSections sections
922- Defines the sections in the page header divider.
923 */
924 readonly property alias sections: headSections
925 PageHeadSections {
926 id: headSections
927 }
928-
929- /*!
930- The color of the text and icons.
931- */
932 property color foregroundColor: Theme.palette.selected.backgroundText
933 }
934
935=== added file 'modules/Ubuntu/Components/PageHeadConfiguration13.qml'
936--- modules/Ubuntu/Components/PageHeadConfiguration13.qml 1970-01-01 00:00:00 +0000
937+++ modules/Ubuntu/Components/PageHeadConfiguration13.qml 2015-03-23 15:53:11 +0000
938@@ -0,0 +1,30 @@
939+/*
940+ * Copyright 2015 Canonical Ltd.
941+ *
942+ * This program is free software; you can redistribute it and/or modify
943+ * it under the terms of the GNU Lesser General Public License as published by
944+ * the Free Software Foundation; version 3.
945+ *
946+ * This program is distributed in the hope that it will be useful,
947+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
948+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
949+ * GNU Lesser General Public License for more details.
950+ *
951+ * You should have received a copy of the GNU Lesser General Public License
952+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
953+ */
954+
955+import Ubuntu.Components 1.2 as Toolkit12
956+
957+/*!
958+ \internal
959+ documented in PageHeadConfiguration.qdoc
960+ */
961+Toolkit12.PageHeadConfiguration {
962+ id: headerConfig
963+
964+ property bool locked: false
965+
966+ // auto-updated by AppHeader, but may be set by the developer
967+ property bool visible
968+}
969
970=== modified file 'modules/Ubuntu/Components/Tabs.qml'
971--- modules/Ubuntu/Components/Tabs.qml 2015-03-05 09:35:06 +0000
972+++ modules/Ubuntu/Components/Tabs.qml 2015-03-23 15:53:11 +0000
973@@ -310,7 +310,10 @@
974 if (tabBar && tabBar.__styleInstance && tabBar.__styleInstance.hasOwnProperty("sync")) {
975 tabBar.__styleInstance.sync();
976 }
977- if (tabs.active && internal.header) {
978+ if ((tabs.active && internal.header) &&
979+ !(internal.header.config &&
980+ internal.header.config.hasOwnProperty("locked") &&
981+ internal.header.config.locked)) {
982 internal.header.show();
983 }
984 // deprecated, however use it till we remove it completely
985
986=== added file 'modules/Ubuntu/Components/pageUtils.js'
987--- modules/Ubuntu/Components/pageUtils.js 1970-01-01 00:00:00 +0000
988+++ modules/Ubuntu/Components/pageUtils.js 2015-03-23 15:53:11 +0000
989@@ -0,0 +1,47 @@
990+/*
991+ * Copyright 2015 Canonical Ltd.
992+ *
993+ * This program is free software; you can redistribute it and/or modify
994+ * it under the terms of the GNU Lesser General Public License as published by
995+ * the Free Software Foundation; version 3.
996+ *
997+ * This program is distributed in the hope that it will be useful,
998+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
999+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1000+ * GNU Lesser General Public License for more details.
1001+ *
1002+ * You should have received a copy of the GNU Lesser General Public License
1003+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1004+ */
1005+
1006+/*!
1007+ Return true if the object is a Flickable that can be flicked in vertical direction.
1008+ */
1009+function isVerticalFlickable(object) {
1010+ if (object && object.hasOwnProperty("flickableDirection") && object.hasOwnProperty("contentHeight")) {
1011+ var direction = object.flickableDirection;
1012+ if ( ((direction === Flickable.AutoFlickDirection) && (object.contentHeight !== object.height) )
1013+ || direction === Flickable.VerticalFlick
1014+ || direction === Flickable.HorizontalAndVerticalFlick) {
1015+ return true;
1016+ }
1017+ }
1018+ return false;
1019+}
1020+
1021+/*!
1022+ Return the first child of the item that is flickable in the vertical direction.
1023+ */
1024+function getFlickableChild(item) {
1025+ if (item && item.hasOwnProperty("children")) {
1026+ for (var i=0; i < item.children.length; i++) {
1027+ var child = item.children[i];
1028+ if (isVerticalFlickable(child)) {
1029+ if (child.anchors.top === page.top || child.anchors.fill === page) {
1030+ return item.children[i];
1031+ }
1032+ }
1033+ }
1034+ }
1035+ return null;
1036+}
1037
1038=== modified file 'modules/Ubuntu/Components/qmldir'
1039--- modules/Ubuntu/Components/qmldir 2015-03-02 13:39:03 +0000
1040+++ modules/Ubuntu/Components/qmldir 2015-03-23 15:53:11 +0000
1041@@ -98,7 +98,7 @@
1042 UbuntuListView 1.1 UbuntuListView11.qml
1043 internal PageBase Page10.qml
1044 Page 1.1 Page11.qml
1045-PageHeadConfiguration 1.1 PageHeadConfiguration.qml
1046+PageHeadConfiguration 1.1 PageHeadConfiguration11.qml
1047 PageHeadSections 1.1 PageHeadSections.qml
1048 PageHeadState 1.1 PageHeadState.qml
1049 Icon 1.1 Icon11.qml
1050@@ -111,3 +111,7 @@
1051 #version 1.2
1052 MainView 1.2 MainView12.qml
1053 Captions 1.2 Captions.qml
1054+
1055+#version 1.3
1056+Page 1.3 Page13.qml
1057+PageHeadConfiguration 1.3 PageHeadConfiguration13.qml
1058
1059=== modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml'
1060--- modules/Ubuntu/Test/UbuntuTestCase.qml 2015-03-03 13:47:48 +0000
1061+++ modules/Ubuntu/Test/UbuntuTestCase.qml 2015-03-23 15:53:11 +0000
1062@@ -115,10 +115,10 @@
1063 var iy = 0;
1064
1065 for (var step=0; step < steps; step++) {
1066- if (ix < abs_dx) {
1067+ if (Math.abs(ix) < abs_dx) {
1068 ix += step_dx;
1069 }
1070- if (iy < abs_dy) {
1071+ if (Math.abs(iy) < abs_dy) {
1072 iy += step_dy;
1073 }
1074 mouseMove(item, x + ix, y + iy, stepdelay);
1075@@ -237,4 +237,22 @@
1076 function warningFormat(line, column, message) {
1077 return util.callerFile() + ":" + line + ":" + column + ": " + message;
1078 }
1079+
1080+ /*!
1081+ Wait for animations of the header and the style inside the header to finish.
1082+ The MainView that has the header that may animate must be passed as an argument.
1083+ */
1084+ function waitForHeaderAnimation(mainView) {
1085+ var header = findChild(mainView, "MainView_Header");
1086+ verify(header !== null, "Could not find header.");
1087+ var headerStyle = findChild(header, "PageHeadStyle");
1088+ verify(headerStyle !== null, "Could not find header style.");
1089+
1090+ // Wait for the header to start to move:
1091+ wait(50);
1092+ // Wait for animation of the style inside the header (when pushing/popping):
1093+ tryCompareFunction(function(){ return headerStyle.animating }, false);
1094+ // Wait for the header to finish showing/hiding:
1095+ tryCompareFunction(function(){ return header.moving }, false);
1096+ }
1097 }
1098
1099=== modified file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py'
1100--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 2015-03-04 21:57:25 +0000
1101+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 2015-03-23 15:53:11 +0000
1102@@ -1,6 +1,6 @@
1103 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1104 #
1105-# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
1106+# Copyright (C) 2012-2015 Canonical Ltd.
1107 #
1108 # This program is free software; you can redistribute it and/or modify
1109 # it under the terms of the GNU Lesser General Public License as published by
1110@@ -64,6 +64,9 @@
1111 # so no need to wait.
1112 return
1113
1114+ # Wait showing/hiding animation of the header.
1115+ self.moving.wait_for(False)
1116+
1117 @autopilot_logging.log_action(logger.info)
1118 def switch_to_section_by_index(self, index):
1119 """Select a section in the header divider
1120
1121=== removed file 'tests/resources/header/lockedToolbar.deprecated.qml'
1122--- tests/resources/header/lockedToolbar.deprecated.qml 2015-03-06 12:42:00 +0000
1123+++ tests/resources/header/lockedToolbar.deprecated.qml 1970-01-01 00:00:00 +0000
1124@@ -1,52 +0,0 @@
1125-/*
1126- * Copyright (C) 2014 Canonical Ltd.
1127- *
1128- * This program is free software; you can redistribute it and/or modify
1129- * it under the terms of the GNU Lesser General Public License as published by
1130- * the Free Software Foundation; version 3.
1131- *
1132- * This program is distributed in the hope that it will be useful,
1133- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1134- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1135- * GNU Lesser General Public License for more details.
1136- *
1137- * You should have received a copy of the GNU Lesser General Public License
1138- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1139- */
1140-
1141-import QtQuick 2.0
1142-import Ubuntu.Components 1.1
1143-
1144-MainView {
1145- width: units.gu(50)
1146- height: units.gu(80)
1147- id: mainView
1148-
1149- Page {
1150- id: page
1151- title: "test page"
1152- Label {
1153- anchors.centerIn: parent
1154- text: "testing the toolbar"
1155- }
1156- tools: ToolbarItems {
1157- id: toolbarItems
1158- ToolbarButton {
1159- text: "action1"
1160- }
1161- }
1162- }
1163-
1164- ToolbarItems {
1165- id: lockedTools
1166- ToolbarButton {
1167- text: "locked"
1168- }
1169- locked: true
1170- opened: true
1171- }
1172-
1173- Component.onCompleted: {
1174- page.tools = lockedTools;
1175- }
1176-}
1177
1178=== modified file 'tests/unit_x11/tst_components/tst_header_actions.qml'
1179--- tests/unit_x11/tst_components/tst_header_actions.qml 2015-03-06 12:42:00 +0000
1180+++ tests/unit_x11/tst_components/tst_header_actions.qml 2015-03-23 15:53:11 +0000
1181@@ -1,5 +1,5 @@
1182 /*
1183- * Copyright 2014 Canonical Ltd.
1184+ * Copyright 2014-2015 Canonical Ltd.
1185 *
1186 * This program is free software; you can redistribute it and/or modify
1187 * it under the terms of the GNU Lesser General Public License as published by
1188@@ -14,9 +14,9 @@
1189 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1190 */
1191
1192-import QtQuick 2.2
1193+import QtQuick 2.4
1194 import Ubuntu.Test 1.0
1195-import Ubuntu.Components 1.1
1196+import Ubuntu.Components 1.2
1197
1198 // NOTE: Other parts of the page head API are tested with autopilot in
1199 // ubuntuuitoolkit.tests.components.test_header
1200@@ -28,7 +28,6 @@
1201 MainView {
1202 id: mainView
1203 anchors.fill: parent
1204- useDeprecatedToolbar: false
1205
1206 PageStack {
1207 id: pageStack
1208@@ -61,55 +60,48 @@
1209 when: windowShown
1210 id: testCase
1211
1212- property var app_header
1213 property var back_button
1214 property var custom_back_button
1215- property var head_style
1216
1217 function initTestCase() {
1218- testCase.app_header = findChild(mainView, "MainView_Header");
1219+ var app_header = findChild(mainView, "MainView_Header");
1220 testCase.back_button = findChild(app_header, "backButton");
1221 testCase.custom_back_button = findChild(app_header, "customBackButton");
1222- testCase.head_style = findChild(app_header, "PageHeadStyle");
1223
1224- wait_for_animation();
1225+ waitForHeaderAnimation(mainView);
1226 compare(page2.head.backAction, null, "Back action set by default.");
1227 compare(back_button.visible, false, "Back button visible with only 1 page on the stack.");
1228 compare(custom_back_button.visible, false, "Custom back button visible without custom back action.")
1229 }
1230
1231- function wait_for_animation() {
1232- tryCompareFunction(function(){return testCase.head_style.animating}, false);
1233- }
1234-
1235 function test_default_back_button() {
1236 pageStack.push(page2);
1237- wait_for_animation();
1238+ waitForHeaderAnimation(mainView);
1239 compare(back_button.visible, true, "Back button not visible with 2 pages on stack.");
1240 compare(custom_back_button.visible, false, "Showing custom back button without custom back action.");
1241 pageStack.pop();
1242- wait_for_animation();
1243+ waitForHeaderAnimation(mainView);
1244 }
1245
1246 function test_custom_back_button() {
1247 page2.head.backAction = customBackAction;
1248 pageStack.push(page2);
1249- wait_for_animation();
1250+ waitForHeaderAnimation(mainView);
1251 compare(back_button.visible, false, "Default back button visible with custom back action.");
1252 compare(custom_back_button.visible, true, "Custom back button invisible with back action.");
1253 pageStack.pop();
1254- wait_for_animation();
1255+ waitForHeaderAnimation(mainView);
1256 page2.head.backAction = null;
1257 }
1258
1259 function test_no_back_button() {
1260 page2.head.backAction = invisibleAction;
1261 pageStack.push(page2);
1262- wait_for_animation();
1263+ waitForHeaderAnimation(mainView);
1264 compare(back_button.visible, false, "Default back button visible with invisible custom back action.");
1265 compare(custom_back_button.visible, false, "Custom back button visible with invisible custom back action.");
1266 pageStack.pop();
1267- wait_for_animation();
1268+ waitForHeaderAnimation(mainView);
1269 page2.head.backAction = null;
1270 }
1271 }
1272
1273=== modified file 'tests/unit_x11/tst_components/tst_header_contents_width.qml'
1274--- tests/unit_x11/tst_components/tst_header_contents_width.qml 2015-03-03 13:20:06 +0000
1275+++ tests/unit_x11/tst_components/tst_header_contents_width.qml 2015-03-23 15:53:11 +0000
1276@@ -14,9 +14,9 @@
1277 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1278 */
1279
1280-import QtQuick 2.2
1281+import QtQuick 2.4
1282 import Ubuntu.Test 1.0
1283-import Ubuntu.Components 1.1
1284+import Ubuntu.Components 1.2
1285
1286 Item {
1287 width: units.gu(50)
1288@@ -25,7 +25,6 @@
1289 MainView {
1290 id: mainView
1291 anchors.fill: parent
1292- useDeprecatedToolbar: false
1293
1294 Tabs {
1295 id: tabs
1296@@ -69,12 +68,8 @@
1297
1298 property var head_style
1299
1300- function wait_for_animation() {
1301- tryCompareFunction(function(){return testCase.head_style.animating}, false);
1302- }
1303 function initTestCase() {
1304- testCase.head_style = findChild(mainView, "PageHeadStyle");
1305- testCase.wait_for_animation();
1306+ waitForHeaderAnimation(mainView);
1307 }
1308
1309 function test_header_contents_width_bug1408481() {
1310@@ -82,14 +77,14 @@
1311 testCase.verify(initialWidth > 0, "Initial width has a positive value.");
1312 // Select the tab that has more actions.
1313 tabs.selectedTabIndex = 1;
1314- testCase.wait_for_animation();
1315+ waitForHeaderAnimation(mainView);
1316 // Now less space is available for the header contents, so that the action buttons fit.
1317 testCase.verify(searchField.width < initialWidth, "Contents width is reduced.");
1318 // Without this wait(), the test does not reproduce bug 1408481.
1319 wait(100);
1320 // Select the first tab again:
1321 tabs.selectedTabIndex = 0;
1322- testCase.wait_for_animation();
1323+ waitForHeaderAnimation(mainView);
1324 // Without actions in tab one, the full width is available again for contents
1325 testCase.verify(searchField.width === initialWidth, "Contents width is reset.");
1326 }
1327
1328=== added file 'tests/unit_x11/tst_components/tst_header_visible.qml'
1329--- tests/unit_x11/tst_components/tst_header_visible.qml 1970-01-01 00:00:00 +0000
1330+++ tests/unit_x11/tst_components/tst_header_visible.qml 2015-03-23 15:53:11 +0000
1331@@ -0,0 +1,288 @@
1332+/*
1333+ * Copyright (C) 2015 Canonical Ltd.
1334+ *
1335+ * This program is free software; you can redistribute it and/or modify
1336+ * it under the terms of the GNU Lesser General Public License as published by
1337+ * the Free Software Foundation; version 3.
1338+ *
1339+ * This program is distributed in the hope that it will be useful,
1340+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1341+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1342+ * GNU Lesser General Public License for more details.
1343+ *
1344+ * You should have received a copy of the GNU Lesser General Public License
1345+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1346+ */
1347+
1348+import QtQuick 2.4
1349+import Ubuntu.Components 1.3
1350+import Ubuntu.Test 1.0
1351+
1352+Item {
1353+ width: units.gu(50)
1354+ height: units.gu(70)
1355+
1356+ MainView {
1357+ id: mainView
1358+ width: units.gu(50)
1359+ height: units.gu(70)
1360+
1361+ PageStack {
1362+ id: stack
1363+ Component.onCompleted: stack.push(page)
1364+
1365+ Page {
1366+ id: page
1367+ title: "Auto-hide"
1368+ head {
1369+ locked: lockedSwitch.checked
1370+ onVisibleChanged: {
1371+ visibleSwitch.checked = page.head.visible
1372+ }
1373+ }
1374+ Flickable {
1375+ id: flickable
1376+ anchors.fill: parent
1377+ contentHeight: units.gu(200)
1378+ Rectangle {
1379+ color: "green"
1380+ opacity: 0.3
1381+ anchors {
1382+ left: parent.left
1383+ right: parent.right
1384+ top: parent.top
1385+ }
1386+ height: 1
1387+ }
1388+
1389+ Grid {
1390+ id: switchGrid
1391+ columns: 2
1392+ spacing: units.gu(1)
1393+ anchors {
1394+ top: parent.top
1395+ left: parent.left
1396+ leftMargin: units.gu(5)
1397+ topMargin: units.gu(15)
1398+ }
1399+ Switch {
1400+ id: lockedSwitch
1401+ checked: false
1402+ }
1403+ Label {
1404+ text: "header locked"
1405+ }
1406+ Switch {
1407+ id: visibleSwitch
1408+ checked: page.head.visible
1409+ onClicked: page.head.visible = checked
1410+ }
1411+ Label {
1412+ text: "header visible"
1413+ }
1414+ }
1415+ Label {
1416+ id: label
1417+ anchors {
1418+ horizontalCenter: parent.horizontalCenter
1419+ top: switchGrid.bottom
1420+ topMargin: units.gu(15)
1421+ }
1422+ text: "Flick me"
1423+ }
1424+ Button {
1425+ anchors {
1426+ horizontalCenter: parent.horizontalCenter
1427+ top: label.bottom
1428+ topMargin: units.gu(5)
1429+ }
1430+ text: "Click me"
1431+ onTriggered: stack.push(otherPage)
1432+ }
1433+ }
1434+ }
1435+ Page {
1436+ id: otherPage
1437+ title: "On stack"
1438+ visible: false
1439+
1440+ Label {
1441+ anchors.centerIn: parent
1442+ text: "Stacked"
1443+ }
1444+ }
1445+ Page {
1446+ id: titleLessPage
1447+ visible: false
1448+ }
1449+ }
1450+ }
1451+
1452+ UbuntuTestCase {
1453+ name: "HeaderLockedVisible"
1454+ when: windowShown
1455+ id: testCase
1456+
1457+ property var header
1458+ function initTestCase() {
1459+ testCase.header = findChild(mainView, "MainView_Header");
1460+ }
1461+
1462+ function init() {
1463+ page.head.visible = true;
1464+ page.head.locked = false;
1465+ otherPage.head.visible = true;
1466+ otherPage.head.locked = false;
1467+ wait_for_visible(true, "Header is not visible initially.");
1468+ compare(stack.currentPage, page, "Wrong Page on PageStack initially.");
1469+ compare(page.head.locked, false, "Header is not locked initially.");
1470+ }
1471+
1472+ function scroll(dy) {
1473+ var p = centerOf(mainView);
1474+ // Use mouseWheel to scroll because mouseDrag is very unreliable
1475+ // and does not properly handle negative values for dy.
1476+ mouseWheel(mainView, p.x, p.y, 0, 2*dy);
1477+ }
1478+
1479+ function scroll_down() {
1480+ scroll(-header.height);
1481+ }
1482+
1483+ function scroll_up() {
1484+ scroll(header.height);
1485+ }
1486+
1487+ function wait_for_visible(visible, errorMessage) {
1488+ waitForHeaderAnimation(mainView);
1489+ compare(stack.currentPage.head.visible, visible, errorMessage);
1490+ var mismatchMessage = " Page.head.visible does not match header visibility.";
1491+ if (visible) {
1492+ compare(header.y, 0, errorMessage + mismatchMessage);
1493+ } else {
1494+ compare(header.y, -header.height, errorMessage + mismatchMessage);
1495+ }
1496+ }
1497+
1498+ function test_set_visible_to_hide_and_show() {
1499+ page.head.visible = false;
1500+ wait_for_visible(false, "Cannot hide unlocked header by setting visible to false.");
1501+ page.head.visible = true;
1502+ wait_for_visible(true, "Cannot show unlocked header by setting visible to true.");
1503+
1504+ page.head.locked = true;
1505+ page.head.visible = false;
1506+ wait_for_visible(false, "Cannot hide locked header by setting visible to false.");
1507+ page.head.visible = true;
1508+ wait_for_visible(true, "Cannot show locked header by setting visible to true.");
1509+ }
1510+
1511+ function test_scroll_when_unlocked_updates_visible() {
1512+ scroll_down();
1513+ wait_for_visible(false, "Scrolling down does not hide header.");
1514+ scroll_up();
1515+ wait_for_visible(true, "Scrolling up does not show header.");
1516+ }
1517+
1518+ function test_scroll_when_locked_does_not_update_visible() {
1519+ // Note that with a locked header, scrolling up and down does not
1520+ // cause the header to move, so the wait_for_visible() calls below
1521+ // will return almost instantly.
1522+ page.head.locked = true;
1523+ scroll_down();
1524+ wait_for_visible(true, "Scrolling down hides locked header.");
1525+ scroll_up();
1526+ wait_for_visible(true, "Scrolling up hides locked header.");
1527+
1528+ page.head.visible = false;
1529+ waitForHeaderAnimation(mainView);
1530+ scroll_down();
1531+ wait_for_visible(false, "Scrolling down shows locked header.");
1532+ scroll_up();
1533+ wait_for_visible(false, "Scrolling up shows locked header.");
1534+ }
1535+
1536+ function test_locking_updates_visible() {
1537+ // locked = false, visible = true.
1538+ page.head.locked = true;
1539+ wait_for_visible(true, "Locking hides header.");
1540+ page.head.locked = false;
1541+ wait_for_visible(true, "Unlocking hides header.");
1542+
1543+ page.head.locked = true;
1544+ page.head.visible = false;
1545+ waitForHeaderAnimation(mainView);
1546+ // When the flickable is scrolled to the top, unlocking the header must show
1547+ // the header because you cannot scroll more up to reveal it:
1548+ page.head.locked = false;
1549+ wait_for_visible(true, "Unlocking header when flickable is at Y beginning "+
1550+ "does not show header.");
1551+
1552+ scroll_down();
1553+ wait_for_visible(false, "Scrolling down does not hide header.");
1554+ page.head.locked = true;
1555+ wait_for_visible(false, "Locking shows header.");
1556+ // When flickable is scrolled down, unlocking header does not show header
1557+ // because the user can scroll up to reveal it:
1558+ page.head.locked = false;
1559+ wait_for_visible(false, "Unlocking shows header when flickable is not at " +
1560+ "Y beginning.");
1561+ }
1562+
1563+ function test_activate_page_shows_header() {
1564+ page.head.visible = false;
1565+ waitForHeaderAnimation(mainView);
1566+
1567+ // Header becomes visible when new Page becomes active:
1568+ stack.push(otherPage);
1569+ wait_for_visible(true, "Pushing page on stack does not show header.");
1570+
1571+ // Header becomes visible when Page with previously hidden header
1572+ // becomes active:
1573+ stack.pop();
1574+ wait_for_visible(true, "Activating unlocked Page does not make header visible.");
1575+ }
1576+
1577+ function test_activate_hides_locked_hidden_header() {
1578+ otherPage.head.locked = true;
1579+ otherPage.head.visible = false;
1580+
1581+ stack.push(otherPage);
1582+ wait_for_visible(false, "Pushing Page with locked hidden header shows header.");
1583+ compare(otherPage.head.locked, true, "Pushing Page unlocks header.");
1584+ compare(page.head.locked, false, "Pushing Page locks previous header.");
1585+
1586+ stack.pop();
1587+ wait_for_visible(true, "Popping to a Page with unlocked header does not show header.");
1588+ compare(otherPage.head.locked, true, "Popping Page unlocks previous header.");
1589+ compare(page.head.locked, false, "Popping Page locks header.");
1590+ }
1591+
1592+ function test_hidden_locked_header_stays_hidden() {
1593+ page.head.locked = true;
1594+ page.head.visible = false;
1595+ waitForHeaderAnimation(mainView);
1596+ stack.push(otherPage);
1597+ waitForHeaderAnimation(mainView);
1598+ stack.pop();
1599+ wait_for_visible(false, "Popping to a Page with locked hidden header shows header.");
1600+ }
1601+
1602+ function test_page_with_no_title_on_pagestack_has_back_button_bug1402054() {
1603+ page.head.visible = false;
1604+ waitForHeaderAnimation(mainView);
1605+ stack.push(titleLessPage);
1606+ wait_for_visible(true, "Page with no title hides the header.");
1607+
1608+ var backButton = findChild(testCase.header, "backButton");
1609+ verify(null !== backButton, "Header has no back button.");
1610+ compare(backButton.visible, true, "Page with no title hides the back button.");
1611+
1612+ var center = centerOf(backButton);
1613+ mouseClick(backButton, center.x, center.y);
1614+ waitForHeaderAnimation(mainView);
1615+ compare(stack.depth, 1, "Clicking back button of page with no title does not "+
1616+ "pop the page from the PageStack.");
1617+ }
1618+ }
1619+}
1620
1621=== renamed file 'tests/unit_x11/tst_components/tst_page.qml' => 'tests/unit_x11/tst_components/tst_page11.qml'
1622--- tests/unit_x11/tst_components/tst_page.qml 2015-03-03 13:20:06 +0000
1623+++ tests/unit_x11/tst_components/tst_page11.qml 2015-03-23 15:53:11 +0000
1624@@ -54,7 +54,7 @@
1625 }
1626
1627 TestCase {
1628- name: "PageAPI"
1629+ name: "Page11API"
1630 when: windowShown
1631
1632 function initTestCase() {
1633
1634=== added file 'tests/unit_x11/tst_components/tst_page13.qml'
1635--- tests/unit_x11/tst_components/tst_page13.qml 1970-01-01 00:00:00 +0000
1636+++ tests/unit_x11/tst_components/tst_page13.qml 2015-03-23 15:53:11 +0000
1637@@ -0,0 +1,141 @@
1638+/*
1639+ * Copyright 2015 Canonical Ltd.
1640+ *
1641+ * This program is free software; you can redistribute it and/or modify
1642+ * it under the terms of the GNU Lesser General Public License as published by
1643+ * the Free Software Foundation; version 3.
1644+ *
1645+ * This program is distributed in the hope that it will be useful,
1646+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1647+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1648+ * GNU Lesser General Public License for more details.
1649+ *
1650+ * You should have received a copy of the GNU Lesser General Public License
1651+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1652+ */
1653+
1654+import QtQuick 2.4
1655+import Ubuntu.Test 1.0
1656+import Ubuntu.Components 1.3
1657+
1658+Item {
1659+ width: 400
1660+ height: 600
1661+
1662+ Flickable {
1663+ id: testFlickable
1664+ }
1665+
1666+ MainView {
1667+ anchors.fill: parent
1668+ id: mainView
1669+ Page {
1670+ id: page
1671+ Flickable {
1672+ id: pageFlickable
1673+ anchors.fill: parent
1674+ contentHeight: column.height
1675+
1676+ Column {
1677+ id: column
1678+ Repeater {
1679+ model: 100
1680+ Label {
1681+ text: "line "+index
1682+ }
1683+ }
1684+ }
1685+ }
1686+ }
1687+ }
1688+
1689+ UbuntuTestCase {
1690+ id: testCase
1691+ name: "Page13API"
1692+ when: windowShown
1693+
1694+ property var header
1695+ property var headerStyle
1696+ function initTestCase() {
1697+ testCase.header = findChild(mainView, "MainView_Header");
1698+ testCase.headerStyle = findChild(mainView, "PageHeadStyle");
1699+ }
1700+
1701+ function init() {
1702+ compare(page.title, "", "Page title is set by default.");
1703+ compare(page.active, true, "Page is inactive by default.");
1704+ compare(page.pageStack, null, "Page has a PageStack by default.");
1705+ }
1706+
1707+ function test_title() {
1708+ var newTitle = "Hello World!";
1709+ page.title = newTitle;
1710+ compare(page.title, newTitle, "Could not set page title.");
1711+ page.title = "";
1712+ compare(page.title, "", "Could not unset page title.");
1713+ }
1714+
1715+ function test_header_title() {
1716+ var newTitle = "Hello header!";
1717+ page.title = newTitle;
1718+ var header = mainView.__propagated.header;
1719+ compare(header.title, newTitle, "Header title does not match active page title.");
1720+ page.title = "";
1721+ }
1722+
1723+ function test_flickable_bug1200642_bug1192591() {
1724+ var header = page.__propagated.header;
1725+ compare(page.flickable, pageFlickable,
1726+ "Flickable is not correctly detected.");
1727+ compare(header.flickable, pageFlickable,
1728+ "Header flickable is not correctly set.");
1729+
1730+ page.flickable = testFlickable;
1731+ compare(page.flickable, testFlickable, "Flickable could not be set.");
1732+ compare(header.flickable, testFlickable,
1733+ "Header flickable was not update correctly.");
1734+
1735+ page.flickable = null;
1736+ compare(page.flickable, null, "Flickable cannot be unset.");
1737+ compare(header.flickable, null,
1738+ "Header flickable was not correctly unset.");
1739+ }
1740+
1741+ function wait_for_header_animation() {
1742+ // Wait for the header to start to move:
1743+ wait(50);
1744+ // Wait for animation of the style inside the header (when pushing/popping):
1745+ tryCompareFunction(function(){ return testCase.headerStyle.animating }, false);
1746+ // Wait for the header to finish showing/hiding:
1747+ tryCompareFunction(function(){ return testCase.header.moving }, false);
1748+ }
1749+
1750+ function test_flickableY_bug1201452() {
1751+ var headerHeight = testCase.header.height
1752+ var flickableY = 150;
1753+ page.flickable.contentY = flickableY;
1754+ compare(page.flickable.contentY, flickableY,
1755+ "Flickable.contentY could not be set.");
1756+ compare(page.flickable.topMargin, headerHeight,
1757+ "topMargin of the flickable does not equal header height.");
1758+
1759+ page.head.locked = true;
1760+ page.head.visible = false;
1761+ wait_for_header_animation();
1762+
1763+ compare(page.flickable.topMargin, 0,
1764+ "topMargin is not 0 when header is locked hidden.");
1765+ compare(page.flickable.contentY, flickableY + headerHeight,
1766+ "contentY was not updated properly when header was hidden.");
1767+
1768+ page.head.locked.locked = false;
1769+ page.head.visible = true;
1770+ wait_for_header_animation();
1771+
1772+ compare(page.flickable.contentY, flickableY,
1773+ "Hiding and showing header changes flickable.contentY.");
1774+ compare(page.flickable.topMargin, headerHeight,
1775+ "topMargin was not updated when header became visible.");
1776+ }
1777+ }
1778+}
1779
1780=== modified file 'tests/unit_x11/tst_components/tst_pagestack.new_header.qml'
1781--- tests/unit_x11/tst_components/tst_pagestack.new_header.qml 2015-03-06 12:42:00 +0000
1782+++ tests/unit_x11/tst_components/tst_pagestack.new_header.qml 2015-03-23 15:53:11 +0000
1783@@ -1,5 +1,5 @@
1784 /*
1785- * Copyright 2012-2014 Canonical Ltd.
1786+ * Copyright 2012-2015 Canonical Ltd.
1787 *
1788 * This program is free software; you can redistribute it and/or modify
1789 * it under the terms of the GNU Lesser General Public License as published by
1790@@ -16,7 +16,7 @@
1791
1792 import QtQuick 2.2
1793 import Ubuntu.Test 1.0
1794-import Ubuntu.Components 1.1
1795+import Ubuntu.Components 1.2
1796
1797 // pagestack tests for deprecated toolbar are in
1798 // unit/tst_components/tst_pagestack_deprecated_toolbar.qml
1799@@ -28,7 +28,6 @@
1800 MainView {
1801 id: mainView
1802 anchors.fill: parent
1803- useDeprecatedToolbar: false
1804 PageStack {
1805 id: pageStack
1806 Page {
1807@@ -73,43 +72,36 @@
1808 when: windowShown
1809 id: testCase
1810
1811- property var head_style
1812-
1813 function initTestCase() {
1814- testCase.head_style = findChild(mainView, "PageHeadStyle");
1815- wait_for_animation();
1816+ waitForHeaderAnimation(mainView);
1817 compare(pageStack.currentPage, null, "is not set by default");
1818 compare(pageStack.__propagated, mainView.__propagated, "propagated property of pageStack equals mainView.__propagated")
1819 compare(mainView.__propagated.header.title, "", "empty title by default");
1820 }
1821
1822- function wait_for_animation() {
1823- tryCompareFunction(function(){return testCase.head_style.animating}, false);
1824- }
1825-
1826 function test_depth() {
1827 compare(pageStack.depth, 0, "depth is 0 by default");
1828 pageStack.push(page1);
1829- wait_for_animation();
1830+ waitForHeaderAnimation(mainView);
1831 compare(pageStack.depth, 1, "depth is correctly increased when pushing a page");
1832 pageStack.push(page2);
1833- wait_for_animation();
1834+ waitForHeaderAnimation(mainView);
1835 compare(pageStack.depth, 2, "depth is correctly updated when pushing a page");
1836 pageStack.pop();
1837- wait_for_animation();
1838+ waitForHeaderAnimation(mainView);
1839 compare(pageStack.depth, 1, "depth is correctly decreased when popping a page");
1840 pageStack.clear();
1841- wait_for_animation();
1842+ waitForHeaderAnimation(mainView);
1843 compare(pageStack.depth, 0, "depth is after clearing");
1844 }
1845
1846 function test_currentPage() {
1847 compare(pageStack.currentPage, null, "currentPage is null by default");
1848 pageStack.push(page1);
1849- wait_for_animation();
1850+ waitForHeaderAnimation(mainView);
1851 compare(pageStack.currentPage, page1, "currentPage properly updated");
1852 pageStack.clear();
1853- wait_for_animation();
1854+ waitForHeaderAnimation(mainView);
1855 compare(pageStack.currentPage, null, "currentPage properly reset");
1856 }
1857
1858@@ -117,60 +109,60 @@
1859 compare(pageStack.depth, 0, "depth is 0 initially");
1860 pageStack.push(page1);
1861 pageStack.push(page2);
1862- wait_for_animation();
1863+ waitForHeaderAnimation(mainView);
1864 compare(pageStack.currentPage, page2, "last pushed page is on top");
1865 pageStack.pop();
1866- wait_for_animation();
1867+ waitForHeaderAnimation(mainView);
1868 compare(pageStack.currentPage, page1, "popping puts previously pushed page on top");
1869 pageStack.clear();
1870- wait_for_animation();
1871+ waitForHeaderAnimation(mainView);
1872 }
1873
1874 function test_active_bug1260116() {
1875 pageStack.push(page1);
1876- wait_for_animation();
1877+ waitForHeaderAnimation(mainView);
1878
1879 compare(page1.active, true, "Page is active after pushing");
1880 pageStack.push(page2);
1881- wait_for_animation();
1882+ waitForHeaderAnimation(mainView);
1883
1884 compare(page1.active, false, "Page no longer active after pushing a new page");
1885 compare(page2.active, true, "New page is active after pushing");
1886 pageStack.pop();
1887- wait_for_animation();
1888+ waitForHeaderAnimation(mainView);
1889 compare(page1.active, true, "Page re-activated when on top of the stack");
1890 compare(page2.active, false, "Page no longer active after being popped");
1891 pageStack.clear();
1892- wait_for_animation();
1893+ waitForHeaderAnimation(mainView);
1894
1895 compare(pageInStack.active, false, "Page defined inside PageStack is not active by default");
1896 pageStack.push(pageInStack);
1897- wait_for_animation();
1898+ waitForHeaderAnimation(mainView);
1899 compare(pageInStack.active, true, "Pushing a page on PageStack makes it active");
1900 pageStack.pop();
1901- wait_for_animation();
1902+ waitForHeaderAnimation(mainView);
1903 compare(pageInStack.active, false, "Popping a page from PageStack makes it inactive");
1904 pageStack.clear();
1905- wait_for_animation();
1906+ waitForHeaderAnimation(mainView);
1907 }
1908
1909 function test_title_bug1143345_bug1317902() {
1910 pageStack.push(page1);
1911- wait_for_animation();
1912+ waitForHeaderAnimation(mainView);
1913 compare(mainView.__propagated.header.title, "Title 1", "Header title is correctly set by page");
1914 page1.title = "New title";
1915 compare(mainView.__propagated.header.title, "New title", "Header title correctly updated by page");
1916 pageStack.push(page2);
1917- wait_for_animation();
1918+ waitForHeaderAnimation(mainView);
1919 compare(mainView.__propagated.header.title, "Title 2", "Header title is correctly set by page");
1920 pageStack.clear();
1921 page1.title = "Title 1";
1922
1923 pageStack.push(pageWithPage);
1924- wait_for_animation();
1925+ waitForHeaderAnimation(mainView);
1926 compare(mainView.__propagated.header.title, pageWithPage.title, "Embedded page sets title of outer page");
1927 pageStack.clear();
1928- wait_for_animation();
1929+ waitForHeaderAnimation(mainView);
1930 }
1931
1932 function get_tabs_button() {
1933@@ -182,36 +174,36 @@
1934 function test_tabs_inside_stack_bug1187850() {
1935 compare(get_tabs_button(), null, "Without tabs there is no visible tabs button.");
1936 pageStack.push(tabs);
1937- wait_for_animation();
1938+ waitForHeaderAnimation(mainView);
1939 compare(pageStack.currentPage, tabs, "Tabs can be pushed on a PageStack");
1940 compare(tabs.active, true, "Tabs on top of a PageStack are active");
1941 compare(get_tabs_button().visible, true, "Pushing tabs on pagestack enables the tabs button");
1942 pageStack.push(page1);
1943- wait_for_animation();
1944+ waitForHeaderAnimation(mainView);
1945 compare(pageStack.currentPage, page1, "A page can be pushed on top of a Tabs");
1946 compare(tabs.active, false, "Tabs on a PageStack, but not on top, are inactive");
1947 compare(get_tabs_button(), null, "Contents of inactive Tabs is not applied to header");
1948 pageStack.pop();
1949- wait_for_animation();
1950+ waitForHeaderAnimation(mainView);
1951 compare(tabs.active, true, "Tabs on top of PageStack is active");
1952 compare(get_tabs_button().visible, true, "Active Tabs controls header contents");
1953 pageStack.clear();
1954- wait_for_animation();
1955+ waitForHeaderAnimation(mainView);
1956 }
1957
1958 function test_pop_to_tabs_bug1316736() {
1959 pageStack.push(tabs);
1960- wait_for_animation();
1961+ waitForHeaderAnimation(mainView);
1962 tabs.selectedTabIndex = 1;
1963 pageStack.push(page1);
1964- wait_for_animation();
1965+ waitForHeaderAnimation(mainView);
1966 compare(tabs.active, false, "Tabs on a PageStack, but not on top, are inactive");
1967 pageStack.pop();
1968- wait_for_animation();
1969+ waitForHeaderAnimation(mainView);
1970 compare(tabs.active, true, "Tabs on top of PageStack is active");
1971 compare(tabs.selectedTabIndex, 1, "Pushing and popping another page on top of Tabs does not change selectedTabsIndex");
1972 pageStack.clear();
1973- wait_for_animation();
1974+ waitForHeaderAnimation(mainView);
1975 }
1976
1977 function test_push_return_values() {
1978@@ -225,7 +217,7 @@
1979 compare(pushedPage.title, "Page from QML file",
1980 "PageStack.push() returns Page created from QML file");
1981 pageStack.clear();
1982- wait_for_animation();
1983+ waitForHeaderAnimation(mainView);
1984 }
1985 }
1986 }

Subscribers

People subscribed via source and target branches