← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~ahayzen/music-app/cards-use-gridview into lp:music-app

 

Andrew Hayzen has proposed merging lp:~ahayzen/music-app/cards-use-gridview into lp:music-app.

Commit message:
* Switch to using Qt GridView instead of custom CardView

Requested reviews:
  Music App Developers (music-app-dev)

For more details, see:
https://code.launchpad.net/~ahayzen/music-app/cards-use-gridview/+merge/279700

* Switch to using Qt GridView instead of custom CardView

Should it be fixed to two lines of text on for the artist and genre cards?

Note, I have not run autopilot yet.
-- 
Your team Music App Developers is requested to review the proposed merge of lp:~ahayzen/music-app/cards-use-gridview into lp:music-app.
=== removed file 'app/components/ColumnFlow.qml'
--- app/components/ColumnFlow.qml	2015-07-08 00:32:49 +0000
+++ app/components/ColumnFlow.qml	1970-01-01 00:00:00 +0000
@@ -1,514 +0,0 @@
-/*
- * Copyright (C) 2014, 2015
- *      Andrew Hayzen <ahayzen@xxxxxxxxx>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.4
-
-Item {
-    id: columnFlow
-    property int columns: 1
-    property Flickable flickable
-    property var model
-    property Component delegate
-
-    property var getter: function (i) { return model.get(i); }  // optional getter override (useful for music-app ms2 models)
-
-    property int buffer: units.gu(20)
-    property var columnHeights: []
-    property var columnHeightsMax: []
-    property int columnWidth: parent.width / columns
-    property int contentHeight: 0
-    property int count: model === undefined ? 0 : model.count
-    property int delayRebuildIndex: -1
-    property var incubating: ({})  // incubating objects
-    property var items: ({})
-    property var itemToColumn: ({})  // cache of the columns of indexes
-    property int lastIndex: 0  // the furtherest index loaded
-    property bool removing: false
-    property bool restoring: false  // is the view restoring?
-    property var restoreItems: ({})  // when rebuilding items are stored here temporarily
-    // Disable preloading for now until async loading of pages is implemented
-    property bool preloading: true  // when visible has only been false allow loading (as no child objects [eg pages] can have been created on the fly)
-
-    onColumnWidthChanged: {
-        if (restoring) {
-            return;
-        } else if (columns != columnHeights.length && visible) {
-            // number of columns has changed so rebuild the columns
-            rebuildColumns()
-        } else {  // column width has changed update visible items properties linked to columnWidth
-            for (var column=0; column < columnHeights.length; column++) {
-                for (var i in columnHeights[column]) {
-                    if (columnHeights[column].hasOwnProperty(i) && items.hasOwnProperty(i)) {
-                        items[i].width = columnWidth;
-                        items[i].x = column * columnWidth;
-                    }
-                }
-            }
-
-            ensureItemsVisible()
-        }
-    }
-
-    onVisibleChanged: {
-        if (visible) {
-            preloading = false;
-        }
-
-        if (visible && delayRebuildIndex !== -1) {  // restore from count change
-            if (delayRebuildIndex === 0) {
-                reset()
-            } else {
-                removeIndex(delayRebuildIndex)
-            }
-
-            delayRebuildIndex = -1
-            append(true)
-        }
-
-        // number of columns has changed while invisible so reset if not already restoring
-        if (visible && !restoring && columns != columnHeights.length) {
-            rebuildColumns()
-        }
-    }
-
-    ListModel {  // fakemodel for connections to link to when there is no model
-        id: fakeModel
-    }
-
-    Connections {
-        target: model === undefined ? fakeModel : model
-        onModelReset: {
-            if (!visible && lastIndex > 0 && !preloading) {
-                delayRebuildIndex = 0
-            } else {
-                reset()
-                append()
-            }
-        }
-        onRowsInserted: {
-            if (!visible && lastIndex > 0 && !preloading) {
-                setDelayRebuildIndex(first)
-            } else {
-                if (first <= lastIndex) {
-                    if (first === 0) {
-                        reset()
-                    } else {
-                        removeIndex(first)  // remove earliest index and all items after
-                    }
-                }
-
-                // Supply last index if larger as count is not updated until after insertion
-                append(true, last > count ? last : count)
-            }
-        }
-        onRowsRemoved: {
-            if (!visible) {
-                setDelayRebuildIndex(first)
-            } else {
-                if (first <= lastIndex) {
-                    if (first === 0) {
-                        reset()
-                    } else {
-                        removeIndex(first)  // remove earliest index and all items after
-                    }
-
-                    // count is not updated until after removal, so send insertMax
-                    // insertMax is count - removal region inclusive - 1 (lastIndex is 1 infront)
-
-                    append(true, count - (1 + last - first) - 1)  // rebuild any items on screen or before
-                }
-            }
-        }
-    }
-
-
-    Connections {
-        target: flickable
-        onContentYChanged: {
-            append()  // Append any new items (scrolling down)
-
-            ensureItemsVisible()
-        }
-    }
-
-    // Append a new row of items if possible
-    function append(loadBefore, insertMax)
-    {
-        // Do not allow append to run if incubating
-        if (isIncubating() || restoring || removing) {
-            return;
-        }
-
-        // get the columns in order
-        var columnsByHeight = getColumnsByHeight();
-        var workDone = false;
-
-        // check if a new item in each column is possible
-        for (var i=0; i < columnsByHeight.length; i++) {
-            var y = columnHeightsMax[columnsByHeight[i]];
-
-            // build new object in column if possible
-            // if insertMax is undefined then allow if there is work todo (from the count in the model)
-            // otherwise use the insertMax as the count to compare with the lastIndex added to the columnFlow
-            // and
-            // allow if the y position is within the viewport
-            // or if loadBefore is true then allow if the y position is before the viewport
-            if (((count > 0 && lastIndex < count && insertMax === undefined) || (insertMax !== undefined && lastIndex <= insertMax))
-                    && (inViewport(y, 0) || (loadBefore === true && beforeViewport(y)))) {
-                incubateObject(lastIndex++, columnsByHeight[i], getMaxInColumn(columnsByHeight[i]), append);
-                workDone = true
-            } else {
-                break;
-            }
-        }
-
-        if (!workDone) {  // last iteration over append so visible ensure items are correct
-            ensureItemsVisible();
-        }
-    }
-
-    // Detect if a loaded object is before the viewport with a buffer
-    function beforeViewport(y)
-    {
-        return y <= flickable.contentY - buffer;
-    }
-
-    // Cache the size of the columns for use later
-    function cacheColumnHeights()
-    {
-        columnHeightsMax = [];
-
-        for (var i=0; i < columnHeights.length; i++) {
-            var sum = 0;
-
-            for (var j in columnHeights[i]) {
-                sum += columnHeights[i][j];
-            }
-
-            columnHeightsMax.push(sum);
-        }
-
-        if (!restoring) {  // when not restoring otherwise user will be pushed to the top of the view
-            // set the height of columnFlow to max column (for flickable contentHeight)
-            contentHeight = Math.max.apply(null, columnHeightsMax);
-        }
-    }
-
-    // Recache the visible items heights (due to a change in their height)
-    function cacheVisibleItemsHeights()
-    {
-        for (var i in items) {
-            if (items.hasOwnProperty(i)) {
-                columnHeights[itemToColumn[i]][i] = items[i].height;
-            }
-        }
-
-        cacheColumnHeights();
-    }
-
-    // Ensures that the correct items are visible
-    function ensureItemsVisible()
-    {
-        for (var i in items) {
-            if (items.hasOwnProperty(i)) {
-                items[i].visible = inViewport(items[i].y, items[i].height)
-            }
-        }
-    }
-
-    // Return if there are incubating objects
-    function isIncubating()
-    {
-        for (var i in incubating) {
-            if (incubating.hasOwnProperty(i)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    // Run after incubation to store new column height and call any further append/restores
-    function finishIncubation(index, callback)
-    {
-        var obj = incubating[index].object;
-        delete incubating[index];
-
-        obj.heightChanged.connect(cacheVisibleItemsHeights)  // if the height changes recache
-
-        // Ensure properties linked to columnWidth are correct (as width may still be changing)
-        obj.x = itemToColumn[index] * columnWidth;
-        obj.width = columnWidth;
-
-        items[index] = obj;
-
-        columnHeights[itemToColumn[index]][index] = obj.height;  // ensure height is the latest
-
-        if (!isIncubating()) {
-            cacheColumnHeights();
-
-            // Check if there is any more work to be done (append or restore)
-            callback();
-        }
-    }
-
-    // Force any incubation to finish
-    function forceIncubationCompletion()
-    {
-        for (var i in incubating) {
-            if (incubating.hasOwnProperty(i)) {
-                incubating[i].forceCompletion()
-            }
-        }
-    }
-
-    // Get the column index in order of height
-    function getColumnsByHeight()
-    {
-        var columnsByHeight = [];
-
-        for (var i=0; i < columnHeightsMax.length; i++) {
-            var min = undefined;
-            var index = -1;
-
-            // Find the smallest column that has not been found yet
-            for (var j=0; j < columnHeightsMax.length; j++) {
-                if (columnsByHeight.indexOf(j) === -1 && (min === undefined || columnHeightsMax[j] < min)) {
-                    min = columnHeightsMax[j];
-                    index = j;
-                }
-            }
-
-            columnsByHeight.push(index);
-        }
-
-        return columnsByHeight;
-    }
-
-    // Get the highest index for a column
-    function getMaxInColumn(column)
-    {
-        var max;
-
-        for (var i in columnHeights[column]) {
-            if (columnHeights[column].hasOwnProperty(i)) {
-                i = parseInt(i);
-
-                if (items.hasOwnProperty(i)) {
-                    if (i > max || max === undefined) {
-                        max = i;
-                    }
-                }
-            }
-        }
-
-        return max;
-    }
-
-    // Incubate an object for creation
-    function incubateObject(index, column, anchorIndex, callback)
-    {
-        // Load parameters to send to the object on creation
-        var params = {
-            "anchors.top": anchorIndex === undefined ? parent.top : items[anchorIndex].bottom,
-            index: index,
-            model: getter(index),
-            width: columnWidth,
-            x: column * columnWidth
-        };
-
-        // Start incubating and cache the column
-        incubating[index] = delegate.incubateObject(parent, params);
-        itemToColumn[index] = column;
-
-        if (incubating[index].status != Component.Ready) {
-            incubating[index].onStatusChanged = function(status) {
-                if (status == Component.Ready) {
-                    finishIncubation(index, callback)
-                }
-            }
-        } else {
-            finishIncubation(index, callback)
-        }
-    }
-
-    // Detect if a loaded object is in the viewport with a buffer
-    function inViewport(y, height)
-    {
-        return flickable.contentY - buffer < y + height && y < flickable.contentY + flickable.height + buffer;
-    }
-
-    // Number of columns has changed rebuild with live items
-    function rebuildColumns()
-    {
-        restoring = true;
-        var i;
-
-        forceIncubationCompletion()
-
-        columnHeights = []
-        columnHeightsMax = []
-
-        for (i=0; i < columns; i++) {
-            columnHeights.push({});
-            columnHeightsMax.push(0);
-        }
-
-        lastIndex = 0;
-
-        restoreItems = items;
-        items = {};
-
-        restoreExisting()
-
-        restoring = false;
-
-        cacheColumnHeights();  // rebuilds contentHeight
-
-        // If the columns have changed while the view was locked rerun
-        if (columns != columnHeights.length && visible) {
-            rebuildColumns()
-        } else {
-            append()  // check if any new items can be added
-        }
-    }
-
-    // Remove an index from the model (invalidating anything after)
-    function removeIndex(index)
-    {
-        removing = true
-
-        forceIncubationCompletion()
-
-        for (var i in items) {
-            if (i >= index && items.hasOwnProperty(i)) {
-                delete columnHeights[itemToColumn[i]][i]
-                delete itemToColumn[i]
-
-                items[i].destroy()
-                delete items[i]
-            }
-        }
-
-        lastIndex = index
-        removing = false
-
-        cacheColumnHeights()
-    }
-
-    // Restores existing items into potentially new positions
-    function restoreExisting()
-    {
-        var i;
-
-        // get the columns in order
-        var columnsByHeight = getColumnsByHeight();
-        var workDone = false;
-
-        // check if a new item in each column is possible
-        for (i=0; i < columnsByHeight.length; i++) {
-            var column = columnsByHeight[i];
-
-            // build new object in column if possible
-            if (count > 0 && lastIndex < count) {
-                if (restoreItems.hasOwnProperty(lastIndex)) {
-                    var item = restoreItems[lastIndex];
-                    var maxInColumn = getMaxInColumn(column);  // get lowest item in column
-
-                    itemToColumn[lastIndex] = column;
-                    columnHeights[column][lastIndex] = item.height;  // ensure height is the latest
-
-                    // Rebuild item properties
-                    item.anchors.bottom = undefined
-                    item.anchors.top = maxInColumn === undefined ? parent.top : items[maxInColumn].bottom;
-                    item.x = column * columnWidth;
-                    item.visible = inViewport(item.y, item.height);
-
-                    // Migrate item from restoreItems to items
-                    items[lastIndex] = item;
-                    delete restoreItems[lastIndex];
-
-                    // set after restore as height will likely change causing cacheVisibleItemsHeights to be run
-                    item.width = columnWidth;
-
-                    cacheColumnHeights();  // ensure column heights are up to date
-
-                    lastIndex++;
-                    workDone = true;
-                }
-            } else {
-                break;
-            }
-        }
-
-        if (workDone) {
-            restoreExisting()  // if work done then check if any more is needed
-        } else {
-            restoreItems = {};  // ensure restoreItems is empty
-        }
-    }
-
-    // Reset the column flow
-    function reset()
-    {
-        forceIncubationCompletion()
-
-        // Destroy any old items
-        for (var j in items) {
-            if (items.hasOwnProperty(j)) {
-                items[j].destroy()
-            }
-        }
-
-        // Reset and rebuild the variables
-        items = ({})
-        itemToColumn = ({})
-        lastIndex = 0
-
-        columnHeights = []
-
-        for (var k=0; k < columns; k++) {
-            columnHeights.push({})
-        }
-
-        cacheColumnHeights()
-
-        contentHeight = 0
-    }
-
-    function setDelayRebuildIndex(index)
-    {
-        // Only update the delay index if it isn't set (-1) or
-        // if it is within the size of the model (lastIndex)
-        // and is lower than the current delayed index
-        if (delayRebuildIndex === -1 ||
-                (index < delayRebuildIndex && index < lastIndex)) {
-            delayRebuildIndex = index
-        }
-    }
-
-    Component.onCompleted: {
-        // Ensure that initial column vars are set
-        for (var j=0; j < columns; j++) {
-            columnHeights.push({})
-        }
-
-        cacheColumnHeights()
-
-        append(true)
-    }
-}

=== modified file 'app/components/Delegates/Card.qml'
--- app/components/Delegates/Card.qml	2015-08-12 23:36:44 +0000
+++ app/components/Delegates/Card.qml	2015-12-06 20:42:34 +0000
@@ -22,11 +22,8 @@
 
 Item {
     id: card
-    height: cardColumn.childrenRect.height + 2 * bg.anchors.margins
-
-    /* Required by ColumnFlow */
-    property int index
-    property var model
+    height: parent.parent.cellHeight
+    width: parent.parent.cellWidth
 
     property alias coverSources: coverGrid.covers
     property alias primaryText: primaryLabel.text
