on xenial help sign in
Create ratings on demand instead of all at the same time
c7cb640Albert Astals Cid
qml/Dash/Previews/Preview.qml+2
@@ -90,6 +90,8 @@ Item {
90 widgetData: model.properties
90 widgetData: model.properties
91 isCurrentPreview: root.isCurrent
91 isCurrentPreview: root.isCurrent
92 scopeStyle: root.scopeStyle
92 scopeStyle: root.scopeStyle
93+ parentFlickable: column
94+
93 anchors {
95 anchors {
94 left: parent.left
96 left: parent.left
95 right: parent.right
97 right: parent.right
qml/Dash/Previews/PreviewRatingDisplay.qml+67−16
@@ -40,28 +40,79 @@ PreviewWidget {
40 id: root
40 id: root
41 implicitHeight: childrenRect.height
41 implicitHeight: childrenRect.height
42
42
43− Column {
43+ onIsCurrentPreviewChanged: ratingsList.updateRanges();
44+ onParentFlickableChanged: ratingsList.updateRanges();
45+
46+ Connections {
47+ target: parentFlickable
48+ onOriginYChanged: ratingsList.updateRanges();
49+ onContentYChanged: ratingsList.updateRanges();
50+ onHeightChanged: ratingsList.updateRanges();
51+ onContentHeightChanged: ratingsList.updateRanges();
52+ }
53+
54+ ListView {
55+ id: ratingsList
44 anchors { left: parent.left; right: parent.right; }
56 anchors { left: parent.left; right: parent.right; }
45− visible: reviewsRepeater.count > 0
57+ height: contentHeight
58+ interactive: false
59+
60+ model: root.widgetData["reviews"]
61+
62+ delegate: PreviewRatingSingleDisplay {
63+ id: prsd
64+ objectName: "reviewItem" + index
46
65
47− Repeater {
66+ anchors { left: parent.left; right: parent.right; }
48− id: reviewsRepeater
49− objectName: "reviewsRepeater"
50− model: root.widgetData["reviews"]
51
67
52− delegate: PreviewRatingSingleDisplay {
68+ rating: modelData["rating"] || -1
53− objectName: "reviewItem" + index
69+ author: modelData["author"] || ""
70+ review: modelData["review"] || ""
71+ urlIconEmpty: widgetData["rating-icon-empty"]
72+ urlIconFull: widgetData["rating-icon-full"]
73+ urlIconHalf: widgetData["rating-icon-half"]
74+ labelColor: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
75+ }
76+
77+ onContentHeightChanged: ratingsList.updateRanges();
54
78
55− anchors { left: parent.left; right: parent.right; }
79+ function updateRanges() {
80+ var baseItem = root.parent;
81+ if (!parentFlickable || !baseItem) {
82+ ratingsList.displayMarginBeginning = 0;
83+ ratingsList.displayMarginEnd = 0;
84+ return;
85+ }
56
86
57− rating: modelData["rating"] || -1
87+ if (parentFlickable.moving) {
58− author: modelData["author"] || ""
88+ // Do not update the range if we are overshooting up or down, since we'll come back
59− review: modelData["review"] || ""
89+ // to the stable position and delete/create items without any reason
60− urlIconEmpty: widgetData["rating-icon-empty"]
90+ if (parentFlickable.contentY < parentFlickable.originY) {
61− urlIconFull: widgetData["rating-icon-full"]
91+ return;
62− urlIconHalf: widgetData["rating-icon-half"]
92+ } else if (parentFlickable.contentHeight - parentFlickable.originY > parentFlickable.height &&
63− labelColor: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
93+ parentFlickable.contentY + parentFlickable.height > parentFlickable.contentHeight) {
94+ return;
95+ }
64 }
96 }
97+
98+ // A item view creates its delegates synchronously from
99+ // -displayMarginBeginning
100+ // to
101+ // height + displayMarginEnd
102+ // Around that area it adds the cacheBuffer area where delegates are created async
103+ //
104+ // We adjust displayMarginEnd to be negative so that the range of created delegates matches
105+ // from the beginning of the list to the end of the viewport.
106+ // Ideally we would also use displayMarginBeginning
107+ // so that delegates at the beginning get destroyed but that causes issues with
108+ // listview and is not really necessary to provide the better experience we're after
109+ var itemYOnViewPort = baseItem.y - parentFlickable.contentY;
110+ var displayMarginEnd = -ratingsList.contentHeight + parentFlickable.height - itemYOnViewPort;
111+ displayMarginEnd = -Math.max(-displayMarginEnd, 0);
112+ displayMarginEnd = -Math.min(-displayMarginEnd, ratingsList.contentHeight);
113+ displayMarginEnd = Math.round(displayMarginEnd);
114+
115+ ratingsList.displayMarginEnd = displayMarginEnd;
65 }
116 }
66 }
117 }
67}
118}
qml/Dash/Previews/PreviewWidget.qml+3
@@ -37,6 +37,9 @@ Item {
37 //! Set margins width.
37 //! Set margins width.
38 property real widgetMargins: units.gu(1)
38 property real widgetMargins: units.gu(1)
39
39
40+ /// The parent (vertical) flickable this widget is in (if any)
41+ property var parentFlickable: null
42+
40 /*! \brief This signal should be emitted when a preview action was triggered.
43 /*! \brief This signal should be emitted when a preview action was triggered.
41 *
44 *
42 * \param widgetId, actionId Respective identifiers from widgetData.
45 * \param widgetId, actionId Respective identifiers from widgetData.
qml/Dash/Previews/PreviewWidgetFactory.qml+4
@@ -43,6 +43,9 @@ Loader {
43 //! Set margins width.
43 //! Set margins width.
44 property real widgetMargins: status === Loader.Ready ? item.widgetMargins : units.gu(1)
44 property real widgetMargins: status === Loader.Ready ? item.widgetMargins : units.gu(1)
45
45
46+ /// The parent (vertical) flickable this widget is in (if any)
47+ property var parentFlickable: null
48+
46 //! Triggered signal forwarded from the widgets.
49 //! Triggered signal forwarded from the widgets.
47 signal triggered(string widgetId, string actionId, var data)
50 signal triggered(string widgetId, string actionId, var data)
48
51
@@ -82,6 +85,7 @@ Loader {
82 item.isCurrentPreview = Qt.binding(function() { return root.isCurrentPreview } )
85 item.isCurrentPreview = Qt.binding(function() { return root.isCurrentPreview } )
83 item.expanded = Qt.binding(function() { return root.expanded } )
86 item.expanded = Qt.binding(function() { return root.expanded } )
84 item.scopeStyle = Qt.binding(function() { return root.scopeStyle } )
87 item.scopeStyle = Qt.binding(function() { return root.scopeStyle } )
88+ item.parentFlickable = Qt.binding(function() { return root.parentFlickable } )
85 }
89 }
86
90
87 Connections {
91 Connections {
tests/qmltests/CMakeLists.txt+1
@@ -41,6 +41,7 @@ add_unity8_qmltest(Dash/Previews PreviewImageGallery)
41add_unity8_qmltest(Dash/Previews PreviewPayments)
41add_unity8_qmltest(Dash/Previews PreviewPayments)
42add_unity8_qmltest(Dash/Previews PreviewProgress)
42add_unity8_qmltest(Dash/Previews PreviewProgress)
43add_unity8_qmltest(Dash/Previews PreviewRatingDisplay)
43add_unity8_qmltest(Dash/Previews PreviewRatingDisplay)
44+add_unity8_qmltest(Dash/Previews PreviewRatingDisplayCreationRanges)
44add_unity8_qmltest(Dash/Previews PreviewRatingEdit)
45add_unity8_qmltest(Dash/Previews PreviewRatingEdit)
45add_unity8_qmltest(Dash/Previews PreviewRatingInput)
46add_unity8_qmltest(Dash/Previews PreviewRatingInput)
46add_unity8_qmltest(Dash/Previews PreviewSharing)
47add_unity8_qmltest(Dash/Previews PreviewSharing)
tests/qmltests/Dash/Previews/tst_PreviewRatingDisplay.qml+1−3
@@ -70,9 +70,7 @@ Rectangle {
70
70
71 function test_reviews(data) {
71 function test_reviews(data) {
72 previewRatingDisplay.widgetData = data.reviewsModel;
72 previewRatingDisplay.widgetData = data.reviewsModel;
73−
74− var reviewsRepeater = findChild(previewRatingDisplay, "reviewsRepeater");
73+ waitForRendering(previewRatingDisplay);
75− compare(reviewsRepeater.count, data.reviewsModel["reviews"].length);
76
74
77 for (var i = 0; i < data.reviewsModel["reviews"].length; ++i) {
75 for (var i = 0; i < data.reviewsModel["reviews"].length; ++i) {
78 var reviewItem = findChild(previewRatingDisplay, "reviewItem" + i);
76 var reviewItem = findChild(previewRatingDisplay, "reviewItem" + i);
tests/qmltests/Dash/Previews/tst_PreviewRatingDisplayCreationRanges.qmlnew file+94
@@ -0,0 +1,94 @@
1+/*
2+ * Copyright 2014,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 QtTest 1.0
19+import Ubuntu.Components 1.3
20+import "../../../../qml/Dash/Previews"
21+import Unity.Test 0.1 as UT
22+
23+Flickable {
24+ id: root
25+ width: units.gu(40)
26+ height: units.gu(80)
27+ contentHeight: factory.implicitHeight
28+
29+ property var reviewsEmpty: {
30+ "reviews": [ ]
31+ }
32+
33+ Item {
34+ id: widgetData500
35+ property var reviews: []
36+ }
37+
38+ PreviewWidgetFactory {
39+ id: factory
40+ anchors.fill: parent
41+ widgetType: "reviews"
42+ parentFlickable: root
43+ }
44+
45+ Component.onCompleted: {
46+ for (var i = 0; i < 500; ++i) {
47+ widgetData500.reviews.push({ author: "Some dude", review: "Very cool app" + i });
48+ }
49+ factory.widgetData = widgetData500;
50+ }
51+
52+ UT.UnityTestCase {
53+ name: "PreviewRatingDisplayTest"
54+ when: windowShown
55+
56+ function test_creation_speed() {
57+ factory.parentFlickable = null;
58+ factory.widgetData = reviewsEmpty;
59+ waitForRendering(root);
60+
61+ var start = new Date().getTime();
62+ factory.widgetData = widgetData500;
63+ waitForRendering(root);
64+ var end = new Date().getTime();
65+ var time500 = end - start;
66+
67+ factory.widgetData = reviewsEmpty;
68+ waitForRendering(root);
69+
70+ factory.parentFlickable = root;
71+ start = new Date().getTime();
72+ factory.widgetData = widgetData500;
73+ waitForRendering(root);
74+ end = new Date().getTime();
75+ var timeRanges500 = end - start;
76+
77+ // Measurements show it's usually like 20 times faster
78+ // but we set the range at 8 times fast to make sure it's not an unstable test
79+ verify(timeRanges500 * 8 < time500);
80+ }
81+
82+ function test_check_indexes() {
83+ // Check that item 499 isn't there on startup but if we scroll down
84+ // it will be there
85+ var reviewItem499 = findChild(factory, "reviewItem499");
86+ verify(reviewItem499 === null);
87+
88+ flickToYEnd(root);
89+
90+ var reviewItem499 = findChild(factory, "reviewItem499");
91+ verify(reviewItem499 !== null);
92+ }
93+ }
94+}
tests/utils/modules/Unity/Test/UnityTestCase.qml+2−1
@@ -239,7 +239,8 @@ TestCase {
239 var x = item.width / 2;
239 var x = item.width / 2;
240 var y = item.height - units.gu(1);
240 var y = item.height - units.gu(1);
241 var toY = units.gu(1);
241 var toY = units.gu(1);
242+ var maxIterations = 5 + item.contentHeight / item.height;
242− while (i < 5 && !item.atYEnd) {
243+ while (i < maxIterations && !item.atYEnd) {
243 touchFlick(item, x, y, x, toY);
244 touchFlick(item, x, y, x, toY);
244 tryCompare(item, "moving", false);
245 tryCompare(item, "moving", false);
245 ++i;
246 ++i;