When designing a QML application, it is important to design a software Component that can be reused. It can be used again and again in other applications. Just like designing our own applications in our C++ and other languages, we can create our own modules. It can be widely used by other applications with little or no modification at all. We covered this a couple of days ago in our article “Using the Ubuntu Component Store to augment our QML Components”. Today we’ll show you how to do this with a concrete example. In today’s routine, we introduce an example of a podcast player.

\

  \

\

In the application we designed today, there are two pages. The first page is an introduction to the PODCAST RSS feed and a list of the audio it plays. On the second page, it displays an image of its logo and several buttons that can be used for playback.

\

This design uses a Component called GenericPodCastApp.qml. Its design is as follows:

\

GenericPodcastApp.qml

import QtQuick 2.0
import Ubuntu.Components 0.1
import QtQuick.XmlListModel 2.0
import Ubuntu.Components.ListItems 0.1 as ListItem
import QtMultimedia 5.0

PageStack {
    id: ps
    Component.onCompleted: ps.push(front)

    property alias squareLogo: logo.source
    property alias author: author.text
    property alias category: category.text
    property alias name: front.title
    property alias description: desc.text
    property alias feed: rssmodel.source

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: rssmodel.reload()
    }

    Page {
        id: front
        visible: true

        tools: ToolbarItems {
            ToolbarButton {
                action: reloadAction
            }
        }

        Flickable {
            anchors.fill: parent
            contentHeight: row.height + desc.height + showlist.height + desc.anchors.topMargin + showlist.anchors.topMargin

            Row {
                id: row
                width: parent.width
                anchors.top: parent.top
                anchors.left: parent.left
                anchors.topMargin: units.gu(1)
                anchors.leftMargin: units.gu(1)
                anchors.rightMargin: units.gu(1)
                spacing: units.gu(2)

                UbuntuShape {
                    id: logoshape
                    width: parent.width / 3
                    height: parent.width / 3
                    image: Image {
                        id: logo
                        fillMode: Image.PreserveAspectFit
                    }
                    ActivityIndicator {
                        running: logo.status != Image.Ready
                        anchors.centerIn: logoshape
                    }
                }

                Column {
                    width: row.width - row.spacing - row.anchors.leftMargin- row.anchors.rightMargin - logoshape.width
                    spacing: units.gu(1)
                    anchors.bottom: parent.bottom
                    Label {
                        id: author
                        fontSize: "small"
                        wrapMode: Text.WordWrap
                        width: parent.width
                    }
                    Label {
                        id: category
                        wrapMode: Text.WordWrap
                        width: parent.width
                        fontSize: "small"
                    }
                }
            }

            Label {
                id: desc
                anchors.top: row.bottom
                anchors.left: parent.left
                anchors.topMargin: units.gu(2)
                anchors.leftMargin: row.anchors.leftMargin
                width: parent.width - (row.anchors.leftMargin * 2)
                wrapMode: Text.WrapAtWordBoundaryOrAnywhere
                property bool expanded: false
                clip: true
                height: {
                    if (desc.contentHeight > units.gu(12) && !expanded) {
                        return units.gu(12)
                    }
                    return desc.contentHeight
                }

                Rectangle {
                    color: "black"
                    width: moretxt.contentWidth + units.gu(2)
                    height: moretxt.contentHeight
                    anchors.bottom: desc.bottom
                    anchors.right: desc.right
                    Label {
                        id: moretxt
                        color: "white"
                        anchors.centerIn: parent
                        text: desc.expanded ? "<<" : ">>"
                    }
                    visible: desc.contentHeight > units.gu(12)
                }

                MouseArea {
                    anchors.fill: parent
                    onClicked: desc.expanded = !desc.expanded
                }
            }

            Column {
                id: showlist
                anchors.top: desc.bottom
                anchors.topMargin: units.gu(2)
                width: parent.width
                Repeater {
                    model: rssmodel
                    ListItem.Standard {
                        text: title
                        width: parent.width
                        progression: true
                        onClicked: { ps.push(episode, {download: model.download, summary: model.summary, title: model.title}); }
                    }
                }
            }
            ActivityIndicator {
                anchors.top: desc.bottom
                anchors.topMargin: units.gu(2)
                height: reloadbutton.height
                width: height
                anchors.horizontalCenter: parent.horizontalCenter
                running: rssmodel.status != XmlListModel.Ready && rssmodel.status != XmlListModel.Error
            }
        }
    }

    Page {
        id: episode
        property string download
        property string summary
        visible: false

        Flickable {
            anchors.fill: parent
            contentHeight: biglogo.height + positionbar.height + buttons.height + epdesc.height + (epcol.spacing * 4)

            Column {
                id: epcol
                width: parent.width
                spacing: units.gu(2)

                Image {
                    id: biglogo
                    source: logo.source
                    width: parent.width
                    height: parent.width
                    fillMode: Image.PreserveAspectFit
                }

                Rectangle {
                    id: positionbar
                    width: buttons.width
                    anchors.horizontalCenter: parent.horizontalCenter
                    height: units.gu(5)
                    color: "transparent"

                    Rectangle {
                        id: actualbar
                        width: parent.width
                        height: units.gu(0.5)
                        color: "#999999"
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.centerIn: parent
                    }
                    Rectangle {
                        width: units.gu(0.5)
                        height: units.gu(2)
                        color: "#444444"
                        anchors.verticalCenter: actualbar.verticalCenter
                        x: actualbar.width * aud.position / aud.duration
                    }
                    MouseArea {
                        anchors.fill: parent
                        onPressed: {
                            aud.seek(aud.duration * mouse.x / actualbar.width)
                        }
                    }
                }

                Row {
                    id: buttons
                    spacing: units.gu(2)
                    anchors.horizontalCenter: parent.horizontalCenter
                    Button {
                        text: "<<30"
                        onClicked: aud.seek(aud.position - 30000)
                    }
                    Button {
                        text: aud.status == Audio.Loading ? "load" : (aud.playbackState == Audio.PlayingState ? "Stop" : "Play")
                        onClicked: {
                            aud.source = episode.download;
                            if (aud.playbackState == Audio.PlayingState) {
                                aud.pause();
                            } else {
                                aud.play();
                            }
                            console.log(aud.duration, aud.position);
                        }
                    }
                    Button {
                        text: "30>>"
                        onClicked: aud.seek(aud.position + 30000)
                    }

                }

                Label {
                    id: epdesc
                    width: parent.width - units.gu(4)
                    anchors.horizontalCenter: parent.horizontalCenter
                    text: episode.summary
                    wrapMode: Text.Wrap
                    color: "white"
                    textFormat: Text.RichText
                }
            }
        }
    }

    XmlListModel {
        id: rssmodel
        query: "/rss/channel/item"
        namespaceDeclarations: "declare namespace itunes='http://www.itunes.com/dtds/podcast-1.0.dtd'; declare namespace content='http://purl.org/rss/1.0/modules/content/';"
        XmlRole { name: "title"; query: "title/string()" }
        XmlRole { name: "pubDate"; query: "pubDate/string()" }
        XmlRole { name: "download"; query: "enclosure/@url/string()" }
        XmlRole { name: "summary"; query: "content:encoded/string()" }
    }

    Audio {
        id: aud
    }
}
Copy the code