@@ -38,26 +35,14 @@
 
     /* Animations */
     Behavior on height {
-        UbuntuNumberAnimation {
-
+        NumberAnimation {
+            duration: UbuntuAnimation.FastDuration
         }
     }
 
     Behavior on width {
-        UbuntuNumberAnimation {
-
-        }
-    }
-
-    Behavior on x {
-        UbuntuNumberAnimation {
-
-        }
-    }
-
-    Behavior on y {
-        UbuntuNumberAnimation {
-
+        NumberAnimation {
+            duration: UbuntuAnimation.FastDuration
         }
     }
 
@@ -85,7 +70,7 @@
         }
 
         Item {
-            height: units.gu(1)
+            height: units.gu(.5)
             width: units.gu(1)
         }
 
@@ -100,8 +85,9 @@
             color: "#FFF"
             elide: Text.ElideRight
             fontSize: "small"
+            height: units.gu(1.5)
             opacity: 1.0
-            wrapMode: Text.WordWrap
+            wrapMode: Text.NoWrap
         }
 
         Label {
@@ -115,12 +101,13 @@
             color: "#FFF"
             elide: Text.ElideRight
             fontSize: "small"
+            height: units.gu(1.5)
             opacity: 0.4
-            wrapMode: Text.WordWrap
+            wrapMode: Text.NoWrap
         }
 
         Item {
-            height: units.gu(1.5)
+            height: units.gu(1) + cardColumn.spacing
             width: units.gu(1)
         }
     }
