ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #00720
[Merge] lp:~vthompson/ubuntu-weather-app/reboot-fix-location-reordering-text into lp:ubuntu-weather-app
Victor Thompson has proposed merging lp:~vthompson/ubuntu-weather-app/reboot-fix-location-reordering-text into lp:ubuntu-weather-app.
Commit message:
* Fix eliding on LocationsPage
* Hide condition data on LocationsPage when in multiselect mode
Requested reviews:
Ubuntu Weather Developers (ubuntu-weather-dev)
For more details, see:
https://code.launchpad.net/~vthompson/ubuntu-weather-app/reboot-fix-location-reordering-text/+merge/252983
* Fix eliding on LocationsPage
* Hide condition data on LocationsPage when in multiselect mode
There are some spacing issues when in multiselect mode: http://i.imgur.com/dyz529A.png
This MP should fix that.
--
Your team Ubuntu Weather Developers is requested to review the proposed merge of lp:~vthompson/ubuntu-weather-app/reboot-fix-location-reordering-text into lp:ubuntu-weather-app.
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2015-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,108 @@
+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")
+
+ execute_process(
+ COMMAND dpkg-architecture -qDEB_HOST_ARCH
+ OUTPUT_VARIABLE CLICK_ARCH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ 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(backend)
+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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,40 @@
+# 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 retaining the build directory:
+ `click-buddy --dir . --no-clean`
+ 2. Copy the .pot file from the <build dir> mentioned in the output to your
+ original source:
+ `cp <build dir>/po/*.pot po/`
+ 3. Commit the generated .pot file:
+ `bzr commit -m"Updated translation template"`
+ 4. 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-03-14 20:11:20 +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-03-14 20:11:20 +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/DayDelegate.qml'
--- app/components/DayDelegate.qml 1970-01-01 00:00:00 +0000
+++ app/components/DayDelegate.qml 2015-03-14 20:11:20 +0000
@@ -0,0 +1,95 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+ListItem.Standard {
+ height: units.gu(8)
+
+ // TODO: will expand when clicked to reveal more info
+
+ property alias day: dayLabel.text
+ property alias image: weatherImage.source
+ property alias high: highLabel.text
+ property alias low: lowLabel.text
+
+ // Standard divider is not full width so add a ThinDivider to the bottom
+ showDivider: false
+
+ ListItem.ThinDivider {
+ anchors {
+ bottom: parent.bottom
+ }
+ }
+
+ Label {
+ id: dayLabel
+ anchors {
+ left: parent.left
+ right: weatherImage.left
+ rightMargin: units.gu(1)
+ verticalCenter: parent.verticalCenter
+ }
+ elide: Text.ElideRight
+ font.weight: Font.Light
+ fontSize: "medium"
+ }
+
+ Image {
+ id: weatherImage
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ height: units.gu(3)
+ width: units.gu(3)
+ }
+
+ Label {
+ id: lowLabel
+ anchors {
+ left: weatherImage.right
+ right: highLabel.left
+ rightMargin: units.gu(1)
+ verticalCenter: parent.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?
+ }
+}
=== added file 'app/components/FastScroll.js'
--- app/components/FastScroll.js 1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.js 2015-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** 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.qml
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+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/HeaderRow.qml'
--- app/components/HeaderRow.qml 1970-01-01 00:00:00 +0000
+++ app/components/HeaderRow.qml 2015-03-14 20:11:20 +0000
@@ -0,0 +1,65 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+
+
+Row {
+ spacing: units.gu(2)
+ width: parent.width
+
+ property alias locationName: locationNameLabel.text
+
+ Label {
+ id: locationNameLabel
+ color: UbuntuColors.darkGrey
+ elide: Text.ElideRight
+ font.weight: Font.Normal
+ fontSize: "large"
+ height: settingsButton.height
+ width: parent.width - settingsButton.width - parent.spacing
+ 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
+ name: "settings"
+ height: width
+ 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-03-14 20:11:20 +0000
@@ -0,0 +1,38 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+
+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
+ 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-03-14 20:11:20 +0000
@@ -0,0 +1,94 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import QtGraphicalEffects 1.0
+
+Item {
+ id: homeHourly
+
+ onVisibleChanged: {
+ if(visible) {
+ ListView.model = forecasts.length
+ }
+ }
+
+ ListView {
+ id: hourlyForecasts
+ width:parent.width
+ height:parent.height
+ model: forecasts.length
+ orientation: ListView.Horizontal
+ clip:true
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ homeGraphic.visible = true
+ }
+ }
+ delegate: Rectangle {
+ width: childrenRect.width
+ height: parent.height
+ property var hourData: forecasts[index]
+ Column {
+ id: hourColumn
+ width: units.gu(10)
+ height: childrenRect.height
+ anchors.verticalCenter: parent.verticalCenter
+ Label {
+ text: formatTimestamp(hourData.date, 'ddd')+" "+formatTime(hourData.date, 'h:mm')
+ fontSize: "small"
+ font.weight: Font.Light
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ Item {
+ width: units.gu(7)
+ height: units.gu(7)
+ anchors.horizontalCenter: parent.horizontalCenter
+ Image {
+ id: iconImage
+ fillMode: Image.PreserveAspectFit
+ anchors.fill: parent
+ source: (hourData.icon !== undefined && iconMap[hourData.icon] !== undefined) ? iconMap[hourData.icon] : ""
+ }
+ ColorOverlay {
+ anchors.fill: iconImage
+ source: iconImage
+ color: UbuntuColors.orange
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ Label {
+ font.pixelSize: units.gu(3)
+ font.weight: Font.Light
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: Math.round(hourData[tempUnits].temp).toString()+settings.tempScale
+ }
+
+ }
+ Rectangle {
+ width: units.gu(0.1)
+ height: hourColumn.height
+ color: UbuntuColors.darkGrey
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+}
=== added file 'app/components/HomeTempInfo.qml'
--- app/components/HomeTempInfo.qml 1970-01-01 00:00:00 +0000
+++ app/components/HomeTempInfo.qml 2015-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+
+
+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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012-2014 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.2
+import Ubuntu.Components 1.1
+
+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-03-14 20:11:20 +0000
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 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.3
+import Ubuntu.Components 1.1
+
+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-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+
+
+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-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+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/MultiSelectHeadState.qml'
--- app/components/MultiSelectHeadState.qml 1970-01-01 00:00:00 +0000
+++ app/components/MultiSelectHeadState.qml 2015-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+
+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-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+
+
+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-03-14 20:11:20 +0000
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2014 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.2
+import Ubuntu.Components 1.1
+
+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/WeatherListItem.qml'
--- app/components/WeatherListItem.qml 1970-01-01 00:00:00 +0000
+++ app/components/WeatherListItem.qml 2015-03-14 20:11:20 +0000
@@ -0,0 +1,139 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+
+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-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+
+
+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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,189 @@
+/*
+ * 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.3
+
+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))
+
+ // Move to new Settings API
+ settings.migrated = true
+ settings.precipUnits = oldSettings["precip_units"]
+ settings.service = oldSettings["service"]
+ settings.tempScale = "°" + (oldSettings["units"] === "metric" ? "C" : "F")
+ settings.units = oldSettings["units"]
+ settings.windUnits = oldSettings["wind_units"]
+
+ /*
+ 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 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-03-14 20:11:20 +0000
@@ -0,0 +1,750 @@
+.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 = 20150307;
+
+/**
+* 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 calcKmh(ms) {
+ return ms*3.6;
+}
+//
+function convertKmhToMph(kmh) {
+ return kmh*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¶m2=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: calcKmh(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: calcKmh(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: convertKmhToMph(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: convertKmhToMph(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["current"]) {
+ var today = todayDate.year+"-"+todayDate.month+"-"+todayDate.date
+ tmpResult[today]["current"] = _buildDataPoint(todayDate, data["current"]);
+ }
+ 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));
+ }
+ })
+ }
+ //
+ 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
+});
+
+/**
+* following WorkerScript handles the data requests against the weather API.
+* "message" requires a "params" property with the required params to perform
+* the API call and an "action" property, which will be added also to the response.
+*/
+
+if(typeof WorkerScript != "undefined") {
+ WorkerScript.onMessage = function(message) {
+ // handles the response data
+ var finished = function(result) {
+ // print result to get data for test json files
+ // print(JSON.stringify(result));
+ WorkerScript.sendMessage({
+ action: message.action,
+ result: result
+ })
+ }
+ // handles errors
+ var onError = function(err) {
+ console.log(JSON.stringify(err, null, true));
+ WorkerScript.sendMessage({ '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-03-14 20:11:20 +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-03-14 20:11:20 +0000 differ
=== added file 'app/graphics/CMakeLists.txt'
--- app/graphics/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/graphics/CMakeLists.txt 2015-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +0000 differ
=== added file 'app/graphics/clouds.svg'
--- app/graphics/clouds.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/clouds.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M59.975,15.531c-6.848,0.01-13.273,3.294-17.295,8.844l-0.411,0.567l-0.671-0.19
+ c-1.473-0.416-2.996-0.631-4.526-0.637c-9.091,0.014-16.524,7.214-16.842,16.316l-0.031,0.855l-0.849,0.102
+ c-6.084,0.731-10.651,5.881-10.661,12.02v0.002c0.001,6.707,5.407,12.121,12.102,12.121h3.322c0.589-1.477,1.377-2.828,2.297-4.092
+ l-5.6,0.092H20.8c-4.47,0-8.115-3.646-8.115-8.115c0-3.366,1.763-5.854,4.17-7.211c2.048-1.154,4.522-1.433,6.938-1.211
+ c-0.458-4.202,0.209-8.238,2.176-11.357c2.15-3.412,5.85-5.666,10.734-5.666c2.806,0,5.319,1.063,7.457,2.701
+ c2.545-6.614,8.791-11.138,15.965-11.141c9.521,0.001,17.26,7.74,17.26,17.26v0.002v0.002c-0.005,1.498-0.358,2.956-0.745,4.399
+ c5.03,0.398,9.048,4.506,9.048,9.638c0.001,4.139-2.635,7.588-6.279,9.016c0.326,1.08,0.611,2.18,0.791,3.318
+ c0.511,0.35,0.987,0.742,1.451,1.146c5.283-2.223,9.037-7.371,9.037-13.481c0-5.812-3.426-11.069-8.73-13.424l-0.58-0.257
+ l-0.014-0.636C81.113,24.848,71.623,15.539,59.975,15.531 M57.005,48.5c-7.043,0-13.026,4.346-15.889,10.567l-0.246,0.537
+ l-0.59,0.043c-6.467,0.471-11.592,6.07-11.592,12.9c0,7.137,5.573,12.984,12.455,12.984h15.861H71.79
+ c5.129,0,9.283-4.365,9.283-9.666c0-4.021-2.406-7.385-5.82-8.838l-0.58-0.246l-0.027-0.629C74.202,56.361,66.522,48.5,57.005,48.5
+ M57.079,52.549h0.002c6.954,0,12.623,5.336,13.289,12.119l0.105-0.105l-0.01,1.07c0.003,0.113,0.033,0.221,0.033,0.336v0.002
+ c0,0.309-0.021,0.602-0.043,0.895v0.141c0,0.033-0.009,0.041-0.01,0.074c0,0.004,0,0.004,0,0.008
+ c-0.02,0.967-0.148,1.457-0.273,2.15c0.313-0.035,0.307-0.07,0.711-0.076h0.01h0.01c3.422,0,6.217,2.797,6.217,6.219
+ s-2.795,6.217-6.217,6.219l-29.236,0.012c-4.991,0-9.058-4.066-9.059-9.057c0-4.992,4.066-9.059,9.059-9.059
+ c0.797,0,1.559,0.252,2.324,0.459C44.999,57.496,50.432,52.555,57.079,52.549"/>
+</svg>
=== added file 'app/graphics/cloudy-night.svg'
--- app/graphics/cloudy-night.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/cloudy-night.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M22.384,58.326c0.157,0.785,0.342,1.566,0.555,2.34C22.726,59.893,22.541,59.111,22.384,58.326"/>
+<path fill="#818181" d="M33.675,14.738v0.007h-0.012c-1.445,2.071-2.622,4.172-3.442,5.757c-1.776,0.263-4.07,0.684-6.311,1.341
+ v0.007c0,0.003,0.012,0,0.012,0v0.007c1.472,2.028,3.06,3.815,4.286,5.108c-0.262,1.768-0.519,4.046-0.54,6.352
+ c0,0.003,0.012,0,0.012,0c0,0.003,0.012,0,0.012,0c2.334-0.807,4.47-1.787,6.056-2.578c1.612,0.814,3.758,1.801,6,2.564
+ c0-0.002,0.012,0.003,0.012,0c0-0.002,0-0.006,0-0.006c0-0.003,0-0.007,0-0.007c-0.037-2.434-0.288-4.731-0.539-6.477
+ c1.256-1.303,2.847-3.076,4.244-5.004c0-0.003,0,0-0.012,0l-0.013-0.007c-2.329-0.681-4.598-1.122-6.338-1.396
+ c-0.836-1.613-2.019-3.708-3.415-5.668C33.687,14.738,33.675,14.735,33.675,14.738z"/>
+<path fill="#818181" d="M14.458,35.462v0.005c-0.91,1.304-1.652,2.628-2.169,3.627c-1.119,0.166-2.563,0.431-3.976,0.845v0.004
+ c0,0.003,0,0,0,0v0.005c0.928,1.278,1.928,2.404,2.7,3.218c-0.165,1.114-0.326,2.549-0.34,4.002c0,0.001,0,0,0,0c0,0.001,0,0,0,0
+ c1.47-0.509,2.815-1.126,3.814-1.624c1.016,0.513,2.367,1.135,3.779,1.615c0-0.001,0,0.001,0,0s0-0.004,0-0.004
+ c0-0.001,0-0.004,0-0.005c-0.021-1.533-0.181-2.98-0.339-4.08c0.791-0.821,1.794-1.938,2.673-3.153c0-0.001,0,0,0,0v-0.005
+ c-1.468-0.429-2.896-0.706-3.993-0.879C16.083,38.018,15.338,36.698,14.458,35.462C14.458,35.462,14.458,35.461,14.458,35.462"/>
+<path fill="#818181" d="M67.917,53.49c-6.222,0-11.51,3.842-14.033,9.324v0.004l-0.133,0.291l-0.322,0.021
+ c-5.765,0.422-10.324,5.414-10.324,11.483c0,6.342,4.962,11.556,11.098,11.556h13.719h12.787c4.62,0,8.354-3.929,8.354-8.685
+ c0-3.607-2.164-6.637-5.232-7.942v-0.004l-0.311-0.132l-0.02-0.338v-0.002c-0.392-8.631-7.17-15.576-15.579-15.576H67.917z
+ M67.987,57.638L67.987,57.638h0.264v0.056c6.099,0.145,11.026,5.094,11.026,11.228c0,0.206-0.015,0.397-0.026,0.58v0.015v0.301
+ v0.002c-0.011,0.988-0.195,1.824-0.291,2.295c0.228-0.043,0.428-0.098,0.99-0.108c2.786,0.001,5.051,2.272,5.051,5.058
+ c0,2.784-2.267,5.053-5.051,5.055l-25.287,0.009c-4.142,0.001-7.514-3.366-7.515-7.507c0-4.143,3.372-7.514,7.515-7.512
+ c0.78,0,1.523,0.201,2.246,0.44c0.699-5.594,5.373-9.894,11.073-9.905v-0.005H67.987z M67.987,57.645l-0.248,0.248v0.012
+ c0,0,0.01,0,0.014,0v-0.017l0.016,0.017c0.073-0.002,0.145-0.012,0.218-0.012v-0.002L67.987,57.645L67.987,57.645z"/>
+<path fill="#818181" d="M67.329,20.841c4.776,7.285,6.465,16.21,4.197,24.725v0.002c-1.296,4.837-3.683,9.09-6.816,12.581
+ c1.036-0.322,2.132-0.504,3.273-0.506v-0.005h0.004h0.264v0.056c0.62,0.015,1.223,0.09,1.814,0.201
+ c2.396-3.314,4.242-7.105,5.361-11.284v-0.008c1.096-4.116,1.181-8.337,0.744-12.537l-0.354-3.391h0.002l2.129,2.662
+ c5.514,6.899,7.812,16.207,5.35,25.422v0.002c-0.878,3.277-2.283,6.265-4.094,8.904c0.047,0.413,0.074,0.831,0.074,1.256
+ c0,0.206-0.016,0.397-0.027,0.58v0.015v0.301v0.002c-0.012,0.987-0.195,1.824-0.291,2.295c0.228-0.043,0.428-0.098,0.991-0.108
+ c0.324,0,0.642,0.035,0.95,0.095c2.86-3.5,5.048-7.642,6.295-12.294c4.364-16.322-4.422-32.944-19.865-38.965H67.329z
+ M24.888,63.143c3.756,9.717,11.88,17.168,22.051,19.907v-0.002c6.843,1.834,13.753,1.362,19.92-0.926l-12.195,0.004
+ c-2.345,0.001-4.441-1.08-5.82-2.767c-0.287-0.068-0.574-0.133-0.861-0.209v-0.002c-3.729-1.006-6.977-2.896-9.885-5.123
+ l-2.717-2.08l3.408,0.291c3.014,0.256,6.002,0.108,8.9-0.396c1.107-2.771,3.814-4.735,6.975-4.733c0.78,0,1.523,0.201,2.245,0.44
+ c0.174-1.376,0.589-2.672,1.198-3.847c-7.001,4.381-15.729,5.963-24.326,3.66v-0.002C30.58,66.49,27.638,64.969,24.888,63.143"/>
+<path fill="#818181" d="M67.987,57.645l-0.248,0.248v0.012c0,0,0.01,0,0.014,0v-0.017l0.017,0.017
+ c0.072-0.002,0.144-0.012,0.217-0.012v-0.002L67.987,57.645L67.987,57.645z"/>
+</svg>
=== added file 'app/graphics/cloudy.svg'
--- app/graphics/cloudy.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/cloudy.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818285" d="M53.295,21.823c-11.104,0-20.535,6.845-25.036,16.612l-0.246,0.534l-0.59,0.043
+ c-10.279,0.748-18.41,9.636-18.41,20.439c0,11.291,8.845,20.57,19.783,20.57h24.499H76.13c8.231,0,14.883-6.986,14.883-15.449
+ c0-6.416-3.855-11.809-9.324-14.13l-0.58-0.246l-0.028-0.629C80.383,34.195,68.294,21.823,53.295,21.823 M53.524,25.834h0.008
+ c12.847,0,23.281,10.436,23.281,23.281v0.002v0.004c-0.008,1.425-0.246,2.832-0.512,4.232c5.966,0.365,10.747,5.225,10.746,11.279
+ c0.001,6.295-5.123,11.418-11.418,11.418L29.604,75.91c-9.086-0.002-16.473-7.389-16.473-16.475c0-9.089,7.389-16.478,16.477-16.478
+ c0.507,0.004,1.011,0.07,1.517,0.121C33.848,32.968,42.981,25.844,53.524,25.834"/>
+</svg>
=== added file 'app/graphics/fog.svg'
--- app/graphics/fog.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/fog.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M52.593,12.922c-10.004,0.005-18.5,6.132-22.555,14.873l-0.246,0.536l-0.588,0.043
+ c-9.254,0.669-16.569,8.611-16.568,18.269v1.711v0.568v4h4.169v-4v-0.568v-1.635c0-7.826,6.401-14.187,14.268-14.186
+ c0.548,0.004,1.09,0.09,1.633,0.154c2.178-9.186,10.397-15.778,19.969-15.783c11.354,0,20.587,9.168,20.586,20.459v0.002v0.005
+ c-0.008,1.419-0.303,2.811-0.602,4.198c0.026,0,0.053-0.006,0.08-0.006c5.362,0,9.737,4.343,9.737,9.683
+ c0,5.338-4.375,9.68-9.737,9.68l-60.115-0.004v4h60.631c7.406,0,13.369-6.129,13.369-13.676c0-5.726-3.46-10.533-8.375-12.607
+ l-0.582-0.246l-0.028-0.631c-0.627-13.758-11.518-24.833-25.035-24.84H52.593z M12.624,76.922h74v-4h-74V76.922z M12.624,88.922h74
+ v-4h-74V88.922z"/>
+</svg>
=== added file 'app/graphics/partly-cloudy.svg'
--- app/graphics/partly-cloudy.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/partly-cloudy.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818285" d="M52.013,12.326l-4,1.332v7.279h4V12.326z M31.745,17.295l-3.465,2l4,6.929l3.465-2L31.745,17.295z
+ M68.28,17.295l-4,6.929l3.465,2l4-6.929L68.28,17.295z M50.013,26.937c-14.371,0-26,11.629-26,26
+ c0.008,11.83,7.988,22.154,19.436,25.141l0.131,0.033c0.488,5.753,5.141,10.326,10.828,10.326h13.412h12.498
+ c4.533,0,8.195-3.854,8.195-8.518c0-3.537-2.122-6.509-5.132-7.789l-0.289-0.125l-0.015-0.312
+ c-0.255-5.624-3.288-10.506-7.711-13.15l0.039-0.172c0.394-1.785,0.596-3.609,0.607-5.438
+ C76.011,38.564,64.383,26.937,50.013,26.937 M50.013,30.937c12.139,0,22,9.862,22,22v0.004v0.004
+ c-0.01,1.154-0.109,2.306-0.298,3.445l-0.095,0.564c-1.216-0.326-2.483-0.518-3.801-0.518c-6.097,0-11.277,3.765-13.748,9.135
+ l-0.123,0.268l-0.295,0.021c-4.655,0.339-8.502,3.721-9.717,8.225l-0.468-0.146c-9.189-2.863-15.455-11.379-15.456-21.002
+ C28.013,40.799,37.874,30.937,50.013,30.937 M16.37,31.205l-2,3.465l6.931,4l2-3.465L16.37,31.205z M83.655,31.205l-6.932,4
+ l2.002,3.465l6.93-4L83.655,31.205z M10.013,54.938h8v-4h-8V54.938z M82.013,54.938h8v-4h-8V54.938z M67.872,60.551h0.002h0.009
+ c6.065,0.004,10.993,4.935,10.993,11.002c0,0.201-0.018,0.392-0.029,0.586v0.287c-0.011,0.986-0.192,1.777-0.291,2.279
+ c0.302-0.049,0.428-0.098,1.002-0.107h0.012c2.706,0,4.91,2.203,4.91,4.91s-2.203,4.914-4.91,4.914l-24.717,0.01
+ c-4.032,0-7.314-3.28-7.314-7.312c-0.001-4.033,3.281-7.312,7.314-7.312c0.759,0,1.493,0.205,2.213,0.438
+ C57.735,64.773,62.304,60.562,67.872,60.551 M21.301,67.205l-6.931,4l2,3.465l6.931-4L21.301,67.205z M32.28,79.65l-4,6.93l3.465,2
+ l4-6.93L32.28,79.65z"/>
+</svg>
=== added file 'app/graphics/rain.svg'
--- app/graphics/rain.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/rain.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M67.463,68.004c-2.34,5.406-4.102,10.639-4.219,11.028l-0.016,0.043l-0.014,0.043
+ c0.009-0.024-0.006,0.019-0.043,0.132c-0.15,0.455-0.232,0.928-0.232,1.418c0,2.518,2.01,4.526,4.527,4.526
+ c2.513-0.006,4.516-2.013,4.516-4.526c0-0.496-0.08-0.969-0.229-1.418l-0.01-0.021l-0.01-0.022
+ C71.734,79.207,69.963,73.832,67.463,68.004 M32.829,69.213c-2.008,4.703-3.531,9.19-3.633,9.53l-0.016,0.046l-0.017,0.045
+ c0.017-0.043,0.003-0.002-0.034,0.11v0.002c-0.129,0.392-0.199,0.796-0.199,1.22c0,2.174,1.73,3.903,3.905,3.903
+ c2.167-0.007,3.892-1.733,3.892-3.903c0-0.427-0.069-0.834-0.197-1.222l-0.01-0.022l-0.01-0.021
+ C36.51,78.901,34.975,74.278,32.829,69.213 M50.146,70.35c-2.967,6.725-5.175,13.365-5.322,13.855l-0.012,0.041l-0.018,0.038
+ c0.012-0.034-0.003,0.028-0.052,0.175v0.002c-0.189,0.571-0.293,1.168-0.293,1.785c0,3.166,2.535,5.7,5.703,5.7
+ c3.161-0.007,5.687-2.538,5.687-5.7c0-0.625-0.102-1.223-0.287-1.787l-0.01-0.021l-0.01-0.022
+ C55.533,84.416,53.318,77.611,50.146,70.35"/>
+<path fill="#818181" d="M52.523,8.96c-9.938,0-18.38,6.125-22.41,14.871l-0.246,0.537l-0.59,0.043
+ C20.092,25.08,12.825,33.02,12.825,42.68c0,10.095,7.905,18.386,17.679,18.386h22.02h20.525c7.342,0,13.275-6.232,13.275-13.784
+ c0-5.727-3.439-10.535-8.318-12.608l-0.58-0.246l-0.027-0.629C76.775,20.033,65.949,8.96,52.523,8.96 M52.6,12.942h0.012
+ c11.287,0,20.457,9.172,20.457,20.459v0.002v0.002c-0.004,1.421-0.3,2.814-0.598,4.201c0.023,0,0.048-0.006,0.072-0.006
+ c5.336,0,9.682,4.347,9.682,9.682s-4.346,9.681-9.682,9.681l-41.402-0.021c-7.823,0-14.186-6.362-14.186-14.185
+ c0-7.822,6.363-14.185,14.186-14.185c0.543,0.003,1.079,0.089,1.617,0.154C34.922,19.542,43.085,12.947,52.6,12.942"/>
+</svg>
=== added file 'app/graphics/snow.svg'
--- app/graphics/snow.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/snow.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M52.523,10.719c-9.938,0-18.379,6.125-22.41,14.871l-0.246,0.537l-0.59,0.043
+ c-9.186,0.669-16.453,8.609-16.453,18.269c0,10.094,7.905,18.387,17.679,18.387h22.02h20.525c7.342,0,13.275-6.232,13.275-13.786
+ c0-5.727-3.439-10.535-8.318-12.607l-0.58-0.246l-0.027-0.629C76.775,21.792,65.949,10.719,52.523,10.719 M52.6,14.701h0.012
+ c11.287,0,20.457,9.172,20.457,20.459v0.002v0.002c-0.004,1.421-0.299,2.814-0.598,4.201c0.024,0,0.049-0.006,0.072-0.006
+ c5.336,0,9.682,4.347,9.682,9.682c0,5.334-4.346,9.682-9.682,9.682l-41.402-0.021c-7.823,0-14.186-6.363-14.186-14.186
+ c0-7.822,6.363-14.186,14.186-14.186c0.543,0.003,1.079,0.089,1.617,0.154C34.922,21.301,43.085,14.706,52.6,14.701"/>
+<polygon fill="#818181" points="51.199,73.178 47.95,73.828 47.95,79.105 43.092,76.301 41.467,79.113 46.325,81.92 41.467,84.725
+ 43.092,87.537 47.95,84.732 47.95,90.344 51.199,90.344 51.199,84.732 56.057,87.537 57.682,84.725 52.824,81.92 57.682,79.113
+ 56.057,76.301 51.199,79.105 "/>
+<polygon fill="#818181" points="34.26,68.629 32.006,69.078 32.006,72.705 28.666,70.777 27.539,72.73 30.879,74.658 27.539,76.586
+ 28.666,78.539 32.006,76.611 32.006,80.469 34.26,80.469 34.26,76.611 37.602,78.539 38.729,76.586 35.389,74.658 38.729,72.73
+ 37.602,70.777 34.26,72.705 "/>
+<polygon fill="#818181" points="68.324,66.795 65.662,67.326 65.662,71.455 61.852,69.256 60.52,71.561 64.332,73.762 60.52,75.963
+ 61.852,78.268 65.662,76.066 65.662,80.469 68.324,80.469 68.324,76.066 72.138,78.268 73.467,75.963 69.654,73.762 73.467,71.561
+ 72.138,69.256 68.324,71.455 "/>
+</svg>
=== added file 'app/graphics/starry.svg'
--- app/graphics/starry.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/starry.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M67.59,20.306c4.775,7.285,6.465,16.21,4.197,24.726v0.002C67.385,61.461,50.47,71.227,34.041,66.826v-0.002
+ c-3.2-0.867-6.142-2.391-8.893-4.217c3.757,9.717,11.88,17.166,22.051,19.908v-0.002c17.547,4.701,35.555-5.697,40.258-23.242
+ c4.365-16.322-4.422-32.944-19.865-38.965H67.59z M76.08,30.14h0.002l2.129,2.662c5.514,6.899,7.812,16.207,5.35,25.422v0.002
+ C79.425,73.662,63.679,82.75,48.244,78.615v-0.002c-3.729-1.006-6.977-2.896-9.885-5.123l-2.717-2.08l3.409,0.291
+ c16.48,1.4,32.213-9.111,36.639-25.625v-0.009c1.097-4.115,1.181-8.337,0.744-12.536L76.08,30.14z"/>
+<path fill="#818181" d="M33.936,14.203v0.007h-0.012c-1.445,2.071-2.622,4.173-3.442,5.758c-1.777,0.263-4.07,0.684-6.311,1.341
+ v0.007c0,0.004,0.012,0,0.012,0v0.007c1.472,2.028,3.06,3.815,4.286,5.108c-0.262,1.768-0.518,4.046-0.54,6.352
+ c0,0.003,0.012,0,0.012,0c0,0.003,0.012,0,0.012,0c2.333-0.807,4.469-1.787,6.055-2.578c1.612,0.814,3.758,1.802,6,2.564
+ c0-0.002,0.012,0.003,0.012,0c0-0.002,0-0.006,0-0.006c0-0.003,0-0.008,0-0.008c-0.037-2.434-0.288-4.73-0.539-6.477
+ c1.256-1.303,2.847-3.076,4.244-5.004c0-0.003,0,0-0.012,0l-0.012-0.007c-2.33-0.682-4.598-1.122-6.338-1.396
+ c-0.836-1.612-2.019-3.708-3.415-5.668C33.947,14.203,33.936,14.201,33.936,14.203z"/>
+<path fill="#818181" d="M14.719,34.928v0.005c-0.91,1.304-1.652,2.628-2.169,3.627c-1.119,0.166-2.563,0.431-3.975,0.845v0.004
+ c0,0.003,0,0,0,0v0.005c0.927,1.278,1.927,2.404,2.7,3.219c-0.165,1.113-0.326,2.549-0.34,4.002c1.47-0.51,2.815-1.126,3.814-1.624
+ c1.016,0.513,2.368,1.135,3.78,1.614c0-0.001,0,0.001,0,0s0-0.004,0-0.004c0-0.001,0-0.004,0-0.005
+ c-0.022-1.533-0.181-2.979-0.339-4.08c0.791-0.821,1.794-1.938,2.673-3.152c0-0.001,0,0,0,0v-0.006
+ c-1.468-0.429-2.896-0.706-3.993-0.879C16.344,37.483,15.599,36.163,14.719,34.928C14.719,34.928,14.719,34.927,14.719,34.928z"/>
+</svg>
=== added file 'app/graphics/sunny.svg'
--- app/graphics/sunny.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/sunny.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818285" d="M52.013,10.225l-4,1.332v7.279h4V10.225z M31.745,15.194l-3.464,2l4,6.93l3.464-2L31.745,15.194z
+ M68.281,15.194l-4,6.93l3.464,2l4-6.93L68.281,15.194z M50.013,24.836c-14.371,0-26,11.629-26,26c0,14.373,11.629,26,26,26
+ s26-11.627,26-26C76.013,36.465,64.384,24.836,50.013,24.836 M50.013,28.836c12.139,0,22,9.862,22,22c0,12.138-9.861,22-22,22
+ c-12.138,0-22-9.861-22-22C28.013,38.698,37.875,28.836,50.013,28.836 M16.37,29.104l-2,3.465l6.93,4l2-3.465L16.37,29.104z
+ M83.656,29.104l-6.933,4l2.002,3.465l6.931-4L83.656,29.104z M10.013,52.836h8v-4h-8V52.836z M82.013,52.836h8v-4h-8V52.836z
+ M21.3,65.104l-6.93,4l2,3.465l6.93-4L21.3,65.104z M78.726,65.104l-2.002,3.465l6.933,4l2-3.465L78.726,65.104z M32.281,77.549
+ l-4,6.93l3.464,2l4-6.93L32.281,77.549z M67.745,77.549l-3.464,2l4,6.93l3.464-2L67.745,77.549z M48.013,90.836h4v-8h-4V90.836z"/>
+</svg>
=== added file 'app/graphics/thunder.svg'
--- app/graphics/thunder.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/thunder.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M52.573,8.953c-9.938,0-18.38,6.125-22.41,14.871l-0.246,0.537l-0.59,0.043
+ c-9.186,0.669-16.453,8.609-16.453,18.269c0,10.094,7.905,18.387,17.679,18.387h22.02h20.524c7.343,0,13.276-6.233,13.276-13.785
+ c0-5.727-3.44-10.535-8.319-12.608l-0.58-0.246l-0.026-0.629C76.824,20.026,65.999,8.953,52.573,8.953 M52.649,12.935h0.012
+ c11.287,0,20.457,9.172,20.457,20.459v0.002v0.002c-0.005,1.421-0.3,2.814-0.599,4.201c0.024,0,0.049-0.006,0.072-0.006
+ c5.336,0,9.683,4.347,9.683,9.682s-4.347,9.682-9.683,9.682L31.19,56.936c-7.823,0-14.186-6.363-14.186-14.186
+ c0-7.822,6.363-14.185,14.186-14.185c0.543,0.003,1.079,0.089,1.617,0.154C34.971,19.535,43.134,12.94,52.649,12.935"/>
+<polygon fill="#818181" points="43.806,56.953 42.515,74.045 50.502,74.045 48.624,91.953 62.321,66.953 52.867,66.953
+ 54.472,56.953 "/>
+</svg>
=== added file 'app/graphics/windy-snow.svg'
--- app/graphics/windy-snow.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/windy-snow.svg 2015-03-14 20:11:20 +0000
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
+<path fill="#818181" d="M51.571,15.195c-0.906,0-1.818,0.151-2.74,0.459l-0.018,0.006l-0.02,0.007
+ c-0.888,0.258-1.664,0.676-2.365,1.267v0.002v0.002c-0.644,0.536-1.198,1.251-1.652,2.199v0.008v0.008
+ c-0.04,0.08-0.075,0.247-0.113,0.355l2.098,1.209c0.027-0.074,0.007-0.116,0.041-0.193l0.01-0.016l0.01-0.014
+ c0.312-0.624,0.756-1.17,1.326-1.572c0.501-0.393,1.048-0.687,1.635-0.85c0.592-0.193,1.179-0.293,1.746-0.293
+ c1.209,0,2.363,0.473,3.348,1.299c1.135,0.872,1.715,2.258,1.715,3.824c0,1.649-0.584,3.115-1.771,4.039
+ c-1.13,0.909-2.661,1.297-4.494,1.297H49.43h-5.028H41.47H25.175v3.912h23.486c3.466,0,6.062-0.713,7.869-2.031
+ c1.718-1.253,2.631-3.407,2.641-6.805v-0.025c0-1.669-0.282-2.966-0.782-3.924v-0.008v-0.008c-0.534-1.075-1.18-1.872-1.933-2.445
+ l-0.012-0.008l-0.012-0.01c-0.794-0.643-1.605-1.058-2.471-1.273l-0.025-0.006l-0.023-0.008c-0.908-0.276-1.685-0.404-2.317-0.404
+ H51.571z M81.45,26.9c-0.922,0-1.849,0.154-2.787,0.467l-0.018,0.006l-0.021,0.007c-0.903,0.263-1.695,0.688-2.408,1.291v0.001
+ v0.002c-0.654,0.546-1.218,1.272-1.68,2.238v0.009v0.007c-0.044,0.088-0.08,0.259-0.121,0.375l2.16,1.244
+ c0.03-0.082,0.006-0.129,0.045-0.215l0.01-0.016l0.012-0.014c0.314-0.63,0.763-1.183,1.34-1.589c0.509-0.399,1.062-0.695,1.657-0.86
+ c0.597-0.196,1.191-0.299,1.769-0.299c1.224,0,2.393,0.479,3.391,1.316c1.149,0.884,1.734,2.286,1.734,3.873
+ c0,1.672-0.591,3.153-1.793,4.088c-1.145,0.921-2.695,1.314-4.555,1.314h-0.906h-5.109h-2.977H18.825v0.004h-1
+ c-1.016,0.001-2.016,0.159-2.939,0.488v0.002v0.002c-0.942,0.327-1.824,0.867-2.525,1.567v0.002v0.002
+ c-0.729,0.717-1.272,1.624-1.627,2.606v0.002c-0.373,1.025-0.529,2.134-0.529,3.332c0,1.2,0.157,2.315,0.529,3.348l0.01,0.029
+ c0.357,0.967,0.894,1.863,1.607,2.578l0.012,0.012v0.004c0.699,0.684,1.567,1.215,2.496,1.551l0.01,0.002l0.041,0.016
+ c0.923,0.316,1.915,0.467,2.932,0.467h1v0.002h40.344h2.98h5.115h0.908c1.861,0,3.414,0.396,4.559,1.316
+ c1.203,0.934,1.797,2.418,1.797,4.092c0,1.588-0.587,2.99-1.736,3.875c-0.998,0.838-2.169,1.318-3.395,1.318
+ c-0.578,0-1.173-0.104-1.771-0.299c-0.595-0.166-1.149-0.463-1.658-0.861c-0.577-0.408-1.026-0.961-1.342-1.592l-0.01-0.016
+ l-0.01-0.016c-0.035-0.084-0.016-0.133-0.045-0.213l-2.166,1.248c0.04,0.115,0.078,0.289,0.121,0.375v0.008v0.008
+ c0.463,0.965,1.028,1.695,1.684,2.242v0.002v0.002c0.714,0.604,1.509,1.029,2.412,1.293l0.018,0.006l0.02,0.006
+ c0.939,0.312,1.867,0.467,2.791,0.467c0.645,0,1.438-0.131,2.361-0.412l0.021-0.008l0.025-0.006c0.883-0.221,1.71-0.643,2.518-1.297
+ l0.012-0.01l0.014-0.008c0.768-0.584,1.425-1.396,1.971-2.492v-0.008v-0.008c0.508-0.979,0.797-2.299,0.797-3.998
+ c0-3.477-0.94-5.678-2.697-6.959c-1.842-1.342-4.482-2.068-8.01-2.068H18.812v0.01h-1c-0.629,0-1.163-0.094-1.611-0.244
+ l-0.016-0.004l-0.014-0.006c-0.402-0.148-0.757-0.377-1.016-0.627l-0.01-0.01l-0.01-0.01c-0.242-0.248-0.49-0.62-0.67-1.104v-0.014
+ c-0.002-0.004-0.003-0.011-0.004-0.015l-0.053-0.14v-0.076c-0.139-0.466-0.234-1.025-0.234-1.758c0-0.723,0.092-1.27,0.227-1.725
+ v-0.088l0.059-0.164c0.185-0.511,0.439-0.89,0.676-1.121v-0.004l0.012-0.011c0.256-0.258,0.629-0.49,1.021-0.625l0.025-0.011v-0.002
+ c0.456-0.164,0.99-0.254,1.613-0.254h60.67c3.522,0,6.161-0.725,8-2.065c1.755-1.28,2.693-3.479,2.693-6.95
+ c0-1.696-0.288-3.017-0.795-3.992v-0.008v-0.007c-0.545-1.095-1.202-1.905-1.969-2.489l-0.012-0.008l-0.012-0.01
+ c-0.807-0.652-1.632-1.074-2.514-1.295l-0.025-0.006l-0.023-0.008c-0.923-0.281-1.717-0.412-2.359-0.412H81.45z"/>
+<polygon fill="#818181" points="51.683,69.762 48.683,70.361 48.683,75.754 43.746,72.902 42.244,75.5 47.183,78.352 42.244,81.201
+ 43.746,83.799 48.683,80.947 48.683,86.65 51.683,86.65 51.683,80.947 56.619,83.799 58.119,81.201 53.183,78.352 58.119,75.5
+ 56.619,72.902 51.683,75.754 "/>
+<polygon fill="#818181" points="30.938,63.518 28.558,63.994 28.558,68.199 24.702,65.975 23.513,68.037 27.368,70.262
+ 23.513,72.486 24.702,74.549 28.558,72.324 28.558,76.775 30.938,76.775 30.938,72.324 34.793,74.549 35.983,72.486 32.13,70.262
+ 35.983,68.037 34.793,65.975 30.938,68.199 "/>
+</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-03-14 20:11:20 +0000
@@ -0,0 +1,240 @@
+/*
+ * 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.3
+import Qt.labs.settings 1.0
+import Ubuntu.Components 1.1
+import "components"
+import "data" as Data
+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"
+
+ useDeprecatedToolbar: false
+ 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
+
+ /*
+ (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.
+ */
+ WorkerScript {
+ id: lookupWorker
+ source: Qt.resolvedUrl("./data/WeatherApi.js")
+ onMessage: {
+ 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
+ if(from_storage === true && force_refresh !== true) {
+ storage.getLocations(fillPages);
+ } else {
+ storage.getLocations(function(locations) {
+ lookupWorker.sendMessage({
+ action: "updateData",
+ params: {
+ locations: locations,
+ force: force_refresh,
+ service: settings.service,
+ api_key: Key.twcKey,
+ interval: settings.refreshInterval
+ }
+ })
+ });
+ }
+ }
+
+ 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 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 ? "kmh" : "mph"
+ }
+ }
+ }
+
+ Data.Storage {
+ id: storage
+
+ // 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
+ 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)) {
+ exists = true;
+ }
+ }
+
+ return exists;
+ }
+
+ function moveLocation(from, to) {
+ // 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) {
+ 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 >= i) { // 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]].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-03-14 20:11:20 +0000
@@ -0,0 +1,286 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 1.0 as ListItem
+import Ubuntu.Components.Popups 1.0
+import "../components"
+import "../data/CitiesList.js" as Cities
+import "../data/WeatherApi.js" as WeatherApi
+
+Page {
+ id: 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
+ 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)
+ }
+ }
+ }
+ }
+
+ function appendCities(list) {
+ list.forEach(function(loc) {
+ 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()
+
+ lookupWorker.sendMessage({
+ action: "searchByName",
+ params: {
+ name: search,
+ units: "metric"
+ }
+ });
+ }
+
+
+ WorkerScript {
+ id: lookupWorker
+ source: Qt.resolvedUrl("../data/WeatherApi.js")
+ onMessage: {
+ if (!messageObject.error) {
+ appendCities(messageObject.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.Header {
+ text: section
+ }
+
+ model: ListModel {
+ id: citiesModel
+
+ property bool loading: true
+ property bool httpError: false
+
+ onRowsAboutToBeInserted: loading = false
+ }
+
+ delegate: ListItem.Empty {
+ showDivider: 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: countryName
+ }
+ }
+
+ 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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,148 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import "../components"
+
+
+PageWithBottomEdge {
+ // Set to null otherwise the header is shown (but blank) over the top of the listview
+ id: locationPage
+ flickable: null
+
+ bottomEdgePageSource: Qt.resolvedUrl("LocationsPage.qml")
+ bottomEdgeTitle: i18n.tr("Locations")
+ tipColor: UbuntuColors.orange
+ tipLabelColor: "#FFF"
+
+ property var iconMap: {
+ "sun": Qt.resolvedUrl("../graphics/sunny.svg"),
+ "moon": Qt.resolvedUrl("../graphics/starry.svg"),
+ "cloud_sun": Qt.resolvedUrl("../graphics/partly-cloudy.svg"),
+ "cloud_moon": Qt.resolvedUrl("../graphics/cloudy-night.svg"),
+ "cloud": Qt.resolvedUrl("../graphics/cloudy.svg"),
+ "rain": Qt.resolvedUrl("../graphics/rain.svg"),
+ "thunder": Qt.resolvedUrl("../graphics/thunder.svg"),
+ "snow_shower": Qt.resolvedUrl("../graphics/snow.svg"),
+ "fog": Qt.resolvedUrl("../graphics/fog.svg"),
+ "snow_rain": Qt.resolvedUrl("../graphics/snow.svg"),
+ "scattered": Qt.resolvedUrl("../graphics/rain.svg"),
+ "overcast": Qt.resolvedUrl("../graphics/cloudy.svg")
+ }
+
+ 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))
+ }
+
+ /*
+ 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
+ contentWidth: parent.width
+
+ PullToRefresh {
+ id: pullToRefresh
+ parent: locationFlickable
+ refreshing: false
+ onRefresh: {
+ refreshing = true
+ refreshData(false, true)
+ }
+ }
+
+ /*
+ ListView for locations with snap-scrolling horizontally.
+ */
+ ListView {
+ id: locationPages
+ anchors.fill: parent
+ width: parent.width
+ height: childrenRect.height
+ contentWidth: parent.width
+ contentHeight: childrenRect.height
+ model: weatherApp.locationsList.length
+ // TODO with snapMode, currentIndex is not properly set and setting currentIndex fails
+ //snapMode: ListView.SnapOneItem
+ orientation: ListView.Horizontal
+ currentIndex: settings.current
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ onCurrentIndexChanged: {
+ if (loaded) {
+ // FIXME: when a model is reloaded this causes the currentIndex to be lost
+ settings.current = currentIndex
+ }
+ }
+ onModelChanged: {
+ currentIndex = settings.current
+
+ if (model > 0) {
+ pullToRefresh.refreshing = false
+ loaded = true
+ }
+ }
+ delegate: LocationPane {}
+
+ property bool loaded: false
+ // 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
+ }
+ }
+ }
+ }
+}
=== added file 'app/ui/LocationPane.qml'
--- app/ui/LocationPane.qml 1970-01-01 00:00:00 +0000
+++ app/ui/LocationPane.qml 2015-03-14 20:11:20 +0000
@@ -0,0 +1,183 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import "../components"
+
+Rectangle {
+ 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
+
+ /*
+ Calculates the height of all location data components, to set the Flickable.contentHeight right.
+ */
+ function setFlickableContentHeight() {
+ var contentHeightGu = (homeTempInfo.height+homeGraphic.height
+ +(weekdayColumn.height*mainPageWeekdayListView.model.count))/units.gridUnit;
+ locationFlickable.contentHeight = units.gu(contentHeightGu+25);
+ }
+
+ /*
+ 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.main) ? current.condition.main : current.condition; // difference TWC/OWM
+ 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] : ""
+ }
+ mainPageWeekdayListView.model.append(dayData);
+ }
+ }
+ setFlickableContentHeight();
+
+ // set data for hourly forecasts
+ if(hourlyForecasts.length > 0) {
+ homeHourlyLoader.forecasts = hourlyForecasts;
+ homeHourlyLoader.tempUnits = tempUnits;
+ }
+ }
+
+ Column {
+ id: locationTop
+ anchors {
+ 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
+ }
+
+ ListItem.ThinDivider {}
+ }
+ Column {
+ id: weekdayColumn
+ width: parent.width
+ height: childrenRect.height
+ anchors {
+ top: locationTop.bottom
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+ Repeater {
+ id: mainPageWeekdayListView
+ model: ListModel{}
+ DayDelegate {
+ day: model.day
+ high: model.high
+ image: model.image
+ low: model.low
+ }
+ }
+ }
+}
=== added file 'app/ui/LocationsPage.qml'
--- app/ui/LocationsPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/LocationsPage.qml 2015-03-14 20:11:20 +0000
@@ -0,0 +1,152 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+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())
+ }
+ ]
+
+ MultiSelectListView {
+ id: locationsListView
+ anchors {
+ fill: parent
+ }
+ model: ListModel {
+ id: locationsModel
+ }
+ delegate: WeatherListItem {
+ id: locationsListItem
+ leftSideAction: Remove {
+ onTriggered: storage.removeLocation(index)
+ }
+ multiselectable: true
+ reorderable: true
+
+ onItemClicked: {
+ settings.current = index;
+ pageStack.pop()
+ }
+ onReorder: {
+ console.debug("Move: ", from, to);
+
+ storage.moveLocation(from, to);
+ }
+
+ 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)
+
+ Label {
+ elide: Text.ElideRight
+ height: locationsListItem.height
+ text: model.location.name
+ verticalAlignment: Text.AlignVCenter
+ width: weatherImage.visible ? (locationsListItem.width / 2) - units.gu(4)
+ : locationsListItem.width - units.gu(16)
+ }
+
+ Image {
+ id: weatherImage
+ anchors {
+ centerIn: parent
+ }
+ height: units.gu(3)
+ source: locationPage.iconMap[locationPages.contentItem.children[index].iconName] || ""
+ visible: locationsPage.state === "default"
+ width: units.gu(3)
+ }
+
+ Label {
+ id: nowLabel
+ anchors {
+ right: parent.right
+ }
+ color: UbuntuColors.orange
+ font.pixelSize: units.gu(4)
+ font.weight: Font.Light
+ height: units.gu(6)
+ text: locationPages.contentItem.children[index].currentTemp ? locationPages.contentItem.children[index].currentTemp + settings.tempScale[1]
+ : ""
+ verticalAlignment: Text.AlignVCenter
+ visible: locationsPage.state === "default"
+ }
+ }
+
+ ListItem.ThinDivider {
+ anchors {
+ bottom: parent.bottom
+ }
+ }
+ }
+ }
+
+ function populateLocationsModel() {
+ locationsModel.clear()
+
+ for (var i=0; i < weatherApp.locationsList.length; i++) {
+ locationsModel.append(weatherApp.locationsList[i])
+ }
+ }
+
+ 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-03-14 20:11:20 +0000
@@ -0,0 +1,61 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+Page {
+ title: i18n.tr("Settings")
+
+ Flickable {
+ anchors {
+ fill: parent
+ }
+ height: parent.height
+ contentHeight: settingsColumn.childrenRect.height
+
+ Column {
+ id: settingsColumn
+ anchors {
+ fill: parent
+ }
+
+ ListItem.SingleValue {
+ progression: true
+ text: i18n.tr("Units")
+
+ onClicked: mainPageStack.push(Qt.resolvedUrl("settings/UnitsPage.qml"))
+ }
+
+ ListItem.SingleValue {
+ progression: true
+ text: i18n.tr("Data Provider")
+
+ onClicked: mainPageStack.push(Qt.resolvedUrl("settings/DataProviderPage.qml"))
+ }
+
+ ListItem.SingleValue {
+ progression: true
+ text: 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-03-14 20:11:20 +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-03-14 20:11:20 +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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+
+Page {
+ title: i18n.tr("Data Provider")
+
+ Flickable {
+ anchors {
+ fill: parent
+ }
+ height: parent.height
+ contentHeight: unitsColumn.childrenRect.height
+
+ Column {
+ id: unitsColumn
+ anchors {
+ fill: parent
+ }
+
+ ListItem.ItemSelector {
+ expanded: true
+ model: ["openweathermap", "weatherchannel"] // "geonames", "geoip"]
+ selectedIndex: model.indexOf(settings.service)
+ text: i18n.tr("Provider")
+
+ onDelegateClicked: {
+ settings.service = model[index]
+ 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-03-14 20:11:20 +0000
@@ -0,0 +1,56 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+
+Page {
+ title: i18n.tr("Refresh Interval")
+
+ Flickable {
+ anchors {
+ fill: parent
+ }
+ height: parent.height
+ contentHeight: unitsColumn.childrenRect.height
+
+ Column {
+ id: unitsColumn
+ anchors {
+ fill: parent
+ }
+
+ ListItem.ItemSelector {
+ delegate: OptionSelectorDelegate {
+ text: Math.floor(modelData / 60).toString() + " " + i18n.tr("minutes")
+ }
+ expanded: true
+ model: [600, 900, 1800, 3600]
+ selectedIndex: model.indexOf(settings.refreshInterval)
+ text: i18n.tr("Interval")
+
+ onDelegateClicked: {
+ settings.refreshInterval = model[index]
+ 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-03-14 20:11:20 +0000
@@ -0,0 +1,77 @@
+/*
+ * 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.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 0.1 as ListItem
+
+
+Page {
+ title: i18n.tr("Units")
+
+ Flickable {
+ anchors {
+ fill: parent
+ }
+ height: parent.height
+ contentHeight: unitsColumn.childrenRect.height
+
+ Column {
+ id: unitsColumn
+ anchors {
+ fill: parent
+ }
+
+ ListItem.ItemSelector {
+ expanded: true
+ model: ["°C", "°F"]
+ selectedIndex: model.indexOf(settings.tempScale)
+ text: i18n.tr("Temperature")
+
+ onDelegateClicked: {
+ settings.tempScale = model[index]
+ refreshData(true)
+ }
+ }
+
+ ListItem.ItemSelector {
+ expanded: true
+ model: ["mm", "in"]
+ selectedIndex: model.indexOf(settings.precipUnits)
+ text: i18n.tr("Precipitation")
+
+ onDelegateClicked: {
+ settings.precipUnits = model[index]
+ refreshData(true)
+ }
+ }
+
+ ListItem.ItemSelector {
+ expanded: true
+ model: ["kmh", "mph"]
+ text: i18n.tr("Wind Speed")
+ selectedIndex: model.indexOf(settings.windUnits)
+
+ onDelegateClicked: {
+ settings.windUnits = model[index]
+ 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-03-14 20:11:20 +0000 differ
=== added directory 'backend'
=== added file 'backend/CMakeLists.txt'
--- backend/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ backend/CMakeLists.txt 2015-03-14 20:11:20 +0000
@@ -0,0 +1,29 @@
+project(backend)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+#set(
+# timeZone_SRCS
+# TimeZone/timezone.cpp
+# TimeZone/timezone_plugin.cpp
+#)
+
+#add_library(TimeZoneBackend MODULE
+# ${timeZone_SRCS}
+#)
+#set_target_properties(TimeZoneBackend PROPERTIES
+# LIBRARY_OUTPUT_DIRECTORY TimeZone)
+
+#qt5_use_modules(TimeZoneBackend Gui Qml Quick)
+
+# Copy qmldir file to build dir for running in QtCreator
+#add_custom_target(TimeZoneBackend-qmldir ALL
+# COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/TimeZone/qmldir ${CMAKE_CURRENT_BINARY_DIR}/TimeZone
+# DEPENDS ${QML_JS_FILES}
+#)
+
+# Install plugin file
+#install(TARGETS TimeZoneBackend DESTINATION ${QT_IMPORTS_DIR}/qt5/qml/UbuntuWeather/TimeZone/)
+#install(FILES TimeZone/qmldir DESTINATION ${QT_IMPORTS_DIR}/qt5/qml/UbuntuWeather/TimeZone/)
=== added directory 'debian'
=== renamed directory 'debian' => 'debian.moved'
=== added file 'debian/changelog'
--- debian/changelog 1970-01-01 00:00:00 +0000
+++ debian/changelog 2015-03-14 20:11:20 +0000
@@ -0,0 +1,173 @@
+ubuntu-weather-app (2.1) UNRELEASED; urgency=medium
+
+ *
+
+ -- 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-03-14 20:11:20 +0000
@@ -0,0 +1,1 @@
+9
=== added file 'debian/control'
--- debian/control 1970-01-01 00:00:00 +0000
+++ debian/control 2015-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,23 @@
+{
+ "architecture": "@CLICK_ARCH@",
+ "description": "A weather forecast application for Ubuntu with support for multiple online weather data sources",
+ "framework": "ubuntu-sdk-14.10",
+ "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-03-14 20:11:20 +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-03-14 20:11:20 +0000
@@ -0,0 +1,128 @@
+# 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-03-14 11:54+0100\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=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../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/ui/AddLocationPage.qml:30
+msgid "Select a city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:47
+msgid "City"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:62
+msgid "Back"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:89
+msgid "Search city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:254
+msgid "No city found"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:267
+msgid "Couldn't load weather data, please try later again!"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:277
+msgid "Location already added."
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:280
+msgid "OK"
+msgstr ""
+
+#: ../app/ui/HomePage.qml:30 ../app/ui/LocationsPage.qml:30
+msgid "Locations"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:24
+msgid "Settings"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:41 ../app/ui/settings/UnitsPage.qml:25
+msgid "Units"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:48 ../app/ui/settings/DataProviderPage.qml:25
+msgid "Data Provider"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:55 ../app/ui/settings/RefreshIntervalPage.qml:25
+msgid "Refresh Interval"
+msgstr ""
+
+#: ../app/ui/settings/DataProviderPage.qml:44
+msgid "Provider"
+msgstr ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:42
+msgid "minutes"
+msgstr ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:47
+msgid "Interval"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:44
+msgid "Temperature"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:56
+msgid "Precipitation"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:67
+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-03-14 20:11:20 +0000
@@ -0,0 +1,3 @@
+if(NOT CLICK_MODE)
+ add_subdirectory(autopilot)
+endif(NOT CLICK_MODE)
=== 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-03-14 20:11:20 +0000
@@ -0,0 +1,8 @@
+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)
=== added directory 'tests/autopilot/ubuntu_weather_app'
=== added file 'ubuntu-weather-app.apparmor'
--- ubuntu-weather-app.apparmor 1970-01-01 00:00:00 +0000
+++ ubuntu-weather-app.apparmor 2015-03-14 20:11:20 +0000
@@ -0,0 +1,6 @@
+{
+ "policy_groups": [
+ "networking"
+ ],
+ "policy_version": 1.2
+}
=== 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-03-14 20:11:20 +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