Thursday, March 28, 2013

Time Waster Turbo Charge


After my post yesterday about browsing 9gag.com, I decided I should make an even more efficient time waster. Way back in the day, I made an app called "lolz". But a lot has changed since then. Including the emergence of Imgur. Imgur is a service that hosts images for Reddit. And Reddit is the world's most efficient time waster.

Also, Imgur has a nice API, so access to the data seemed pretty easy. Thus, I bring you, the greatest time wasting app of ever.

Here's how I got started.

QML often uses a Model-View-Controller architecture built in. I found that I can take advantage of this by for my app because. Specifically, the imgur app optionally serves up XML. If you have the option to get XML, use it! It makes things go much faster.

So, the heart of the app as it exists today is my XmlListModel. An XmlListModel essentially turns XML into a list of objects or key/value pairs that other QML components can use. So, I made a model like this:

    XmlListModel
    {
        id: imagesXML;
        query: "/data/item";
        XmlRole
        {
            name: "imgURL";
            query: "link/string()";
        }
    }

There are 2 parts, the first is the "query". This tells the model what types in the XML to look for. The picture from Imgur are "items" so I tell the model to look inside data tags for item tags.

The second part is a set of XmlRoles. XmlRoles convert the info in the Xml tags into key value pairs for other components to consume. So, I tell it to make a key called "imgURL" that is paired with value of whatever string is stored in the "link" tag. In the imgur API, "link" is a url that goes directly to the image.

But where does the XML come from? Typically, you would use the source property to point the XmlListModel to a url with xml. But this won't work with the imgur API because you need to set a header in the http request that includes your client idea for the API. Well, at least I couldn't figure out how to tell the XmlListModel to set a value in the header.

However, I didn't fret. Instead I used good old javascript XMLHttpRequest to get the XML, and then to set the xml property for the list model. So I made an init() function that runs when the app is ready.

    function init()
    {
        var req = new XMLHttpRequest();
        var location = "https://api.imgur.com/3/gallery/r/funny/time/1.xml";
        req.open("GET", location, true);
        req.setRequestHeader('Authorization', 'Client-ID xxxxxxxx');
        req.send(null);
        req.onreadystatechange = function()
        {
            if (req.readyState == 4)
            {
                imagesXML.xml = req.responseText;
                activityIndicator.running = false;
                activityIndicator.visible = false;
                imagesView.visible = true;
            }
        };

This function makes a request, and when it gets a response it sets the XmlListModel's xml property to the responseText. Again, this is very classic AJAXy programming. Then I do a few lines of setting the UI. You may notice that this includes making the imagesView visible. imagesView is the list view that I created to be the view for the model.

        ListView
        {
            visible: false;
            id: imagesView;
            model: imagesXML;

            orientation: Qt.Horizontal;
            anchors.fill: parent;
            delegate: MouseArea
            {
                height: parent.height;
                width: root.width;

                Image
                {
                    width: root.width;
                    height: parent.height;
                    fillMode: Image.PreserveAspectFit;
                    source: imgURL;
                }

                onClicked:
                {
                    if(children[0].fillMode == Image.Pad)
                    {
                        children[0].fillMode = Image.PreserveAspectFit;
                    }
                    else
                    {
                     children[0].fillMode = Image.Pad;
                    }
                }
            }
        }

A ListView has 2 key parts. The first part specifies the model for the view using the model property. Easy enough.

The second part is the "delegate". A delegate is the component that you create for each entry in the model. As you can see, I chose to make a MouseArea for each imgurURL. The MouseArea, in turn, contains an Image for displaying the image. The MouseArea is set up so I can do some interactions with the Image as desired. Currently, a tap toggles the Image between resizing to fit the size of the Image and displaying the Image at it's normal size.

The great thing about the ListView is that it is a Flickable. So I get the nice flicking behavior of scrolling the list left and right for free! The ListView does other key things, especially, loading the images on demand. It only loads the images when they are scrolled into view. This saves a lot of network and memory resources.

So, for the next features, I think I shall allow the user to choose a subreddit to browse.

No comments:

Post a Comment