@@ -132,22 +119,22 @@
             fill: bg
         }
         color: "#000"
-        opacity: 0
+        opacity: cardMouseArea.pressed ? 0.3 : 0
 
         Behavior on opacity {
-            UbuntuNumberAnimation {
-
+            NumberAnimation {
+                duration: UbuntuAnimation.FastDuration
             }
         }
     }
 
     /* Capture mouse events */
     MouseArea {
+        id: cardMouseArea
         anchors {
             fill: parent
         }
         onClicked: card.clicked(mouse)
         onPressAndHold: card.pressAndHold(mouse)
-        onPressedChanged: overlay.opacity = pressed ? 0.3 : 0
     }
 }

=== removed file 'app/components/Flickables/CardView.qml'
--- app/components/Flickables/CardView.qml	2015-08-12 23:36:44 +0000
+++ app/components/Flickables/CardView.qml	1970-01-01 00:00:00 +0000
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2014, 2015
- *      Andrew Hayzen <ahayzen@xxxxxxxxx>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.4
-import Ubuntu.Components 1.3
-import "../"
-
-
-Flickable {
-    id: cardViewFlickable
-    anchors {
-        fill: parent
-    }
-
-    // dont use flow.contentHeight as it is inaccurate due to height of labels
-    // changing as they load
-    contentHeight: headerLoader.childrenRect.height + flow.contentHeight + flowContainer.anchors.margins * 2
-    contentWidth: width
-
-    property alias count: flow.count
-    property alias delegate: flow.delegate
-    property var getter
-    property alias header: headerLoader.sourceComponent
-    property alias model: flow.model
-    property real itemWidth: units.gu(15)
-
-    onGetterChanged: flow.getter = getter  // cannot use alias to set a function (must be var)
-
-    Loader {
-        id: headerLoader
-        visible: sourceComponent !== undefined
-        width: parent.width
-    }
-
-    Item {
-        id: flowContainer
-        anchors {
-            bottom: parent.bottom
-            left: parent.left
-            margins: units.gu(1)
-            right: parent.right
-            top: headerLoader.bottom
-        }
-        width: parent.width
-
-        ColumnFlow {
-            id: flow
-            anchors {
-                fill: parent
-            }
-            columns: parseInt(cardViewFlickable.width / itemWidth) || 1  // never drop to 0
-            flickable: cardViewFlickable
-        }
-    }
-
-    Component.onCompleted: {
-        // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
-        // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
-        var scaleFactor = units.gridUnit / 8;
-        maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
-        flickDeceleration = flickDeceleration * scaleFactor;
-    }
-}

