← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app

 

Victor Thompson has proposed merging lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app.

Commit message:
Allow adding the current location as a duplicate to the Locations List.

Requested reviews:
  Ubuntu Weather Developers (ubuntu-weather-dev)

For more details, see:
https://code.launchpad.net/~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location/+merge/263208

Allow adding the current location as a duplicate to the Locations List.
-- 
Your team Ubuntu Weather Developers is requested to review the proposed merge of lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app.
=== added file '.bzrignore'
--- .bzrignore	1970-01-01 00:00:00 +0000
+++ .bzrignore	2015-06-28 23:49:12 +0000
@@ -0,0 +1,19 @@
+*.user*
+debian/files
+debian/tmp
+debian/ubuntu-weather-app*
+debian/app-template/
+debian/*.debhelper.log
+debian/*.substvars
+.build
+Makefile
+CMakeCache.txt
+CMakeFiles/
+*.cmake
+*.gmo
+*.mo
+*.desktop
+*.desktop.in
+*.desktop.in.in.h
+.excludes
+ubuntu-weather-app.json

=== renamed file '.bzrignore' => '.bzrignore.moved'
=== added file 'CMakeLists.txt'
--- CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,101 @@
+project(com.ubuntu.weather)
+cmake_minimum_required(VERSION 2.8.9)
+
+find_program(INTLTOOL_MERGE intltool-merge)
+if(NOT INTLTOOL_MERGE)
+    message(FATAL_ERROR "Could not find intltool-merge, please install the intltool package")
+endif()
+find_program(INTLTOOL_EXTRACT intltool-extract)
+if(NOT INTLTOOL_EXTRACT)
+    message(FATAL_ERROR "Could not find intltool-extract, please install the intltool package")
+endif()
+
+set (UBUNTU_MANIFEST_PATH "manifest.json.in" CACHE INTERNAL "Relative path to the manifest file")
+set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
+
+find_package(Qt5Core REQUIRED)
+find_package(Qt5Qml REQUIRED)
+find_package(Qt5Quick REQUIRED)
+
+# Automatically create moc files
+set(CMAKE_AUTOMOC ON)
+
+option(INSTALL_TESTS "Install the tests on make install" on)
+option(CLICK_MODE "Build as a click package" on)
+
+# Tests
+enable_testing()
+
+# Standard install paths
+include(GNUInstallDirs)
+
+set(APP_NAME weather)
+set(APP_HARDCODE ubuntu-weather-app)
+set(MAIN_QML ${APP_HARDCODE}.qml)
+set(DESKTOP_FILE "${APP_HARDCODE}.desktop")
+set(ICON weather-app@xxxxxx)
+set(AUTOPILOT_DIR ubuntu_weather_app)
+
+# Set install paths
+if(CLICK_MODE)
+  set(CMAKE_INSTALL_PREFIX "/")
+  set(UBUNTU-WEATHER_APP_DIR "${CMAKE_INSTALL_DATADIR}/qml")
+
+  set(QT_IMPORTS_DIR "${CMAKE_INSTALL_LIBDIR}")
+  set(EXEC "qmlscene $@ ${UBUNTU-WEATHER_APP_DIR}/${MAIN_QML}")
+  set(MODULE_PATH ${QT_IMPORTS_DIR})
+  if(NOT BZR_REVNO)
+    execute_process(
+      COMMAND bzr revno
+              OUTPUT_VARIABLE BZR_REVNO
+              WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+              OUTPUT_STRIP_TRAILING_WHITESPACE
+      )
+  endif(NOT BZR_REVNO)
+  if(NOT BZR_SOURCE)
+    set(BZR_SOURCE "lp:${APP_HARDCODE}/reboot")
+    message("-- Setting BZR_SOURCE to ${BZR_SOURCE}")
+  endif(NOT BZR_SOURCE)
+else(CLICK_MODE)
+  set(UBUNTU-WEATHER_APP_DIR "${CMAKE_INSTALL_DATADIR}/ubuntu-weather-app")
+  execute_process(
+    COMMAND qmake -query QT_INSTALL_QML
+            OUTPUT_VARIABLE QT_IMPORTS_DIR
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
+  set(MODULE_PATH ${QT_IMPORTS_DIR}/WeatherApp)
+endif(CLICK_MODE)
+
+if(${CLICK_MODE})
+  message("-- Configuring manifest.json")
+
+  configure_file(${UBUNTU_MANIFEST_PATH} ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)
+  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX})
+  install(FILES "${APP_HARDCODE}.apparmor" DESTINATION ${CMAKE_INSTALL_PREFIX})
+else(CLICK_MODE)
+  set(EXEC "qmlscene $@ -I ${MODULE_PATH}  ${CMAKE_INSTALL_PREFIX}/${UBUNTU-WEATHER_APP_DIR}/${MAIN_QML}")
+endif()
+
+
+file(GLOB_RECURSE I18N_SRC_FILES
+    RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po
+    *.qml *.js)
+list(APPEND I18N_SRC_FILES ${DESKTOP_FILE}.in.in.h)
+list(SORT I18N_SRC_FILES)
+
+configure_file(${DESKTOP_FILE}.in.in ${DESKTOP_FILE}.in)
+
+add_custom_target(${DESKTOP_FILE} ALL
+    COMMENT "Merging translations into ${DESKTOP_FILE}..."
+    COMMAND LC_ALL=C ${INTLTOOL_MERGE} -d -u ${CMAKE_SOURCE_DIR}/po ${DESKTOP_FILE}.in ${DESKTOP_FILE}
+)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}
+        DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
+
+add_subdirectory(app)
+add_subdirectory(po)
+add_subdirectory(tests)
+
+# TODO: Add custom target for autopilot and run.
+

=== renamed file 'CMakeLists.txt' => 'CMakeLists.txt.moved'
=== added file 'COPYING'
--- COPYING	1970-01-01 00:00:00 +0000
+++ COPYING	2015-06-28 23:49:12 +0000
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

=== renamed file 'COPYING' => 'COPYING.moved'
=== added file 'README'
--- README	1970-01-01 00:00:00 +0000
+++ README	2015-06-28 23:49:12 +0000
@@ -0,0 +1,11 @@
+Weather app for Ubuntu devices
+
+To contribute:
+    https://wiki.ubuntu.com/Touch/CoreApps/Weather
+    https://wiki.ubuntu.com/Touch/CoreApps/DevelopmentGuide
+
+The following essential packages are required to develop this app:
+- ubuntu-sdk (see http://developer.ubuntu.com/start)
+- intltool - run the `sudo apt-get install intltool` command for installation
+
+See the debian/control file for an up-to-date list of dependencies

=== renamed file 'README' => 'README.moved'
=== added file 'README.translations'
--- README.translations	1970-01-01 00:00:00 +0000
+++ README.translations	2015-06-28 23:49:12 +0000
@@ -0,0 +1,37 @@
+# Updating translations
+
+Translations for the Weather app happen in [Launchpad Translations][] and
+are automatically committed daily on the trunk branch in the po/ folder.
+
+They are then built and installed as part of the package build, so that
+developers don't really need to worry about them.
+
+However, there is one task that needs to be taken care of: exposing new
+translatable messages to translators. So whenever you add new translatable
+messages in the code, make sure to follow these steps:
+
+ 1. Run click-buddy to build the project and generate a new .pot file:
+    `click-buddy --dir .`
+ 2. Commit the generated .pot file:
+    `bzr commit -m"Updated translation template"`
+ 3. Push the branch and send a merge proposal as usual
+
+And that's it, once the branch lands Launchpad should take care of all the rest!
+
+# Behind the scenes
+
+Behind the scenes, whenever the po/*.pot file (also known as translations template)
+is committed to trunk Launchpad reads it and updates the translatable strings
+exposed in the web UI. This will enable translators to work on the new strings.
+The translations template contains all translatable strings that have been
+extracted from the source code files.
+
+Launchpad will then store translations in its database and will commit them daily
+in the form of textual po/*.po files to trunk. The PO files are also usually
+referred to as the translations files. You'll find a translation file for each
+language the app has got at least a translated message available for.
+
+Translations for core apps follow the standard [gettext format].
+
+ [Launchpad Translations]: https://translations.launchpad.net/ubuntu-weather-app
+ [gettext format]: https://www.gnu.org/software/gettext/

=== renamed file 'README.translations' => 'README.translations.moved'
=== added directory 'app'
=== added file 'app/CMakeLists.txt'
--- app/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,18 @@
+if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+  file(GLOB QML_JS_FILES *.qml *.js)
+  add_custom_target(ubuntu-weather-app_QMlFiles ALL SOURCES ${QML_JS_FILES})
+endif(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+
+
+if(CLICK_MODE)
+  set(ICON ${ICON})
+  install(FILES ${ICON} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(CLICK_MODE)
+
+install(FILES ${MAIN_QML} DESTINATION ${UBUNTU-WEATHER_APP_DIR})
+
+add_subdirectory(components)
+add_subdirectory(data)
+add_subdirectory(graphics)
+add_subdirectory(ui)
+

=== added directory 'app/components'
=== added file 'app/components/CMakeLists.txt'
--- app/components/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/components/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,7 @@
+add_subdirectory(ListItemActions)
+
+file(GLOB COMPONENTS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_components_QMlFiles ALL SOURCES ${COMPONENTS_QML_JS_FILES})
+
+install(FILES ${COMPONENTS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/components)

=== added file 'app/components/CurrentLocation.qml'
--- app/components/CurrentLocation.qml	1970-01-01 00:00:00 +0000
+++ app/components/CurrentLocation.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtLocation 5.3
+import QtPositioning 5.2
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../data/WeatherApi.js" as WeatherApi
+
+
+Item {
+    id: currentLocation
+
+    property string string: "Undefined"
+
+    function searchForLocation(lat, lon) {
+        WeatherApi.sendRequest({
+                                   action: "searchByPoint",
+                                   params: {
+                                       coords: {
+                                           lat: lat,
+                                           lon: lon
+                                       }
+                                   }
+                               }, searchResponseHandler)
+    }
+
+    function searchResponseHandler(msgObject) {
+        if (!msgObject.error) {
+            console.log("Loc to add:", JSON.stringify(msgObject.result.locations[0]))
+            storage.updateCurrentLocation(msgObject.result.locations[0])
+        }
+    }
+
+
+    PositionSource {
+        id: currentPosition
+        updateInterval: 1000
+        active: true
+
+        onPositionChanged: {
+            var coord = currentPosition.position.coordinate
+            if (coord.isValid) {
+                geocodeModel.query = coord
+                geocodeModel.update()
+            }
+        }
+    }
+
+    Plugin {
+        id: osmPlugin
+        name: "osm"
+    }
+
+    GeocodeModel {
+        id: geocodeModel
+        autoUpdate: false
+        plugin: osmPlugin
+
+        onCountChanged: {
+            // Update the currentLocation if one is found and it does not match the stored location
+            if (count > 0 && currentLocation.string !== geocodeModel.get(0).address.city) {
+                var loc = geocodeModel.get(0)
+                currentLocation.string = loc.address.city
+                searchForLocation(loc.coordinate.latitude, loc.coordinate.longitude)
+            }
+        }
+    }
+}

=== added file 'app/components/DayDelegate.qml'
--- app/components/DayDelegate.qml	1970-01-01 00:00:00 +0000
+++ app/components/DayDelegate.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListItem {
+    id: dayDelegate
+    height: collapsedHeight
+
+    property int collapsedHeight: units.gu(8)
+    property int expandedHeight: collapsedHeight + units.gu(4) + extraInfoColumn.height
+
+    property alias day: dayLabel.text
+    property alias image: weatherImage.name
+    property alias high: highLabel.text
+    property alias low: lowLabel.text
+
+    property alias chanceOfRain: chanceOfRainForecast.chance
+    property alias humidity: humidityForecast.value
+    property alias pollen: pollenForecast.value
+    property alias sunrise: sunriseForecast.value
+    property alias sunset: sunsetForecast.value
+    property alias wind: windForecast.value
+    property alias uvIndex: uvIndexForecast.value
+
+    state: "normal"
+    states: [
+        State {
+            name: "normal"
+            PropertyChanges {
+                target: dayDelegate
+                height: collapsedHeight
+            }
+        },
+        State {
+            name: "expanded"
+            PropertyChanges {
+                target: dayDelegate
+                height: expandedHeight
+            }
+            PropertyChanges {
+                target: expandedInfo
+                opacity: 1
+            }
+        }
+    ]
+
+    transitions: [
+        Transition {
+            from: "normal"
+            to: "expanded"
+            SequentialAnimation {
+                NumberAnimation {
+                    easing.type: Easing.InOutQuad
+                    properties: "height"
+                }
+                NumberAnimation {
+                    easing.type: Easing.InOutQuad
+                    properties: "opacity"
+                }
+            }
+        },
+        Transition {
+            from: "expanded"
+            to: "normal"
+            SequentialAnimation {
+                NumberAnimation {
+                    easing.type: Easing.InOutQuad
+                    properties: "opacity"
+                }
+                NumberAnimation {
+                    easing.type: Easing.InOutQuad
+                    properties: "height"
+                }
+            }
+        }
+    ]
+
+    onClicked: {
+        state = state === "normal" ? "expanded" : "normal"
+        locationPages.collapseOtherDelegates(index)
+    }
+
+    Item {
+        id: mainInfo
+
+        height: collapsedHeight
+        anchors {
+            left: parent.left
+            right: parent.right
+            margins: units.gu(2)
+        }
+
+        Label {
+            id: dayLabel
+            anchors {
+                left: parent.left
+                right: weatherImage.left
+                rightMargin: units.gu(1)
+                top: parent.top
+                topMargin: (collapsedHeight - dayLabel.height) / 2
+            }
+            elide: Text.ElideRight
+            font.weight: Font.Light
+            fontSize: "medium"
+        }
+
+        Icon {
+            id: weatherImage
+            anchors {
+                horizontalCenter: parent.horizontalCenter
+                verticalCenter: dayLabel.verticalCenter
+            }
+            height: units.gu(3)
+            width: units.gu(3)
+        }
+
+        Label {
+            id: lowLabel
+            anchors {
+                left: weatherImage.right
+                right: highLabel.left
+                rightMargin: units.gu(1)
+                verticalCenter: dayLabel.verticalCenter
+            }
+            elide: Text.ElideRight
+            font.pixelSize: units.gu(2)
+            font.weight: Font.Light
+            fontSize: "medium"
+            height: units.gu(2)
+            horizontalAlignment: Text.AlignRight
+            verticalAlignment: Text.AlignTop  // AlignTop appears to align bottom?
+        }
+
+        Label {
+            id: highLabel
+            anchors {
+                bottom: lowLabel.bottom
+                right: parent.right
+            }
+            color: UbuntuColors.orange
+            elide: Text.ElideRight
+            font.pixelSize: units.gu(3)
+            font.weight: Font.Normal
+            height: units.gu(3)
+            verticalAlignment: Text.AlignTop  // AlignTop appears to align bottom?
+        }
+    }
+
+    Item {
+        id: expandedInfo
+        anchors {
+            bottom: parent.bottom
+            left: parent.left
+            right: parent.right
+            top: mainInfo.bottom
+            bottomMargin: units.gu(2)
+        }
+        opacity: 0
+        visible: opacity !== 0
+
+        Column {
+            id: extraInfoColumn
+            anchors {
+                centerIn: parent
+            }
+            spacing: units.gu(2)
+
+            // FIXME: extended-infomation_* aren't actually on device
+
+            // Overview text
+            Label {
+                id: chanceOfRainForecast
+                color: UbuntuColors.coolGrey
+                fontSize: "x-large"
+                horizontalAlignment: Text.AlignHCenter
+                text: i18n.tr("Chance of rain")
+                width: parent.width
+                visible: false  // FIXME: add overview text eg "Chance of flurries"
+
+                property int chance: 0
+            }
+
+            ForecastDetailsDelegate {
+                id: windForecast
+                forecast: i18n.tr("Winds")
+                imageSource: "../graphics/extended-information_wind.svg"
+            }
+
+            ForecastDetailsDelegate {
+                id: uvIndexForecast
+                imageSource: "../graphics/extended-information_uv-level.svg"
+                forecast: i18n.tr("UV Index")
+            }
+
+            ForecastDetailsDelegate {
+                id: pollenForecast
+                forecast: i18n.tr("Pollen")
+                // FIXME: need icon
+            }
+
+            ForecastDetailsDelegate {
+                id: humidityForecast
+                forecast: i18n.tr("Humidity")
+                imageSource: "../graphics/extended-information_humidity.svg"
+            }
+
+            ForecastDetailsDelegate {
+                id: sunriseForecast
+                forecast: i18n.tr("Sunrise")
+                // FIXME: need icon
+            }
+
+            ForecastDetailsDelegate {
+                id: sunsetForecast
+                forecast: i18n.tr("Sunset")
+                // FIXME: need icon
+            }
+        }
+    }
+
+    Component.onCompleted: {
+        locationPages.collapseOtherDelegates.connect(function(otherIndex) {
+            if (dayDelegate && typeof index !== "undefined" && otherIndex !== index) {
+                state = "normal"
+            }
+        });
+    }
+}

=== added file 'app/components/ExpandableListItem.qml'
--- app/components/ExpandableListItem.qml	1970-01-01 00:00:00 +0000
+++ app/components/ExpandableListItem.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 1.0 as ListItem
+
+/*
+ Component which extends the SDK Expandable list item and provides a easy
+ to use component where the title, subtitle and listview can be displayed. It
+ matches the design specification provided for clock.
+*/
+
+ListItem.Expandable {
+    id: expandableListItem
+
+    property ListModel model
+    property Component delegate
+    property alias text: expandableHeader.text
+    property alias subText: expandableHeader.subText
+    property alias listViewHeight: expandableList.height
+
+    anchors {
+        left: parent.left
+        right: parent.right
+        margins: units.gu(-2)
+    }
+
+    collapseOnClick: true
+    expandedHeight: contentColumn.height + units.gu(1)
+
+    Column {
+        id: contentColumn
+
+        anchors {
+            left: parent.left
+            right: parent.right
+        }
+
+        Item {
+            width: parent.width
+            height: expandableListItem.collapsedHeight
+
+            ListItem.Subtitled {
+                id: expandableHeader
+                onClicked: expandableListItem.expanded = true
+
+                Icon {
+                    id: arrow
+
+                    width: units.gu(2)
+                    height: width
+                    anchors.right: parent.right
+                    anchors.verticalCenter: parent.verticalCenter
+
+                    name: "go-down"
+                    color: "Grey"
+                    rotation: expandableListItem.expanded ? 180 : 0
+
+                    Behavior on rotation {
+                        UbuntuNumberAnimation {}
+                    }
+                }
+            }
+        }
+
+        ListView {
+            id: expandableList
+            width: parent.width
+            interactive: false
+            model: expandableListItem.model
+            delegate: expandableListItem.delegate
+        }
+    }
+}

=== added file 'app/components/FastScroll.js'
--- app/components/FastScroll.js	1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.js	2015-06-28 23:49:12 +0000
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.js - this is just SectionScroller.js with a fix for
+// section.criteria == ViewSection.FirstCharacter
+var sectionData = [];
+var _sections = [];
+
+function initialize(list) {
+    initSectionData(list);
+}
+
+function contains(name) {
+    return (_sections.indexOf(name) > -1)
+}
+
+function initSectionData(list) {
+    if (!list || !list.model) return;
+    sectionData = [];
+    _sections = [];
+    var current = "",
+        prop = list.section.property,
+        sectionText;
+
+    if (list.section.criteria === ViewSection.FullString) {
+        for (var i = 0, count = list.model.count; i < count; i++) {
+            sectionText = list.getSectionText(i)
+            if (sectionText !== current) {
+                current = sectionText;
+                _sections.push(current);
+                sectionData.push({ index: i, header: current });
+            }
+        }
+    } else if (list.section.criteria === ViewSection.FirstCharacter) {
+        for (var i = 0, count = list.model.count; i < count; i++) {
+            sectionText = list.getSectionText(i).substring(0, 1)
+            if (sectionText !== current) {
+                current = sectionText
+                _sections.push(sectionText);
+                sectionData.push({ index: i, header: current });
+            }
+        }
+    }
+}
+
+function getSectionPositionString(name) {
+    var val = _sections.indexOf(name);
+    return val === 0 ? "first" :
+           val === _sections.length - 1 ? "last" : false;
+}
+
+function getAt(pos) {
+    return _sections[pos] ? _sections[pos] : "";
+}
+
+function getRelativeSections(current) {
+    var val = _sections.indexOf(current),
+        sect = [],
+        sl = _sections.length;
+
+    val = val < 1 ? 1 : val >= sl-1 ? sl-2 : val;
+    sect = [getAt(val - 1), getAt(val), getAt(val + 1)];
+
+    return sect;
+}
+
+function getClosestSection(pos, down) {
+    var tmp = (_sections.length) * pos;
+    var val = Math.ceil(tmp) // TODO: better algorithm
+    val = val < 2 ? 1 : val;
+    return _sections[val-1];
+}
+
+function getNextSection(current) {
+    var val = _sections.indexOf(current);
+    return (val > -1 ? _sections[(val < _sections.length - 1 ? val + 1 : val)] : _sections[0]) || "";
+}
+
+function getPreviousSection(current) {
+    var val = _sections.indexOf(current);
+    return (val > -1 ? _sections[(val > 0 ? val - 1 : val)] : _sections[0]) || "";
+}
+
+function getIndexFor(sectionName) {
+    var data = sectionData[_sections.indexOf(sectionName)]
+    if (data) {
+        var val = data.index;
+        return val === 0 || val > 0 ? val : -1;
+    } else {
+        return -1
+    }
+}

=== added file 'app/components/FastScroll.qml'
--- app/components/FastScroll.qml	1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014, 2015 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+**     the names of its contributors may be used to endorse or promote
+**     products derived from this software without specific prior written
+**     permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.qml
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "FastScroll.js" as Sections
+
+Item {
+    id: root
+
+    property ListView listView
+    property int pinSize: units.gu(2)
+
+    readonly property var letters: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
+    readonly property alias fastScrolling: internal.fastScrolling
+    readonly property bool showing: (rail.opacity !== 0.0)
+    readonly property double minimumHeight: rail.height
+
+    width: units.gu(7)
+    height: rail.height
+
+    onListViewChanged: {
+        if (listView && listView.model) {
+            internal.initDirtyObserver();
+        } else if (listView) {
+            listView.modelChanged.connect(function() {
+                if (listView.model) {
+                    internal.initDirtyObserver();
+                }
+            });
+        }
+    }
+
+    Connections {
+        target: listView
+        onCurrentIndexChanged: {
+            if (currentIndex != -1) {
+                rail.opacity = 0.0
+            }
+        }
+    }
+
+    Rectangle {
+        id: magnified
+
+        color: Theme.palette.normal.overlay
+        radius: height * 0.3
+        height: pinSize * 2
+        width: height
+        opacity: internal.fastScrolling && root.enabled ? 1.0 : 0.0
+        x: -cursor.width - units.gu(3)
+        y: {
+            if (internal.currentItem) {
+                var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+                return (itemCenterY - (magnified.height / 2))
+            } else {
+                return 0
+            }
+        }
+
+        Label {
+            anchors.fill: parent
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            text: internal.desireSection
+            fontSize: "small"
+        }
+
+        Behavior on opacity {
+            UbuntuNumberAnimation {}
+        }
+    }
+
+    Rectangle {
+        id: cursor
+
+        property bool showLabel: false
+        property string currentSectionName: ""
+
+        radius: pinSize * 0.3
+        height: pinSize
+        width: height
+        color: Theme.palette.normal.foreground
+        opacity: rail.opacity
+        x: rail.x
+        y: {
+            if (internal.currentItem) {
+                var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+                return (itemCenterY - (cursor.height / 2))
+            } else {
+                return 0
+            }
+        }
+        Behavior on y {
+            enabled: !internal.fastScrolling
+            UbuntuNumberAnimation { }
+        }
+    }
+
+    Column {
+        id: rail
+
+        property bool isVisible: root.enabled &&
+                                 (listView.flicking || dragArea.pressed) &&
+                                 (listView.currentIndex == -1)
+        anchors {
+            right: parent.right
+            rightMargin: units.gu(2)
+            left: parent.left
+            leftMargin: units.gu(2)
+            top: parent.top
+        }
+        height: childrenRect.height
+        opacity: 0.0
+        onIsVisibleChanged: {
+            if (isVisible) {
+                rail.opacity = 1.0
+                hideTimer.stop()
+            } else if (!root.enabled) {
+                rail.opacity = 0.0
+            } else {
+                hideTimer.restart()
+            }
+        }
+
+        Behavior on opacity {
+            UbuntuNumberAnimation { }
+        }
+
+        Repeater {
+            id: sectionsRepeater
+
+            model: root.letters
+            Label {
+                id: lbl
+
+                anchors.left: parent.left
+                height: pinSize
+                width: pinSize
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+                text: modelData
+                fontSize: "x-small"
+                color: cursor.y === y ? "white" : Theme.palette.selected.backgroundText
+                opacity: !internal.modelDirty && Sections.contains(text) ? 1.0 : 0.5
+            }
+        }
+
+        Timer {
+            id: hideTimer
+
+            running: false
+            interval: 2000
+            onTriggered: rail.opacity = 0.0
+        }
+    }
+
+    MouseArea {
+        id: dragArea
+
+        anchors {
+            left: parent.left
+            right: parent.right
+        }
+        y: rail.y
+        height: rail.height
+        visible: rail.opacity == 1.0
+
+        preventStealing: true
+        onPressed: {
+            internal.adjustContentPosition(mouseY)
+            dragginTimer.start()
+        }
+
+        onReleased: {
+            dragginTimer.stop()
+            internal.desireSection = ""
+            internal.fastScrolling = false
+        }
+
+        onPositionChanged: internal.adjustContentPosition(mouseY)
+
+        Timer {
+            id: dragginTimer
+
+            running: false
+            interval: 150
+            onTriggered: {
+                internal.fastScrolling = true
+            }
+        }
+    }
+
+    Timer {
+        id: dirtyTimer
+        interval: 500
+        running: false
+        onTriggered: {
+            Sections.initSectionData(listView);
+            internal.modelDirty = false;
+        }
+    }
+
+    Timer {
+        id: timerScroll
+
+        running: false
+        interval: 10
+        onTriggered: {
+            if (internal.desireSection != internal.currentSection) {
+                var idx = Sections.getIndexFor(internal.desireSection)
+                if (idx !== -1) {
+                    listView.cancelFlick()
+                    listView.positionViewAtIndex(idx, ListView.Beginning)
+                }
+            }
+        }
+    }
+
+    QtObject {
+        id: internal
+
+        property string currentSection: listView.currentSection
+        property string desireSection: ""
+        property string targetSection: fastScrolling ? desireSection : currentSection
+        property int oldY: 0
+        property bool modelDirty: false
+        property bool down: true
+        property bool fastScrolling: false
+        property var currentItem: null
+
+        onTargetSectionChanged: moveIndicator(targetSection)
+
+        function initDirtyObserver() {
+            Sections.initialize(listView);
+            function dirtyObserver() {
+                if (!internal.modelDirty) {
+                    internal.modelDirty = true;
+                    dirtyTimer.running = true;
+                }
+            }
+
+            if (listView.model.countChanged)
+                listView.model.countChanged.connect(dirtyObserver);
+
+            if (listView.model.itemsChanged)
+                listView.model.itemsChanged.connect(dirtyObserver);
+
+            if (listView.model.itemsInserted)
+                listView.model.itemsInserted.connect(dirtyObserver);
+
+            if (listView.model.itemsMoved)
+                listView.model.itemsMoved.connect(dirtyObserver);
+
+            if (listView.model.itemsRemoved)
+                listView.model.itemsRemoved.connect(dirtyObserver);
+        }
+
+        function adjustContentPosition(mouseY) {
+            var child = rail.childAt(rail.width / 2, mouseY)
+            if (!child || child.text === "") {
+                return
+            }
+            var section = child.text
+            if (internal.desireSection !== section) {
+                internal.desireSection = section
+                moveIndicator(section)
+                if (dragArea.pressed) {
+                    timerScroll.restart()
+                }
+            }
+        }
+
+        function moveIndicator(section)
+        {
+            var index = root.letters.indexOf(section)
+            if (index != -1) {
+                currentItem = sectionsRepeater.itemAt(index)
+            }
+        }
+    }
+}

