git.iko.soy/maciek134/podphoenix
on main help sign in
Merged devel branch rev 130 (new grid view)
commita1cd092aaa6839f73052bf61ca361d29d07ca51b authorNekhelesh Ramananthan <krnekhelesh@gmail.com> dateSat 19 Mar 2016, 14:32 (11 years ago) parentdbee41b
4 files changed+70−623
app/components/Card.qml+54−109 app/components/CardView.qml+14−19 app/components/ColumnFlow.qml−494 app/ui/PodcastsTab.qml+2−1
app/components/Card.qml+54−109
@@ -1,12 +1,13 @@
11/*
2− * Copyright (C) 2014-2016
3− * Andrew Hayzen <ahayzen@gmail.com>
2+ * Copyright 2016 Podbird Team
43 *
5− * This program is free software; you can redistribute it and/or modify
4+ * This file is part of Podbird.
5+ *
6+ * Podbird is free software; you can redistribute it and/or modify
67 * it under the terms of the GNU General Public License as published by
78 * the Free Software Foundation; version 3.
89 *
9− * This program is distributed in the hope that it will be useful,
10+ * Podbird is distributed in the hope that it will be useful,
1011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1112 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1213 * GNU General Public License for more details.
@@ -18,122 +19,66 @@
1819import QtQuick 2.4
1920import Ubuntu.Components 1.3
2021
21−Item {
22+AbstractButton {
2223 id: card
2324
24− /* Required by ColumnFlow */
25− property int index
26− property var model
25+ height: parent.parent.cellHeight
26+ width: parent.parent.cellWidth
2727
2828 property alias coverArt: imgFrame.source
2929 property alias primaryText: primaryLabel.text
30− property alias secondaryText: secondaryLabel.text
31− property alias secondaryTextVisible: secondaryLabel.visible
32−
33− signal clicked(var mouse)
34− signal pressAndHold(var mouse)
35−
36− height: cardColumn.childrenRect.height + 2 * bg.anchors.margins
37−
38− /* Background for card */
39− Rectangle {
40− id: bg
41− anchors.fill: parent
42− anchors.margins: units.gu(1)
43− color: podbird.appTheme.hightlightListView
44− }
45−
46− /* Column containing image and labels */
47− Column {
48− id: cardColumn
49−
50− anchors.fill: bg
51− spacing: units.gu(0.5)
52−
53− Image {
54− id: imgFrame
55− width: parent.width
56− height: width
57− sourceSize.height: width
58− sourceSize.width: width
59− }
60−
61− Item {
62− height: units.gu(1)
63− width: units.gu(1)
64− }
65−
66− Label {
67− id: primaryLabel
68− anchors {
69− left: parent.left
70− right: parent.right
71− margins: units.gu(1)
72− }
73− color: podbird.appTheme.baseText
74− elide: Text.ElideRight
75− textSize: Label.Small
76− opacity: 1.0
77− wrapMode: Text.WordWrap
78− horizontalAlignment: Text.AlignHCenter
30+ property string secondaryText: ""
31+
32+ Image {
33+ id: imgFrame
34+ width: parent.width/1.2
35+ height: width
36+ anchors.top: parent.top
37+ anchors.horizontalCenter: parent.horizontalCenter
38+ sourceSize.height: width
39+ sourceSize.width: width
40+
41+ Loader {
42+ id: hintLoader
43+ anchors.verticalCenter: parent.top
44+ anchors.right: parent.right
45+ anchors.rightMargin: units.gu(-0.5)
46+ sourceComponent: secondaryText !== "" ? hintComponent : undefined
7947 }
8048
81− Label {
82− id: secondaryLabel
83− anchors {
84− left: parent.left
85− leftMargin: units.gu(1)
86− right: parent.right
87− rightMargin: units.gu(1)
49+ Component {
50+ id: hintComponent
51+ Rectangle {
52+ color: podbird.appTheme.focusText
53+ width: secondaryLabel.implicitWidth + units.gu(1)
54+ height: secondaryLabel.implicitHeight + units.gu(1)
55+ radius: units.gu(0.5)
56+ visible: secondaryLabel.text !== ""
57+ Label {
58+ id: secondaryLabel
59+ anchors.centerIn: parent
60+ text: secondaryText
61+ visible: text !== ""
62+ textSize: Label.Small
63+ color: "White"
64+ }
8865 }
89− color: podbird.appTheme.baseSubText
90− elide: Text.ElideRight
91− textSize: Label.Small
92− opacity: 1.0
93− wrapMode: Text.WordWrap
94− horizontalAlignment: Text.AlignHCenter
95− }
96−
97− Item {
98− height: units.gu(1.5)
99− width: units.gu(1)
10066 }
10167 }
10268
103− /* Overlay for when card is pressed */
104− Rectangle {
105− id: overlay
106− anchors.fill: bg
107− color: "#000"
108− opacity: 0
109−
110− Behavior on opacity {
111− UbuntuNumberAnimation {}
69+ Label {
70+ id: primaryLabel
71+ anchors {
72+ top: imgFrame.bottom
73+ left: imgFrame.left
74+ right: imgFrame.right
75+ margins: units.gu(1)
11276 }
113− }
114−
115− /* Capture mouse events */
116− MouseArea {
117− anchors.fill: parent
118− onClicked: card.clicked(mouse)
119− onPressAndHold: card.pressAndHold(mouse)
120− onPressedChanged: overlay.opacity = pressed ? 0.3 : 0
121− }
122−
123− /* Animations */
124− Behavior on height {
125− UbuntuNumberAnimation {}
126− }
127−
128− Behavior on width {
129− UbuntuNumberAnimation {}
130− }
131−
132− Behavior on x {
133− UbuntuNumberAnimation {}
134− }
135−
136− Behavior on y {
137− UbuntuNumberAnimation {}
77+ color: podbird.appTheme.baseText
78+ elide: Text.ElideRight
79+ textSize: Label.Small
80+ wrapMode: Text.WordWrap
81+ maximumLineCount: 2
82+ horizontalAlignment: Text.AlignHCenter
13883 }
13984}
app/components/CardView.qml+14−19
@@ -18,33 +18,28 @@
1818import QtQuick 2.4
1919import Ubuntu.Components 1.3
2020
21−Flickable {
22− id: cardViewFlickable
21+GridView {
22+ id: gridView
23+
2324 anchors {
2425 fill: parent
2526 margins: units.gu(1)
2627 }
2728
28− // dont use flow.contentHeight as it is inaccurate due to height of labels
29− // changing as they load
30− contentHeight: flow.contentHeight + flow.anchors.margins * 2 + units.gu(8)
31− contentWidth: width
32−
33− property alias count: flow.count
34− property alias delegate: flow.delegate
35− property var getter
36− property alias model: flow.model
37− property real itemWidth: units.gu(15)
29+ cellHeight: cellSize + heightOffset
30+ cellWidth: cellSize + widthOffset
3831
39− onGetterChanged: flow.getter = getter // cannot use alias to set a function (must be var)
40−
41− ColumnFlow {
42− id: flow
43− anchors.fill: parent
44− columns: parseInt(cardViewFlickable.width / itemWidth) || 1 // never drop to 0
45− flickable: cardViewFlickable
32+ header: Item {
33+ width: parent.width
34+ height: units.gu(2)
4635 }
4736
37+ readonly property int columns: parseInt(width / itemWidth) || 1 // never drop to 0
38+ readonly property int cellSize: width / columns
39+ property int itemWidth: units.gu(15)
40+ property int heightOffset: 0
41+ property int widthOffset: 0
42+
4843 Component.onCompleted: {
4944 // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
5045 // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
app/components/ColumnFlow.qmldeleted−494
@@ -1,494 +0,0 @@
1−/*
2− * Copyright (C) 2014-2016
3− * Andrew Hayzen <ahayzen@gmail.com>
4− *
5− * This program is free software; you can redistribute it and/or modify
6− * it under the terms of the GNU General Public License as published by
7− * the Free Software Foundation; version 3.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU General Public License for more details.
13− *
14− * You should have received a copy of the GNU General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− */
17−
18−import QtQuick 2.4
19−
20−Item {
21− id: columnFlow
22− property int columns: 1
23− property Flickable flickable
24− property var model
25− property Component delegate
26−
27− property var getter: function (i) { return model.get(i); } // optional getter override (useful for music-app ms2 models)
28−
29− property int buffer: units.gu(20)
30− property var columnHeights: []
31− property var columnHeightsMax: []
32− property int columnWidth: parent.width / columns
33− property int contentHeight: 0
34− property int count: model === undefined ? 0 : model.count
35− property int delayRebuildIndex: -1
36− property var incubating: ({}) // incubating objects
37− property var items: ({})
38− property var itemToColumn: ({}) // cache of the columns of indexes
39− property int lastIndex: 0 // the furtherest index loaded
40− property bool removing: false
41− property bool restoring: false // is the view restoring?
42− property var restoreItems: ({}) // when rebuilding items are stored here temporarily
43−
44− onColumnWidthChanged: {
45− if (restoring) {
46− return;
47− } else if (columns != columnHeights.length && visible) {
48− // number of columns has changed so rebuild the columns
49− rebuildColumns()
50− } else { // column width has changed update visible items properties linked to columnWidth
51− for (var column=0; column < columnHeights.length; column++) {
52− for (var i in columnHeights[column]) {
53− if (columnHeights[column].hasOwnProperty(i) && items.hasOwnProperty(i)) {
54− items[i].width = columnWidth;
55− items[i].x = column * columnWidth;
56− }
57− }
58− }
59−
60− ensureItemsVisible()
61− }
62− }
63−
64− onVisibleChanged: {
65− if (visible && delayRebuildIndex !== -1) { // restore from count change
66− if (delayRebuildIndex === 0) {
67− reset()
68− } else {
69− removeIndex(delayRebuildIndex)
70− }
71−
72− delayRebuildIndex = -1
73− append(true)
74− }
75−
76− // number of columns has changed while invisible so reset if not already restoring
77− if (visible && !restoring && columns != columnHeights.length) {
78− rebuildColumns()
79− }
80− }
81−
82− ListModel { // fakemodel for connections to link to when there is no model
83− id: fakeModel
84− }
85−
86− Connections {
87− target: model === undefined ? fakeModel : model
88− onModelReset: {
89− if (!visible && lastIndex > 0) {
90− delayRebuildIndex = 0
91− } else {
92− reset()
93− append()
94− }
95− }
96− onRowsInserted: {
97− if (!visible && lastIndex > 0) {
98− setDelayRebuildIndex(first)
99− } else {
100− if (first <= lastIndex) {
101− if (first === 0) {
102− reset()
103− } else {
104− removeIndex(first) // remove earliest index and all items after
105− }
106− }
107−
108− // Supply last index if larger as count is not updated until after insertion
109− append(true, last > count ? last : count)
110− }
111− }
112− onRowsRemoved: {
113− if (!visible) {
114− setDelayRebuildIndex(first)
115− } else {
116− if (first <= lastIndex) {
117− if (first === 0) {
118− reset()
119− } else {
120− removeIndex(first) // remove earliest index and all items after
121− }
122−
123− // count is not updated until after removal, so send insertMax
124− // insertMax is count - removal region inclusive - 1 (lastIndex is 1 infront)
125−
126− append(true, count - (1 + last - first) - 1) // rebuild any items on screen or before
127− }
128− }
129− }
130− }
131−
132−
133− Connections {
134− target: flickable
135− onContentYChanged: {
136− append() // Append any new items (scrolling down)
137−
138− ensureItemsVisible()
139− }
140− }
141−
142− // Append a new row of items if possible
143− function append(loadBefore, insertMax)
144− {
145− // Do not allow append to run if incubating
146− if (isIncubating() || restoring || removing) {
147− return;
148− }
149−
150− // get the columns in order
151− var columnsByHeight = getColumnsByHeight();
152− var workDone = false;
153−
154− // check if a new item in each column is possible
155− for (var i=0; i < columnsByHeight.length; i++) {
156− var y = columnHeightsMax[columnsByHeight[i]];
157−
158− // build new object in column if possible
159− // if insertMax is undefined then allow if there is work todo (from the count in the model)
160− // otherwise use the insertMax as the count to compare with the lastIndex added to the columnFlow
161− // and
162− // allow if the y position is within the viewport
163− // or if loadBefore is true then allow if the y position is before the viewport
164− if (((count > 0 && lastIndex < count && insertMax === undefined) || (insertMax !== undefined && lastIndex <= insertMax)) && (inViewport(y, 0) || (loadBefore === true && beforeViewport(y)))) {
165− incubateObject(lastIndex++, columnsByHeight[i], getMaxInColumn(columnsByHeight[i]), append);
166− workDone = true
167− } else {
168− break;
169− }
170− }
171−
172− if (!workDone) { // last iteration over append so visible ensure items are correct
173− ensureItemsVisible();
174− }
175− }
176−
177− // Detect if a loaded object is before the viewport with a buffer
178− function beforeViewport(y)
179− {
180− return y <= flickable.contentY - buffer;
181− }
182−
183− // Cache the size of the columns for use later
184− function cacheColumnHeights()
185− {
186− columnHeightsMax = [];
187−
188− for (var i=0; i < columnHeights.length; i++) {
189− var sum = 0;
190−
191− for (var j in columnHeights[i]) {
192− sum += columnHeights[i][j];
193− }
194−
195− columnHeightsMax.push(sum);
196− }
197−
198− if (!restoring) { // when not restoring otherwise user will be pushed to the top of the view
199− // set the height of columnFlow to max column (for flickable contentHeight)
200− contentHeight = Math.max.apply(null, columnHeightsMax);
201− }
202− }
203−
204− // Recache the visible items heights (due to a change in their height)
205− function cacheVisibleItemsHeights()
206− {
207− for (var i in items) {
208− if (items.hasOwnProperty(i)) {
209− columnHeights[itemToColumn[i]][i] = items[i].height;
210− }
211− }
212−
213− cacheColumnHeights();
214− }
215−
216− // Ensures that the correct items are visible
217− function ensureItemsVisible()
218− {
219− for (var i in items) {
220− if (items.hasOwnProperty(i)) {
221− items[i].visible = inViewport(items[i].y, items[i].height)
222− }
223− }
224− }
225−
226− // Return if there are incubating objects
227− function isIncubating()
228− {
229− for (var i in incubating) {
230− if (incubating.hasOwnProperty(i)) {
231− return true;
232− }
233− }
234−
235− return false;
236− }
237−
238− // Run after incubation to store new column height and call any further append/restores
239− function finishIncubation(index, callback)
240− {
241− var obj = incubating[index].object;
242− delete incubating[index];
243−
244− obj.heightChanged.connect(cacheVisibleItemsHeights) // if the height changes recache
245−
246− // Ensure properties linked to columnWidth are correct (as width may still be changing)
247− obj.x = itemToColumn[index] * columnWidth;
248− obj.width = columnWidth;
249−
250− items[index] = obj;
251−
252− columnHeights[itemToColumn[index]][index] = obj.height; // ensure height is the latest
253−
254− if (!isIncubating()) {
255− cacheColumnHeights();
256−
257− // Check if there is any more work to be done (append or restore)
258− callback();
259− }
260− }
261−
262− // Force any incubation to finish
263− function forceIncubationCompletion()
264− {
265− for (var i in incubating) {
266− if (incubating.hasOwnProperty(i)) {
267− incubating[i].forceCompletion()
268− }
269− }
270− }
271−
272− // Get the column index in order of height
273− function getColumnsByHeight()
274− {
275− var columnsByHeight = [];
276−
277− for (var i=0; i < columnHeightsMax.length; i++) {
278− var min = undefined;
279− var index = -1;
280−
281− // Find the smallest column that has not been found yet
282− for (var j=0; j < columnHeightsMax.length; j++) {
283− if (columnsByHeight.indexOf(j) === -1 && (min === undefined || columnHeightsMax[j] < min)) {
284− min = columnHeightsMax[j];
285− index = j;
286− }
287− }
288−
289− columnsByHeight.push(index);
290− }
291−
292− return columnsByHeight;
293− }
294−
295− // Get the highest index for a column
296− function getMaxInColumn(column)
297− {
298− var max;
299−
300− for (var i in columnHeights[column]) {
301− if (columnHeights[column].hasOwnProperty(i)) {
302− i = parseInt(i);
303−
304− if (items.hasOwnProperty(i)) {
305− if (i > max || max === undefined) {
306− max = i;
307− }
308− }
309− }
310− }
311−
312− return max;
313− }
314−
315− // Incubate an object for creation
316− function incubateObject(index, column, anchorIndex, callback)
317− {
318− // Load parameters to send to the object on creation
319− var params = {
320− "anchors.top": anchorIndex === undefined ? parent.top : items[anchorIndex].bottom,
321− index: index,
322− model: getter(index),
323− width: columnWidth,
324− x: column * columnWidth
325− };
326−
327− // Start incubating and cache the column
328− incubating[index] = delegate.incubateObject(parent, params);
329− itemToColumn[index] = column;
330−
331− if (incubating[index].status != Component.Ready) {
332− incubating[index].onStatusChanged = function(status) {
333− if (status == Component.Ready) {
334− finishIncubation(index, callback)
335− }
336− }
337− } else {
338− finishIncubation(index, callback)
339− }
340− }
341−
342− // Detect if a loaded object is in the viewport with a buffer
343− function inViewport(y, height)
344− {
345− return flickable.contentY - buffer < y + height && y < flickable.contentY + flickable.height + buffer;
346− }
347−
348− // Number of columns has changed rebuild with live items
349− function rebuildColumns()
350− {
351− restoring = true;
352− var i;
353−
354− forceIncubationCompletion()
355−
356− columnHeights = []
357− columnHeightsMax = []
358−
359− for (i=0; i < columns; i++) {
360− columnHeights.push({});
361− columnHeightsMax.push(0);
362− }
363−
364− lastIndex = 0;
365−
366− restoreItems = items;
367− items = {};
368−
369− restoreExisting()
370−
371− restoring = false;
372−
373− cacheColumnHeights(); // rebuilds contentHeight
374−
375− // If the columns have changed while the view was locked rerun
376− if (columns != columnHeights.length && visible) {
377− rebuildColumns()
378− } else {
379− append() // check if any new items can be added
380− }
381− }
382−
383− // Remove an index from the model (invalidating anything after)
384− function removeIndex(index)
385− {
386− removing = true
387−
388− forceIncubationCompletion()
389−
390− for (var i in items) {
391− if (i >= index && items.hasOwnProperty(i)) {
392− delete columnHeights[itemToColumn[i]][i]
393− delete itemToColumn[i]
394−
395− items[i].destroy()
396− delete items[i]
397− }
398− }
399−
400− lastIndex = index
401− removing = false
402−
403− cacheColumnHeights()
404− }
405−
406− // Restores existing items into potentially new positions
407− function restoreExisting()
408− {
409− var i;
410−
411− // get the columns in order
412− var columnsByHeight = getColumnsByHeight();
413− var workDone = false;
414−
415− // check if a new item in each column is possible
416− for (i=0; i < columnsByHeight.length; i++) {
417− var column = columnsByHeight[i];
418−
419− // build new object in column if possible
420− if (count > 0 && lastIndex < count) {
421− if (restoreItems.hasOwnProperty(lastIndex)) {
422− var item = restoreItems[lastIndex];
423− var maxInColumn = getMaxInColumn(column); // get lowest item in column
424−
425− itemToColumn[lastIndex] = column;
426− columnHeights[column][lastIndex] = item.height; // ensure height is the latest
427−
428− // Rebuild item properties
429− item.anchors.bottom = undefined
430− item.anchors.top = maxInColumn === undefined ? parent.top : items[maxInColumn].bottom;
431− item.x = column * columnWidth;
432− item.visible = inViewport(item.y, item.height);
433−
434− // Migrate item from restoreItems to items
435− items[lastIndex] = item;
436− delete restoreItems[lastIndex];
437−
438− // set after restore as height will likely change causing cacheVisibleItemsHeights to be run
439− item.width = columnWidth;
440−
441− cacheColumnHeights(); // ensure column heights are up to date
442−
443− lastIndex++;
444− workDone = true;
445− }
446− } else {
447− break;
448− }
449− }
450−
451− if (workDone) {
452− restoreExisting() // if work done then check if any more is needed
453− } else {
454− restoreItems = {}; // ensure restoreItems is empty
455− }
456− }
457−
458− // Reset the column flow
459− function reset()
460− {
461− forceIncubationCompletion()
462−
463− // Destroy any old items
464− for (var j in items) {
465− if (items.hasOwnProperty(j)) {
466− items[j].destroy()
467− }
468− }
469−
470− // Reset and rebuild the variables
471− items = ({})
472− itemToColumn = ({})
473− lastIndex = 0
474−
475− columnHeights = []
476−
477− for (var k=0; k < columns; k++) {
478− columnHeights.push({})
479− }
480−
481− cacheColumnHeights()
482−
483− contentHeight = 0
484− }
485−
486− function setDelayRebuildIndex(index)
487− {
488− if (delayRebuildIndex === -1 || index < lastIndex) {
489− delayRebuildIndex = index
490− }
491− }
492−
493− Component.onCompleted: append(true)
494−}
app/ui/PodcastsTab.qml+2−1
@@ -166,12 +166,13 @@ Page {
166166 CardView {
167167 id: cardView
168168 clip: true
169+ heightOffset: units.gu(4)
169170 model: sortedPodcastModel
170171 delegate: Card {
171172 id: albumCard
172173 coverArt: model.image !== undefined ? model.image : Qt.resolvedUrl("../graphics/podbird.png")
173174 primaryText: model.name !== undefined ? model.name.trim() : "Undefined"
174− secondaryText: model.episodeCount > 0 ? i18n.tr("%1 unheard episode", "%1 unheard episodes", model.episodeCount).arg(model.episodeCount)
175+ secondaryText: model.episodeCount > 0 ? model.episodeCount
175176 : ""
176177 onClicked: {
177178 if(podcastPage.header === searchHeader) {