=== added file 'app/components/Flickables/MusicGridView.qml'
--- app/components/Flickables/MusicGridView.qml	1970-01-01 00:00:00 +0000
+++ app/components/Flickables/MusicGridView.qml	2015-12-06 20:42:34 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+GridView {
+    id: gridView
+    anchors {
+        fill: parent
+    }
+    cellHeight: cellSize + heightOffset
+    cellWidth: cellSize + widthOffset
+    displaced: Transition {
+        NumberAnimation {
+            duration: UbuntuAnimation.FastDuration
+            properties: "x,y"
+        }
+    }
+    populate: Transition {
+        NumberAnimation {
+            duration: UbuntuAnimation.FastDuration
+            properties: "x,y"
+        }
+    }
+
+    readonly property int columns: parseInt(parent.width / itemWidth) || 1  // never drop to 0
+    readonly property int cellSize: parent.width / columns
+    property int itemWidth: units.gu(15)
+    property int heightOffset: 0
+    property int widthOffset: 0
+
+    Component.onCompleted: {
+        // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
+        // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
+        var scaleFactor = units.gridUnit / 8;
+        maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+        flickDeceleration = flickDeceleration * scaleFactor;
+    }
+}

=== modified file 'app/ui/AddToPlaylist.qml'
--- app/ui/AddToPlaylist.qml	2015-10-28 01:05:33 +0000
+++ app/ui/AddToPlaylist.qml	2015-12-06 20:42:34 +0000
@@ -68,9 +68,10 @@
         }
     }
 