\

\

Very simple in the above design. We use the Component by designating properties in it and assigning them externally. As I’ve talked about in some tutorials before. If we need to change the attributes of the child components in the Component, we can do so through alias in QML. The top of the first page displays the most basic information for a podcast. At the bottom of it, it uses a repeater to display the list of all epsodes.

\

Column { id: showlist anchors.top: desc.bottom anchors.topMargin: units.gu(2) width: parent.width Repeater { model: rssmodel ListItem.Standard { text: title width: parent.width progression: true onClicked: { ps.push(episode, {download: model.download, summary: model.summary, title: model.title}); }}}}Copy the code


\

Here we use rSSModel, which is defined as follows:

\

XmlListModel { id: rssmodel query: "/rss/channel/item" namespaceDeclarations: "To declare the namespace itunes = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; Declare the namespace content = 'http://purl.org/rss/1.0/modules/content/'." XmlRole { name: "title"; query: "title/string()" } XmlRole { name: "pubDate"; query: "pubDate/string()" } XmlRole { name: "download"; query: "enclosure/@url/string()" } XmlRole { name: "summary"; query: "content:encoded/string()" } }Copy the code

For more information about the use of XmlListModel, see the API introduction. I also have a lot of introductions on my blog. You can refer to some of my design routines.

\

So far, we’ve designed my GenericPodcastApp Component. If we need to apply it elsewhere, all we need to do is:

\

Main.qml

\

Import QtQuick 2.0 import Ubuntu.Components 1.1 /*! \brief MainView with a Label and Button elements. */ MainView { // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "podcast.liu-xiao-guo" /* This property enables the application to change orientation when the device is rotated. The default is false. */ //automaticOrientation: true // Removes the old toolbar and enables new features of the new header. useDeprecatedToolbar: false width: units.gu(60) height: units.gu(85) GenericPodcastApp { name: "Bad Voltage" squareLogo: "images/logo.jpg" author: "Stuart Langridge, Jono Bacon, Jeremy Garcia, and Bryan Lunduke" category: "Technology" feed: "http://www.badvoltage.org/feed/ogg/" description: "Every two weeks Bad Voltage delivers an amusing take on technology, Open Source, politics, music, and anything else we think is interesting." } }Copy the code


\

Its application is very simple. We just need to assign properties to it. We’ll gracefully make it a podcast for other sources. Of course, we can also use this to design more complex applications, so that we can easily add the RSS feeds we want to our list of podcasts. I’ll leave that exercise to our developers.

\

The entire project source at: github.com/liu-xiao-gu…

\