=== added file 'app/components/ForecastDetailsDelegate.qml'
--- app/components/ForecastDetailsDelegate.qml	1970-01-01 00:00:00 +0000
+++ app/components/ForecastDetailsDelegate.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Row {
+    height: icon.height
+    spacing: units.gu(2)
+    visible: value !== ""
+
+    property alias imageSource: icon.source
+    property alias forecast: forecastLabel.text
+    property alias value: forecastValue.text
+    
+    Icon {
+        id: icon
+        color: "#000"
+        height: units.gu(2)
+        width: height
+    }
+    
+    Label {
+        id: forecastLabel
+        elide: Text.ElideRight
+        width: units.gu(8)
+    }
+    
+    Label {
+        id: forecastValue
+        width: units.gu(10)
+    }
+}

=== added file 'app/components/HeaderRow.qml'
--- app/components/HeaderRow.qml	1970-01-01 00:00:00 +0000
+++ app/components/HeaderRow.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.2
+
+RowLayout {
+    id: headerRow
+
+    property alias locationName: locationNameLabel.text
+
+    width: parent.width
+
+    Label {
+        id: locationNameLabel
+        color: UbuntuColors.darkGrey
+        elide: Text.ElideRight
+        font.weight: Font.Normal
+        fontSize: "large"
+        height: settingsButton.height
+        Layout.fillWidth: true
+        verticalAlignment: Text.AlignVCenter
+    }
+
+    AbstractButton {
+        id: settingsButton
+        height: width
+        width: units.gu(4)
+
+        onClicked: mainPageStack.push(Qt.resolvedUrl("../ui/SettingsPage.qml"))
+
+        Rectangle {
+            anchors.fill: parent
+            color: Theme.palette.selected.background
+            visible: parent.pressed
+        }
+
+        Icon {
+            anchors.centerIn: parent
+            color: UbuntuColors.darkGrey
+            height: width
+            name: "settings"
+            width: units.gu(2.5)
+        }
+    }
+}

=== added file 'app/components/HomeGraphic.qml'
--- app/components/HomeGraphic.qml	1970-01-01 00:00:00 +0000
+++ app/components/HomeGraphic.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Item {
+    height: units.gu(32)
+    width: parent.width
+
+    property alias icon: iconImage.source
+
+    // TODO: will be on 'rails' (horizontal listview?) to reveal hourly forecast
+    Image {
+        id: iconImage
+        anchors {
+            centerIn: parent
+        }
+        fillMode: Image.PreserveAspectFit
+        height: parent.height
+        width: parent.width
+    }
+}
+

=== added file 'app/components/HomeHourly.qml'
--- app/components/HomeHourly.qml	1970-01-01 00:00:00 +0000
+++ app/components/HomeHourly.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListView {
+    id: homeHourly
+
+    clip:true
+    height: parent ? parent.height : undefined
+    width: parent ? parent.width : undefined
+    model: forecasts.length
+    orientation: ListView.Horizontal
+
+    property string currentDate: Qt.formatTime(new Date())
+
+    onVisibleChanged: {
+        if(visible) {
+            ListView.model = forecasts.length
+        }
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: {
+            homeGraphic.visible = true
+        }
+    }
+
+    delegate: Item {
+        id: delegate
+
+        property var hourData: forecasts[index]
+
+        height: parent.height
+        width: childrenRect.width
+
+        Column {
+            id: hourColumn
+
+            anchors.verticalCenter: parent.verticalCenter
+            height: childrenRect.height
+            width: units.gu(10)
+
+            Label {
+                anchors.horizontalCenter: parent.horizontalCenter
+                fontSize: "small"
+                font.weight: Font.Light
+                text: currentDate.search(Qt.locale().amText) !== -1 || currentDate.search(Qt.locale().pmText) !== -1 ? "%1 %2".arg(formatTimestamp(hourData.date, 'ddd')).arg(formatTime(hourData.date, 'hap')) : "%1 %2".arg(formatTimestamp(hourData.date, 'ddd')).arg(formatTime(hourData.date, 'h:mm'))
+            }
+
+            Item {
+                anchors.horizontalCenter: parent.horizontalCenter
+                height: units.gu(7)
+                width: units.gu(7)
+
+                Icon {
+                    anchors {
+                        fill: parent
+                        margins: units.gu(0.5)
+                    }
+                    color: UbuntuColors.orange
+                    name: (hourData.icon !== undefined && iconMap[hourData.icon] !== undefined) ? iconMap[hourData.icon] : ""
+                }
+            }
+
+            Label {
+                anchors.horizontalCenter: parent.horizontalCenter
+                font.pixelSize: units.gu(3)
+                font.weight: Font.Light
+                text: Math.round(hourData[tempUnits].temp).toString()+settings.tempScale
+            }
+        }
+
+        Rectangle {
+            anchors.verticalCenter: parent.verticalCenter
+            color: UbuntuColors.darkGrey
+            height: hourColumn.height
+            opacity: 0.2
+            visible: index > 0
+            width: units.gu(0.1)
+        }
+    }
+}
+

=== added file 'app/components/HomeTempInfo.qml'
--- app/components/HomeTempInfo.qml	1970-01-01 00:00:00 +0000
+++ app/components/HomeTempInfo.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+Column {
+    anchors {
+        left: parent.left
+        right: parent.right
+    }
+    spacing: units.gu(1)
+
+    property alias description: descriptionLabel.text
+    property alias high: highLabel.text
+    property alias low: lowLabel.text
+    property alias now: nowLabel.text
+
+    Label {
+        font.weight: Font.Light
+        fontSize: "small"
+        text: i18n.tr("Today")
+    }
+
+    Label {
+        id: descriptionLabel
+        font.weight: Font.Normal
+        fontSize: "large"
+    }
+
+    Row {
+        spacing: units.gu(2)
+
+        Label {
+            id: nowLabel
+            color: UbuntuColors.orange
+            font.pixelSize: units.gu(8)
+            font.weight: Font.Light
+            height: units.gu(8)
+            verticalAlignment: Text.AlignBottom  // AlignBottom seems to put it at the top?
+        }
+
+        Column {
+            Label {
+                id: lowLabel
+                font.weight: Font.Light
+                fontSize: "medium"
+            }
+
+            Label {
+                id: highLabel
+                font.weight: Font.Light
+                fontSize: "medium"
+            }
+        }
+    }
+}

=== added directory 'app/components/ListItemActions'
=== added file 'app/components/ListItemActions/CMakeLists.txt'
--- app/components/ListItemActions/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB LISTITEMACTIONS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_listitemactions_QMlFiles ALL SOURCES ${LISTITEMACTIONS_QML_JS_FILES})
+
+install(FILES ${LISTITEMACTIONS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/components/ListItemActions)

=== added file 'app/components/ListItemActions/CheckBox.qml'
--- app/components/ListItemActions/CheckBox.qml	1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/CheckBox.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012-2014, 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+CheckBox {
+    checked: root.selected
+    width: implicitWidth
+    // disable item mouse area to avoid conflicts with parent mouse area
+    __mouseArea.enabled: false
+}

=== added file 'app/components/ListItemActions/Remove.qml'
--- app/components/ListItemActions/Remove.qml	1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/Remove.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014, 2015 Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *                    Daniel Holm <d.holmen@xxxxxxxxx>
+ *                    Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Action {
+    id: removeAction
+    iconName: "delete"
+    objectName: "swipeDeleteAction"
+    text: i18n.tr("Remove")
+}

=== added file 'app/components/ListItemReorderComponent.qml'
--- app/components/ListItemReorderComponent.qml	1970-01-01 00:00:00 +0000
+++ app/components/ListItemReorderComponent.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+Item {
+    id: actionReorder
+    width: units.gu(4)
+
+    Icon {
+        anchors {
+            horizontalCenter: parent.horizontalCenter
+            verticalCenter: parent.verticalCenter
+        }
+        name: "navigation-menu"  // TODO: use proper image
+        height: width
+        width: units.gu(3)
+    }
+
+    MouseArea {
+        id: actionReorderMouseArea
+        anchors {
+            fill: parent
+        }
+        property int startY: 0
+        property int startContentY: 0
+
+        onPressed: {
+            root.parent.parent.interactive = false;  // stop scrolling of listview
+            startY = root.y;
+            startContentY = root.parent.parent.contentY;
+            root.z += 10;  // force ontop of other elements
+
+            console.debug("Reorder listitem pressed", root.y)
+        }
+        onMouseYChanged: root.y += mouse.y - (root.height / 2);
+        onReleased: {
+            console.debug("Reorder diff by position", getDiff());
+
+            var diff = getDiff();
+
+            // Remove the height of the actual item if moved down
+            if (diff > 0) {
+                diff -= 1;
+            }
+
+            root.parent.parent.interactive = true;  // reenable scrolling
+
+            if (diff === 0) {
+                // Nothing has changed so reset the item
+                // z index is restored after animation
+                resetListItemYAnimation.start();
+            }
+            else {
+                var newIndex = index + diff;
+
+                if (newIndex < 0) {
+                    newIndex = 0;
+                }
+                else if (newIndex > root.parent.parent.count - 1) {
+                    newIndex = root.parent.parent.count - 1;
+                }
+
+                root.z -= 10;  // restore z index
+                reorder(index, newIndex)
+            }
+        }
+
+        function getDiff() {
+            // Get the amount of items that have been passed over (by centre)
+            return Math.round((((root.y - startY) + (root.parent.parent.contentY - startContentY)) / root.height) + 0.5);
+        }
+    }
+
+    SequentialAnimation {
+        id: resetListItemYAnimation
+        UbuntuNumberAnimation {
+            target: root;
+            property: "y";
+            to: actionReorderMouseArea.startY
+        }
+        ScriptAction {
+            script: {
+                root.z -= 10;  // restore z index
+            }
+        }
+    }
+}

=== added file 'app/components/ListItemWithActions.qml'
--- app/components/ListItemWithActions.qml	1970-01-01 00:00:00 +0000
+++ app/components/ListItemWithActions.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2012-2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 1.0 as ListItem
+
+
+Item {
+    id: root
+    width: parent.width
+
+    property Action leftSideAction: null
+    property list<Action> rightSideActions
+    property double defaultHeight: units.gu(8)
+    property bool locked: false
+    property Action activeAction: null
+    property var activeItem: null
+    property bool triggerActionOnMouseRelease: false
+    property color color: Theme.palette.normal.background
+    property color selectedColor: "#E6E6E6"
+    property bool selected: false
+    property bool selectionMode: false
+    property alias internalAnchors: mainContents.anchors
+    default property alias contents: mainContents.children
+
+    readonly property double actionWidth: units.gu(4)
+    readonly property double leftActionWidth: units.gu(10)
+    readonly property double actionThreshold: actionWidth * 0.4
+    readonly property double threshold: 0.4
+    readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
+    readonly property alias swipping: mainItemMoving.running
+    readonly property bool _showActions: mouseArea.pressed || swipeState != "Normal" || swipping
+
+    property alias _main: main  // CUSTOM
+    property alias pressed: mouseArea.pressed  // CUSTOM
+
+    /* internal */
+    property var _visibleRightSideActions: filterVisibleActions(rightSideActions)
+
+    signal itemClicked(var mouse)
+    signal itemPressAndHold(var mouse)
+
+    function returnToBoundsRTL(direction)
+    {
+        var actionFullWidth = actionWidth + units.gu(2)
+
+        // go back to normal state if swipping reverse
+        if (direction === "LTR") {
+            updatePosition(0)
+            return
+        } else if (!triggerActionOnMouseRelease) {
+            updatePosition(-rightActionsView.width + units.gu(2))
+            return
+        }
+
+        var xOffset = Math.abs(main.x)
+        var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
+        var newX = 0
+
+        if (index === _visibleRightSideActions.length) {
+            newX = -(rightActionsView.width - units.gu(2))
+        } else if (index >= 1) {
+            newX = -(actionFullWidth * index)
+        }
+
+        updatePosition(newX)
+    }
+
+    function returnToBoundsLTR(direction)
+    {
+        var finalX = leftActionWidth
+        if ((direction === "RTL") || (main.x <= (finalX * root.threshold)))
+            finalX = 0
+        updatePosition(finalX)
+    }
+
+    function returnToBounds(direction)
+    {
+        if (main.x < 0) {
+            returnToBoundsRTL(direction)
+        } else if (main.x > 0) {
+            returnToBoundsLTR(direction)
+        } else {
+            updatePosition(0)
+        }
+    }
+
+    function contains(item, point, marginX)
+    {
+        var itemStartX = item.x - marginX
+        var itemEndX = item.x + item.width + marginX
+        return (point.x >= itemStartX) && (point.x <= itemEndX) &&
+               (point.y >= item.y) && (point.y <= (item.y + item.height));
+    }
+
+    function getActionAt(point)
+    {
+        if (leftSideAction && contains(leftActionViewLoader.item, point, 0)) {
+            return leftSideAction
+        } else if (contains(rightActionsView, point, 0)) {
+            var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
+            for (var i = 0; i < rightActionsRepeater.count; i++) {
+                var child = rightActionsRepeater.itemAt(i)
+                if (contains(child, newPoint, units.gu(1))) {
+                    return i
+                }
+            }
+        }
+        return -1
+    }
+
+    function updateActiveAction()
+    {
+        if (triggerActionOnMouseRelease &&
+            (main.x <= -(root.actionWidth + units.gu(2))) &&
+            (main.x > -(rightActionsView.width - units.gu(2)))) {
+            var actionFullWidth = actionWidth + units.gu(2)
+            var xOffset = Math.abs(main.x)
+            var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
+            index = index - 1
+            if (index > -1) {
+                root.activeItem = rightActionsRepeater.itemAt(index)
+                root.activeAction = root._visibleRightSideActions[index]
+            }
+        } else {
+            root.activeAction = null
+        }
+    }
+
+    function resetSwipe()
+    {
+        updatePosition(0)
+    }
+
+    function filterVisibleActions(actions)
+    {
+        var visibleActions = []
+        for(var i = 0; i < actions.length; i++) {
+            var action = actions[i]
+            if (action.visible) {
+                visibleActions.push(action)
+            }
+        }
+        return visibleActions
+    }
+
+    function updatePosition(pos)
+    {
+        if (!root.triggerActionOnMouseRelease && (pos !== 0)) {
+            mouseArea.state = pos > 0 ? "RightToLeft" : "LeftToRight"
+        } else {
+            mouseArea.state = ""
+        }
+        main.x = pos
+    }
+
+    // CUSTOM remove animation
+    SequentialAnimation {
+        id: removeAnimation
+
+        property var action
+
+        UbuntuNumberAnimation {
+            target: root
+            duration: UbuntuAnimation.BriskDuration
+            property: "height";
+            to: 0
+        }
+        ScriptAction {
+            script: removeAnimation.action.trigger()
+        }
+    }
+
+    states: [
+        State {
+            name: "select"
+            when: selectionMode || selected
+            PropertyChanges {
+                target: selectionIcon
+                source: Qt.resolvedUrl("ListItemActions/CheckBox.qml")
+                anchors.leftMargin: units.gu(2)
+            }
+            PropertyChanges {
+                target: root
+                locked: true
+            }
+            PropertyChanges {
+                target: main
+                x: 0
+            }
+        }
+    ]
+
+    height: defaultHeight
+    //clip: height !== defaultHeight  // CUSTOM
+
+    Loader {  // CUSTOM
+        id: leftActionViewLoader
+        anchors {
+            top: parent.top
+            bottom: parent.bottom
+            right: main.left
+        }
+        asynchronous: true
+        sourceComponent: leftSideAction ? leftActionViewComponent : undefined
+    }
+
+    Component {  // CUSTOM
+        id: leftActionViewComponent
+
+        Rectangle {
+            id: leftActionView
+            width: root.leftActionWidth + actionThreshold
+            color: UbuntuColors.red
+
+            Icon {
+                id: leftActionIcon
+                anchors {
+                    centerIn: parent
+                    horizontalCenterOffset: actionThreshold / 2
+                }
+                objectName: "swipeDeleteAction"  // CUSTOM
+                name: leftSideAction && _showActions ? leftSideAction.iconName : ""
+                color: Theme.palette.selected.field
+                height: units.gu(3)
+                width: units.gu(3)
+            }
+        }
+    }
+
+    //Rectangle {
+    Item {  // CUSTOM
+       id: rightActionsView
+
+       anchors {
+           top: main.top
+           left: main.right
+           bottom: main.bottom
+       }
+       visible: _visibleRightSideActions.length > 0
+       width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + root.actionThreshold + units.gu(2) : 0
+       // color: "white"  // CUSTOM
+
+       Row {
+           anchors{
+               top: parent.top
+               left: parent.left
+               leftMargin: units.gu(2)
+               right: parent.right
+               rightMargin: units.gu(2)
+               bottom: parent.bottom
+           }
+           spacing: units.gu(2)
+           Repeater {
+               id: rightActionsRepeater
+
+               model: _showActions ? _visibleRightSideActions : []
+               Item {
+                   property alias image: img
+
+                   height: rightActionsView.height
+                   width: root.actionWidth
+
+                   Icon {
+                       id: img
+
+                       anchors.centerIn: parent
+                       objectName: rightSideActions[index].objectName  // CUSTOM
+                       width: units.gu(3)
+                       height: units.gu(3)
+                       name: modelData.iconName
+                       color: root.activeAction === modelData ? UbuntuColors.orange : UbuntuColors.coolGrey  // CUSTOM
+                   }
+               }
+           }
+       }
+    }
+
+    Rectangle {
+        id: main
+        objectName: "mainItem"
+
+        anchors {
+            top: parent.top
+            bottom: parent.bottom
+        }
+
+        width: parent.width
+        color: root.selected ? root.selectedColor : root.color
+
+        Loader {
+            id: selectionIcon
+
+            anchors {
+                left: main.left
+                verticalCenter: main.verticalCenter
+            }
+            asynchronous: true  // CUSTOM
+            width: (status === Loader.Ready) ? item.implicitWidth : 0
+            visible: (status === Loader.Ready) && (item.width === item.implicitWidth)
+
+            Behavior on width {
+                NumberAnimation {
+                    duration: UbuntuAnimation.SnapDuration
+                }
+            }
+        }
+
+        Item {
+            id: mainContents
+
+            anchors {
+                left: selectionIcon.right
+                //leftMargin: units.gu(2)  // CUSTOM
+                top: parent.top
+                //topMargin: units.gu(1)  // CUSTOM
+                right: parent.right
+                //rightMargin: units.gu(2)  // CUSTOM
+                bottom: parent.bottom
+                //bottomMargin: units.gu(1)  // CUSTOM
+            }
+        }
+
+        Behavior on x {
+            UbuntuNumberAnimation {
+                id: mainItemMoving
+
+                easing.type: Easing.OutElastic
+                duration: UbuntuAnimation.SlowDuration
+            }
+        }
+    }
+
+    SequentialAnimation {
+        id: triggerAction
+
+        property var currentItem: root.activeItem ? root.activeItem.image : null
+
+        running: false
+        ParallelAnimation {
+            UbuntuNumberAnimation {
+                target: triggerAction.currentItem
+                property: "opacity"
+                from: 1.0
+                to: 0.0
+                duration: UbuntuAnimation.SlowDuration
+                easing {type: Easing.InOutBack; }
+            }
+            UbuntuNumberAnimation {
+                target: triggerAction.currentItem
+                properties: "width, height"
+                from: units.gu(3)
+                to: root.actionWidth
+                duration: UbuntuAnimation.SlowDuration
+                easing {type: Easing.InOutBack; }
+            }
+        }
+        PropertyAction {
+            target: triggerAction.currentItem
+            properties: "width, height"
+            value: units.gu(3)
+        }
+        PropertyAction {
+            target: triggerAction.currentItem
+            properties: "opacity"
+            value: 1.0
+        }
+        ScriptAction {
+            script: {
+                root.activeAction.triggered(root)
+                mouseArea.state = ""
+            }
+        }
+        PauseAnimation {
+            duration: 500
+        }
+        UbuntuNumberAnimation {
+            target: main
+            property: "x"
+            to: 0
+        }
+    }
+
+    MouseArea {
+        id: mouseArea
+
+        property bool locked: root.locked || ((root.leftSideAction === null) && (root._visibleRightSideActions.count === 0))  // CUSTOM
+        property bool manual: false
+        property string direction: "None"
+        property real lastX: -1
+
+        anchors.fill: parent
+        drag {
+            target: locked ? null : main
+            axis: Drag.XAxis
+            minimumX: rightActionsView.visible ? -(rightActionsView.width) : 0
+            maximumX: leftSideAction ? leftActionViewLoader.item.width : 0
+            threshold: root.actionThreshold
+        }
+
+        states: [
+            State {
+                name: "LeftToRight"
+                PropertyChanges {
+                    target: mouseArea
+                    drag.maximumX: 0
+                }
+            },
+            State {
+                name: "RightToLeft"
+                PropertyChanges {
+                    target: mouseArea
+                    drag.minimumX: 0
+                }
+            }
+        ]
+
+        onMouseXChanged: {
+            var offset = (lastX - mouseX)
+            if (Math.abs(offset) <= root.actionThreshold) {
+                return
+            }
+            lastX = mouseX
+            direction = offset > 0 ? "RTL" : "LTR";
+        }
+
+        onPressed: {
+            lastX = mouse.x
+        }
+
+        onReleased: {
+            if (root.triggerActionOnMouseRelease && root.activeAction) {
+                triggerAction.start()
+            } else {
+                root.returnToBounds()
+                root.activeAction = null
+            }
+            lastX = -1
+            direction = "None"
+        }
+        onClicked: {
+            if (selectionMode) {  // CUSTOM - selecting a listitem should toggle selection if in selectionMode
+                selected = !selected
+                return
+            } else if (main.x === 0) {
+                root.itemClicked(mouse)
+            } else if (main.x > 0) {
+                var action = getActionAt(Qt.point(mouse.x, mouse.y))
+                if (action && action !== -1) {
+                    //action.triggered(root)
+                    removeAnimation.action = action  // CUSTOM - use our animation instead
+                    removeAnimation.start()  // CUSTOM
+                }
+            } else {
+                var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y))
+
+                if (actionIndex !== -1 && actionIndex !== leftSideAction) {  // CUSTOM - can be leftAction
+                    root.activeItem = rightActionsRepeater.itemAt(actionIndex)
+                    root.activeAction = root.rightSideActions[actionIndex]
+                    triggerAction.start()
+                    return
+                }
+            }
+            root.resetSwipe()
+        }
+
+        onPositionChanged: {
+            if (mouseArea.pressed) {
+                updateActiveAction()
+
+                listItemSwiping(index)  // CUSTOM - tells other listitems to dismiss any swipe
+            }
+        }
+        onPressAndHold: {
+            if (main.x === 0) {
+                root.itemPressAndHold(mouse)
+            }
+        }
+
+        z: -1
+    }
+}

=== added file 'app/components/LoadingIndicator.qml'
--- app/components/LoadingIndicator.qml	1970-01-01 00:00:00 +0000
+++ app/components/LoadingIndicator.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *  
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *  
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Rectangle {
+    id: indicator
+    objectName: "processingIndicator"
+    anchors {
+        left: parent.left
+        right: parent.right
+        bottom: parent.bottom
+        bottomMargin: Qt.inputMethod.keyboardRectangle.height
+    }
+    height: units.dp(3)
+    color: "white"
+    opacity: 0
+    visible: opacity > 0
+
+    readonly property bool processing: loading
+
+    Behavior on opacity {
+        UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
+    }
+
+    onProcessingChanged: {
+        if (processing) delay.start();
+        else if (!persist.running) indicator.opacity = 0;
+    }
+
+    Timer {
+        id: delay
+        interval: 200
+        onTriggered: if (indicator.processing) {
+            persist.restart();
+            indicator.opacity = 1;
+        }
+    }
+
+    Timer {
+        id: persist
+        interval: 2 * UbuntuAnimation.SleepyDuration - UbuntuAnimation.FastDuration
+        onTriggered: if (!indicator.processing) indicator.opacity = 0
+    }
+
+    Rectangle {
+        id: orange
+        anchors { top: parent.top;  bottom: parent.bottom }
+        width: parent.width / 4
+        color: UbuntuColors.orange
+
+        SequentialAnimation {
+            running: indicator.visible
+            loops: Animation.Infinite
+            XAnimator {
+                from: -orange.width / 2
+                to: indicator.width - orange.width / 2
+                duration: UbuntuAnimation.SleepyDuration
+                easing.type: Easing.InOutSine
+                target: orange
+            }
+            XAnimator {
+                from: indicator.width - orange.width / 2
+                to: -orange.width / 2
+                duration: UbuntuAnimation.SleepyDuration
+                easing.type: Easing.InOutSine
+                target: orange
+            }
+        }
+    }
+}

=== added file 'app/components/MultiSelectHeadState.qml'
--- app/components/MultiSelectHeadState.qml	1970-01-01 00:00:00 +0000
+++ app/components/MultiSelectHeadState.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+PageHeadState {
+    id: selectionState
+    actions: [
+        Action {
+            iconName: "select"
+            text: i18n.tr("Select All")
+            onTriggered: {
+                if (listview.selectedItems.length === listview.model.count) {
+                    listview.clearSelection()
+                } else {
+                    listview.selectAll()
+                }
+            }
+        },
+        Action {
+            enabled: listview.selectedItems.length > 0
+            iconName: "delete"
+            text: i18n.tr("Delete")
+            visible: removable
+
+            onTriggered: {
+                removed(listview.selectedItems)
+
+                listview.closeSelection()
+            }
+        }
+
+    ]
+    backAction: Action {
+        text: i18n.tr("Cancel selection")
+        iconName: "back"
+        onTriggered: {
+            listview.clearSelection()
+            listview.state = "normal"
+        }
+    }
+    head: thisPage.head
+    name: "selection"
+
+    PropertyChanges {
+        target: thisPage.head
+        backAction: selectionState.backAction
+        actions: selectionState.actions
+    }
+
+    property ListView listview
+    property bool removable: false
+    property Page thisPage
+
+    signal removed(var selectedItems)
+}

