MeeGo 1.2 Harmattan Developer Documentation Develop for the Nokia N9

Flickr Example

This demo uses Qt Quick components to create a simple Flickr client. It shows a selection of the images that have most recently been added to Flickr. The chief purpose of the demo is to show how to use Qt Quick components to create a user interface that responds to orientation changes.

It also demonstrates the use of the following Qt Quick components: Window, Page, PageStack, Button, ProgressBar, TextField, Toolbar, ToolBarLayout, ToolButton, Slider, ScrollDecorator and StatusBar.

Source: Browse here

Application Structure

The Flickr demo is almost completely written in QML. Qt Quick components are used to create the UI and a simple C++ wrapper collects the QML files and image files of the application into a single binary using the Qt resource system. The main.cpp sets the network proxy for the QApplication that runs the demo. Note that if FLICKR_FULLSCREEN is defined, then the demo will be compiled to run in fullscreen mode. This is required when compiling the application for mobile devices.

The code in main.cpp sets the main QML file of the application to be flickr.qml which starts loading the first page and shows a splash screen for a short period while network requests to Flickr service are being made.

 #include <QApplication>
 #include <QDeclarativeEngine>
 #include <QDeclarativeView>
 #include <QNetworkProxy>
 #include <QUrl>

 int main(int argc, char **argv)
 {
     QApplication app(argc, argv);

     QUrl proxyUrl(qgetenv("http_proxy"));
     if (proxyUrl.isValid() && !proxyUrl.host().isEmpty()) {
         int proxyPort = (proxyUrl.port() > 0) ? proxyUrl.port() : 8080;
         QNetworkProxy proxy(QNetworkProxy::HttpProxy, proxyUrl.host(), proxyPort);
         QNetworkProxy::setApplicationProxy(proxy);
     }
     else {
         QNetworkProxyQuery query(QUrl(QLatin1String("http://www.flickr.com")));
         QNetworkProxy proxy = QNetworkProxyFactory::systemProxyForQuery(query).value(0);
         if (proxy.type() != QNetworkProxy::NoProxy)
             QNetworkProxy::setApplicationProxy(proxy);
     }

     QDeclarativeView view;
     view.setSource(QUrl("qrc:/qml/flickr.qml"));

 #if defined(FLICKR_FULLSCREEN)
     view.window()->showFullScreen();
 #else
     view.window()->show();
 #endif

     QObject::connect(view.engine(), SIGNAL(quit()), &view, SLOT(close()));
     return app.exec();
 }

flickr.qml

The Window component is the root component of the application. It provides the means to react to orientation changes, as well as sizing the application.

The PageStack component manages a stack of Page components, which in turn implement the different views of the Flickr application. There are three Page components in the application:

  • ThumbnailPage: shows a list of image thumbnails in portrait orientation, and a grid of thumbnails in landscape orientation.
  • LargeImagePage: a large image view that shows a larger version of a thumbnail.
  • DetailsPage: implements a details view that shows detailed information about an image.

All three Page components share a common ancestor, the FlickrPage component, which implements background graphics.

The ToolBar component shows the buttons for the different Pages. A Page component can specify the a set of tools that automatically show up in the ToolBar when the page is on the top of the stack of pages in the PageStack.

When the application starts the Component.onCompleted() handler in Window pushes the ThumbnailPage to the PageStack.

The StatusBar and ToolBar are hidden based on largeImagePage.chromeOpacity.

Note the use of the PageStack.replace() function, pressing on the rightmost button of the LargeImagePage will not increase the stack depth in PageStack, it will instead replace the LargeImagePage with a DetailsPage object with the id detailsPage. The LargeImagePage object will not be destroyed even if it is not in the PageStack as it is defined within the Window element in flickr.qml. The PageStack.replace() function is used similarly in the DetailsPage to replace itself with the id largeImagePage.

 Window {
     id: window

     PageStack {
         id: pageStack

         anchors.fill: parent
         toolBar: toolBar
         onDepthChanged: searchBar.close();
     }

     StatusBar {
         id: statusBar

         anchors { top: parent.top; left: parent.left; right: parent.right }
         opacity: largeImagePage.chromeOpacity
     }

     SearchBar {
         id: searchBar

         anchors.top: statusBar.bottom
         width: statusBar.width
         onSearchTagChanged: photoFeedModel.tags = searchTag
     }

     ToolBar {
         id: toolBar

         anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
         opacity: largeImagePage.chromeOpacity
     }

     ThumbnailPage {
         id: thumbnailPage

         anchors { fill: parent; topMargin: statusBar.height; bottomMargin: toolBar.height }
         inPortrait: window.inPortrait
         model: PhotoFeedModel {
             id: photoFeedModel
         }
         tools: ToolBarLayout {
             ToolButton {
                 iconSource: "images/tb_back.svg"
                 onClicked: Qt.quit();
             }
             ToolButton {
                 iconSource: "images/tb_reload.svg"
                 onClicked: {
                     photoFeedModel.reload();
                     searchBar.close();
                 }
             }
             ToolButton {
                 iconSource: "images/tb_search.svg"
                 onClicked: searchBar.toggle();
             }
         }
         onPhotoClicked: {
             largeImagePage.setPhotoData(url, photoWidth, photoHeight);
             detailsPage.setPhotoData(author, date, description, tags, title,
                                      photoWidth, photoHeight);
             pageStack.push(largeImagePage);
         }
     }

     LargeImagePage {
         id: largeImagePage

         tools: ToolBarLayout {
             ToolButton {
                 iconSource: "images/tb_back.svg"
                 onClicked: pageStack.pop();
             }
             ToolButton {
                 iconSource: "images/tb_info.svg"
                 checked: false
                 onClicked: pageStack.replace(detailsPage);
             }
         }
     }

     DetailsPage {
         id: detailsPage

         anchors { fill: parent; topMargin: statusBar.height; bottomMargin: toolBar.height }

         tools: ToolBarLayout {
             ToolButton {
                 iconSource: "images/tb_back.svg"
                 onClicked: pageStack.pop();
             }
             ToolButton {
                 iconSource: "images/tb_info.svg"
                 checked: true
                 onClicked: pageStack.replace(largeImagePage);
             }
         }
     }

     Splash {
         id: splash

         image : "images/splash.png"
         timeout: 1000
         fadeout: 700
         Component.onCompleted: splash.activate();
         onFinished: splash.destroy();
     }

     Component.onCompleted: pageStack.push(thumbnailPage);
 }

