Building an Ubuntu SDK App: rev 5-6

Well I’m back again, with part 4 of this series.  No, that’s not a typo in the title, this post will be primarily about revision 5 and revision 6 of my bzr branch.  What happened to rev 4?  Well it was pretty boring to be honest, just removing some console.log() calls that I used as a poor excuse for a debugger.  Anyway, boring.

If you haven’t read the previous articles in this series, you’ll want to do that before reading any further here:

Comments

Everybody knows that comments are half the fun of Reddit, but up until now uReadIt wasn’t able to view them.  Now, the proper way to do this would be to use the Reddit API to download the comment threads, and load them using nested ListViews.  But that’s going to take a while, and I wanted comments now.  So I cheated, took the easy way out, and just used the existing WebView to load the Reddit comments page URL instead.  I’ll do it the right way in a later revision….probably.

The first thing I needed was a way to load comments instead of the article content.  This meant finally using the Comments toolbar action I put in place earlier.  But I needed a way to change back too, nobody likes a one-way trip, so I added an Article action as well.

    Action {
        id: commentAction
        objectName: "comment"
        visible: true

        iconSource: Qt.resolvedUrl("comments.png")
        text: i18n.tr("Comments")

        onTriggered: {
            articleContent.showComments()
            articleAction.enabled = true
            commentAction.enabled = false
            articleViewActions.active = false
        }
    }
    Action {
        id: articleAction
        objectName: "article"
        enabled: false

        iconSource: Qt.resolvedUrl("avatar.png")
        text: i18n.tr("Article")

        onTriggered: {
            articleContent.showArticle()
            commentAction.enabled = true
            articleAction.enabled = false
            articleViewActions.active = false
        }
    }

Then I had to write the showComments and showArticle functions, which would switch the WebView.url from one to the other.  There was just one problem, that code didn’t have the comments url, only the content url.  So first I had to pass more data to my articleView page.  To avoid having to do this again, I decided to just pass it the whole article data model that I was getting from JSONListModel, that way I would have all the data I could potentially need for future features.

Since I created a new property called article, I also get a callback handler called onArticleChanged, which I took advantage of to determine if an article’s content was already a link to a Reddit comment page, and if so disabling the option to switch between Article and Comments.