=== added file 'app/components/MultiSelectListView.qml'
--- app/components/MultiSelectListView.qml	1970-01-01 00:00:00 +0000
+++ app/components/MultiSelectListView.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Daniel Holm <d.holmen@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+WeatherListView {
+    property var selectedItems: []
+
+    signal clearSelection()
+    signal closeSelection()
+    signal selectAll()
+
+    onClearSelection: selectedItems = []
+    onCloseSelection: {
+        clearSelection()
+        state = "normal"
+    }
+    onSelectAll: {
+        var tmp = selectedItems
+
+        for (var i=0; i < model.count; i++) {
+            if (tmp.indexOf(i) === -1) {
+                tmp.push(i)
+            }
+        }
+
+        selectedItems = tmp
+    }
+    onVisibleChanged: {
+        if (!visible) {
+            closeSelection()
+        }
+    }
+}

=== added file 'app/components/PageWithBottomEdge.qml'
--- app/components/PageWithBottomEdge.qml	1970-01-01 00:00:00 +0000
+++ app/components/PageWithBottomEdge.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2014, 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+    Example:
+
+    MainView {
+        objectName: "mainView"
+
+        applicationName: "com.ubuntu.developer.boiko.bottomedge"
+
+        width: units.gu(100)
+        height: units.gu(75)
+
+        Component {
+            id: pageComponent
+
+            PageWithBottomEdge {
+                id: mainPage
+                title: i18n.tr("Main Page")
+
+                Rectangle {
+                    anchors.fill: parent
+                    color: "white"
+                }
+
+                bottomEdgePageComponent: Page {
+                    title: "Contents"
+                    anchors.fill: parent
+                    //anchors.topMargin: contentsPage.flickable.contentY
+
+                    ListView {
+                        anchors.fill: parent
+                        model: 50
+                        delegate: ListItems.Standard {
+                            text: "One Content Item: " + index
+                        }
+                    }
+                }
+                bottomEdgeTitle: i18n.tr("Bottom edge action")
+            }
+        }
+
+        PageStack {
+            id: stack
+            Component.onCompleted: stack.push(pageComponent)
+        }
+    }
+
+*/
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Page {
+    id: page
+
+    property alias bottomEdgePageComponent: edgeLoader.sourceComponent
+    property alias bottomEdgePageSource: edgeLoader.source
+    property alias bottomEdgeTitle: tipLabel.text
+    property bool bottomEdgeEnabled: true
+    property int bottomEdgeExpandThreshold: page.height * 0.2
+    property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
+    property bool reloadBottomEdgePage: true
+
+    readonly property alias bottomEdgePage: edgeLoader.item
+    readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
+    readonly property bool isCollapsed: (bottomEdge.y === page.height)
+    readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
+
+    property bool _showEdgePageWhenReady: false
+    property int _areaWhenExpanded: 0
+
+    // CUSTOM properties to allow changing of color
+    property alias tipColor: tip.color
+    property alias tipLabelColor: tipLabel.color
+
+    signal bottomEdgeReleased()
+    signal bottomEdgeDismissed()
+
+
+    function showBottomEdgePage(source, properties)
+    {
+        edgeLoader.setSource(source, properties)
+        _showEdgePageWhenReady = true
+    }
+
+    function setBottomEdgePage(source, properties)
+    {
+        edgeLoader.setSource(source, properties)
+    }
+
+    function _pushPage()
+    {
+        if (edgeLoader.status === Loader.Ready) {
+            edgeLoader.item.active = true
+            page.pageStack.push(edgeLoader.item)
+            if (edgeLoader.item.flickable) {
+                edgeLoader.item.flickable.contentY = -page.header.height
+                edgeLoader.item.flickable.returnToBounds()
+            }
+            if (edgeLoader.item.ready)
+                edgeLoader.item.ready()
+        }
+    }
+
+
+    Component.onCompleted: {
+        // avoid a binding on the expanded height value
+        var expandedHeight = height;
+        _areaWhenExpanded = expandedHeight;
+    }
+
+    onActiveChanged: {
+        if (active) {
+            bottomEdge.state = "collapsed"
+        }
+    }
+
+    onBottomEdgePageLoadedChanged: {
+        if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
+            bottomEdge.state = "expanded"
+            _showEdgePageWhenReady = false
+        }
+    }
+
+    Rectangle {
+        id: bgVisual
+
+        color: "black"
+        anchors.fill: page
+        opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
+        z: 1
+    }
+
+    UbuntuShape {
+        id: tip
+        objectName: "bottomEdgeTip"
+
+        property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
+
+        enabled: mouseArea.enabled
+        visible: page.bottomEdgeEnabled
+        anchors {
+            bottom: parent.bottom
+            horizontalCenter: bottomEdge.horizontalCenter
+            bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
+            Behavior on bottomMargin {
+                SequentialAnimation {
+                    // wait some msecs in case of the focus change again, to avoid flickering
+                    PauseAnimation {
+                        duration: 300
+                    }
+                    UbuntuNumberAnimation {
+                        duration: UbuntuAnimation.SnapDuration
+                    }
+                }
+            }
+        }
+
+        z: 1
+        width: tipLabel.paintedWidth + units.gu(6)
+        height: bottomEdge.tipHeight + units.gu(1)
+        color: Theme.palette.normal.overlay
+        Label {
+            id: tipLabel
+
+            anchors {
+                top: parent.top
+                left: parent.left
+                right: parent.right
+            }
+            height: bottomEdge.tipHeight
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+            opacity: tip.hidden ? 0.0 : 1.0
+            Behavior on opacity {
+                UbuntuNumberAnimation {
+                    duration: UbuntuAnimation.SnapDuration
+                }
+            }
+        }
+    }
+
+    Rectangle {
+        id: shadow
+
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: parent.bottom
+        }
+        height: units.gu(1)
+        z: 1
+        opacity: 0.0
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "transparent" }
+            GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
+        }
+    }
+
+    MouseArea {
+        id: mouseArea
+
+        property real previousY: -1
+        property string dragDirection: "None"
+
+        preventStealing: true
+        drag {
+            axis: Drag.YAxis
+            target: bottomEdge
+            minimumY: bottomEdge.pageStartY
+            maximumY: page.height
+        }
+        enabled: edgeLoader.status == Loader.Ready
+        visible: page.bottomEdgeEnabled
+
+        anchors {
+            left: parent.left
+            right: parent.right
+            bottom: parent.bottom
+
+        }
+        height: bottomEdge.tipHeight
+        z: 1
+
+        onReleased: {
+            page.bottomEdgeReleased()
+            if ((dragDirection === "BottomToTop") &&
+                bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
+                bottomEdge.state = "expanded"
+            } else {
+                bottomEdge.state = "collapsed"
+            }
+            previousY = -1
+            dragDirection = "None"
+        }
+
+        onPressed: {
+            previousY = mouse.y
+            tip.forceActiveFocus()
+        }
+
+        onMouseYChanged: {
+            var yOffset = previousY - mouseY
+            // skip if was a small move
+            if (Math.abs(yOffset) <= units.gu(2)) {
+                return
+            }
+            previousY = mouseY
+            dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
+        }
+    }
+
+    Rectangle {
+        id: bottomEdge
+        objectName: "bottomEdge"
+
+        readonly property int tipHeight: units.gu(3)
+        readonly property int pageStartY: 0
+
+        z: 1
+        color: Theme.palette.normal.background
+        clip: true
+        anchors {
+            left: parent.left
+            right: parent.right
+        }
+        height: page.height
+        y: height
+        visible: !page.isCollapsed
+        state: "collapsed"
+        states: [
+            State {
+                name: "collapsed"
+                PropertyChanges {
+                    target: bottomEdge
+                    y: bottomEdge.height
+                }
+            },
+            State {
+                name: "expanded"
+                PropertyChanges {
+                    target: bottomEdge
+                    y: bottomEdge.pageStartY
+                }
+            },
+            State {
+                name: "floating"
+                when: mouseArea.drag.active
+                PropertyChanges {
+                    target: shadow
+                    opacity: 1.0
+                }
+            }
+        ]
+
+        transitions: [
+            Transition {
+                to: "expanded"
+                SequentialAnimation {
+                    alwaysRunToEnd: true
+
+                    SmoothedAnimation {
+                        target: bottomEdge
+                        property: "y"
+                        duration: UbuntuAnimation.FastDuration
+                        easing.type: Easing.Linear
+                    }
+                    SmoothedAnimation {
+                        target: edgeLoader
+                        property: "anchors.topMargin"
+                        to: - units.gu(4)
+                        duration: UbuntuAnimation.FastDuration
+                        easing.type: Easing.Linear
+                    }
+                    SmoothedAnimation {
+                        target: edgeLoader
+                        property: "anchors.topMargin"
+                        to: 0
+                        duration: UbuntuAnimation.FastDuration
+                        easing: UbuntuAnimation.StandardEasing
+                    }
+                    ScriptAction {
+                        script: page._pushPage()
+                    }
+                }
+            },
+            Transition {
+                from: "expanded"
+                to: "collapsed"
+                SequentialAnimation {
+                    alwaysRunToEnd: true
+
+                    ScriptAction {
+                        script: {
+                            Qt.inputMethod.hide()
+                            edgeLoader.item.parent = edgeLoader
+                            edgeLoader.item.anchors.fill = edgeLoader
+                            edgeLoader.item.active = false
+                        }
+                    }
+                    SmoothedAnimation {
+                        target: bottomEdge
+                        property: "y"
+                        duration: UbuntuAnimation.SlowDuration
+                    }
+                    ScriptAction {
+                        script: {
+                            // destroy current bottom page
+                            if (page.reloadBottomEdgePage) {
+                                edgeLoader.active = false
+                                // tip will receive focus on page active true
+                            } else {
+                                tip.forceActiveFocus()
+                            }
+
+                            // notify
+                            page.bottomEdgeDismissed()
+
+                            edgeLoader.active = true
+                        }
+                    }
+                }
+            },
+            Transition {
+                from: "floating"
+                to: "collapsed"
+                SmoothedAnimation {
+                    target: bottomEdge
+                    property: "y"
+                    duration: UbuntuAnimation.FastDuration
+                }
+            }
+        ]
+
+        Loader {
+            id: edgeLoader
+
+            asynchronous: true
+            anchors.fill: parent
+            //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
+            Binding {
+                target: edgeLoader.status === Loader.Ready ? edgeLoader : null
+                property: "anchors.topMargin"
+                value:  edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
+                when: !page.isReady
+            }
+
+            onLoaded: {
+                tip.forceActiveFocus()
+                if (page.isReady && edgeLoader.item.active !== true) {
+                    page._pushPage()
+                }
+            }
+        }
+    }
+}

=== added file 'app/components/StandardListItem.qml'
--- app/components/StandardListItem.qml	1970-01-01 00:00:00 +0000
+++ app/components/StandardListItem.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.2
+
+ListItem {
+    id: listItem
+
+    property alias title: _title.text
+    property alias icon: _icon.name
+    property alias showIcon: _icon.visible
+
+    RowLayout {
+        anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter; margins: units.gu(2) }
+        height: _icon.height
+        spacing: units.gu(2)
+        
+        Label {
+            id: _title
+            anchors.verticalCenter: _icon.verticalCenter
+            elide: Text.ElideRight
+            Layout.fillWidth: true
+        }
+        
+        Icon {
+            id: _icon
+            height: units.gu(2); width: height
+            name: "go-next"
+        }
+    }
+}

=== added file 'app/components/WeatherListItem.qml'
--- app/components/WeatherListItem.qml	1970-01-01 00:00:00 +0000
+++ app/components/WeatherListItem.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListItemWithActions {
+    id: root
+
+    property int listItemIndex: index
+    property bool multiselectable: false
+    property int previousListItemIndex: -1
+    property bool reorderable: false
+
+    signal reorder(int from, int to)
+
+    onItemPressAndHold: {
+        if (multiselectable) {
+            selectionMode = true
+        }
+    }
+
+    onListItemIndexChanged: {
+        var i = parent.parent.selectedItems.lastIndexOf(previousListItemIndex)
+
+        if (i !== -1) {
+            parent.parent.selectedItems[i] = listItemIndex
+        }
+
+        previousListItemIndex = listItemIndex
+    }
+
+    onSelectedChanged: {
+        if (selectionMode) {
+            var tmp = parent.parent.selectedItems
+
+            if (selected) {
+                if (parent.parent.selectedItems.indexOf(listItemIndex) === -1) {
+                    tmp.push(listItemIndex)
+                    parent.parent.selectedItems = tmp
+                }
+            } else {
+                tmp.splice(parent.parent.selectedItems.indexOf(listItemIndex), 1)
+                parent.parent.selectedItems = tmp
+            }
+        }
+    }
+
+    onSelectionModeChanged: {
+        if (reorderable && selectionMode) {
+            resetSwipe()
+        }
+
+        for (var j=0; j < _main.children.length; j++) {
+            if (_main.children[j] !== actionReorderLoader) {
+                _main.children[j].anchors.rightMargin = reorderable && selectionMode ? actionReorderLoader.width + units.gu(2) : 0
+            }
+        }
+
+        parent.parent.state = selectionMode ? "multiselectable" : "normal"
+
+        if (!selectionMode) {
+            selected = false
+        }
+    }
+
+    /* Highlight the listitem on press */
+    Rectangle {
+        id: listItemBrighten
+        color: root.pressed ? UbuntuColors.coolGrey : "transparent"
+        opacity: 0.1
+        height: root.height
+        x: root.x - parent.x  // -parent.x due to selectionIcon in ListItemWithActions
+        width: root.width
+    }
+
+    /* Reorder Component */
+    Loader {
+        id: actionReorderLoader
+        active: reorderable && selectionMode && root.parent.parent.selectedItems.length === 0
+        anchors {
+            bottom: parent.bottom
+            right: parent.right
+            rightMargin: units.gu(1)
+            top: parent.top
+        }
+        asynchronous: true
+        source: "ListItemReorderComponent.qml"
+    }
+
+    Item {
+        Connections {  // Only allow one ListItem to be swiping at any time
+            target: weatherApp
+            onListItemSwiping: {
+                if (i !== index) {
+                    root.resetSwipe();
+                }
+            }
+        }
+
+        Connections {  // Connections from signals in the ListView
+            target: root.parent.parent
+            onClearSelection: selected = false
+            onFlickingChanged: {
+                if (root.parent.parent.flicking) {
+                    root.resetSwipe()
+                }
+            }
+            onSelectAll: selected = true
+            onStateChanged: selectionMode = root.parent.parent.state === "multiselectable"
+        }
+    }
+
+    Component.onCompleted: {  // reload settings as delegates are destroyed
+        if (parent.parent.selectedItems.indexOf(index) !== -1) {
+            selected = true
+        }
+
+        selectionMode = root.parent.parent.state === "multiselectable"
+    }
+}

=== added file 'app/components/WeatherListView.qml'
--- app/components/WeatherListView.qml	1970-01-01 00:00:00 +0000
+++ app/components/WeatherListView.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Daniel Holm <d.holmen@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+ListView {
+    Component.onCompleted: {
+        // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
+        // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
+        var scaleFactor = units.gridUnit / 8;
+        maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+        flickDeceleration = flickDeceleration * scaleFactor;
+    }
+}

