ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #03195
[Merge] lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app
Victor Thompson has proposed merging lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app.
Commit message:
Allow adding the current location as a duplicate to the Locations List.
Requested reviews:
Ubuntu Weather Developers (ubuntu-weather-dev)
For more details, see:
https://code.launchpad.net/~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location/+merge/263208
Allow adding the current location as a duplicate to the Locations List.
--
Your team Ubuntu Weather Developers is requested to review the proposed merge of lp:~vthompson/ubuntu-weather-app/reboot-allow-adding-duplicate-current-location into lp:ubuntu-weather-app.
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2015-06-28 23:49:12 +0000
@@ -0,0 +1,19 @@
+*.user*
+debian/files
+debian/tmp
+debian/ubuntu-weather-app*
+debian/app-template/
+debian/*.debhelper.log
+debian/*.substvars
+.build
+Makefile
+CMakeCache.txt
+CMakeFiles/
+*.cmake
+*.gmo
+*.mo
+*.desktop
+*.desktop.in
+*.desktop.in.in.h
+.excludes
+ubuntu-weather-app.json
=== renamed file '.bzrignore' => '.bzrignore.moved'
=== added file 'CMakeLists.txt'
--- CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,101 @@
+project(com.ubuntu.weather)
+cmake_minimum_required(VERSION 2.8.9)
+
+find_program(INTLTOOL_MERGE intltool-merge)
+if(NOT INTLTOOL_MERGE)
+ message(FATAL_ERROR "Could not find intltool-merge, please install the intltool package")
+endif()
+find_program(INTLTOOL_EXTRACT intltool-extract)
+if(NOT INTLTOOL_EXTRACT)
+ message(FATAL_ERROR "Could not find intltool-extract, please install the intltool package")
+endif()
+
+set (UBUNTU_MANIFEST_PATH "manifest.json.in" CACHE INTERNAL "Relative path to the manifest file")
+set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-permissive -pedantic -Wall -Wextra")
+
+find_package(Qt5Core REQUIRED)
+find_package(Qt5Qml REQUIRED)
+find_package(Qt5Quick REQUIRED)
+
+# Automatically create moc files
+set(CMAKE_AUTOMOC ON)
+
+option(INSTALL_TESTS "Install the tests on make install" on)
+option(CLICK_MODE "Build as a click package" on)
+
+# Tests
+enable_testing()
+
+# Standard install paths
+include(GNUInstallDirs)
+
+set(APP_NAME weather)
+set(APP_HARDCODE ubuntu-weather-app)
+set(MAIN_QML ${APP_HARDCODE}.qml)
+set(DESKTOP_FILE "${APP_HARDCODE}.desktop")
+set(ICON weather-app@xxxxxx)
+set(AUTOPILOT_DIR ubuntu_weather_app)
+
+# Set install paths
+if(CLICK_MODE)
+ set(CMAKE_INSTALL_PREFIX "/")
+ set(UBUNTU-WEATHER_APP_DIR "${CMAKE_INSTALL_DATADIR}/qml")
+
+ set(QT_IMPORTS_DIR "${CMAKE_INSTALL_LIBDIR}")
+ set(EXEC "qmlscene $@ ${UBUNTU-WEATHER_APP_DIR}/${MAIN_QML}")
+ set(MODULE_PATH ${QT_IMPORTS_DIR})
+ if(NOT BZR_REVNO)
+ execute_process(
+ COMMAND bzr revno
+ OUTPUT_VARIABLE BZR_REVNO
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ endif(NOT BZR_REVNO)
+ if(NOT BZR_SOURCE)
+ set(BZR_SOURCE "lp:${APP_HARDCODE}/reboot")
+ message("-- Setting BZR_SOURCE to ${BZR_SOURCE}")
+ endif(NOT BZR_SOURCE)
+else(CLICK_MODE)
+ set(UBUNTU-WEATHER_APP_DIR "${CMAKE_INSTALL_DATADIR}/ubuntu-weather-app")
+ execute_process(
+ COMMAND qmake -query QT_INSTALL_QML
+ OUTPUT_VARIABLE QT_IMPORTS_DIR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ set(MODULE_PATH ${QT_IMPORTS_DIR}/WeatherApp)
+endif(CLICK_MODE)
+
+if(${CLICK_MODE})
+ message("-- Configuring manifest.json")
+
+ configure_file(${UBUNTU_MANIFEST_PATH} ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX})
+ install(FILES "${APP_HARDCODE}.apparmor" DESTINATION ${CMAKE_INSTALL_PREFIX})
+else(CLICK_MODE)
+ set(EXEC "qmlscene $@ -I ${MODULE_PATH} ${CMAKE_INSTALL_PREFIX}/${UBUNTU-WEATHER_APP_DIR}/${MAIN_QML}")
+endif()
+
+
+file(GLOB_RECURSE I18N_SRC_FILES
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po
+ *.qml *.js)
+list(APPEND I18N_SRC_FILES ${DESKTOP_FILE}.in.in.h)
+list(SORT I18N_SRC_FILES)
+
+configure_file(${DESKTOP_FILE}.in.in ${DESKTOP_FILE}.in)
+
+add_custom_target(${DESKTOP_FILE} ALL
+ COMMENT "Merging translations into ${DESKTOP_FILE}..."
+ COMMAND LC_ALL=C ${INTLTOOL_MERGE} -d -u ${CMAKE_SOURCE_DIR}/po ${DESKTOP_FILE}.in ${DESKTOP_FILE}
+)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
+
+add_subdirectory(app)
+add_subdirectory(po)
+add_subdirectory(tests)
+
+# TODO: Add custom target for autopilot and run.
+
=== renamed file 'CMakeLists.txt' => 'CMakeLists.txt.moved'
=== added file 'COPYING'
--- COPYING 1970-01-01 00:00:00 +0000
+++ COPYING 2015-06-28 23:49:12 +0000
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
=== renamed file 'COPYING' => 'COPYING.moved'
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2015-06-28 23:49:12 +0000
@@ -0,0 +1,11 @@
+Weather app for Ubuntu devices
+
+To contribute:
+ https://wiki.ubuntu.com/Touch/CoreApps/Weather
+ https://wiki.ubuntu.com/Touch/CoreApps/DevelopmentGuide
+
+The following essential packages are required to develop this app:
+- ubuntu-sdk (see http://developer.ubuntu.com/start)
+- intltool - run the `sudo apt-get install intltool` command for installation
+
+See the debian/control file for an up-to-date list of dependencies
=== renamed file 'README' => 'README.moved'
=== added file 'README.translations'
--- README.translations 1970-01-01 00:00:00 +0000
+++ README.translations 2015-06-28 23:49:12 +0000
@@ -0,0 +1,37 @@
+# Updating translations
+
+Translations for the Weather app happen in [Launchpad Translations][] and
+are automatically committed daily on the trunk branch in the po/ folder.
+
+They are then built and installed as part of the package build, so that
+developers don't really need to worry about them.
+
+However, there is one task that needs to be taken care of: exposing new
+translatable messages to translators. So whenever you add new translatable
+messages in the code, make sure to follow these steps:
+
+ 1. Run click-buddy to build the project and generate a new .pot file:
+ `click-buddy --dir .`
+ 2. Commit the generated .pot file:
+ `bzr commit -m"Updated translation template"`
+ 3. Push the branch and send a merge proposal as usual
+
+And that's it, once the branch lands Launchpad should take care of all the rest!
+
+# Behind the scenes
+
+Behind the scenes, whenever the po/*.pot file (also known as translations template)
+is committed to trunk Launchpad reads it and updates the translatable strings
+exposed in the web UI. This will enable translators to work on the new strings.
+The translations template contains all translatable strings that have been
+extracted from the source code files.
+
+Launchpad will then store translations in its database and will commit them daily
+in the form of textual po/*.po files to trunk. The PO files are also usually
+referred to as the translations files. You'll find a translation file for each
+language the app has got at least a translated message available for.
+
+Translations for core apps follow the standard [gettext format].
+
+ [Launchpad Translations]: https://translations.launchpad.net/ubuntu-weather-app
+ [gettext format]: https://www.gnu.org/software/gettext/
=== renamed file 'README.translations' => 'README.translations.moved'
=== added directory 'app'
=== added file 'app/CMakeLists.txt'
--- app/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,18 @@
+if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+ file(GLOB QML_JS_FILES *.qml *.js)
+ add_custom_target(ubuntu-weather-app_QMlFiles ALL SOURCES ${QML_JS_FILES})
+endif(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+
+
+if(CLICK_MODE)
+ set(ICON ${ICON})
+ install(FILES ${ICON} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif(CLICK_MODE)
+
+install(FILES ${MAIN_QML} DESTINATION ${UBUNTU-WEATHER_APP_DIR})
+
+add_subdirectory(components)
+add_subdirectory(data)
+add_subdirectory(graphics)
+add_subdirectory(ui)
+
=== added directory 'app/components'
=== added file 'app/components/CMakeLists.txt'
--- app/components/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/components/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,7 @@
+add_subdirectory(ListItemActions)
+
+file(GLOB COMPONENTS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_components_QMlFiles ALL SOURCES ${COMPONENTS_QML_JS_FILES})
+
+install(FILES ${COMPONENTS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/components)
=== added file 'app/components/CurrentLocation.qml'
--- app/components/CurrentLocation.qml 1970-01-01 00:00:00 +0000
+++ app/components/CurrentLocation.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtLocation 5.3
+import QtPositioning 5.2
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../data/WeatherApi.js" as WeatherApi
+
+
+Item {
+ id: currentLocation
+
+ property string string: "Undefined"
+
+ function searchForLocation(lat, lon) {
+ WeatherApi.sendRequest({
+ action: "searchByPoint",
+ params: {
+ coords: {
+ lat: lat,
+ lon: lon
+ }
+ }
+ }, searchResponseHandler)
+ }
+
+ function searchResponseHandler(msgObject) {
+ if (!msgObject.error) {
+ console.log("Loc to add:", JSON.stringify(msgObject.result.locations[0]))
+ storage.updateCurrentLocation(msgObject.result.locations[0])
+ }
+ }
+
+
+ PositionSource {
+ id: currentPosition
+ updateInterval: 1000
+ active: true
+
+ onPositionChanged: {
+ var coord = currentPosition.position.coordinate
+ if (coord.isValid) {
+ geocodeModel.query = coord
+ geocodeModel.update()
+ }
+ }
+ }
+
+ Plugin {
+ id: osmPlugin
+ name: "osm"
+ }
+
+ GeocodeModel {
+ id: geocodeModel
+ autoUpdate: false
+ plugin: osmPlugin
+
+ onCountChanged: {
+ // Update the currentLocation if one is found and it does not match the stored location
+ if (count > 0 && currentLocation.string !== geocodeModel.get(0).address.city) {
+ var loc = geocodeModel.get(0)
+ currentLocation.string = loc.address.city
+ searchForLocation(loc.coordinate.latitude, loc.coordinate.longitude)
+ }
+ }
+ }
+}
=== added file 'app/components/DayDelegate.qml'
--- app/components/DayDelegate.qml 1970-01-01 00:00:00 +0000
+++ app/components/DayDelegate.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListItem {
+ id: dayDelegate
+ height: collapsedHeight
+
+ property int collapsedHeight: units.gu(8)
+ property int expandedHeight: collapsedHeight + units.gu(4) + extraInfoColumn.height
+
+ property alias day: dayLabel.text
+ property alias image: weatherImage.name
+ property alias high: highLabel.text
+ property alias low: lowLabel.text
+
+ property alias chanceOfRain: chanceOfRainForecast.chance
+ property alias humidity: humidityForecast.value
+ property alias pollen: pollenForecast.value
+ property alias sunrise: sunriseForecast.value
+ property alias sunset: sunsetForecast.value
+ property alias wind: windForecast.value
+ property alias uvIndex: uvIndexForecast.value
+
+ state: "normal"
+ states: [
+ State {
+ name: "normal"
+ PropertyChanges {
+ target: dayDelegate
+ height: collapsedHeight
+ }
+ },
+ State {
+ name: "expanded"
+ PropertyChanges {
+ target: dayDelegate
+ height: expandedHeight
+ }
+ PropertyChanges {
+ target: expandedInfo
+ opacity: 1
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ from: "normal"
+ to: "expanded"
+ SequentialAnimation {
+ NumberAnimation {
+ easing.type: Easing.InOutQuad
+ properties: "height"
+ }
+ NumberAnimation {
+ easing.type: Easing.InOutQuad
+ properties: "opacity"
+ }
+ }
+ },
+ Transition {
+ from: "expanded"
+ to: "normal"
+ SequentialAnimation {
+ NumberAnimation {
+ easing.type: Easing.InOutQuad
+ properties: "opacity"
+ }
+ NumberAnimation {
+ easing.type: Easing.InOutQuad
+ properties: "height"
+ }
+ }
+ }
+ ]
+
+ onClicked: {
+ state = state === "normal" ? "expanded" : "normal"
+ locationPages.collapseOtherDelegates(index)
+ }
+
+ Item {
+ id: mainInfo
+
+ height: collapsedHeight
+ anchors {
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+
+ Label {
+ id: dayLabel
+ anchors {
+ left: parent.left
+ right: weatherImage.left
+ rightMargin: units.gu(1)
+ top: parent.top
+ topMargin: (collapsedHeight - dayLabel.height) / 2
+ }
+ elide: Text.ElideRight
+ font.weight: Font.Light
+ fontSize: "medium"
+ }
+
+ Icon {
+ id: weatherImage
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: dayLabel.verticalCenter
+ }
+ height: units.gu(3)
+ width: units.gu(3)
+ }
+
+ Label {
+ id: lowLabel
+ anchors {
+ left: weatherImage.right
+ right: highLabel.left
+ rightMargin: units.gu(1)
+ verticalCenter: dayLabel.verticalCenter
+ }
+ elide: Text.ElideRight
+ font.pixelSize: units.gu(2)
+ font.weight: Font.Light
+ fontSize: "medium"
+ height: units.gu(2)
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignTop // AlignTop appears to align bottom?
+ }
+
+ Label {
+ id: highLabel
+ anchors {
+ bottom: lowLabel.bottom
+ right: parent.right
+ }
+ color: UbuntuColors.orange
+ elide: Text.ElideRight
+ font.pixelSize: units.gu(3)
+ font.weight: Font.Normal
+ height: units.gu(3)
+ verticalAlignment: Text.AlignTop // AlignTop appears to align bottom?
+ }
+ }
+
+ Item {
+ id: expandedInfo
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ top: mainInfo.bottom
+ bottomMargin: units.gu(2)
+ }
+ opacity: 0
+ visible: opacity !== 0
+
+ Column {
+ id: extraInfoColumn
+ anchors {
+ centerIn: parent
+ }
+ spacing: units.gu(2)
+
+ // FIXME: extended-infomation_* aren't actually on device
+
+ // Overview text
+ Label {
+ id: chanceOfRainForecast
+ color: UbuntuColors.coolGrey
+ fontSize: "x-large"
+ horizontalAlignment: Text.AlignHCenter
+ text: i18n.tr("Chance of rain")
+ width: parent.width
+ visible: false // FIXME: add overview text eg "Chance of flurries"
+
+ property int chance: 0
+ }
+
+ ForecastDetailsDelegate {
+ id: windForecast
+ forecast: i18n.tr("Winds")
+ imageSource: "../graphics/extended-information_wind.svg"
+ }
+
+ ForecastDetailsDelegate {
+ id: uvIndexForecast
+ imageSource: "../graphics/extended-information_uv-level.svg"
+ forecast: i18n.tr("UV Index")
+ }
+
+ ForecastDetailsDelegate {
+ id: pollenForecast
+ forecast: i18n.tr("Pollen")
+ // FIXME: need icon
+ }
+
+ ForecastDetailsDelegate {
+ id: humidityForecast
+ forecast: i18n.tr("Humidity")
+ imageSource: "../graphics/extended-information_humidity.svg"
+ }
+
+ ForecastDetailsDelegate {
+ id: sunriseForecast
+ forecast: i18n.tr("Sunrise")
+ // FIXME: need icon
+ }
+
+ ForecastDetailsDelegate {
+ id: sunsetForecast
+ forecast: i18n.tr("Sunset")
+ // FIXME: need icon
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ locationPages.collapseOtherDelegates.connect(function(otherIndex) {
+ if (dayDelegate && typeof index !== "undefined" && otherIndex !== index) {
+ state = "normal"
+ }
+ });
+ }
+}
=== added file 'app/components/ExpandableListItem.qml'
--- app/components/ExpandableListItem.qml 1970-01-01 00:00:00 +0000
+++ app/components/ExpandableListItem.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 1.0 as ListItem
+
+/*
+ Component which extends the SDK Expandable list item and provides a easy
+ to use component where the title, subtitle and listview can be displayed. It
+ matches the design specification provided for clock.
+*/
+
+ListItem.Expandable {
+ id: expandableListItem
+
+ property ListModel model
+ property Component delegate
+ property alias text: expandableHeader.text
+ property alias subText: expandableHeader.subText
+ property alias listViewHeight: expandableList.height
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ margins: units.gu(-2)
+ }
+
+ collapseOnClick: true
+ expandedHeight: contentColumn.height + units.gu(1)
+
+ Column {
+ id: contentColumn
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+
+ Item {
+ width: parent.width
+ height: expandableListItem.collapsedHeight
+
+ ListItem.Subtitled {
+ id: expandableHeader
+ onClicked: expandableListItem.expanded = true
+
+ Icon {
+ id: arrow
+
+ width: units.gu(2)
+ height: width
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+
+ name: "go-down"
+ color: "Grey"
+ rotation: expandableListItem.expanded ? 180 : 0
+
+ Behavior on rotation {
+ UbuntuNumberAnimation {}
+ }
+ }
+ }
+ }
+
+ ListView {
+ id: expandableList
+ width: parent.width
+ interactive: false
+ model: expandableListItem.model
+ delegate: expandableListItem.delegate
+ }
+ }
+}
=== added file 'app/components/FastScroll.js'
--- app/components/FastScroll.js 1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.js 2015-06-28 23:49:12 +0000
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.js - this is just SectionScroller.js with a fix for
+// section.criteria == ViewSection.FirstCharacter
+var sectionData = [];
+var _sections = [];
+
+function initialize(list) {
+ initSectionData(list);
+}
+
+function contains(name) {
+ return (_sections.indexOf(name) > -1)
+}
+
+function initSectionData(list) {
+ if (!list || !list.model) return;
+ sectionData = [];
+ _sections = [];
+ var current = "",
+ prop = list.section.property,
+ sectionText;
+
+ if (list.section.criteria === ViewSection.FullString) {
+ for (var i = 0, count = list.model.count; i < count; i++) {
+ sectionText = list.getSectionText(i)
+ if (sectionText !== current) {
+ current = sectionText;
+ _sections.push(current);
+ sectionData.push({ index: i, header: current });
+ }
+ }
+ } else if (list.section.criteria === ViewSection.FirstCharacter) {
+ for (var i = 0, count = list.model.count; i < count; i++) {
+ sectionText = list.getSectionText(i).substring(0, 1)
+ if (sectionText !== current) {
+ current = sectionText
+ _sections.push(sectionText);
+ sectionData.push({ index: i, header: current });
+ }
+ }
+ }
+}
+
+function getSectionPositionString(name) {
+ var val = _sections.indexOf(name);
+ return val === 0 ? "first" :
+ val === _sections.length - 1 ? "last" : false;
+}
+
+function getAt(pos) {
+ return _sections[pos] ? _sections[pos] : "";
+}
+
+function getRelativeSections(current) {
+ var val = _sections.indexOf(current),
+ sect = [],
+ sl = _sections.length;
+
+ val = val < 1 ? 1 : val >= sl-1 ? sl-2 : val;
+ sect = [getAt(val - 1), getAt(val), getAt(val + 1)];
+
+ return sect;
+}
+
+function getClosestSection(pos, down) {
+ var tmp = (_sections.length) * pos;
+ var val = Math.ceil(tmp) // TODO: better algorithm
+ val = val < 2 ? 1 : val;
+ return _sections[val-1];
+}
+
+function getNextSection(current) {
+ var val = _sections.indexOf(current);
+ return (val > -1 ? _sections[(val < _sections.length - 1 ? val + 1 : val)] : _sections[0]) || "";
+}
+
+function getPreviousSection(current) {
+ var val = _sections.indexOf(current);
+ return (val > -1 ? _sections[(val > 0 ? val - 1 : val)] : _sections[0]) || "";
+}
+
+function getIndexFor(sectionName) {
+ var data = sectionData[_sections.indexOf(sectionName)]
+ if (data) {
+ var val = data.index;
+ return val === 0 || val > 0 ? val : -1;
+ } else {
+ return -1
+ }
+}
=== added file 'app/components/FastScroll.qml'
--- app/components/FastScroll.qml 1970-01-01 00:00:00 +0000
+++ app/components/FastScroll.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,321 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2014, 2015 Canonical Ltda
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@xxxxxxxxx)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// FastScroll.qml
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "FastScroll.js" as Sections
+
+Item {
+ id: root
+
+ property ListView listView
+ property int pinSize: units.gu(2)
+
+ readonly property var letters: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
+ readonly property alias fastScrolling: internal.fastScrolling
+ readonly property bool showing: (rail.opacity !== 0.0)
+ readonly property double minimumHeight: rail.height
+
+ width: units.gu(7)
+ height: rail.height
+
+ onListViewChanged: {
+ if (listView && listView.model) {
+ internal.initDirtyObserver();
+ } else if (listView) {
+ listView.modelChanged.connect(function() {
+ if (listView.model) {
+ internal.initDirtyObserver();
+ }
+ });
+ }
+ }
+
+ Connections {
+ target: listView
+ onCurrentIndexChanged: {
+ if (currentIndex != -1) {
+ rail.opacity = 0.0
+ }
+ }
+ }
+
+ Rectangle {
+ id: magnified
+
+ color: Theme.palette.normal.overlay
+ radius: height * 0.3
+ height: pinSize * 2
+ width: height
+ opacity: internal.fastScrolling && root.enabled ? 1.0 : 0.0
+ x: -cursor.width - units.gu(3)
+ y: {
+ if (internal.currentItem) {
+ var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+ return (itemCenterY - (magnified.height / 2))
+ } else {
+ return 0
+ }
+ }
+
+ Label {
+ anchors.fill: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: internal.desireSection
+ fontSize: "small"
+ }
+
+ Behavior on opacity {
+ UbuntuNumberAnimation {}
+ }
+ }
+
+ Rectangle {
+ id: cursor
+
+ property bool showLabel: false
+ property string currentSectionName: ""
+
+ radius: pinSize * 0.3
+ height: pinSize
+ width: height
+ color: Theme.palette.normal.foreground
+ opacity: rail.opacity
+ x: rail.x
+ y: {
+ if (internal.currentItem) {
+ var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
+ return (itemCenterY - (cursor.height / 2))
+ } else {
+ return 0
+ }
+ }
+ Behavior on y {
+ enabled: !internal.fastScrolling
+ UbuntuNumberAnimation { }
+ }
+ }
+
+ Column {
+ id: rail
+
+ property bool isVisible: root.enabled &&
+ (listView.flicking || dragArea.pressed) &&
+ (listView.currentIndex == -1)
+ anchors {
+ right: parent.right
+ rightMargin: units.gu(2)
+ left: parent.left
+ leftMargin: units.gu(2)
+ top: parent.top
+ }
+ height: childrenRect.height
+ opacity: 0.0
+ onIsVisibleChanged: {
+ if (isVisible) {
+ rail.opacity = 1.0
+ hideTimer.stop()
+ } else if (!root.enabled) {
+ rail.opacity = 0.0
+ } else {
+ hideTimer.restart()
+ }
+ }
+
+ Behavior on opacity {
+ UbuntuNumberAnimation { }
+ }
+
+ Repeater {
+ id: sectionsRepeater
+
+ model: root.letters
+ Label {
+ id: lbl
+
+ anchors.left: parent.left
+ height: pinSize
+ width: pinSize
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ text: modelData
+ fontSize: "x-small"
+ color: cursor.y === y ? "white" : Theme.palette.selected.backgroundText
+ opacity: !internal.modelDirty && Sections.contains(text) ? 1.0 : 0.5
+ }
+ }
+
+ Timer {
+ id: hideTimer
+
+ running: false
+ interval: 2000
+ onTriggered: rail.opacity = 0.0
+ }
+ }
+
+ MouseArea {
+ id: dragArea
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ y: rail.y
+ height: rail.height
+ visible: rail.opacity == 1.0
+
+ preventStealing: true
+ onPressed: {
+ internal.adjustContentPosition(mouseY)
+ dragginTimer.start()
+ }
+
+ onReleased: {
+ dragginTimer.stop()
+ internal.desireSection = ""
+ internal.fastScrolling = false
+ }
+
+ onPositionChanged: internal.adjustContentPosition(mouseY)
+
+ Timer {
+ id: dragginTimer
+
+ running: false
+ interval: 150
+ onTriggered: {
+ internal.fastScrolling = true
+ }
+ }
+ }
+
+ Timer {
+ id: dirtyTimer
+ interval: 500
+ running: false
+ onTriggered: {
+ Sections.initSectionData(listView);
+ internal.modelDirty = false;
+ }
+ }
+
+ Timer {
+ id: timerScroll
+
+ running: false
+ interval: 10
+ onTriggered: {
+ if (internal.desireSection != internal.currentSection) {
+ var idx = Sections.getIndexFor(internal.desireSection)
+ if (idx !== -1) {
+ listView.cancelFlick()
+ listView.positionViewAtIndex(idx, ListView.Beginning)
+ }
+ }
+ }
+ }
+
+ QtObject {
+ id: internal
+
+ property string currentSection: listView.currentSection
+ property string desireSection: ""
+ property string targetSection: fastScrolling ? desireSection : currentSection
+ property int oldY: 0
+ property bool modelDirty: false
+ property bool down: true
+ property bool fastScrolling: false
+ property var currentItem: null
+
+ onTargetSectionChanged: moveIndicator(targetSection)
+
+ function initDirtyObserver() {
+ Sections.initialize(listView);
+ function dirtyObserver() {
+ if (!internal.modelDirty) {
+ internal.modelDirty = true;
+ dirtyTimer.running = true;
+ }
+ }
+
+ if (listView.model.countChanged)
+ listView.model.countChanged.connect(dirtyObserver);
+
+ if (listView.model.itemsChanged)
+ listView.model.itemsChanged.connect(dirtyObserver);
+
+ if (listView.model.itemsInserted)
+ listView.model.itemsInserted.connect(dirtyObserver);
+
+ if (listView.model.itemsMoved)
+ listView.model.itemsMoved.connect(dirtyObserver);
+
+ if (listView.model.itemsRemoved)
+ listView.model.itemsRemoved.connect(dirtyObserver);
+ }
+
+ function adjustContentPosition(mouseY) {
+ var child = rail.childAt(rail.width / 2, mouseY)
+ if (!child || child.text === "") {
+ return
+ }
+ var section = child.text
+ if (internal.desireSection !== section) {
+ internal.desireSection = section
+ moveIndicator(section)
+ if (dragArea.pressed) {
+ timerScroll.restart()
+ }
+ }
+ }
+
+ function moveIndicator(section)
+ {
+ var index = root.letters.indexOf(section)
+ if (index != -1) {
+ currentItem = sectionsRepeater.itemAt(index)
+ }
+ }
+ }
+}
=== added file 'app/components/ForecastDetailsDelegate.qml'
--- app/components/ForecastDetailsDelegate.qml 1970-01-01 00:00:00 +0000
+++ app/components/ForecastDetailsDelegate.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Row {
+ height: icon.height
+ spacing: units.gu(2)
+ visible: value !== ""
+
+ property alias imageSource: icon.source
+ property alias forecast: forecastLabel.text
+ property alias value: forecastValue.text
+
+ Icon {
+ id: icon
+ color: "#000"
+ height: units.gu(2)
+ width: height
+ }
+
+ Label {
+ id: forecastLabel
+ elide: Text.ElideRight
+ width: units.gu(8)
+ }
+
+ Label {
+ id: forecastValue
+ width: units.gu(10)
+ }
+}
=== added file 'app/components/HeaderRow.qml'
--- app/components/HeaderRow.qml 1970-01-01 00:00:00 +0000
+++ app/components/HeaderRow.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.2
+
+RowLayout {
+ id: headerRow
+
+ property alias locationName: locationNameLabel.text
+
+ width: parent.width
+
+ Label {
+ id: locationNameLabel
+ color: UbuntuColors.darkGrey
+ elide: Text.ElideRight
+ font.weight: Font.Normal
+ fontSize: "large"
+ height: settingsButton.height
+ Layout.fillWidth: true
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ AbstractButton {
+ id: settingsButton
+ height: width
+ width: units.gu(4)
+
+ onClicked: mainPageStack.push(Qt.resolvedUrl("../ui/SettingsPage.qml"))
+
+ Rectangle {
+ anchors.fill: parent
+ color: Theme.palette.selected.background
+ visible: parent.pressed
+ }
+
+ Icon {
+ anchors.centerIn: parent
+ color: UbuntuColors.darkGrey
+ height: width
+ name: "settings"
+ width: units.gu(2.5)
+ }
+ }
+}
=== added file 'app/components/HomeGraphic.qml'
--- app/components/HomeGraphic.qml 1970-01-01 00:00:00 +0000
+++ app/components/HomeGraphic.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Item {
+ height: units.gu(32)
+ width: parent.width
+
+ property alias icon: iconImage.source
+
+ // TODO: will be on 'rails' (horizontal listview?) to reveal hourly forecast
+ Image {
+ id: iconImage
+ anchors {
+ centerIn: parent
+ }
+ fillMode: Image.PreserveAspectFit
+ height: parent.height
+ width: parent.width
+ }
+}
+
=== added file 'app/components/HomeHourly.qml'
--- app/components/HomeHourly.qml 1970-01-01 00:00:00 +0000
+++ app/components/HomeHourly.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListView {
+ id: homeHourly
+
+ clip:true
+ height: parent ? parent.height : undefined
+ width: parent ? parent.width : undefined
+ model: forecasts.length
+ orientation: ListView.Horizontal
+
+ property string currentDate: Qt.formatTime(new Date())
+
+ onVisibleChanged: {
+ if(visible) {
+ ListView.model = forecasts.length
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ homeGraphic.visible = true
+ }
+ }
+
+ delegate: Item {
+ id: delegate
+
+ property var hourData: forecasts[index]
+
+ height: parent.height
+ width: childrenRect.width
+
+ Column {
+ id: hourColumn
+
+ anchors.verticalCenter: parent.verticalCenter
+ height: childrenRect.height
+ width: units.gu(10)
+
+ Label {
+ anchors.horizontalCenter: parent.horizontalCenter
+ fontSize: "small"
+ font.weight: Font.Light
+ text: currentDate.search(Qt.locale().amText) !== -1 || currentDate.search(Qt.locale().pmText) !== -1 ? "%1 %2".arg(formatTimestamp(hourData.date, 'ddd')).arg(formatTime(hourData.date, 'hap')) : "%1 %2".arg(formatTimestamp(hourData.date, 'ddd')).arg(formatTime(hourData.date, 'h:mm'))
+ }
+
+ Item {
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: units.gu(7)
+ width: units.gu(7)
+
+ Icon {
+ anchors {
+ fill: parent
+ margins: units.gu(0.5)
+ }
+ color: UbuntuColors.orange
+ name: (hourData.icon !== undefined && iconMap[hourData.icon] !== undefined) ? iconMap[hourData.icon] : ""
+ }
+ }
+
+ Label {
+ anchors.horizontalCenter: parent.horizontalCenter
+ font.pixelSize: units.gu(3)
+ font.weight: Font.Light
+ text: Math.round(hourData[tempUnits].temp).toString()+settings.tempScale
+ }
+ }
+
+ Rectangle {
+ anchors.verticalCenter: parent.verticalCenter
+ color: UbuntuColors.darkGrey
+ height: hourColumn.height
+ opacity: 0.2
+ visible: index > 0
+ width: units.gu(0.1)
+ }
+ }
+}
+
=== added file 'app/components/HomeTempInfo.qml'
--- app/components/HomeTempInfo.qml 1970-01-01 00:00:00 +0000
+++ app/components/HomeTempInfo.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+Column {
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ spacing: units.gu(1)
+
+ property alias description: descriptionLabel.text
+ property alias high: highLabel.text
+ property alias low: lowLabel.text
+ property alias now: nowLabel.text
+
+ Label {
+ font.weight: Font.Light
+ fontSize: "small"
+ text: i18n.tr("Today")
+ }
+
+ Label {
+ id: descriptionLabel
+ font.weight: Font.Normal
+ fontSize: "large"
+ }
+
+ Row {
+ spacing: units.gu(2)
+
+ Label {
+ id: nowLabel
+ color: UbuntuColors.orange
+ font.pixelSize: units.gu(8)
+ font.weight: Font.Light
+ height: units.gu(8)
+ verticalAlignment: Text.AlignBottom // AlignBottom seems to put it at the top?
+ }
+
+ Column {
+ Label {
+ id: lowLabel
+ font.weight: Font.Light
+ fontSize: "medium"
+ }
+
+ Label {
+ id: highLabel
+ font.weight: Font.Light
+ fontSize: "medium"
+ }
+ }
+ }
+}
=== added directory 'app/components/ListItemActions'
=== added file 'app/components/ListItemActions/CMakeLists.txt'
--- app/components/ListItemActions/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB LISTITEMACTIONS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_listitemactions_QMlFiles ALL SOURCES ${LISTITEMACTIONS_QML_JS_FILES})
+
+install(FILES ${LISTITEMACTIONS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/components/ListItemActions)
=== added file 'app/components/ListItemActions/CheckBox.qml'
--- app/components/ListItemActions/CheckBox.qml 1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/CheckBox.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012-2014, 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+CheckBox {
+ checked: root.selected
+ width: implicitWidth
+ // disable item mouse area to avoid conflicts with parent mouse area
+ __mouseArea.enabled: false
+}
=== added file 'app/components/ListItemActions/Remove.qml'
--- app/components/ListItemActions/Remove.qml 1970-01-01 00:00:00 +0000
+++ app/components/ListItemActions/Remove.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014, 2015 Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Daniel Holm <d.holmen@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Action {
+ id: removeAction
+ iconName: "delete"
+ objectName: "swipeDeleteAction"
+ text: i18n.tr("Remove")
+}
=== added file 'app/components/ListItemReorderComponent.qml'
--- app/components/ListItemReorderComponent.qml 1970-01-01 00:00:00 +0000
+++ app/components/ListItemReorderComponent.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ * Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+Item {
+ id: actionReorder
+ width: units.gu(4)
+
+ Icon {
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ }
+ name: "navigation-menu" // TODO: use proper image
+ height: width
+ width: units.gu(3)
+ }
+
+ MouseArea {
+ id: actionReorderMouseArea
+ anchors {
+ fill: parent
+ }
+ property int startY: 0
+ property int startContentY: 0
+
+ onPressed: {
+ root.parent.parent.interactive = false; // stop scrolling of listview
+ startY = root.y;
+ startContentY = root.parent.parent.contentY;
+ root.z += 10; // force ontop of other elements
+
+ console.debug("Reorder listitem pressed", root.y)
+ }
+ onMouseYChanged: root.y += mouse.y - (root.height / 2);
+ onReleased: {
+ console.debug("Reorder diff by position", getDiff());
+
+ var diff = getDiff();
+
+ // Remove the height of the actual item if moved down
+ if (diff > 0) {
+ diff -= 1;
+ }
+
+ root.parent.parent.interactive = true; // reenable scrolling
+
+ if (diff === 0) {
+ // Nothing has changed so reset the item
+ // z index is restored after animation
+ resetListItemYAnimation.start();
+ }
+ else {
+ var newIndex = index + diff;
+
+ if (newIndex < 0) {
+ newIndex = 0;
+ }
+ else if (newIndex > root.parent.parent.count - 1) {
+ newIndex = root.parent.parent.count - 1;
+ }
+
+ root.z -= 10; // restore z index
+ reorder(index, newIndex)
+ }
+ }
+
+ function getDiff() {
+ // Get the amount of items that have been passed over (by centre)
+ return Math.round((((root.y - startY) + (root.parent.parent.contentY - startContentY)) / root.height) + 0.5);
+ }
+ }
+
+ SequentialAnimation {
+ id: resetListItemYAnimation
+ UbuntuNumberAnimation {
+ target: root;
+ property: "y";
+ to: actionReorderMouseArea.startY
+ }
+ ScriptAction {
+ script: {
+ root.z -= 10; // restore z index
+ }
+ }
+ }
+}
=== added file 'app/components/ListItemWithActions.qml'
--- app/components/ListItemWithActions.qml 1970-01-01 00:00:00 +0000
+++ app/components/ListItemWithActions.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2012-2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 1.0 as ListItem
+
+
+Item {
+ id: root
+ width: parent.width
+
+ property Action leftSideAction: null
+ property list<Action> rightSideActions
+ property double defaultHeight: units.gu(8)
+ property bool locked: false
+ property Action activeAction: null
+ property var activeItem: null
+ property bool triggerActionOnMouseRelease: false
+ property color color: Theme.palette.normal.background
+ property color selectedColor: "#E6E6E6"
+ property bool selected: false
+ property bool selectionMode: false
+ property alias internalAnchors: mainContents.anchors
+ default property alias contents: mainContents.children
+
+ readonly property double actionWidth: units.gu(4)
+ readonly property double leftActionWidth: units.gu(10)
+ readonly property double actionThreshold: actionWidth * 0.4
+ readonly property double threshold: 0.4
+ readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
+ readonly property alias swipping: mainItemMoving.running
+ readonly property bool _showActions: mouseArea.pressed || swipeState != "Normal" || swipping
+
+ property alias _main: main // CUSTOM
+ property alias pressed: mouseArea.pressed // CUSTOM
+
+ /* internal */
+ property var _visibleRightSideActions: filterVisibleActions(rightSideActions)
+
+ signal itemClicked(var mouse)
+ signal itemPressAndHold(var mouse)
+
+ function returnToBoundsRTL(direction)
+ {
+ var actionFullWidth = actionWidth + units.gu(2)
+
+ // go back to normal state if swipping reverse
+ if (direction === "LTR") {
+ updatePosition(0)
+ return
+ } else if (!triggerActionOnMouseRelease) {
+ updatePosition(-rightActionsView.width + units.gu(2))
+ return
+ }
+
+ var xOffset = Math.abs(main.x)
+ var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
+ var newX = 0
+
+ if (index === _visibleRightSideActions.length) {
+ newX = -(rightActionsView.width - units.gu(2))
+ } else if (index >= 1) {
+ newX = -(actionFullWidth * index)
+ }
+
+ updatePosition(newX)
+ }
+
+ function returnToBoundsLTR(direction)
+ {
+ var finalX = leftActionWidth
+ if ((direction === "RTL") || (main.x <= (finalX * root.threshold)))
+ finalX = 0
+ updatePosition(finalX)
+ }
+
+ function returnToBounds(direction)
+ {
+ if (main.x < 0) {
+ returnToBoundsRTL(direction)
+ } else if (main.x > 0) {
+ returnToBoundsLTR(direction)
+ } else {
+ updatePosition(0)
+ }
+ }
+
+ function contains(item, point, marginX)
+ {
+ var itemStartX = item.x - marginX
+ var itemEndX = item.x + item.width + marginX
+ return (point.x >= itemStartX) && (point.x <= itemEndX) &&
+ (point.y >= item.y) && (point.y <= (item.y + item.height));
+ }
+
+ function getActionAt(point)
+ {
+ if (leftSideAction && contains(leftActionViewLoader.item, point, 0)) {
+ return leftSideAction
+ } else if (contains(rightActionsView, point, 0)) {
+ var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
+ for (var i = 0; i < rightActionsRepeater.count; i++) {
+ var child = rightActionsRepeater.itemAt(i)
+ if (contains(child, newPoint, units.gu(1))) {
+ return i
+ }
+ }
+ }
+ return -1
+ }
+
+ function updateActiveAction()
+ {
+ if (triggerActionOnMouseRelease &&
+ (main.x <= -(root.actionWidth + units.gu(2))) &&
+ (main.x > -(rightActionsView.width - units.gu(2)))) {
+ var actionFullWidth = actionWidth + units.gu(2)
+ var xOffset = Math.abs(main.x)
+ var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
+ index = index - 1
+ if (index > -1) {
+ root.activeItem = rightActionsRepeater.itemAt(index)
+ root.activeAction = root._visibleRightSideActions[index]
+ }
+ } else {
+ root.activeAction = null
+ }
+ }
+
+ function resetSwipe()
+ {
+ updatePosition(0)
+ }
+
+ function filterVisibleActions(actions)
+ {
+ var visibleActions = []
+ for(var i = 0; i < actions.length; i++) {
+ var action = actions[i]
+ if (action.visible) {
+ visibleActions.push(action)
+ }
+ }
+ return visibleActions
+ }
+
+ function updatePosition(pos)
+ {
+ if (!root.triggerActionOnMouseRelease && (pos !== 0)) {
+ mouseArea.state = pos > 0 ? "RightToLeft" : "LeftToRight"
+ } else {
+ mouseArea.state = ""
+ }
+ main.x = pos
+ }
+
+ // CUSTOM remove animation
+ SequentialAnimation {
+ id: removeAnimation
+
+ property var action
+
+ UbuntuNumberAnimation {
+ target: root
+ duration: UbuntuAnimation.BriskDuration
+ property: "height";
+ to: 0
+ }
+ ScriptAction {
+ script: removeAnimation.action.trigger()
+ }
+ }
+
+ states: [
+ State {
+ name: "select"
+ when: selectionMode || selected
+ PropertyChanges {
+ target: selectionIcon
+ source: Qt.resolvedUrl("ListItemActions/CheckBox.qml")
+ anchors.leftMargin: units.gu(2)
+ }
+ PropertyChanges {
+ target: root
+ locked: true
+ }
+ PropertyChanges {
+ target: main
+ x: 0
+ }
+ }
+ ]
+
+ height: defaultHeight
+ //clip: height !== defaultHeight // CUSTOM
+
+ Loader { // CUSTOM
+ id: leftActionViewLoader
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: main.left
+ }
+ asynchronous: true
+ sourceComponent: leftSideAction ? leftActionViewComponent : undefined
+ }
+
+ Component { // CUSTOM
+ id: leftActionViewComponent
+
+ Rectangle {
+ id: leftActionView
+ width: root.leftActionWidth + actionThreshold
+ color: UbuntuColors.red
+
+ Icon {
+ id: leftActionIcon
+ anchors {
+ centerIn: parent
+ horizontalCenterOffset: actionThreshold / 2
+ }
+ objectName: "swipeDeleteAction" // CUSTOM
+ name: leftSideAction && _showActions ? leftSideAction.iconName : ""
+ color: Theme.palette.selected.field
+ height: units.gu(3)
+ width: units.gu(3)
+ }
+ }
+ }
+
+ //Rectangle {
+ Item { // CUSTOM
+ id: rightActionsView
+
+ anchors {
+ top: main.top
+ left: main.right
+ bottom: main.bottom
+ }
+ visible: _visibleRightSideActions.length > 0
+ width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + root.actionThreshold + units.gu(2) : 0
+ // color: "white" // CUSTOM
+
+ Row {
+ anchors{
+ top: parent.top
+ left: parent.left
+ leftMargin: units.gu(2)
+ right: parent.right
+ rightMargin: units.gu(2)
+ bottom: parent.bottom
+ }
+ spacing: units.gu(2)
+ Repeater {
+ id: rightActionsRepeater
+
+ model: _showActions ? _visibleRightSideActions : []
+ Item {
+ property alias image: img
+
+ height: rightActionsView.height
+ width: root.actionWidth
+
+ Icon {
+ id: img
+
+ anchors.centerIn: parent
+ objectName: rightSideActions[index].objectName // CUSTOM
+ width: units.gu(3)
+ height: units.gu(3)
+ name: modelData.iconName
+ color: root.activeAction === modelData ? UbuntuColors.orange : UbuntuColors.coolGrey // CUSTOM
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: main
+ objectName: "mainItem"
+
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+
+ width: parent.width
+ color: root.selected ? root.selectedColor : root.color
+
+ Loader {
+ id: selectionIcon
+
+ anchors {
+ left: main.left
+ verticalCenter: main.verticalCenter
+ }
+ asynchronous: true // CUSTOM
+ width: (status === Loader.Ready) ? item.implicitWidth : 0
+ visible: (status === Loader.Ready) && (item.width === item.implicitWidth)
+
+ Behavior on width {
+ NumberAnimation {
+ duration: UbuntuAnimation.SnapDuration
+ }
+ }
+ }
+
+ Item {
+ id: mainContents
+
+ anchors {
+ left: selectionIcon.right
+ //leftMargin: units.gu(2) // CUSTOM
+ top: parent.top
+ //topMargin: units.gu(1) // CUSTOM
+ right: parent.right
+ //rightMargin: units.gu(2) // CUSTOM
+ bottom: parent.bottom
+ //bottomMargin: units.gu(1) // CUSTOM
+ }
+ }
+
+ Behavior on x {
+ UbuntuNumberAnimation {
+ id: mainItemMoving
+
+ easing.type: Easing.OutElastic
+ duration: UbuntuAnimation.SlowDuration
+ }
+ }
+ }
+
+ SequentialAnimation {
+ id: triggerAction
+
+ property var currentItem: root.activeItem ? root.activeItem.image : null
+
+ running: false
+ ParallelAnimation {
+ UbuntuNumberAnimation {
+ target: triggerAction.currentItem
+ property: "opacity"
+ from: 1.0
+ to: 0.0
+ duration: UbuntuAnimation.SlowDuration
+ easing {type: Easing.InOutBack; }
+ }
+ UbuntuNumberAnimation {
+ target: triggerAction.currentItem
+ properties: "width, height"
+ from: units.gu(3)
+ to: root.actionWidth
+ duration: UbuntuAnimation.SlowDuration
+ easing {type: Easing.InOutBack; }
+ }
+ }
+ PropertyAction {
+ target: triggerAction.currentItem
+ properties: "width, height"
+ value: units.gu(3)
+ }
+ PropertyAction {
+ target: triggerAction.currentItem
+ properties: "opacity"
+ value: 1.0
+ }
+ ScriptAction {
+ script: {
+ root.activeAction.triggered(root)
+ mouseArea.state = ""
+ }
+ }
+ PauseAnimation {
+ duration: 500
+ }
+ UbuntuNumberAnimation {
+ target: main
+ property: "x"
+ to: 0
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ property bool locked: root.locked || ((root.leftSideAction === null) && (root._visibleRightSideActions.count === 0)) // CUSTOM
+ property bool manual: false
+ property string direction: "None"
+ property real lastX: -1
+
+ anchors.fill: parent
+ drag {
+ target: locked ? null : main
+ axis: Drag.XAxis
+ minimumX: rightActionsView.visible ? -(rightActionsView.width) : 0
+ maximumX: leftSideAction ? leftActionViewLoader.item.width : 0
+ threshold: root.actionThreshold
+ }
+
+ states: [
+ State {
+ name: "LeftToRight"
+ PropertyChanges {
+ target: mouseArea
+ drag.maximumX: 0
+ }
+ },
+ State {
+ name: "RightToLeft"
+ PropertyChanges {
+ target: mouseArea
+ drag.minimumX: 0
+ }
+ }
+ ]
+
+ onMouseXChanged: {
+ var offset = (lastX - mouseX)
+ if (Math.abs(offset) <= root.actionThreshold) {
+ return
+ }
+ lastX = mouseX
+ direction = offset > 0 ? "RTL" : "LTR";
+ }
+
+ onPressed: {
+ lastX = mouse.x
+ }
+
+ onReleased: {
+ if (root.triggerActionOnMouseRelease && root.activeAction) {
+ triggerAction.start()
+ } else {
+ root.returnToBounds()
+ root.activeAction = null
+ }
+ lastX = -1
+ direction = "None"
+ }
+ onClicked: {
+ if (selectionMode) { // CUSTOM - selecting a listitem should toggle selection if in selectionMode
+ selected = !selected
+ return
+ } else if (main.x === 0) {
+ root.itemClicked(mouse)
+ } else if (main.x > 0) {
+ var action = getActionAt(Qt.point(mouse.x, mouse.y))
+ if (action && action !== -1) {
+ //action.triggered(root)
+ removeAnimation.action = action // CUSTOM - use our animation instead
+ removeAnimation.start() // CUSTOM
+ }
+ } else {
+ var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y))
+
+ if (actionIndex !== -1 && actionIndex !== leftSideAction) { // CUSTOM - can be leftAction
+ root.activeItem = rightActionsRepeater.itemAt(actionIndex)
+ root.activeAction = root.rightSideActions[actionIndex]
+ triggerAction.start()
+ return
+ }
+ }
+ root.resetSwipe()
+ }
+
+ onPositionChanged: {
+ if (mouseArea.pressed) {
+ updateActiveAction()
+
+ listItemSwiping(index) // CUSTOM - tells other listitems to dismiss any swipe
+ }
+ }
+ onPressAndHold: {
+ if (main.x === 0) {
+ root.itemPressAndHold(mouse)
+ }
+ }
+
+ z: -1
+ }
+}
=== added file 'app/components/LoadingIndicator.qml'
--- app/components/LoadingIndicator.qml 1970-01-01 00:00:00 +0000
+++ app/components/LoadingIndicator.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Rectangle {
+ id: indicator
+ objectName: "processingIndicator"
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ bottomMargin: Qt.inputMethod.keyboardRectangle.height
+ }
+ height: units.dp(3)
+ color: "white"
+ opacity: 0
+ visible: opacity > 0
+
+ readonly property bool processing: loading
+
+ Behavior on opacity {
+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
+ }
+
+ onProcessingChanged: {
+ if (processing) delay.start();
+ else if (!persist.running) indicator.opacity = 0;
+ }
+
+ Timer {
+ id: delay
+ interval: 200
+ onTriggered: if (indicator.processing) {
+ persist.restart();
+ indicator.opacity = 1;
+ }
+ }
+
+ Timer {
+ id: persist
+ interval: 2 * UbuntuAnimation.SleepyDuration - UbuntuAnimation.FastDuration
+ onTriggered: if (!indicator.processing) indicator.opacity = 0
+ }
+
+ Rectangle {
+ id: orange
+ anchors { top: parent.top; bottom: parent.bottom }
+ width: parent.width / 4
+ color: UbuntuColors.orange
+
+ SequentialAnimation {
+ running: indicator.visible
+ loops: Animation.Infinite
+ XAnimator {
+ from: -orange.width / 2
+ to: indicator.width - orange.width / 2
+ duration: UbuntuAnimation.SleepyDuration
+ easing.type: Easing.InOutSine
+ target: orange
+ }
+ XAnimator {
+ from: indicator.width - orange.width / 2
+ to: -orange.width / 2
+ duration: UbuntuAnimation.SleepyDuration
+ easing.type: Easing.InOutSine
+ target: orange
+ }
+ }
+ }
+}
=== added file 'app/components/MultiSelectHeadState.qml'
--- app/components/MultiSelectHeadState.qml 1970-01-01 00:00:00 +0000
+++ app/components/MultiSelectHeadState.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015
+ * Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+PageHeadState {
+ id: selectionState
+ actions: [
+ Action {
+ iconName: "select"
+ text: i18n.tr("Select All")
+ onTriggered: {
+ if (listview.selectedItems.length === listview.model.count) {
+ listview.clearSelection()
+ } else {
+ listview.selectAll()
+ }
+ }
+ },
+ Action {
+ enabled: listview.selectedItems.length > 0
+ iconName: "delete"
+ text: i18n.tr("Delete")
+ visible: removable
+
+ onTriggered: {
+ removed(listview.selectedItems)
+
+ listview.closeSelection()
+ }
+ }
+
+ ]
+ backAction: Action {
+ text: i18n.tr("Cancel selection")
+ iconName: "back"
+ onTriggered: {
+ listview.clearSelection()
+ listview.state = "normal"
+ }
+ }
+ head: thisPage.head
+ name: "selection"
+
+ PropertyChanges {
+ target: thisPage.head
+ backAction: selectionState.backAction
+ actions: selectionState.actions
+ }
+
+ property ListView listview
+ property bool removable: false
+ property Page thisPage
+
+ signal removed(var selectedItems)
+}
=== added file 'app/components/MultiSelectListView.qml'
--- app/components/MultiSelectListView.qml 1970-01-01 00:00:00 +0000
+++ app/components/MultiSelectListView.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ * Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Daniel Holm <d.holmen@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+WeatherListView {
+ property var selectedItems: []
+
+ signal clearSelection()
+ signal closeSelection()
+ signal selectAll()
+
+ onClearSelection: selectedItems = []
+ onCloseSelection: {
+ clearSelection()
+ state = "normal"
+ }
+ onSelectAll: {
+ var tmp = selectedItems
+
+ for (var i=0; i < model.count; i++) {
+ if (tmp.indexOf(i) === -1) {
+ tmp.push(i)
+ }
+ }
+
+ selectedItems = tmp
+ }
+ onVisibleChanged: {
+ if (!visible) {
+ closeSelection()
+ }
+ }
+}
=== added file 'app/components/PageWithBottomEdge.qml'
--- app/components/PageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
+++ app/components/PageWithBottomEdge.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2014, 2015 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ Example:
+
+ MainView {
+ objectName: "mainView"
+
+ applicationName: "com.ubuntu.developer.boiko.bottomedge"
+
+ width: units.gu(100)
+ height: units.gu(75)
+
+ Component {
+ id: pageComponent
+
+ PageWithBottomEdge {
+ id: mainPage
+ title: i18n.tr("Main Page")
+
+ Rectangle {
+ anchors.fill: parent
+ color: "white"
+ }
+
+ bottomEdgePageComponent: Page {
+ title: "Contents"
+ anchors.fill: parent
+ //anchors.topMargin: contentsPage.flickable.contentY
+
+ ListView {
+ anchors.fill: parent
+ model: 50
+ delegate: ListItems.Standard {
+ text: "One Content Item: " + index
+ }
+ }
+ }
+ bottomEdgeTitle: i18n.tr("Bottom edge action")
+ }
+ }
+
+ PageStack {
+ id: stack
+ Component.onCompleted: stack.push(pageComponent)
+ }
+ }
+
+*/
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+Page {
+ id: page
+
+ property alias bottomEdgePageComponent: edgeLoader.sourceComponent
+ property alias bottomEdgePageSource: edgeLoader.source
+ property alias bottomEdgeTitle: tipLabel.text
+ property bool bottomEdgeEnabled: true
+ property int bottomEdgeExpandThreshold: page.height * 0.2
+ property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
+ property bool reloadBottomEdgePage: true
+
+ readonly property alias bottomEdgePage: edgeLoader.item
+ readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
+ readonly property bool isCollapsed: (bottomEdge.y === page.height)
+ readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
+
+ property bool _showEdgePageWhenReady: false
+ property int _areaWhenExpanded: 0
+
+ // CUSTOM properties to allow changing of color
+ property alias tipColor: tip.color
+ property alias tipLabelColor: tipLabel.color
+
+ signal bottomEdgeReleased()
+ signal bottomEdgeDismissed()
+
+
+ function showBottomEdgePage(source, properties)
+ {
+ edgeLoader.setSource(source, properties)
+ _showEdgePageWhenReady = true
+ }
+
+ function setBottomEdgePage(source, properties)
+ {
+ edgeLoader.setSource(source, properties)
+ }
+
+ function _pushPage()
+ {
+ if (edgeLoader.status === Loader.Ready) {
+ edgeLoader.item.active = true
+ page.pageStack.push(edgeLoader.item)
+ if (edgeLoader.item.flickable) {
+ edgeLoader.item.flickable.contentY = -page.header.height
+ edgeLoader.item.flickable.returnToBounds()
+ }
+ if (edgeLoader.item.ready)
+ edgeLoader.item.ready()
+ }
+ }
+
+
+ Component.onCompleted: {
+ // avoid a binding on the expanded height value
+ var expandedHeight = height;
+ _areaWhenExpanded = expandedHeight;
+ }
+
+ onActiveChanged: {
+ if (active) {
+ bottomEdge.state = "collapsed"
+ }
+ }
+
+ onBottomEdgePageLoadedChanged: {
+ if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
+ bottomEdge.state = "expanded"
+ _showEdgePageWhenReady = false
+ }
+ }
+
+ Rectangle {
+ id: bgVisual
+
+ color: "black"
+ anchors.fill: page
+ opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
+ z: 1
+ }
+
+ UbuntuShape {
+ id: tip
+ objectName: "bottomEdgeTip"
+
+ property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
+
+ enabled: mouseArea.enabled
+ visible: page.bottomEdgeEnabled
+ anchors {
+ bottom: parent.bottom
+ horizontalCenter: bottomEdge.horizontalCenter
+ bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
+ Behavior on bottomMargin {
+ SequentialAnimation {
+ // wait some msecs in case of the focus change again, to avoid flickering
+ PauseAnimation {
+ duration: 300
+ }
+ UbuntuNumberAnimation {
+ duration: UbuntuAnimation.SnapDuration
+ }
+ }
+ }
+ }
+
+ z: 1
+ width: tipLabel.paintedWidth + units.gu(6)
+ height: bottomEdge.tipHeight + units.gu(1)
+ color: Theme.palette.normal.overlay
+ Label {
+ id: tipLabel
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ height: bottomEdge.tipHeight
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ opacity: tip.hidden ? 0.0 : 1.0
+ Behavior on opacity {
+ UbuntuNumberAnimation {
+ duration: UbuntuAnimation.SnapDuration
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: shadow
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ height: units.gu(1)
+ z: 1
+ opacity: 0.0
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "transparent" }
+ GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ property real previousY: -1
+ property string dragDirection: "None"
+
+ preventStealing: true
+ drag {
+ axis: Drag.YAxis
+ target: bottomEdge
+ minimumY: bottomEdge.pageStartY
+ maximumY: page.height
+ }
+ enabled: edgeLoader.status == Loader.Ready
+ visible: page.bottomEdgeEnabled
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+
+ }
+ height: bottomEdge.tipHeight
+ z: 1
+
+ onReleased: {
+ page.bottomEdgeReleased()
+ if ((dragDirection === "BottomToTop") &&
+ bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
+ bottomEdge.state = "expanded"
+ } else {
+ bottomEdge.state = "collapsed"
+ }
+ previousY = -1
+ dragDirection = "None"
+ }
+
+ onPressed: {
+ previousY = mouse.y
+ tip.forceActiveFocus()
+ }
+
+ onMouseYChanged: {
+ var yOffset = previousY - mouseY
+ // skip if was a small move
+ if (Math.abs(yOffset) <= units.gu(2)) {
+ return
+ }
+ previousY = mouseY
+ dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
+ }
+ }
+
+ Rectangle {
+ id: bottomEdge
+ objectName: "bottomEdge"
+
+ readonly property int tipHeight: units.gu(3)
+ readonly property int pageStartY: 0
+
+ z: 1
+ color: Theme.palette.normal.background
+ clip: true
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ height: page.height
+ y: height
+ visible: !page.isCollapsed
+ state: "collapsed"
+ states: [
+ State {
+ name: "collapsed"
+ PropertyChanges {
+ target: bottomEdge
+ y: bottomEdge.height
+ }
+ },
+ State {
+ name: "expanded"
+ PropertyChanges {
+ target: bottomEdge
+ y: bottomEdge.pageStartY
+ }
+ },
+ State {
+ name: "floating"
+ when: mouseArea.drag.active
+ PropertyChanges {
+ target: shadow
+ opacity: 1.0
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ to: "expanded"
+ SequentialAnimation {
+ alwaysRunToEnd: true
+
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.FastDuration
+ easing.type: Easing.Linear
+ }
+ SmoothedAnimation {
+ target: edgeLoader
+ property: "anchors.topMargin"
+ to: - units.gu(4)
+ duration: UbuntuAnimation.FastDuration
+ easing.type: Easing.Linear
+ }
+ SmoothedAnimation {
+ target: edgeLoader
+ property: "anchors.topMargin"
+ to: 0
+ duration: UbuntuAnimation.FastDuration
+ easing: UbuntuAnimation.StandardEasing
+ }
+ ScriptAction {
+ script: page._pushPage()
+ }
+ }
+ },
+ Transition {
+ from: "expanded"
+ to: "collapsed"
+ SequentialAnimation {
+ alwaysRunToEnd: true
+
+ ScriptAction {
+ script: {
+ Qt.inputMethod.hide()
+ edgeLoader.item.parent = edgeLoader
+ edgeLoader.item.anchors.fill = edgeLoader
+ edgeLoader.item.active = false
+ }
+ }
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.SlowDuration
+ }
+ ScriptAction {
+ script: {
+ // destroy current bottom page
+ if (page.reloadBottomEdgePage) {
+ edgeLoader.active = false
+ // tip will receive focus on page active true
+ } else {
+ tip.forceActiveFocus()
+ }
+
+ // notify
+ page.bottomEdgeDismissed()
+
+ edgeLoader.active = true
+ }
+ }
+ }
+ },
+ Transition {
+ from: "floating"
+ to: "collapsed"
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.FastDuration
+ }
+ }
+ ]
+
+ Loader {
+ id: edgeLoader
+
+ asynchronous: true
+ anchors.fill: parent
+ //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
+ Binding {
+ target: edgeLoader.status === Loader.Ready ? edgeLoader : null
+ property: "anchors.topMargin"
+ value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
+ when: !page.isReady
+ }
+
+ onLoaded: {
+ tip.forceActiveFocus()
+ if (page.isReady && edgeLoader.item.active !== true) {
+ page._pushPage()
+ }
+ }
+ }
+ }
+}
=== added file 'app/components/StandardListItem.qml'
--- app/components/StandardListItem.qml 1970-01-01 00:00:00 +0000
+++ app/components/StandardListItem.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.2
+
+ListItem {
+ id: listItem
+
+ property alias title: _title.text
+ property alias icon: _icon.name
+ property alias showIcon: _icon.visible
+
+ RowLayout {
+ anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter; margins: units.gu(2) }
+ height: _icon.height
+ spacing: units.gu(2)
+
+ Label {
+ id: _title
+ anchors.verticalCenter: _icon.verticalCenter
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+
+ Icon {
+ id: _icon
+ height: units.gu(2); width: height
+ name: "go-next"
+ }
+ }
+}
=== added file 'app/components/WeatherListItem.qml'
--- app/components/WeatherListItem.qml 1970-01-01 00:00:00 +0000
+++ app/components/WeatherListItem.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ * Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+ListItemWithActions {
+ id: root
+
+ property int listItemIndex: index
+ property bool multiselectable: false
+ property int previousListItemIndex: -1
+ property bool reorderable: false
+
+ signal reorder(int from, int to)
+
+ onItemPressAndHold: {
+ if (multiselectable) {
+ selectionMode = true
+ }
+ }
+
+ onListItemIndexChanged: {
+ var i = parent.parent.selectedItems.lastIndexOf(previousListItemIndex)
+
+ if (i !== -1) {
+ parent.parent.selectedItems[i] = listItemIndex
+ }
+
+ previousListItemIndex = listItemIndex
+ }
+
+ onSelectedChanged: {
+ if (selectionMode) {
+ var tmp = parent.parent.selectedItems
+
+ if (selected) {
+ if (parent.parent.selectedItems.indexOf(listItemIndex) === -1) {
+ tmp.push(listItemIndex)
+ parent.parent.selectedItems = tmp
+ }
+ } else {
+ tmp.splice(parent.parent.selectedItems.indexOf(listItemIndex), 1)
+ parent.parent.selectedItems = tmp
+ }
+ }
+ }
+
+ onSelectionModeChanged: {
+ if (reorderable && selectionMode) {
+ resetSwipe()
+ }
+
+ for (var j=0; j < _main.children.length; j++) {
+ if (_main.children[j] !== actionReorderLoader) {
+ _main.children[j].anchors.rightMargin = reorderable && selectionMode ? actionReorderLoader.width + units.gu(2) : 0
+ }
+ }
+
+ parent.parent.state = selectionMode ? "multiselectable" : "normal"
+
+ if (!selectionMode) {
+ selected = false
+ }
+ }
+
+ /* Highlight the listitem on press */
+ Rectangle {
+ id: listItemBrighten
+ color: root.pressed ? UbuntuColors.coolGrey : "transparent"
+ opacity: 0.1
+ height: root.height
+ x: root.x - parent.x // -parent.x due to selectionIcon in ListItemWithActions
+ width: root.width
+ }
+
+ /* Reorder Component */
+ Loader {
+ id: actionReorderLoader
+ active: reorderable && selectionMode && root.parent.parent.selectedItems.length === 0
+ anchors {
+ bottom: parent.bottom
+ right: parent.right
+ rightMargin: units.gu(1)
+ top: parent.top
+ }
+ asynchronous: true
+ source: "ListItemReorderComponent.qml"
+ }
+
+ Item {
+ Connections { // Only allow one ListItem to be swiping at any time
+ target: weatherApp
+ onListItemSwiping: {
+ if (i !== index) {
+ root.resetSwipe();
+ }
+ }
+ }
+
+ Connections { // Connections from signals in the ListView
+ target: root.parent.parent
+ onClearSelection: selected = false
+ onFlickingChanged: {
+ if (root.parent.parent.flicking) {
+ root.resetSwipe()
+ }
+ }
+ onSelectAll: selected = true
+ onStateChanged: selectionMode = root.parent.parent.state === "multiselectable"
+ }
+ }
+
+ Component.onCompleted: { // reload settings as delegates are destroyed
+ if (parent.parent.selectedItems.indexOf(index) !== -1) {
+ selected = true
+ }
+
+ selectionMode = root.parent.parent.state === "multiselectable"
+ }
+}
=== added file 'app/components/WeatherListView.qml'
--- app/components/WeatherListView.qml 1970-01-01 00:00:00 +0000
+++ app/components/WeatherListView.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013, 2014, 2015
+ * Andrew Hayzen <ahayzen@xxxxxxxxx>
+ * Daniel Holm <d.holmen@xxxxxxxxx>
+ * Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+
+
+ListView {
+ Component.onCompleted: {
+ // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
+ // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
+ var scaleFactor = units.gridUnit / 8;
+ maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+ flickDeceleration = flickDeceleration * scaleFactor;
+ }
+}
=== added directory 'app/data'
=== added file 'app/data/CMakeLists.txt'
--- app/data/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/data/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB DATA_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_data_QMlFiles ALL SOURCES ${DATA_QML_JS_FILES})
+
+install(FILES ${DATA_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/data)
=== added file 'app/data/CitiesList.js'
--- app/data/CitiesList.js 1970-01-01 00:00:00 +0000
+++ app/data/CitiesList.js 2015-06-28 23:49:12 +0000
@@ -0,0 +1,65 @@
+.pragma library
+/*
+ * Copyright (C) 2013 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Martin Borho <martin@xxxxxxxxx>
+ */
+
+var preList = [
+ {"coord":{"lon":"4.88969","lat":"52.37403"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Amsterdam"},"population":741636,"adminName2":"Gemeente Amsterdam","name":"Amsterdam","country":"NL","countryName":"Netherlands","adminName1":"North Holland","adminName3":"","services":{"geonames":2759794},"areaLabel":"North Holland, Netherlands"},
+ {"coord":{"lon":"116.39723","lat":"39.9075"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":7480601,"adminName2":"","name":"Beijing","country":"CN","countryName":"China","adminName1":"Beijing","adminName3":"","services":{"geonames":1816670},"areaLabel":"Beijing, China"},
+ {"coord":{"lon":"-74.08175","lat":"4.60971"},"timezone":{"gmtOffset":-5,"dstOffset":-5,"timeZoneId":"America/Bogota"},"population":7674366,"adminName2":"","name":"Bogotá","country":"CO","countryName":"Colombia","adminName1":"Bogota D.C.","adminName3":"","services":{"geonames":3688689},"areaLabel":"Bogota D.C., Colombia"},
+ {"coord":{"lon":"-58.37723","lat":"-34.61315"},"timezone":{"gmtOffset":-3,"dstOffset":-3,"timeZoneId":"America/Argentina/Buenos_Aires"},"population":13076300,"adminName2":"","name":"Buenos Aires","country":"AR","countryName":"Argentina","adminName1":"Buenos Aires F.D.","adminName3":"","services":{"geonames":3435910},"areaLabel":"Buenos Aires F.D., Argentina"},
+ {"coord":{"lon":"31.24967","lat":"30.06263"},"timezone":{"gmtOffset":2,"dstOffset":2,"timeZoneId":"Africa/Cairo"},"population":7734614,"adminName2":"","name":"Cairo","country":"EG","countryName":"Egypt","adminName1":"Al Qāhirah","adminName3":"","services":{"geonames":360630},"areaLabel":"Al Qāhirah, Egypt"},
+ {"coord":{"lon":"-66.87919","lat":"10.48801"},"timezone":{"gmtOffset":-4.5,"dstOffset":-4.5,"timeZoneId":"America/Caracas"},"population":3000000,"adminName2":"Municipio Libertador","name":"Caracas","country":"VE","countryName":"Venezuela","adminName1":"Distrito Federal","adminName3":"","services":{"geonames":3646738},"areaLabel":"Distrito Federal, Venezuela, Bolivarian Republic of"},
+ {"coord":{"lon":"-87.65005","lat":"41.85003"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Chicago"},"population":2695598,"adminName2":"Cook County","name":"Chicago","country":"US","countryName":"United States","adminName1":"Illinois","adminName3":"","services":{"geonames":4887398},"areaLabel":"Illinois, United States"},
+ {"coord":{"lon":"77.22897","lat":"28.65381"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":10927986,"adminName2":"","name":"Dehli","country":"IN","countryName":"India","adminName1":"NCT","adminName3":"","services":{"geonames":1273294},"areaLabel":"NCT, India"},
+ {"coord":{"lon":"55.30472","lat":"25.25817"},"timezone":{"gmtOffset":4,"dstOffset":4,"timeZoneId":"Asia/Dubai"},"population":1137347,"adminName2":"","name":"Dubai","country":"AE","countryName":"United Arab Emirates","adminName1":"Dubai","adminName3":"","services":{"geonames":292223},"areaLabel":"Dubai, United Arab Emirates"},
+ {"coord":{"lon":"113.25","lat":"23.11667"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3152825,"adminName2":"","name":"Guangzhou","country":"CN","countryName":"China","adminName1":"Guangdong Province","adminName3":"","services":{"geonames":1809858},"areaLabel":"Guangdong Province, China"},
+ {"coord":{"lon":"10.01534","lat":"53.57532"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Berlin"},"population":1739117,"adminName2":"","name":"Hamburg","country":"DE","countryName":"Germany","adminName1":"Hamburg","adminName3":"","services":{"geonames":2911298},"areaLabel":"Hamburg, Germany"},
+ {"coord":{"lon":"-95.36327","lat":"29.76328"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Chicago"},"population":2099451,"adminName2":"Harris County","name":"Houston","country":"US","countryName":"United States","adminName1":"Texas","adminName3":"","services":{"geonames":4699066},"areaLabel":"Texas, United States"},
+ {"coord":{"lon":"28.94966","lat":"41.01384"},"timezone":{"gmtOffset":2,"dstOffset":3,"timeZoneId":"Europe/Istanbul"},"population":11174257,"adminName2":"","name":"Istanbul","country":"TR","countryName":"Turkey","adminName1":"Istanbul","adminName3":"","services":{"geonames":745044},"areaLabel":"Istanbul, Turkey"},
+ {"coord":{"lon":"106.84513","lat":"-6.21462"},"timezone":{"gmtOffset":7,"dstOffset":7,"timeZoneId":"Asia/Jakarta"},"population":8540121,"adminName2":"","name":"Jakarta","country":"ID","countryName":"Indonesia","adminName1":"Jakarta Raya","adminName3":"","services":{"geonames":1642911},"areaLabel":"Jakarta Raya, Indonesia"},
+ {"coord":{"lon":"28.04363","lat":"-26.20227"},"timezone":{"gmtOffset":2,"dstOffset":2,"timeZoneId":"Africa/Johannesburg"},"population":2026469,"adminName2":"City of Johannesburg Metropolitan Municipality","name":"Johannesburg","country":"ZA","countryName":"South Africa","adminName1":"Gauteng","adminName3":"City of Johannesburg","services":{"geonames":993800},"areaLabel":"Gauteng, South Africa"},
+ {"coord":{"lon":"67.0822","lat":"24.9056"},"timezone":{"gmtOffset":5,"dstOffset":5,"timeZoneId":"Asia/Karachi"},"population":11624219,"adminName2":"Karāchi District","name":"Karachi","country":"PK","countryName":"Pakistan","adminName1":"Sindh","adminName3":"","services":{"geonames":1174872},"areaLabel":"Sindh, Pakistan"},
+ {"coord":{"lon":"15.30807","lat":"-4.32142"},"timezone":{"gmtOffset":1,"dstOffset":1,"timeZoneId":"Africa/Kinshasa"},"population":7785965,"adminName2":"","name":"Kinshasa","country":"CD","countryName":"Democratic Republic of the Congo","adminName1":"Kinshasa","adminName3":"","services":{"geonames":2314302},"areaLabel":"Kinshasa, Congo, the Democratic Republic of the"},
+ {"coord":{"lon":"88.36304","lat":"22.56263"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":4631392,"adminName2":"","name":"Kolkata","country":"IN","countryName":"India","adminName1":"Bengal","adminName3":"","services":{"geonames":1275004},"areaLabel":"Bengal, India"},
+ {"coord":{"lon":"3.39583","lat":"6.45306"},"timezone":{"gmtOffset":1,"dstOffset":1,"timeZoneId":"Africa/Lagos"},"population":9000000,"adminName2":"","name":"Lagos","country":"NG","countryName":"Nigeria","adminName1":"Lagos","adminName3":"","services":{"geonames":2332459},"areaLabel":"Lagos, Nigeria"},
+ {"coord":{"lon":"-77.02824","lat":"-12.04318"},"timezone":{"gmtOffset":-5,"dstOffset":-5,"timeZoneId":"America/Lima"},"population":7737002,"adminName2":"","name":"Lima","country":"PE","countryName":"Peru","adminName1":"Provincia de Lima","adminName3":"","services":{"geonames":3936456},"areaLabel":"Provincia de Lima, Peru"},
+ {"coord":{"lon":"-0.12574","lat":"51.50853"},"timezone":{"gmtOffset":0,"dstOffset":1,"timeZoneId":"Europe/London"},"population":7556900,"adminName2":"Greater London","name":"London","country":"GB","countryName":"United Kingdom","adminName1":"England","adminName3":"","services":{"geonames":2643743},"areaLabel":"England, Greater London, United Kingdom"},
+ {"coord":{"lon":"-118.24368","lat":"34.05223"},"timezone":{"gmtOffset":-8,"dstOffset":-7,"timeZoneId":"America/Los_Angeles"},"population":3792621,"adminName2":"Los Angeles County","name":"Los Angeles","country":"US","countryName":"United States","adminName1":"California","adminName3":"","services":{"geonames":5368361},"areaLabel":"California, United States"},
+ {"coord":{"lon":"-3.70256","lat":"40.4165"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Madrid"},"population":3255944,"adminName2":"Madrid","name":"Madrid","country":"ES","countryName":"Spain","adminName1":"Madrid","adminName3":"Madrid","services":{"geonames":3117735},"areaLabel":"Madrid, Spain"},
+ {"coord":{"lon":"120.9822","lat":"14.6042"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Manila"},"population":10444527,"adminName2":"PH.NCR.D9","name":"Manila","country":"PH","countryName":"Philippines","adminName1":"National Capital Region","adminName3":"","services":{"geonames":1701668},"areaLabel":"National Capital, Philippines"},
+ {"coord":{"lon":"-73.58781","lat":"45.50884"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/Montreal"},"population":3268513,"adminName2":"Montréal","name":"Montreal","country":"CA","countryName":"Canada","adminName1":"Quebec","adminName3":"","services":{"geonames":6077243},"areaLabel":"Quebec, Canada"},
+ {"coord":{"lon":"-99.12766","lat":"19.42847"},"timezone":{"gmtOffset":-6,"dstOffset":-5,"timeZoneId":"America/Mexico_City"},"population":12294193,"adminName2":"","name":"Mexico City","country":"MX","countryName":"Mexico","adminName1":"The Federal District","adminName3":"","services":{"geonames":3530597},"areaLabel":"The Federal District, Mexico"},
+ {"coord":{"lon":"37.61556","lat":"55.75222"},"timezone":{"gmtOffset":4,"dstOffset":4,"timeZoneId":"Europe/Moscow"},"population":10381222,"adminName2":"","name":"Moscow","country":"RU","countryName":"Russia","adminName1":"Moscow","adminName3":"","services":{"geonames":524901},"areaLabel":"Moscow, Russian Federation"},
+ {"coord":{"lon":"72.88261","lat":"19.07283"},"timezone":{"gmtOffset":5.5,"dstOffset":5.5,"timeZoneId":"Asia/Kolkata"},"population":12691836,"adminName2":"Konkan Division","name":"Mumbai","country":"IN","countryName":"India","adminName1":"Mahārāshtra","adminName3":"","services":{"geonames":1275339},"areaLabel":"Mahārāshtra, India"},
+ {"coord":{"lon":"36.81667","lat":"-1.28333"},"timezone":{"gmtOffset":3,"dstOffset":3,"timeZoneId":"Africa/Nairobi"},"population":2750547,"adminName2":"","name":"Nairobi","country":"KE","countryName":"Kenya","adminName1":"Nairobi Area","adminName3":"","services":{"geonames":184745},"areaLabel":"Nairobi Area, Kenya"},
+ {"coord":{"lon":"-74.00597","lat":"40.71427"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/New_York"},"population":8175133,"adminName2":"","name":"New York City","country":"US","countryName":"United States","adminName1":"New York","adminName3":"","services":{"geonames":5128581},"areaLabel":"New York, United States"},
+ {"coord":{"lon":"135.50218","lat":"34.69374"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Tokyo"},"population":2592413,"adminName2":"","name":"Osaka","country":"JP","countryName":"Japan","adminName1":"Ōsaka","adminName3":"","services":{"geonames":1853909},"areaLabel":"Ōsaka, Japan"},
+ {"coord":{"lon":"-43.2075","lat":"-22.90278"},"timezone":{"gmtOffset":-2,"dstOffset":-3,"timeZoneId":"America/Sao_Paulo"},"population":6023699,"adminName2":"Rio de Janeiro","name":"Rio de Janeiro","country":"BR","countryName":"Brazil","adminName1":"Rio de Janeiro","adminName3":"","services":{"geonames":3451190},"areaLabel":"Rio de Janeiro, Brazil"},
+ {"coord":{"lon":"12.4839","lat":"41.89474"},"timezone":{"gmtOffset":1,"dstOffset":2,"timeZoneId":"Europe/Rome"},"population":2563241,"adminName2":"Rome","name":"Rome","country":"IT","countryName":"Italy","adminName1":"Latium","adminName3":"Rome","services":{"geonames":3169070},"areaLabel":"Latium, Italy"},
+ {"coord":{"lon":"126.97783","lat":"37.56826"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Seoul"},"population":10349312,"adminName2":"","name":"Seoul","country":"KR","countryName":"South Korea","adminName1":"Seoul","adminName3":"","services":{"geonames":1835848},"areaLabel":"Seoul, Korea, Republic of"},
+ {"coord":{"lon":"-122.41942","lat":"37.77493"},"timezone":{"gmtOffset":-8,"dstOffset":-7,"timeZoneId":"America/Los_Angeles"},"population":805235,"adminName2":"San Francisco County","name":"San Francisco","country":"US","countryName":"United States","adminName1":"California","adminName3":"","services":{"geonames":5391959},"areaLabel":"California, United States"},
+ {"coord":{"lon":"-46.63611","lat":"-23.5475"},"timezone":{"gmtOffset":-2,"dstOffset":-3,"timeZoneId":"America/Sao_Paulo"},"population":10021295,"adminName2":"São Paulo","name":"São Paulo","country":"BR","countryName":"Brazil","adminName1":"São Paulo","adminName3":"","services":{"geonames":3448439},"areaLabel":"São Paulo, Brazil"},
+ {"coord":{"lon":"121.45806","lat":"31.22222"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":14608512,"adminName2":"","name":"Shanghai","country":"CN","countryName":"China","adminName1":"Shanghai Shi","adminName3":"","services":{"geonames":1796236},"areaLabel":"Shanghai Shi, China"},
+ {"coord":{"lon":"114.0683","lat":"22.54554"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3000000,"adminName2":"Shenzhen","name":"Shenzhen","country":"CN","countryName":"China","adminName1":"Guangdong Province","adminName3":"","services":{"geonames":1795565},"areaLabel":"Guangdong Province, Shenzhen, China"},
+ {"coord":{"lon":"103.85007","lat":"1.28967"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Singapore"},"population":3547809,"adminName2":"","name":"Singapore","country":"SG","countryName":"Singapore","adminName1":"","adminName3":"","services":{"geonames":1880252},"areaLabel":"Singapore"},
+ {"coord":{"lon":"151.20732","lat":"-33.86785"},"timezone":{"gmtOffset":11,"dstOffset":10,"timeZoneId":"Australia/Sydney"},"population":4627345,"adminName2":"City of Sydney","name":"Sydney","country":"AU","countryName":"Australia","adminName1":"New South Wales","adminName3":"","services":{"geonames":2147714},"areaLabel":"New South Wales, Australia"},
+ {"coord":{"lon":"51.42151","lat":"35.69439"},"timezone":{"gmtOffset":3.5,"dstOffset":4.5,"timeZoneId":"Asia/Tehran"},"population":7153309,"adminName2":"","name":"Tehran","country":"IR","countryName":"Iran","adminName1":"Tehrān","adminName3":"","services":{"geonames":112931},"areaLabel":"Tehrān, Iran, Islamic Republic of"},
+ {"coord":{"lon":"117.17667","lat":"39.14222"},"timezone":{"gmtOffset":8,"dstOffset":8,"timeZoneId":"Asia/Shanghai"},"population":3766207,"adminName2":"","name":"Tianjin","country":"CN","countryName":"China","adminName1":"Tianjin Shi","adminName3":"","services":{"geonames":1792947},"areaLabel":"Tianjin Shi, China"},
+ {"coord":{"lon":"139.69171","lat":"35.6895"},"timezone":{"gmtOffset":9,"dstOffset":9,"timeZoneId":"Asia/Tokyo"},"population":8336599,"adminName2":"","name":"Tokyo","country":"JP","countryName":"Japan","adminName1":"Tōkyō","adminName3":"","services":{"geonames":1850147},"areaLabel":"Tōkyō, Japan"},
+ {"coord":{"lon":"-79.4163","lat":"43.70011"},"timezone":{"gmtOffset":-5,"dstOffset":-4,"timeZoneId":"America/Toronto"},"population":4612191,"adminName2":"","name":"Toronto","country":"CA","countryName":"Canada","adminName1":"Ontario","adminName3":"","services":{"geonames":6167865},"areaLabel":"Ontario, Canada"}
+]
=== added file 'app/data/Storage.qml'
--- app/data/Storage.qml 1970-01-01 00:00:00 +0000
+++ app/data/Storage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2013, 2014, 2015 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Martin Borho <martin@xxxxxxxxx>
+ */
+import QtQuick.LocalStorage 2.0
+import QtQuick 2.4
+
+Item {
+ property var db: null
+
+ function openDB() {
+ if(db !== null) return;
+
+ db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+
+ if (db.version === "") {
+ db.changeVersion("", "0.1",
+ function(tx) {
+ tx.executeSql('CREATE TABLE IF NOT EXISTS Locations(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, date TEXT)');
+ console.log('Database created');
+ });
+ // reopen database with new version number
+ db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+ }
+
+ if(db.version === "0.1") {
+ db.changeVersion("0.1", "0.2",
+ function(tx) {
+ tx.executeSql('CREATE TABLE IF NOT EXISTS settings(key TEXT UNIQUE, value TEXT)');
+ console.log('Settings table added, Database upgraded to v0.2');
+ });
+ // reopen database with new version number
+ db = LocalStorage.openDatabaseSync("com.ubuntu.weather", "", "Default Ubuntu weather app", 100000);
+ }
+
+ if(db.version === "0.2") {
+ db.changeVersion("0.2", "0.3",
+ function(tx) {
+ tx.executeSql('DELETE FROM Locations WHERE 1');
+ console.log('Removed old locations, Database upgraded to v0.3');
+ });
+ }
+
+ if (!settings.migrated) {
+ try { // attempt to read the old settings
+ var oldSettings = {};
+
+ // Load old settings
+ db.readTransaction( function(tx) {
+ var rs = tx.executeSql("SELECT * FROM settings")
+
+ for(var i = 0; i < rs.rows.length; i++) {
+ var row = rs.rows.item(i);
+ oldSettings[row.key] = row.value;
+ }
+ });
+
+ console.debug("Migrating old data:", JSON.stringify(oldSettings))
+
+ var metric = Qt.locale().measurementSystem === Locale.MetricSystem
+
+ // Move to new Settings API
+ settings.migrated = true
+ settings.service = oldSettings["service"] === undefined ? "weatherchannel" : oldSettings["service"]
+
+ if (oldSettings["precip_units"] !== undefined) {
+ settings.precipUnits = oldSettings["precip_units"]
+ } else {
+ settings.precipUnits = metric ? "mm" : "in"
+ }
+
+ if (oldSettings["units"] !== undefined) {
+ settings.tempScale = oldSettings["units"] === "metric" ? "°C" : "°F"
+ settings.units = oldSettings["units"]
+ } else {
+ settings.tempScale = metric ? "°C" : "°F"
+ settings.units = metric ? "metric" : "imperial"
+ }
+
+ if (oldSettings["units"] !== undefined) {
+ // If old wind speed was in "kmh" use "kph" instead
+ settings.windUnits = oldSettings["wind_units"] === "kmh" ? "kph" : oldSettings["wind_units"]
+ } else {
+ settings.windUnits = metric ? "kph" : "mph"
+ }
+
+ /*
+ TODO: uncomment when reboot is ready to replace existing app
+ db.transaction( function(tx) {
+ tx.executeSql("DROP TABLE IF EXISTS settings")
+ });
+ */
+ } catch (e) { // likely table did not exist
+ console.debug("No old data to migrate.")
+ settings.migrated = true
+ }
+ }
+ }
+
+ function insertLocation(data) {
+ openDB();
+ var res;
+
+ db.transaction( function(tx){
+ var r = tx.executeSql('INSERT INTO Locations(data, date) VALUES(?, ?)', [JSON.stringify(data), new Date().getTime()]);
+ res = r.insertId;
+ });
+ return res;
+ }
+
+ function insertLocationAtStart(data) {
+ var res = insertLocation(data);
+ reorder(res, 0);
+ return 0;
+ }
+
+ function updateLocation(dbId, data) {
+ openDB();
+ db.transaction( function(tx){
+ var r = tx.executeSql('UPDATE Locations SET data = ?, date=? WHERE id = ?', [JSON.stringify(data), new Date().getTime(), dbId])
+ });
+ }
+
+ function getLocations(callback) {
+ openDB();
+ db.readTransaction(
+ function(tx){
+ var locations = [];
+ var rs = tx.executeSql('SELECT * FROM Locations');
+ for(var i = 0; i < rs.rows.length; i++) {
+ var row = rs.rows.item(i),
+ locData = JSON.parse(row.data);
+ locData["updated"] = parseInt(row.date, 10);
+ locData["db"] = {id: row.id, updated: new Date(parseInt(row.date, 10))};
+ locations.push(locData);
+ }
+ callback(locations);
+ }
+ );
+ }
+
+ function clearLocation(location_id) {
+ openDB();
+ db.transaction(function(tx){
+ tx.executeSql('DELETE FROM Locations WHERE id = ?', [location_id]);
+ });
+ }
+
+ function clearMultiLocation(locations) {
+ openDB();
+
+ db.transaction(function (tx) {
+ // Remove all the deleted indexes
+ for (var i=0; i < locations.length; i++) {
+ tx.executeSql('DELETE FROM Locations WHERE id=?;', [locations[i]])
+ }
+
+ // Rebuild locations in order
+ var rs = tx.executeSql('SELECT id FROM Locations ORDER BY id ASC')
+
+ for (i=0; i < rs.rows.length; i++) {
+ tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+ [i, rs.rows.item(i).id])
+ }
+ })
+ }
+
+ function clearDB() { // for dev purposes
+ openDB();
+ db.transaction(function(tx){
+ tx.executeSql('DELETE FROM Locations WHERE 1');
+ });
+ }
+
+ function reorder(from, to) {
+ openDB();
+
+ db.transaction(function(tx) {
+ // Track to move put as -1 for now
+ tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+ [-1, from])
+
+ // Shuffle locations inbetween from->to
+ if (from > to) {
+ for (var i = from-1; i >= to; i--) {
+ tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+ [i+1, i])
+ }
+ } else {
+ for (var j = from+1; j <= to; j++) {
+ tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+ [j-1, j])
+ }
+ }
+
+ // Switch moving location to its new position
+ tx.executeSql('UPDATE Locations SET id=? WHERE id=?;',
+ [to, -1])
+ })
+ }
+}
=== added file 'app/data/WeatherApi.js'
--- app/data/WeatherApi.js 1970-01-01 00:00:00 +0000
+++ app/data/WeatherApi.js 2015-06-28 23:49:12 +0000
@@ -0,0 +1,754 @@
+.pragma library
+/*
+ * Copyright (C) 2013 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by: Raúl Yeguas <neokore@xxxxxxxxx>
+ * Martin Borho <martin@xxxxxxxxx>
+ * Andrew Starr-Bochicchio <a.starr.b@xxxxxxxxx>
+ */
+
+/**
+* Version of the response data format.
+* Increase this number to force a refresh.
+*/
+var RESPONSE_DATA_VERSION = 20150404;
+
+/**
+* Helper functions
+*/
+function debug(obj) {
+ print(JSON.stringify(obj))
+}
+//
+function calcFahrenheit(celsius) {
+ return celsius * 1.8 + 32;
+}
+//
+function calcMph(ms) {
+ return ms*2.24;
+}
+//
+function calcInch(mm) {
+ return mm/25.4;
+}
+//
+function calcKph(ms) {
+ return ms*3.6;
+}
+//
+function convertKphToMph(kph) {
+ return kph*0.621;
+}
+//
+function calcWindDir(degrees) {
+ var direction = "?";
+ if(degrees >=0 && degrees <= 30){
+ direction = "N";
+ } else if(degrees >30 && degrees <= 60){
+ direction = "NE";
+ } else if(degrees >60 && degrees <= 120){
+ direction = "E";
+ } else if(degrees >120 && degrees <= 150){
+ direction = "SE";
+ } else if(degrees >150 && degrees <= 210){
+ direction = "S";
+ } else if(degrees >210 && degrees <= 240){
+ direction = "SW";
+ } else if(degrees >240 && degrees <= 300){
+ direction = "W";
+ } else if(degrees >300 && degrees <= 330){
+ direction = "NW";
+ } else if(degrees >330 && degrees <= 360){
+ direction = "N";
+ }
+ return direction;
+}
+
+//
+function getLocationTime(tstamp) {
+ var locTime = new Date(tstamp);
+ return {
+ year: locTime.getUTCFullYear(),
+ month: locTime.getUTCMonth(),
+ date: locTime.getUTCDate(),
+ hours: locTime.getUTCHours(),
+ minutes: locTime.getUTCMinutes()
+ }
+}
+// Serialize a JavaScript object to URL parameters
+// E.g. {param1: value1, param2: value2} to "param1=value¶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: calcKph(data.wind.speed),
+ rain: data.main.rain || ((data.rain) ? data.rain["3h"] : false ) || 0,
+ snow: data.main.snow || ((data.snow) ? data.snow["3h"] : false ) || 0
+ },
+ imperial: {
+ temp: calcFahrenheit(data.main.temp),
+ windSpeed: calcMph(data.wind.speed),
+ rain: calcInch(data.main.rain || ((data.rain) ? data.rain["3h"] : false ) || 0),
+ snow: calcInch(data.main.snow || ((data.snow) ? data.snow["3h"] : false ) ||0)
+ },
+ humidity: data.main.humidity,
+ pressure: data.main.pressure,
+ windDeg: data.wind.deg,
+ windDir: calcWindDir(data.wind.deg),
+ icon: _icon_map[data.weather[0].icon],
+ condition: data.weather[0].main
+ };
+ if(data.id !== undefined) {
+ result["service"] = _serviceName;
+ result["service_id"] = data.id;
+ }
+ return result;
+ }
+ //
+ function _buildDayFormat(date, data) {
+ var result = {
+ date: date,
+ timestamp: data.dt,
+ metric: {
+ tempMin: data.temp.min,
+ tempMax: data.temp.max,
+ windSpeed: calcKph(data.speed),
+ rain: data.rain || 0,
+ snow: data.snow || 0
+ },
+ imperial: {
+ tempMin: calcFahrenheit(data.temp.min),
+ tempMax: calcFahrenheit(data.temp.max),
+ windSpeed: calcMph(data.speed),
+ rain: calcInch(data.rain || 0),
+ snow: calcInch(data.snow || 0)
+ },
+ pressure: data.pressure,
+ humidity: data.humidity,
+ icon: _icon_map[data.weather[0].icon],
+ condition: data.weather[0].main,
+ windDeg: data.deg,
+ windDir: calcWindDir(data.deg),
+ hourly: []
+ }
+ return result;
+ }
+ //
+ function formatResult(data, location) {
+ var tmpResult = {},
+ result = [],
+ day=null,
+ offset=(location.timezone && location.timezone.gmtOffset) ? location.timezone.gmtOffset*60*60*1000: 0,
+ localNow = getLocationTime(new Date().getTime()+offset),
+ todayDate;
+ print("["+location.name+"] "+JSON.stringify(localNow))
+ // add openweathermap id for faster responses
+ if(location.services && !location.services[_serviceName] && data["current"].id) {
+ location.services[_serviceName] = data["current"].id
+ }
+ //
+ data["daily"]["list"].forEach(function(dayData) {
+ var date = getLocationTime(((dayData.dt*1000)-1000)+offset), // minus 1 sec to handle +/-12 TZ
+ day = date.year+"-"+date.month+"-"+date.date;
+ if(!todayDate) {
+ if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {
+ // skip "yesterday"
+ return;
+ }
+ todayDate = date;
+ }
+ tmpResult[day] = _buildDayFormat(date, dayData);
+ })
+ //
+ var today = todayDate.year+"-"+todayDate.month+"-"+todayDate.date
+ tmpResult[today]["current"] = _buildDataPoint(todayDate, data["current"]);
+ if(data["forecast"] !== undefined) {
+ data["forecast"]["list"].forEach(function(hourData) {
+ var dateData = getLocationTime((hourData.dt*1000)+offset),
+ day = dateData.year+"-"+dateData.month+"-"+dateData.date;
+ if(tmpResult[day]) {
+ tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));
+ }
+ })
+ }
+ //
+ for(var d in tmpResult) {
+ result.push(tmpResult[d]);
+ }
+ return result;
+ }
+ //
+ function _getUrls(params) {
+ var urls = {
+ current: "",
+ daily: "",
+ forecast: ""
+ },
+ latLongParams = "&lat="+encodeURIComponent(params.location.coord.lat)
+ + "&lon="+encodeURIComponent(params.location.coord.lon);
+ if(params.location.services && params.location.services[_serviceName]) {
+ urls.current = _baseUrl + "weather?units="+params.units+"&id="+params.location.services[_serviceName];
+ urls.daily = _baseUrl + "forecast/daily?id="+params.location.services[_serviceName]+"&cnt=10&units="+params.units
+ urls.forecast = _baseUrl + "forecast?id="+params.location.services[_serviceName]+"&units="+params.units
+
+ } else if (params.location.coord) {
+ urls.current = _baseUrl + "weather?units="+params.units+latLongParams;
+ urls.daily = _baseUrl+"forecast/daily?cnt=10&units="+params.units+latLongParams;
+ urls.forecast = _baseUrl+"forecast?units="+params.units+latLongParams;
+ }
+ return urls;
+ }
+ //
+ return {
+ //
+ getData: function(params, apiCaller, onSuccess, onError) {
+ var urls = _getUrls(params),
+ handlerMap = {
+ current: { type: "current",url: urls.current},
+ daily: { type: "daily",url: urls.daily},
+ forecast: { type: "forecast", url: urls.forecast}},
+ response = {
+ location: params.location,
+ db: (params.db) ? params.db : null,
+ format: RESPONSE_DATA_VERSION
+ },
+ respData = {},
+ addDataToResponse = (function(request, data) {
+ var formattedResult;
+ respData[request.type] = data;
+ if(respData["current"] !== undefined
+ && respData["forecast"] !== undefined
+ && respData["daily"] !== undefined) {
+ response["data"] = formatResult(respData, params.location)
+ onSuccess(response);
+ }
+ }),
+ onErrorHandler = (function(err) {
+ onError(err);
+ }),
+ retryHandler = (function(err) {
+ console.log("retry of "+err.request.url);
+ var retryFunc = handlerMap[err.request.type];
+ apiCaller(retryFunc, addDataToResponse, onErrorHandler);
+ });
+ //
+ apiCaller(handlerMap.current, addDataToResponse, retryHandler);
+ apiCaller(handlerMap.forecast, addDataToResponse, retryHandler);
+ apiCaller(handlerMap.daily, addDataToResponse, retryHandler);
+ }
+ }
+
+})();
+
+var WeatherChannelApi = (function() {
+ /**
+ provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
+ */
+ var _baseUrl = "http://wxdata.weather.com/wxdata/";
+ //
+ var _serviceName = "weatherchannel";
+ //
+ // see http://s.imwx.com/v.20131006.223722/img/wxicon/72/([0-9]+).png
+ var _iconMap = {
+ "0": "thunder", // ??
+ "1": "thunder", // ??
+ "2": "thunder", // ??
+ "3": "thunder", // ??
+ "4": "thunder", //T-Storms
+ "5": "snow_rain", //Rain / Snow
+ "6": "snow_rain", // ??
+ "7": "snow_rain", //Wintry Mix
+ "8": "scattered", //Freezing Drizzle
+ "9": "scattered", //Drizzle
+ "10": "rain", // ??
+ "11": "rain", //Showers
+ "12": "rain", //Rain
+ "13": "snow_shower", // ??
+ "14": "snow_shower", //Snow shower/Light snow
+ "15": "snow_shower", //
+ "16": "snow_shower", //Snow
+ "17": "thunder", // Hail??
+ "18": "snow_rain", // Rain / Snow ??
+ "19": "fog", //Fog ??
+ "20": "fog", //Fog
+ "21": "fog", //Haze
+ "22": "fog", // ??
+ "23": "fog", // Wind ??
+ "24": "overcast", //Partly Cloudy / Wind
+ "25": "overcast", // ??
+ "26": "overcast",//Cloudy
+ "27": "cloud_moon",//Mostly Cloudy
+ "28": "cloud_sun", //Mostly Cloudy
+ "29": "cloud_moon", //Partly Cloudy
+ "30": "cloud_sun", //Partly Cloudy
+ "31": "moon", //Clear
+ "32": "sun", //Sunny
+ "33": "cloud_moon", //Mostly Clear
+ "34": "cloud_sun", //Mostly Sunny
+ "35": "snow_rain", // ??
+ "36": "sun", //Sunny
+ "37": "thunder", //Isolated T-Storms
+ "38": "thunder", //Scattered T-Storms
+ "39": "scattered", //Scattered Showers
+ "40": "rain", // ??
+ "41": "snow", //Scattered Snow Showers
+ "42": "snow_shower", // ??
+ "43": "snow_shower", // ??
+ "44": "fog", // ??
+ "45": "scattered", // ??
+ "46": "snow_shower", //Snow Showers Early
+ "47": "thunder" //Isolated T-Storms
+ };
+ //
+ function _buildDataPoint(date, dataObj) {
+ var data = dataObj["Observation"] || dataObj,
+ result = {
+ timestamp: data.date || data.dateTime,
+ date: date,
+ metric: {
+ temp: data.temp,
+ tempFeels: data.feelsLike,
+ windSpeed: data.wSpeed
+ },
+ imperial: {
+ temp: calcFahrenheit(data.temp),
+ tempFeels: calcFahrenheit(data.feelsLike),
+ windSpeed: convertKphToMph(data.wSpeed)
+ },
+ precipType: (data.precip_type !== undefined) ? data.precip_type : null,
+ propPrecip: (data.pop !== undefined) ? data.pop : null,
+ humidity: data.humid,
+ pressure: data.pressure,
+ windDeg: data.wDir,
+ windDir: data.wDirText,
+ icon: _iconMap[(data.wxIcon||data.icon)],
+ condition: data.text || data.wDesc,
+ uv: data.uv
+ };
+ if(_iconMap[data.wxIcon||data.icon] === undefined) {
+ print("ICON MISSING POINT: "+(data.wxIcon||data.icon)+" "+result.condition)
+ }
+ return result;
+ }
+ //
+ function _buildDayFormat(date, data, now) {
+ var partData = (now > data.validDate || data.day === undefined) ? data.night : data.day,
+ result = {
+ date: date,
+ timestamp: data.validDate,
+ metric: {
+ tempMin: data.minTemp,
+ tempMax: data.maxTemp,
+ windSpeed: partData.wSpeed
+ },
+ imperial: {
+ tempMin: calcFahrenheit(data.minTemp),
+ tempMax: calcFahrenheit(data.maxTemp !== undefined ? data.maxTemp : data.minTemp),
+ windSpeed: convertKphToMph(partData.wSpeed)
+ },
+ precipType: partData.precip_type,
+ propPrecip: partData.pop,
+ pressure: null,
+ humidity: partData.humid,
+ icon: _iconMap[partData.icon],
+ condition: partData.phrase,
+ windDeg: partData.wDir,
+ windDir: partData.wDirText,
+ uv: partData.uv,
+ hourly: []
+ }
+ if(_iconMap[partData.icon] === undefined) {
+ print("ICON MISSING DAY: "+partData.icon+" "+result.condition)
+ }
+ return result;
+ }
+ //
+ function formatResult(combinedData, location) {
+ var tmpResult = {}, result = [],
+ day=null, todayDate,
+ offset=(location.timezone && location.timezone.gmtOffset) ? location.timezone.gmtOffset*60*60*1000: 0,
+ now = new Date().getTime(),
+ nowMs = parseInt(now/1000),
+ localNow = getLocationTime(now+offset),
+ data = {
+ "location": combinedData[0]["Location"],
+ "daily": combinedData[0]["DailyForecasts"],
+ "forecast": combinedData[0]["HourlyForecasts"],
+ "current": combinedData[0]["StandardObservation"],
+ };
+ print("["+location.name+"] "+JSON.stringify(localNow));
+ // add openweathermap id for faster responses
+ if(location.services && !location.services[_serviceName] && data["location"].key) {
+ location.services[_serviceName] = data["location"].key
+ }
+ // only 5 days of forecast for TWC
+ for(var x=0;x<5;x++) {
+ var dayData = data["daily"][x],
+ date = getLocationTime(((dayData.validDate*1000)-1000)+offset); // minus 1 sec to handle +/-12 TZ
+ day = date.year+"-"+date.month+"-"+date.date;
+ if(!todayDate) {
+ if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {
+ // skip "yesterday"
+ continue;
+ }
+ todayDate = date;
+ }
+ tmpResult[day] = _buildDayFormat(date, dayData, nowMs);
+ }
+ //
+ if(data["forecast"] !== undefined) {
+ data["forecast"].forEach(function(hourData) {
+ var dateData = getLocationTime((hourData.dateTime*1000)+offset),
+ day = dateData.year+"-"+dateData.month+"-"+dateData.date;
+ if(tmpResult[day]) {
+ tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));
+ }
+ })
+ }
+ //
+ if(data["current"]) {
+ var today = todayDate.year+"-"+todayDate.month+"-"+todayDate.date;
+ tmpResult[today]["current"] = _buildDataPoint(todayDate, data["current"]);
+ // if the icon is missing, use the first from the hourly forecast
+ if(!tmpResult[today]["current"].icon && tmpResult[today]["hourly"] && tmpResult[today]["hourly"].length > 0) {
+ tmpResult[today]["current"].icon = tmpResult[today]["hourly"][0].icon;
+ }
+ // if condition text is missing, use the condition from the first hourly forecast
+ if((tmpResult[today]["current"].condition === "-" || tmpResult[today]["current"].condition === undefined)
+ && (tmpResult[today]["hourly"] && tmpResult[today]["hourly"].length > 0)) {
+ tmpResult[today]["current"].condition = tmpResult[today]["hourly"][0].condition;
+ }
+ }
+ //
+ for(var d in tmpResult) {
+ result.push(tmpResult[d]);
+ }
+ return result;
+ }
+ //
+ function _getUrl(params) {
+ var url, serviceId,
+ baseParams = {
+ key: params.api_key,
+ units: (params.units === "metric") ? "m" : "e",
+ locale: Qt.locale().name,
+ hours: "48",
+ },
+ commands = {
+ "mobileaggregation": "mobile/mobagg/",
+ };
+ if(params.location.services && params.location.services[_serviceName]) {
+ serviceId = encodeURIComponent(params.location.services[_serviceName]);
+ url = _baseUrl+commands["mobileaggregation"]+serviceId+".js?"+parameterize(baseParams);
+ } else if (params.location.coord) {
+ var coord = {lat: params.location.coord.lat, lng: params.location.coord.lon};
+ url = _baseUrl+commands["mobileaggregation"]+"get.js?"+parameterize(baseParams)+"&"+
+ parameterize(coord);
+ }
+ return url;
+ }
+ //
+ return {
+ getData: function(params, apiCaller, onSuccess, onError) {
+ var url = _getUrl(params),
+ handlerMap = {
+ all: { type: "all", url: url}
+ },
+ response = {
+ location: params.location,
+ db: (params.db) ? params.db : null,
+ format: RESPONSE_DATA_VERSION
+ },
+ addDataToResponse = (function(request, data) {
+ var formattedResult;
+ response["data"] = formatResult(data, params.location);
+ onSuccess(response);
+ }),
+ onErrorHandler = (function(err) {
+ onError(err);
+ });
+ apiCaller(handlerMap.all, addDataToResponse, onErrorHandler);
+ }
+ }
+})();
+
+var WeatherApi = (function(_services) {
+ /**
+ proxy for requesting weather apis, the passed _services are providing the respective api endpoints
+ and formatters to build a uniform response object
+ */
+ function _getService(name) {
+ if(_services[name] !== undefined) {
+ return _services[name];
+ }
+ return _services["weatherchannel"];
+ }
+ //
+ function _sendRequest(request, onSuccess, onError) {
+ var xmlHttp = new XMLHttpRequest();
+ if (xmlHttp) {
+ console.log("Sent request URL: " + request.url);
+ xmlHttp.open('GET', request.url, true);
+ xmlHttp.onreadystatechange = function () {
+ try {
+ if (xmlHttp.readyState == 4) {
+ if(xmlHttp.status === 200) {
+ if(xmlHttp.responseXML) {
+ onSuccess(request, xmlHttp.responseXML.documentElement);
+ } else {
+ var json = JSON.parse(xmlHttp.responseText);
+ onSuccess(request,json);
+ }
+ } else {
+ onError({
+ msg: "wrong response http code, got "+xmlHttp.status,
+ request: request
+ });
+ }
+ }
+ } catch (e) {
+ print("Exception: "+e)
+ onError({msg: "wrong response data format", request: request});
+ }
+ };
+ xmlHttp.send(null);
+ }
+ }
+ //
+ return {
+ //
+ geoLookup: function(params, onSuccess, onError) {
+ var service = _getService('geoip'),
+ geoNameService = _getService('geonames'),
+ lookupHandler = function(data) {
+ print("Geolookup: "+JSON.stringify(data))
+ geoNameService.search("point", {coords:data}, _sendRequest, onSuccess, onError);
+ };
+ service.getLatLong(params, _sendRequest, lookupHandler, onError)
+ },
+ //
+ search: function(mode, params, onSuccess, onError) {
+ var service = _getService('geonames');
+ service.search(mode, params, _sendRequest, onSuccess, onError);
+ },
+ //
+ getLocationData: function(params, onSuccess, onError) {
+ var service = _getService(params.service);
+ service.getData(params, _sendRequest, onSuccess, onError);
+ },
+ }
+})({
+ "openweathermap": OpenWeatherMapApi,
+ "weatherchannel": WeatherChannelApi,
+ "geonames": GeonamesApi,
+ "geoip": GeoipApi
+});
+
+var sendRequest = function(message, responseCallback) {
+ // handles the response data
+ var finished = function(result) {
+ // print result to get data for test json files
+ // print(JSON.stringify(result));
+ //WorkerScript.sendMessage({
+ responseCallback({
+ action: message.action,
+ result: result
+ })
+ }
+ // handles errors
+ var onError = function(err) {
+ console.log(JSON.stringify(err, null, true));
+ //WorkerScript.sendMessage({ 'error': err})
+ responseCallback({ 'error': err})
+ }
+ // keep order of locations, sort results
+ var sortDataResults = function(locA, locB) {
+ return locA.db.id - locB.db.id;
+ }
+ // perform the api calls
+ if(message.action === "searchByName") {
+ WeatherApi.search("name", message.params, finished, onError);
+ } else if(message.action === "searchByPoint") {
+ WeatherApi.search("point", message.params, finished, onError);
+ } else if(message.action === "getGeoIp") {
+ WeatherApi.geoLookup(message.params, finished, onError);
+ } else if(message.action === "updateData") {
+ var locLength = message.params.locations.length,
+ locUpdated = 0,
+ result = [],
+ now = new Date().getTime();
+ if(locLength > 0) {
+ message.params.locations.forEach(function(loc) {
+ var updatedHnd = function (newData, cached) {
+ locUpdated += 1;
+ if(cached === true) {
+ newData["save"] = false;
+ } else {
+ newData["save"] = true;
+ newData["updated"] = new Date().getTime();
+ }
+ result.push(newData);
+ if(locUpdated === locLength) {
+ result.sort(sortDataResults);
+ finished(result);
+ }
+ },
+ params = {
+ location:loc.location,
+ db: loc.db,
+ units: 'metric',
+ service: message.params.service,
+ api_key: message.params.api_key,
+ interval: message.params.interval
+ },
+ secsFromLastFetch = (now-loc.updated)/1000;
+ if( message.params.force===true || loc.format !== RESPONSE_DATA_VERSION || secsFromLastFetch > params.interval){
+ // data older than 30min, location is new or data format is deprecated
+ WeatherApi.getLocationData(params, updatedHnd, onError);
+ } else {
+ console.log("["+loc.location.name+"] returning cached data, time from last fetch: "+secsFromLastFetch)
+ updatedHnd(loc, true);
+ }
+ })
+ } else {
+ finished(result);
+ }
+ }
+}
=== added file 'app/data/key.js'
--- app/data/key.js 1970-01-01 00:00:00 +0000
+++ app/data/key.js 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+var twcKey = "";
=== added directory 'app/graphics'
=== added file 'app/graphics/Big-Rain.png'
Binary files app/graphics/Big-Rain.png 1970-01-01 00:00:00 +0000 and app/graphics/Big-Rain.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/CMakeLists.txt'
--- app/graphics/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/graphics/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB IMAGE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.png *.svg)
+
+add_custom_target(ubuntu-weather-app_graphics_IMAGEFiles ALL SOURCES ${IMAGE_FILES})
+
+install(FILES ${IMAGE_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/graphics)
=== added file 'app/graphics/Cloudy-Circles.png'
Binary files app/graphics/Cloudy-Circles.png 1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Circles.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy-Night.png'
Binary files app/graphics/Cloudy-Night.png 1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Night.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy-Snow.png'
Binary files app/graphics/Cloudy-Snow.png 1970-01-01 00:00:00 +0000 and app/graphics/Cloudy-Snow.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Cloudy.png'
Binary files app/graphics/Cloudy.png 1970-01-01 00:00:00 +0000 and app/graphics/Cloudy.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Fog.png'
Binary files app/graphics/Fog.png 1970-01-01 00:00:00 +0000 and app/graphics/Fog.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Raindrop.png'
Binary files app/graphics/Raindrop.png 1970-01-01 00:00:00 +0000 and app/graphics/Raindrop.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Showers.png'
Binary files app/graphics/Showers.png 1970-01-01 00:00:00 +0000 and app/graphics/Showers.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Starry-Night.png'
Binary files app/graphics/Starry-Night.png 1970-01-01 00:00:00 +0000 and app/graphics/Starry-Night.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Stormy.png'
Binary files app/graphics/Stormy.png 1970-01-01 00:00:00 +0000 and app/graphics/Stormy.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Sunny.png'
Binary files app/graphics/Sunny.png 1970-01-01 00:00:00 +0000 and app/graphics/Sunny.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/Windy-n-Snow.png'
Binary files app/graphics/Windy-n-Snow.png 1970-01-01 00:00:00 +0000 and app/graphics/Windy-n-Snow.png 2015-06-28 23:49:12 +0000 differ
=== added file 'app/graphics/extended-information_chance-of-rain.svg'
--- app/graphics/extended-information_chance-of-rain.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_chance-of-rain.svg 2015-06-28 23:49:12 +0000
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="90"
+ id="svg6138"
+ version="1.1"
+ inkscape:version="0.91pre2 r"
+ viewBox="0 0 90 90.000001"
+ sodipodi:docname="weather-chance-of-rain02.svg">
+ <defs
+ id="defs6140" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.0931702"
+ inkscape:cx="112.1011"
+ inkscape:cy="19.378885"
+ inkscape:document-units="px"
+ inkscape:current-layer="g6442"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:bbox-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-others="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ showguides="false"
+ inkscape:guide-bbox="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid6700"
+ empspacing="6" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="90,87"
+ id="guide4064" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="89,84"
+ id="guide4066" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="3,69"
+ id="guide4068" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="6,60"
+ id="guide4070" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="87,58"
+ id="guide4072" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="84,47"
+ id="guide4074" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="77,3"
+ id="guide4076" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="81,6"
+ id="guide4078" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="81,25"
+ id="guide4080" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="78,9"
+ id="guide4082" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="79,81"
+ id="guide4084" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="9,39"
+ id="guide4086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6143">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-283.57144,-358.79068)">
+ <g
+ transform="translate(169.57144,223.42822)"
+ id="g5937"
+ inkscape:export-filename="envelope02.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <g
+ id="g6442"
+ transform="translate(-753.45981,336.4283)">
+ <rect
+ style="fill:none;stroke:none"
+ id="rect1687"
+ width="90"
+ height="90"
+ x="1037.0312"
+ y="22.362379" />
+ <path
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 44.929688 12.066406 C 34.037288 12.066406 24.849688 14.943458 17.367188 20.697266 C 9.7896875 26.523909 6 33.624202 6 42 C 7.7011 40.603306 9.6530686 39.474739 11.855469 38.615234 C 14.075869 37.773637 16.4573 37.353516 19 37.353516 C 21.5427 37.353516 23.914587 37.773637 26.117188 38.615234 C 28.337486 39.474739 30.2989 40.603306 32 42 C 33.7011 40.603306 35.653069 39.474739 37.855469 38.615234 C 40.075769 37.773637 42.4573 37.353516 45 37.353516 C 47.5427 37.353516 49.914586 37.773637 52.117188 38.615234 C 54.337487 39.474739 56.2989 40.603306 58 42 L 58.654297 42 C 60.235137 40.784631 62.009869 39.776665 64 39 C 66.2203 38.158403 68.601831 37.738281 71.144531 37.738281 C 73.687231 37.738281 76.059119 38.158403 78.261719 39 C 80.268226 39.776743 82.052173 40.784465 83.634766 42 L 84 42 C 84 33.624202 80.210313 26.523909 72.632812 20.697266 C 65.055613 14.943458 55.822287 12.066406 44.929688 12.066406 z "
+ transform="translate(1037.0313,22.36238)"
+ id="path4181" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:end;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 1079.0312,28.363281 0,66.335938 0.01,0.09766 c 0.015,0.226687 0.022,0.780446 0.022,1.509766 0,1.918939 -0.4328,2.763898 -1.0039,3.289062 l -0.01,0.0059 -0.01,0.0078 c -0.6357,0.592743 -1.5903,1.007813 -3.4297,1.007813 -1.8313,0 -2.8017,-0.41364 -3.461,-1.015626 -0.5541,-0.519673 -0.9882,-1.37472 -0.9882,-3.294921 0,-0.72932 0.01,-1.281931 0.021,-1.501953 l -5.9863,-0.410157 c -0.041,0.598238 -0.035,1.18279 -0.035,1.91211 0,2.983063 0.8627,5.791367 2.9003,7.691407 l 0.01,0.008 0.01,0.006 c 1.9716,1.8129 4.6659,2.60547 7.5332,2.60547 2.8642,0 5.5555,-0.79304 7.5136,-2.61524 2.0532,-1.89396 2.9356,-4.70919 2.9356,-7.695307 0,-0.72932 0.01,-1.312724 -0.033,-1.904297 l 0.01,0.197265 0,-67.236328 -6,1 z"
+ id="path4225"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccscccscsccscccscsccccc" />
+ </g>
+ </g>
+</svg>
=== added file 'app/graphics/extended-information_humidity.svg'
--- app/graphics/extended-information_humidity.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_humidity.svg 2015-06-28 23:49:12 +0000
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="90"
+ id="svg6138"
+ version="1.1"
+ inkscape:version="0.91pre2 r"
+ viewBox="0 0 90 90.000001"
+ sodipodi:docname="weather-chance-of-rain.svg">
+ <defs
+ id="defs6140" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.0745362"
+ inkscape:cx="90.550182"
+ inkscape:cy="25.205316"
+ inkscape:document-units="px"
+ inkscape:current-layer="g6442"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:snap-global="false"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:bbox-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-others="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ showguides="false"
+ inkscape:guide-bbox="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid6700"
+ empspacing="6" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="90,87"
+ id="guide4064" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="89,84"
+ id="guide4066" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="3,69"
+ id="guide4068" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="6,60"
+ id="guide4070" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="87,58"
+ id="guide4072" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="84,47"
+ id="guide4074" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="77,3"
+ id="guide4076" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="81,6"
+ id="guide4078" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="81,25"
+ id="guide4080" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="78,9"
+ id="guide4082" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="79,81"
+ id="guide4084" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="9,39"
+ id="guide4086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6143">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-283.57144,-358.79068)">
+ <g
+ transform="translate(169.57144,223.42822)"
+ id="g5937"
+ inkscape:export-filename="envelope02.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <g
+ id="g6442"
+ transform="translate(-753.45981,336.4283)">
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="M 45.046875 3 C 32.915975 27.594868 24.552722 55.021065 23.982422 56.908203 C 23.901522 57.129789 23.841578 57.361086 23.767578 57.585938 C 23.036878 59.796557 22.623047 62.152027 22.623047 64.607422 C 22.623047 76.973464 32.652638 86.998047 45.023438 86.998047 C 57.372742 86.972595 67.376953 76.959723 67.376953 64.609375 C 67.376953 62.155598 66.975594 59.797236 66.246094 57.587891 C 66.246094 57.587891 58.008075 29.900931 45.046875 3.0390625 L 45.046875 3.03125 L 45.046875 3.0195312 L 45.046875 3.0058594 L 45.046875 3 z M 44.996094 17.728516 C 54.458094 39.650002 60.503906 59.296875 60.503906 59.296875 A 5.9907304 5.9907304 0 0 0 60.558594 59.466797 C 61.095294 61.092212 61.386719 62.80886 61.386719 64.609375 C 61.386719 73.725917 54.126659 80.989079 45.005859 81.005859 C 35.879459 80.996009 28.613281 73.728301 28.613281 64.607422 C 28.613281 62.825026 28.910878 61.113375 29.455078 59.466797 A 5.9907304 5.9907304 0 0 0 29.457031 59.458984 C 29.615031 58.978962 29.665675 58.806709 29.609375 58.960938 A 5.9907304 5.9907304 0 0 0 29.716797 58.638672 C 30.156997 57.182234 36.199294 37.969649 44.996094 17.728516 z "
+ transform="translate(1037.0313,22.36238)"
+ id="path4253" />
+ <rect
+ style="fill:none;stroke:none"
+ id="rect1687"
+ width="90"
+ height="90"
+ x="1037.0312"
+ y="22.362379" />
+ </g>
+ </g>
+</svg>
=== added file 'app/graphics/extended-information_uv-level.svg'
--- app/graphics/extended-information_uv-level.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_uv-level.svg 2015-06-28 23:49:12 +0000
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="90"
+ id="svg6138"
+ version="1.1"
+ inkscape:version="0.91pre2 r"
+ viewBox="0 0 90 90.000001"
+ sodipodi:docname="weather-uv-level.svg">
+ <defs
+ id="defs6140">
+ <linearGradient
+ id="linearGradient3899">
+ <stop
+ id="stop4292"
+ offset="0"
+ style="stop-color:#fbf7e3;stop-opacity:1" />
+ <stop
+ id="stop4294"
+ offset="1"
+ style="stop-color:#c6b386;stop-opacity:1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.9580785"
+ inkscape:cx="32.979067"
+ inkscape:cy="45.789944"
+ inkscape:document-units="px"
+ inkscape:current-layer="g6442"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:bbox-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-others="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid6700"
+ empspacing="6" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="90,87"
+ id="guide4064" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="89,84"
+ id="guide4066" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="3,69"
+ id="guide4068" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="6,60"
+ id="guide4070" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="87,58"
+ id="guide4072" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="84,47"
+ id="guide4074" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="77,3"
+ id="guide4076" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="81,6"
+ id="guide4078" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="81,25"
+ id="guide4080" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="78,9"
+ id="guide4082" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="79,81"
+ id="guide4084" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="9,39"
+ id="guide4086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6143">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-283.57144,-358.79068)">
+ <g
+ transform="translate(169.57144,223.42822)"
+ id="g5937"
+ inkscape:export-filename="envelope02.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <g
+ id="g6442"
+ transform="translate(-753.45981,336.4283)">
+ <rect
+ style="fill:none;stroke:none"
+ id="rect1687"
+ width="90"
+ height="90"
+ x="1037.0312"
+ y="22.362379" />
+ <path
+ style="color:#000000;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 1054.1015,75.51863 a 33.872092,33.872092 0 0 0 0.5547,2.339844 33.872092,33.872092 0 0 1 -0.5547,-2.339844 z"
+ id="path4162" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.25;marker:none;enable-background:accumulate"
+ d="M 45 12 A 33.00005 33.00005 0 0 0 12 45 A 33.00005 33.00005 0 0 0 45 78 A 33.00005 33.00005 0 0 0 78 45 A 33.00005 33.00005 0 0 0 45 12 z M 45 18 A 27 27 0 0 1 72 45 A 27 27 0 0 1 45 72 A 27 27 0 0 1 18 45 A 27 27 0 0 1 45 18 z "
+ transform="translate(1037.0313,22.36238)"
+ id="path4189" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="-37.504247"
+ inkscape:transform-center-x="-0.0125"
+ inkscape:connector-curvature="0"
+ id="path2194-5"
+ d="m 1088.2477,35.36157 c 0,0 -2.5383,-5.872205 -6.1954,-11.007637 0,0 0,0 -0.01,0.0017 0,0 0,0.0017 -0.01,0.0024 0,0 -0.01,0.002 -0.01,0.0026 0,0 -0.01,0.0017 -0.01,0.0017 -3.7738,5.416839 -6.1725,11 -6.1725,11 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1103.4153,42.757091 c 0,0 0.7378,-6.354629 0.1384,-12.630594 0,0 0,0 -0.01,-0.0035 0,0 -9e-4,0.0015 -0.01,-0.0029 0,0 -0.01,-0.0033 -0.01,-0.0027 0,0 -0.01,-0.0035 -0.01,-0.0035 -5.9767,2.80422 -10.8456,6.440029 -10.8456,6.440029 z"
+ id="path4192"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-16.197006"
+ inkscape:transform-center-y="-30.926886"
+ sodipodi:nodetypes="ccsssccc" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="-16.173909"
+ inkscape:transform-center-x="-30.9307"
+ inkscape:connector-curvature="0"
+ id="path4194"
+ d="m 1112.853,56.745599 c 0,0 3.8163,-5.13437 6.4352,-10.869215 0,0 0,0 -0.01,-0.008 0,0 0,8.49e-4 -0.01,-0.0075 0,0 -0.01,-0.0079 -0.01,-0.0073 0,0 -0.01,-0.008 -0.01,-0.008 -6.578,-0.559824 -12.6125,0.154429 -12.6125,0.154429 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1114.0321,73.578853 c 0,0 5.8722,-2.538345 11.0076,-6.195417 0,0 0,0 0,-0.01193 0,0 -5e-4,7.35e-4 -0.01,-0.0115 0,0 0,-0.01184 0,-0.01132 0,0 0,-0.01193 0,-0.01193 -5.4168,-3.773822 -11,-6.172511 -11,-6.172511 z"
+ id="path4196"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-37.50345"
+ inkscape:transform-center-y="0.0091698086"
+ sodipodi:nodetypes="ccsssccc" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="16.194334"
+ inkscape:transform-center-x="-30.9356"
+ inkscape:connector-curvature="0"
+ id="path4198"
+ d="m 1106.6366,88.746428 c 0,0 6.3546,0.737829 12.6305,0.138412 0,0 0,0 0.01,-0.01033 0,0 -8e-4,3.87e-4 0,-0.01496 0,0 0.01,-0.01025 0.01,-0.0098 0,0 0.01,-0.01033 0.01,-0.01033 -2.8042,-5.976626 -6.44,-10.845552 -6.44,-10.845552 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1092.6481,98.184183 c 0,0 5.1343,3.816277 10.8691,6.435117 0,0 0,0 0.014,-0.004 0,0 -8e-4,-7e-5 0.01,-0.013 0,0 0.014,-0.004 0.014,-0.003 0,0 0.014,-0.004 0.014,-0.004 0.5598,-6.578013 -0.1544,-12.612527 -0.1544,-12.612527 z"
+ id="path4200"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-16.184519"
+ inkscape:transform-center-y="30.938658"
+ sodipodi:nodetypes="ccsssccc" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="37.507651"
+ inkscape:transform-center-x="0.003949878"
+ inkscape:connector-curvature="0"
+ id="path4202"
+ d="m 1075.8148,99.363269 c 0,0 2.5383,5.872141 6.1954,11.007521 0,0 0,0 0.014,0.004 0,0 -7e-4,-4.6e-4 0.015,-0.006 0,0 0.014,0.004 0.014,0.004 0,0 0.014,0.004 0.014,0.004 3.7738,-5.41683 6.1726,-10.999969 6.1726,-10.999969 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1060.6472,91.967738 c 0,0 -0.7378,6.354573 -0.1384,12.630492 0,0 0,0 0.01,0.0105 0,0 -3e-4,-7.4e-4 0.016,0.002 0,0 0.01,0.0105 0.01,0.0105 0,0 0.01,0.0105 0.01,0.0105 5.9766,-2.80422 10.8456,-6.439956 10.8456,-6.439956 z"
+ id="path4204"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="16.194006"
+ inkscape:transform-center-y="30.937355"
+ sodipodi:nodetypes="ccsssccc" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="16.184506"
+ inkscape:transform-center-x="30.94415"
+ inkscape:connector-curvature="0"
+ id="path4206"
+ d="m 1051.2094,77.97922 c 0,0 -3.8162,5.134322 -6.4351,10.869127 0,0 0,0 0,0.01409 0,0 2e-4,-7.9e-4 0.013,0.0097 0,0 0,0.01409 0,0.01409 0,0 0,0.01409 0,0.01409 6.578,0.559774 12.6126,-0.154365 12.6126,-0.154365 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1050.0303,61.145908 c 0,0 -5.8721,2.538353 -11.0075,6.19539 0,0 0,0 -0.01,0.0122 0,0 5e-4,-5.85e-4 0.01,0.0149 0,0 -0.01,0.0122 -0.01,0.0122 0,0 -0.01,0.0122 -0.01,0.0122 5.4168,3.773778 11,6.172616 11,6.172616 z"
+ id="path4208"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="37.5147"
+ inkscape:transform-center-y="-0.0067180911"
+ sodipodi:nodetypes="ccsssccc" />
+ <path
+ sodipodi:nodetypes="ccsssccc"
+ inkscape:transform-center-y="-16.199665"
+ inkscape:transform-center-x="30.94315"
+ inkscape:connector-curvature="0"
+ id="path4210"
+ d="m 1057.4258,45.978282 c 0,0 -6.3545,-0.737771 -12.6304,-0.138384 0,0 0,0 -0.015,0.0056 0,0 7e-4,-2.57e-4 0,0.0179 0,0 -0.015,0.0056 -0.015,0.0056 0,0 -0.015,0.0056 -0.015,0.0056 2.8042,5.976587 6.44,10.845642 6.44,10.845642 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate" />
+ <path
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
+ d="m 1071.4144,36.540483 c 0,0 -5.1343,-3.816179 -10.8691,-6.435044 0,0 0,0 -0.016,-0.0026 0,0 7e-4,1.27e-4 -0.01,0.0155 0,0 -0.016,-0.0026 -0.016,-0.0026 0,0 -0.016,-0.0026 -0.016,-0.0026 -0.5598,6.577976 0.1544,12.612601 0.1544,12.612601 z"
+ id="path4212"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="16.187519"
+ inkscape:transform-center-y="-30.94809"
+ sodipodi:nodetypes="ccsssccc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:31.51123047px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Medium';text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="1103.2705"
+ y="79.316757"
+ id="text4214"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan4216"
+ x="1103.2705"
+ y="79.316757">UV</tspan></text>
+ </g>
+ </g>
+</svg>
=== added file 'app/graphics/extended-information_wind.svg'
--- app/graphics/extended-information_wind.svg 1970-01-01 00:00:00 +0000
+++ app/graphics/extended-information_wind.svg 2015-06-28 23:49:12 +0000
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="90"
+ id="svg6138"
+ version="1.1"
+ inkscape:version="0.91pre2 r"
+ viewBox="0 0 90 90.000001"
+ sodipodi:docname="weather-chance-of-wind02.svg">
+ <defs
+ id="defs6140" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.2596289"
+ inkscape:cx="81.435648"
+ inkscape:cy="24.842587"
+ inkscape:document-units="px"
+ inkscape:current-layer="g6442"
+ showgrid="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:bbox-nodes="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-others="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ showguides="false"
+ inkscape:guide-bbox="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid6700"
+ empspacing="6" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="90,87"
+ id="guide4064" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="89,84"
+ id="guide4066" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="3,69"
+ id="guide4068" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="6,60"
+ id="guide4070" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="87,58"
+ id="guide4072" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="84,47"
+ id="guide4074" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="77,3"
+ id="guide4076" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="81,6"
+ id="guide4078" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="81,25"
+ id="guide4080" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="78,9"
+ id="guide4082" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="79,81"
+ id="guide4084" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="9,39"
+ id="guide4086" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata6143">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-283.57144,-358.79068)">
+ <g
+ transform="translate(169.57144,223.42822)"
+ id="g5937"
+ inkscape:export-filename="envelope02.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90" />
+ <g
+ id="g6442"
+ transform="translate(-753.45981,336.4283)">
+ <rect
+ style="fill:none;stroke:none"
+ id="rect1687"
+ width="90"
+ height="90"
+ x="1037.0312"
+ y="22.362379" />
+ <path
+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:end;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 75.279297,28.75 c -1.0334,0 -2.070116,0.17314 -3.103516,0.517578 -1.0332,0.301421 -1.957037,0.799407 -2.773437,1.488281 -0.7749,0.645859 -1.426106,1.497766 -1.941406,2.574219 -0.193801,0.386934 -0.311535,0.83777 -0.427735,1.285156 l 4.048828,2.333985 c 0.066,-0.47516 0.09082,-0.97049 0.265625,-1.34961 0.2583,-0.516687 0.599203,-0.93497 1.033203,-1.236328 0.4327,-0.344501 0.877363,-0.575907 1.351563,-0.705078 0.5166,-0.172251 1.013028,-0.253906 1.486328,-0.253906 0.9909,0 1.914591,0.365704 2.775391,1.097656 0.9043,0.688938 1.349609,1.71392 1.349609,3.091797 0,1.463904 -0.463956,2.57267 -1.410156,3.304688 -0.9054,0.731951 -2.2137,1.097656 -3.9375,1.097656 l -0.90625,0 -5.109375,0 -2.976563,0 -53.367237,0 0,0.0039 c -1.118972,8.07e-4 -2.232917,0.175074 -3.2753908,0.546875 -1.0812,0.374449 -2.0898969,0.990457 -2.9042969,1.802734 -0.8511,0.837684 -1.4685875,1.874775 -1.8671875,2.980469 -0.422,1.158702 -0.5898438,2.388837 -0.5898438,3.673828 0,1.287641 0.1681907,2.523171 0.5878907,3.6875 l 0,0.0059 0,0.0078 c 0.4008,1.096658 1.0169625,2.124759 1.8515625,2.960938 l 0.011719,0.01172 0.011719,0.01172 c 0.8122,0.795376 1.8055406,1.402256 2.8691406,1.785156 l 0.023437,0.0059 0.019531,0.0078 c 1.041,0.357709 2.145119,0.521485 3.261719,0.521485 l 0,0.002 41.3438,0 2.980469,0 5.115234,0 0.908203,0 c 1.7259,0 3.034806,0.36677 3.941406,1.099609 0.9473,0.732906 1.414063,1.842913 1.414063,3.308594 0,1.379548 -0.446163,2.403976 -1.351563,3.09375 -0.8619,0.73284 -1.787297,1.099609 -2.779297,1.099609 -0.4739,0 -0.970981,-0.08145 -1.488281,-0.253906 -0.4746,-0.129329 -0.920316,-0.362111 -1.353515,-0.707031 -0.434501,-0.301723 -0.776457,-0.720967 -1.035157,-1.238282 -0.1741,-0.379579 -0.200625,-0.873874 -0.265625,-1.349609 l -4.052734,2.335938 c 0.1164,0.44793 0.231781,0.899706 0.425781,1.287109 0.5159,1.07776 1.169413,1.931483 1.945313,2.578125 0.8175,0.689709 1.742843,1.188446 2.777343,1.490234 1.0347,0.344855 2.072722,0.517579 3.107422,0.517579 0.7758,0 1.660344,-0.153292 2.652344,-0.455079 1.0346,-0.258658 1.998997,-0.75733 2.904297,-1.490234 0.906,-0.68971 1.658365,-1.634709 2.259766,-2.841797 0.605199,-1.163958 0.910156,-2.648384 0.910156,-4.458984 l 0.0039,0 C 72,60.363013 70.961878,57.768642 68.892578,56.259766 66.823378,54.750954 63.956869,54 60.292969,54 l -48.6563,0 0,0.0098 c -0.5345,0 -0.948916,-0.07592 -1.291016,-0.191407 -0.2996,-0.111208 -0.4927686,-0.23897 -0.6679686,-0.408203 -0.1552,-0.158288 -0.3061656,-0.371439 -0.4472656,-0.751953 l 0,-0.0039 c -0.1279,-0.355148 -0.2324219,-0.897858 -0.2324219,-1.650391 0,-0.752625 0.1039625,-1.28965 0.2265625,-1.625 l 0,-0.0059 0,-0.0078 c 0.1465,-0.406946 0.2963938,-0.609758 0.4335938,-0.74414 l 0.011719,-0.01172 0.011719,-0.01172 c 0.1749,-0.175302 0.3458723,-0.286102 0.6386723,-0.386718 l 0.01953,-0.0078 0.02148,-0.0078 C 10.695178,48.075095 11.107669,48 11.636669,48 l 60.669972,0 c 3.6595,0 6.523043,-0.750829 8.589843,-2.257812 C 82.963184,44.23514 84,41.644264 84,37.984375 l -0.0039,0 c 0,-1.808407 -0.303703,-3.290578 -0.908203,-4.453125 -0.6007,-1.205626 -1.352913,-2.149016 -2.257813,-2.837891 -0.9043,-0.732015 -1.86719,-1.229936 -2.90039,-1.488281 C 76.938787,28.903658 76.054197,28.75 75.279297,28.75 Z"
+ transform="translate(1037.0313,22.36238)"
+ id="path4178"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sccccccccscscsccccccccsccccccccccccccscscsccccccccscccccssccccccscccccccccssccccccs" />
+ <path
+ sodipodi:nodetypes="ccccsccccccccscscscccccsscc"
+ inkscape:connector-curvature="0"
+ id="path4171"
+ d="m 1091.0274,48.500103 c 0,-1.780677 -0.2989,-3.239878 -0.8941,-4.384599 -0.5915,-1.187139 -1.3323,-2.116397 -2.2233,-2.794708 -0.8904,-0.720792 -1.8392,-1.211305 -2.8567,-1.465688 -0.9756,-0.2968 -1.8456,-0.44714 -2.6087,-0.44714 -1.0176,0 -2.0383,0.170054 -3.0558,0.50921 -1.0175,0.2968 -1.9269,0.787377 -2.7308,1.465687 -0.7631,0.635956 -1.4055,1.473955 -1.9129,2.533903 -0.1908,0.381001 -0.3053,0.826425 -0.4198,1.266952 l 3.9862,2.297899 c 0.064,-0.467873 0.089,-0.955778 0.2607,-1.329084 0.2544,-0.508765 0.5915,-0.920483 1.0188,-1.21722 0.4261,-0.339219 0.8637,-0.568417 1.3305,-0.695608 0.5087,-0.16961 0.9978,-0.248405 1.4639,-0.248405 0.9756,0 1.885,0.359888 2.7327,1.080617 0.8904,0.678374 1.3292,1.686427 1.3292,3.043176 0,1.441458 -0.4579,2.533521 -1.3896,3.254314 -0.8916,0.720728 -2.1794,1.080616 -3.8768,1.080616 l -0.8929,0 -5.031,0 -2.9311,0 -17.2945,0 0,5.912355 24.4865,0 c 3.6033,0 6.4232,-0.739426 8.4582,-2.223302 2.0351,-1.48394 3.0552,-4.035204 3.0552,-7.638975 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
=== added file 'app/ubuntu-weather-app.qml'
--- app/ubuntu-weather-app.qml 1970-01-01 00:00:00 +0000
+++ app/ubuntu-weather-app.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Qt.labs.settings 1.0
+import Ubuntu.Components 1.2
+import "components"
+import "data" as Data
+import "data/WeatherApi.js" as WeatherApi
+import "data/key.js" as Key
+import "ui"
+
+MainView {
+ id: weatherApp
+
+ objectName: "weather"
+
+ applicationName: "com.ubuntu.weather"
+
+ automaticOrientation: false
+
+ width: units.gu(40)
+ height: units.gu(70)
+
+ backgroundColor: "#F5F5F5"
+
+ anchorToKeyboard: true
+
+ signal listItemSwiping(int i)
+
+ /*
+ List of locations and their data, accessible through index
+ */
+ property var locationsList: []
+
+ /*
+ Index of Location before a refresh, to go back after
+ */
+ property int indexAtRefresh: -1
+
+ /*
+ Is the app loading?
+ */
+ property bool loading: false
+
+ /*
+ (re)load the pages on completion
+ */
+ Component.onCompleted: {
+ storage.getLocations(fillPages);
+ refreshData();
+ }
+
+ /*
+ Handle response data from data backend. Checks if a location
+ was updated and has to be stored again.
+ */
+ function responseDataHandler(messageObject) {
+ if(!messageObject.error) {
+ if(messageObject.action === "updateData") {
+ messageObject.result.forEach(function(loc) {
+ // replace location data in cache with refreshed values
+ if(loc["save"] === true) {
+ storage.updateLocation(loc.db.id, loc);
+ }
+ });
+ //print(JSON.stringify(messageObject.result));
+ fillPages(messageObject.result);
+ }
+ } else {
+ console.log(messageObject.error.msg+" / "+messageObject.error.request.url)
+ // TODO error handling
+ }
+ }
+
+ /* Fill the location pages with their data. */
+ function fillPages(locations) {
+ locationsList = []
+ locationsList = locations;
+ }
+
+ /*
+ Refresh data, either directly from storage or by checking against
+ API instead.
+ */
+ function refreshData(from_storage, force_refresh) {
+ from_storage = from_storage || false
+ force_refresh = force_refresh || false
+ loading = true
+ if(from_storage === true && force_refresh !== true) {
+ storage.getLocations(fillPages);
+ } else {
+ storage.getLocations(function(locations) {
+ WeatherApi.sendRequest({
+ action: "updateData",
+ params: {
+ locations: locations,
+ force: force_refresh,
+ service: settings.service,
+ api_key: Key.twcKey,
+ interval: settings.refreshInterval
+ }
+ }, responseDataHandler)
+ });
+ }
+ }
+
+ CurrentLocation {
+ id: currentLocation
+ }
+
+ Settings {
+ id: settings
+ category: "weatherSettings"
+ /*
+ Index of the current locationList of locations and their data, accessible through index
+ */
+ property int current: 0
+
+ property int refreshInterval: 1800
+ property string precipUnits
+ property string service
+ property string tempScale
+ property string units
+ property string windUnits
+
+ property bool addedCurrentLocation: false
+ property bool migrated: false
+
+ Component.onCompleted: {
+ if (units === "") { // No settings so load defaults
+ console.debug("No settings, using defaults")
+ var metric = Qt.locale().measurementSystem === Locale.MetricSystem
+
+ precipUnits = metric ? "mm" : "in"
+ service = "weatherchannel"
+ tempScale = "°" + (metric ? "C" : "F")
+ units = metric ? "metric" : "imperial"
+ windUnits = metric ? "kph" : "mph"
+ }
+ }
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: units.gu(4)
+ visible: (locationsList == null || locationsList.length == 0) && mainPageStack.depth == 1
+ z: 1000
+
+ Label {
+ id: emptyStateLabel
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: i18n.tr("Searching for current location...")
+ }
+
+ Button {
+ id: emptyStateButton
+ objectName: "emptyStateButton"
+
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ text: i18n.tr("Add a manual location")
+
+ onTriggered: mainPageStack.push(Qt.resolvedUrl("ui/AddLocationPage.qml"));
+ }
+ }
+
+ Data.Storage {
+ id: storage
+
+ // Add or replace the current locaiton to the storage and refresh the
+ // locationList
+ function updateCurrentLocation(location) {
+ if (!settings.addedCurrentLocation || locationsList == null || locationsList.length == 0) {
+ storage.insertLocationAtStart({location: location});
+ settings.addedCurrentLocation = true;
+ } else {
+ storage.updateLocation(locationsList[0].db.id, {location: location});
+ }
+
+ refreshData(false, true);
+ }
+
+ // Add the location to the storage and refresh the locationList
+ // Return true if a location is added
+ function addLocation(location) {
+ var exists = checkLocationExists(location)
+
+ if(!exists) {
+ if(location.dbId === undefined || location.dbId === 0) {
+ storage.insertLocation({location: location});
+ }
+ refreshData();
+ }
+
+ return !exists;
+ }
+
+ // Return true if the location given is already in the locationsList, and is not the same
+ // as the current location.
+ function checkLocationExists(location) {
+ var exists = false;
+
+ for (var i=0; !exists && i < locationsList.length; i++) {
+ var loc = locationsList[i].location;
+
+ if (loc.services.geonames && (loc.services.geonames === location.services.geonames) && !(settings.addedCurrentLocation && i === 0)) {
+ exists = true;
+ }
+ }
+
+ return exists;
+ }
+
+ function moveLocation(from, to) {
+ // Indexes are offset by 1 to account for current location
+ from += 1
+ to += 1
+
+ // Update settings to respect new changes
+ if (from === settings.current) {
+ settings.current = to;
+ } else if (from < settings.current && to >= settings.current) {
+ settings.current -= 1;
+ } else if (from > settings.current && to <= settings.current) {
+ settings.current += 1;
+ }
+
+ storage.reorder(locationsList[from].db.id, locationsList[to].db.id);
+
+ refreshData(true, false);
+ }
+
+ // Remove a location from the list
+ function removeLocation(index) {
+ // Indexes are offset by 1 to account for current location
+ index += 1
+ if (settings.current >= index) { // Update settings to respect new changes
+ settings.current -= settings.current;
+ }
+
+ storage.clearLocation(locationsList[index].db.id);
+
+ refreshData(true, false);
+ }
+
+ function removeMultiLocations(indexes) {
+ var i;
+
+ // Sort the item indexes as loops below assume *numeric* sort
+ indexes.sort(function(a,b) { return a - b })
+
+ for (i=0; i < indexes.length; i++) {
+ if (settings.current >= indexes[i] + 1) { // Update settings to respect new changes
+ settings.current -= settings.current;
+ }
+ }
+
+ // Get location db ids to remove
+ var locations = []
+
+ for (i=0; i < indexes.length; i++) {
+ locations.push(locationsList[indexes[i] + 1].db.id)
+ }
+
+ storage.clearMultiLocation(locations);
+
+ refreshData(true, false);
+ }
+ }
+
+ PageStack {
+ id: mainPageStack
+ Component.onCompleted: mainPageStack.push(Qt.resolvedUrl("ui/HomePage.qml"))
+ }
+}
=== added directory 'app/ui'
=== added file 'app/ui/AddLocationPage.qml'
--- app/ui/AddLocationPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/AddLocationPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.Popups 1.0
+import "../components"
+import "../data/CitiesList.js" as Cities
+import "../data/WeatherApi.js" as WeatherApi
+
+Page {
+ id: addLocationPage
+ objectName: "addLocationPage"
+ title: i18n.tr("Select a city")
+ visible: false
+
+ /*
+ Flickable is set to null to stop page header from hiding since the fast
+ scroll component hides top anchor margin is incorrect.
+ */
+ flickable: null
+
+ state: "default"
+ states: [
+ PageHeadState {
+ name: "default"
+ head: addLocationPage.head
+ backAction: Action {
+ iconName: "back"
+ text: i18n.tr("Back")
+ onTriggered: mainPageStack.pop()
+ }
+ actions: [
+ Action {
+ iconName: "search"
+ text: i18n.tr("City")
+ onTriggered: {
+ addLocationPage.state = "search"
+ searchComponentLoader.sourceComponent = searchComponent
+ searchComponentLoader.item.forceActiveFocus()
+ }
+ }
+ ]
+ },
+
+ PageHeadState {
+ name: "search"
+ head: addLocationPage.head
+ backAction: Action {
+ iconName: "back"
+ text: i18n.tr("Back")
+ onTriggered: {
+ locationList.forceActiveFocus()
+ searchComponentLoader.item.text = ""
+ addLocationPage.state = "default"
+ searchComponentLoader.sourceComponent = undefined
+ }
+ }
+
+ contents: Loader {
+ id: searchComponentLoader
+ anchors {
+ left: parent ? parent.left : undefined
+ right: parent ? parent.right : undefined
+ rightMargin: units.gu(2)
+ }
+ }
+ }
+ ]
+
+ Component {
+ id: searchComponent
+ TextField {
+ id: searchField
+ objectName: "searchField"
+
+ inputMethodHints: Qt.ImhNoPredictiveText
+ placeholderText: i18n.tr("Search city")
+ hasClearButton: true
+
+ onTextChanged: {
+ if (text.trim() === "") {
+ loadEmpty()
+ } else {
+ loadFromProvider(text)
+ }
+ }
+ }
+ }
+
+ // Builds a area label for the location, depending on the uniqueness of name, adminName1 and country
+ function buildAreaLabel(loc, a1Counts) {
+ var label = "";
+ label += ((loc.adminName1) ? loc.adminName1.replace(/ Region$/,''):"");
+ if (loc.adminName2 && a1Counts[loc.name+loc.adminName1] > 1) {
+ // even name and adminName1 are multiple, add adminName2
+ label += ", "+loc.adminName2;
+ }
+ label += ((label !== "") ? ", " : "") + loc.countryName
+ return label;
+ }
+
+ function appendCities(list) {
+ var a1Counts = {};
+ // count occurrences of name+adminName1 and name+country
+ list.forEach(function(loc) {
+ a1Counts[loc.name+loc.adminName1] = (!a1Counts[loc.name+loc.adminName1]) ? 1 : a1Counts[loc.name+loc.adminName1]+1;
+ });
+ // add locations to listmodel
+ list.forEach(function(loc) {
+ loc.areaLabel = buildAreaLabel(loc, a1Counts)
+ citiesModel.append(loc);
+ })
+ }
+
+ function clearModelForLoading() {
+ citiesModel.clear()
+ citiesModel.loading = true
+ citiesModel.httpError = false
+ }
+
+ function loadEmpty() {
+ clearModelForLoading()
+
+ appendCities(Cities.preList)
+
+ citiesModel.loading = false
+ }
+
+ function loadFromProvider(search) {
+ clearModelForLoading()
+
+ WeatherApi.sendRequest({
+ action: "searchByName",
+ params: {
+ name: search,
+ units: "metric"
+ }
+ }, searchResponseHandler)
+ }
+
+ function searchResponseHandler(msgObject) {
+ if (!msgObject.error) {
+ appendCities(msgObject.result.locations)
+ } else {
+ citiesModel.httpError = true
+ }
+
+ citiesModel.loading = false
+ }
+
+ ListView {
+ id: locationList
+
+ clip: true
+ currentIndex: -1
+ anchors.fill: parent
+ anchors.rightMargin: fastScroll.showing ? fastScroll.width - units.gu(1)
+ : 0
+
+ function getSectionText(index) {
+ return citiesModel.get(index).name.substring(0,1)
+ }
+
+ onFlickStarted: {
+ forceActiveFocus()
+ }
+
+ section.property: "name"
+ section.criteria: ViewSection.FirstCharacter
+ section.labelPositioning: ViewSection.InlineLabels
+
+ section.delegate: ListItem {
+ height: headerText.implicitHeight + units.gu(1)
+ Label {
+ id: headerText
+ text: section
+ anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
+ font.weight: Font.DemiBold
+ }
+ }
+
+ model: ListModel {
+ id: citiesModel
+
+ property bool loading: true
+ property bool httpError: false
+
+ onRowsAboutToBeInserted: loading = false
+ }
+
+ delegate: ListItem {
+ divider.visible: false
+ Column {
+ anchors {
+ left: parent.left
+ leftMargin: units.gu(2)
+ right: parent.right
+ rightMargin: units.gu(2)
+ verticalCenter: parent.verticalCenter
+ }
+
+ Label {
+ color: UbuntuColors.darkGrey
+ elide: Text.ElideRight
+ fontSize: "medium"
+ text: name
+ }
+
+ Label {
+ color: UbuntuColors.lightGrey
+ elide: Text.ElideRight
+ fontSize: "xx-small"
+ text: areaLabel
+ }
+ }
+
+ onClicked: {
+ if (storage.addLocation(citiesModel.get(index))) {
+ mainPageStack.pop()
+ } else {
+ PopupUtils.open(locationExistsComponent, addPage)
+ }
+ }
+ }
+
+ Component.onCompleted: loadEmpty()
+
+ Behavior on anchors.rightMargin {
+ UbuntuNumberAnimation {}
+ }
+ }
+
+ FastScroll {
+ id: fastScroll
+
+ listView: locationList
+
+ enabled: (locationList.contentHeight > (locationList.height * 2)) &&
+ (locationList.height >= minimumHeight)
+
+ anchors {
+ top: locationList.top
+ topMargin: units.gu(1.5)
+ bottom: locationList.bottom
+ right: parent.right
+ }
+ }
+
+ ActivityIndicator {
+ anchors {
+ centerIn: parent
+ }
+ running: visible
+ visible: citiesModel.loading
+ }
+
+ Label {
+ id: noCity
+ anchors {
+ centerIn: parent
+ }
+ text: i18n.tr("No city found")
+ visible: citiesModel.count === 0 && !citiesModel.loading
+ }
+
+ Label {
+ id: httpFail
+ anchors {
+ left: parent.left
+ margins: units.gu(1)
+ right: parent.right
+ top: noCity.bottom
+ }
+ horizontalAlignment: Text.AlignHCenter
+ text: i18n.tr("Couldn't load weather data, please try later again!")
+ visible: citiesModel.httpError
+ wrapMode: Text.WordWrap
+ }
+
+ Component {
+ id: locationExistsComponent
+
+ Dialog {
+ id: locationExists
+ title: i18n.tr("Location already added.")
+
+ Button {
+ text: i18n.tr("OK")
+ onClicked: PopupUtils.close(locationExists)
+ }
+ }
+ }
+
+}
=== added file 'app/ui/CMakeLists.txt'
--- app/ui/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/ui/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,7 @@
+add_subdirectory(settings)
+
+file(GLOB UI_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_ui_QMlFiles ALL SOURCES ${UI_QML_JS_FILES})
+
+install(FILES ${UI_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/ui)
=== added file 'app/ui/HomePage.qml'
--- app/ui/HomePage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/HomePage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../components"
+
+
+PageWithBottomEdge {
+ // Set to null otherwise the header is shown (but blank) over the top of the listview
+ id: locationPage
+ objectName: "homePage"
+ flickable: null
+
+ bottomEdgePageSource: Qt.resolvedUrl("LocationsPage.qml")
+ bottomEdgeTitle: i18n.tr("Locations")
+ tipColor: UbuntuColors.orange
+ tipLabelColor: "#FFF"
+
+ property var iconMap: {
+ "sun": "weather-clear-symbolic",
+ "moon": "weather-clear-night-symbolic",
+ "cloud_sun": "weather-few-clouds-symbolic",
+ "cloud_moon": "weather-few-clouds-night-symbolic",
+ "cloud": "weather-clouds-symbolic",
+ "rain": "weather-showers-symbolic",
+ "thunder": "weather-storm-symbolic",
+ "snow_shower": "weather-snow-symbolic",
+ "fog": "weather-fog-symbolic",
+ "snow_rain": "weather-snow-symbolic",
+ "scattered": "weather-showers-scattered-symbolic",
+ "overcast": "weather-overcast-symbolic"
+ }
+
+ property var imageMap: {
+ "sun": Qt.resolvedUrl("../graphics/Sunny.png"),
+ "moon": Qt.resolvedUrl("../graphics/Starry-Night.png"),
+ "cloud_sun": Qt.resolvedUrl("../graphics/Cloudy-Circles.png"),
+ "cloud_moon": Qt.resolvedUrl("../graphics/Cloudy-Night.png"),
+ "cloud": Qt.resolvedUrl("../graphics/Cloudy.png"),
+ "rain": Qt.resolvedUrl("../graphics/Big-Rain.png"),
+ "thunder": Qt.resolvedUrl("../graphics/Stormy.png"),
+ "snow_shower": Qt.resolvedUrl("../graphics/Cloudy-Snow.png"),
+ "fog": Qt.resolvedUrl("../graphics/Fog.png"),
+ "snow_rain": Qt.resolvedUrl("../graphics/Cloudy-Snow.png"),
+ "scattered": Qt.resolvedUrl("../graphics/Showers.png"),
+ "overcast": Qt.resolvedUrl("../graphics/Cloudy.png")
+ }
+
+ /*
+ Format date object by given format.
+ */
+ function formatTimestamp(dateData, format) {
+ var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
+ return Qt.formatDate(date, i18n.tr(format))
+ }
+
+ /*
+ Format time object by given format.
+ */
+ function formatTime(dateData, format) {
+ var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
+ return Qt.formatTime(date, i18n.tr(format))
+ }
+
+ /*
+ Background for the PageWithBottomEdge
+ */
+ Rectangle {
+ id: pageBackground
+ anchors.fill: parent
+ color: "white"
+ }
+
+ /*
+ Flickable to scroll the location vertically.
+ The respective contentHeight gets calculated after the Location is filled with data.
+ */
+ Flickable {
+ id: locationFlickable
+ width: parent.width
+ height: parent.height
+
+ // FIXME: not sure where the 3GU comes from, PullToRefresh or something in HomePage?
+ contentHeight: locationPages.currentItem.childrenRect.height + units.gu(3)
+ contentWidth: parent.width
+
+ Behavior on contentHeight {
+ NumberAnimation {
+
+ }
+ }
+
+ PullToRefresh {
+ id: pullToRefresh
+ parent: locationFlickable
+ refreshing: false
+ onRefresh: {
+ locationPages.loaded = false
+ refreshing = true
+ refreshData(false, true)
+ }
+ }
+
+ /*
+ ListView for locations with snap-scrolling horizontally.
+ */
+ ListView {
+ id: locationPages
+ anchors.fill: parent
+ currentIndex: settings.current
+ delegate: LocationPane {}
+ height: childrenRect.height
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ model: weatherApp.locationsList.length
+ orientation: ListView.Horizontal
+ // TODO with snapMode, currentIndex is not properly set and setting currentIndex fails
+ //snapMode: ListView.SnapOneItem
+ width: parent.width
+
+ property bool loaded: false
+
+ signal collapseOtherDelegates(int index)
+
+ onCurrentIndexChanged: {
+ if (loaded) {
+ // FIXME: when a model is reloaded this causes the currentIndex to be lost
+ settings.current = currentIndex
+
+ collapseOtherDelegates(-1) // collapse all
+ }
+ }
+ onModelChanged: {
+ currentIndex = settings.current
+
+ if (model > 0) {
+ pullToRefresh.refreshing = false
+ loading = false
+ loaded = true
+ }
+ }
+ onVisibleChanged: {
+ if (!visible && loaded) {
+ collapseOtherDelegates(-1) // collapse all
+ }
+ }
+
+ // TODO: workaround for not being able to use snapMode property
+ Component.onCompleted: {
+ var scaleFactor = units.gridUnit * 10;
+ maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+ flickDeceleration = flickDeceleration * scaleFactor;
+ }
+
+ Connections {
+ target: settings
+ onCurrentChanged: {
+ locationPages.currentIndex = settings.current
+ }
+ }
+ }
+ }
+
+ LoadingIndicator {
+ id: loadingIndicator
+ }
+}
=== added file 'app/ui/LocationPane.qml'
--- app/ui/LocationPane.qml 1970-01-01 00:00:00 +0000
+++ app/ui/LocationPane.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import "../components"
+
+Item {
+ id: locationItem
+ /*
+ Data properties
+ */
+ property string name
+ property string conditionText
+ property string currentTemp
+ property string todayMaxTemp
+ property string todayMinTemp
+ property string icon
+ property string iconName
+
+ Component.onCompleted: renderData(index)
+
+ width: locationPage.width
+ height: childrenRect.height
+ anchors.fill: parent.fill
+
+ function emptyIfUndefined(variable, append) {
+ if (append === undefined) {
+ append = ""
+ }
+
+ return variable === undefined ? "" : variable + append
+ }
+
+ /*
+ Extracts values from the location weather data and puts them into the appropriate components
+ to display them.
+
+ Attention: Data access happens through "weatherApp.locationList[]" by index, since complex
+ data in models will lead to type problems.
+ */
+ function renderData(index) {
+ var data = weatherApp.locationsList[index],
+ current = data.data[0].current,
+ forecasts = data.data,
+ forecastsLength = forecasts.length,
+ today = forecasts[0],
+ hourlyForecasts = [];
+
+ var tempUnits = settings.tempScale === "°C" ? "metric" : "imperial"
+
+ // set general location data
+ name = data.location.name;
+
+ // set current temps and condition
+ iconName = (current.icon) ? current.icon : "";
+ icon = (imageMap[iconName] !== undefined) ? imageMap[iconName] : "";
+ conditionText = (current.condition !== undefined) ? current.condition : "";
+ todayMaxTemp = (today[tempUnits].tempMax !== undefined) ? Math.round(today[tempUnits].tempMax).toString() + settings.tempScale: "";
+ todayMinTemp = Math.round(today[tempUnits].tempMin).toString() + settings.tempScale;
+ currentTemp = Math.round(current[tempUnits].temp).toString() + String("°");
+
+ // reset days list
+ mainPageWeekdayListView.model.clear()
+
+ // set daily forecasts
+ if(forecastsLength > 0) {
+ for(var x=0;x<forecastsLength;x++) {
+ // collect hourly forecasts if available
+ if(forecasts[x].hourly !== undefined && forecasts[x].hourly.length > 0) {
+ hourlyForecasts = hourlyForecasts.concat(forecasts[x].hourly)
+ }
+ if(x === 0) {
+ // skip todays daydata
+ continue;
+ }
+
+ // set daydata
+ var dayData = {
+ day: formatTimestamp(forecasts[x].date, 'dddd'),
+ low: Math.round(forecasts[x][tempUnits].tempMin).toString() + settings.tempScale,
+ high: (forecasts[x][tempUnits].tempMax !== undefined) ? Math.round(forecasts[x][tempUnits].tempMax).toString() + settings.tempScale : "",
+ image: (forecasts[x].icon !== undefined && iconMap[forecasts[x].icon] !== undefined) ? iconMap[forecasts[x].icon] : "",
+ chanceOfRain: forecasts[x].propPrecip === undefined ? -1 : forecasts[x].propPrecip,
+ humidity: emptyIfUndefined(forecasts[x].humidity, "%"),
+ uvIndex: emptyIfUndefined(forecasts[x].uv),
+ wind: forecasts[x][tempUnits].windSpeed === undefined || forecasts[x].windDir === undefined
+ ? "" : Math.round(forecasts[x][tempUnits].windSpeed) + settings.windUnits + " " + forecasts[x].windDir
+ }
+ mainPageWeekdayListView.model.append(dayData);
+ }
+ }
+
+ // set data for hourly forecasts
+ if(hourlyForecasts.length > 0) {
+ homeHourlyLoader.forecasts = hourlyForecasts;
+ homeHourlyLoader.tempUnits = tempUnits;
+ }
+ }
+
+ Column {
+ id: locationTop
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+ spacing: units.gu(1)
+
+ HeaderRow {
+ id: headerRow
+ locationName: locationItem.name
+ }
+
+ HomeGraphic {
+ id: homeGraphic
+ icon: locationItem.icon
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ homeGraphic.visible = false;
+ }
+ }
+ }
+
+ Loader {
+ id: homeHourlyLoader
+ active: !homeGraphic.visible
+ asynchronous: true
+ height: units.gu(32)
+ source: "../components/HomeHourly.qml"
+ visible: active
+ width: parent.width
+
+ property var forecasts: []
+ property string tempUnits: ""
+ }
+
+ HomeTempInfo {
+ id: homeTempInfo
+ description: conditionText
+ high: locationItem.todayMaxTemp
+ low: locationItem.todayMinTemp
+ now: locationItem.currentTemp
+ }
+
+ // TODO: Migrate this to using the new SDK list item when possible.
+ ListItem.ThinDivider { anchors { leftMargin: units.gu(-2); rightMargin: units.gu(-2) } }
+ }
+ Column {
+ id: weekdayColumn
+
+ anchors.top: locationTop.bottom
+ height: childrenRect.height
+ width: parent.width
+
+ Repeater {
+ id: mainPageWeekdayListView
+ model: ListModel{}
+ delegate: DayDelegate {
+ day: model.day
+ high: model.high
+ image: model.image
+ low: model.low
+
+ chanceOfRain: model.chanceOfRain
+ humidity: model.humidity
+ // TODO: extra from API
+ //pollen: model.pollen
+ //sunrise: model.sunrise
+ //sunset: model.sunset
+ wind: model.wind
+ uvIndex: model.uvIndex
+ }
+ }
+ }
+}
=== added file 'app/ui/LocationsPage.qml'
--- app/ui/LocationsPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/LocationsPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import Ubuntu.Components.ListItems 0.1 as ListItem
+import "../components"
+import "../components/ListItemActions"
+
+
+Page {
+ id: locationsPage
+ // Set to null otherwise the first delegate appears +header.height down the page
+ flickable: null
+ title: i18n.tr("Locations")
+
+ state: locationsListView.state === "multiselectable" ? "selection" : "default"
+ states: [
+ PageHeadState {
+ id: defaultState
+ name: "default"
+ actions: [
+ Action {
+ iconName: "add"
+ onTriggered: mainPageStack.push(Qt.resolvedUrl("AddLocationPage.qml"))
+ }
+ ]
+ PropertyChanges {
+ target: locationsPage.head
+ actions: defaultState.actions
+ }
+ },
+ MultiSelectHeadState {
+ listview: locationsListView
+ removable: true
+ thisPage: locationsPage
+
+ onRemoved: storage.removeMultiLocations(selectedItems.slice())
+ }
+ ]
+
+ ListModel {
+ id: currentLocationModel
+ }
+
+ MultiSelectListView {
+ id: locationsListView
+ anchors {
+ fill: parent
+ }
+ model: ListModel {
+ id: locationsModel
+ }
+ header: MultiSelectListView {
+ id: currentLocationListView
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ height: settings.addedCurrentLocation ? units.gu(8) : units.gu(0)
+ interactive: false
+ model: currentLocationModel
+ delegate: WeatherListItem {
+ id: currentLocationListItem
+
+ onItemClicked: {
+ settings.current = index;
+ pageStack.pop()
+ }
+
+ Column {
+ anchors {
+ left: parent.left
+ right: currentWeatherImage.left
+ rightMargin: units.gu(1)
+ verticalCenter: parent.verticalCenter
+ }
+
+ Label {
+ id: currentLocationName
+
+ anchors {
+ left: parent.left
+ leftMargin: units.gu(2)
+ }
+
+ elide: Text.ElideRight
+ fontSize: "medium"
+ text: i18n.tr("Current Location")
+ }
+ Label {
+ id: currentLocationName2
+
+ anchors {
+ left: parent.left
+ leftMargin: units.gu(2)
+ }
+
+ color: UbuntuColors.lightGrey
+ elide: Text.ElideRight
+ fontSize: "small"
+ font.weight: Font.Light
+ text: name + ", " + (adminName1 == name ? countryName : adminName1)
+ }
+ }
+
+ Icon {
+ id: currentWeatherImage
+ anchors {
+ right: parent.right
+ rightMargin: units.gu(14)
+ verticalCenter: parent.verticalCenter
+ }
+ name: icon
+ height: units.gu(3)
+ width: units.gu(3)
+ }
+
+ Label {
+ id: currentTemperatureLabel
+ anchors {
+ left: currentWeatherImage.right
+ leftMargin: units.gu(1)
+ right: parent.right
+ rightMargin: units.gu(2)
+ verticalCenter: parent.verticalCenter
+ }
+ color: UbuntuColors.orange
+ elide: Text.ElideRight
+ font.pixelSize: units.gu(4)
+ font.weight: Font.Light
+ horizontalAlignment: Text.AlignRight
+ text: temp + settings.tempScale
+ }
+ }
+ }
+
+ delegate: WeatherListItem {
+ id: locationsListItem
+ leftSideAction: Remove {
+ onTriggered: storage.removeLocation(index)
+ }
+ multiselectable: true
+ reorderable: true
+
+ onItemClicked: {
+ settings.current = index + 1;
+ pageStack.pop()
+ }
+ onReorder: {
+ console.debug("Move: ", from, to);
+
+ storage.moveLocation(from, to);
+ }
+
+ ListItem.ThinDivider {
+ anchors {
+ top: parent.top
+ }
+ visible: index == 0
+ }
+
+ Item {
+ anchors {
+ fill: parent
+ leftMargin: units.gu(2)
+ rightMargin: units.gu(2)
+ }
+
+ Column {
+ anchors {
+ left: parent.left
+ right: weatherImage.visible ? weatherImage.left : parent.right
+ rightMargin: units.gu(1)
+ verticalCenter: parent.verticalCenter
+ }
+
+ Label {
+ id: locationName
+ elide: Text.ElideRight
+ fontSize: "medium"
+ text: name
+ }
+ Label {
+ id: locationName2
+ color: UbuntuColors.lightGrey
+ elide: Text.ElideRight
+ fontSize: "small"
+ font.weight: Font.Light
+ text: adminName1 == name ? countryName : adminName1
+ }
+ }
+
+ Icon {
+ id: weatherImage
+ anchors {
+ right: parent.right
+ rightMargin: units.gu(12)
+ verticalCenter: parent.verticalCenter
+ }
+ name: icon
+ height: units.gu(3)
+ width: units.gu(3)
+ visible: locationsPage.state === "default"
+ }
+
+ Label {
+ id: temperatureLabel
+ anchors {
+ left: weatherImage.right
+ leftMargin: units.gu(1)
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ color: UbuntuColors.orange
+ elide: Text.ElideRight
+ font.pixelSize: units.gu(4)
+ font.weight: Font.Light
+ horizontalAlignment: Text.AlignRight
+ text: temp + settings.tempScale
+ visible: locationsPage.state === "default"
+ }
+ }
+
+ ListItem.ThinDivider {
+ anchors {
+ bottom: parent.bottom
+ }
+ }
+ }
+ }
+
+ function populateLocationsModel() {
+ currentLocationModel.clear()
+ locationsModel.clear()
+ var loc = {}, data = {},
+ tempUnits = settings.tempScale === "°C" ? "metric" : "imperial";
+
+ for (var i=0; i < weatherApp.locationsList.length; i++) {
+ data = weatherApp.locationsList[i];
+ loc = {
+ "name": data.location.name,
+ "adminName1": data.location.adminName1,
+ "areaLabel": data.location.areaLabel,
+ "countryName": data.location.countryName,
+ "temp": Math.round(data.data[0].current[tempUnits].temp).toString(),
+ "icon": iconMap[data.data[0].current.icon]
+ }
+
+ if (!settings.addedCurrentLocation || i > 0) {
+ locationsModel.append(loc)
+ } else {
+ currentLocationModel.append(loc)
+ }
+ }
+ }
+
+ Connections {
+ target: weatherApp
+ onLocationsListChanged: populateLocationsModel()
+ }
+
+ Component.onCompleted: populateLocationsModel()
+}
=== added file 'app/ui/SettingsPage.qml'
--- app/ui/SettingsPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/SettingsPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../components"
+
+Page {
+ title: i18n.tr("Settings")
+
+ Flickable {
+ clip: true
+ anchors.fill: parent
+ contentHeight: settingsColumn.height
+ height: parent.height
+
+ Column {
+ id: settingsColumn
+
+ anchors.fill: parent
+
+ StandardListItem {
+ title: i18n.tr("Units")
+ onClicked: mainPageStack.push(Qt.resolvedUrl("settings/UnitsPage.qml"))
+ }
+
+ StandardListItem {
+ title: i18n.tr("Data Provider")
+ onClicked: mainPageStack.push(Qt.resolvedUrl("settings/DataProviderPage.qml"))
+ }
+
+ StandardListItem {
+ title: i18n.tr("Refresh Interval")
+ onClicked: mainPageStack.push(Qt.resolvedUrl("settings/RefreshIntervalPage.qml"))
+ }
+ }
+ }
+}
=== added directory 'app/ui/settings'
=== added file 'app/ui/settings/CMakeLists.txt'
--- app/ui/settings/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ app/ui/settings/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,5 @@
+file(GLOB UI_SETTINGS_QML_JS_FILES *.qml *.js)
+
+add_custom_target(ubuntu-weather-app_ui_settings_QMlFiles ALL SOURCES ${UI_SETTINGS_QML_JS_FILES})
+
+install(FILES ${UI_SETTINGS_QML_JS_FILES} DESTINATION ${UBUNTU-WEATHER_APP_DIR}/ui/settings)
=== added file 'app/ui/settings/DataProviderPage.qml'
--- app/ui/settings/DataProviderPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/settings/DataProviderPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+ title: i18n.tr("Data Provider")
+
+ ListModel {
+ id: dataProviderModel
+ ListElement { text: "OpenWeatherMap" }
+ ListElement { text: "The Weather Channel" }
+ }
+
+ ExpandableListItem {
+ id: dataProviderSetting
+
+ listViewHeight: dataProviderModel.count*units.gu(7) - units.gu(1)
+ model: dataProviderModel
+ text: i18n.tr("Provider")
+ subText: settings.service === "weatherchannel" ? "The Weather Channel" : "OpenWeatherMap"
+
+ delegate: StandardListItem {
+ title: model.text
+ icon: "ok"
+ showIcon: dataProviderSetting.subText === model.text
+ onClicked: {
+ if (model.text === "The Weather Channel") {
+ settings.service = "weatherchannel"
+ } else {
+ settings.service = "openweathermap"
+ }
+ refreshData(false, true)
+ }
+ }
+ }
+}
=== added file 'app/ui/settings/RefreshIntervalPage.qml'
--- app/ui/settings/RefreshIntervalPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/settings/RefreshIntervalPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+ title: i18n.tr("Refresh Interval")
+
+ ListModel {
+ id: refreshModel
+ Component.onCompleted: initialize()
+ function initialize() {
+ refreshModel.append({"interval": 600, "text": i18n.tr("%1 minute", "%1 minutes", 10).arg(10)})
+ refreshModel.append({"interval": 900, "text": i18n.tr("%1 minute", "%1 minutes", 15).arg(15)})
+ refreshModel.append({"interval": 1800, "text": i18n.tr("%1 minute", "%1 minutes", 30).arg(30)})
+ refreshModel.append({"interval": 3600, "text": i18n.tr("%1 minute", "%1 minutes", 60).arg(60)})
+ }
+ }
+
+ ExpandableListItem {
+ id: dataProviderSetting
+
+ listViewHeight: refreshModel.count*units.gu(7) - units.gu(1)
+ model: refreshModel
+ text: i18n.tr("Interval")
+ subText: i18n.tr("%1 minute", "%1 minutes", Math.floor(settings.refreshInterval / 60).toString()).arg(Math.floor(settings.refreshInterval / 60).toString())
+
+ delegate: StandardListItem {
+ title: model.text
+ icon: "ok"
+ showIcon: settings.refreshInterval === model.interval
+ onClicked: {
+ settings.refreshInterval = model.interval
+ refreshData(false, true)
+ }
+ }
+ }
+}
=== added file 'app/ui/settings/UnitsPage.qml'
--- app/ui/settings/UnitsPage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/settings/UnitsPage.qml 2015-06-28 23:49:12 +0000
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Weather App
+ *
+ * Ubuntu Weather App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Weather App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.2
+import "../../components"
+
+Page {
+ title: i18n.tr("Units")
+
+ flickable: null
+
+ Flickable {
+ clip: true
+ anchors.fill: parent
+ height: parent.height
+ contentHeight: unitsColumn.childrenRect.height
+
+ ListModel {
+ id: temperatureModel
+ Component.onCompleted: initialize()
+ function initialize() {
+ // TRANSLATORS: The strings are standard measurement units
+ // of temperature in Celcius and are shown in the settings page.
+ // Only the abbreviated form of Celcius should be used.
+ temperatureModel.append({"text": i18n.tr("°C"), "value": "°C"})
+
+ // TRANSLATORS: The strings are standard measurement units
+ // of temperature in Fahrenheit and are shown in the settings page.
+ // Only the abbreviated form of Fahrenheit should be used.
+ temperatureModel.append({"text": i18n.tr("°F"), "value": "°F"})
+ }
+ }
+
+ ListModel {
+ id: precipationModel
+ Component.onCompleted: initialize()
+ function initialize() {
+ // TRANSLATORS: The strings are standard measurement units
+ // of precipitation in millimeters and are shown in the settings page.
+ // Only the abbreviated form of millimeters should be used.
+ precipationModel.append({"text": i18n.tr("mm"), "value": "mm"})
+
+ // TRANSLATORS: The strings are standard measurement units
+ // of precipitation in inches and are shown in the settings page.
+ // Only the abbreviated form of inches should be used.
+ precipationModel.append({"text": i18n.tr("in"), "value": "in"})
+ }
+ }
+
+ ListModel {
+ id: windSpeedModel
+ Component.onCompleted: initialize()
+ function initialize() {
+ // TRANSLATORS: The strings are standard measurement units
+ // of wind speed in kilometers per hour and are shown in the settings page.
+ // Only the abbreviated form of kilometers per hour should be used.
+ windSpeedModel.append({"text": i18n.tr("kph"), "value": "kph"})
+
+ // TRANSLATORS: The strings are standard measurement units
+ // of wind speed in miles per hour and are shown in the settings page.
+ // Only the abbreviated form of miles per hour should be used.
+ windSpeedModel.append({"text": i18n.tr("mph"), "value": "mph"})
+ }
+ }
+
+ Column {
+ id: unitsColumn
+ anchors.fill: parent
+
+ ExpandableListItem {
+ id: temperatureSetting
+
+ listViewHeight: temperatureModel.count*units.gu(7) - units.gu(1)
+ model: temperatureModel
+ text: i18n.tr("Temperature")
+ subText: settings.tempScale === "°C" ? i18n.tr("°C")
+ : i18n.tr("°F")
+
+ delegate: StandardListItem {
+ title: model.text
+ icon: "ok"
+ showIcon: settings.tempScale === model.value
+ onClicked: {
+ settings.tempScale = model.value
+ refreshData(true)
+ }
+ }
+ }
+
+ ExpandableListItem {
+ id: precipationSetting
+
+ listViewHeight: precipationModel.count*units.gu(7) - units.gu(1)
+ model: precipationModel
+ text: i18n.tr("Precipitation")
+ subText: settings.precipUnits === "mm" ? i18n.tr("mm")
+ : i18n.tr("in")
+
+ delegate: StandardListItem {
+ title: model.text
+ icon: "ok"
+ showIcon: settings.precipUnits === model.value
+ onClicked: {
+ settings.precipUnits = model.value
+ refreshData(true)
+ }
+ }
+ }
+
+ ExpandableListItem {
+ id: windSetting
+
+ listViewHeight: windSpeedModel.count*units.gu(7) - units.gu(1)
+ model: windSpeedModel
+ text: i18n.tr("Wind Speed")
+ subText: settings.windUnits === "kph" ? i18n.tr("kph")
+ : i18n.tr("mph")
+
+ delegate: StandardListItem {
+ title: model.text
+ icon: "ok"
+ showIcon: settings.windUnits === model.value
+ onClicked: {
+ settings.windUnits = model.value
+ refreshData(true)
+ }
+ }
+ }
+ }
+ }
+}
=== added file 'app/weather-app@xxxxxx'
Binary files app/weather-app@xxxxxx 1970-01-01 00:00:00 +0000 and app/weather-app@xxxxxx 2015-06-28 23:49:12 +0000 differ
=== added directory 'debian'
=== renamed directory 'debian' => 'debian.moved'
=== added file 'debian/changelog'
--- debian/changelog 1970-01-01 00:00:00 +0000
+++ debian/changelog 2015-06-28 23:49:12 +0000
@@ -0,0 +1,179 @@
+ubuntu-weather-app (3.0) UNRELEASED; urgency=medium
+
+ * Sync debian version with click version.
+
+ -- Victor Thompson <victor.thompson@xxxxxxxxx> Mon, 01 Jun 2015 20:11:23 -0500
+
+ubuntu-weather-app (2.1) UNRELEASED; urgency=medium
+
+ * Update packaging to reflect reboot changes.
+
+ -- Daniel Holbach <daniel.holbach@xxxxxxxxxx> Wed, 04 Feb 2015 11:25:02 +0100
+
+ubuntu-weather-app (2.0) utopic; urgency=low
+
+ [ Andrew Hayzen ]
+ * Initial version of reboot
+
+ -- Andrew Hayzen <ahayzen@xxxxxxxxx> Mon, 02 Feb 2014 15:10:00 +0000
+
+ubuntu-weather-app (1.8.3ubuntu1) utopic; urgency=low
+
+ [ Dan Chapman ]
+ * Add timezone c++ extension plugin
+
+ -- Dan Chapman <dpniel@xxxxxxxxxx> Thu, 25 Sep 2014 10:46:29 +0100
+
+ubuntu-weather-app (1.8.3) raring; urgency=low
+
+ [ Martin Borho ]
+ * updated icons for rain and snow
+ * bug fixes and optimizations
+
+ [ Alan Pope ]
+ * Update click framework and apparmor profile version (LP: #1315318)
+
+ -- Alan Pope <popey@xxxxxxxxxx> Fri, 02 May 2014 13:51:57 +0100
+
+ubuntu-weather-app (1.8.1ubuntu1) UNRELEASED; urgency=low
+
+ * added missing icon
+
+ -- Martin Borho <martin@xxxxxxxxx> Sun, 23 Mar 2014 13:06:17 +0000
+
+ubuntu-weather-app (1.8ubuntu1) UNRELEASED; urgency=low
+
+ [Leo Arias]
+ * Port to autopilot 1.4.
+ * Set the objectNames for the settings options.
+
+ [Nicholas Skaggs]
+ * Fixed test_del_add_bug test, simplify tests, re-enable disabled tests.
+ * Redo create blank db, ensure it's being used across all tests.
+ * Add cmake build.
+ * Set revno to match store.
+ * Updated tests to use more emulator standards.
+ * Tweaked activity indicator handling.
+
+ [Dennis O'Flaherty]
+ * Load weather for the selected city when clicking the OWM logo instead of loading for London.
+
+ [Raul Yeguas]
+ * Added an modified logo for OpenWeatherMap (have mercy on me, christina-li and michal) and an action to open its website on click.
+ * Added animations for daily and houly forecast scroll.
+ * Improved animation speed by reducing image size.
+ * Removes SideStage hint, fixes related to the sizes of the current weather data display on tablets.
+
+ [Victor Thompson]
+ * Added unit of measure for main temperature value.
+ * Fixed conversion of wind speed data for TWC data.
+ * Prevent predictive text in search bar.
+
+ [Arash Badie Modiri]
+ * Changed a translation string into something a little bit more general.
+
+ [Martin Borho]
+ * Optimized and added haptic effect to hourly scrolling .
+ * Added The Weather Channel as default weather data provider.
+ * Added keyboard shortcuts.
+ * Initial tablet design of the Weather app.
+ * Refactored all width:parent.width and height:parent.height through corresponding anchors
+ * Fixed edit dialog to fill window on N7 2013 landscape
+ * Fixed weather AP tests to not assume tablet mode
+ * Fixed content sticking to the top, when height was changed in phone mode
+ * Fixed StateNotFoundError failures in image 206
+ * Removed WorkerScriptfor API calls
+ * New icon names used in API clients
+ * Using the current condition icon for the current day
+
+ [David Planella]
+ * Fixed the rule to generate a .pot template after the migration to cmake.
+ * Minor improvements on the weather API: added parameterize(), added locale parameters to TWC calls
+ * Fixes empty TWC URL if locale is not supported.
+ * Adds documentation for the The Weather Channel API.
+ * Makes the provider footer to be full width in order for it to make it look good on a Nexus 7.
+ * Fixes the shorter length header on the N7 using anchors instead of widths.
+
+ [Alan Pope]
+ * New icon.
+
+ -- Martin Borho <martin@xxxxxxxxx> Fri, 21 Mar 2014 11:26:17 +0000
+
+ubuntu-weather-app (1.0ubuntu1) UNRELEASED; urgency=medium
+
+ * New icon
+
+ -- Alan Pope <alan.pope@xxxxxxxxxxxxx> Tue, 18 Feb 2014 21:26:17 +0000
+
+ubuntu-weather-app (1.0) saucy; urgency=low
+
+ [Andrew Starr-Bochicchio]
+ * Use the system locale to determine both the default units and the display of time.
+ * Add predictive search for locations. (LP: #1218910)
+ * Increase visibility of ActivityIndicator (LP: #1218904)
+
+ [David Planella]
+ * Updated translatable strings, a couple of small fixes to ease translations
+
+ [Dennis O'Flaherty]
+ * Fixes for precipitation values.
+
+ [Martin Borho]
+ * Removing a location add adding a new one fails. (LP: #1230297)
+ * Added location lookup via geoip and geonames.org with test (LP: #1187312, #1188726)
+ * Added fixes for new removable ListItem and the changed LocalStorage path. (LP: #1234544)
+ * Added setting for precipitation unit, mm or in.
+ * Location search now via geonames.org. (LP: #1230153)
+ * Updated predefined cities list.
+ * Added component for notifications.
+ * Added bottom margin to scrolling area in tabs and moved warm colors up the gradient color scheme.
+ * Visual improvements and fixes to LocationManagerSheet and AddLocationSheet. (LP: #1198541, #1221734)
+ * Added updated label, removed years from date labels. (LP: #1219200, #1221149, #1221162)
+ * Improved scrolling, added slow/fast scrolling for day/hour scrolling distinction. (LP:# 1221169)
+ * Added option in settings to change the wind speed unit. (LP: #1214934, #1190784)
+ * Dynamic gradient backgrounds according to the visual design.
+ * Added missing localstorage dependency.
+
+ [Nicholas Skaags]
+ * Add support for testing via click packages.
+
+ [Raúl Yeguas]
+ * Updated colors in GradientsMap.js. (LP: #1226746)
+ * Returns the last element to primary information screen and skip animation. (LP: #1227150)
+ * improved CurrentTemp text size
+ * UbuntuAnimations should be used (LP: #1218805)
+ * Replaced ValueSelector with new OptionSelector in Settings.
+ * Implemented a new scrolling.
+ * Added pressure data and modified the transition animation.
+ * Font color should be always white, with drop shadow.
+
+ [Sergio Schvezov]
+ * changing applicationName to com.ubuntu.weather to write in the unconfined areas
+ * cleaning up python code
+ * add the click data into the package and added apparmor rules (LP: #121839)
+
+ -- Martin Borho <martin@xxxxxxxxx> Tue, 8 Oct 2013 22:42:00 +0200
+
+ubuntu-weather-app (0.3) raring; urgency=low
+
+ * LocationManager-Sheet added
+ * Settings-Sheet added
+ * Autopilot tests
+ * Data-provider calls now in WorkerScript
+ * ActivityIndicators while loading
+ * Suru theme added
+ * Displaying more weather data for every location
+
+ -- Martin Borho <martin@xxxxxxxxx> Tue, 13 Aug 2013 21:33:33 +0200
+
+ubuntu-weather-app (0.2) raring; urgency=low
+
+ * Added support for packaging and installing translations
+
+ -- David Planella <david.planella@xxxxxxxxxx> Tue, 28 May 2013 14:21:33 +0200
+
+ubuntu-weather-app (0.1) raring; urgency=low
+
+ * Initial release
+
+ -- Michael Hall <mhall119@xxxxxxxxxx> Mon, 11 Feb 2013 16:04:00 -0500
=== added file 'debian/compat'
--- debian/compat 1970-01-01 00:00:00 +0000
+++ debian/compat 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+9
=== added file 'debian/control'
--- debian/control 1970-01-01 00:00:00 +0000
+++ debian/control 2015-06-28 23:49:12 +0000
@@ -0,0 +1,41 @@
+Source: ubuntu-weather-app
+Priority: extra
+Maintainer: Ubuntu App Cats <ubuntu-touch-coreapps@xxxxxxxxxxxxxxxxxxx>
+Build-Depends: cmake,
+ debhelper (>= 9),
+ gettext,
+ intltool,
+ python3 | python-all | python3-dev | python3-all-dev,
+ python3-minimal,
+ qtbase5-dev,
+ qtbase5-dev-tools,
+ qtdeclarative5-dev,
+ qtdeclarative5-dev-tools,
+ qtdeclarative5-ubuntu-ui-toolkit-plugin,
+Standards-Version: 3.9.6
+Section: misc
+Homepage: https://launchpad.net/ubuntu-weather-app
+Vcs-Bzr: https://code.launchpad.net/~ubuntu-weather-dev/ubuntu-weather-app/trunk
+
+Package: ubuntu-weather-app
+Architecture: all
+Depends: qmlscene,
+ qtdeclarative5-localstorage-plugin,
+ qml-module-qt-labs-settings,
+ qtdeclarative5-qtquick2-plugin,
+ qtdeclarative5-ubuntu-ui-toolkit-plugin | qt-components-ubuntu,
+ suru-icon-theme | ubuntu-mobile-icons,
+ ${misc:Depends}
+Description: Weather application
+ Core Weather application
+
+Package: ubuntu-weather-app-autopilot
+Architecture: all
+Depends: libautopilot-qt (>= 1.4),
+ libqt5test5,
+ libqt5widgets5,
+ ubuntu-ui-toolkit-autopilot,
+ ubuntu-weather-app (>= ${source:Version}),
+ ${misc:Depends}
+Description: Test package for the weather app
+ Autopilot tests for the weather app package
=== added file 'debian/copyright'
--- debian/copyright 1970-01-01 00:00:00 +0000
+++ debian/copyright 2015-06-28 23:49:12 +0000
@@ -0,0 +1,54 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: ubuntu-weather-app
+Source: https://launchpad.net/ubuntu-weather-app
+
+Files: *
+Copyright: 2013 Canonical Ltd.
+ 2013 Jakub <jakub@xxxxxxxxxxx>
+ 2013 Martin Borho <martin@xxxxxxxxx>
+ 2013 Nekhelesh Ramananthan <krnekhelesh@xxxxxxxxx>
+ 2013 Omer Akram <om26er@xxxxxxxxxx>
+ 2013 Raúl Yeguas <neokore@xxxxxxxxx>
+ 2013 Riccardo Padovani <rpadovani@xxxxxxxxxxxxx>
+ 2013 Zonov Roman <roman2861@xxxxxxxxx>
+ 2015 Andrew Hayzen <ahayzen@xxxxxxxxx>
+ 2015 Victor Thompson <victor.thompson@xxxxxxxxx>
+License: GPL-3
+
+Files: debian/*
+Copyright: 2013 Canonical Ltd.
+License: LGPL-3
+
+License: GPL-3
+ This package is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License can be found in "/usr/share/common-licenses/GPL-3".
+
+License: LGPL-3
+ This package is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU Lesser General
+ Public License can be found in "/usr/share/common-licenses/LGPL-3".
=== added file 'debian/rules'
--- debian/rules 1970-01-01 00:00:00 +0000
+++ debian/rules 2015-06-28 23:49:12 +0000
@@ -0,0 +1,14 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@
+
+override_dh_auto_configure:
+ dh_auto_configure -- -DCLICK_MODE=OFF
+
+override_dh_install:
+ dh_install --list-missing
=== added directory 'debian/source'
=== added file 'debian/source/format'
--- debian/source/format 1970-01-01 00:00:00 +0000
+++ debian/source/format 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+1.0
=== added file 'debian/ubuntu-weather-app-autopilot.install'
--- debian/ubuntu-weather-app-autopilot.install 1970-01-01 00:00:00 +0000
+++ debian/ubuntu-weather-app-autopilot.install 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+usr/lib
=== added file 'debian/ubuntu-weather-app.install'
--- debian/ubuntu-weather-app.install 1970-01-01 00:00:00 +0000
+++ debian/ubuntu-weather-app.install 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+usr/share
=== added file 'manifest.json.in'
--- manifest.json.in 1970-01-01 00:00:00 +0000
+++ manifest.json.in 2015-06-28 23:49:12 +0000
@@ -0,0 +1,23 @@
+{
+ "architecture": "all",
+ "description": "A weather forecast application for Ubuntu with support for multiple online weather data sources",
+ "framework": "ubuntu-sdk-15.04-qml",
+ "hooks": {
+ "weather": {
+ "apparmor": "ubuntu-weather-app.apparmor",
+ "desktop": "@CMAKE_INSTALL_DATADIR@/applications/ubuntu-weather-app.desktop"
+ }
+ },
+ "icon": "@ICON@",
+ "maintainer": "Ubuntu App Cats <ubuntu-touch-coreapps@xxxxxxxxxxxxxxxxxxx>",
+ "name": "@PROJECT_NAME@",
+ "title": "Weather",
+ "version": "3.0.@BZR_REVNO@",
+ "x-source": {
+ "vcs-bzr": "@BZR_SOURCE@",
+ "vcs-bzr-revno": "@BZR_REVNO@"
+ },
+ "x-test": {
+ "autopilot": "@AUTOPILOT_DIR@"
+ }
+}
=== added directory 'po'
=== renamed directory 'po' => 'po.moved'
=== added file 'po/CMakeLists.txt'
--- po/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ po/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,33 @@
+include(FindGettext)
+find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
+
+set(DOMAIN ${PROJECT_NAME})
+set(POT_FILE ${DOMAIN}.pot)
+file(GLOB PO_FILES *.po)
+
+# Creates the .pot file containing the translations template
+add_custom_target(${POT_FILE} ALL
+ COMMENT "Generating translation template"
+ COMMAND ${INTLTOOL_EXTRACT} --update --type=gettext/ini
+ --srcdir=${CMAKE_SOURCE_DIR} ${DESKTOP_FILE}.in.in
+ COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} -o ${POT_FILE}
+ -D ${CMAKE_CURRENT_SOURCE_DIR}
+ -D ${CMAKE_CURRENT_BINARY_DIR}
+ --from-code=UTF-8
+ --c++ --qt --add-comments=TRANSLATORS
+ --keyword=tr --keyword=tr:1,2 --keyword=N_
+ --package-name='${APP_HARDCODE}'
+ --copyright-holder='Canonical Ltd.'
+ ${I18N_SRC_FILES}
+ COMMAND ${CMAKE_COMMAND} -E copy ${POT_FILE} ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Builds the binary translations catalog for each language
+# it finds source translations (*.po) for
+foreach(PO_FILE ${PO_FILES})
+ get_filename_component(LANG ${PO_FILE} NAME_WE)
+ gettext_process_po_files(${LANG} ALL PO_FILES ${PO_FILE})
+ set(INSTALL_DIR ${CMAKE_INSTALL_LOCALEDIR}/${LANG}/LC_MESSAGES)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.gmo
+ DESTINATION ${INSTALL_DIR}
+ RENAME ${DOMAIN}.mo)
+endforeach(PO_FILE)
=== added file 'po/com.ubuntu.weather.pot'
--- po/com.ubuntu.weather.pot 1970-01-01 00:00:00 +0000
+++ po/com.ubuntu.weather.pot 2015-06-28 23:49:12 +0000
@@ -0,0 +1,218 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Canonical Ltd.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: ubuntu-weather-app\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-06-21 20:10+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@xxxxxx>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+#: ../app/components/DayDelegate.qml:193
+msgid "Chance of rain"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:202
+msgid "Winds"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:209
+msgid "UV Index"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:214
+msgid "Pollen"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:220
+msgid "Humidity"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:226
+msgid "Sunrise"
+msgstr ""
+
+#: ../app/components/DayDelegate.qml:232
+msgid "Sunset"
+msgstr ""
+
+#: ../app/components/HomeTempInfo.qml:38
+msgid "Today"
+msgstr ""
+
+#: ../app/components/ListItemActions/Remove.qml:26
+msgid "Remove"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:27
+msgid "Select All"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:39
+msgid "Delete"
+msgstr ""
+
+#: ../app/components/MultiSelectHeadState.qml:51
+msgid "Cancel selection"
+msgstr ""
+
+#: ../app/ubuntu-weather-app.qml:167
+msgid "Searching for current location..."
+msgstr ""
+
+#: ../app/ubuntu-weather-app.qml:176
+msgid "Add a manual location"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:29
+msgid "Select a city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:45 ../app/ui/AddLocationPage.qml:66
+msgid "Back"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:51
+msgid "City"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:93
+msgid "Search city"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:278
+msgid "No city found"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:291
+msgid "Couldn't load weather data, please try later again!"
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:301
+msgid "Location already added."
+msgstr ""
+
+#: ../app/ui/AddLocationPage.qml:304
+msgid "OK"
+msgstr ""
+
+#: ../app/ui/HomePage.qml:31 ../app/ui/LocationsPage.qml:30
+msgid "Locations"
+msgstr ""
+
+#: ../app/ui/LocationsPage.qml:104
+msgid "Current Location"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:24
+msgid "Settings"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:38 ../app/ui/settings/UnitsPage.qml:24
+msgid "Units"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:43 ../app/ui/settings/DataProviderPage.qml:24
+msgid "Data Provider"
+msgstr ""
+
+#: ../app/ui/SettingsPage.qml:48 ../app/ui/settings/RefreshIntervalPage.qml:24
+msgid "Refresh Interval"
+msgstr ""
+
+#: ../app/ui/settings/DataProviderPage.qml:37
+msgid "Provider"
+msgstr ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:30
+#: ../app/ui/settings/RefreshIntervalPage.qml:31
+#: ../app/ui/settings/RefreshIntervalPage.qml:32
+#: ../app/ui/settings/RefreshIntervalPage.qml:33
+#: ../app/ui/settings/RefreshIntervalPage.qml:43
+#, qt-format
+msgid "%1 minute"
+msgid_plural "%1 minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../app/ui/settings/RefreshIntervalPage.qml:42
+msgid "Interval"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of temperature in Celcius and are shown in the settings page.
+#. Only the abbreviated form of Celcius should be used.
+#: ../app/ui/settings/UnitsPage.qml:41 ../app/ui/settings/UnitsPage.qml:92
+msgid "°C"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of temperature in Fahrenheit and are shown in the settings page.
+#. Only the abbreviated form of Fahrenheit should be used.
+#: ../app/ui/settings/UnitsPage.qml:46 ../app/ui/settings/UnitsPage.qml:93
+msgid "°F"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of precipitation in millimeters and are shown in the settings page.
+#. Only the abbreviated form of millimeters should be used.
+#: ../app/ui/settings/UnitsPage.qml:57 ../app/ui/settings/UnitsPage.qml:112
+msgid "mm"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of precipitation in inches and are shown in the settings page.
+#. Only the abbreviated form of inches should be used.
+#: ../app/ui/settings/UnitsPage.qml:62 ../app/ui/settings/UnitsPage.qml:113
+msgid "in"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of wind speed in kilometers per hour and are shown in the settings page.
+#. Only the abbreviated form of kilometers per hour should be used.
+#: ../app/ui/settings/UnitsPage.qml:73 ../app/ui/settings/UnitsPage.qml:132
+msgid "kph"
+msgstr ""
+
+#. TRANSLATORS: The strings are standard measurement units
+#. of wind speed in miles per hour and are shown in the settings page.
+#. Only the abbreviated form of miles per hour should be used.
+#: ../app/ui/settings/UnitsPage.qml:78 ../app/ui/settings/UnitsPage.qml:133
+msgid "mph"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:91
+msgid "Temperature"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:111
+msgid "Precipitation"
+msgstr ""
+
+#: ../app/ui/settings/UnitsPage.qml:131
+msgid "Wind Speed"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:1
+msgid "Weather"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:2
+msgid ""
+"A weather forecast application for Ubuntu with support for multiple online "
+"weather data sources"
+msgstr ""
+
+#: ubuntu-weather-app.desktop.in.in.h:3
+msgid "weather;forecast;twc;openweathermap;the weather channel;"
+msgstr ""
=== added directory 'tests'
=== renamed directory 'tests' => 'tests.moved'
=== added file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,1 @@
+add_subdirectory(autopilot)
=== added directory 'tests/autopilot'
=== added file 'tests/autopilot/CMakeLists.txt'
--- tests/autopilot/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/autopilot/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,10 @@
+if(INSTALL_TESTS)
+execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
+ OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+install(DIRECTORY ${AUTOPILOT_DIR}
+ DESTINATION ${PYTHON_PACKAGE_DIR}
+ )
+endif(INSTALL_TESTS)
+
+add_subdirectory(ubuntu_weather_app)
=== added directory 'tests/autopilot/ubuntu_weather_app'
=== added file 'tests/autopilot/ubuntu_weather_app/CMakeLists.txt'
--- tests/autopilot/ubuntu_weather_app/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,8 @@
+add_subdirectory(tests)
+
+# make the emulator files visible on qtcreator
+file(GLOB PYTHON_EMULATOR_FILES
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ *.py)
+
+add_custom_target(ubuntu-weather-app_PYTHONEMULATORFiles ALL SOURCES ${PYTHON_EMULATOR_FILES})
=== added file 'tests/autopilot/ubuntu_weather_app/__init__.py'
--- tests/autopilot/ubuntu_weather_app/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/__init__.py 2015-06-28 23:49:12 +0000
@@ -0,0 +1,59 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+# Copyright 2013, 2014, 2015 Canonical
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+
+"""ubuntu-weather-app tests and emulators - top level package."""
+from ubuntuuitoolkit import MainView, UbuntuUIToolkitCustomProxyObjectBase
+
+
+class UbuntuWeatherAppException(Exception):
+ """Exception raised when there's an error in the Weather App."""
+
+
+def click_object(func):
+ """Wrapper which clicks the returned object"""
+ def func_wrapper(self, *args, **kwargs):
+ return self.pointing_device.click_object(func(self, *args, **kwargs))
+
+ return func_wrapper
+
+
+class UbuntuWeatherApp(object):
+ """Autopilot helper object for the Weather application."""
+
+ def __init__(self, app_proxy):
+ self.app = app_proxy
+ self.main_view = self.app.wait_select_single(MainView)
+
+ def get_add_location_page(self):
+ return self.main_view.wait_select_single(
+ AddLocationPage, objectName="addLocationPage")
+
+ def click_add_location_button(self):
+ add_location_button = self.main_view.wait_select_single(
+ "Button", objectName="emptyStateButton")
+ self.app.pointing_device.click_object(add_location_button)
+
+
+class Page(UbuntuUIToolkitCustomProxyObjectBase):
+ """Autopilot helper for Pages."""
+ def __init__(self, *args):
+ super(Page, self).__init__(*args)
+
+
+class AddLocationPage(Page):
+ """Autopilot helper for AddLocationPage."""
+ def __init__(self, *args):
+ super(AddLocationPage, self).__init__(*args)
+
+
+class MainView(MainView):
+ """Autopilot custom proxy object for the MainView."""
+ retry_delay = 0.2
+
+ def __init__(self, *args):
+ super(MainView, self).__init__(*args)
+ self.visible.wait_for(True)
=== added directory 'tests/autopilot/ubuntu_weather_app/tests'
=== added file 'tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt'
--- tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/CMakeLists.txt 2015-06-28 23:49:12 +0000
@@ -0,0 +1,6 @@
+# make the test files visible on qtcreator
+file(GLOB PYTHON_TEST_FILES
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ *.py)
+
+add_custom_target(ubuntu-weather-app_PYTHONTESTFiles ALL SOURCES ${PYTHON_TEST_FILES})
=== added file 'tests/autopilot/ubuntu_weather_app/tests/__init__.py'
--- tests/autopilot/ubuntu_weather_app/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/__init__.py 2015-06-28 23:49:12 +0000
@@ -0,0 +1,150 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+#
+# Copyright (C) 2013, 2014, 2015 Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Weather app autopilot tests."""
+
+import os
+import os.path
+import shutil
+import logging
+
+import fixtures
+from ubuntu_weather_app import UbuntuWeatherApp
+
+from autopilot import logging as autopilot_logging
+from autopilot.testcase import AutopilotTestCase
+
+import ubuntuuitoolkit
+from ubuntuuitoolkit import base
+
+logger = logging.getLogger(__name__)
+
+
+class BaseTestCaseWithPatchedHome(AutopilotTestCase):
+
+ """A common test case class that provides several useful methods for
+ ubuntu-weather-app tests.
+
+ """
+
+ working_dir = os.getcwd()
+ local_location_dir = os.path.dirname(os.path.dirname(working_dir))
+ local_location = local_location_dir + "/app/ubuntu-weather-app.qml"
+ installed_location = "/usr/share/ubuntu-weather-app/app/" \
+ "ubuntu-weather-app.qml"
+
+ def get_launcher_method_and_type(self):
+ if os.path.exists(self.local_location):
+ launch = self.launch_test_local
+ test_type = 'local'
+ elif os.path.exists(self.installed_location):
+ launch = self.launch_test_installed
+ test_type = 'deb'
+ else:
+ launch = self.launch_test_click
+ test_type = 'click'
+ return launch, test_type
+
+ def setUp(self):
+ super(BaseTestCaseWithPatchedHome, self).setUp()
+ self.launcher, self.test_type = self.get_launcher_method_and_type()
+ self.home_dir = self._patch_home()
+
+ @autopilot_logging.log_action(logger.info)
+ def launch_test_local(self):
+ return self.launch_test_application(
+ base.get_qmlscene_launch_command(),
+ self.local_location,
+ "debug",
+ app_type='qt',
+ emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+ @autopilot_logging.log_action(logger.info)
+ def launch_test_installed(self):
+ return self.launch_test_application(
+ base.get_qmlscene_launch_command(),
+ self.installed_location,
+ "debug",
+ app_type='qt',
+ emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+ @autopilot_logging.log_action(logger.info)
+ def launch_test_click(self):
+ return self.launch_click_package(
+ "com.ubuntu.weather",
+ emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
+
+ def _copy_xauthority_file(self, directory):
+ """ Copy .Xauthority file to directory, if it exists in /home
+ """
+ # If running under xvfb, as jenkins does,
+ # xsession will fail to start without xauthority file
+ # Thus if the Xauthority file is in the home directory
+ # make sure we copy it to our temp home directory
+
+ xauth = os.path.expanduser(os.path.join(os.environ.get('HOME'),
+ '.Xauthority'))
+ if os.path.isfile(xauth):
+ logger.debug("Copying .Xauthority to %s" % directory)
+ shutil.copyfile(
+ os.path.expanduser(os.path.join(os.environ.get('HOME'),
+ '.Xauthority')),
+ os.path.join(directory, '.Xauthority'))
+
+ def _patch_home(self):
+ """ mock /home for testing purposes to preserve user data
+ """
+
+ # if running on non-phablet device,
+ # run in temp folder to avoid mucking up home
+ # bug 1316746
+ # bug 1376423
+ if self.test_type is 'click':
+ # just use home for now on devices
+ temp_dir = os.environ.get('HOME')
+
+ # before each test, remove the app's databases
+ local_dir = os.path.join(temp_dir,
+ '.local/share/com.ubuntu.weather')
+
+ if (os.path.exists(local_dir)):
+ shutil.rmtree(local_dir)
+
+ local_dir = os.path.join(temp_dir, '.config/com.ubuntu.weather')
+
+ if (os.path.exists(local_dir)):
+ shutil.rmtree(local_dir)
+ else:
+ temp_dir_fixture = fixtures.TempDir()
+ self.useFixture(temp_dir_fixture)
+ temp_dir = temp_dir_fixture.path
+
+ # before we set fixture, copy xauthority if needed
+ self._copy_xauthority_file(temp_dir)
+ self.useFixture(fixtures.EnvironmentVariable('HOME',
+ newvalue=temp_dir))
+
+ logger.debug("Patched home to fake home directory %s" % temp_dir)
+ return temp_dir
+
+
+class UbuntuWeatherAppTestCase(BaseTestCaseWithPatchedHome):
+
+ """Base test case that launches the ubuntu-weather-app."""
+
+ def setUp(self):
+ super(UbuntuWeatherAppTestCase, self).setUp()
+ self.app = UbuntuWeatherApp(self.launcher())
=== added file 'tests/autopilot/ubuntu_weather_app/tests/test_weather.py'
--- tests/autopilot/ubuntu_weather_app/tests/test_weather.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_weather_app/tests/test_weather.py 2015-06-28 23:49:12 +0000
@@ -0,0 +1,35 @@
+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
+# Copyright 2013, 2014, 2015 Canonical
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+
+"""Ubuntu Weather app autopilot tests."""
+
+from __future__ import absolute_import
+
+import logging
+from autopilot.matchers import Eventually
+from testtools.matchers import Equals
+
+
+from ubuntu_weather_app.tests import UbuntuWeatherAppTestCase
+
+logger = logging.getLogger(__name__)
+
+
+class TestMainWindow(UbuntuWeatherAppTestCase):
+
+ def setUp(self):
+ super(TestMainWindow, self).setUp()
+
+ def test_add_location_button(self):
+ """ tests that the add location page is shown after the Add Location
+ button is clicked """
+
+ self.app.click_add_location_button()
+
+ add_location_page = self.app.get_add_location_page()
+
+ self.assertThat(add_location_page.visible, Eventually(Equals(True)))
=== added file 'ubuntu-weather-app.apparmor'
--- ubuntu-weather-app.apparmor 1970-01-01 00:00:00 +0000
+++ ubuntu-weather-app.apparmor 2015-06-28 23:49:12 +0000
@@ -0,0 +1,8 @@
+{
+ "policy_groups": [
+ "location",
+ "networking",
+ "sensors"
+ ],
+ "policy_version": 1.3
+}
=== added file 'ubuntu-weather-app.desktop.in.in'
--- ubuntu-weather-app.desktop.in.in 1970-01-01 00:00:00 +0000
+++ ubuntu-weather-app.desktop.in.in 2015-06-28 23:49:12 +0000
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Terminal=false
+Exec=@EXEC@
+Icon=@ICON@
+_Name=Weather
+_Comment=A weather forecast application for Ubuntu with support for multiple online weather data sources
+_Keywords=weather;forecast;twc;openweathermap;the weather channel;
+X-Ubuntu-Touch=true
+X-Ubuntu-Default-Department-ID=accessories
+X-Ubuntu-Splash-Color=#F5F5F5