Thumbnail Page

When the Flickr application starts, it shows most recently added images from Flickr as thumbnails. The ThumbnailPage implements this view, it shows the thumbnails differently in portrait and landscape orientations.

 import QtQuick 1.1
 import com.nokia.symbian 1.1
 import "UIConstants.js" as UI

 FlickrPage {
     id: thumbnailPage

     property XmlListModel model
     property bool inPortrait

     signal photoClicked(string url, int photoWidth, int photoHeight,
                         string author, string date, string description,
                         string tags, string title)

     Loader {
         sourceComponent: inPortrait ? listComponent : gridComponent
         anchors { fill: parent; margins: UI.LISTVIEW_MARGIN }
     }

     Component {
         id: gridComponent

         GridView {
             property int thumbnailsInRow: 4

             function cellWidth() {
                 return Math.floor(width / thumbnailsInRow);
             }

             cacheBuffer: 2 * height
             cellHeight: cellWidth
             cellWidth: cellWidth()
             delegate: GridDelegate {
                 onPhotoClicked: {
                     thumbnailPage.photoClicked(url, photoWidth, photoHeight, author,
                                                date, description, tags, title);
                 }
             }
             model: thumbnailPage.model

             onWidthChanged: {
                 thumbnailsInRow = width / (UI.THUMBNAIL_WRAPPER_SIDE + UI.THUMBNAIL_SPACING);
             }
         }
     }

     Component {
         id: listComponent

         ListView {
             cacheBuffer: 2 * height
             delegate: ListDelegate {
                 onPhotoClicked: {
                     thumbnailPage.photoClicked(url, photoWidth, photoHeight, author,
                                                date, description, tags, title);
                 }
             }
             model: thumbnailPage.model
         }
     }
 }

The property inPortrait is used to control whether a list of thumbnails or a grid of thumbnails is loaded by a Loader element. The GridView component is loaded in !inPortrait mode and the ListView component in inPortrait mode. The inPortrait property is bound to window.inPortrait in flickr.qml.

The GridView and ListView use a common specialized XmlListModel (PhotoFeedModel) that fetches the thumbnail information from an online feed provided by Flickr. Sharing the model reduces code duplication and improves performance as the shared model fetches the thumbnail information only once for both views.

Image of the ThumbnailPage in portrait orientation:

Image of the ThumbnailPage showing a grid of thumbnails in landscape orientation:

Image of the ThumbnailPage with the SearchBar visible:

The Flickr demo will show the a larger version of an image when the user clicks or taps on the thumbnail. This action is handled in the delegates of ListView and GridView, both emitting the photoClicked() signal that is handled in flickr.qml so that the LargeImagePage gets pushed to the PageStack with the function PageStack.push().

LargeImage Page

The LargeImagePage shows a larger version of an image that is flickable and zoomable.

The LargeImagePage shows a ProgressBar while loading an image. Once the image is loaded, ProgressBar will be hidden and the actual image is shown.

 ProgressBar {
     anchors.centerIn: parent
     minimumValue: 1
     maximumValue: 100
     value: image.progress * 100
     visible: image.status != Image.Ready

     Text {
         text: Math.floor(parent.value) + qsTr(" %");
         anchors.horizontalCenter: parent.horizontalCenter
         anchors.bottom: parent.top
         anchors.bottomMargin: UI.PROGRESSBAR_LABEL_BOTTOM_MARGIN
         font.bold: true
         color: UI.PROGRESSBAR_LABEL_COLOR
     }
 }

The Slider component is used to zoom the image, dragging the Slider handle scales the image creating a zoom effect.

 Slider {
     id: slider

     maximumValue: 1
     stepSize: (maximumValue - minimumValue) / 100
     opacity: UI.SLIDER_OPACITY
     anchors {
         bottom: parent.bottom
         bottomMargin: UI.SLIDER_BOTTOM_MARGIN
         left: parent.left
         leftMargin: UI.SLIDER_SIDE_MARGIN
         right: parent.right
         rightMargin: UI.SLIDER_SIDE_MARGIN
     }
 }

 Binding { target: image; property: "scale"; value: slider.value; when: slider.visible }

If the zoom level is such that the image is larger than the screen size, scroll decorators are visible. ScrollDecorator indicates the part of the image that is shown while flicking. The flickableItem property of ScrollDecorator is bound to the id flickable, which identifies the Flickable element that wraps the image.

 ScrollDecorator {
     flickableItem: flickable
 }

The following image depicts the LargeImagePage component in action, the scroll decorators are visible as the user has zoomed in on the image.

Details Page

The Details page, implemented in the file DetailsPage.qml, shows detailed information of the image.