Page {
    id: articleView
    title: 'Article'
    property var article: undefined

    onArticleChanged: {
        if (article) {
            articleContent.article = article
            commentAction.enabled = !article.data.is_self
            articleAction.enabled = false
            articleView.title = article.data.title
            articleContent.visible = true
        }
    }

Now I could finally implement showComments and showArticle, which I decided to do inside of ArticleView.  To support that, I would also need to pass the article data model on again, this time to ArticleView.  Then I could use that data to switch the WebView’s url.

Item {
    property var article: undefined
    property string baseUrl: 'http://www.reddit.com'

    onArticleChanged: {
        if (article) {
            articleWebView.url = article.data.url
        }
    }
    ...
    function showComments() {
        console.log('Comments: '+baseUrl + article.data.permalink)
        articleWebView.url = baseUrl + article.data.permalink
    }
    function showArticle() {
        articleWebView.url = article.data.url
    }

Subreddit Filters

I usually only read the Hot subreddit filter, I’ve only used New a handful of times, but like I said in the first article in this series, I’m going things to learn the Ubuntu SDK, not make a Reddit app.  I wanted to write some code that used the Ubuntu Popups.Popover component, and changing Reddit filters seemed like a good use for that kind of component.

Like Popups.Dialog, using a Popover is relatively simple.  You start with a Component to contain your popup, add your components to it, then call PopupUtils.open.  For changing filters, I chose to just put in a Column filled with ListItem.Standard items, one for each filter.  When one of them is selected, it will change a new filter property on my SubredditListView (which will reload from Reddit using the new filter).

Component {
    id: popoverComponent
    Popups.Popover {
        id: popover
        Column {
            id: containerLayout
            ...
            ListItem.Standard {
                text: "Hot"
                selected: articleList.filter == 'hot'
                onClicked: {
                    articleList.filter = 'hot'
                    PopupUtils.close(popover)
                }
            }
            ListItem.Standard {
                text: "New"
                selected: articleList.filter == 'new'
                onClicked: {
                    articleList.filter = 'new'
                    PopupUtils.close(popover)
                }
            }
            ListItem.Standard {
                text: "Rising"
                selected: articleList.filter == 'rising'
                onClicked: {
                    articleList.filter = 'rising'
                    PopupUtils.close(popover)
                }
            }
            ListItem.Standard {
                text: "Controversial"
                selected: articleList.filter == 'controversial'
                onClicked: {
                    articleList.filter = 'controversial'
                    PopupUtils.close(popover)
                }
            }
            ListItem.Standard {
                text: "Top"
                selected: articleList.filter == 'top'
                onClicked: {
                    articleList.filter = 'top'
                    PopupUtils.close(popover)
                }
            }
        }
    }
}
tools: ToolbarActions {
    ...
    Action {
        id: filterAction
        objectName: "filterAction"

        iconSource: Qt.resolvedUrl("settings.png")
        text: i18n.tr("Filter")

        onTriggered: {
            PopupUtils.open(popoverComponent, filterAction.itemHint)
        }
    }
}

Packaging

Finally I was ready to package uReadIt, to make it easy to install.  I copied my packaging files from what was used by the Ubuntu Touch Core Apps, which was itself copied from packaging files used by the notepad-qml app.  Now I’ll admit, it’s not perfect, and we’ve already had patches submitted to fix the Core Apps packaging, changes which I will be applying to uReadIt at some point.  So don’t take these as the right way to package your app, I’m putting them here to explain in a broad sense what the different files do in a Debian package.

debian/control

The control file gives all of the data about your package.  It has two sections, the first is for the source package, it contains the source package name, list of dependent packages needed to build your app and package, and some other miscellaneous information used by the packaging system.  Below that will be one or more binary package definitions.  I only have one, you probably will too.  This section contains another list of dependent packages, but these are packages needed to run your app, not build it. It also contains a space to describe your application.  The first line of the Description should be a brief description, used when listing a lot of packages together, and the lines below it should have a longer description, used when showing more information about a single package.

Source: ureadit
Priority: extra
Maintainer: Michael Hall 
Build-Depends: debhelper (>= 8.0.0), 
Standards-Version: 3.9.4
Section: misc
Homepage: https://launchpad.net/~mhall119/

Package: ureadit
Section: misc
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends},
         qmlscene,
         qtdeclarative5-ubuntu-ui-toolkit-plugin | qt-components-ubuntu,
         qtdeclarative5-qtquick2-plugin
Description: Reddit Browser
 Desktop application for browsing Reddit, it's articles and comments

debian/rules

The rules file is what actually does the building, it’s like a Makefile for your package.  Ideally this doesn’t do much more than calling dh (debhelper).  In fact, mine should be doing that, but it has a lot of unnecessary complication due to being copied from one project to another to another.  You’re probably better of just ignoring mine, just remember that debian/rules does the building.

#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.

# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1

# Work-around for some machines where INSTALL_ROOT is not set properly by
# dh_auto_install
override_dh_auto_install:
	dh_auto_install -- INSTALL_ROOT=$(CURDIR)/debian/tmp

# Workaround a bug in that debhelper package version
override_dh_install:
	mkdir -p $(CURDIR)/debian/tmp/usr/share/applications/
	mkdir -p $(CURDIR)/debian/tmp/usr/bin/
	mkdir -p $(CURDIR)/debian/tmp/usr/share/uReadIt/
	cp uReadIt.desktop $(CURDIR)/debian/tmp/usr/share/applications/
	cp uReadIt.bin $(CURDIR)/debian/tmp/usr/bin/uReadIt
	cp -r *.qml *.js *.png $(CURDIR)/debian/tmp/usr/share/uReadIt/
	
	dh_install --sourcedir=debian/tmp --fail-missing

%:
	dh $@

debian/changelog

The changelog file contains a record of revisions to your package, just like a bzr or git changelog.  More importantly, the changelog is what tells debhelper the lasted version of your package.  So the (0.3) in the top line on mine tells it to build ureadit_0.3_all.deb.  It will also use the signature line to try and find a matching GPG key when signing packages.

ureadit (0.3) raring; urgency=low

  * Initial release

 -- Michael Hall   Mon, 25 March 2013 23:09:00 -0400

debian/copyright

Making sure that FOSS packages can be distributed, modified, and redistributed is important, so in Debian and Ubuntu having a properly formed copyright file is a requirement.  I won’t go into much detail on how to do this, the link at the top will take you to the official spec.  The key pieces are the sections that give a file glob, license and attribution.  You can have as many of these sections as you need to properly cover all of your app.

Format: http://dep.debian.net/deps/dep5
Upstream-Name: uReadIt
Source:

Files: *
Copyright: 2013 Michael Hall
License: GPL-3.0

Files: debian/*
Copyright: 2013 Michael Hall
License: LGPL-3.0

Next time: Customizing headers

The stock Ubuntu component Headers are nice, but they weren’t serving my purposes.  I wanted them to display more text, and ideally take up less room.  So in the next revision, I replaced them with some custom components that did exactly what I wanted.

This entry was posted in OpenSource, Programming, Projects and tagged , , , , , , . Bookmark the permalink.

2 Responses to Building an Ubuntu SDK App: rev 5-6

  1. Tim Peeters says:

    Great work :)

    I have a comment on this line of code:
    PopupUtils.open(popoverComponent, filterAction.itemHint)

    The second parameter of open() is used to determine the Item that the user tapped/clicked to open the popover. This is then used to to show a little pointer from the popover to that Item (usually a Button). I don’t think that this will work, and in your case it is null because no filterAction.itemHint was defined. That won’t cause any problems because passing a null parameter will center the Popover in your screen and not show the pointer to the button.

    However, there is a way to get the pointer here, if you want that. The triggered() signal of Action has a caller parameter, and the caller is exactly what you need: the button that represents the Action that the user taps to open the popover. So try this line instead in onTriggered and let me know how it works out:
    PopupUtils.open(popoverComponent, caller)

  2. The rules file is what actually does the building, it’s like a Makefile for your package. Ideally this doesn’t do much more than calling dh (debhelper). In fact, mine should be doing that, but it has a lot of unnecessary complication due to being copied from one project to another to another. You’re probably better of just ignoring mine, just remember that debian/rules does the building.

Comments are closed.