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…
\