-    CardView {
+    MusicGridView {
         id: addtoPlaylistView
         itemWidth: units.gu(12)
+        heightOffset: units.gu(7)
         model: SortFilterModel {
             // Sorting disabled as it is incorrect on first run (due to workers?)
             // and SQL sorts the data correctly
@@ -80,7 +81,7 @@
             filter.pattern: new RegExp(searchHeader.query, "i")
             filterCaseSensitivity: Qt.CaseInsensitive
         }
-        objectName: "addToPlaylistCardView"
+        objectName: "addToPlaylistGridView"
         delegate: Card {
             id: playlist
             coverSources: Playlists.getPlaylistCovers(playlist.name)

=== modified file 'app/ui/Albums.qml'
--- app/ui/Albums.qml	2015-08-12 23:36:44 +0000
+++ app/ui/Albums.qml	2015-12-06 20:42:34 +0000
@@ -49,8 +49,11 @@
     // qml doesn't optimise using the parent type
     property bool bug1341671workaround: true
 
-    CardView {
-        id: albumCardView
+    MusicGridView {
+        id: albumGridView
+        itemWidth: units.gu(15)
+        heightOffset: units.gu(7)
+
         model: SortFilterModel {
             id: albumsModelFilter
             property alias rowCount: albumsModel.rowCount

=== modified file 'app/ui/ArtistView.qml'
--- app/ui/ArtistView.qml	2015-10-28 01:05:33 +0000
+++ app/ui/ArtistView.qml	2015-12-06 20:42:34 +0000
@@ -37,18 +37,10 @@
     property var covers: []
     property bool loaded: false  // used to detect difference between first and further loads
 
-    CardView {
+    MusicGridView {
         id: artistAlbumView
-        anchors {
-            fill: parent
-        }
-        getter: function (i) {
-            return {
-                "art": albumsModel.get(i, AlbumsModel.RoleArt),
-                "artist": albumsModel.get(i, AlbumsModel.RoleArtist),
-                "title": albumsModel.get(i, AlbumsModel.RoleTitle),
-            };
-        }
+        itemWidth: units.gu(12)
+        heightOffset: units.gu(5.5)
         header: BlurredHeader {
             id: blurredHeader
             rightColumn: Column {
@@ -109,7 +101,6 @@
                 store: musicStore
             }
         }
-        itemWidth: units.gu(12)
         model: AlbumsModel {
             id: albumsModel
             albumArtist: artistViewPage.artist

=== modified file 'app/ui/Artists.qml'
--- app/ui/Artists.qml	2015-10-28 01:05:33 +0000
+++ app/ui/Artists.qml	2015-12-06 20:42:34 +0000
@@ -53,9 +53,10 @@
     // qml doesn't optimise using the parent type
     property bool bug1341671workaround: true
 
-    CardView {
-        id: artistCardView
+    MusicGridView {
+        id: artistGridView
         itemWidth: units.gu(12)
+        heightOffset: units.gu(5.5)
         model: SortFilterModel {
             id: artistsModelFilter
             property alias rowCount: artistsModel.rowCount

=== modified file 'app/ui/Genres.qml'
--- app/ui/Genres.qml	2015-08-12 23:36:44 +0000
+++ app/ui/Genres.qml	2015-12-06 20:42:34 +0000
@@ -49,9 +49,10 @@
     // qml doesn't optimise using the parent type
     property bool bug1341671workaround: true
 
-    CardView {
-        id: genreCardView
+    MusicGridView {
+        id: genreGridView
         itemWidth: units.gu(12)
+        heightOffset: units.gu(5.5)
         model: SortFilterModel {
             id: genresModelFilter
             model: GenresModel {

=== modified file 'app/ui/Playlists.qml'
--- app/ui/Playlists.qml	2015-10-28 01:05:33 +0000
+++ app/ui/Playlists.qml	2015-12-06 20:42:34 +0000
@@ -66,8 +66,10 @@
         onTriggered: playlistModel.filterPlaylists()
     }
 
-    CardView {
+    MusicGridView {
         id: playlistslist
+        itemWidth: units.gu(15)
+        heightOffset: units.gu(7)
         model: SortFilterModel {
             // Sorting disabled as it is incorrect on first run (due to workers?)
             // and SQL sorts the data correctly
@@ -77,7 +79,7 @@
             filter.pattern: new RegExp(searchHeader.query, "i")
             filterCaseSensitivity: Qt.CaseInsensitive
         }
-        objectName: "playlistsCardView"
+        objectName: "playlistsGridView"
         delegate: Card {
             id: playlistCard
             coverSources: Playlists.getPlaylistCovers(model.name)

=== modified file 'app/ui/Recent.qml'
--- app/ui/Recent.qml	2015-10-28 01:05:33 +0000
+++ app/ui/Recent.qml	2015-12-06 20:42:34 +0000
@@ -63,8 +63,10 @@
         ]
     }
 
-    CardView {
-        id: recentCardView
+    MusicGridView {
+        id: recentGridView
+        itemWidth: units.gu(15)
+        heightOffset: units.gu(7)
         model: recentModel.model
         delegate: Card {
             id: albumCard

=== modified file 'debian/changelog'
--- debian/changelog	2015-12-03 19:53:17 +0000
+++ debian/changelog	2015-12-06 20:42:34 +0000
@@ -2,6 +2,7 @@
 
   [ Andrew Hayzen ]
   * Release 2.2ubuntu2 and start on 2.3
+  * Switch to using Qt GridView instead of custom CardView
 
   [ Ken VanDine ]
   * Install the content-hub json file in the correct place for peer registry

=== modified file 'tests/autopilot/music_app/__init__.py'
--- tests/autopilot/music_app/__init__.py	2015-11-03 03:55:07 +0000
+++ tests/autopilot/music_app/__init__.py	2015-12-06 20:42:34 +0000
@@ -212,7 +212,7 @@
 
     def get_count(self):
         return self.wait_select_single(
-            "CardView", objectName="playlistsCardView").count
+            "MusicGridView", objectName="playlistsGridView").count
 
     def click_new_playlist_action(self):
             self.main_view.get_header(
@@ -243,7 +243,7 @@
 
     def get_count(self):  # careful not to conflict until Page11 is fixed
         return self.wait_select_single(
-            "CardView", objectName="addToPlaylistCardView").count
+            "MusicGridView", objectName="addToPlaylistGridView").count
 
     def get_playlist(self, i):
         return (self.wait_select_single("Card",


Follow ups