=== added directory 'app/data'
=== added file 'app/data/CMakeLists.txt'
--- app/data/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/data/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB DATA_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_data_QMlFiles ALL SOURCES ${DATA_QML_JS_FILES})
+
+install(FILES ${DATA_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/data)

=== added file 'app/data/CitiesList.js'
--- app/data/CitiesList.js	1970-01-01 00:00:00 +0000
+++ app/data/CitiesList.js	2015-06-28 23:49:12 +0000
@@ -0,0 +1,65 @@
+.pragma library
+/*
+ * Copyright (C) 2013 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Martin Borho <martin@xxxxxxxxx>
+ */
+
+var preList = [
+    {"coord":{"lon":"4.88969","lat":"52.37403"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Amsterdam"},"population":741636,"adminName2":"Gemeente Amsterdam","name":"Amsterdam","country":"NL","countryName":"Netherlands","adminName1":"North Holland","adminName3":"","services":{"geonames":2759794},"areaLabel":"North Holland, Netherlands"},
+    {"coord":{"lon":"116.39723","lat":"39.9075"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":7480601,"adminName2":"","name":"Beijing","country":"CN","countryName":"China","adminName1":"Beijing","adminName3":"","services":{"geonames":1816670},"areaLabel":"Beijing, China"},
+    {"coord":{"lon":"-74.08175","lat":"4.60971"},"timezone":{"gmtOffset":-5,"dstOffset":-5,"timeZoneId":"America/Bogota"},"population":7674366,"adminName2":"","name":"Bogotá","country":"CO","countryName":"Colombia","adminName1":"Bogota D.C.","adminName3":"","services":{"geonames":3688689},"areaLabel":"Bogota D.C., Colombia"},
+    {"coord":{"lon":"-58.37723","lat":"-34.61315"},"timezone":{"gmtOffset":-3,"dstOffset":-3,"timeZoneId":"America/Argentina/Buenos_Aires"},"population":13076300,"adminName2":"","name":"Buenos Aires","country":"AR","countryName":"Argentina","adminName1":"Buenos Aires F.D.","adminName3":"","services":{"geonames":3435910},"areaLabel":"Buenos Aires F.D., Argentina"},
+    {"coord":{"lon":"31.24967","lat":"30.06263"},"timezone":{"gmtOffset":2,"dstOffset":2,"timeZoneId":"Africa/Cairo"},"population":7734614,"adminName2":"","name":"Cairo","country":"EG","countryName":"Egypt","adminName1":"Al Qāhirah","adminName3":"","services":{"geonames":360630},"areaLabel":"Al Qāhirah, Egypt"},
+    {"coord":{"lon":"-66.87919","lat":"10.48801"},"timezone":{"gmtOffset":-4.5,"dstOffset":-4.5,"timeZoneId":"America/Caracas"},"population":3000000,"adminName2":"Municipio Libertador","name":"Caracas","country":"VE","countryName":"Venezuela","adminName1":"Distrito Federal","adminName3":"","services":{"geonames":3646738},"areaLabel":"Distrito Federal, Venezuela, Bolivarian Republic of"},
+    {"coord":{"lon":"-87.65005","lat":"41.85003"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Chicago"},"population":2695598,"adminName2":"Cook County","name":"Chicago","country":"US","countryName":"United States","adminName1":"Illinois","adminName3":"","services":{"geonames":4887398},"areaLabel":"Illinois, United States"},
+    {"coord":{"lon":"77.22897","lat":"28.65381"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":10927986,"adminName2":"","name":"Dehli","country":"IN","countryName":"India","adminName1":"NCT","adminName3":"","services":{"geonames":1273294},"areaLabel":"NCT, India"},
+    {"coord":{"lon":"55.30472","lat":"25.25817"},"timezone":{"gmtOffset":4,"dstOffset":4,"timeZoneId":"Asia/Dubai"},"population":1137347,"adminName2":"","name":"Dubai","country":"AE","countryName":"United Arab Emirates","adminName1":"Dubai","adminName3":"","services":{"geonames":292223},"areaLabel":"Dubai, United Arab Emirates"},
+    {"coord":{"lon":"113.25","lat":"23.11667"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3152825,"adminName2":"","name":"Guangzhou","country":"CN","countryName":"China","adminName1":"Guangdong Province","adminName3":"","services":{"geonames":1809858},"areaLabel":"Guangdong Province, China"},
+    {"coord":{"lon":"10.01534","lat":"53.57532"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Berlin"},"population":1739117,"adminName2":"","name":"Hamburg","country":"DE","countryName":"Germany","adminName1":"Hamburg","adminName3":"","services":{"geonames":2911298},"areaLabel":"Hamburg, Germany"},
+    {"coord":{"lon":"-95.36327","lat":"29.76328"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Chicago"},"population":2099451,"adminName2":"Harris County","name":"Houston","country":"US","countryName":"United States","adminName1":"Texas","adminName3":"","services":{"geonames":4699066},"areaLabel":"Texas, United States"},
+    {"coord":{"lon":"28.94966","lat":"41.01384"},"timezone":{"gmtOffset":2,"dstOffset":3,"timeZoneId":"Europe/Istanbul"},"population":11174257,"adminName2":"","name":"Istanbul","country":"TR","countryName":"Turkey","adminName1":"Istanbul","adminName3":"","services":{"geonames":745044},"areaLabel":"Istanbul, Turkey"},
+    {"coord":{"lon":"106.84513","lat":"-6.21462"},"timezone":{"gmtOffset":7,"dstOffset":7,"timeZoneId":"Asia/Jakarta"},"population":8540121,"adminName2":"","name":"Jakarta","country":"ID","countryName":"Indonesia","adminName1":"Jakarta Raya","adminName3":"","services":{"geonames":1642911},"areaLabel":"Jakarta Raya, Indonesia"},
+    {"coord":{"lon":"28.04363","lat":"-26.20227"},"timezone":{"gmtOffset":2,"dstOffset":2,"timeZoneId":"Africa/Johannesburg"},"population":2026469,"adminName2":"City of Johannesburg Metropolitan Municipality","name":"Johannesburg","country":"ZA","countryName":"South Africa","adminName1":"Gauteng","adminName3":"City of Johannesburg","services":{"geonames":993800},"areaLabel":"Gauteng, South Africa"},
+    {"coord":{"lon":"67.0822","lat":"24.9056"},"timezone":{"gmtOffset":5,"dstOffset":5,"timeZoneId":"Asia/Karachi"},"population":11624219,"adminName2":"Karāchi District","name":"Karachi","country":"PK","countryName":"Pakistan","adminName1":"Sindh","adminName3":"","services":{"geonames":1174872},"areaLabel":"Sindh, Pakistan"},
+    {"coord":{"lon":"15.30807","lat":"-4.32142"},"timezone":{"gmtOffset":1,"dstOffset":1,"timeZoneId":"Africa/Kinshasa"},"population":7785965,"adminName2":"","name":"Kinshasa","country":"CD","countryName":"Democratic Republic of the Congo","adminName1":"Kinshasa","adminName3":"","services":{"geonames":2314302},"areaLabel":"Kinshasa, Congo, the Democratic Republic of the"},
+    {"coord":{"lon":"88.36304","lat":"22.56263"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":4631392,"adminName2":"","name":"Kolkata","country":"IN","countryName":"India","adminName1":"Bengal","adminName3":"","services":{"geonames":1275004},"areaLabel":"Bengal, India"},
+    {"coord":{"lon":"3.39583","lat":"6.45306"},"timezone":{"gmtOffset":1,"dstOffset":1,"timeZoneId":"Africa/Lagos"},"population":9000000,"adminName2":"","name":"Lagos","country":"NG","countryName":"Nigeria","adminName1":"Lagos","adminName3":"","services":{"geonames":2332459},"areaLabel":"Lagos, Nigeria"},
+    {"coord":{"lon":"-77.02824","lat":"-12.04318"},"timezone":{"gmtOffset":-5,"dstOffset":-5,"timeZoneId":"America/Lima"},"population":7737002,"adminName2":"","name":"Lima","country":"PE","countryName":"Peru","adminName1":"Provincia de Lima","adminName3":"","services":{"geonames":3936456},"areaLabel":"Provincia de Lima, Peru"},
+    {"coord":{"lon":"-0.12574","lat":"51.50853"},"timezone":{"gmtOffset":0,"dstOffset":1,"timeZoneId":"Europe/London"},"population":7556900,"adminName2":"Greater London","name":"London","country":"GB","countryName":"United Kingdom","adminName1":"England","adminName3":"","services":{"geonames":2643743},"areaLabel":"England, Greater London, United Kingdom"},
+    {"coord":{"lon":"-118.24368","lat":"34.05223"},"timezone":{"gmtOffset":-8,"dstOffset":-7,"timeZoneId":"America/Los_Angeles"},"population":3792621,"adminName2":"Los Angeles County","name":"Los Angeles","country":"US","countryName":"United States","adminName1":"California","adminName3":"","services":{"geonames":5368361},"areaLabel":"California, United States"},
+    {"coord":{"lon":"-3.70256","lat":"40.4165"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Madrid"},"population":3255944,"adminName2":"Madrid","name":"Madrid","country":"ES","countryName":"Spain","adminName1":"Madrid","adminName3":"Madrid","services":{"geonames":3117735},"areaLabel":"Madrid, Spain"},
+    {"coord":{"lon":"120.9822","lat":"14.6042"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Manila"},"population":10444527,"adminName2":"PH.NCR.D9","name":"Manila","country":"PH","countryName":"Philippines","adminName1":"National Capital Region","adminName3":"","services":{"geonames":1701668},"areaLabel":"National Capital, Philippines"},
+    {"coord":{"lon":"-73.58781","lat":"45.50884"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/Montreal"},"population":3268513,"adminName2":"Montréal","name":"Montreal","country":"CA","countryName":"Canada","adminName1":"Quebec","adminName3":"","services":{"geonames":6077243},"areaLabel":"Quebec, Canada"},
+    {"coord":{"lon":"-99.12766","lat":"19.42847"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Mexico_City"},"population":12294193,"adminName2":"","name":"Mexico City","country":"MX","countryName":"Mexico","adminName1":"The Federal District","adminName3":"","services":{"geonames":3530597},"areaLabel":"The Federal District, Mexico"},
+    {"coord":{"lon":"37.61556","lat":"55.75222"},"timezone":{"gmtOffset":4,"dstOffset":4,"timeZoneId":"Europe/Moscow"},"population":10381222,"adminName2":"","name":"Moscow","country":"RU","countryName":"Russia","adminName1":"Moscow","adminName3":"","services":{"geonames":524901},"areaLabel":"Moscow, Russian Federation"},
+    {"coord":{"lon":"72.88261","lat":"19.07283"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":12691836,"adminName2":"Konkan Division","name":"Mumbai","country":"IN","countryName":"India","adminName1":"Mahārāshtra","adminName3":"","services":{"geonames":1275339},"areaLabel":"Mahārāshtra, India"},
+    {"coord":{"lon":"36.81667","lat":"-1.28333"},"timezone":{"gmtOffset":3,"dstOffset":3,"timeZoneId":"Africa/Nairobi"},"population":2750547,"adminName2":"","name":"Nairobi","country":"KE","countryName":"Kenya","adminName1":"Nairobi Area","adminName3":"","services":{"geonames":184745},"areaLabel":"Nairobi Area, Kenya"},
+    {"coord":{"lon":"-74.00597","lat":"40.71427"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/New_York"},"population":8175133,"adminName2":"","name":"New York City","country":"US","countryName":"United States","adminName1":"New York","adminName3":"","services":{"geonames":5128581},"areaLabel":"New York, United States"},
+    {"coord":{"lon":"135.50218","lat":"34.69374"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Tokyo"},"population":2592413,"adminName2":"","name":"Osaka","country":"JP","countryName":"Japan","adminName1":"Ōsaka","adminName3":"","services":{"geonames":1853909},"areaLabel":"Ōsaka, Japan"},
+    {"coord":{"lon":"-43.2075","lat":"-22.90278"},"timezone":{"gmtOffset":-2,"dstOffset":-3,"timeZoneId":"America/Sao_Paulo"},"population":6023699,"adminName2":"Rio de Janeiro","name":"Rio de Janeiro","country":"BR","countryName":"Brazil","adminName1":"Rio de Janeiro","adminName3":"","services":{"geonames":3451190},"areaLabel":"Rio de Janeiro, Brazil"},
+    {"coord":{"lon":"12.4839","lat":"41.89474"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Rome"},"population":2563241,"adminName2":"Rome","name":"Rome","country":"IT","countryName":"Italy","adminName1":"Latium","adminName3":"Rome","services":{"geonames":3169070},"areaLabel":"Latium, Italy"},
+    {"coord":{"lon":"126.97783","lat":"37.56826"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Seoul"},"population":10349312,"adminName2":"","name":"Seoul","country":"KR","countryName":"South Korea","adminName1":"Seoul","adminName3":"","services":{"geonames":1835848},"areaLabel":"Seoul, Korea, Republic of"},
+    {"coord":{"lon":"-122.41942","lat":"37.77493"},"timezone":{"gmtOffset":-8,"dstOffset":-7,"timeZoneId":"America/Los_Angeles"},"population":805235,"adminName2":"San Francisco County","name":"San Francisco","country":"US","countryName":"United States","adminName1":"California","adminName3":"","services":{"geonames":5391959},"areaLabel":"California, United States"},
+    {"coord":{"lon":"-46.63611","lat":"-23.5475"},"timezone":{"gmtOffset":-2,"dstOffset":-3,"timeZoneId":"America/Sao_Paulo"},"population":10021295,"adminName2":"São Paulo","name":"São Paulo","country":"BR","countryName":"Brazil","adminName1":"São Paulo","adminName3":"","services":{"geonames":3448439},"areaLabel":"São Paulo, Brazil"},
+    {"coord":{"lon":"121.45806","lat":"31.22222"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":14608512,"adminName2":"","name":"Shanghai","country":"CN","countryName":"China","adminName1":"Shanghai Shi","adminName3":"","services":{"geonames":1796236},"areaLabel":"Shanghai Shi, China"},
+    {"coord":{"lon":"114.0683","lat":"22.54554"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3000000,"adminName2":"Shenzhen","name":"Shenzhen","country":"CN","countryName":"China","adminName1":"Guangdong Province","adminName3":"","services":{"geonames":1795565},"areaLabel":"Guangdong Province, Shenzhen, China"},
+    {"coord":{"lon":"103.85007","lat":"1.28967"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Singapore"},"population":3547809,"adminName2":"","name":"Singapore","country":"SG","countryName":"Singapore","adminName1":"","adminName3":"","services":{"geonames":1880252},"areaLabel":"Singapore"},
+    {"coord":{"lon":"151.20732","lat":"-33.86785"},"timezone":{"gmtOffset":11,"dstOffset":10,"timeZoneId":"Australia/Sydney"},"population":4627345,"adminName2":"City of Sydney","name":"Sydney","country":"AU","countryName":"Australia","adminName1":"New South Wales","adminName3":"","services":{"geonames":2147714},"areaLabel":"New South Wales, Australia"},
+    {"coord":{"lon":"51.42151","lat":"35.69439"},"timezone":{"gmtOffset":3.5,"dstOffset":4.5,"timeZoneId":"Asia/Tehran"},"population":7153309,"adminName2":"","name":"Tehran","country":"IR","countryName":"Iran","adminName1":"Tehrān","adminName3":"","services":{"geonames":112931},"areaLabel":"Tehrān, Iran, Islamic Republic of"},
+    {"coord":{"lon":"117.17667","lat":"39.14222"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3766207,"adminName2":"","name":"Tianjin","country":"CN","countryName":"China","adminName1":"Tianjin Shi","adminName3":"","services":{"geonames":1792947},"areaLabel":"Tianjin Shi, China"},
+    {"coord":{"lon":"139.69171","lat":"35.6895"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Tokyo"},"population":8336599,"adminName2":"","name":"Tokyo","country":"JP","countryName":"Japan","adminName1":"Tōkyō","adminName3":"","services":{"geonames":1850147},"areaLabel":"Tōkyō, Japan"},
+    {"coord":{"lon":"-79.4163","lat":"43.70011"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/Toronto"},"population":4612191,"adminName2":"","name":"Toronto","country":"CA","countryName":"Canada","adminName1":"Ontario","adminName3":"","services":{"geonames":6167865},"areaLabel":"Ontario, Canada"}
+]

=== added file 'app/data/Storage.qml'
--- app/data/Storage.qml	1970-01-01 00:00:00 +0000
+++ app/data/Storage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2013, 2014, 2015 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Martin Borho <martin@xxxxxxxxx>
+ */
+import QtQuick.LocalStorage 2.0
+import QtQuick 2.4
+
+Item {
+    property var db: null
+
+    function openDB() {
+        if(db !== null) return;
+
+        db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+
+        if (db.version === "") {
+            db.changeVersion("", "0.1",
+                function(tx) {
+                    tx.executeSql('CREATE TABLE IF NOT EXISTS Locations(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, date TEXT)');
+                    console.log('Database created');
+                });
+            // reopen database with new version number
+            db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+        }
+
+        if(db.version === "0.1") {
+            db.changeVersion("0.1", "0.2",
+                function(tx) {
+                    tx.executeSql('CREATE TABLE IF NOT EXISTS settings(key TEXT UNIQUE, value TEXT)');
+                    console.log('Settings table added, Database upgraded to v0.2');
+                });
+            // reopen database with new version number
+            db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+        }
+
+        if(db.version === "0.2") {
+            db.changeVersion("0.2", "0.3",
+                function(tx) {
+                    tx.executeSql('DELETE FROM Locations WHERE 1');
+                    console.log('Removed old locations, Database upgraded to v0.3');
+                });
+        }
+
+        if (!settings.migrated) {
+            try {  // attempt to read the old settings
+                var oldSettings = {};
+
+                // Load old settings
+                db.readTransaction( function(tx) {
+                    var rs = tx.executeSql("SELECT * FROM settings")
+
+                    for(var i = 0; i < rs.rows.length; i++) {
+                        var row = rs.rows.item(i);
+                        oldSettings[row.key] = row.value;
+                    }
+                });
+
+                console.debug("Migrating old data:", JSON.stringify(oldSettings))
+
+                var metric = Qt.locale().measurementSystem === Locale.MetricSystem
+
+                // Move to new Settings API
+                settings.migrated = true
+                settings.service = oldSettings["service"] === undefined ? "weatherchannel" : oldSettings["service"]
+
+                if (oldSettings["precip_units"] !== undefined) {
+                    settings.precipUnits = oldSettings["precip_units"]
+                } else {
+                    settings.precipUnits = metric ? "mm" : "in"
+                }
+
+                if (oldSettings["units"] !== undefined) {
+                    settings.tempScale = oldSettings["units"] === "metric" ? "°C" : "°F"
+                    settings.units = oldSettings["units"]
+                } else {
+                    settings.tempScale = metric ? "°C" : "°F"
+                    settings.units = metric ? "metric" : "imperial"
+                }
+
+                if (oldSettings["units"] !== undefined) {
+                    // If old wind speed was in "kmh" use "kph" instead
+                    settings.windUnits = oldSettings["wind_units"] === "kmh" ? "kph" : oldSettings["wind_units"]
+                } else {
+                    settings.windUnits = metric ? "kph" : "mph"
+                }
+
+                /*
+                  TODO: uncomment when reboot is ready to replace existing app
+                db.transaction( function(tx) {
+                    tx.executeSql("DROP TABLE IF EXISTS settings")
+                });
+                */
+            } catch (e) {  // likely table did not exist
+                console.debug("No old data to migrate.")
+                settings.migrated = true
+            }
+        }
+    }
+
+    function insertLocation(data) {
+        openDB();
+        var res;
+
+        db.transaction( function(tx){
+            var r = tx.executeSql('INSERT INTO Locations(data, date) VALUES(?, ?)', [JSON.stringify(data), new Date().getTime()]);
+            res = r.insertId;
+        });
+        return res;
+    }
+
+    function insertLocationAtStart(data) {
+        var res = insertLocation(data);
+        reorder(res, 0);
+        return 0;
+    }
+
+    function updateLocation(dbId, data) {
+        openDB();
+        db.transaction( function(tx){
+            var r = tx.executeSql('UPDATE Locations SET data = ?, date=? WHERE id = ?', [JSON.stringify(data), new Date().getTime(), dbId])
+        });
+    }
+
+    function getLocations(callback) {
+        openDB();
+        db.readTransaction(
+            function(tx){
+                var locations = [];
+                var rs = tx.executeSql('SELECT * FROM Locations');
+                for(var i = 0; i < rs.rows.length; i++) {
+                    var row = rs.rows.item(i),
+                        locData = JSON.parse(row.data);
+                    locData["updated"] = parseInt(row.date, 10);
+                    locData["db"] = {id: row.id, updated: new Date(parseInt(row.date, 10))};
+                    locations.push(locData);
+                }
+                callback(locations);
+            }
+        );
+    }
+
+    function clearLocation(location_id) {
+        openDB();
+        db.transaction(function(tx){
+            tx.executeSql('DELETE FROM Locations WHERE id = ?', [location_id]);
+        });
+    }
+
+    function clearMultiLocation(locations) {
+        openDB();
+
+        db.transaction(function (tx) {
+            // Remove all the deleted indexes
+            for (var i=0; i < locations.length; i++) {
+                tx.executeSql('DELETE FROM Locations WHERE id=?;', [locations[i]])
+            }
+
+            // Rebuild locations in order
+            var rs = tx.executeSql('SELECT id FROM Locations ORDER BY id ASC')
+
+            for (i=0; i < rs.rows.length; i++) {
+                tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+                              [i, rs.rows.item(i).id])
+            }
+        })
+    }
+
+    function clearDB() { // for dev purposes
+        openDB();
+        db.transaction(function(tx){
+            tx.executeSql('DELETE FROM Locations WHERE 1');
+        });
+    }
+
+    function reorder(from, to) {
+        openDB();
+
+        db.transaction(function(tx) {
+            // Track to move put as -1 for now
+            tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+                          [-1, from])
+
+            // Shuffle locations inbetween from->to
+            if (from > to) {
+                for (var i = from-1; i >= to; i--) {
+                    tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+                                  [i+1, i])
+                }
+            } else {
+                for (var j = from+1; j <= to; j++) {
+                    tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+                                  [j-1, j])
+                }
+            }
+
+            // Switch moving location to its new position
+            tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+                          [to, -1])
+        })
+    }
+}

=== added file 'app/data/WeatherApi.js'
--- app/data/WeatherApi.js	1970-01-01 00:00:00 +0000
+++ app/data/WeatherApi.js	2015-06-28 23:49:12 +0000
@@ -0,0 +1,754 @@
+.pragma library
+/*
+ * Copyright (C) 2013 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Raúl Yeguas <neokore@xxxxxxxxx>
+ *              Martin Borho <martin@xxxxxxxxx>
+ *              Andrew Starr-Bochicchio <a.starr.b@xxxxxxxxx>
+ */
+
+/**
+*  Version of the response data format.
+*  Increase this number to force a refresh.
+*/
+var RESPONSE_DATA_VERSION = 20150404;
+
+/**
+* Helper functions
+*/
+function debug(obj) {
+    print(JSON.stringify(obj))
+}
+//
+function calcFahrenheit(celsius) {
+        return celsius * 1.8 + 32;
+}
+//
+function calcMph(ms) {
+    return ms*2.24;
+}
+//
+function calcInch(mm) {
+    return mm/25.4;
+}
+//
+function calcKph(ms) {
+    return ms*3.6;
+}
+//
+function convertKphToMph(kph) {
+    return kph*0.621;
+}
+//
+function calcWindDir(degrees) {
+    var direction =  "?";
+    if(degrees >=0 && degrees <= 30){
+        direction = "N";
+    } else if(degrees >30 && degrees <= 60){
+        direction = "NE";
+    } else if(degrees >60 && degrees <= 120){
+        direction = "E";
+    } else if(degrees >120 && degrees <= 150){
+        direction = "SE";
+    } else if(degrees >150 && degrees <= 210){
+        direction = "S";
+    } else if(degrees >210 && degrees <= 240){
+        direction = "SW";
+    } else if(degrees >240 && degrees <= 300){
+        direction = "W";
+    } else if(degrees >300 && degrees <= 330){
+        direction = "NW";
+    } else if(degrees >330 && degrees <= 360){
+        direction = "N";
+    }
+    return direction;
+}
+
+//
+function getLocationTime(tstamp) {
+    var locTime = new Date(tstamp);
+    return {
+        year: locTime.getUTCFullYear(),
+        month: locTime.getUTCMonth(),
+        date: locTime.getUTCDate(),
+        hours: locTime.getUTCHours(),
+        minutes: locTime.getUTCMinutes()
+    }
+}
+// Serialize a JavaScript object to URL parameters
+// E.g. {param1: value1, param2: value2} to "param1=value&param2=value"
+// TODO: it'd be nice to make it work with either passing a single object
+// or several at once
+function parameterize(obj) {
+  var str = [];
+  for(var param in obj) {
+     str.push(encodeURIComponent(param) + "=" + encodeURIComponent(obj[param]));
+  }
+  return str.join("&");
+}
+
+var GeoipApi = (function() {
+    var _baseUrl = "http://geoip.ubuntu.com/lookup";;
+    return {
+        getLatLong: function(params, apiCaller, onSuccess, onError) {
+            var request = { type: "geolookup",url: _baseUrl},
+                resultHandler = (function(request, xmlDoc) {
+                    var coords = {},
+                        childNodes = xmlDoc.childNodes;
+                    for(var i=0;i<childNodes.length;i++) {
+                        if(childNodes[i].nodeName === "Latitude") {
+                            coords.lat = childNodes[i].firstChild.nodeValue;
+                        } else if(childNodes[i].nodeName === "Longitude") {
+                            coords.lon = childNodes[i].firstChild.nodeValue;
+                        }
+                    }
+                    onSuccess(coords);
+                }),
+                retryHandler = (function(err) {
+                    console.log("geolookup retry of "+err.request.url);
+                    apiCaller(request, resultHandler, onError);
+                });
+            apiCaller(request, resultHandler, retryHandler);
+        }
+    }
+})();
+
+var GeonamesApi = (function() {
+    /**
+      provides neccessary methods for requesting and preparing data from Geonames.org
+    */
+    var _baseUrl = "http://api.geonames.org/";;
+    var _username = "uweatherdev"
+    var _addParams = "&maxRows=25&featureClass=P"
+    //
+    function _buildSearchResult(request, data) {
+        var searchResult = { locations: [], request: request };
+        if(data.geonames) {
+            data.geonames.forEach(function(r) {
+               searchResult.locations.push({
+                    name: r.name,
+                    coord: {lat: r.lat, lon: r.lng},
+                    country: r.countryCode,
+                    countryName: r.countryName,
+                    timezone: r.timezone,
+                    adminName1: r.adminName1,
+                    adminName2: r.adminName2,
+                    adminName3: r.adminName3,
+                    population: r.population,
+                    services: {
+                        "geonames": r.geonameId
+                    }
+                });
+            })
+        }
+        return searchResult;
+    }
+    //
+    return {
+        //
+        search: function(mode, params, apiCaller, onSuccess, onError) {
+            var request,
+                retryHandler = (function(err) {
+                        console.log("search retry of "+err.request.url);
+                        apiCaller(request, searchResponseHandler, onError);
+                }),
+                searchResponseHandler = function(request, data) {
+                    onSuccess(_buildSearchResult(request, data));
+                };
+            if(mode === "point") {
+                request = { type: "search",
+                            url: _baseUrl+ "findNearbyPlaceNameJSON?style=full&username="+encodeURIComponent(_username)
+                            +"&lat="+encodeURIComponent(params.coords.lat)+"&lng="+encodeURIComponent(params.coords.lon)
+                            +_addParams}
+            } else {
+                request = { type: "search",
+                            url: _baseUrl+ "searchJSON?style=full&username="+encodeURIComponent(_username)
+                                    +"&name_startsWith="+encodeURIComponent(params.name)+_addParams}
+            }
+            apiCaller(request, searchResponseHandler, retryHandler);
+        }
+    }
+
+})();
+
+var OpenWeatherMapApi = (function() {
+    /**
+      provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
+    */
+    var _baseUrl = "http://api.openweathermap.org/data/2.5/";;
+    //
+    var _serviceName = "openweathermap";
+    //
+    var _icon_map = {
+        "01d": "sun",
+        "01n": "moon",
+        "02d": "cloud_sun",
+        "02n": "cloud_moon",
+        "03d": "cloud_sun",
+        "03n": "cloud_moon",
+        "04d": "cloud",
+        "04n": "cloud",
+        "09d": "rain",
+        "09n": "rain",
+        "10d": "rain",
+        "10n": "rain",
+        "11d": "thunder",
+        "11n": "thunder",
+        "13d": "snow_shower",
+        "13n": "snow_shower",
+        "50d": "fog",
+        "50n": "fog"
+    }    
+    //
+    function _buildDataPoint(date, data) {
+        var result = {
+            timestamp: data.dt,
+            date: date,
+            metric: {
+                temp:data.main.temp,
+                windSpeed: calcKph(data.wind.speed),
+                rain: data.main.rain || ((data.rain) ? data.rain["3h"] : false ) || 0,
+                snow: data.main.snow || ((data.snow) ? data.snow["3h"] : false ) || 0
+            },
+            imperial: {
+                temp: calcFahrenheit(data.main.temp),
+                windSpeed: calcMph(data.wind.speed),
+                rain: calcInch(data.main.rain || ((data.rain) ? data.rain["3h"] : false ) || 0),
+                snow: calcInch(data.main.snow || ((data.snow) ? data.snow["3h"] : false ) ||0)
+            },
+            humidity: data.main.humidity,
+            pressure: data.main.pressure,
+            windDeg: data.wind.deg,
+            windDir: calcWindDir(data.wind.deg),
+            icon: _icon_map[data.weather[0].icon],
+            condition: data.weather[0].main
+        };
+        if(data.id !== undefined) {
+            result["service"] = _serviceName;
+            result["service_id"] =  data.id;
+        }
+        return result;
+    }
+    //
+    function _buildDayFormat(date, data) {
+        var result = {
+            date: date,
+            timestamp: data.dt,
+            metric: {
+                tempMin: data.temp.min,
+                tempMax: data.temp.max,
+                windSpeed: calcKph(data.speed),
+                rain: data.rain || 0,
+                snow: data.snow || 0
+            },
+            imperial: {
+                tempMin: calcFahrenheit(data.temp.min),
+                tempMax: calcFahrenheit(data.temp.max),
+                windSpeed: calcMph(data.speed),
+                rain: calcInch(data.rain || 0),
+                snow: calcInch(data.snow || 0)
+            },
+            pressure: data.pressure,
+            humidity: data.humidity,
+            icon: _icon_map[data.weather[0].icon],
+            condition: data.weather[0].main,
+            windDeg: data.deg,
+            windDir: calcWindDir(data.deg),
+            hourly: []
+        }
+        return result;
+    }
+    //
+    function formatResult(data, location) {
+        var tmpResult = {},
+            result = [],
+            day=null,
+            offset=(location.timezone && location.timezone.gmtOffset) ? location.timezone.gmtOffset*60*60*1000: 0,
+            localNow = getLocationTime(new Date().getTime()+offset),
+            todayDate;        
+        print("["+location.name+"] "+JSON.stringify(localNow))
+        // add openweathermap id for faster responses
+        if(location.services && !location.services[_serviceName] && data["current"].id) {
+            location.services[_serviceName] = data["current"].id
+        }
+        //
+        data["daily"]["list"].forEach(function(dayData) {
+            var date = getLocationTime(((dayData.dt*1000)-1000)+offset), // minus 1 sec to handle +/-12 TZ
+                day = date.year+"-"+date.month+"-"+date.date;
+            if(!todayDate) {
+                if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {
+                    // skip "yesterday"
+                    return;
+                }
+                todayDate = date;
+            }
+            tmpResult[day] = _buildDayFormat(date, dayData);
+        })
+        //
+        var today = todayDate.year+"-"+todayDate.month+"-"+todayDate.date
+        tmpResult[today]["current"] = _buildDataPoint(todayDate, data["current"]);
+        if(data["forecast"] !== undefined) {
+            data["forecast"]["list"].forEach(function(hourData) {                
+                var dateData = getLocationTime((hourData.dt*1000)+offset),
+                    day = dateData.year+"-"+dateData.month+"-"+dateData.date;
+                if(tmpResult[day]) {
+                    tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));
+                }
+            })
+        }
+        //
+        for(var d in tmpResult) {
+            result.push(tmpResult[d]);
+        }
+        return result;
+    }
+    //
+    function _getUrls(params) {
+        var urls = {
+                current: "",
+                daily: "",
+                forecast: ""
+            },
+            latLongParams = "&lat="+encodeURIComponent(params.location.coord.lat)
+                + "&lon="+encodeURIComponent(params.location.coord.lon);
+        if(params.location.services && params.location.services[_serviceName]) {
+            urls.current = _baseUrl + "weather?units="+params.units+"&id="+params.location.services[_serviceName];
+            urls.daily = _baseUrl + "forecast/daily?id="+params.location.services[_serviceName]+"&cnt=10&units="+params.units
+            urls.forecast = _baseUrl + "forecast?id="+params.location.services[_serviceName]+"&units="+params.units
+
+        } else if (params.location.coord) {
+            urls.current = _baseUrl + "weather?units="+params.units+latLongParams;
+            urls.daily = _baseUrl+"forecast/daily?cnt=10&units="+params.units+latLongParams;
+            urls.forecast = _baseUrl+"forecast?units="+params.units+latLongParams;
+        }
+        return urls;
+    }
+    //
+    return {     
+        //
+        getData: function(params, apiCaller, onSuccess, onError) {
+            var urls = _getUrls(params),
+                handlerMap = {
+                current: { type: "current",url: urls.current},
+                daily: { type: "daily",url: urls.daily},
+                forecast: { type: "forecast", url: urls.forecast}},
+            response = {
+                location: params.location,
+                db: (params.db) ? params.db : null,
+                format: RESPONSE_DATA_VERSION
+            },
+            respData = {},
+            addDataToResponse = (function(request, data) {
+                var formattedResult;
+                respData[request.type] = data;
+                if(respData["current"] !== undefined
+                        && respData["forecast"] !== undefined
+                            && respData["daily"] !== undefined) {
+                    response["data"] = formatResult(respData, params.location)
+                    onSuccess(response);
+                }
+            }),
+            onErrorHandler = (function(err) {
+                onError(err);
+            }),
+            retryHandler = (function(err) {
+                console.log("retry of "+err.request.url);
+                var retryFunc = handlerMap[err.request.type];
+                apiCaller(retryFunc, addDataToResponse, onErrorHandler);
+            });
+            //
+            apiCaller(handlerMap.current, addDataToResponse, retryHandler);
+            apiCaller(handlerMap.forecast, addDataToResponse, retryHandler);
+            apiCaller(handlerMap.daily, addDataToResponse, retryHandler);
+        }
+    }
+
+})();
+
+var WeatherChannelApi = (function() {
+    /**
+      provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
+    */
+    var _baseUrl = "http://wxdata.weather.com/wxdata/";;
+    //
+    var _serviceName = "weatherchannel";
+    //
+    // see http://s.imwx.com/v.20131006.223722/img/wxicon/72/([0-9]+).png
+    var _iconMap = {
+        "0": "thunder", // ??
+        "1": "thunder", // ??
+        "2": "thunder", // ??
+        "3": "thunder", // ??
+        "4": "thunder", //T-Storms
+        "5": "snow_rain", //Rain / Snow
+        "6": "snow_rain", // ??
+        "7": "snow_rain", //Wintry Mix
+        "8": "scattered", //Freezing Drizzle
+        "9": "scattered", //Drizzle
+        "10": "rain", // ??
+        "11": "rain", //Showers
+        "12": "rain", //Rain
+        "13": "snow_shower", // ??
+        "14": "snow_shower", //Snow shower/Light snow
+        "15": "snow_shower", //
+        "16": "snow_shower", //Snow
+        "17": "thunder", // Hail??
+        "18": "snow_rain", // Rain / Snow ??
+        "19": "fog", //Fog ??
+        "20": "fog", //Fog
+        "21": "fog", //Haze
+        "22": "fog", // ??
+        "23": "fog", // Wind ??
+        "24": "overcast", //Partly Cloudy / Wind
+        "25": "overcast", // ??
+        "26": "overcast",//Cloudy
+        "27": "cloud_moon",//Mostly Cloudy
+        "28": "cloud_sun", //Mostly Cloudy
+        "29": "cloud_moon", //Partly Cloudy
+        "30": "cloud_sun", //Partly Cloudy
+        "31": "moon", //Clear
+        "32": "sun", //Sunny
+        "33": "cloud_moon", //Mostly Clear
+        "34": "cloud_sun", //Mostly Sunny
+        "35": "snow_rain", // ??
+        "36": "sun", //Sunny
+        "37": "thunder", //Isolated T-Storms
+        "38": "thunder", //Scattered T-Storms
+        "39": "scattered", //Scattered Showers
+        "40": "rain", // ??
+        "41": "snow", //Scattered Snow Showers
+        "42": "snow_shower", // ??
+        "43": "snow_shower", // ??
+        "44": "fog", // ??
+        "45": "scattered", // ??
+        "46": "snow_shower", //Snow Showers Early
+        "47": "thunder" //Isolated T-Storms
+    };
+    //
+    function _buildDataPoint(date, dataObj) {
+        var data = dataObj["Observation"] || dataObj,
+            result = {
+                timestamp: data.date || data.dateTime,
+                date: date,
+                metric: {
+                    temp: data.temp,
+                    tempFeels: data.feelsLike,
+                    windSpeed: data.wSpeed
+                },
+                imperial: {
+                    temp: calcFahrenheit(data.temp),
+                    tempFeels: calcFahrenheit(data.feelsLike),
+                    windSpeed: convertKphToMph(data.wSpeed)
+                },
+                precipType: (data.precip_type !== undefined) ? data.precip_type : null,
+                propPrecip: (data.pop !== undefined) ? data.pop : null,
+                humidity: data.humid,
+                pressure: data.pressure,
+                windDeg: data.wDir,
+                windDir: data.wDirText,
+                icon: _iconMap[(data.wxIcon||data.icon)],
+                condition: data.text || data.wDesc,
+                uv: data.uv
+        };
+        if(_iconMap[data.wxIcon||data.icon] === undefined) {
+            print("ICON MISSING POINT: "+(data.wxIcon||data.icon)+" "+result.condition)
+        }
+        return result;
+    }
+    //
+    function _buildDayFormat(date, data, now) {
+        var partData = (now > data.validDate || data.day === undefined) ? data.night : data.day,
+            result = {
+            date: date,
+            timestamp: data.validDate,
+            metric: {
+                tempMin: data.minTemp,
+                tempMax: data.maxTemp,
+                windSpeed: partData.wSpeed
+            },
+            imperial: {
+                tempMin: calcFahrenheit(data.minTemp),
+                tempMax: calcFahrenheit(data.maxTemp !== undefined ? data.maxTemp : data.minTemp),
+                windSpeed: convertKphToMph(partData.wSpeed)
+            },
+            precipType: partData.precip_type,
+            propPrecip: partData.pop,
+            pressure: null,
+            humidity: partData.humid,
+            icon: _iconMap[partData.icon],
+            condition: partData.phrase,
+            windDeg: partData.wDir,
+            windDir: partData.wDirText,
+            uv: partData.uv,
+            hourly: []
+        }
+        if(_iconMap[partData.icon] === undefined) {
+            print("ICON MISSING  DAY: "+partData.icon+" "+result.condition)
+        }
+        return result;
+    }
+    //
+    function formatResult(combinedData, location) {
+        var tmpResult = {}, result = [],
+            day=null, todayDate,
+            offset=(location.timezone && location.timezone.gmtOffset) ? location.timezone.gmtOffset*60*60*1000: 0,
+            now = new Date().getTime(),
+            nowMs = parseInt(now/1000),
+            localNow = getLocationTime(now+offset),
+            data = {
+                "location": combinedData[0]["Location"],
+                "daily": combinedData[0]["DailyForecasts"],
+                "forecast": combinedData[0]["HourlyForecasts"],
+                "current": combinedData[0]["StandardObservation"],
+            };
+        print("["+location.name+"] "+JSON.stringify(localNow));
+        // add openweathermap id for faster responses
+        if(location.services && !location.services[_serviceName] && data["location"].key) {
+            location.services[_serviceName] = data["location"].key
+        }                
+        // only 5 days of forecast for TWC
+        for(var x=0;x<5;x++) {
+            var dayData = data["daily"][x],
+                date = getLocationTime(((dayData.validDate*1000)-1000)+offset); // minus 1 sec to handle +/-12 TZ
+            day = date.year+"-"+date.month+"-"+date.date;
+            if(!todayDate) {
+                if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {
+                    // skip "yesterday"
+                    continue;
+                }
+                todayDate = date;
+            }
+            tmpResult[day] = _buildDayFormat(date, dayData, nowMs);
+        }
+        //
+        if(data["forecast"] !== undefined) {
+            data["forecast"].forEach(function(hourData) {
+                var dateData = getLocationTime((hourData.dateTime*1000)+offset),
+                    day = dateData.year+"-"+dateData.month+"-"+dateData.date;
+                if(tmpResult[day]) {
+                    tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));
+                }
+            })
+        }
+        //
+        if(data["current"]) {
+            var today = todayDate.year+"-"+todayDate.month+"-"+todayDate.date;
+            tmpResult[today]["current"] = _buildDataPoint(todayDate, data["current"]);
+            // if the icon is missing, use the first from the hourly forecast
+            if(!tmpResult[today]["current"].icon && tmpResult[today]["hourly"] && tmpResult[today]["hourly"].length > 0) {
+                tmpResult[today]["current"].icon = tmpResult[today]["hourly"][0].icon;
+            }
+            // if condition text is missing, use the condition from the first hourly forecast
+            if((tmpResult[today]["current"].condition === "-" || tmpResult[today]["current"].condition === undefined)
+                && (tmpResult[today]["hourly"] && tmpResult[today]["hourly"].length > 0)) {
+                    tmpResult[today]["current"].condition = tmpResult[today]["hourly"][0].condition;
+            }
+        }
+        //
+        for(var d in tmpResult) {
+            result.push(tmpResult[d]);
+        }
+        return result;
+    }
+    //
+    function _getUrl(params) {
+        var url, serviceId,
+            baseParams = {
+                key: params.api_key,
+                units: (params.units === "metric") ? "m" : "e",
+                locale: Qt.locale().name,
+                hours: "48",
+            },
+            commands = {
+                "mobileaggregation": "mobile/mobagg/",
+            };
+        if(params.location.services && params.location.services[_serviceName]) {
+            serviceId = encodeURIComponent(params.location.services[_serviceName]);
+            url = _baseUrl+commands["mobileaggregation"]+serviceId+".js?"+parameterize(baseParams);
+        } else if (params.location.coord) {
+            var coord = {lat: params.location.coord.lat, lng: params.location.coord.lon};
+            url = _baseUrl+commands["mobileaggregation"]+"get.js?"+parameterize(baseParams)+"&"+
+                  parameterize(coord);
+        }
+        return url;
+    }
+    //
+    return {
+        getData: function(params, apiCaller, onSuccess, onError) {
+            var url = _getUrl(params),
+                handlerMap = {
+                    all: { type: "all", url: url}
+                },
+                response = {
+                    location: params.location,
+                    db: (params.db) ? params.db : null,
+                    format: RESPONSE_DATA_VERSION
+                },
+                addDataToResponse = (function(request, data) {
+                    var formattedResult;
+                    response["data"] = formatResult(data, params.location);
+                    onSuccess(response);
+                }),
+                onErrorHandler = (function(err) {
+                    onError(err);
+                });
+            apiCaller(handlerMap.all, addDataToResponse, onErrorHandler);
+        }
+    }
+})();
+
+var WeatherApi = (function(_services) {
+    /**
+      proxy for requesting weather apis, the passed _services are providing the respective api endpoints
+      and formatters to build a uniform response object
+    */
+    function _getService(name) {
+        if(_services[name] !== undefined) {
+            return _services[name];
+        }
+        return _services["weatherchannel"];
+    }
+    //
+    function _sendRequest(request, onSuccess, onError) {
+        var xmlHttp = new XMLHttpRequest();
+        if (xmlHttp) {
+            console.log("Sent request URL: " + request.url);
+            xmlHttp.open('GET', request.url, true);
+            xmlHttp.onreadystatechange = function () {
+                try {
+                    if (xmlHttp.readyState == 4) {
+                        if(xmlHttp.status === 200) {
+                            if(xmlHttp.responseXML) {
+                                onSuccess(request, xmlHttp.responseXML.documentElement);
+                            } else {
+                                var json = JSON.parse(xmlHttp.responseText);
+                                onSuccess(request,json);
+                            }
+                        } else {
+                            onError({
+                                msg: "wrong response http code, got "+xmlHttp.status,
+                                request: request
+                            });
+                        }
+                    }
+                } catch (e) {
+                    print("Exception: "+e)
+                    onError({msg: "wrong response data format", request: request});
+                }
+            };
+            xmlHttp.send(null);
+        }
+    }
+    //
+    return  {
+        //
+        geoLookup: function(params, onSuccess, onError) {
+            var service = _getService('geoip'),
+                geoNameService = _getService('geonames'),
+                lookupHandler = function(data) {
+                    print("Geolookup: "+JSON.stringify(data))
+                    geoNameService.search("point", {coords:data}, _sendRequest, onSuccess, onError);
+                };
+            service.getLatLong(params, _sendRequest, lookupHandler, onError)
+        },
+        //
+        search: function(mode, params, onSuccess, onError) {
+            var service = _getService('geonames');
+            service.search(mode, params, _sendRequest, onSuccess, onError);
+        },
+        //
+        getLocationData: function(params, onSuccess, onError) {
+            var service = _getService(params.service);
+            service.getData(params, _sendRequest, onSuccess, onError);
+        },
+    }
+})({
+    "openweathermap": OpenWeatherMapApi,
+    "weatherchannel": WeatherChannelApi,
+    "geonames": GeonamesApi,
+    "geoip": GeoipApi
+});
+
+var sendRequest = function(message, responseCallback) {
+    // handles the response data
+    var finished = function(result) {
+        // print result to get data for test json files
+        // print(JSON.stringify(result));
+        //WorkerScript.sendMessage({
+        responseCallback({
+            action: message.action,
+            result: result
+        })
+    }
+    // handles errors
+    var onError = function(err) {
+        console.log(JSON.stringify(err, null, true));
+        //WorkerScript.sendMessage({ 'error': err})
+        responseCallback({ 'error': err})
+    }
+    // keep order of locations, sort results
+    var sortDataResults = function(locA, locB) {
+        return locA.db.id - locB.db.id;
+    }
+    // perform the api calls
+    if(message.action === "searchByName") {
+        WeatherApi.search("name", message.params, finished, onError);
+    } else if(message.action === "searchByPoint") {
+        WeatherApi.search("point", message.params, finished, onError);
+    } else if(message.action === "getGeoIp") {
+        WeatherApi.geoLookup(message.params, finished, onError);
+    } else if(message.action === "updateData") {
+        var locLength = message.params.locations.length,
+            locUpdated = 0,
+            result = [],
+            now = new Date().getTime();
+        if(locLength > 0) {
+            message.params.locations.forEach(function(loc) {
+                var updatedHnd = function (newData, cached) {
+                        locUpdated += 1;
+                        if(cached === true) {
+                            newData["save"] = false;
+                        } else {
+                            newData["save"] = true;
+                            newData["updated"] =  new Date().getTime();
+                        }
+                        result.push(newData);
+                        if(locUpdated === locLength) {
+                            result.sort(sortDataResults);
+                            finished(result);
+                        }
+                    },
+                    params = {
+                        location:loc.location,
+                        db: loc.db,
+                        units: 'metric',
+                        service: message.params.service,
+                        api_key: message.params.api_key,
+                        interval: message.params.interval
+                    },
+                    secsFromLastFetch = (now-loc.updated)/1000;
+                if( message.params.force===true || loc.format !== RESPONSE_DATA_VERSION || secsFromLastFetch > params.interval){
+                    // data older than 30min, location is new or data format is deprecated
+                    WeatherApi.getLocationData(params, updatedHnd, onError);
+                } else {
+                    console.log("["+loc.location.name+"] returning cached data, time from last fetch: "+secsFromLastFetch)
+                    updatedHnd(loc, true);
+                }
+            })
+        } else {
+            finished(result);
+        }
+    }
+}

=== added file 'app/data/key.js'
--- app/data/key.js	1970-01-01 00:00:00 +0000
+++ app/data/key.js	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+var twcKey = "";

=== added directory 'app/graphics'
=== added file 'app/graphics/Big-Rain.png'
Binary files app/graphics/Big-Rain.png	1970-01-01 00:00:00 +0000 and app/graphics/Big-Rain.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/CMakeLists.txt'
--- app/graphics/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/graphics/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB IMAGE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.png *.svg)
+
+add_custom_target(ubuntu-weather-app_graphics_IMAGEFiles ALL SOURCES ${IMAGE_FILES})
+
+install(FILES ${IMAGE_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/graphics)

=== added file 'app/graphics/Cloudy-Circles.png'
Binary files app/graphics/Cloudy-Circles.png	1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Circles.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy-Night.png'
Binary files app/graphics/Cloudy-Night.png	1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Night.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy-Snow.png'
Binary files app/graphics/Cloudy-Snow.png	1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Snow.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy.png'
Binary files app/graphics/Cloudy.png	1970-01-01 00:00:00 +0000 and app/graphics/Cloudy.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Fog.png'
Binary files app/graphics/Fog.png	1970-01-01 00:00:00 +0000 and app/graphics/Fog.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Raindrop.png'
Binary files app/graphics/Raindrop.png	1970-01-01 00:00:00 +0000 and app/graphics/Raindrop.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Showers.png'
Binary files app/graphics/Showers.png	1970-01-01 00:00:00 +0000 and app/graphics/Showers.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Starry-Night.png'
Binary files app/graphics/Starry-Night.png	1970-01-01 00:00:00 +0000 and app/graphics/Starry-Night.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Stormy.png'
Binary files app/graphics/Stormy.png	1970-01-01 00:00:00 +0000 and app/graphics/Stormy.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Sunny.png'
Binary files app/graphics/Sunny.png	1970-01-01 00:00:00 +0000 and app/graphics/Sunny.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Windy-n-Snow.png'
Binary files app/graphics/Windy-n-Snow.png	1970-01-01 00:00:00 +0000 and app/graphics/Windy-n-Snow.png	2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/extended-information_chance-of-rain.svg'
--- app/graphics/extended-information_chance-of-rain.svg	1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_chance-of-rain.svg	2015-06-28 23:49:12 +0000
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="90"
+   height="90"
+   id="svg6138"
+   version="1.1"
+   inkscape:version="0.91pre2 r"
+   viewBox="0 0 90 90.000001"
+   sodipodi:docname="weather-chance-of-rain02.svg">
+  <defs
+     id="defs6140" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="5.0931702"
+     inkscape:cx="112.1011"
+     inkscape:cy="19.378885"
+     inkscape:document-units="px"
+     inkscape:current-layer="g6442"
+     showgrid="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:snap-global="true"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:snap-bbox-midpoints="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:bbox-nodes="true"
+     inkscape:object-paths="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:snap-others="true"
+     inkscape:snap-object-midpoints="true"
+     inkscape:snap-center="true"
+     showguides="false"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid6700"
+       empspacing="6" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="90,87"
+       id="guide4064" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="89,84"
+       id="guide4066" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="3,69"
+       id="guide4068" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="6,60"
+       id="guide4070" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="87,58"
+       id="guide4072" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="84,47"
+       id="guide4074" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="77,3"
+       id="guide4076" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="81,6"
+       id="guide4078" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="81,25"
+       id="guide4080" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="78,9"
+       id="guide4082" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="79,81"
+       id="guide4084" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="9,39"
+       id="guide4086" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata6143">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-283.57144,-358.79068)">
+    <g
+       transform="translate(169.57144,223.42822)"
+       id="g5937"
+       inkscape:export-filename="envelope02.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+    <g
+       id="g6442"
+       transform="translate(-753.45981,336.4283)">
+      <rect
+         style="fill:none;stroke:none"
+         id="rect1687"
+         width="90"
+         height="90"
+         x="1037.0312"
+         y="22.362379" />
+      <path
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="M 44.929688 12.066406 C 34.037288 12.066406 24.849688 14.943458 17.367188 20.697266 C 9.7896875 26.523909 6 33.624202 6 42 C 7.7011 40.603306 9.6530686 39.474739 11.855469 38.615234 C 14.075869 37.773637 16.4573 37.353516 19 37.353516 C 21.5427 37.353516 23.914587 37.773637 26.117188 38.615234 C 28.337486 39.474739 30.2989 40.603306 32 42 C 33.7011 40.603306 35.653069 39.474739 37.855469 38.615234 C 40.075769 37.773637 42.4573 37.353516 45 37.353516 C 47.5427 37.353516 49.914586 37.773637 52.117188 38.615234 C 54.337487 39.474739 56.2989 40.603306 58 42 L 58.654297 42 C 60.235137 40.784631 62.009869 39.776665 64 39 C 66.2203 38.158403 68.601831 37.738281 71.144531 37.738281 C 73.687231 37.738281 76.059119 38.158403 78.261719 39 C 80.268226 39.776743 82.052173 40.784465 83.634766 42 L 84 42 C 84 33.624202 80.210313 26.523909 72.632812 20.697266 C 65.055613 14.943458 55.822287 12.066406 44.929688 12.066406 z "
+         transform="translate(1037.0313,22.36238)"
+         id="path4181" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:end;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 1079.0312,28.363281 0,66.335938 0.01,0.09766 c 0.015,0.226687 0.022,0.780446 0.022,1.509766 0,1.918939 -0.4328,2.763898 -1.0039,3.289062 l -0.01,0.0059 -0.01,0.0078 c -0.6357,0.592743 -1.5903,1.007813 -3.4297,1.007813 -1.8313,0 -2.8017,-0.41364 -3.461,-1.015626 -0.5541,-0.519673 -0.9882,-1.37472 -0.9882,-3.294921 0,-0.72932 0.01,-1.281931 0.021,-1.501953 l -5.9863,-0.410157 c -0.041,0.598238 -0.035,1.18279 -0.035,1.91211 0,2.983063 0.8627,5.791367 2.9003,7.691407 l 0.01,0.008 0.01,0.006 c 1.9716,1.8129 4.6659,2.60547 7.5332,2.60547 2.8642,0 5.5555,-0.79304 7.5136,-2.61524 2.0532,-1.89396 2.9356,-4.70919 2.9356,-7.695307 0,-0.72932 0.01,-1.312724 -0.033,-1.904297 l 0.01,0.197265 0,-67.236328 -6,1 z"
+         id="path4225"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cccscccscsccscccscsccccc" />
+    </g>
+  </g>
+</svg>

=== added file 'app/graphics/extended-information_humidity.svg'
--- app/graphics/extended-information_humidity.svg	1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_humidity.svg	2015-06-28 23:49:12 +0000
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="90"
+   height="90"
+   id="svg6138"
+   version="1.1"
+   inkscape:version="0.91pre2 r"
+   viewBox="0 0 90 90.000001"
+   sodipodi:docname="weather-chance-of-rain.svg">
+  <defs
+     id="defs6140" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4.0745362"
+     inkscape:cx="90.550182"
+     inkscape:cy="25.205316"
+     inkscape:document-units="px"
+     inkscape:current-layer="g6442"
+     showgrid="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:snap-global="false"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:snap-bbox-midpoints="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:bbox-nodes="true"
+     inkscape:object-paths="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:snap-others="true"
+     inkscape:snap-object-midpoints="true"
+     inkscape:snap-center="true"
+     showguides="false"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid6700"
+       empspacing="6" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="90,87"
+       id="guide4064" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="89,84"
+       id="guide4066" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="3,69"
+       id="guide4068" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="6,60"
+       id="guide4070" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="87,58"
+       id="guide4072" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="84,47"
+       id="guide4074" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="77,3"
+       id="guide4076" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="81,6"
+       id="guide4078" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="81,25"
+       id="guide4080" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="78,9"
+       id="guide4082" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="79,81"
+       id="guide4084" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="9,39"
+       id="guide4086" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata6143">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-283.57144,-358.79068)">
+    <g
+       transform="translate(169.57144,223.42822)"
+       id="g5937"
+       inkscape:export-filename="envelope02.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+    <g
+       id="g6442"
+       transform="translate(-753.45981,336.4283)">
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="M 45.046875 3 C 32.915975 27.594868 24.552722 55.021065 23.982422 56.908203 C 23.901522 57.129789 23.841578 57.361086 23.767578 57.585938 C 23.036878 59.796557 22.623047 62.152027 22.623047 64.607422 C 22.623047 76.973464 32.652638 86.998047 45.023438 86.998047 C 57.372742 86.972595 67.376953 76.959723 67.376953 64.609375 C 67.376953 62.155598 66.975594 59.797236 66.246094 57.587891 C 66.246094 57.587891 58.008075 29.900931 45.046875 3.0390625 L 45.046875 3.03125 L 45.046875 3.0195312 L 45.046875 3.0058594 L 45.046875 3 z M 44.996094 17.728516 C 54.458094 39.650002 60.503906 59.296875 60.503906 59.296875 A 5.9907304 5.9907304 0 0 0 60.558594 59.466797 C 61.095294 61.092212 61.386719 62.80886 61.386719 64.609375 C 61.386719 73.725917 54.126659 80.989079 45.005859 81.005859 C 35.879459 80.996009 28.613281 73.728301 28.613281 64.607422 C 28.613281 62.825026 28.910878 61.113375 29.455078 59.466797 A 5.9907304 5.9907304 0 0 0 29.457031 59.458984 C 29.615031 58.978962 29.665675 58.806709 29.609375 58.960938 A 5.9907304 5.9907304 0 0 0 29.716797 58.638672 C 30.156997 57.182234 36.199294 37.969649 44.996094 17.728516 z "
+         transform="translate(1037.0313,22.36238)"
+         id="path4253" />
+      <rect
+         style="fill:none;stroke:none"
+         id="rect1687"
+         width="90"
+         height="90"
+         x="1037.0312"
+         y="22.362379" />
+    </g>
+  </g>
+</svg>

=== added file 'app/graphics/extended-information_uv-level.svg'
--- app/graphics/extended-information_uv-level.svg	1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_uv-level.svg	2015-06-28 23:49:12 +0000
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="90"
+   height="90"
+   id="svg6138"
+   version="1.1"
+   inkscape:version="0.91pre2 r"
+   viewBox="0 0 90 90.000001"
+   sodipodi:docname="weather-uv-level.svg">
+  <defs
+     id="defs6140">
+    <linearGradient
+       id="linearGradient3899">
+      <stop
+         id="stop4292"
+         offset="0"
+         style="stop-color:#fbf7e3;stop-opacity:1" />
+      <stop
+         id="stop4294"
+         offset="1"
+         style="stop-color:#c6b386;stop-opacity:1" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9580785"
+     inkscape:cx="32.979067"
+     inkscape:cy="45.789944"
+     inkscape:document-units="px"
+     inkscape:current-layer="g6442"
+     showgrid="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:snap-global="true"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:snap-bbox-midpoints="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:bbox-nodes="true"
+     inkscape:object-paths="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:snap-others="true"
+     inkscape:snap-object-midpoints="true"
+     inkscape:snap-center="true"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid6700"
+       empspacing="6" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="90,87"
+       id="guide4064" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="89,84"
+       id="guide4066" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="3,69"
+       id="guide4068" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="6,60"
+       id="guide4070" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="87,58"
+       id="guide4072" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="84,47"
+       id="guide4074" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="77,3"
+       id="guide4076" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="81,6"
+       id="guide4078" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="81,25"
+       id="guide4080" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="78,9"
+       id="guide4082" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="79,81"
+       id="guide4084" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="9,39"
+       id="guide4086" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata6143">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-283.57144,-358.79068)">
+    <g
+       transform="translate(169.57144,223.42822)"
+       id="g5937"
+       inkscape:export-filename="envelope02.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+    <g
+       id="g6442"
+       transform="translate(-753.45981,336.4283)">
+      <rect
+         style="fill:none;stroke:none"
+         id="rect1687"
+         width="90"
+         height="90"
+         x="1037.0312"
+         y="22.362379" />
+      <path
+         style="color:#000000;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         d="m 1054.1015,75.51863 a 33.872092,33.872092 0 0 0 0.5547,2.339844 33.872092,33.872092 0 0 1 -0.5547,-2.339844 z"
+         id="path4162" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.25;marker:none;enable-background:accumulate"
+         d="M 45 12 A 33.00005 33.00005 0 0 0 12 45 A 33.00005 33.00005 0 0 0 45 78 A 33.00005 33.00005 0 0 0 78 45 A 33.00005 33.00005 0 0 0 45 12 z M 45 18 A 27 27 0 0 1 72 45 A 27 27 0 0 1 45 72 A 27 27 0 0 1 18 45 A 27 27 0 0 1 45 18 z "
+         transform="translate(1037.0313,22.36238)"
+         id="path4189" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="-37.504247"
+         inkscape:transform-center-x="-0.0125"
+         inkscape:connector-curvature="0"
+         id="path2194-5"
+         d="m 1088.2477,35.36157 c 0,0 -2.5383,-5.872205 -6.1954,-11.007637 0,0 0,0 -0.01,0.0017 0,0 0,0.0017 -0.01,0.0024 0,0 -0.01,0.002 -0.01,0.0026 0,0 -0.01,0.0017 -0.01,0.0017 -3.7738,5.416839 -6.1725,11 -6.1725,11 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1103.4153,42.757091 c 0,0 0.7378,-6.354629 0.1384,-12.630594 0,0 0,0 -0.01,-0.0035 0,0 -9e-4,0.0015 -0.01,-0.0029 0,0 -0.01,-0.0033 -0.01,-0.0027 0,0 -0.01,-0.0035 -0.01,-0.0035 -5.9767,2.80422 -10.8456,6.440029 -10.8456,6.440029 z"
+         id="path4192"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="-16.197006"
+         inkscape:transform-center-y="-30.926886"
+         sodipodi:nodetypes="ccsssccc" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="-16.173909"
+         inkscape:transform-center-x="-30.9307"
+         inkscape:connector-curvature="0"
+         id="path4194"
+         d="m 1112.853,56.745599 c 0,0 3.8163,-5.13437 6.4352,-10.869215 0,0 0,0 -0.01,-0.008 0,0 0,8.49e-4 -0.01,-0.0075 0,0 -0.01,-0.0079 -0.01,-0.0073 0,0 -0.01,-0.008 -0.01,-0.008 -6.578,-0.559824 -12.6125,0.154429 -12.6125,0.154429 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1114.0321,73.578853 c 0,0 5.8722,-2.538345 11.0076,-6.195417 0,0 0,0 0,-0.01193 0,0 -5e-4,7.35e-4 -0.01,-0.0115 0,0 0,-0.01184 0,-0.01132 0,0 0,-0.01193 0,-0.01193 -5.4168,-3.773822 -11,-6.172511 -11,-6.172511 z"
+         id="path4196"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="-37.50345"
+         inkscape:transform-center-y="0.0091698086"
+         sodipodi:nodetypes="ccsssccc" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="16.194334"
+         inkscape:transform-center-x="-30.9356"
+         inkscape:connector-curvature="0"
+         id="path4198"
+         d="m 1106.6366,88.746428 c 0,0 6.3546,0.737829 12.6305,0.138412 0,0 0,0 0.01,-0.01033 0,0 -8e-4,3.87e-4 0,-0.01496 0,0 0.01,-0.01025 0.01,-0.0098 0,0 0.01,-0.01033 0.01,-0.01033 -2.8042,-5.976626 -6.44,-10.845552 -6.44,-10.845552 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1092.6481,98.184183 c 0,0 5.1343,3.816277 10.8691,6.435117 0,0 0,0 0.014,-0.004 0,0 -8e-4,-7e-5 0.01,-0.013 0,0 0.014,-0.004 0.014,-0.003 0,0 0.014,-0.004 0.014,-0.004 0.5598,-6.578013 -0.1544,-12.612527 -0.1544,-12.612527 z"
+         id="path4200"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="-16.184519"
+         inkscape:transform-center-y="30.938658"
+         sodipodi:nodetypes="ccsssccc" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="37.507651"
+         inkscape:transform-center-x="0.003949878"
+         inkscape:connector-curvature="0"
+         id="path4202"
+         d="m 1075.8148,99.363269 c 0,0 2.5383,5.872141 6.1954,11.007521 0,0 0,0 0.014,0.004 0,0 -7e-4,-4.6e-4 0.015,-0.006 0,0 0.014,0.004 0.014,0.004 0,0 0.014,0.004 0.014,0.004 3.7738,-5.41683 6.1726,-10.999969 6.1726,-10.999969 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1060.6472,91.967738 c 0,0 -0.7378,6.354573 -0.1384,12.630492 0,0 0,0 0.01,0.0105 0,0 -3e-4,-7.4e-4 0.016,0.002 0,0 0.01,0.0105 0.01,0.0105 0,0 0.01,0.0105 0.01,0.0105 5.9766,-2.80422 10.8456,-6.439956 10.8456,-6.439956 z"
+         id="path4204"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="16.194006"
+         inkscape:transform-center-y="30.937355"
+         sodipodi:nodetypes="ccsssccc" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="16.184506"
+         inkscape:transform-center-x="30.94415"
+         inkscape:connector-curvature="0"
+         id="path4206"
+         d="m 1051.2094,77.97922 c 0,0 -3.8162,5.134322 -6.4351,10.869127 0,0 0,0 0,0.01409 0,0 2e-4,-7.9e-4 0.013,0.0097 0,0 0,0.01409 0,0.01409 0,0 0,0.01409 0,0.01409 6.578,0.559774 12.6126,-0.154365 12.6126,-0.154365 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1050.0303,61.145908 c 0,0 -5.8721,2.538353 -11.0075,6.19539 0,0 0,0 -0.01,0.0122 0,0 5e-4,-5.85e-4 0.01,0.0149 0,0 -0.01,0.0122 -0.01,0.0122 0,0 -0.01,0.0122 -0.01,0.0122 5.4168,3.773778 11,6.172616 11,6.172616 z"
+         id="path4208"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="37.5147"
+         inkscape:transform-center-y="-0.0067180911"
+         sodipodi:nodetypes="ccsssccc" />
+      <path
+         sodipodi:nodetypes="ccsssccc"
+         inkscape:transform-center-y="-16.199665"
+         inkscape:transform-center-x="30.94315"
+         inkscape:connector-curvature="0"
+         id="path4210"
+         d="m 1057.4258,45.978282 c 0,0 -6.3545,-0.737771 -12.6304,-0.138384 0,0 0,0 -0.015,0.0056 0,0 7e-4,-2.57e-4 0,0.0179 0,0 -0.015,0.0056 -0.015,0.0056 0,0 -0.015,0.0056 -0.015,0.0056 2.8042,5.976587 6.44,10.845642 6.44,10.845642 z"
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+      <path
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+         d="m 1071.4144,36.540483 c 0,0 -5.1343,-3.816179 -10.8691,-6.435044 0,0 0,0 -0.016,-0.0026 0,0 7e-4,1.27e-4 -0.01,0.0155 0,0 -0.016,-0.0026 -0.016,-0.0026 0,0 -0.016,-0.0026 -0.016,-0.0026 -0.5598,6.577976 0.1544,12.612601 0.1544,12.612601 z"
+         id="path4212"
+         inkscape:connector-curvature="0"
+         inkscape:transform-center-x="16.187519"
+         inkscape:transform-center-y="-30.94809"
+         sodipodi:nodetypes="ccsssccc" />
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:31.51123047px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Medium';text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         x="1103.2705"
+         y="79.316757"
+         id="text4214"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan4216"
+           x="1103.2705"
+           y="79.316757">UV</tspan></text>
+    </g>
+  </g>
+</svg>

=== added file 'app/graphics/extended-information_wind.svg'
--- app/graphics/extended-information_wind.svg	1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_wind.svg	2015-06-28 23:49:12 +0000
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="90"
+   height="90"
+   id="svg6138"
+   version="1.1"
+   inkscape:version="0.91pre2 r"
+   viewBox="0 0 90 90.000001"
+   sodipodi:docname="weather-chance-of-wind02.svg">
+  <defs
+     id="defs6140" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="3.2596289"
+     inkscape:cx="81.435648"
+     inkscape:cy="24.842587"
+     inkscape:document-units="px"
+     inkscape:current-layer="g6442"
+     showgrid="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:snap-global="true"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:snap-bbox-midpoints="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:bbox-nodes="true"
+     inkscape:object-paths="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:snap-others="true"
+     inkscape:snap-object-midpoints="true"
+     inkscape:snap-center="true"
+     showguides="false"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid6700"
+       empspacing="6" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="90,87"
+       id="guide4064" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="89,84"
+       id="guide4066" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="3,69"
+       id="guide4068" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="6,60"
+       id="guide4070" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="87,58"
+       id="guide4072" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="84,47"
+       id="guide4074" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="77,3"
+       id="guide4076" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="81,6"
+       id="guide4078" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="81,25"
+       id="guide4080" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="78,9"
+       id="guide4082" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="79,81"
+       id="guide4084" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="9,39"
+       id="guide4086" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata6143">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-283.57144,-358.79068)">
+    <g
+       transform="translate(169.57144,223.42822)"
+       id="g5937"
+       inkscape:export-filename="envelope02.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+    <g
+       id="g6442"
+       transform="translate(-753.45981,336.4283)">
+      <rect
+         style="fill:none;stroke:none"
+         id="rect1687"
+         width="90"
+         height="90"
+         x="1037.0312"
+         y="22.362379" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:end;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 75.279297,28.75 c -1.0334,0 -2.070116,0.17314 -3.103516,0.517578 -1.0332,0.301421 -1.957037,0.799407 -2.773437,1.488281 -0.7749,0.645859 -1.426106,1.497766 -1.941406,2.574219 -0.193801,0.386934 -0.311535,0.83777 -0.427735,1.285156 l 4.048828,2.333985 c 0.066,-0.47516 0.09082,-0.97049 0.265625,-1.34961 0.2583,-0.516687 0.599203,-0.93497 1.033203,-1.236328 0.4327,-0.344501 0.877363,-0.575907 1.351563,-0.705078 0.5166,-0.172251 1.013028,-0.253906 1.486328,-0.253906 0.9909,0 1.914591,0.365704 2.775391,1.097656 0.9043,0.688938 1.349609,1.71392 1.349609,3.091797 0,1.463904 -0.463956,2.57267 -1.410156,3.304688 -0.9054,0.731951 -2.2137,1.097656 -3.9375,1.097656 l -0.90625,0 -5.109375,0 -2.976563,0 -53.367237,0 0,0.0039 c -1.118972,8.07e-4 -2.232917,0.175074 -3.2753908,0.546875 -1.0812,0.374449 -2.0898969,0.990457 -2.9042969,1.802734 -0.8511,0.837684 -1.4685875,1.874775 -1.8671875,2.980469 -0.422,1.158702 -0.5898438,2.388837 -0.5898438,3.673828 0,1.287641 0.1681907,2.523171 0.5878907,3.6875 l 0,0.0059 0,0.0078 c 0.4008,1.096658 1.0169625,2.124759 1.8515625,2.960938 l 0.011719,0.01172 0.011719,0.01172 c 0.8122,0.795376 1.8055406,1.402256 2.8691406,1.785156 l 0.023437,0.0059 0.019531,0.0078 c 1.041,0.357709 2.145119,0.521485 3.261719,0.521485 l 0,0.002 41.3438,0 2.980469,0 5.115234,0 0.908203,0 c 1.7259,0 3.034806,0.36677 3.941406,1.099609 0.9473,0.732906 1.414063,1.842913 1.414063,3.308594 0,1.379548 -0.446163,2.403976 -1.351563,3.09375 -0.8619,0.73284 -1.787297,1.099609 -2.779297,1.099609 -0.4739,0 -0.970981,-0.08145 -1.488281,-0.253906 -0.4746,-0.129329 -0.920316,-0.362111 -1.353515,-0.707031 -0.434501,-0.301723 -0.776457,-0.720967 -1.035157,-1.238282 -0.1741,-0.379579 -0.200625,-0.873874 -0.265625,-1.349609 l -4.052734,2.335938 c 0.1164,0.44793 0.231781,0.899706 0.425781,1.287109 0.5159,1.07776 1.169413,1.931483 1.945313,2.578125 0.8175,0.689709 1.742843,1.188446 2.777343,1.490234 1.0347,0.344855 2.072722,0.517579 3.107422,0.517579 0.7758,0 1.660344,-0.153292 2.652344,-0.455079 1.0346,-0.258658 1.998997,-0.75733 2.904297,-1.490234 0.906,-0.68971 1.658365,-1.634709 2.259766,-2.841797 0.605199,-1.163958 0.910156,-2.648384 0.910156,-4.458984 l 0.0039,0 C 72,60.363013 70.961878,57.768642 68.892578,56.259766 66.823378,54.750954 63.956869,54 60.292969,54 l -48.6563,0 0,0.0098 c -0.5345,0 -0.948916,-0.07592 -1.291016,-0.191407 -0.2996,-0.111208 -0.4927686,-0.23897 -0.6679686,-0.408203 -0.1552,-0.158288 -0.3061656,-0.371439 -0.4472656,-0.751953 l 0,-0.0039 c -0.1279,-0.355148 -0.2324219,-0.897858 -0.2324219,-1.650391 0,-0.752625 0.1039625,-1.28965 0.2265625,-1.625 l 0,-0.0059 0,-0.0078 c 0.1465,-0.406946 0.2963938,-0.609758 0.4335938,-0.74414 l 0.011719,-0.01172 0.011719,-0.01172 c 0.1749,-0.175302 0.3458723,-0.286102 0.6386723,-0.386718 l 0.01953,-0.0078 0.02148,-0.0078 C 10.695178,48.075095 11.107669,48 11.636669,48 l 60.669972,0 c 3.6595,0 6.523043,-0.750829 8.589843,-2.257812 C 82.963184,44.23514 84,41.644264 84,37.984375 l -0.0039,0 c 0,-1.808407 -0.303703,-3.290578 -0.908203,-4.453125 -0.6007,-1.205626 -1.352913,-2.149016 -2.257813,-2.837891 -0.9043,-0.732015 -1.86719,-1.229936 -2.90039,-1.488281 C 76.938787,28.903658 76.054197,28.75 75.279297,28.75 Z"
+         transform="translate(1037.0313,22.36238)"
+         id="path4178"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="sccccccccscscsccccccccsccccccccccccccscscsccccccccscccccssccccccscccccccccssccccccs" />
+      <path
+         sodipodi:nodetypes="ccccsccccccccscscscccccsscc"
+         inkscape:connector-curvature="0"
+         id="path4171"
+         d="m 1091.0274,48.500103 c 0,-1.780677 -0.2989,-3.239878 -0.8941,-4.384599 -0.5915,-1.187139 -1.3323,-2.116397 -2.2233,-2.794708 -0.8904,-0.720792 -1.8392,-1.211305 -2.8567,-1.465688 -0.9756,-0.2968 -1.8456,-0.44714 -2.6087,-0.44714 -1.0176,0 -2.0383,0.170054 -3.0558,0.50921 -1.0175,0.2968 -1.9269,0.787377 -2.7308,1.465687 -0.7631,0.635956 -1.4055,1.473955 -1.9129,2.533903 -0.1908,0.381001 -0.3053,0.826425 -0.4198,1.266952 l 3.9862,2.297899 c 0.064,-0.467873 0.089,-0.955778 0.2607,-1.329084 0.2544,-0.508765 0.5915,-0.920483 1.0188,-1.21722 0.4261,-0.339219 0.8637,-0.568417 1.3305,-0.695608 0.5087,-0.16961 0.9978,-0.248405 1.4639,-0.248405 0.9756,0 1.885,0.359888 2.7327,1.080617 0.8904,0.678374 1.3292,1.686427 1.3292,3.043176 0,1.441458 -0.4579,2.533521 -1.3896,3.254314 -0.8916,0.720728 -2.1794,1.080616 -3.8768,1.080616 l -0.8929,0 -5.031,0 -2.9311,0 -17.2945,0 0,5.912355 24.4865,0 c 3.6033,0 6.4232,-0.739426 8.4582,-2.223302 2.0351,-1.48394 3.0552,-4.035204 3.0552,-7.638975 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    </g>
+  </g>
+</svg>

=== added file 'app/ubuntu-weather-app.qml'
--- app/ubuntu-weather-app.qml	1970-01-01 00:00:00 +0000
+++ app/ubuntu-weather-app.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Qt.labs.settings 1.0
+import Ubuntu.Components 1.2
+import "components"
+import "data" as Data
+import "data/WeatherApi.js" as WeatherApi
+import "data/key.js" as Key
+import "ui"
+
+MainView {
+    id: weatherApp
+
+    objectName: "weather"
+
+    applicationName: "com.ubuntu.weather"
+
+    automaticOrientation: false
+
+    width: units.gu(40)
+    height: units.gu(70)
+
+    backgroundColor: "#F5F5F5"
+
+    anchorToKeyboard: true
+
+    signal listItemSwiping(int i)
+
+    /*
+      List of locations and their data, accessible through index
+    */
+    property var locationsList: []    
+
+    /*
+      Index of Location before a refresh, to go back after
+    */
+    property int indexAtRefresh: -1
+
+    /*
+      Is the app loading?
+    */
+    property bool loading: false
+
+    /*
+      (re)load the pages on completion
+    */
+    Component.onCompleted: {
+        storage.getLocations(fillPages);
+        refreshData();
+    }
+
+    /*
+      Handle response data from data backend. Checks if a location
+      was updated and has to be stored again.
+    */
+    function responseDataHandler(messageObject) {
+         if(!messageObject.error) {
+             if(messageObject.action === "updateData") {
+                 messageObject.result.forEach(function(loc) {
+                     // replace location data in cache with refreshed values
+                     if(loc["save"] === true) {
+                         storage.updateLocation(loc.db.id, loc);
+                     }
+                 });
+                 //print(JSON.stringify(messageObject.result));
+                 fillPages(messageObject.result);
+             }
+         } else {
+             console.log(messageObject.error.msg+" / "+messageObject.error.request.url)
+             // TODO error handling
+         }
+     }
+
+    /* Fill the location pages with their data. */
+    function fillPages(locations) {
+        locationsList = []
+        locationsList = locations;
+    }
+
+    /*
+      Refresh data, either directly from storage or by checking against
+      API instead.
+    */
+    function refreshData(from_storage, force_refresh) {
+        from_storage = from_storage || false
+        force_refresh = force_refresh || false
+        loading = true
+        if(from_storage === true && force_refresh !== true) {
+            storage.getLocations(fillPages);
+        } else {
+            storage.getLocations(function(locations) {
+                WeatherApi.sendRequest({
+                    action: "updateData",
+                    params: {
+                        locations: locations,
+                        force: force_refresh,
+                        service: settings.service,
+                        api_key: Key.twcKey,
+                        interval: settings.refreshInterval
+                    }
+                }, responseDataHandler)
+            });
+        }
+    }
+
+    CurrentLocation {
+        id: currentLocation
+    }
+
+    Settings {
+        id: settings
+        category: "weatherSettings"
+        /*
+          Index of the current locationList of locations and their data, accessible through index
+        */
+        property int current: 0
+
+        property int refreshInterval: 1800
+        property string precipUnits
+        property string service
+        property string tempScale
+        property string units
+        property string windUnits
+
+        property bool addedCurrentLocation: false
+        property bool migrated: false
+
+        Component.onCompleted: {
+            if (units === "") {  // No settings so load defaults
+                console.debug("No settings, using defaults")
+                var metric = Qt.locale().measurementSystem === Locale.MetricSystem
+
+                precipUnits = metric ? "mm" : "in"
+                service = "weatherchannel"
+                tempScale = "°" + (metric ? "C" : "F")
+                units = metric ? "metric" : "imperial"
+                windUnits = metric ? "kph" : "mph"
+            }
+        }
+    }
+
+    Column {
+        anchors.centerIn: parent
+        spacing: units.gu(4)
+        visible: (locationsList == null || locationsList.length == 0) && mainPageStack.depth == 1
+        z: 1000
+
+        Label {
+            id: emptyStateLabel
+            anchors.horizontalCenter: parent.horizontalCenter
+            text: i18n.tr("Searching for current location...")
+        }
+
+        Button {
+            id: emptyStateButton
+            objectName: "emptyStateButton"
+
+            anchors.horizontalCenter: parent.horizontalCenter
+
+            text: i18n.tr("Add a manual location")
+
+            onTriggered: mainPageStack.push(Qt.resolvedUrl("ui/AddLocationPage.qml"));
+        }
+    }
+
+    Data.Storage {
+        id: storage
+
+        // Add or replace the current locaiton to the storage and refresh the
+        // locationList
+        function updateCurrentLocation(location) {
+            if (!settings.addedCurrentLocation || locationsList == null || locationsList.length == 0) {
+                storage.insertLocationAtStart({location: location});
+                settings.addedCurrentLocation = true;
+            } else {
+                storage.updateLocation(locationsList[0].db.id, {location: location});
+            }
+
+            refreshData(false, true);
+        }
+
+        // Add the location to the storage and refresh the locationList
+        // Return true if a location is added
+        function addLocation(location) {
+            var exists = checkLocationExists(location)
+
+            if(!exists) {
+                if(location.dbId === undefined || location.dbId === 0) {
+                    storage.insertLocation({location: location});
+                }
+                refreshData();
+            }
+
+            return !exists;
+        }
+
+        // Return true if the location given is already in the locationsList, and is not the same
+        // as the current location.
+        function checkLocationExists(location) {
+            var exists = false;
+
+            for (var i=0; !exists && i < locationsList.length; i++) {
+                var loc = locationsList[i].location;
+
+                if (loc.services.geonames && (loc.services.geonames === location.services.geonames) && !(settings.addedCurrentLocation && i === 0)) {
+                    exists = true;
+                }
+            }
+
+            return exists;
+        }
+
+        function moveLocation(from, to) {
+            // Indexes are offset by 1 to account for current location
+            from += 1
+            to += 1
+
+            // Update settings to respect new changes
+            if (from === settings.current) {
+                settings.current = to;
+            } else if (from < settings.current && to >= settings.current) {
+                settings.current -= 1;
+            } else if (from > settings.current && to <= settings.current) {
+                settings.current += 1;
+            }
+
+            storage.reorder(locationsList[from].db.id, locationsList[to].db.id);
+
+            refreshData(true, false);
+        }
+
+        // Remove a location from the list
+        function removeLocation(index) {
+            // Indexes are offset by 1 to account for current location
+            index += 1
+            if (settings.current >= index) {  // Update settings to respect new changes
+                settings.current -= settings.current;
+            }
+
+            storage.clearLocation(locationsList[index].db.id);
+
+            refreshData(true, false);
+        }
+
+        function removeMultiLocations(indexes) {
+            var i;
+
+            // Sort the item indexes as loops below assume *numeric* sort
+            indexes.sort(function(a,b) { return a - b })
+
+            for (i=0; i < indexes.length; i++) {
+                if (settings.current >= indexes[i] + 1) {  // Update settings to respect new changes
+                    settings.current -= settings.current;
+                }
+            }
+
+            // Get location db ids to remove
+            var locations = []
+
+            for (i=0; i < indexes.length; i++) {
+                locations.push(locationsList[indexes[i] + 1].db.id)
+            }
+
+            storage.clearMultiLocation(locations);
+
+            refreshData(true, false);
+        }
+    }
+
+    PageStack {
+        id: mainPageStack
+        Component.onCompleted: mainPageStack.push(Qt.resolvedUrl("ui/HomePage.qml"))
+    }
+}

=== added directory 'app/ui'
=== added file 'app/ui/AddLocationPage.qml'
--- app/ui/AddLocationPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/AddLocationPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.Popups 1.0
+import "../components"
+import "../data/CitiesList.js" as Cities
+import "../data/WeatherApi.js" as WeatherApi
+
+Page {
+    id: addLocationPage
+    objectName: "addLocationPage"
+    title: i18n.tr("Select a city")
+    visible: false
+
+    /*
+      Flickable is set to null to stop page header from hiding since the fast
+      scroll component hides top anchor margin is incorrect.
+    */
+    flickable: null
+
+    state: "default"
+    states: [
+        PageHeadState {
+            name: "default"
+            head: addLocationPage.head
+            backAction: Action {
+                iconName: "back"
+                text: i18n.tr("Back")
+                onTriggered: mainPageStack.pop()
+            }
+            actions: [
+                Action {
+                    iconName: "search"
+                    text: i18n.tr("City")
+                    onTriggered: {
+                        addLocationPage.state = "search"
+                        searchComponentLoader.sourceComponent = searchComponent
+                        searchComponentLoader.item.forceActiveFocus()
+                    }
+                }
+            ]
+        },
+
+        PageHeadState {
+            name: "search"
+            head: addLocationPage.head
+            backAction: Action {
+                iconName: "back"
+                text: i18n.tr("Back")
+                onTriggered: {
+                    locationList.forceActiveFocus()
+                    searchComponentLoader.item.text = ""
+                    addLocationPage.state = "default"
+                    searchComponentLoader.sourceComponent = undefined
+                }
+            }
+
+            contents: Loader {
+                id: searchComponentLoader
+                anchors {
+                    left: parent ? parent.left : undefined
+                    right: parent ? parent.right : undefined
+                    rightMargin: units.gu(2)
+                }
+            }
+        }
+    ]
+
+    Component {
+        id: searchComponent
+        TextField {
+            id: searchField
+            objectName: "searchField"
+
+            inputMethodHints: Qt.ImhNoPredictiveText
+            placeholderText: i18n.tr("Search city")
+            hasClearButton: true
+
+            onTextChanged: {
+                if (text.trim() === "") {
+                    loadEmpty()
+                } else {
+                    loadFromProvider(text)
+                }
+            }
+        }
+    }
+
+    // Builds a area label for the location, depending on the uniqueness of name, adminName1 and country
+     function buildAreaLabel(loc, a1Counts) {
+         var label = "";
+         label += ((loc.adminName1) ? loc.adminName1.replace(/ Region$/,''):"");
+         if (loc.adminName2 && a1Counts[loc.name+loc.adminName1] > 1) {
+             // even name and adminName1 are multiple, add adminName2
+             label += ", "+loc.adminName2;
+         }
+         label += ((label !== "") ? ", " : "") + loc.countryName
+         return label;
+     }
+
+     function appendCities(list) {
+         var a1Counts = {};
+         // count occurrences of name+adminName1 and name+country
+         list.forEach(function(loc) {
+             a1Counts[loc.name+loc.adminName1] = (!a1Counts[loc.name+loc.adminName1]) ? 1 : a1Counts[loc.name+loc.adminName1]+1;
+         });
+         // add locations to listmodel
+         list.forEach(function(loc) {
+             loc.areaLabel = buildAreaLabel(loc, a1Counts)
+             citiesModel.append(loc);
+         })
+     }
+
+    function clearModelForLoading() {
+        citiesModel.clear()
+        citiesModel.loading = true
+        citiesModel.httpError = false
+    }
+
+    function loadEmpty() {
+        clearModelForLoading()
+
+        appendCities(Cities.preList)
+
+        citiesModel.loading = false
+    }
+
+    function loadFromProvider(search) {
+        clearModelForLoading()
+
+        WeatherApi.sendRequest({
+                                   action: "searchByName",
+                                   params: {
+                                       name: search,
+                                       units: "metric"
+                                   }
+                               }, searchResponseHandler)
+    }
+
+    function searchResponseHandler(msgObject) {
+        if (!msgObject.error) {
+            appendCities(msgObject.result.locations)
+        } else {
+            citiesModel.httpError = true
+        }
+
+        citiesModel.loading = false
+    }
+
+    ListView {
+        id: locationList
+
+        clip: true
+        currentIndex: -1
+        anchors.fill: parent
+        anchors.rightMargin: fastScroll.showing ? fastScroll.width - units.gu(1)
+                                                : 0
+
+        function getSectionText(index) {
+            return citiesModel.get(index).name.substring(0,1)
+        }
+
+        onFlickStarted: {
+            forceActiveFocus()
+        }
+
+        section.property: "name"
+        section.criteria: ViewSection.FirstCharacter
+        section.labelPositioning: ViewSection.InlineLabels
+
+        section.delegate: ListItem {
+            height: headerText.implicitHeight + units.gu(1)
+            Label {
+                id: headerText
+                text: section
+                anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
+                font.weight: Font.DemiBold
+            }
+        }
+
+        model: ListModel {
+            id: citiesModel
+
+            property bool loading: true
+            property bool httpError: false
+
+            onRowsAboutToBeInserted: loading = false
+        }
+
+        delegate: ListItem {
+            divider.visible: false
+            Column {
+                anchors {
+                    left: parent.left
+                    leftMargin: units.gu(2)
+                    right: parent.right
+                    rightMargin: units.gu(2)
+                    verticalCenter: parent.verticalCenter
+                }
+
+                Label {
+                    color: UbuntuColors.darkGrey
+                    elide: Text.ElideRight
+                    fontSize: "medium"
+                    text: name
+                }
+
+                Label {
+                    color: UbuntuColors.lightGrey
+                    elide: Text.ElideRight
+                    fontSize: "xx-small"
+                    text: areaLabel
+                }
+            }
+
+            onClicked: {
+                if (storage.addLocation(citiesModel.get(index))) {
+                    mainPageStack.pop()
+                } else {
+                    PopupUtils.open(locationExistsComponent, addPage)
+                }
+            }
+        }
+
+        Component.onCompleted: loadEmpty()
+
+        Behavior on anchors.rightMargin {
+            UbuntuNumberAnimation {}
+        }
+    }
+
+    FastScroll {
+        id: fastScroll
+
+        listView: locationList
+
+        enabled: (locationList.contentHeight > (locationList.height * 2)) &&
+                 (locationList.height >= minimumHeight)
+
+        anchors {
+            top: locationList.top
+            topMargin: units.gu(1.5)
+            bottom: locationList.bottom
+            right: parent.right
+        }
+    }
+
+    ActivityIndicator {
+        anchors {
+            centerIn: parent
+        }
+        running: visible
+        visible: citiesModel.loading
+    }
+
+    Label {
+        id: noCity
+        anchors {
+            centerIn: parent
+        }
+        text: i18n.tr("No city found")
+        visible: citiesModel.count === 0 && !citiesModel.loading
+    }
+
+    Label {
+        id: httpFail
+        anchors {
+            left: parent.left
+            margins: units.gu(1)
+            right: parent.right
+            top: noCity.bottom
+        }
+        horizontalAlignment: Text.AlignHCenter
+        text: i18n.tr("Couldn't load weather data, please try later again!")
+        visible: citiesModel.httpError
+        wrapMode: Text.WordWrap
+    }
+
+    Component {
+        id: locationExistsComponent
+
+        Dialog {
+            id: locationExists
+            title: i18n.tr("Location already added.")
+
+            Button {
+                text: i18n.tr("OK")
+                onClicked: PopupUtils.close(locationExists)
+            }
+        }
+    }
+
+}

=== added file 'app/ui/CMakeLists.txt'
--- app/ui/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/ui/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,7 @@
+add_subdirectory(settings)
+
+file(GLOB UI_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_ui_QMlFiles ALL SOURCES ${UI_QML_JS_FILES})
+
+install(FILES ${UI_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/ui)

=== added file 'app/ui/HomePage.qml'
--- app/ui/HomePage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/HomePage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../components"
+
+
+PageWithBottomEdge {
+    // Set to null otherwise the header is shown (but blank) over the top of the listview
+    id: locationPage
+    objectName: "homePage"
+    flickable: null
+
+    bottomEdgePageSource: Qt.resolvedUrl("LocationsPage.qml")
+    bottomEdgeTitle: i18n.tr("Locations")
+    tipColor: UbuntuColors.orange
+    tipLabelColor: "#FFF"
+
+    property var iconMap: {
+        "sun": "weather-clear-symbolic",
+        "moon": "weather-clear-night-symbolic",
+        "cloud_sun": "weather-few-clouds-symbolic",
+        "cloud_moon": "weather-few-clouds-night-symbolic",
+        "cloud": "weather-clouds-symbolic",
+        "rain": "weather-showers-symbolic",
+        "thunder": "weather-storm-symbolic",
+        "snow_shower": "weather-snow-symbolic",
+        "fog": "weather-fog-symbolic",
+        "snow_rain": "weather-snow-symbolic",
+        "scattered": "weather-showers-scattered-symbolic",
+        "overcast": "weather-overcast-symbolic"
+    }
+
+    property var imageMap: {
+        "sun": Qt.resolvedUrl("../graphics/Sunny.png"),
+        "moon": Qt.resolvedUrl("../graphics/Starry-Night.png"),
+        "cloud_sun": Qt.resolvedUrl("../graphics/Cloudy-Circles.png"),
+        "cloud_moon": Qt.resolvedUrl("../graphics/Cloudy-Night.png"),
+        "cloud": Qt.resolvedUrl("../graphics/Cloudy.png"),
+        "rain": Qt.resolvedUrl("../graphics/Big-Rain.png"),
+        "thunder": Qt.resolvedUrl("../graphics/Stormy.png"),
+        "snow_shower": Qt.resolvedUrl("../graphics/Cloudy-Snow.png"),
+        "fog": Qt.resolvedUrl("../graphics/Fog.png"),
+        "snow_rain": Qt.resolvedUrl("../graphics/Cloudy-Snow.png"),
+        "scattered": Qt.resolvedUrl("../graphics/Showers.png"),
+        "overcast": Qt.resolvedUrl("../graphics/Cloudy.png")
+    }
+
+    /*
+      Format date object by given format.
+    */
+    function formatTimestamp(dateData, format) {
+        var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
+        return Qt.formatDate(date, i18n.tr(format))
+    }
+
+    /*
+      Format time object by given format.
+    */
+    function formatTime(dateData, format) {
+        var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
+        return Qt.formatTime(date, i18n.tr(format))
+    }
+
+    /*
+      Background for the PageWithBottomEdge
+    */
+    Rectangle {
+        id: pageBackground
+        anchors.fill: parent
+        color: "white"
+    }
+
+    /*
+      Flickable to scroll the location vertically.
+      The respective contentHeight gets calculated after the Location is filled with data.
+    */
+    Flickable {
+        id: locationFlickable
+        width: parent.width
+        height: parent.height
+
+        // FIXME: not sure where the 3GU comes from, PullToRefresh or something in HomePage?
+        contentHeight: locationPages.currentItem.childrenRect.height + units.gu(3)
+        contentWidth: parent.width
+
+        Behavior on contentHeight {
+            NumberAnimation {
+
+            }
+        }
+
+        PullToRefresh {
+            id: pullToRefresh
+            parent: locationFlickable
+            refreshing: false
+            onRefresh: {
+                locationPages.loaded = false
+                refreshing = true
+                refreshData(false, true)
+            }
+        }
+
+        /*
+          ListView for locations with snap-scrolling horizontally.
+        */
+        ListView {
+            id: locationPages
+            anchors.fill: parent
+            currentIndex: settings.current
+            delegate: LocationPane {}
+            height: childrenRect.height
+            highlightRangeMode: ListView.StrictlyEnforceRange
+            model: weatherApp.locationsList.length
+            orientation: ListView.Horizontal
+            // TODO with snapMode, currentIndex is not properly set and setting currentIndex fails
+            //snapMode: ListView.SnapOneItem
+            width: parent.width
+
+            property bool loaded: false
+
+            signal collapseOtherDelegates(int index)
+
+            onCurrentIndexChanged: {
+                if (loaded) {
+                    // FIXME: when a model is reloaded this causes the currentIndex to be lost
+                    settings.current = currentIndex
+
+                    collapseOtherDelegates(-1)  // collapse all
+                }
+            }
+            onModelChanged: {
+                currentIndex = settings.current
+
+                if (model > 0) {
+                    pullToRefresh.refreshing = false
+                    loading = false
+                    loaded = true
+                }
+            }
+            onVisibleChanged: {
+                if (!visible && loaded) {
+                    collapseOtherDelegates(-1)  // collapse all
+                }
+            }
+
+            // TODO: workaround for not being able to use snapMode property
+            Component.onCompleted: {
+                var scaleFactor = units.gridUnit * 10;
+                maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+                flickDeceleration = flickDeceleration * scaleFactor;
+            }
+
+            Connections {
+                target: settings
+                onCurrentChanged: {
+                    locationPages.currentIndex = settings.current
+                }
+            }
+        }
+    }
+
+    LoadingIndicator {
+        id: loadingIndicator
+    }
+}

=== added file 'app/ui/LocationPane.qml'
--- app/ui/LocationPane.qml	1970-01-01 00:00:00 +0000
+++ app/ui/LocationPane.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import "../components"
+
+Item {
+    id: locationItem
+    /*
+      Data properties
+    */
+    property string name
+    property string conditionText
+    property string currentTemp
+    property string todayMaxTemp
+    property string todayMinTemp
+    property string icon
+    property string iconName
+
+    Component.onCompleted: renderData(index)
+
+    width: locationPage.width
+    height: childrenRect.height
+    anchors.fill: parent.fill
+
+    function emptyIfUndefined(variable, append) {
+        if (append === undefined) {
+            append = ""
+        }
+
+        return variable === undefined ? "" : variable + append
+    }
+
+    /*
+      Extracts values from the location weather data and puts them into the appropriate components
+      to display them.
+
+      Attention: Data access happens through "weatherApp.locationList[]" by index, since complex
+      data in models will lead to type problems.
+    */
+    function renderData(index) {
+        var data = weatherApp.locationsList[index],
+                current = data.data[0].current,
+                forecasts = data.data,
+                forecastsLength = forecasts.length,
+                today = forecasts[0],
+                hourlyForecasts = [];
+
+        var tempUnits = settings.tempScale === "°C" ? "metric" : "imperial"
+
+        // set general location data
+        name = data.location.name;
+
+        // set current temps and condition
+        iconName = (current.icon) ? current.icon : "";
+        icon = (imageMap[iconName] !== undefined) ? imageMap[iconName] : "";
+        conditionText = (current.condition !== undefined) ? current.condition : "";
+        todayMaxTemp = (today[tempUnits].tempMax !== undefined) ? Math.round(today[tempUnits].tempMax).toString() + settings.tempScale: "";
+        todayMinTemp = Math.round(today[tempUnits].tempMin).toString() + settings.tempScale;
+        currentTemp = Math.round(current[tempUnits].temp).toString() + String("°");
+
+        // reset days list
+        mainPageWeekdayListView.model.clear()
+
+        // set daily forecasts
+        if(forecastsLength > 0) {
+            for(var x=0;x<forecastsLength;x++) {
+                // collect hourly forecasts if available
+                if(forecasts[x].hourly !== undefined && forecasts[x].hourly.length > 0) {
+                    hourlyForecasts = hourlyForecasts.concat(forecasts[x].hourly)
+                }
+                if(x === 0) {
+                    // skip todays daydata
+                    continue;
+                }
+
+                // set daydata
+                var dayData = {
+                    day: formatTimestamp(forecasts[x].date, 'dddd'),
+                    low: Math.round(forecasts[x][tempUnits].tempMin).toString() + settings.tempScale,
+                    high: (forecasts[x][tempUnits].tempMax !== undefined) ? Math.round(forecasts[x][tempUnits].tempMax).toString() + settings.tempScale : "",
+                    image: (forecasts[x].icon !== undefined && iconMap[forecasts[x].icon] !== undefined) ? iconMap[forecasts[x].icon] : "",
+                    chanceOfRain: forecasts[x].propPrecip === undefined ? -1 : forecasts[x].propPrecip,
+                    humidity: emptyIfUndefined(forecasts[x].humidity, "%"),
+                    uvIndex: emptyIfUndefined(forecasts[x].uv),
+                    wind: forecasts[x][tempUnits].windSpeed === undefined || forecasts[x].windDir === undefined
+                                ? "" : Math.round(forecasts[x][tempUnits].windSpeed) + settings.windUnits + " " + forecasts[x].windDir
+                }
+                mainPageWeekdayListView.model.append(dayData);
+            }
+        }        
+
+        // set data for hourly forecasts
+        if(hourlyForecasts.length > 0) {
+            homeHourlyLoader.forecasts = hourlyForecasts;
+            homeHourlyLoader.tempUnits = tempUnits;
+        }
+    }
+
+    Column {
+        id: locationTop
+
+        anchors {
+            top: parent.top
+            left: parent.left
+            right: parent.right
+            margins: units.gu(2)
+        }
+        spacing: units.gu(1)
+
+        HeaderRow {
+            id: headerRow
+            locationName: locationItem.name
+        }
+
+        HomeGraphic {
+            id: homeGraphic
+            icon: locationItem.icon
+            MouseArea {
+                anchors.fill: parent
+                onClicked: {
+                    homeGraphic.visible = false;
+                }
+            }
+        }
+
+        Loader {
+            id: homeHourlyLoader
+            active: !homeGraphic.visible
+            asynchronous: true
+            height: units.gu(32)
+            source: "../components/HomeHourly.qml"
+            visible: active
+            width: parent.width
+
+            property var forecasts: []
+            property string tempUnits: ""
+        }
+
+        HomeTempInfo {
+            id: homeTempInfo
+            description: conditionText
+            high: locationItem.todayMaxTemp
+            low: locationItem.todayMinTemp
+            now: locationItem.currentTemp
+        }
+
+        // TODO: Migrate this to using the new SDK list item when possible.
+        ListItem.ThinDivider { anchors { leftMargin: units.gu(-2); rightMargin: units.gu(-2) } }
+    }
+    Column {
+        id: weekdayColumn
+
+        anchors.top: locationTop.bottom
+        height: childrenRect.height
+        width: parent.width
+
+        Repeater {
+            id: mainPageWeekdayListView
+            model: ListModel{}
+            delegate: DayDelegate {
+                day: model.day
+                high: model.high
+                image: model.image
+                low: model.low
+
+                chanceOfRain: model.chanceOfRain
+                humidity: model.humidity
+                // TODO: extra from API
+                //pollen: model.pollen
+                //sunrise: model.sunrise
+                //sunset: model.sunset
+                wind: model.wind
+                uvIndex: model.uvIndex
+            }
+        }
+    }
+}

=== added file 'app/ui/LocationsPage.qml'
--- app/ui/LocationsPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/LocationsPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import "../components"
+import "../components/ListItemActions"
+
+
+Page {
+    id: locationsPage
+    // Set to null otherwise the first delegate appears +header.height down the page
+    flickable: null
+    title: i18n.tr("Locations")
+
+    state: locationsListView.state === "multiselectable" ? "selection" : "default"
+    states: [
+        PageHeadState {
+            id: defaultState
+            name: "default"
+            actions: [
+                Action {
+                    iconName: "add"
+                    onTriggered: mainPageStack.push(Qt.resolvedUrl("AddLocationPage.qml"))
+                }
+            ]
+            PropertyChanges {
+                target: locationsPage.head
+                actions: defaultState.actions
+            }
+        },
+        MultiSelectHeadState {
+            listview: locationsListView
+            removable: true
+            thisPage: locationsPage
+
+            onRemoved: storage.removeMultiLocations(selectedItems.slice())
+        }
+    ]
+
+    ListModel {
+        id: currentLocationModel
+    }
+
+    MultiSelectListView {
+        id: locationsListView
+        anchors {
+            fill: parent
+        }
+        model: ListModel {
+            id: locationsModel
+        }
+        header: MultiSelectListView {
+            id: currentLocationListView
+            anchors {
+                left: parent.left
+                right: parent.right
+            }
+            height: settings.addedCurrentLocation ? units.gu(8) : units.gu(0)
+            interactive: false
+            model: currentLocationModel
+            delegate: WeatherListItem {
+                id: currentLocationListItem
+
+                onItemClicked: {
+                    settings.current = index;
+                    pageStack.pop()
+                }
+
+                Column {
+                    anchors {
+                        left: parent.left
+                        right: currentWeatherImage.left
+                        rightMargin: units.gu(1)
+                        verticalCenter: parent.verticalCenter
+                    }
+
+                    Label {
+                        id: currentLocationName
+
+                        anchors {
+                            left: parent.left
+                            leftMargin: units.gu(2)
+                        }
+
+                        elide: Text.ElideRight
+                        fontSize: "medium"
+                        text: i18n.tr("Current Location")
+                    }
+                    Label {
+                        id: currentLocationName2
+
+                        anchors {
+                            left: parent.left
+                            leftMargin: units.gu(2)
+                        }
+
+                        color: UbuntuColors.lightGrey
+                        elide: Text.ElideRight
+                        fontSize: "small"
+                        font.weight: Font.Light
+                        text: name + ", " + (adminName1 == name ? countryName : adminName1)
+                    }
+                }
+
+                Icon {
+                    id: currentWeatherImage
+                    anchors {
+                        right: parent.right
+                        rightMargin: units.gu(14)
+                        verticalCenter: parent.verticalCenter
+                    }
+                    name: icon
+                    height: units.gu(3)
+                    width: units.gu(3)
+                }
+
+                Label {
+                    id: currentTemperatureLabel
+                    anchors {
+                        left: currentWeatherImage.right
+                        leftMargin: units.gu(1)
+                        right: parent.right
+                        rightMargin: units.gu(2)
+                        verticalCenter: parent.verticalCenter
+                    }
+                    color: UbuntuColors.orange
+                    elide: Text.ElideRight
+                    font.pixelSize: units.gu(4)
+                    font.weight: Font.Light
+                    horizontalAlignment: Text.AlignRight
+                    text: temp + settings.tempScale
+                }
+            }
+        }
+
+        delegate: WeatherListItem {
+            id: locationsListItem
+            leftSideAction: Remove {
+                onTriggered: storage.removeLocation(index)
+            }
+            multiselectable: true
+            reorderable: true
+
+            onItemClicked: {
+                settings.current = index + 1;
+                pageStack.pop()
+            }
+            onReorder: {
+                console.debug("Move: ", from, to);
+
+                storage.moveLocation(from, to);
+            }
+
+            ListItem.ThinDivider {
+                anchors {
+                    top: parent.top
+                }
+                visible: index == 0
+            }
+
+            Item {
+                anchors {
+                    fill: parent
+                    leftMargin: units.gu(2)
+                    rightMargin: units.gu(2)
+                }
+
+                Column {
+                    anchors {
+                        left: parent.left
+                        right: weatherImage.visible ? weatherImage.left : parent.right
+                        rightMargin: units.gu(1)
+                        verticalCenter: parent.verticalCenter
+                    }
+
+                    Label {
+                        id: locationName
+                        elide: Text.ElideRight
+                        fontSize: "medium"
+                        text: name
+                    }
+                    Label {
+                        id: locationName2
+                        color: UbuntuColors.lightGrey
+                        elide: Text.ElideRight
+                        fontSize: "small"
+                        font.weight: Font.Light
+                        text: adminName1 == name ? countryName : adminName1
+                    }
+                }
+
+                Icon {
+                    id: weatherImage
+                    anchors {
+                        right: parent.right
+                        rightMargin: units.gu(12)
+                        verticalCenter: parent.verticalCenter
+                    }
+                    name: icon
+                    height: units.gu(3)
+                    width: units.gu(3)
+                    visible: locationsPage.state === "default"
+                }
+
+                Label {
+                    id: temperatureLabel
+                    anchors {
+                        left: weatherImage.right
+                        leftMargin: units.gu(1)
+                        right: parent.right
+                        verticalCenter: parent.verticalCenter
+                    }
+                    color: UbuntuColors.orange
+                    elide: Text.ElideRight
+                    font.pixelSize: units.gu(4)
+                    font.weight: Font.Light
+                    horizontalAlignment: Text.AlignRight
+                    text: temp + settings.tempScale
+                    visible: locationsPage.state === "default"
+                }
+            }
+
+            ListItem.ThinDivider {
+                anchors {
+                    bottom: parent.bottom
+                }
+            }
+        }
+    }
+
+    function populateLocationsModel() {
+        currentLocationModel.clear()
+        locationsModel.clear()
+        var loc = {}, data = {},
+            tempUnits = settings.tempScale === "°C" ? "metric" : "imperial";
+
+        for (var i=0; i < weatherApp.locationsList.length; i++) {
+            data = weatherApp.locationsList[i];
+            loc = {
+                "name": data.location.name,
+                "adminName1": data.location.adminName1,
+                "areaLabel": data.location.areaLabel,
+                "countryName": data.location.countryName,
+                "temp": Math.round(data.data[0].current[tempUnits].temp).toString(),
+                "icon": iconMap[data.data[0].current.icon]
+            }
+
+            if (!settings.addedCurrentLocation || i > 0) {
+                locationsModel.append(loc)
+            } else {
+                currentLocationModel.append(loc)
+            }
+        }
+    }
+
+    Connections {
+        target: weatherApp
+        onLocationsListChanged: populateLocationsModel()
+    }
+
+    Component.onCompleted: populateLocationsModel()
+}

=== added file 'app/ui/SettingsPage.qml'
--- app/ui/SettingsPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/SettingsPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../components"
+
+Page {
+    title: i18n.tr("Settings")
+
+    Flickable {
+        clip: true
+        anchors.fill: parent
+        contentHeight: settingsColumn.height
+        height: parent.height
+
+        Column {
+            id: settingsColumn
+
+            anchors.fill: parent
+
+            StandardListItem {
+                title: i18n.tr("Units")
+                onClicked: mainPageStack.push(Qt.resolvedUrl("settings/UnitsPage.qml"))
+            }
+
+            StandardListItem {
+                title: i18n.tr("Data Provider")
+                onClicked: mainPageStack.push(Qt.resolvedUrl("settings/DataProviderPage.qml"))
+            }
+
+            StandardListItem {
+                title: i18n.tr("Refresh Interval")
+                onClicked: mainPageStack.push(Qt.resolvedUrl("settings/RefreshIntervalPage.qml"))
+            }
+        }
+    }
+}

=== added directory 'app/ui/settings'
=== added file 'app/ui/settings/CMakeLists.txt'
--- app/ui/settings/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ app/ui/settings/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB UI_SETTINGS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_ui_settings_QMlFiles ALL SOURCES ${UI_SETTINGS_QML_JS_FILES})
+
+install(FILES ${UI_SETTINGS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/ui/settings)

=== added file 'app/ui/settings/DataProviderPage.qml'
--- app/ui/settings/DataProviderPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/settings/DataProviderPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+    title: i18n.tr("Data Provider")
+
+    ListModel {
+        id: dataProviderModel
+        ListElement { text: "OpenWeatherMap" }
+        ListElement { text: "The Weather Channel" }
+    }
+
+    ExpandableListItem {
+        id: dataProviderSetting
+
+        listViewHeight: dataProviderModel.count*units.gu(7) - units.gu(1)
+        model: dataProviderModel
+        text: i18n.tr("Provider")
+        subText: settings.service === "weatherchannel" ? "The Weather Channel" : "OpenWeatherMap"
+
+        delegate: StandardListItem {
+            title: model.text
+            icon: "ok"
+            showIcon: dataProviderSetting.subText === model.text
+            onClicked: {
+                if (model.text === "The Weather Channel") {
+                    settings.service = "weatherchannel"
+                } else {
+                    settings.service = "openweathermap"
+                }
+                refreshData(false, true)
+            }
+        }
+    }
+}

=== added file 'app/ui/settings/RefreshIntervalPage.qml'
--- app/ui/settings/RefreshIntervalPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/settings/RefreshIntervalPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+    title: i18n.tr("Refresh Interval")
+
+    ListModel {
+        id: refreshModel
+        Component.onCompleted: initialize()
+        function initialize() {
+            refreshModel.append({"interval": 600, "text": i18n.tr("%1 minute", "%1 minutes", 10).arg(10)})
+            refreshModel.append({"interval": 900, "text": i18n.tr("%1 minute", "%1 minutes", 15).arg(15)})
+            refreshModel.append({"interval": 1800, "text": i18n.tr("%1 minute", "%1 minutes", 30).arg(30)})
+            refreshModel.append({"interval": 3600, "text": i18n.tr("%1 minute", "%1 minutes", 60).arg(60)})
+        }
+    }
+
+    ExpandableListItem {
+        id: dataProviderSetting
+
+        listViewHeight: refreshModel.count*units.gu(7) - units.gu(1)
+        model: refreshModel
+        text: i18n.tr("Interval")
+        subText: i18n.tr("%1 minute", "%1 minutes", Math.floor(settings.refreshInterval / 60).toString()).arg(Math.floor(settings.refreshInterval / 60).toString())
+
+        delegate: StandardListItem {
+            title: model.text
+            icon: "ok"
+            showIcon: settings.refreshInterval === model.interval
+            onClicked: {
+                settings.refreshInterval = model.interval
+                refreshData(false, true)
+            }
+        }
+    }
+}

=== added file 'app/ui/settings/UnitsPage.qml'
--- app/ui/settings/UnitsPage.qml	1970-01-01 00:00:00 +0000
+++ app/ui/settings/UnitsPage.qml	2015-06-28 23:49:12 +0000
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+    title: i18n.tr("Units")
+
+    flickable: null
+
+    Flickable {
+        clip: true
+        anchors.fill: parent
+        height: parent.height
+        contentHeight: unitsColumn.childrenRect.height
+
+        ListModel {
+            id: temperatureModel
+            Component.onCompleted: initialize()
+            function initialize() {
+                // TRANSLATORS: The strings are standard measurement units
+                // of temperature in Celcius and are shown in the settings page.
+                // Only the abbreviated form of Celcius should be used.
+                temperatureModel.append({"text": i18n.tr("°C"), "value": "°C"})
+
+                // TRANSLATORS: The strings are standard measurement units
+                // of temperature in Fahrenheit and are shown in the settings page.
+                // Only the abbreviated form of Fahrenheit should be used.
+                temperatureModel.append({"text": i18n.tr("°F"), "value": "°F"})
+            }
+        }
+
+        ListModel {
+            id: precipationModel
+            Component.onCompleted: initialize()
+            function initialize() {
+                // TRANSLATORS: The strings are standard measurement units
+                // of precipitation in millimeters and are shown in the settings page.
+                // Only the abbreviated form of millimeters should be used.
+                precipationModel.append({"text": i18n.tr("mm"), "value": "mm"})
+
+                // TRANSLATORS: The strings are standard measurement units
+                // of precipitation in inches and are shown in the settings page.
+                // Only the abbreviated form of inches should be used.
+                precipationModel.append({"text": i18n.tr("in"), "value": "in"})
+            }
+        }
+
+        ListModel {
+            id: windSpeedModel
+            Component.onCompleted: initialize()
+            function initialize() {
+                // TRANSLATORS: The strings are standard measurement units
+                // of wind speed in kilometers per hour and are shown in the settings page.
+                // Only the abbreviated form of kilometers per hour should be used.
+                windSpeedModel.append({"text": i18n.tr("kph"), "value": "kph"})
+
+                // TRANSLATORS: The strings are standard measurement units
+                // of wind speed in miles per hour and are shown in the settings page.
+                // Only the abbreviated form of miles per hour should be used.
+                windSpeedModel.append({"text": i18n.tr("mph"), "value": "mph"})
+            }
+        }
+
+        Column {
+            id: unitsColumn
+            anchors.fill: parent
+
+            ExpandableListItem {
+                id: temperatureSetting
+
+                listViewHeight: temperatureModel.count*units.gu(7) - units.gu(1)
+                model: temperatureModel
+                text: i18n.tr("Temperature")
+                subText: settings.tempScale === "°C" ? i18n.tr("°C")
+                                                     : i18n.tr("°F")
+
+                delegate: StandardListItem {
+                    title: model.text
+                    icon: "ok"
+                    showIcon: settings.tempScale === model.value
+                    onClicked: {
+                        settings.tempScale = model.value
+                        refreshData(true)
+                    }
+                }
+            }
+
+            ExpandableListItem {
+                id: precipationSetting
+
+                listViewHeight: precipationModel.count*units.gu(7) - units.gu(1)
+                model: precipationModel
+                text: i18n.tr("Precipitation")
+                subText: settings.precipUnits === "mm" ? i18n.tr("mm")
+                                                       : i18n.tr("in")
+
+                delegate: StandardListItem {
+                    title: model.text
+                    icon: "ok"
+                    showIcon: settings.precipUnits === model.value
+                    onClicked: {
+                        settings.precipUnits = model.value
+                        refreshData(true)
+                    }
+                }
+            }
+
+            ExpandableListItem {
+                id: windSetting
+
+                listViewHeight: windSpeedModel.count*units.gu(7) - units.gu(1)
+                model: windSpeedModel
+                text: i18n.tr("Wind Speed")
+                subText: settings.windUnits === "kph" ? i18n.tr("kph")
+                                                      : i18n.tr("mph")
+
+                delegate: StandardListItem {
+                    title: model.text
+                    icon: "ok"
+                    showIcon: settings.windUnits === model.value
+                    onClicked: {
+                        settings.windUnits = model.value
+                        refreshData(true)
+                    }
+                }
+            }
+        }
+    }
+}

=== added file 'app/weather-app@xxxxxx'
Binary files app/weather-app@xxxxxx	1970-01-01 00:00:00 +0000 and app/weather-app@xxxxxx	2015-06-28 23:49:12 +0000 differ
=== added directory 'debian'
=== renamed directory 'debian' => 'debian.moved'
=== added file 'debian/changelog'
--- debian/changelog	1970-01-01 00:00:00 +0000
+++ debian/changelog	2015-06-28 23:49:12 +0000
@@ -0,0 +1,179 @@
+ubuntu-weather-app (3.0) UNRELEASED; urgency=medium
+
+  * Sync debian version with click version.
+
+ -- Victor Thompson <victor.thompson@xxxxxxxxx>  Mon, 01 Jun 2015 20:11:23 -0500
+
+ubuntu-weather-app (2.1) UNRELEASED; urgency=medium
+
+  * Update packaging to reflect reboot changes.
+
+ -- Daniel Holbach <daniel.holbach@xxxxxxxxxx>  Wed, 04 Feb 2015 11:25:02 +0100
+
+ubuntu-weather-app (2.0) utopic; urgency=low
+
+  [ Andrew Hayzen ]
+  * Initial version of reboot
+
+ -- Andrew Hayzen <ahayzen@xxxxxxxxx>  Mon, 02 Feb 2014 15:10:00 +0000
+
+ubuntu-weather-app (1.8.3ubuntu1) utopic; urgency=low
+
+  [ Dan Chapman ]
+  * Add timezone c++ extension plugin
+
+ -- Dan Chapman <dpniel@xxxxxxxxxx>  Thu, 25 Sep 2014 10:46:29 +0100
+
+ubuntu-weather-app (1.8.3) raring; urgency=low
+
+  [ Martin Borho ]
+  * updated icons for rain and snow
+  * bug fixes and optimizations
+
+  [ Alan Pope ]
+  * Update click framework and apparmor profile version (LP: #1315318) 
+
+ -- Alan Pope <popey@xxxxxxxxxx>  Fri, 02 May 2014 13:51:57 +0100
+
+ubuntu-weather-app (1.8.1ubuntu1) UNRELEASED; urgency=low
+
+  * added missing icon
+
+ -- Martin Borho <martin@xxxxxxxxx>  Sun, 23 Mar 2014 13:06:17 +0000
+
+ubuntu-weather-app (1.8ubuntu1) UNRELEASED; urgency=low
+
+  [Leo Arias]
+  * Port to autopilot 1.4.
+  * Set the objectNames for the settings options.
+
+  [Nicholas Skaggs]
+  * Fixed test_del_add_bug test, simplify tests, re-enable disabled tests.
+  * Redo create blank db, ensure it's being used across all tests.
+  * Add cmake build.
+  * Set revno to match store.
+  * Updated tests to use more emulator standards.
+  * Tweaked activity indicator handling.
+
+  [Dennis O'Flaherty]
+  * Load weather for the selected city when clicking the OWM logo instead of loading for London.
+
+  [Raul Yeguas]
+  * Added an modified logo for OpenWeatherMap (have mercy on me, christina-li and michal) and an action to open its website on click.
+  * Added animations for daily and houly forecast scroll.
+  * Improved animation speed by reducing image size.
+  * Removes SideStage hint, fixes related to the sizes of the current weather data display on tablets.
+
+  [Victor Thompson]
+  * Added unit of measure for main temperature value.
+  * Fixed conversion of wind speed data for TWC data.
+  * Prevent predictive text in search bar.
+
+  [Arash Badie Modiri]
+  * Changed a translation string into something a little bit more general.
+
+  [Martin Borho]
+  * Optimized and added haptic effect to hourly scrolling .
+  * Added The Weather Channel as default weather data provider.
+  * Added keyboard shortcuts.
+  * Initial tablet design of the Weather app.
+  * Refactored all width:parent.width and height:parent.height through corresponding anchors
+  * Fixed edit dialog to fill window on N7 2013 landscape
+  * Fixed weather AP tests to not assume tablet mode
+  * Fixed content sticking to the top, when height was changed in phone mode
+  * Fixed StateNotFoundError failures in image 206
+  * Removed WorkerScriptfor API calls
+  * New icon names used in API clients
+  * Using the current condition icon for the current day
+
+  [David Planella]
+  * Fixed the rule to generate a .pot template after the migration to cmake.
+  * Minor improvements on the weather API: added parameterize(), added locale parameters to TWC calls
+  * Fixes empty TWC URL if locale is not supported.
+  * Adds documentation for the The Weather Channel API.
+  * Makes the provider footer to be full width in order for it to make it look good on a Nexus 7.
+  * Fixes the shorter length header on the N7 using anchors instead of widths.
+
+  [Alan Pope]
+  * New icon.
+
+ -- Martin Borho <martin@xxxxxxxxx>  Fri, 21 Mar 2014 11:26:17 +0000
+
+ubuntu-weather-app (1.0ubuntu1) UNRELEASED; urgency=medium
+
+  * New icon 
+
+ -- Alan Pope <alan.pope@xxxxxxxxxxxxx>  Tue, 18 Feb 2014 21:26:17 +0000
+
+ubuntu-weather-app (1.0) saucy; urgency=low
+
+  [Andrew Starr-Bochicchio]
+  * Use the system locale to determine both the default units and the display of time.
+  * Add predictive search for locations. (LP: #1218910)
+  * Increase visibility of ActivityIndicator (LP: #1218904)
+
+  [David Planella]
+  * Updated translatable strings, a couple of small fixes to ease translations
+
+  [Dennis O'Flaherty]
+  * Fixes for precipitation values.
+
+  [Martin Borho]
+  * Removing a location add adding a new one fails. (LP: #1230297)
+  * Added location lookup via geoip and geonames.org with test (LP: #1187312, #1188726)
+  * Added fixes for new removable ListItem and the changed LocalStorage path. (LP: #1234544)
+  * Added setting for precipitation unit, mm or in.
+  * Location search now via geonames.org. (LP: #1230153)
+  * Updated predefined cities list.
+  * Added component for notifications.
+  * Added bottom margin to scrolling area in tabs and moved warm colors up the gradient color scheme.
+  * Visual improvements and fixes to LocationManagerSheet and AddLocationSheet. (LP: #1198541, #1221734)
+  * Added updated label, removed years from date labels. (LP: #1219200, #1221149, #1221162)
+  * Improved scrolling, added slow/fast scrolling for day/hour scrolling distinction. (LP:# 1221169)
+  * Added option in settings to change the wind speed unit. (LP: #1214934, #1190784)
+  * Dynamic gradient backgrounds according to the visual design.
+  * Added missing localstorage dependency.
+
+  [Nicholas Skaags]
+  * Add support for testing via click packages.
+
+  [Raúl Yeguas]
+  * Updated colors in GradientsMap.js. (LP: #1226746)
+  * Returns the last element to primary information screen and skip animation. (LP: #1227150)
+  * improved CurrentTemp text size
+  * UbuntuAnimations should be used (LP: #1218805)
+  * Replaced ValueSelector with new OptionSelector in Settings.
+  * Implemented a new scrolling.
+  * Added pressure data and modified the transition animation.
+  * Font color should be always white, with drop shadow.
+
+  [Sergio Schvezov]
+  * changing applicationName to com.ubuntu.weather to write in the unconfined areas
+  * cleaning up python code
+  * add the click data into the package and added apparmor rules (LP: #121839)
+
+ -- Martin Borho <martin@xxxxxxxxx>  Tue, 8 Oct 2013 22:42:00 +0200
+
+ubuntu-weather-app (0.3) raring; urgency=low
+  
+  * LocationManager-Sheet added
+  * Settings-Sheet added
+  * Autopilot tests
+  * Data-provider calls now in WorkerScript
+  * ActivityIndicators while loading
+  * Suru theme added
+  * Displaying more weather data for every location
+
+ -- Martin Borho <martin@xxxxxxxxx>  Tue, 13 Aug 2013 21:33:33 +0200
+
+ubuntu-weather-app (0.2) raring; urgency=low
+
+  * Added support for packaging and installing translations 
+
+ -- David Planella <david.planella@xxxxxxxxxx>  Tue, 28 May 2013 14:21:33 +0200
+
+ubuntu-weather-app (0.1) raring; urgency=low
+
+  * Initial release
+
+ -- Michael Hall <mhall119@xxxxxxxxxx>  Mon, 11 Feb 2013 16:04:00 -0500

=== added file 'debian/compat'
--- debian/compat	1970-01-01 00:00:00 +0000
+++ debian/compat	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+9

=== added file 'debian/control'
--- debian/control	1970-01-01 00:00:00 +0000
+++ debian/control	2015-06-28 23:49:12 +0000
@@ -0,0 +1,41 @@
+Source: ubuntu-weather-app
+Priority: extra
+Maintainer: Ubuntu App Cats <ubuntu-touch-coreapps@xxxxxxxxxxxxxxxxxxx>
+Build-Depends: cmake,
+               debhelper (>= 9),
+               gettext,
+               intltool,
+               python3 | python-all | python3-dev | python3-all-dev,
+               python3-minimal,
+               qtbase5-dev,
+               qtbase5-dev-tools,
+               qtdeclarative5-dev,
+               qtdeclarative5-dev-tools,
+               qtdeclarative5-ubuntu-ui-toolkit-plugin,
+Standards-Version: 3.9.6
+Section: misc
+Homepage: https://launchpad.net/ubuntu-weather-app
+Vcs-Bzr: https://code.launchpad.net/~ubuntu-weather-dev/ubuntu-weather-app/trunk
+
+Package: ubuntu-weather-app
+Architecture: all
+Depends: qmlscene,
+         qtdeclarative5-localstorage-plugin,
+         qml-module-qt-labs-settings,
+         qtdeclarative5-qtquick2-plugin,
+         qtdeclarative5-ubuntu-ui-toolkit-plugin | qt-components-ubuntu,
+         suru-icon-theme | ubuntu-mobile-icons,
+         ${misc:Depends}
+Description: Weather application
+ Core Weather application
+
+Package: ubuntu-weather-app-autopilot
+Architecture: all
+Depends: libautopilot-qt (>= 1.4),
+         libqt5test5,
+         libqt5widgets5,
+         ubuntu-ui-toolkit-autopilot,
+         ubuntu-weather-app (>= ${source:Version}),
+         ${misc:Depends}
+Description: Test package for the weather app
+ Autopilot tests for the weather app package

=== added file 'debian/copyright'
--- debian/copyright	1970-01-01 00:00:00 +0000
+++ debian/copyright	2015-06-28 23:49:12 +0000
@@ -0,0 +1,54 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: ubuntu-weather-app
+Source: https://launchpad.net/ubuntu-weather-app
+
+Files: *
+Copyright: 2013 Canonical Ltd.
+           2013 Jakub <jakub@xxxxxxxxxxx>
+           2013 Martin Borho <martin@xxxxxxxxx>
+           2013 Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+           2013 Omer Akram <om26er@xxxxxxxxxx>
+           2013 Raúl Yeguas <neokore@xxxxxxxxx>
+           2013 Riccardo Padovani <rpadovani@xxxxxxxxxxxxx>
+           2013 Zonov Roman <roman2861@xxxxxxxxx>
+           2015 Andrew Hayzen <ahayzen@xxxxxxxxx>
+           2015 Victor Thompson <victor.thompson@xxxxxxxxx>
+License: GPL-3
+
+Files: debian/*
+Copyright: 2013 Canonical Ltd.
+License: LGPL-3
+
+License: GPL-3
+ This package is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License can be found in "/usr/share/common-licenses/GPL-3".
+
+License: LGPL-3
+ This package is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License can be found in "/usr/share/common-licenses/LGPL-3".

=== added file 'debian/rules'
--- debian/rules	1970-01-01 00:00:00 +0000
+++ debian/rules	2015-06-28 23:49:12 +0000
@@ -0,0 +1,14 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+	dh $@
+
+override_dh_auto_configure:
+	dh_auto_configure -- -DCLICK_MODE=OFF
+
+override_dh_install:
+	dh_install --list-missing

=== added directory 'debian/source'
=== added file 'debian/source/format'
--- debian/source/format	1970-01-01 00:00:00 +0000
+++ debian/source/format	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+1.0

=== added file 'debian/ubuntu-weather-app-autopilot.install'
--- debian/ubuntu-weather-app-autopilot.install	1970-01-01 00:00:00 +0000
+++ debian/ubuntu-weather-app-autopilot.install	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+usr/lib

=== added file 'debian/ubuntu-weather-app.install'
--- debian/ubuntu-weather-app.install	1970-01-01 00:00:00 +0000
+++ debian/ubuntu-weather-app.install	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+usr/share

=== added file 'manifest.json.in'
--- manifest.json.in	1970-01-01 00:00:00 +0000
+++ manifest.json.in	2015-06-28 23:49:12 +0000
@@ -0,0 +1,23 @@
+{
+    "architecture": "all",
+    "description": "A weather forecast application for Ubuntu with support for multiple online weather data sources",
+    "framework": "ubuntu-sdk-15.04-qml",
+    "hooks": {
+        "weather": {
+            "apparmor": "ubuntu-weather-app.apparmor",
+            "desktop": "@CMAKE_INSTALL_DATADIR@/applications/ubuntu-weather-app.desktop"
+        }
+    },
+    "icon": "@ICON@",
+    "maintainer": "Ubuntu App Cats <ubuntu-touch-coreapps@xxxxxxxxxxxxxxxxxxx>",
+    "name": "@PROJECT_NAME@",
+    "title": "Weather",
+    "version": "3.0.@BZR_REVNO@",
+    "x-source": {
+        "vcs-bzr": "@BZR_SOURCE@",
+        "vcs-bzr-revno": "@BZR_REVNO@"
+    },
+    "x-test": {
+        "autopilot": "@AUTOPILOT_DIR@"
+    }
+}

=== added directory 'po'
=== renamed directory 'po' => 'po.moved'
=== added file 'po/CMakeLists.txt'
--- po/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ po/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,33 @@
+include(FindGettext)
+find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
+
+set(DOMAIN ${PROJECT_NAME})
+set(POT_FILE ${DOMAIN}.pot)
+file(GLOB PO_FILES *.po)
+
+# Creates the .pot file containing the translations template
+add_custom_target(${POT_FILE} ALL
+    COMMENT "Generating translation template"
+    COMMAND ${INTLTOOL_EXTRACT} --update --type=gettext/ini
+        --srcdir=${CMAKE_SOURCE_DIR} ${DESKTOP_FILE}.in.in
+    COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} -o ${POT_FILE}
+        -D ${CMAKE_CURRENT_SOURCE_DIR}
+        -D ${CMAKE_CURRENT_BINARY_DIR}
+        --from-code=UTF-8
+        --c++ --qt --add-comments=TRANSLATORS
+        --keyword=tr --keyword=tr:1,2 --keyword=N_
+        --package-name='${APP_HARDCODE}'
+        --copyright-holder='Canonical Ltd.'
+        ${I18N_SRC_FILES}
+    COMMAND ${CMAKE_COMMAND} -E copy ${POT_FILE} ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Builds the binary translations catalog for each language
+# it finds source translations (*.po) for
+foreach(PO_FILE ${PO_FILES})
+    get_filename_component(LANG ${PO_FILE} NAME_WE)
+    gettext_process_po_files(${LANG} ALL PO_FILES ${PO_FILE})
+    set(INSTALL_DIR ${CMAKE_INSTALL_LOCALEDIR}/${LANG}/LC_MESSAGES)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.gmo
+            DESTINATION ${INSTALL_DIR}
+            RENAME ${DOMAIN}.mo)
+endforeach(PO_FILE)

=== added file 'po/com.ubuntu.weather.pot'
--- po/com.ubuntu.weather.pot	1970-01-01 00:00:00 +0000
+++ po/com.ubuntu.weather.pot	2015-06-28 23:49:12 +0000
@@ -0,0 +1,218 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Canonical Ltd.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: ubuntu-weather-app\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-06-21 20:10+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@xxxxxx>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: ../app/components/DayDelegate.qml:193
+msgid "Chance of rain"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:202
+msgid "Winds"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:209
+msgid "UV Index"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:214
+msgid "Pollen"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:220
+msgid "Humidity"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:226
+msgid "Sunrise"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:232
+msgid "Sunset"
+msgstr ""
+
+#: ../app/components/HomeTempInfo.qml:38
+msgid "Today"
+msgstr ""
+
+#: ../app/components/ListItemActions/Remove.qml:26
+msgid "Remove"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:27
+msgid "Select All"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:39
+msgid "Delete"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:51
+msgid "Cancel selection"
+msgstr ""
+
+#: ../app/ubuntu-weather-app.qml:167
+msgid "Searching for current location..."
+msgstr ""
+
+#: ../app/ubuntu-weather-app.qml:176
+msgid "Add a manual location"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:29
+msgid "Select a city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:45 ../app/ui/AddLocationPage.qml:66
+msgid "Back"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:51
+msgid "City"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:93
+msgid "Search city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:278
+msgid "No city found"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:291
+msgid "Couldn't load weather data, please try later again!"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:301
+msgid "Location already added."
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:304
+msgid "OK"
+msgstr ""
+
+#: ../app/ui/HomePage.qml:31 ../app/ui/LocationsPage.qml:30
+msgid "Locations"
+msgstr ""
+
+#: ../app/ui/LocationsPage.qml:104
+msgid "Current Location"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:24
+msgid "Settings"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:38 ../app/ui/settings/UnitsPage.qml:24
+msgid "Units"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:43 ../app/ui/settings/DataProviderPage.qml:24
+msgid "Data Provider"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:48 ../app/ui/settings/RefreshIntervalPage.qml:24
+msgid "Refresh Interval"
+msgstr ""
+
+#: ../app/ui/settings/DataProviderPage.qml:37
+msgid "Provider"
+msgstr ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:30
+#: ../app/ui/settings/RefreshIntervalPage.qml:31
+#: ../app/ui/settings/RefreshIntervalPage.qml:32
+#: ../app/ui/settings/RefreshIntervalPage.qml:33
+#: ../app/ui/settings/RefreshIntervalPage.qml:43
+#, qt-format
+msgid "%1 minute"
+msgid_plural "%1 minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:42
+msgid "Interval"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of temperature in Celcius and are shown in the settings page.
+#. Only the abbreviated form of Celcius should be used.
+#: ../app/ui/settings/UnitsPage.qml:41 ../app/ui/settings/UnitsPage.qml:92
+msgid "°C"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of temperature in Fahrenheit and are shown in the settings page.
+#. Only the abbreviated form of Fahrenheit should be used.
+#: ../app/ui/settings/UnitsPage.qml:46 ../app/ui/settings/UnitsPage.qml:93
+msgid "°F"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of precipitation in millimeters and are shown in the settings page.
+#. Only the abbreviated form of millimeters should be used.
+#: ../app/ui/settings/UnitsPage.qml:57 ../app/ui/settings/UnitsPage.qml:112
+msgid "mm"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of precipitation in inches and are shown in the settings page.
+#. Only the abbreviated form of inches should be used.
+#: ../app/ui/settings/UnitsPage.qml:62 ../app/ui/settings/UnitsPage.qml:113
+msgid "in"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of wind speed in kilometers per hour and are shown in the settings page.
+#. Only the abbreviated form of kilometers per hour should be used.
+#: ../app/ui/settings/UnitsPage.qml:73 ../app/ui/settings/UnitsPage.qml:132
+msgid "kph"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of wind speed in miles per hour and are shown in the settings page.
+#. Only the abbreviated form of miles per hour should be used.
+#: ../app/ui/settings/UnitsPage.qml:78 ../app/ui/settings/UnitsPage.qml:133
+msgid "mph"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:91
+msgid "Temperature"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:111
+msgid "Precipitation"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:131
+msgid "Wind Speed"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:1
+msgid "Weather"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:2
+msgid ""
+"A weather forecast application for Ubuntu with support for multiple online "
+"weather data sources"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:3
+msgid "weather;forecast;twc;openweathermap;the weather channel;"
+msgstr ""

=== added directory 'tests'
=== renamed directory 'tests' => 'tests.moved'
=== added file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ tests/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+add_subdirectory(autopilot)

=== added directory 'tests/autopilot'
=== added file 'tests/autopilot/CMakeLists.txt'
--- tests/autopilot/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ tests/autopilot/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,10 @@
+if(INSTALL_TESTS)
+execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
+    OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+install(DIRECTORY ${AUTOPILOT_DIR}
+    DESTINATION ${PYTHON_PACKAGE_DIR}
+    )
+endif(INSTALL_TESTS)
+
+add_subdirectory(ubuntu_weather_app)

=== added directory 'tests/autopilot/ubuntu_weather_app'
=== added file 'tests/autopilot/ubuntu_weather_app/CMakeLists.txt'
--- tests/autopilot/ubuntu_weather_app/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,8 @@
+add_subdirectory(tests)
+
+# make the emulator files visible on qtcreator
+file(GLOB PYTHON_EMULATOR_FILES
+     RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+     *.py)
+
+add_custom_target(ubuntu-weather-app_PYTHONEMULATORFiles ALL SOURCES ${PYTHON_EMULATOR_FILES})

=== added file 'tests/autopilot/ubuntu_weather_app/__init__.py'
--- tests/autopilot/ubuntu_weather_app/__init__.py	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/__init__.py	2015-06-28 23:49:12 +0000
@@ -0,0 +1,59 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+# Copyright 2013, 2014, 2015 Canonical
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+
+"""ubuntu-weather-app tests and emulators - top level package."""
+from ubuntuuitoolkit import MainView, UbuntuUIToolkitCustomProxyObjectBase
+
+
+class UbuntuWeatherAppException(Exception):
+    """Exception raised when there's an error in the Weather App."""
+
+
+def click_object(func):
+    """Wrapper which clicks the returned object"""
+    def func_wrapper(self, *args, **kwargs):
+        return self.pointing_device.click_object(func(self, *args, **kwargs))
+
+    return func_wrapper
+
+
+class UbuntuWeatherApp(object):
+    """Autopilot helper object for the Weather application."""
+
+    def __init__(self, app_proxy):
+        self.app = app_proxy
+        self.main_view = self.app.wait_select_single(MainView)
+
+    def get_add_location_page(self):
+        return self.main_view.wait_select_single(
+            AddLocationPage, objectName="addLocationPage")
+
+    def click_add_location_button(self):
+        add_location_button = self.main_view.wait_select_single(
+            "Button", objectName="emptyStateButton")
+        self.app.pointing_device.click_object(add_location_button)
+
+
+class Page(UbuntuUIToolkitCustomProxyObjectBase):
+    """Autopilot helper for Pages."""
+    def __init__(self, *args):
+        super(Page, self).__init__(*args)
+
+
+class AddLocationPage(Page):
+    """Autopilot helper for AddLocationPage."""
+    def __init__(self, *args):
+        super(AddLocationPage, self).__init__(*args)
+
+
+class MainView(MainView):
+    """Autopilot custom proxy object for the MainView."""
+    retry_delay = 0.2
+
+    def __init__(self, *args):
+        super(MainView, self).__init__(*args)
+        self.visible.wait_for(True)

=== added directory 'tests/autopilot/ubuntu_weather_app/tests'
=== added file 'tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt'
--- tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt	2015-06-28 23:49:12 +0000
@@ -0,0 +1,6 @@
+# make the test files visible on qtcreator
+file(GLOB PYTHON_TEST_FILES
+     RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+     *.py)
+
+add_custom_target(ubuntu-weather-app_PYTHONTESTFiles ALL SOURCES ${PYTHON_TEST_FILES})

=== added file 'tests/autopilot/ubuntu_weather_app/tests/__init__.py'
--- tests/autopilot/ubuntu_weather_app/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/__init__.py	2015-06-28 23:49:12 +0000
@@ -0,0 +1,150 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+#
+# Copyright (C) 2013, 2014, 2015 Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Weather app autopilot tests."""
+
+import os
+import os.path
+import shutil
+import logging
+
+import fixtures
+from ubuntu_weather_app import UbuntuWeatherApp
+
+from autopilot import logging as autopilot_logging
+from autopilot.testcase import AutopilotTestCase
+
+import ubuntuuitoolkit
+from ubuntuuitoolkit import base
+
+logger = logging.getLogger(__name__)
+
+
+class BaseTestCaseWithPatchedHome(AutopilotTestCase):
+
+    """A common test case class that provides several useful methods for
+    ubuntu-weather-app tests.
+
+    """
+
+    working_dir = os.getcwd()
+    local_location_dir = os.path.dirname(os.path.dirname(working_dir))
+    local_location = local_location_dir + "/app/ubuntu-weather-app.qml"
+    installed_location = "/usr/share/ubuntu-weather-app/app/" \
+        "ubuntu-weather-app.qml"
+
+    def get_launcher_method_and_type(self):
+        if os.path.exists(self.local_location):
+            launch = self.launch_test_local
+            test_type = 'local'
+        elif os.path.exists(self.installed_location):
+            launch = self.launch_test_installed
+            test_type = 'deb'
+        else:
+            launch = self.launch_test_click
+            test_type = 'click'
+        return launch, test_type
+
+    def setUp(self):
+        super(BaseTestCaseWithPatchedHome, self).setUp()
+        self.launcher, self.test_type = self.get_launcher_method_and_type()
+        self.home_dir = self._patch_home()
+
+    @autopilot_logging.log_action(logger.info)
+    def launch_test_local(self):
+        return self.launch_test_application(
+            base.get_qmlscene_launch_command(),
+            self.local_location,
+            "debug",
+            app_type='qt',
+            emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+    @autopilot_logging.log_action(logger.info)
+    def launch_test_installed(self):
+        return self.launch_test_application(
+            base.get_qmlscene_launch_command(),
+            self.installed_location,
+            "debug",
+            app_type='qt',
+            emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+    @autopilot_logging.log_action(logger.info)
+    def launch_test_click(self):
+        return self.launch_click_package(
+            "com.ubuntu.weather",
+            emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+    def _copy_xauthority_file(self, directory):
+        """ Copy .Xauthority file to directory, if it exists in /home
+        """
+        # If running under xvfb, as jenkins does,
+        # xsession will fail to start without xauthority file
+        # Thus if the Xauthority file is in the home directory
+        # make sure we copy it to our temp home directory
+
+        xauth = os.path.expanduser(os.path.join(os.environ.get('HOME'),
+                                   '.Xauthority'))
+        if os.path.isfile(xauth):
+            logger.debug("Copying .Xauthority to %s" % directory)
+            shutil.copyfile(
+                os.path.expanduser(os.path.join(os.environ.get('HOME'),
+                                   '.Xauthority')),
+                os.path.join(directory, '.Xauthority'))
+
+    def _patch_home(self):
+        """ mock /home for testing purposes to preserve user data
+        """
+
+        # if running on non-phablet device,
+        # run in temp folder to avoid mucking up home
+        # bug 1316746
+        # bug 1376423
+        if self.test_type is 'click':
+            # just use home for now on devices
+            temp_dir = os.environ.get('HOME')
+
+            # before each test, remove the app's databases
+            local_dir = os.path.join(temp_dir,
+                                     '.local/share/com.ubuntu.weather')
+
+            if (os.path.exists(local_dir)):
+                shutil.rmtree(local_dir)
+
+            local_dir = os.path.join(temp_dir, '.config/com.ubuntu.weather')
+
+            if (os.path.exists(local_dir)):
+                shutil.rmtree(local_dir)
+        else:
+            temp_dir_fixture = fixtures.TempDir()
+            self.useFixture(temp_dir_fixture)
+            temp_dir = temp_dir_fixture.path
+
+            # before we set fixture, copy xauthority if needed
+            self._copy_xauthority_file(temp_dir)
+            self.useFixture(fixtures.EnvironmentVariable('HOME',
+                                                         newvalue=temp_dir))
+
+            logger.debug("Patched home to fake home directory %s" % temp_dir)
+        return temp_dir
+
+
+class UbuntuWeatherAppTestCase(BaseTestCaseWithPatchedHome):
+
+    """Base test case that launches the ubuntu-weather-app."""
+
+    def setUp(self):
+        super(UbuntuWeatherAppTestCase, self).setUp()
+        self.app = UbuntuWeatherApp(self.launcher())

=== added file 'tests/autopilot/ubuntu_weather_app/tests/test_weather.py'
--- tests/autopilot/ubuntu_weather_app/tests/test_weather.py	1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/test_weather.py	2015-06-28 23:49:12 +0000
@@ -0,0 +1,35 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+# Copyright 2013, 2014, 2015 Canonical
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+
+"""Ubuntu Weather app autopilot tests."""
+
+from __future__ import absolute_import
+
+import logging
+from autopilot.matchers import Eventually
+from testtools.matchers import Equals
+
+
+from ubuntu_weather_app.tests import UbuntuWeatherAppTestCase
+
+logger = logging.getLogger(__name__)
+
+
+class TestMainWindow(UbuntuWeatherAppTestCase):
+
+    def setUp(self):
+        super(TestMainWindow, self).setUp()
+
+    def test_add_location_button(self):
+        """ tests that the add location page is shown after the Add Location
+            button is clicked """
+
+        self.app.click_add_location_button()
+
+        add_location_page = self.app.get_add_location_page()
+
+        self.assertThat(add_location_page.visible, Eventually(Equals(True)))

=== added file 'ubuntu-weather-app.apparmor'
--- ubuntu-weather-app.apparmor	1970-01-01 00:00:00 +0000
+++ ubuntu-weather-app.apparmor	2015-06-28 23:49:12 +0000
@@ -0,0 +1,8 @@
+{
+    "policy_groups": [
+    	"location",
+    	"networking",
+    	"sensors"
+    ], 
+    "policy_version": 1.3
+}

=== added file 'ubuntu-weather-app.desktop.in.in'
--- ubuntu-weather-app.desktop.in.in	1970-01-01 00:00:00 +0000
+++ ubuntu-weather-app.desktop.in.in	2015-06-28 23:49:12 +0000
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@EXEC@
+Icon=@ICON@
+_Name=Weather
+_Comment=A weather forecast application for Ubuntu with support for multiple online weather data sources
+_Keywords=weather;forecast;twc;openweathermap;the weather channel;
+X-Ubuntu-Touch=true
+X-Ubuntu-Default-Department-ID=accessories
+X-Ubuntu-Splash-Color=#F5F5F5